Title: [124501] trunk/Tools
Revision
124501
Author
[email protected]
Date
2012-08-02 14:35:34 -0700 (Thu, 02 Aug 2012)

Log Message

test-webkitpy: clean up runner in preparation for running tests serially as necessary
https://bugs.webkit.org/show_bug.cgi?id=92922

Reviewed by Ojan Vafai.

In order to run some tests by themselves (serially, rather than
in parallel with other tests), we will need to be able to run
multiple test suites; this causes us to move loading the logic
for finding test method names out of the runner and into main.

I'm taking advantage of this to simplify some other stuff from
the runner as well; it is now very simple and doesn't expose its
dependency on unittest.TestResult at all (nor will the Printer
use TestResult).

Subsequent patches will move the custom loader from
port_testcase so that we can properly choose whether to run
integration tests and/or serial tests, and then update the
appropriate tests to run only serially.

* Scripts/webkitpy/test/main.py:
(Tester._run_tests):
(Tester):
(Tester._check_imports):
(Tester._test_names):
(Tester._all_test_names):
* Scripts/webkitpy/test/printer.py:
(Printer.__init__):
(Printer.write_update):
(Printer):
(Printer.print_finished_test):
(Printer.print_result):
* Scripts/webkitpy/test/runner.py:
(unit_test_name):
(Runner.__init__):
(Runner.run):
(Runner.handle):
(_Worker.handle):
* Scripts/webkitpy/test/runner_unittest.py:
(FakeLoader.loadTestsFromName):
(RunnerTest.test_run):

Modified Paths

Diff

Modified: trunk/Tools/ChangeLog (124500 => 124501)


--- trunk/Tools/ChangeLog	2012-08-02 21:34:51 UTC (rev 124500)
+++ trunk/Tools/ChangeLog	2012-08-02 21:35:34 UTC (rev 124501)
@@ -1,3 +1,47 @@
+2012-08-02  Dirk Pranke  <[email protected]>
+
+        test-webkitpy: clean up runner in preparation for running tests serially as necessary
+        https://bugs.webkit.org/show_bug.cgi?id=92922
+
+        Reviewed by Ojan Vafai.
+
+        In order to run some tests by themselves (serially, rather than
+        in parallel with other tests), we will need to be able to run
+        multiple test suites; this causes us to move loading the logic
+        for finding test method names out of the runner and into main.
+
+        I'm taking advantage of this to simplify some other stuff from
+        the runner as well; it is now very simple and doesn't expose its
+        dependency on unittest.TestResult at all (nor will the Printer
+        use TestResult).
+
+        Subsequent patches will move the custom loader from
+        port_testcase so that we can properly choose whether to run
+        integration tests and/or serial tests, and then update the
+        appropriate tests to run only serially.
+
+        * Scripts/webkitpy/test/main.py:
+        (Tester._run_tests):
+        (Tester):
+        (Tester._check_imports):
+        (Tester._test_names):
+        (Tester._all_test_names):
+        * Scripts/webkitpy/test/printer.py:
+        (Printer.__init__):
+        (Printer.write_update):
+        (Printer):
+        (Printer.print_finished_test):
+        (Printer.print_result):
+        * Scripts/webkitpy/test/runner.py:
+        (unit_test_name):
+        (Runner.__init__):
+        (Runner.run):
+        (Runner.handle):
+        (_Worker.handle):
+        * Scripts/webkitpy/test/runner_unittest.py:
+        (FakeLoader.loadTestsFromName):
+        (RunnerTest.test_run):
+
 2012-08-02  Adam Barth  <[email protected]>
 
         Turn on tests for the mac-ews, for realz this time.

Modified: trunk/Tools/Scripts/webkitpy/test/main.py (124500 => 124501)


