summaryrefslogtreecommitdiff
path: root/Tools/Scripts/webkitpy/test/main.py
diff options
context:
space:
mode:
authorSimon Hausmann <simon.hausmann@nokia.com>2012-02-03 09:55:33 +0100
committerSimon Hausmann <simon.hausmann@nokia.com>2012-02-03 09:55:33 +0100
commitcd44dc59cdfc39534aef4d417e9f3c412e3be139 (patch)
tree8d89889ba95ed6ec9322e733846cc9cce9d7dff1 /Tools/Scripts/webkitpy/test/main.py
parentd11f84f5b5cdc0d92a08af01b13472fdd5f9acb9 (diff)
downloadqtwebkit-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.py328
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())