diff options
author | Simon Hausmann <simon.hausmann@nokia.com> | 2012-02-03 09:55:33 +0100 |
---|---|---|
committer | Simon Hausmann <simon.hausmann@nokia.com> | 2012-02-03 09:55:33 +0100 |
commit | cd44dc59cdfc39534aef4d417e9f3c412e3be139 (patch) | |
tree | 8d89889ba95ed6ec9322e733846cc9cce9d7dff1 /Tools/Scripts/webkitpy/test/main.py | |
parent | d11f84f5b5cdc0d92a08af01b13472fdd5f9acb9 (diff) | |
download | qtwebkit-cd44dc59cdfc39534aef4d417e9f3c412e3be139.tar.gz |
Imported WebKit commit fce473cb4d55aa9fe9d0b0322a2fffecb731b961 (http://svn.webkit.org/repository/webkit/trunk@106560)
Diffstat (limited to 'Tools/Scripts/webkitpy/test/main.py')
-rw-r--r-- | Tools/Scripts/webkitpy/test/main.py | 328 |
1 files changed, 199 insertions, 129 deletions
diff --git a/Tools/Scripts/webkitpy/test/main.py b/Tools/Scripts/webkitpy/test/main.py index 9b03cced9..78c39db55 100644 --- a/Tools/Scripts/webkitpy/test/main.py +++ b/Tools/Scripts/webkitpy/test/main.py @@ -20,157 +20,227 @@ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -"""Contains the entry method for test-webkitpy.""" +"""unit testing code for webkitpy.""" import logging +import optparse import os +import StringIO import sys +import traceback import unittest -import webkitpy - +# NOTE: We intentionally do not depend on anything else in webkitpy here to avoid breaking test-webkitpy. _log = logging.getLogger(__name__) class Tester(object): + @staticmethod + def clean_packages(dirs): + """Delete all .pyc files under dirs that have no .py file.""" + for dir_to_clean in dirs: + _log.debug("Cleaning orphaned *.pyc files from: %s" % dir_to_clean) + for dir_path, dir_names, file_names in os.walk(dir_to_clean): + for file_name in file_names: + if file_name.endswith(".pyc") and file_name[:-1] not in file_names: + file_path = os.path.join(dir_path, file_name) + _log.info("Deleting orphan *.pyc file: %s" % file_path) + os.remove(file_path) + + def __init__(self): + self._verbosity = 1 + + def parse_args(self, argv): + parser = optparse.OptionParser(usage='usage: %prog [options] [args...]') + parser.add_option('-a', '--all', action='store_true', default=False, + help='run all the tests'), + parser.add_option('-c', '--coverage', action='store_true', default=False, + help='generate code coverage info (requires http://pypi.python.org/pypi/coverage)'), + parser.add_option('-q', '--quiet', action='store_true', default=False, + help='run quietly (errors, warnings, and progress only)'), + parser.add_option('-s', '--silent', action='store_true', default=False, + help='run silently (errors and warnings only)'), + parser.add_option('-x', '--xml', action='store_true', default=False, + help='output xUnit-style XML output') + parser.add_option('-v', '--verbose', action='count', default=0, + help='verbose output (specify once for individual test results, twice for debug messages)') + parser.add_option('--skip-integrationtests', action='store_true', default=False, + help='do not run the integration tests') + + parser.epilog = ('[args...] is an optional list of modules, test_classes, or individual tests. ' + 'If no args are given, all the tests will be run.') + + self.progName = os.path.basename(argv[0]) + return parser.parse_args(argv[1:]) + + def configure(self, options): + self._options = options + + if options.silent: + self._verbosity = 0 + self._configure_logging(logging.WARNING) + elif options.quiet: + self._verbosity = 1 + self._configure_logging(logging.WARNING) + elif options.verbose == 0: + self._verbosity = 1 + self._configure_logging(logging.INFO) + elif options.verbose == 1: + self._verbosity = 2 + self._configure_logging(logging.INFO) + elif options.verbose == 2: + self._verbosity = 2 + self._configure_logging(logging.DEBUG) + + def _configure_logging(self, log_level): + """Configure the root logger. + + Configure the root logger not to log any messages from webkitpy -- + except for messages from the autoinstall module. Also set the + logging level as described below. + """ + handler = logging.StreamHandler(sys.stderr) + # We constrain the level on the handler rather than on the root + # logger itself. This is probably better because the handler is + # configured and known only to this module, whereas the root logger + # is an object shared (and potentially modified) by many modules. + # Modifying the handler, then, is less intrusive and less likely to + # interfere with modifications made by other modules (e.g. in unit + # tests). + handler.setLevel(log_level) + formatter = logging.Formatter("%(message)s") + handler.setFormatter(formatter) + + logger = logging.getLogger() + logger.addHandler(handler) + logger.setLevel(logging.NOTSET) + + # Filter out most webkitpy messages. + # + # Messages can be selectively re-enabled for this script by updating + # this method accordingly. + def filter(record): + """Filter out autoinstall and non-third-party webkitpy messages.""" + # FIXME: Figure out a way not to use strings here, for example by + # using syntax like webkitpy.test.__name__. We want to be + # sure not to import any non-Python 2.4 code, though, until + # after the version-checking code has executed. + if (record.name.startswith("webkitpy.common.system.autoinstall") or + record.name.startswith("webkitpy.test")): + return True + if record.name.startswith("webkitpy"): + return False + return True + + testing_filter = logging.Filter() + testing_filter.filter = filter + + # Display a message so developers are not mystified as to why + # logging does not work in the unit tests. + _log.info("Suppressing most webkitpy logging while running unit tests.") + handler.addFilter(testing_filter) + + def run(self, dirs, args): + args = args or self._find_modules(dirs) + return self._run_tests(dirs, args) + + def _find_modules(self, dirs): + modules = [] + for dir_to_search in dirs: + modules.extend(self._find_modules_under(dir_to_search, '_unittest.py')) + if not self._options.skip_integrationtests: + modules.extend(self._find_modules_under(dir_to_search, '_integrationtest.py')) + modules.sort() - """Discovers and runs webkitpy unit tests.""" - - def _find_test_files(self, webkitpy_dir, suffix): - """Return a list of paths to all unit-test files.""" - unittest_paths = [] # Return value. - - for dir_path, dir_names, file_names in os.walk(webkitpy_dir): - for file_name in file_names: - if not file_name.endswith(suffix): - continue - unittest_path = os.path.join(dir_path, file_name) - unittest_paths.append(unittest_path) - - return unittest_paths - - def _modules_from_paths(self, package_root, paths): - """Return a list of fully-qualified module names given paths.""" - package_path = os.path.abspath(package_root) - root_package_name = os.path.split(package_path)[1] # Equals "webkitpy". + for module in modules: + _log.debug("Found: %s" % module) - prefix_length = len(package_path) + # FIXME: Figure out how to move this to test-webkitpy in order to to make this file more generic. + if not self._options.all: + slow_tests = ('webkitpy.common.checkout.scm.scm_unittest',) + self._exclude(modules, slow_tests, 'are really, really slow', 31818) - modules = [] - for path in paths: - path = os.path.abspath(path) - # This gives us, for example: /common/config/ports_unittest.py - rel_path = path[prefix_length:] - # This gives us, for example: /common/config/ports_unittest - rel_path = os.path.splitext(rel_path)[0] - - parts = [] - while True: - (rel_path, tail) = os.path.split(rel_path) - if not tail: - break - parts.insert(0, tail) - # We now have, for example: common.config.ports_unittest - # FIXME: This is all a hack around the fact that we always prefix webkitpy includes with "webkitpy." - parts.insert(0, root_package_name) # Put "webkitpy" at the beginning. - module = ".".join(parts) - modules.append(module) + if sys.platform == 'win32': + win32_blacklist = ('webkitpy.common.checkout', + 'webkitpy.common.config', + 'webkitpy.tool') + self._exclude(modules, win32_blacklist, 'fail horribly on win32', 54526) return modules - def _win32_blacklist(self, module_path): - # FIXME: Remove this once https://bugs.webkit.org/show_bug.cgi?id=54526 is resolved. - if any([module_path.startswith(package) for package in [ - 'webkitpy.tool', - 'webkitpy.common.checkout', - 'webkitpy.common.config', - ]]): - return False + def _exclude(self, modules, module_prefixes, reason, bugid): + _log.info('Skipping tests in the following modules or packages because they %s:' % reason) + for prefix in module_prefixes: + _log.info(' %s' % prefix) + modules_to_exclude = filter(lambda m: m.startswith(prefix), modules) + for m in modules_to_exclude: + if len(modules_to_exclude) > 1: + _log.debug(' %s' % m) + modules.remove(m) + _log.info(' (https://bugs.webkit.org/show_bug.cgi?id=%d; use --all to include)' % bugid) + _log.info('') - return module_path not in [ - # FIXME: This file also requires common.checkout to work - 'webkitpy.to_be_moved.deduplicate_tests_unittest', - ] + def _find_modules_under(self, dir_to_search, suffix): - def run_tests(self, sys_argv, external_package_paths=None): - """Run the unit tests in all *_unittest.py modules in webkitpy. + def to_package(dir_path): + return dir_path.replace(dir_to_search + os.sep, '').replace(os.sep, '.') - This method excludes "webkitpy.common.checkout.scm.scm_unittest" unless - the --all option is the second element of sys_argv. + def to_module(filename, package): + return package + '.' + filename.replace('.py', '') - Args: - sys_argv: A reference to sys.argv. - - """ - if external_package_paths is None: - external_package_paths = [] - else: - # FIXME: We should consider moving webkitpy off of using "webkitpy." to prefix - # all includes. If we did that, then this would use path instead of dirname(path). - # QueueStatusServer.__init__ has a sys.path import hack due to this code. - sys.path.extend(set(os.path.dirname(path) for path in external_package_paths)) + modules = [] + for dir_path, _, filenames in os.walk(dir_to_search): + package = to_package(dir_path) + modules.extend(to_module(f, package) for f in filenames if f.endswith(suffix)) + return modules - if '--xml' in sys.argv: - sys.argv.remove('--xml') + def _run_tests(self, dirs, args): + if self._options.coverage: + try: + import coverage + except ImportError, e: + _log.error("Failed to import 'coverage'; can't generate coverage numbers.") + return False + cov = coverage.coverage() + cov.start() + + _log.debug("Loading the tests...") + + loader = unittest.defaultTestLoader + suites = [] + for name in args: + if self._is_module(dirs, name): + # import modules explicitly before loading their tests because + # loadTestsFromName() produces lousy error messages for bad modules. + try: + __import__(name) + except ImportError, e: + _log.fatal('Failed to import %s:' % name) + self._log_exception() + return False + suites.append(loader.loadTestsFromName(name, None)) + + test_suite = unittest.TestSuite(suites) + if self._options.xml: from webkitpy.thirdparty.autoinstalled.xmlrunner import XMLTestRunner test_runner = XMLTestRunner(output='test-webkitpy-xml-reports') else: - test_runner = unittest.TextTestRunner() - - if len(sys_argv) > 1 and not sys_argv[-1].startswith("-"): - # Then explicit modules or test names were provided, which - # the unittest module is equipped to handle. - unittest.main(argv=sys_argv, module=None, testRunner=test_runner) - # No need to return since unitttest.main() exits. - - # Otherwise, auto-detect all unit tests. - - # FIXME: This should be combined with the external_package_paths code above. - webkitpy_dir = os.path.dirname(webkitpy.__file__) - - skip_integration_tests = False - if len(sys_argv) > 1 and sys.argv[1] == "--skip-integrationtests": - sys.argv.remove("--skip-integrationtests") - skip_integration_tests = True - - modules = [] - for path in [webkitpy_dir] + external_package_paths: - modules.extend(self._modules_from_paths(path, self._find_test_files(path, "_unittest.py"))) - if not skip_integration_tests: - modules.extend(self._modules_from_paths(path, self._find_test_files(path, "_integrationtest.py"))) - modules.sort() - - # This is a sanity check to ensure that the unit-test discovery - # methods are working. - if len(modules) < 1: - raise Exception("No unit-test modules found.") - - for module in modules: - _log.debug("Found: %s" % module) - - # FIXME: This is a hack, but I'm tired of commenting out the test. - # See https://bugs.webkit.org/show_bug.cgi?id=31818 - if len(sys_argv) > 1 and sys.argv[1] == "--all": - sys.argv.remove("--all") - else: - excluded_module = "webkitpy.common.checkout.scm.scm_unittest" - _log.info("Excluding: %s (use --all to include)" % excluded_module) - modules.remove(excluded_module) - - if sys.platform == 'win32': - modules = filter(self._win32_blacklist, modules) - - # unittest.main has horrible error reporting when module imports are bad - # so we test import here to make debugging bad imports much easier. - for module in modules: - __import__(module) - - sys_argv.extend(modules) - - # We pass None for the module because we do not want the unittest - # module to resolve module names relative to a given module. - # (This would require importing all of the unittest modules from - # this module.) See the loadTestsFromName() method of the - # unittest.TestLoader class for more details on this parameter. - unittest.main(argv=sys_argv, module=None, testRunner=test_runner) + test_runner = unittest.TextTestRunner(verbosity=self._verbosity) + + _log.debug("Running the tests.") + result = test_runner.run(test_suite) + if self._options.coverage: + cov.stop() + cov.save() + return result.wasSuccessful() + + def _is_module(self, dirs, name): + relpath = name.replace('.', os.sep) + '.py' + return any(os.path.exists(os.path.join(d, relpath)) for d in dirs) + + def _log_exception(self): + s = StringIO.StringIO() + traceback.print_exc(file=s) + for l in s.buflist: + _log.error(' ' + l.rstrip()) |