mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 13:41:24 +00:00 
			
		
		
		
	Merged revisions 72570,72582-72583,73027,73049,73071,73151,73247 via svnmerge from
svn+ssh://pythondev@svn.python.org/python/trunk ........ r72570 | michael.foord | 2009-05-11 12:59:43 -0500 (Mon, 11 May 2009) | 7 lines Adds a verbosity keyword argument to unittest.main plus a minor fix allowing you to specify test modules / classes from the command line. Closes issue 5995. Michael Foord ........ r72582 | michael.foord | 2009-05-12 05:46:23 -0500 (Tue, 12 May 2009) | 1 line Fix to restore command line behaviour for test modules using unittest.main(). Regression caused by issue 5995. Michael ........ r72583 | michael.foord | 2009-05-12 05:49:13 -0500 (Tue, 12 May 2009) | 1 line Better fix for modules using unittest.main(). Fixes regression caused by commit for issue 5995. Michael Foord ........ r73027 | michael.foord | 2009-05-29 15:33:46 -0500 (Fri, 29 May 2009) | 1 line Add test discovery to unittest. Issue 6001. ........ r73049 | georg.brandl | 2009-05-30 05:45:40 -0500 (Sat, 30 May 2009) | 1 line Rewrap a few long lines. ........ r73071 | georg.brandl | 2009-05-31 09:15:25 -0500 (Sun, 31 May 2009) | 1 line Fix markup. ........ r73151 | michael.foord | 2009-06-02 13:08:27 -0500 (Tue, 02 Jun 2009) | 1 line Restore default testRunner argument in unittest.main to None. Issue 6177 ........ r73247 | michael.foord | 2009-06-05 09:14:34 -0500 (Fri, 05 Jun 2009) | 1 line Fix unittest discovery tests for Windows. Issue 6199 ........
This commit is contained in:
		
							parent
							
								
									f7a6b508ce
								
							
						
					
					
						commit
						d2397753ee
					
				
					 3 changed files with 709 additions and 55 deletions
				
			
		
							
								
								
									
										195
									
								
								Lib/unittest.py
									
										
									
									
									
								
							
							
						
						
									
										195
									
								
								Lib/unittest.py
									
										
									
									
									
								
							|  | @ -56,6 +56,9 @@ def testMultiply(self): | |||
| import types | ||||
| import warnings | ||||
| 
 | ||||
| from fnmatch import fnmatch | ||||
| 
 | ||||
| 
 | ||||
| ############################################################################## | ||||
| # Exported classes and functions | ||||
| ############################################################################## | ||||
|  | @ -1228,6 +1231,7 @@ class TestLoader(object): | |||
|     testMethodPrefix = 'test' | ||||
|     sortTestMethodsUsing = staticmethod(three_way_cmp) | ||||
|     suiteClass = TestSuite | ||||
|     _top_level_dir = None | ||||
| 
 | ||||
|     def loadTestsFromTestCase(self, testCaseClass): | ||||
|         """Return a suite of all tests cases contained in testCaseClass""" | ||||
|  | @ -1240,13 +1244,17 @@ def loadTestsFromTestCase(self, testCaseClass): | |||
|         suite = self.suiteClass(map(testCaseClass, testCaseNames)) | ||||
|         return suite | ||||
| 
 | ||||
|     def loadTestsFromModule(self, module): | ||||
|     def loadTestsFromModule(self, module, use_load_tests=True): | ||||
|         """Return a suite of all tests cases contained in the given module""" | ||||
|         tests = [] | ||||
|         for name in dir(module): | ||||
|             obj = getattr(module, name) | ||||
|             if isinstance(obj, type) and issubclass(obj, TestCase): | ||||
|                 tests.append(self.loadTestsFromTestCase(obj)) | ||||
| 
 | ||||
|         load_tests = getattr(module, 'load_tests', None) | ||||
|         if use_load_tests and load_tests is not None: | ||||
|             return load_tests(self, tests, None) | ||||
|         return self.suiteClass(tests) | ||||
| 
 | ||||