--- trunk/Tools/Scripts/webkitpy/test/main.py	2012-08-02 21:34:51 UTC (rev 124500)
+++ trunk/Tools/Scripts/webkitpy/test/main.py	2012-08-02 21:35:34 UTC (rev 124501)
@@ -29,13 +29,14 @@
 import os
 import StringIO
 import sys
+import time
 import traceback
 import unittest
 
 from webkitpy.common.system.filesystem import FileSystem
 from webkitpy.test.finder import Finder
 from webkitpy.test.printer import Printer
-from webkitpy.test.runner import Runner
+from webkitpy.test.runner import Runner, unit_test_name
 
 _log = logging.getLogger(__name__)
 
@@ -128,10 +129,30 @@
         # Make sure PYTHONPATH is set up properly.
         sys.path = self.finder.additional_paths(sys.path) + sys.path
 
-        _log.debug("Loading the tests...")
+        self.printer.write_update("Checking imports ...")
+        if not self._check_imports(names):
+            return False
 
+        self.printer.write_update("Finding the individual test methods ...")
         loader = unittest.defaultTestLoader
-        suites = []
+        test_names = self._test_names(loader, names)
+
+        self.printer.write_update("Running the tests ...")
+        self.printer.num_tests = len(test_names)
+        start = time.time()
+        test_runner = Runner(self.printer, loader)
+        test_runner.run(test_names, self._options.child_processes)
+
+        self.printer.print_result(time.time() - start)
+
+        if self._options.coverage:
+            cov.stop()
+            cov.save()
+            cov.report(show_missing=False)
+
+        return not self.printer.num_errors and not self.printer.num_failures
+
+    def _check_imports(self, names):
         for name in names:
             if self.finder.is_module(name):
                 # if we failed to load a name and it looks like a module,
@@ -143,19 +164,23 @@
                     _log.fatal('Failed to import %s:' % name)
                     self._log_exception()
                     return False
+        return True
 
-            suites.append(loader.loadTestsFromName(name, None))
+    def _test_names(self, loader, names):
+        test_names = []
+        for name in names:
+            test_names.extend(self._all_test_names(loader.loadTestsFromName(name, None)))
 
-        test_suite = unittest.TestSuite(suites)
-        test_runner = Runner(self.printer, self._options, loader)
+        return test_names
 
-        _log.debug("Running the tests.")
-        result = test_runner.run(test_suite)
-        if self._options.coverage:
-            cov.stop()
-            cov.save()
-            cov.report(show_missing=False)
-        return result.wasSuccessful()
+    def _all_test_names(self, suite):
+        names = []
+        if hasattr(suite, '_tests'):
+            for t in suite._tests:
+                names.extend(self._all_test_names(t))
+        else:
+            names.append(unit_test_name(suite))
+        return names
 
     def _log_exception(self):
         s = StringIO.StringIO()

Modified: trunk/Tools/Scripts/webkitpy/test/printer.py (124500 => 124501)


--- trunk/Tools/Scripts/webkitpy/test/printer.py	2012-08-02 21:34:51 UTC (rev 124500)
+++ trunk/Tools/Scripts/webkitpy/test/printer.py	2012-08-02 21:35:34 UTC (rev 124501)
@@ -37,6 +37,8 @@
         self.options = options
         self.num_tests = 0
         self.num_completed = 0
+        self.num_errors = 0
+        self.num_failures = 0
         self.running_tests = []
         self.completed_tests = []
         if options:
@@ -102,6 +104,9 @@
         if self.options.pass_through:
             outputcapture.OutputCapture.stream_wrapper = _CaptureAndPassThroughStream
 
+    def write_update(self, msg):
+        self.meter.write_update(msg)
+
     def print_started_test(self, source, test_name):
         self.running_tests.append(test_name)
         if len(self.running_tests) > 1:
@@ -116,14 +121,16 @@
 
         write(self._test_line(self.running_tests[0], suffix))
 
-    def print_finished_test(self, result, test_name, test_time, failure, err):
+    def print_finished_test(self, source, test_name, test_time, failures, errors):
         write = self.meter.writeln
