# _________________________________________________________________________ # # PyUtilib: A Python utility library. # Copyright (c) 2008 Sandia Corporation. # This software is distributed under the BSD License. # Under the terms of Contract DE-AC04-94AL85000 with Sandia Corporation, # the U.S. Government retains certain rights in this software. # _________________________________________________________________________ # __all__ = ['run', 'main', 'create_test_suites'] import sys import optparse import re import os from os.path import dirname, abspath import pyutilib.th as unittest from pyutilib.misc import Options from pyutilib.component.core import ExtensionPoint from pyutilib.autotest import plugins # GAH: Inside create_test_suite all options are (were) being cast to # str(). Although I'm not sure, I assume this is to wash out # unicode coming from json files in Python 2.x. However, we don't # want to cast numeric options in either case (which was # happening, e.g., tolerance). This is my quick hack to fix # things. Please fix if you have a better understanding of what # should be happening. try: unicode except: basestring = str def _str(x): if isinstance(x, basestring): return str(x) return x def validate_test_config(suite): if suite is None: raise IOError( "Empty suite indicates problem processing suite configuration") # tmp = set(suite.keys()) if not tmp.issubset( set(['python', 'solvers', 'problems', 'suites', 'driver'])): raise IOError("Unexpected test sections: " + str(suite.keys())) # if 'python' in suite: if not type(suite['python']) is list: raise IOError("Expected list of Python expressions") # if 'solvers' in suite: if not type(suite['solvers']) is dict: raise IOError("Expected dictionary of solvers") for key in suite['solvers']: if suite['solvers'][key] is None: suite['solvers'][key] = {} elif not type(suite['solvers'][key]) is dict: raise IOError( "Expected solvers to have a dictionary of options: %s" % str(suite)) # if 'problems' in suite: if not type(suite['problems']) is dict: raise IOError("Expected dictionary of problems") for key in suite['problems']: if suite['problems'][key] is None: suite['problems'][key] = {} elif not type(suite['problems'][key]) is dict: raise IOError( "Expected problems to have a dictionary of options") # if 'suites' in suite: if not type(suite['suites']) is dict: raise IOError("Expected dictionary of suites") for key in suite['suites']: if suite['suites'][key] is None: suite['suites'][key] = {} elif not type(suite['suites'][key]) is dict: raise IOError("Expected suites to have a dictionary of options") @unittest.nottest def create_test_suites(filename=None, config=None, _globals=None, options=None): if options is None: #pragma:nocover options = Options() # # Add categories specified by the PYUTILIB_AUTOTEST_CATEGORIES # or PYUTILIB_UNITTEST_CATEGORIES environments # if options is None or options.categories is None or len( options.categories) == 0: options.categories = set() if 'PYUTILIB_AUTOTEST_CATEGORIES' in os.environ: for cat in re.split(',', os.environ['PYUTILIB_AUTOTEST_CATEGORIES']): if cat != '': options.categories.add(cat.strip()) elif 'PYUTILIB_UNITTEST_CATEGORIES' in os.environ: for cat in re.split(',', os.environ['PYUTILIB_UNITTEST_CATEGORIES']): if cat != '': options.categories.add(cat.strip()) # if not filename is None: if options.currdir is None: options.currdir = dirname(abspath(filename)) + os.sep # ep = ExtensionPoint(plugins.ITestParser) ftype = os.path.splitext(filename)[1] if not ftype == '': ftype = ftype[1:] service = ep.service(ftype) if service is None: raise IOError( "Unknown file type. Cannot load test configuration from file '%s'" % filename) config = service.load_test_config(filename) #service.print_test_config(config) validate_test_config(config) # # Evaluate Python expressions # for item in config.get('python', []): try: exec(item, _globals) except Exception: err = sys.exc_info()[1] print("ERROR executing '%s'" % item) print(" Exception: %s" % str(err)) # # Create test driver, which is put in the global namespace # driver = plugins.TestDriverFactory(config['driver']) if driver is None: raise IOError("Unexpected test driver '%s'" % config['driver']) _globals["test_driver"] = driver # # Generate suite # for suite in config.get('suites', {}): create_test_suite(suite, config, _globals, options) @unittest.nottest def create_test_suite(suite, config, _globals, options): # # Skip suite creation if the options categores do not intersect with the list of test suite categories # if len(options.categories) > 0: flag = False for cat in options.categories: if cat in config['suites'][suite].get('categories', []): flag = True break if not flag: return # # Create test driver # if suite in _globals: raise IOError( "Cannot create suite '%s' since there is another symbol with that name in the global namespace!" % suite) def setUpClassFn(cls): options = cls._options[None] cls._test_driver.setUpClass(cls, options) _globals[suite] = type( str(suite), (unittest.TestCase,), {'setUpClass': classmethod(setUpClassFn)}) _globals[suite]._options[None] = options setattr(_globals[suite], '_test_driver', _globals['test_driver']) setattr(_globals[suite], 'suite_categories', config['suites'][suite].get( 'categories', [])) # # Create test functions # tests = [] if 'tests' in config['suites'][suite]: for item in config['suites'][suite]['tests']: tests.append((item['solver'], item['problem'], item)) else: for solver in config['suites'][suite]['solvers']: for problem in config['suites'][suite]['problems']: tests.append((solver, problem, {})) # for solver, problem, item in tests: ##sname = solver if options.testname_format is None: test_name = solver + "_" + problem else: test_name = options.testname_format % (solver, problem) # def fn(testcase, name, suite): options = testcase._options[suite, name] fn.test_driver.setUp(testcase, options) ans = fn.test_driver.run_test(testcase, name, options) fn.test_driver.tearDown(testcase, options) return ans fn.test_driver = _globals['test_driver'] # _options = Options() # problem_options = config['suites'][suite]['problems'][problem] if not problem_options is None and 'problem' in problem_options: _problem = problem_options['problem'] else: _problem = problem for attr, value in config['problems'].get(_problem, {}).items(): _options[attr] = _str(value) if not problem_options is None: for attr, value in problem_options.items(): _options[attr] = _str(value) # solver_options = config['suites'][suite]['solvers'][solver] if not solver_options is None and 'solver' in solver_options: _solver = solver_options['solver'] else: _solver = solver _name = _solver for attr, value in config['solvers'].get(_solver, {}).items(): _options[attr] = _str(value) if attr == 'name': _name = value if not solver_options is None: for attr, value in solver_options.items(): _options[attr] = _str(value) # for key in item: if key not in ['problem', 'solver']: _options[key] = _str(item[key]) # _options.solver = _str(_name) _options.problem = _str(_problem) _options.suite = _str(suite) _options.currdir = _str(options.currdir) # _globals[suite].add_fn_test( name=test_name, fn=fn, suite=suite, options=_options) def cleanup(_globals, suites): for suite in suites: del _globals[suite] def run(argv, _globals=None): # # Set sys.argv to the value specified by the user # sys.argv = argv # # Create the option parser # parser = optparse.OptionParser() parser.remove_option('-h') # parser.add_option( '-h', '--help', action='store_true', dest='help', default=False, help='Print command options') # parser.add_option( '-d', '--debug', action='store_true', dest='debug', default=False, help='Set debugging flag') # parser.add_option( '-v', '--verbose', action='store_true', dest='verbose', default=False, help='Verbose output') # parser.add_option( '-q', '--quiet', action='store_true', dest='quiet', default=False, help='Minimal output') # parser.add_option( '-f', '--failfast', action='store_true', dest='failfast', default=False, help='Stop on first failure') # parser.add_option( '-c', '--catch', action='store_true', dest='catch', default=False, help='Catch control-C and display results') # parser.add_option( '-b', '--buffer', action='store_true', dest='buffer', default=False, help='Buffer stdout and stderr durring test runs') # parser.add_option( '--cat', '--category', action='append', dest='categories', default=[], help='Define a list of categories that filter the execution of test suites') # parser.add_option( '--help-suites', action='store_true', dest='help_suites', default=False, help='Print the test suites that can be executed') # parser.add_option( '--help-tests', action='store', dest='help_tests', default=None, help='Print the tests in the specified test suite') # parser.add_option( '--help-categories', action='store_true', dest='help_categories', default=False, help='Print the test suite categories that can be specified') # # Parse the argument list and print help info if needed # _options, args = parser.parse_args(sys.argv) if _options.help: parser.print_help() print(""" Examples: %s - run all test suites %s MyTestCase.testSomething - run MyTestCase.testSomething %s MyTestCase - run all 'test*' test methods in MyTestCase """ % (args[0], args[0], args[0])) return # # If no value for _globals is specified, then we use the current context. # if _globals is None: _globals = globals() # # Setup and Options object and create test suites from the specified # configuration files. # options = Options() options.debug = _options.debug options.verbose = _options.verbose options.quiet = _options.quiet options.categories = _options.categories _argv = [] for arg in args[1:]: if os.path.exists(arg): create_test_suites(filename=arg, _globals=_globals, options=options) else: _argv.append(arg) # # Collect information about the test suites: suite names and categories # suites = [] categories = set() for key in _globals.keys(): if type(_globals[key]) is type and issubclass(_globals[key], unittest.TestCase): suites.append(key) for c in _globals[key].suite_categories: categories.add(c) # # Process the --help-tests option # if _options.help_tests and not _globals is None: suite = _globals.get(_options.help_tests, None) if not type(suite) is type: print("Test suite '%s' not found!" % str(_options.help_tests)) return cleanup(_globals, suites) tests = [] for item in dir(suite): if item.startswith('test'): tests.append(item) print("") if len(tests) > 0: print("Tests defined in test suite '%s':" % _options.help_tests) for tmp in sorted(tests): print(" " + tmp) else: print("No tests defined in test suite '%s':" % _options.help_tests) print("") return cleanup(_globals, suites) # # Process the --help-suites and --help-categories options # if (_options.help_suites or _options.help_categories) and not _globals is None: if _options.help_suites: print("") if len(suites) > 0: print("Test suites defined in '%s':" % os.path.basename(argv[0])) for suite in sorted(suites): print(" " + suite) else: print("No test suites defined in '%s'!" % os.path.basename(argv[0])) print("") if _options.help_categories: tmp = list(categories) print("") if len(tmp) > 0: print("Test suite categories defined in '%s':" % os.path.basename(argv[0])) for c in sorted(tmp): print(" " + c) else: print("No test suite categories defined in '%s':" % os.path.basename(argv[0])) print("") return cleanup(_globals, suites) # # Reset the value of sys.argv per the expectations of the unittest module # tmp = [args[0]] if _options.quiet: tmp.append('-q') if _options.verbose or _options.debug: tmp.append('-v') if _options.failfast: tmp.append('-f') if _options.catch: tmp.append('-c') if _options.buffer: tmp.append('-b') tmp += _argv sys.argv = tmp # # Execute the unittest main function to run tests # unittest.main(module=_globals['__name__']) cleanup(_globals, suites) def main(_globals=None): run(sys.argv, _globals=_globals)