Commits

Michael Foord committed 520b3c2

Support more common package layouts for test discovery.

  • Participants
  • Parent commits 4c3add0
  • Branches plugins

Comments (0)

Files changed (2)

File description.txt

 
 Output by the TextTestRunner and the TextTestResult goes through the 'message' event. To support this the ``_WritelnDecorator`` takes an optional 'runner' argument in the constructor and has a new `write` method. If the 'runner' argument is used then all calls to `write` and `writeln` are sent on to the ``runner.message`` method (which writes directly to the underlying stream).
 
-TextTestResult has a new addReport method used by the TextTestRunner for test reporting. This adds report objects to the ``.reports`` attribute and also delegates to the standard ``addError`` / ``addFailure`` etc methods for tests with standard outcomes. For non-standard outcomes ``addReport`` reports whatever information is specified in the 
+TextTestResult has a new addReport method used by the TextTestRunner for test reporting. This adds report objects to the ``.reports`` attribute and also delegates to the standard ``addError`` / ``addFailure`` etc methods for tests with standard outcomes. For non-standard outcomes ``addReport`` reports whatever information is specified in the XXXXX
+
+Test discovery has been improved. The initial implementation in Python 2.7 was very conservative. The new implementation supports many more common package layouts. It supports the package code being in a 'src' or a 'lib' subdirectory (that isn't itself a package). It also supports tests being in any top level directory that isn't a package, so long as the directory name contains 'test' in it.
 
 TestLoader has a new attribute ``DEFAULT_PATTERN``. This is so that the
 regex matching plugin can change the default pattern used for test discovery

File unittest2/loader.py

         implicit_start = False
         if start_dir is None:
             start_dir = '.'
+            implicit_start = True
         if pattern is None:
             pattern = DEFAULT_PATTERN
         set_implicit_top = False
         if is_not_importable:
             raise ImportError('Start directory is not importable: %r' % start_dir)
 
+        def check_dir(the_dir):
+            if not implicit_start:
+                return
+            full_path =  os.path.join(start_dir, the_dir)
+            if (os.path.isdir(full_path) and not 
+                os.path.isfile(os.path.join(full_path, '__init__.py'))):
+                sys.path.append(full_path)
+                return full_path
+
+        src_dir = check_dir('src')
+        lib_dir = check_dir('lib')
         tests = list(self._find_tests(start_dir, pattern))
+        real_top_level = self._top_level_dir
+        if src_dir is not None:
+            self._top_level_dir = src_dir
+            tests.extend(list(self._find_tests(src_dir, pattern)))
+        if lib_dir is not None:
+            self._top_level_dir = lib_dir
+            tests.extend(list(self._find_tests(lib_dir, pattern)))
+        if implicit_start:
+            for entry in os.listdir(start_dir):
+                if not 'test' in entry.lower():
+                    continue
+                full = check_dir(entry)
+                if full is None:
+                    continue
+                self._top_level_dir = full
+                tests.extend(list(self._find_tests(full, pattern)))
+        
+        self._top_level_dir = real_top_level
         return self.suiteClass(tests)
 
     def _get_name_from_path(self, path):