-        if failure:
-            lines = failure[0][1].splitlines() + ['']
+        if failures:
+            lines = failures[0].splitlines() + ['']
             suffix = ' failed:'
-        elif err:
-            lines = err[0][1].splitlines() + ['']
+            self.num_failures += 1
+        elif errors:
+            lines = errors[0].splitlines() + ['']
             suffix = ' erred:'
+            self.num_errors += 1
         else:
             suffix = ' passed'
             lines = []
@@ -154,13 +161,13 @@
     def _test_line(self, test_name, suffix):
         return '[%d/%d] %s%s' % (self.num_completed, self.num_tests, test_name, suffix)
 
-    def print_result(self, result, run_time):
+    def print_result(self, run_time):
         write = self.meter.writeln
-        write('Ran %d test%s in %.3fs' % (result.testsRun, result.testsRun != 1 and "s" or "", run_time))
-        if result.wasSuccessful():
+        write('Ran %d test%s in %.3fs' % (self.num_completed, self.num_completed != 1 and "s" or "", run_time))
+        if self.num_failures or self.num_errors:
+            write('FAILED (failures=%d, errors=%d)\n' % (self.num_failures, self.num_errors))
+        else:
             write('\nOK\n')
-        else:
-            write('FAILED (failures=%d, errors=%d)\n' % (len(result.failures), len(result.errors)))
 
 
 class _CaptureAndPassThroughStream(object):

Modified: trunk/Tools/Scripts/webkitpy/test/runner.py (124500 => 124501)


--- trunk/Tools/Scripts/webkitpy/test/runner.py	2012-08-02 21:34:51 UTC (rev 124500)
+++ trunk/Tools/Scripts/webkitpy/test/runner.py	2012-08-02 21:35:34 UTC (rev 124501)
@@ -22,61 +22,47 @@
 
 """code to actually run a list of python tests."""
 
-import logging
 import re
 import time
 import unittest
 
 from webkitpy.common import message_pool
 
-_log = logging.getLogger(__name__)
-
-
 _test_description = re.compile("(\w+) \(([\w.]+)\)")
 
 
-def _test_name(test):
+def unit_test_name(test):
     m = _test_description.match(str(test))
     return "%s.%s" % (m.group(2), m.group(1))
 
 
 class Runner(object):
-    def __init__(self, printer, options, loader):
-        self.options = options
+    def __init__(self, printer, loader):
         self.printer = printer
         self.loader = loader
-        self.result = unittest.TestResult()
+        self.tests_run = 0
+        self.errors = []
+        self.failures = []
         self.worker_factory = lambda caller: _Worker(caller, self.loader)
 
-    def all_test_names(self, suite):
-        names = []
-        if hasattr(suite, '_tests'):
-            for t in suite._tests:
-                names.extend(self.all_test_names(t))
-        else:
-            names.append(_test_name(suite))
-        return names
+    def run(self, test_names, num_workers):
+        if not test_names:
+            return
+        num_workers = min(num_workers, len(test_names))
+        with message_pool.get(self, self.worker_factory, num_workers) as pool:
+            pool.run(('test', test_name) for test_name in test_names)
 
-    def run(self, suite):
-        run_start_time = time.time()
-        all_test_names = self.all_test_names(suite)
-        self.printer.num_tests = len(all_test_names)
-
-        with message_pool.get(self, self.worker_factory, int(self.options.child_processes)) as pool:
-            pool.run(('test', test_name) for test_name in all_test_names)
-
-        self.printer.print_result(self.result, time.time() - run_start_time)
-        return self.result
-
-    def handle(self, message_name, source, test_name, delay=None, result=None):
+    def handle(self, message_name, source, test_name, delay=None, failures=None, errors=None):
         if message_name == 'started_test':
             self.printer.print_started_test(source, test_name)
             return
 