|     def loadTestsFromName(self, name, module=None): | ||||
|  | @ -1320,7 +1328,97 @@ def isTestMethod(attrname, testCaseClass=testCaseClass, | |||
|             testFnNames.sort(key=CmpToKey(self.sortTestMethodsUsing)) | ||||
|         return testFnNames | ||||
| 
 | ||||
|     def discover(self, start_dir, pattern='test*.py', top_level_dir=None): | ||||
|         """Find and return all test modules from the specified start | ||||
|         directory, recursing into subdirectories to find them. Only test files | ||||
|         that match the pattern will be loaded. (Using shell style pattern | ||||
|         matching.) | ||||
| 
 | ||||
|         All test modules must be importable from the top level of the project. | ||||
|         If the start directory is not the top level directory then the top | ||||
|         level directory must be specified separately. | ||||
| 
 | ||||
|         If a test package name (directory with '__init__.py') matches the | ||||
|         pattern then the package will be checked for a 'load_tests' function. If | ||||
|         this exists then it will be called with loader, tests, pattern. | ||||
| 
 | ||||
|         If load_tests exists then discovery does  *not* recurse into the package, | ||||
|         load_tests is responsible for loading all tests in the package. | ||||
| 
 | ||||
|         The pattern is deliberately not stored as a loader attribute so that | ||||
|         packages can continue discovery themselves. top_level_dir is stored so | ||||
|         load_tests does not need to pass this argument in to loader.discover(). | ||||
|         """ | ||||
|         if top_level_dir is None and self._top_level_dir is not None: | ||||
|             # make top_level_dir optional if called from load_tests in a package | ||||
|             top_level_dir = self._top_level_dir | ||||
|         elif top_level_dir is None: | ||||
|             top_level_dir = start_dir | ||||
| 
 | ||||
|         top_level_dir = os.path.abspath(os.path.normpath(top_level_dir)) | ||||
|         start_dir = os.path.abspath(os.path.normpath(start_dir)) | ||||
| 
 | ||||
|         if not top_level_dir in sys.path: | ||||
|             # all test modules must be importable from the top level directory | ||||
|             sys.path.append(top_level_dir) | ||||
|         self._top_level_dir = top_level_dir | ||||
| 
 | ||||
|         if start_dir != top_level_dir and not os.path.isfile(os.path.join(start_dir, '__init__.py')): | ||||
|             # what about __init__.pyc or pyo (etc) | ||||
|             raise ImportError('Start directory is not importable: %r' % start_dir) | ||||
| 
 | ||||
|         tests = list(self._find_tests(start_dir, pattern)) | ||||
|         return self.suiteClass(tests) | ||||
| 
 | ||||
| 
 | ||||
|     def _get_module_from_path(self, path): | ||||
|         """Load a module from a path relative to the top-level directory | ||||
|         of a project. Used by discovery.""" | ||||
|         path = os.path.splitext(os.path.normpath(path))[0] | ||||
| 
 | ||||
|         relpath = os.path.relpath(path, self._top_level_dir) | ||||
|         assert not os.path.isabs(relpath), "Path must be within the project" | ||||
|         assert not relpath.startswith('..'), "Path must be within the project" | ||||
| 
 | ||||
|         name = relpath.replace(os.path.sep, '.') | ||||
|         __import__(name) | ||||
|         return sys.modules[name] | ||||
| 
 | ||||
|     def _find_tests(self, start_dir, pattern): | ||||
|         """Used by discovery. Yields test suites it loads.""" | ||||
|         paths = os.listdir(start_dir) | ||||
| 
 | ||||
|         for path in paths: | ||||
|             full_path = os.path.join(start_dir, path) | ||||
|             # what about __init__.pyc or pyo (etc) | ||||
|             # we would need to avoid loading the same tests multiple times | ||||
|             # from '.py', '.pyc' *and* '.pyo' | ||||
|             if os.path.isfile(full_path) and path.lower().endswith('.py'): | ||||
|                 if fnmatch(path, pattern): | ||||
|                     # if the test file matches, load it | ||||
|                     module = self._get_module_from_path(full_path) | ||||
|                     yield self.loadTestsFromModule(module) | ||||
|             elif os.path.isdir(full_path): | ||||
|                 if not os.path.isfile(os.path.join(full_path, '__init__.py')): | ||||
|                     continue | ||||
| 
 | ||||
|                 load_tests = None | ||||
|                 tests = None | ||||
|                 if fnmatch(path, pattern): | ||||
|                     # only check load_tests if the package directory itself matches the filter | ||||
|                     package = self._get_module_from_path(full_path) | ||||
|                     load_tests = getattr(package, 'load_tests', None) | ||||
|                     tests = self.loadTestsFromModule(package, use_load_tests=False) | ||||
| 
 | ||||
|                 if load_tests is None: | ||||
|                     if tests is not None: | ||||
|                         # tests loaded from package file | ||||
|                         yield tests | ||||
|                     # recurse into the package | ||||
|                     for test in self._find_tests(full_path, pattern): | ||||
|                         yield test | ||||
|                 else: | ||||
|                     yield load_tests(self, tests, pattern) | ||||
| 
 | ||||
| defaultTestLoader = TestLoader() | ||||
| 
 | ||||
|  | @ -1525,11 +1623,37 @@ def run(self, test): | |||
| # Facilities for running tests from the command line | ||||
| ############################################################################## | ||||
| 
 | ||||
| class TestProgram(object): | ||||
|     """A command-line program that runs a set of tests; this is primarily | ||||
|        for making test modules conveniently executable. | ||||
|     """ | ||||
|     USAGE = """\ | ||||
| USAGE_AS_MAIN = """\ | ||||
| Usage: %(progName)s [options] [tests] | ||||
| 
 | ||||
| Options: | ||||
|   -h, --help       Show this message | ||||
|   -v, --verbose    Verbose output | ||||
|   -q, --quiet      Minimal output | ||||
| 
 | ||||
| Examples: | ||||
|   %(progName)s test_module                       - run tests from test_module | ||||
|   %(progName)s test_module.TestClass             - run tests from | ||||
|                                                    test_module.TestClass | ||||
|   %(progName)s test_module.TestClass.test_method - run specified test method | ||||
| 
 | ||||
| [tests] can be a list of any number of test modules, classes and test | ||||
| methods. | ||||
| 
 | ||||
| Alternative Usage: %(progName)s discover [options] | ||||
| 
 | ||||
| Options: | ||||
|   -v, --verbose    Verbose output | ||||
|   -s directory     Directory to start discovery ('.' default) | ||||
|   -p pattern       Pattern to match test files ('test*.py' default) | ||||
|   -t directory     Top level directory of project (default to | ||||
|                    start directory) | ||||
| 
 | ||||
| For test discovery all test modules must be importable from the top | ||||
| level directory of the project. | ||||
| """ | ||||
| 
 | ||||
| USAGE_FROM_MODULE = """\ | ||||
| Usage: %(progName)s [options] [test] [...] | ||||
| 
 | ||||
| Options: | ||||
|  | @ -1544,9 +1668,24 @@ class TestProgram(object): | |||
|   %(progName)s MyTestCase                    - run all 'test*' test methods | ||||
|                                                in MyTestCase | ||||
| """ | ||||
| 
 | ||||
| if __name__ == '__main__': | ||||
|     USAGE = USAGE_AS_MAIN | ||||
| else: | ||||
|     USAGE = USAGE_FROM_MODULE | ||||
| 
 | ||||
| 
 | ||||
| class TestProgram(object): | ||||
|     """A command-line program that runs a set of tests; this is primarily | ||||
|        for making test modules conveniently executable. | ||||
|     """ | ||||
|     USAGE = USAGE | ||||
|     def __init__(self, module='__main__', defaultTest=None, | ||||
|                  argv=None, testRunner=TextTestRunner, | ||||
|                  testLoader=defaultTestLoader, exit=True): | ||||
|                  argv=None, testRunner=None, | ||||
|                  testLoader=defaultTestLoader, exit=True, | ||||
|                  verbosity=1): | ||||
|         if testRunner is None: | ||||
|             testRunner = TextTestRunner | ||||
|         if isinstance(module, str): | ||||
|             self.module = __import__(module) | ||||
|             for part in module.split('.')[1:]: | ||||
|  | @ -1557,7 +1696,7 @@ def __init__(self, module='__main__', defaultTest=None, | |||
|             argv = sys.argv | ||||
| 
 | ||||
|         self.exit = exit | ||||
|         self.verbosity = 1 | ||||
|         self.verbosity = verbosity | ||||
|         self.defaultTest = defaultTest | ||||
|         self.testRunner = testRunner | ||||
|         self.testLoader = testLoader | ||||
|  | @ -1572,6 +1711,10 @@ def usageExit(self, msg=None): | |||
|         sys.exit(2) | ||||
| 
 | ||||
|     def parseArgs(self, argv): | ||||
|         if len(argv) > 1 and argv[1].lower() == 'discover': | ||||
|             self._do_discovery(argv[2:]) | ||||
|             return | ||||
| 
 | ||||
|         import getopt | ||||
|         long_opts = ['help','verbose','quiet'] | ||||
|         try: | ||||
|  | @ -1588,6 +1731,9 @@ def parseArgs(self, argv): | |||
|                 return | ||||
|             if len(args) > 0: | ||||
|                 self.testNames = args | ||||
|                 if __name__ == '__main__': | ||||
|                     # to support python -m unittest ... | ||||
|                     self.module = None | ||||
|             else: | ||||
|                 self.testNames = (self.defaultTest,) | ||||
|             self.createTests() | ||||
|  | @ -1598,6 +1744,36 @@ def createTests(self): | |||
|         self.test = self.testLoader.loadTestsFromNames(self.testNames, | ||||
|                                                        self.module) | ||||
| 
 | ||||
|     def _do_discovery(self, argv, Loader=TestLoader): | ||||
|         # handle command line args for test discovery | ||||
|         import optparse | ||||
|         parser = optparse.OptionParser() | ||||
|         parser.add_option('-v', '--verbose', dest='verbose', default=False, | ||||
|                           help='Verbose output', action='store_true') | ||||
|         parser.add_option('-s', '--start-directory', dest='start', default='.', | ||||
|                           help="Directory to start discovery ('.' default)") | ||||
|         parser.add_option('-p', '--pattern', dest='pattern', default='test*.py', | ||||
|                           help="Pattern to match tests ('test*.py' default)") | ||||
|         parser.add_option('-t', '--top-level-directory', dest='top', default=None, | ||||
|                           help='Top level directory of project (defaults to start directory)') | ||||
| 
 | ||||
|         options, args = parser.parse_args(argv) | ||||
|         if len(args) > 3: | ||||
|             self.usageExit() | ||||
| 
 | ||||
|         for name, value in zip(('start', 'pattern', 'top'), args): | ||||
|             setattr(options, name, value) | ||||
| 
 | ||||
|         if options.verbose: | ||||
|             self.verbosity = 2 | ||||
| 
 | ||||
|         start_dir = options.start | ||||
|         pattern = options.pattern | ||||
|         top_level_dir = options.top | ||||
| 
 | ||||
|         loader = Loader() | ||||
|         self.test = loader.discover(start_dir, pattern, top_level_dir) | ||||
| 
 | ||||
|     def runTests(self): | ||||
|         if isinstance(self.testRunner, type): | ||||
|             try: | ||||
|  | @ -1620,4 +1796,5 @@ def runTests(self): | |||
| ############################################################################## | ||||
| 
 | ||||
| if __name__ == "__main__": | ||||
|     sys.modules['unittest'] = sys.modules['__main__'] | ||||
|     main(module=None) | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Benjamin Peterson
						Benjamin Peterson