Title: [277061] trunk/Tools
Revision
277061
Author
jbed...@apple.com
Date
2021-05-05 17:22:16 -0700 (Wed, 05 May 2021)

Log Message

[run-api-tests] Use webkitcorepy's TaskPool
https://bugs.webkit.org/show_bug.cgi?id=225221
<rdar://problem/77352465>

Reviewed by Dewei Zhu.

* Scripts/libraries/webkitcorepy/setup.py: Bump version.
* Scripts/libraries/webkitcorepy/webkitcorepy/__init__.py: Ditto.
* Scripts/libraries/webkitcorepy/webkitcorepy/task_pool.py:
(TaskPool.__enter__): Ensure that mock has been imported before spawning child processes.
* Scripts/run-api-tests: Moved from webkitpy/api_tests/run_api_tests.py.
* Scripts/webkitpy/api_tests/run_api_tests.py: Moved to run-api-tests.
* Scripts/webkitpy/api_tests/runner.py:
(_Worker): Representation of Worker process.
(_Worker.setup): Pass port object to worker process.
(_Worker.teardown): Un-set port object on worker process.
(_Worker.__init__): Construct object to hold a Worker process's variables.
(_Worker._run_single_test): Log test results, post results to parent.
(_Worker.run): Run shard with and post results to parent process.
(setup_shard): Run setup in Worker process.
(run_shard): Run test shard in Worker process.
(report_result): Receive result in the parent process.
((teardown_shard): Tear down Worker process.
(Runner.__init__):
(Runner.command_for_port):
(Runner._shard_tests): Split tests into shards to be efficiently run.
(Runner.run): Use TaskPool to run tests in sub processes.
(Runner.callback): Save results in parent process.
(Runner.result_map_by_status):
(Runner.handle): Deleted.
(_Worker._run_shard_with_binary): Deleted.
(_Worker.post): Deleted.
(_Worker.handle): Deleted.

Modified Paths

Removed Paths

Diff

Modified: trunk/Tools/ChangeLog (277060 => 277061)


--- trunk/Tools/ChangeLog	2021-05-06 00:19:10 UTC (rev 277060)
+++ trunk/Tools/ChangeLog	2021-05-06 00:22:16 UTC (rev 277061)
@@ -1,3 +1,39 @@
+2021-05-05  Jonathan Bedard  <jbed...@apple.com>
+
+        [run-api-tests] Use webkitcorepy's TaskPool
+        https://bugs.webkit.org/show_bug.cgi?id=225221
+        <rdar://problem/77352465>
+
+        Reviewed by Dewei Zhu.
+
+        * Scripts/libraries/webkitcorepy/setup.py: Bump version.
+        * Scripts/libraries/webkitcorepy/webkitcorepy/__init__.py: Ditto.
+        * Scripts/libraries/webkitcorepy/webkitcorepy/task_pool.py:
+        (TaskPool.__enter__): Ensure that mock has been imported before spawning child processes.
+        * Scripts/run-api-tests: Moved from webkitpy/api_tests/run_api_tests.py.
+        * Scripts/webkitpy/api_tests/run_api_tests.py: Moved to run-api-tests.
+        * Scripts/webkitpy/api_tests/runner.py:
+        (_Worker): Representation of Worker process.
+        (_Worker.setup): Pass port object to worker process.
+        (_Worker.teardown): Un-set port object on worker process.
+        (_Worker.__init__): Construct object to hold a Worker process's variables.
+        (_Worker._run_single_test): Log test results, post results to parent.
+        (_Worker.run): Run shard with and post results to parent process.
+        (setup_shard): Run setup in Worker process.
+        (run_shard): Run test shard in Worker process.
+        (report_result): Receive result in the parent process.
+        ((teardown_shard): Tear down Worker process.
+        (Runner.__init__):
+        (Runner.command_for_port):
+        (Runner._shard_tests): Split tests into shards to be efficiently run.
+        (Runner.run): Use TaskPool to run tests in sub processes.
+        (Runner.callback): Save results in parent process.
+        (Runner.result_map_by_status):
+        (Runner.handle): Deleted.
+        (_Worker._run_shard_with_binary): Deleted.
+        (_Worker.post): Deleted.
+        (_Worker.handle): Deleted.
+
 2021-05-05  Devin Rousso  <drou...@apple.com>
 
         Sampled Page Top Color: don't snapshot if the hit test location is an image or has an animation

Modified: trunk/Tools/Scripts/libraries/webkitcorepy/setup.py (277060 => 277061)


--- trunk/Tools/Scripts/libraries/webkitcorepy/setup.py	2021-05-06 00:19:10 UTC (rev 277060)
+++ trunk/Tools/Scripts/libraries/webkitcorepy/setup.py	2021-05-06 00:22:16 UTC (rev 277061)
@@ -30,7 +30,7 @@
 
 setup(
     name='webkitcorepy',
-    version='0.5.13',
+    version='0.5.14',
     description='Library containing various Python support classes and functions.',
     long_description=readme(),
     classifiers=[

Modified: trunk/Tools/Scripts/libraries/webkitcorepy/webkitcorepy/__init__.py (277060 => 277061)


--- trunk/Tools/Scripts/libraries/webkitcorepy/webkitcorepy/__init__.py	2021-05-06 00:19:10 UTC (rev 277060)
+++ trunk/Tools/Scripts/libraries/webkitcorepy/webkitcorepy/__init__.py	2021-05-06 00:22:16 UTC (rev 277061)
@@ -37,7 +37,7 @@
 from webkitcorepy.task_pool import TaskPool
 from webkitcorepy.credentials import credentials
 
-version = Version(0, 5, 13)
+version = Version(0, 5, 14)
 
 from webkitcorepy.autoinstall import Package, AutoInstall
 if sys.version_info > (3, 0):

Modified: trunk/Tools/Scripts/libraries/webkitcorepy/webkitcorepy/task_pool.py (277060 => 277061)


--- trunk/Tools/Scripts/libraries/webkitcorepy/webkitcorepy/task_pool.py	2021-05-06 00:19:10 UTC (rev 277060)
+++ trunk/Tools/Scripts/libraries/webkitcorepy/webkitcorepy/task_pool.py	2021-05-06 00:22:16 UTC (rev 277061)
@@ -332,6 +332,8 @@
         self.block_size = block_size
 
     def __enter__(self):
+        from mock import patch
+
         with Timeout(seconds=10, patch=False, handler=self.Exception('Failed to start all workers')):
             for worker in self.workers:
                 worker.start()

Modified: trunk/Tools/Scripts/run-api-tests (277060 => 277061)


--- trunk/Tools/Scripts/run-api-tests	2021-05-06 00:19:10 UTC (rev 277060)
+++ trunk/Tools/Scripts/run-api-tests	2021-05-06 00:22:16 UTC (rev 277061)
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 
-# Copyright (C) 2018 Apple Inc. All rights reserved.
+# Copyright (C) 2018-2021 Apple Inc. All rights reserved.
 #
 # Redistribution and use in source and binary forms, with or without
 # modification, are permitted provided that the following conditions
@@ -22,7 +22,145 @@
 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
-"""Wrapper around webkitpy/api_tests/run_api_tests.py"""
-from webkitpy.common import multiprocessing_bootstrap
+from __future__ import print_function
+import logging
+import optparse
+import sys
+import traceback
 
-multiprocessing_bootstrap.run('webkitpy', 'api_tests', 'run_api_tests.py')
+from webkitpy.api_tests.manager import Manager
+from webkitpy.common.host import Host
+from webkitpy.layout_tests.views.metered_stream import MeteredStream
+from webkitpy.port import configuration_options, platform_options, base, win
+from webkitpy.results.options import upload_options
+
+EXCEPTIONAL_EXIT_STATUS = -1
+INTERRUPT_EXIT_STATUS = -2
+
+_log = logging.getLogger(__name__)
+
+
+def main(argv, stdout, stderr):
+    options, args = parse_args(argv)
+    host = Host()
+
+    try:
+        options.webkit_test_runner = True
+        port = host.port_factory.get(options.platform, options)
+    except NotImplementedError as e:
+        print(str(e), file=stderr)
+        return EXCEPTIONAL_EXIT_STATUS
+
+    # Some platforms do not support API tests
+    does_not_support_api_tests = ['ios-device']
+    if port.operating_system() in does_not_support_api_tests:
+        print('{} cannot run API tests'.format(port.operating_system()), file=stderr)
+        return EXCEPTIONAL_EXIT_STATUS
+
+    try:
+        return run(port, options, args, stderr)
+    except KeyboardInterrupt:
+        return INTERRUPT_EXIT_STATUS
+    except BaseException as e:
+        if isinstance(e, Exception):
+            print('\n%s raised: %s' % (e.__class__.__name__, str(e)), file=stderr)
+            traceback.print_exc(file=stderr)
+        return EXCEPTIONAL_EXIT_STATUS
+
+
+def run(port, options, args, logging_stream):
+    logger = logging.getLogger()
+    logger.setLevel(logging.DEBUG if options.verbose else logging.ERROR if options.quiet else logging.INFO)
+
+    try:
+        stream = MeteredStream(logging_stream, options.verbose, logger=logger, number_of_columns=port.host.platform.terminal_width(), print_timestamps=options.timestamps)
+        manager = Manager(port, options, stream)
+
+        result = manager.run(args, json_output=options.json_output)
+        _log.debug("Testing completed, Exit status: %d" % result)
+        return result
+    finally:
+        stream.cleanup()
+
+
+def parse_args(args):
+    option_group_definitions = []
+
+    option_group_definitions.append(('Platform options', platform_options()))
+    option_group_definitions.append(('Configuration options', configuration_options()))
+    option_group_definitions.append(('Printing Options', [
+        optparse.make_option('-q', '--quiet', action='', default=False,
+                             help='Run quietly (errors, warnings, and progress only)'),
+        optparse.make_option('-v', '--verbose', action='', default=False,
+                             help='Enable verbose printing'),
+        optparse.make_option('--timestamps', action='', default=False,
+                             help='Print timestamps for each logged line'),
+        optparse.make_option('--json-output', action='', default=None,
+                             help='Save test results as JSON to file'),
+    ]))
+
+    option_group_definitions.append(('WebKit Options', [
+        optparse.make_option('-g', '--guard-malloc', action='', default=False,
+                             help='Enable Guard Malloc (OS X only)'),
+        optparse.make_option('--root', action='',
+                             help='Path to a directory containing the executables needed to run tests.'),
+    ]))
+
+    option_group_definitions.append(('Testing Options', [
+        optparse.make_option('--wtf-only', action='', const='TestWTF', dest='api_binary',
+                             help='Only build, check and run TestWTF'),
+        optparse.make_option('--webkit-only', action='', const='TestWebKitAPI', dest='api_binary',
+                             help='Only check and run TestWebKitAPI'),
+        optparse.make_option('--web-core-only', action='', const='TestWebCore', dest='api_binary',
+                             help='Only check and run TestWebCore.exe (Windows only)'),
+        optparse.make_option('--webkit-legacy-only', action='', const='TestWebKitLegacy', dest='api_binary',
+                             help='Only check and run TestWebKitLegacy.exe (Windows only)'),
+        optparse.make_option('-d', '--dump', action='', default=False,
+                             help='Dump all test names without running them'),
+        optparse.make_option('--build', dest='build', action='', default=True,
+                             help='Check to ensure the build is up-to-date (default).'),
+        optparse.make_option('--no-build', dest='build', action='',
+                             help="Don't check to see if the build is up-to-date."),
+        optparse.make_option('--timeout', default=30,
+                             help='Number of seconds to wait before a test times out'),
+        optparse.make_option('--no-timeout', dest='timeout', action='',
+                             help='Disable timeouts for all tests'),
+        optparse.make_option('--iterations', type='int', default=1, help='Number of times to run the set of tests (e.g. ABCABCABC)'),
+        optparse.make_option('--repeat-each', type='int', default=1, help='Number of times to run each test (e.g. AAABBBCCC)'),
+
+        # FIXME: Remove the default, API tests should be multiprocess
+        optparse.make_option('--child-processes', default=1,
+                             help='Number of processes to run in parallel.'),
+
+        # FIXME: Default should be false, API tests should not be forced to run singly
+        optparse.make_option('--run-singly', action='', default=True,
+                             help='Run a separate process for each test'),
+
+        optparse.make_option('--force', action='', default=False,
+                             help='Run all tests, even DISABLED tests'),
+        optparse.make_option('--additional-env-var', type='string', action='', default=[],
+                             help='Passes that environment variable to the tests (--additional-env-var=NAME=VALUE)'),
+    ]))
+    option_group_definitions.append(('Upload Options', upload_options()))
+
+    option_parser = optparse.OptionParser(
+        usage='run-api-tests [options] [<test names>...]',
+        description="""By default, run-api-tests will run all API tests. It also allows the user to specify tests of the \
+format <suite>.<test> or <canonicalized binary name>.<suite>.<test>. Note that in the case where a binary is not \
+specified, one will be inferred by listing all available tests. Specifying just a binary or just a suite will cause every \
+test contained within to be run. The canonicalized binary name is the binary name with any filename extension \
+stripped. For Unix ports, these binaries are {} and {}. For Windows ports, they are {} and {}.""".format(
+            ', '.join(base.Port.API_TEST_BINARY_NAMES[:-1]), base.Port.API_TEST_BINARY_NAMES[-1],
+            ', '.join(win.WinPort.API_TEST_BINARY_NAMES[:-1]), win.WinPort.API_TEST_BINARY_NAMES[-1],
+    ))
+
+    for group_name, group_options in option_group_definitions:
+        option_group = optparse.OptionGroup(option_parser, group_name)
+        option_group.add_options(group_options)
+        option_parser.add_option_group(option_group)
+
+    return option_parser.parse_args(args)
+
+
+if __name__ == '__main__':
+    sys.exit(main(sys.argv[1:], sys.stdout, sys.stderr))

Deleted: trunk/Tools/Scripts/webkitpy/api_tests/run_api_tests.py (277060 => 277061)


--- trunk/Tools/Scripts/webkitpy/api_tests/run_api_tests.py	2021-05-06 00:19:10 UTC (rev 277060)
+++ trunk/Tools/Scripts/webkitpy/api_tests/run_api_tests.py	2021-05-06 00:22:16 UTC (rev 277061)
@@ -1,164 +0,0 @@
-# Copyright (C) 2018 Apple Inc. All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions
-# are met:
-# 1.  Redistributions of source code must retain the above copyright
-#    notice, this list of conditions and the following disclaimer.
-# 2.  Redistributions in binary form must reproduce the above copyright
-#    notice, this list of conditions and the following disclaimer in the
-#    documentation and/or other materials provided with the distribution.
-#
-# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
-# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-# DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
-# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
-# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
-# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 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.
-
-from __future__ import print_function
-import logging
-import optparse
-import sys
-import traceback
-
-from webkitpy.api_tests.manager import Manager
-from webkitpy.common.host import Host
-from webkitpy.layout_tests.views.metered_stream import MeteredStream
-from webkitpy.port import configuration_options, platform_options, base, win
-from webkitpy.results.options import upload_options
-
-EXCEPTIONAL_EXIT_STATUS = -1
-INTERRUPT_EXIT_STATUS = -2
-
-_log = logging.getLogger(__name__)
-
-
-def main(argv, stdout, stderr):
-    options, args = parse_args(argv)
-    host = Host()
-
-    try:
-        options.webkit_test_runner = True
-        port = host.port_factory.get(options.platform, options)
-    except NotImplementedError as e:
-        print(str(e), file=stderr)
-        return EXCEPTIONAL_EXIT_STATUS
-
-    # Some platforms do not support API tests
-    does_not_support_api_tests = ['ios-device']
-    if port.operating_system() in does_not_support_api_tests:
-        print('{} cannot run API tests'.format(port.operating_system()), file=stderr)
-        return EXCEPTIONAL_EXIT_STATUS
-
-    try:
-        return run(port, options, args, stderr)
-    except KeyboardInterrupt:
-        return INTERRUPT_EXIT_STATUS
-    except BaseException as e:
-        if isinstance(e, Exception):
-            print('\n%s raised: %s' % (e.__class__.__name__, str(e)), file=stderr)
-            traceback.print_exc(file=stderr)
-        return EXCEPTIONAL_EXIT_STATUS
-
-
-def run(port, options, args, logging_stream):
-    logger = logging.getLogger()
-    logger.setLevel(logging.DEBUG if options.verbose else logging.ERROR if options.quiet else logging.INFO)
-
-    try:
-        stream = MeteredStream(logging_stream, options.verbose, logger=logger, number_of_columns=port.host.platform.terminal_width(), print_timestamps=options.timestamps)
-        manager = Manager(port, options, stream)
-
-        result = manager.run(args, json_output=options.json_output)
-        _log.debug("Testing completed, Exit status: %d" % result)
-        return result
-    finally:
-        stream.cleanup()
-
-
-def parse_args(args):
-    option_group_definitions = []
-
-    option_group_definitions.append(('Platform options', platform_options()))
-    option_group_definitions.append(('Configuration options', configuration_options()))
-    option_group_definitions.append(('Printing Options', [
-        optparse.make_option('-q', '--quiet', action='', default=False,
-                             help='Run quietly (errors, warnings, and progress only)'),
-        optparse.make_option('-v', '--verbose', action='', default=False,
-                             help='Enable verbose printing'),
-        optparse.make_option('--timestamps', action='', default=False,
-                             help='Print timestamps for each logged line'),
-        optparse.make_option('--json-output', action='', default=None,
-                             help='Save test results as JSON to file'),
-    ]))
-
-    option_group_definitions.append(('WebKit Options', [
-        optparse.make_option('-g', '--guard-malloc', action='', default=False,
-                             help='Enable Guard Malloc (OS X only)'),
-        optparse.make_option('--root', action='',
-                             help='Path to a directory containing the executables needed to run tests.'),
-    ]))
-
-    option_group_definitions.append(('Testing Options', [
-        optparse.make_option('--wtf-only', action='', const='TestWTF', dest='api_binary',
-                             help='Only build, check and run TestWTF'),
-        optparse.make_option('--webkit-only', action='', const='TestWebKitAPI', dest='api_binary',
-                             help='Only check and run TestWebKitAPI'),
-        optparse.make_option('--web-core-only', action='', const='TestWebCore', dest='api_binary',
-                             help='Only check and run TestWebCore.exe (Windows only)'),
-        optparse.make_option('--webkit-legacy-only', action='', const='TestWebKitLegacy', dest='api_binary',
-                             help='Only check and run TestWebKitLegacy.exe (Windows only)'),
-        optparse.make_option('-d', '--dump', action='', default=False,
-                             help='Dump all test names without running them'),
-        optparse.make_option('--build', dest='build', action='', default=True,
-                             help='Check to ensure the build is up-to-date (default).'),
-        optparse.make_option('--no-build', dest='build', action='',
-                             help="Don't check to see if the build is up-to-date."),
-        optparse.make_option('--timeout', default=30,
-                             help='Number of seconds to wait before a test times out'),
-        optparse.make_option('--no-timeout', dest='timeout', action='',
-                             help='Disable timeouts for all tests'),
-        optparse.make_option('--iterations', type='int', default=1, help='Number of times to run the set of tests (e.g. ABCABCABC)'),
-        optparse.make_option('--repeat-each', type='int', default=1, help='Number of times to run each test (e.g. AAABBBCCC)'),
-
-        # FIXME: Remove the default, API tests should be multiprocess
-        optparse.make_option('--child-processes', default=1,
-                             help='Number of processes to run in parallel.'),
-
-        # FIXME: Default should be false, API tests should not be forced to run singly
-        optparse.make_option('--run-singly', action='', default=True,
-                             help='Run a separate process for each test'),
-
-        optparse.make_option('--force', action='', default=False,
-                             help='Run all tests, even DISABLED tests'),
-        optparse.make_option('--additional-env-var', type='string', action='', default=[],
-                             help='Passes that environment variable to the tests (--additional-env-var=NAME=VALUE)'),
-    ]))
-    option_group_definitions.append(('Upload Options', upload_options()))
-
-    option_parser = optparse.OptionParser(
-        usage='run-api-tests [options] [<test names>...]',
-        description="""By default, run-api-tests will run all API tests. It also allows the user to specify tests of the \
-format <suite>.<test> or <canonicalized binary name>.<suite>.<test>. Note that in the case where a binary is not \
-specified, one will be inferred by listing all available tests. Specifying just a binary or just a suite will cause every \
-test contained within to be run. The canonicalized binary name is the binary name with any filename extension \
-stripped. For Unix ports, these binaries are {} and {}. For Windows ports, they are {} and {}.""".format(
-            ', '.join(base.Port.API_TEST_BINARY_NAMES[:-1]), base.Port.API_TEST_BINARY_NAMES[-1],
-            ', '.join(win.WinPort.API_TEST_BINARY_NAMES[:-1]), win.WinPort.API_TEST_BINARY_NAMES[-1],
-    ))
-
-    for group_name, group_options in option_group_definitions:
-        option_group = optparse.OptionGroup(option_parser, group_name)
-        option_group.add_options(group_options)
-        option_parser.add_option_group(option_group)
-
-    return option_parser.parse_args(args)
-
-
-if __name__ == '__main__':
-    sys.exit(main(sys.argv[1:], sys.stdout, sys.stderr))

Modified: trunk/Tools/Scripts/webkitpy/api_tests/runner.py (277060 => 277061)


--- trunk/Tools/Scripts/webkitpy/api_tests/runner.py	2021-05-06 00:19:10 UTC (rev 277060)
+++ trunk/Tools/Scripts/webkitpy/api_tests/runner.py	2021-05-06 00:22:16 UTC (rev 277061)
@@ -1,4 +1,4 @@
-# Copyright (C) 2018 Apple Inc. All rights reserved.
+# Copyright (C) 2018-2021 Apple Inc. All rights reserved.
 #
 # Redistribution and use in source and binary forms, with or without
 # modification, are permitted provided that the following conditions
@@ -25,13 +25,35 @@
 import time
 
 from webkitcorepy import string_utils
+from webkitcorepy import TaskPool
 
-from webkitpy.common import message_pool
 from webkitpy.common.iteration_compatibility import iteritems
 from webkitpy.port.server_process import ServerProcess, _log as server_process_logger
 from webkitpy.xcode.simulated_device import SimulatedDeviceManager
 
+_log = logging.getLogger(__name__)
 
+
+def setup_shard(port=None):
+    return _Worker.setup(port=port)
+
+
+def run_shard(name, *tests):
+    return _Worker.instance.run(name, *tests)
+
+
+def report_result(worker, test, status, output):
+    if status == Runner.STATUS_PASSED and (not output or Runner.instance.port.get_option('quiet')):
+        Runner.instance.printer.write_update('{} {} {}'.format(worker, test, Runner.NAME_FOR_STATUS[status]))
+    else:
+        Runner.instance.printer.writeln('{} {} {}'.format(worker, test, Runner.NAME_FOR_STATUS[status]))
+    Runner.instance.results[test] = status, output
+
+
+def teardown_shard():
+    return _Worker.teardown()
+
+
 class Runner(object):
     STATUS_PASSED = 0
     STATUS_FAILED = 1
@@ -48,6 +70,8 @@
         'Disabled',
     ]
 
+    instance = None
+
     def __init__(self, port, printer):
         self.port = port
         self.printer = printer
@@ -56,16 +80,6 @@
         self._has_logged_for_test = True  # Suppress an empty line between "Running tests" and the first test's output.
         self.results = {}
 
-    @staticmethod
-    def _shard_tests(tests):
-        shards = {}
-        for test in tests:
-            shard_prefix = '.'.join(test.split('.')[:-1])
-            if shard_prefix not in shards:
-                shards[shard_prefix] = []
-            shards[shard_prefix].append(test)
-        return shards
-
     # FIXME API tests should run as an app, we won't need this function <https://bugs.webkit.org/show_bug.cgi?id=175204>
     @staticmethod
     def command_for_port(port, args):
@@ -80,6 +94,16 @@
             args[0] = os.path.splitext(args[0])[0] + '.exe'
         return args
 
+    @staticmethod
+    def _shard_tests(tests):
+        shards = {}
+        for test in tests:
+            shard_prefix = '.'.join(test.split('.')[:-1])
+            if shard_prefix not in shards:
+                shards[shard_prefix] = []
+            shards[shard_prefix].append(test)
+        return shards
+
     def run(self, tests, num_workers):
         if not tests:
             return
@@ -91,45 +115,24 @@
         server_process_logger.setLevel(logging.CRITICAL)
 
         try:
+            if Runner.instance:
+                raise RuntimeError('Cannot nest API test runners')
+            Runner.instance = self
             self._num_workers = min(num_workers, len(shards))
-            with message_pool.get(self, lambda caller: _Worker(caller, self.port, shards), self._num_workers) as pool:
-                pool.run(('test', shard) for shard, _ in iteritems(shards))
+
+            with TaskPool(
+                workers=self._num_workers,
+                setup=setup_shard, setupkwargs=dict(port=self.port),
+                teardown=teardown_shard,
+            ) as pool:
+                for name, tests in iteritems(shards):
+                    pool.do(run_shard, name, *tests)
+                pool.wait()
+
         finally:
             server_process_logger.setLevel(original_level)
+            Runner.instance = None
 
-
-    def handle(self, message_name, source, test_name=None, status=0, output=''):
-        if message_name == 'did_spawn_worker':
-            return
-
-        source = '' if self._num_workers == 1 else source + ' '
-        will_stream_logs = self._num_workers == 1 and self.port.get_option('verbose')
-        if message_name == 'ended_test':
-            update = '{}{} {}'.format(source, test_name, Runner.NAME_FOR_STATUS[status])
-
-            # Don't print test output if --quiet.
-            if status != Runner.STATUS_PASSED or (output and not self.port.get_option('quiet')):
-                if not will_stream_logs:
-                    for line in output.splitlines():
-                        if not self._has_logged_for_test:
-                            self._has_logged_for_test = True
-                            self.printer.writeln(source)
-                        self.printer.writeln('{}    {}'.format(source, line))
-                self.printer.writeln(update)
-            else:
-                self.printer.write_update(update)
-            self.tests_run += 1
-            self.results[test_name] = (status, output)
-            self._has_logged_for_test = False
-
-        if message_name == 'log' and will_stream_logs:
-            for line in output.splitlines():
-                if not self._has_logged_for_test:
-                    self._has_logged_for_test = True
-                    self.printer.writeln(source)
-                self.printer.writeln('{}    {}'.format(source, line))
-
-
     def result_map_by_status(self, status=None):
         map = {}
         for test_name, result in iteritems(self.results):
@@ -139,17 +142,25 @@
 
 
 class _Worker(object):
-    def __init__(self, caller, port, shard_map):
-        self._caller = caller
+    instance = None
+
+    @classmethod
+    def setup(cls, port=None):
+        cls.instance = cls(port)
+
+    @classmethod
+    def teardown(cls):
+        cls.instance = None
+
+    def __init__(self, port):
         self._port = port
         self.host = port.host
-        self._shard_map = shard_map
 
         # ServerProcess doesn't allow for a timeout of 'None,' this uses a week instead of None.
         self._timeout = int(self._port.get_option('timeout')) if self._port.get_option('timeout') else 60 * 24 * 7
 
-    @staticmethod
-    def _filter_noisy_output(output):
+    @classmethod
+    def _filter_noisy_output(cls, output):
         result = ''
         for line in output.splitlines():
             if line.lstrip().startswith('objc['):
@@ -167,13 +178,14 @@
         if test.split('.')[1].startswith('DISABLED_') and not self._port.get_option('force'):
             status = Runner.STATUS_DISABLED
 
+        stdout_buffer = ''
+        stderr_buffer = ''
+
         try:
             deadline = time.time() + self._timeout
             if status != Runner.STATUS_DISABLED:
                 server_process.start()
 
-            stdout_buffer = ''
-            stderr_buffer = ''
             while status == Runner.STATUS_RUNNING:
                 stdout_line, stderr_line = server_process.read_either_stdout_or_stderr_line(deadline)
                 if not stderr_line and not stdout_line:
@@ -182,7 +194,7 @@
                 if stderr_line:
                     stderr_line = string_utils.decode(stderr_line, target_type=str)
                     stderr_buffer += stderr_line
-                    self.post('log', output=stderr_line[:-1])
+                    _log.error(stderr_line[:-1])
                 if stdout_line:
                     stdout_line = string_utils.decode(stdout_line, target_type=str)
                     if '**PASS**' in stdout_line:
@@ -191,7 +203,7 @@
                         status = Runner.STATUS_FAILED
                     else:
                         stdout_buffer += stdout_line
-                        self.post('log', output=stdout_line[:-1])
+                        _log.error(stdout_line[:-1])
 
             if status == Runner.STATUS_DISABLED:
                 pass
@@ -205,14 +217,21 @@
         finally:
             remaining_stderr = string_utils.decode(server_process.pop_all_buffered_stderr(), target_type=str)
             remaining_stdout = string_utils.decode(server_process.pop_all_buffered_stdout(), target_type=str)
-            self.post('log', output=remaining_stderr + remaining_stdout)
+            for line in (remaining_stdout + remaining_stderr).splitlines(False):
+                _log.error(line)
             output_buffer = stderr_buffer + stdout_buffer + remaining_stderr + remaining_stdout
             server_process.stop()
 
-        self.post('ended_test', '{}.{}'.format(binary_name, test), status, self._filter_noisy_output(output_buffer))
+        TaskPool.Process.queue.send(TaskPool.Task(
+            report_result, None, TaskPool.Process.name,
+            '{}.{}'.format(binary_name, test),
+            status,
+            self._filter_noisy_output(output_buffer),
+        ))
 
-    def _run_shard_with_binary(self, binary_name, tests):
-        remaining_tests = list(tests)
+    def run(self, name, *tests):
+        binary_name = name.split('.')[0]
+        remaining_tests = ['.'.join(test.split('.')[1:]) for test in tests]
 
         # Try to run the shard in a single process.
         while remaining_tests and not self._port.get_option('run_singly'):
@@ -219,8 +238,9 @@
             starting_length = len(remaining_tests)
             server_process = ServerProcess(
                 self._port, binary_name,
-                Runner.command_for_port(self._port, [self._port._build_path(binary_name), '--gtest_filter={}'.format(':'.join(remaining_tests))]),
-                env=self._port.environment_for_api_tests())
+                Runner.command_for_port(self._port, [
+                    self._port._build_path(binary_name), '--gtest_filter={}'.format(':'.join(remaining_tests))
+                ]), env=self._port.environment_for_api_tests())
 
             try:
                 deadline = time.time() + self._timeout
@@ -230,7 +250,7 @@
 
                 server_process.start()
                 while remaining_tests:
-                    stdout = server_process.read_stdout_line(deadline)
+                    stdout = string_utils.decode(server_process.read_stdout_line(deadline), target_type=str)
 
                     # If we've triggered a timeout, we don't know which test caused it. Break out and run singly.
                     if stdout is None and server_process.timed_out:
@@ -251,7 +271,14 @@
                         continue
                     if last_test is not None:
                         remaining_tests.remove(last_test)
-                        self.post('ended_test', '{}.{}'.format(binary_name, last_test), last_status, stdout_buffer)
+
+                        for line in stdout_buffer.splitlines(False):
+                            _log.error(line)
+                        TaskPool.Process.queue.send(TaskPool.Task(
+                            report_result, None, TaskPool.Process.name,
+                            '{}.{}'.format(binary_name, last_test),
+                            last_status, stdout_buffer,
+                        ))
                         deadline = time.time() + self._timeout
                         stdout_buffer = ''
 
@@ -264,11 +291,18 @@
                 # We assume that stderr is only relevant if there is a crash (meaning we triggered an assert)
                 if last_test:
                     remaining_tests.remove(last_test)
-                    stdout_buffer += server_process.pop_all_buffered_stdout()
-                    stderr_buffer = server_process.pop_all_buffered_stderr() if last_status == Runner.STATUS_CRASHED else ''
-                    self.post('log', output=stdout_buffer + stderr_buffer)
-                    self.post('ended_test', '{}.{}'.format(binary_name, last_test), last_status, self._filter_noisy_output(stdout_buffer + stderr_buffer))
+                    stdout_buffer += string_utils.decode(server_process.pop_all_buffered_stdout(), target_type=str)
+                    stderr_buffer = string_utils.decode(server_process.pop_all_buffered_stderr(), target_type=str) if last_status == Runner.STATUS_CRASHED else ''
+                    for line in (stdout_buffer + stderr_buffer).splitlines(keepends=False):
+                        _log.error(line)
 
+                    TaskPool.Process.queue.send(TaskPool.Task(
+                        report_result, None, TaskPool.Process.name,
+                        '{}.{}'.format(binary_name, last_test),
+                        last_status,
+                        self._filter_noisy_output(stdout_buffer + stderr_buffer),
+                    ))
+
                 if server_process.timed_out:
                     break
 
@@ -281,19 +315,3 @@
         # Now, just try and run the rest of the tests singly.
         for test in remaining_tests:
             self._run_single_test(binary_name, test)
-
-    def post(self, message_name, test_name=None, status=0, output=''):
-        self._caller.post(message_name, test_name, status, output)
-
-    def handle(self, message_name, source, shard_name):
-        assert message_name == 'test'
-        self.post('started_shard', shard_name)
-
-        binary_map = {}
-        for test in self._shard_map[shard_name]:
-            split_test_name = test.split('.')
-            if split_test_name[0] not in binary_map:
-                binary_map[split_test_name[0]] = []
-            binary_map[split_test_name[0]].append('.'.join(split_test_name[1:]))
-        for binary_name, test_list in binary_map.items():
-            self._run_shard_with_binary(binary_name, test_list)
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to