-        self.result.testsRun += 1
-        self.result.errors.extend(result.errors)
-        self.result.failures.extend(result.failures)
-        self.printer.print_finished_test(source, test_name, delay, result.failures, result.errors)
+        self.tests_run += 1
+        if failures:
+            self.failures.append((test_name, failures))
+        if errors:
+            self.errors.append((test_name, errors))
+        self.printer.print_finished_test(source, test_name, delay, failures, errors)
 
 
 class _Worker(object):
@@ -89,13 +75,8 @@
         result = unittest.TestResult()
         start = time.time()
         self._caller.post('started_test', test_name)
-        self._loader.loadTestsFromName(test_name, None).run(result)
 
-        # The tests in the TestResult contain file objects and other unpicklable things; we only
-        # care about the test name, so we rewrite the result to replace the test with the test name.
-        # FIXME: We need an automated test for this, but I don't know how to write an automated
-        # test that will fail in this case that doesn't get picked up by test-webkitpy normally :(.
-        result.failures = [(_test_name(failure[0]), failure[1]) for failure in result.failures]
-        result.errors = [(_test_name(error[0]), error[1]) for error in result.errors]
-
-        self._caller.post('finished_test', test_name, time.time() - start, result)
+        # We will need to rework this if a test_name results in multiple tests.
+        self._loader.loadTestsFromName(test_name, None).run(result)
+        self._caller.post('finished_test', test_name, time.time() - start,
+            [failure[1] for failure in result.failures], [error[1] for error in result.errors])

Modified: trunk/Tools/Scripts/webkitpy/test/runner_unittest.py (124500 => 124501)


--- trunk/Tools/Scripts/webkitpy/test/runner_unittest.py	2012-08-02 21:34:51 UTC (rev 124500)
+++ trunk/Tools/Scripts/webkitpy/test/runner_unittest.py	2012-08-02 21:35:34 UTC (rev 124501)
@@ -65,7 +65,7 @@
     def top_suite(self):
         return FakeTopSuite(self._tests)
 
-    def loadTestsFromName(self, name, dummy):
+    def loadTestsFromName(self, name, _):
         return FakeModuleSuite(*self._results[name])
 
 
@@ -84,29 +84,18 @@
         for handler in self.log_handlers:
             handler.level = self.log_levels.pop(0)
 
-    def assert_run(self, verbose=0, timing=False, child_processes=1, quiet=False):
+    def test_run(self, verbose=0, timing=False, child_processes=1, quiet=False):
         options = MockOptions(verbose=verbose, timing=timing, child_processes=child_processes, quiet=quiet, pass_through=False)
         stream = StringIO.StringIO()
         loader = FakeLoader(('test1 (Foo)', '.', ''),
                             ('test2 (Foo)', 'F', 'test2\nfailed'),
                             ('test3 (Foo)', 'E', 'test3\nerred'))
-        runner = Runner(Printer(stream, options), options, loader)
-        result = runner.run(loader.top_suite())
-        self.assertFalse(result.wasSuccessful())
-        self.assertEquals(result.testsRun, 3)
-        self.assertEquals(len(result.failures), 1)
-        self.assertEquals(len(result.errors), 1)
-        # FIXME: check the output from the test
+        runner = Runner(Printer(stream, options), loader)
+        runner.run(['Foo.test1', 'Foo.test2', 'Foo.test3'], 1)
+        self.assertEquals(runner.tests_run, 3)
+        self.assertEquals(len(runner.failures), 1)
+        self.assertEquals(len(runner.errors), 1)
 
-    def test_regular(self):
-        self.assert_run()
 
-    def test_verbose(self):
-        self.assert_run(verbose=1)
-
-    def test_timing(self):
-        self.assert_run(timing=True)
-
-
 if __name__ == '__main__':
     unittest.main()
_______________________________________________
webkit-changes mailing list
[email protected]
http://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to