Title: [254340] trunk/Tools
Revision
254340
Author
[email protected]
Date
2020-01-10 08:16:19 -0800 (Fri, 10 Jan 2020)

Log Message

Python 3: Add support to run-webkit-tests
https://bugs.webkit.org/show_bug.cgi?id=205291

Reviewed by Stephanie Lewis.

* Scripts/test-webkitpy-python3: Add webkitpy.layout_tests.
* Scripts/webkitpy/common/message_pool.py:
(_MessagePool._loop): Move exception inside of loop.
(_Message.__repr__): Use .format strings.
(_Worker.__init__): Ditto.
* Scripts/webkitpy/common/wavediff.py:
(WaveDiff.__init__): Use Python 3 compatible BytesIO and StringIO.
* Scripts/webkitpy/layout_tests/controllers/layout_test_finder.py:
(LayoutTestFinder._read_test_names_from_file): Use .format string.
(LayoutTestFinder.split_into_chunks): Explicitly use integer division.
* Scripts/webkitpy/layout_tests/controllers/manager.py:
(Manager._get_test_inputs): Use range over xrange.
* Scripts/webkitpy/layout_tests/controllers/single_test_runner.py:
(SingleTestRunner._handle_error): Use .format strings.
* Scripts/webkitpy/layout_tests/layout_package/json_layout_results_generator.py:
(JSONLayoutResultsGenerator._insert_failure_summaries): Use Python 3 compatible itervalues.
* Scripts/webkitpy/layout_tests/models/test_results.py:
(TestResult.__init__): Sort type list.
* Scripts/webkitpy/layout_tests/run_webkit_tests_integrationtest.py:
(RunTest.setUp): Multiple processes are buggy in test-webkitpy.
(RunTest.test_basic): replace buflist with getvalue().
(RunTest.test_child_processes_2): Ditto.
(RunTest.test_child_processes_min): Ditto.
(RunTest.test_keyboard_interrupt): Ditto.
(RunTest.test_missing_and_unexpected_results): Compare dictionaries instead of json strings.
(RunTest.test_crash_with_stderr): Ditto.
(RunTest.test_reftest_should_not_use_naming_convention_if_not_listed_in_reftestlist): Ditto.
(EndToEndTest.test_reftest_with_two_notrefs): Ditto.
* Scripts/webkitpy/layout_tests/views/metered_stream.py:
(MeteredStream.write): Flush stream after writing.
(MeteredStream._erase_last_partial_line): Ditto.
* Scripts/webkitpy/layout_tests/views/printing.py:
(Printer._print_directory_timings): Can't compare string to integer.
(Printer._print_statistics_for_test_timings): Use integer division.
* Scripts/webkitpy/port/darwin.py:
(DarwinPort._merge_crash_logs): Use items over iteritems.
* Scripts/webkitpy/port/device.py:
(Device.__hash__): Allow hashing of devices.
* Scripts/webkitpy/port/device_port.py:
(DevicePort._install): Use range over xrange.
(DevicePort.setup_test_run): Ditto.
(DevicePort.clean_up_test_run): Ditto.
* Scripts/webkitpy/port/driver.py:
(DriverOutput.__init__): Text should be decoded, audio encoded.
(Driver.__init__):
(Driver.run_test):
(Driver._parse_child_processes_output): Output is byte array.
(Driver._check_for_driver_timeout): Ditto.
(Driver._check_for_address_sanitizer_violation): Error lines are byte arrays.
(Driver._check_for_driver_crash_or_unresponsiveness): Ditto.
(Driver._read_optional_image_block): If a block is base64 encoded, we want the decoded
content, otherwise, we want the encoded content.
(Driver._read_header): Lines are byte arrays, decode them before processing.
(Driver._process_stdout_line): Blocks are byte arrays.
(Driver._strip_eof): Lines should be byte arrays, not strings.
(Driver._read_block): Standardize encoding in blocks.
(ContentBlock.__init__): Content should be a byte array.
(ContentBlock.decode_content): Attempt to decode content.
* Scripts/webkitpy/port/driver_unittest.py:
(DriverTest.test_read_binary_block): Content should be encoded.
(DriverTest.test_read_base64_block): Ditto.
(DriverTest.test_check_for_driver_crash): ServerProcess output should be a byte array.
* Scripts/webkitpy/port/image_diff.py:
(ImageDiffer.diff_image): ImageDiff output is in byte arrays.
(ImageDiffer._read): Ditto.
* Scripts/webkitpy/port/server_process.py:
(ServerProcess.write): Encode data before writing it.
* Scripts/webkitpy/port/server_process_mock.py:
(MockServerProcess.__init__): Convert string mock output to bytes.
(MockServerProcess.read_stdout_line): Stdout lines are byte arrays.
(MockServerProcess.read_stdout): Ditto.
* Scripts/webkitpy/port/simulator_process.py:
(SimulatorProcess.NonBlockingFileFromSocket.close): Don't double close socket in Python 3.
(SimulatorProcess._start): Stdin should be a binary stream.
* Scripts/webkitpy/port/test.py:
(unit_test_list): Convert audio streams to byte arrays.
* Scripts/webkitpy/xcode/simulated_device.py:
(SimulatedDevice.is_usable): Decode xcrun output.
(SimulatedDevice.install_app): Use xrange over range.

Modified Paths

Diff

Modified: trunk/Tools/ChangeLog (254339 => 254340)


--- trunk/Tools/ChangeLog	2020-01-10 15:46:26 UTC (rev 254339)
+++ trunk/Tools/ChangeLog	2020-01-10 16:16:19 UTC (rev 254340)
@@ -1,3 +1,90 @@
+2020-01-10  Jonathan Bedard  <[email protected]>
+
+        Python 3: Add support to run-webkit-tests
+        https://bugs.webkit.org/show_bug.cgi?id=205291
+
+        Reviewed by Stephanie Lewis.
+
+        * Scripts/test-webkitpy-python3: Add webkitpy.layout_tests.
+        * Scripts/webkitpy/common/message_pool.py:
+        (_MessagePool._loop): Move exception inside of loop.
+        (_Message.__repr__): Use .format strings.
+        (_Worker.__init__): Ditto.
+        * Scripts/webkitpy/common/wavediff.py:
+        (WaveDiff.__init__): Use Python 3 compatible BytesIO and StringIO.
+        * Scripts/webkitpy/layout_tests/controllers/layout_test_finder.py:
+        (LayoutTestFinder._read_test_names_from_file): Use .format string.
+        (LayoutTestFinder.split_into_chunks): Explicitly use integer division.
+        * Scripts/webkitpy/layout_tests/controllers/manager.py:
+        (Manager._get_test_inputs): Use range over xrange.
+        * Scripts/webkitpy/layout_tests/controllers/single_test_runner.py:
+        (SingleTestRunner._handle_error): Use .format strings.
+        * Scripts/webkitpy/layout_tests/layout_package/json_layout_results_generator.py:
+        (JSONLayoutResultsGenerator._insert_failure_summaries): Use Python 3 compatible itervalues.
+        * Scripts/webkitpy/layout_tests/models/test_results.py:
+        (TestResult.__init__): Sort type list.
+        * Scripts/webkitpy/layout_tests/run_webkit_tests_integrationtest.py:
+        (RunTest.setUp): Multiple processes are buggy in test-webkitpy.
+        (RunTest.test_basic): replace buflist with getvalue().
+        (RunTest.test_child_processes_2): Ditto.
+        (RunTest.test_child_processes_min): Ditto.
+        (RunTest.test_keyboard_interrupt): Ditto.
+        (RunTest.test_missing_and_unexpected_results): Compare dictionaries instead of json strings.
+        (RunTest.test_crash_with_stderr): Ditto.
+        (RunTest.test_reftest_should_not_use_naming_convention_if_not_listed_in_reftestlist): Ditto.
+        (EndToEndTest.test_reftest_with_two_notrefs): Ditto.
+        * Scripts/webkitpy/layout_tests/views/metered_stream.py:
+        (MeteredStream.write): Flush stream after writing.
+        (MeteredStream._erase_last_partial_line): Ditto.
+        * Scripts/webkitpy/layout_tests/views/printing.py:
+        (Printer._print_directory_timings): Can't compare string to integer.
+        (Printer._print_statistics_for_test_timings): Use integer division.
+        * Scripts/webkitpy/port/darwin.py:
+        (DarwinPort._merge_crash_logs): Use items over iteritems.
+        * Scripts/webkitpy/port/device.py:
+        (Device.__hash__): Allow hashing of devices.
+        * Scripts/webkitpy/port/device_port.py:
+        (DevicePort._install): Use range over xrange.
+        (DevicePort.setup_test_run): Ditto.
+        (DevicePort.clean_up_test_run): Ditto.
+        * Scripts/webkitpy/port/driver.py:
+        (DriverOutput.__init__): Text should be decoded, audio encoded.
+        (Driver.__init__):
+        (Driver.run_test):
+        (Driver._parse_child_processes_output): Output is byte array.
+        (Driver._check_for_driver_timeout): Ditto.
+        (Driver._check_for_address_sanitizer_violation): Error lines are byte arrays.
+        (Driver._check_for_driver_crash_or_unresponsiveness): Ditto.
+        (Driver._read_optional_image_block): If a block is base64 encoded, we want the decoded
+        content, otherwise, we want the encoded content.
+        (Driver._read_header): Lines are byte arrays, decode them before processing.
+        (Driver._process_stdout_line): Blocks are byte arrays.
+        (Driver._strip_eof): Lines should be byte arrays, not strings.
+        (Driver._read_block): Standardize encoding in blocks.
+        (ContentBlock.__init__): Content should be a byte array.
+        (ContentBlock.decode_content): Attempt to decode content.
+        * Scripts/webkitpy/port/driver_unittest.py:
+        (DriverTest.test_read_binary_block): Content should be encoded.
+        (DriverTest.test_read_base64_block): Ditto.
+        (DriverTest.test_check_for_driver_crash): ServerProcess output should be a byte array.
+        * Scripts/webkitpy/port/image_diff.py:
+        (ImageDiffer.diff_image): ImageDiff output is in byte arrays.
+        (ImageDiffer._read): Ditto.
+        * Scripts/webkitpy/port/server_process.py:
+        (ServerProcess.write): Encode data before writing it.
+        * Scripts/webkitpy/port/server_process_mock.py:
+        (MockServerProcess.__init__): Convert string mock output to bytes.
+        (MockServerProcess.read_stdout_line): Stdout lines are byte arrays.
+        (MockServerProcess.read_stdout): Ditto.
+        * Scripts/webkitpy/port/simulator_process.py:
+        (SimulatorProcess.NonBlockingFileFromSocket.close): Don't double close socket in Python 3.
+        (SimulatorProcess._start): Stdin should be a binary stream.
+        * Scripts/webkitpy/port/test.py:
+        (unit_test_list): Convert audio streams to byte arrays.
+        * Scripts/webkitpy/xcode/simulated_device.py:
+        (SimulatedDevice.is_usable): Decode xcrun output.
+        (SimulatedDevice.install_app): Use xrange over range.
+
 2020-01-10  Adrian Perez de Castro  <[email protected]>
 
         [Flatpak] Update IceCC to version 1.2

Modified: trunk/Tools/Scripts/test-webkitpy-python3 (254339 => 254340)


--- trunk/Tools/Scripts/test-webkitpy-python3	2020-01-10 15:46:26 UTC (rev 254339)
+++ trunk/Tools/Scripts/test-webkitpy-python3	2020-01-10 16:16:19 UTC (rev 254340)
@@ -37,11 +37,7 @@
   'webkitpy.benchmark_runner',
   'webkitpy.browserperfdash',
   'webkitpy.common',
-  'webkitpy.layout_tests.controllers',
-  'webkitpy.layout_tests.layout_package',
-  'webkitpy.layout_tests.models',
-  'webkitpy.layout_tests.servers',
-  'webkitpy.layout_tests.views',
+  'webkitpy.layout_tests',
   'webkitpy.performance_tests',
   'webkitpy.port',
   'webkitpy.results',

Modified: trunk/Tools/Scripts/webkitpy/common/message_pool.py (254339 => 254340)


--- trunk/Tools/Scripts/webkitpy/common/message_pool.py	2020-01-10 15:46:26 UTC (rev 254339)
+++ trunk/Tools/Scripts/webkitpy/common/message_pool.py	2020-01-10 16:16:19 UTC (rev 254340)
@@ -185,22 +185,24 @@
             return False
 
     def _loop(self, block):
-        try:
-            while True:
-                if len(self._workers_stopped) == len(self._workers):
-                    block = False
+        while True:
+            if len(self._workers_stopped) == len(self._workers):
+                block = False
+
+            try:
                 message = self._messages_to_manager.get(block)
-                self._log_messages(message.logs)
-                if message.from_user:
-                    self._caller.handle(message.name, message.src, *message.args)
-                    continue
-                method = getattr(self, '_handle_' + message.name)
-                assert method, 'bad message %s' % repr(message)
-                method(message.src, *message.args)
-        except queue.Empty:
-            pass
+            except queue.Empty:
+                break
 
+            self._log_messages(message.logs)
+            if message.from_user:
+                self._caller.handle(message.name, message.src, *message.args)
+                continue
+            method = getattr(self, '_handle_' + message.name)
+            assert method, 'bad message %s' % repr(message)
+            method(message.src, *message.args)
 
+
 class WorkerException(BaseException):
     """Raised when we receive an unexpected/unknown exception from a worker."""
     pass
@@ -215,7 +217,13 @@
         self.logs = logs
 
     def __repr__(self):
-        return '_Message(src="" name=%s, args=%s, from_user=%s, logs=%s)' % (self.src, self.name, self.args, self.from_user, self.logs)
+        return '_Message(src="" name={name}, args={args}, from_user={from_user}, logs={logs})'.format(
+            src=""
+            name=self.name,
+            args=self.args,
+            from_user=self.from_user,
+            logs=self.logs,
+        )
 
 
 class _Worker(multiprocessing.Process):
@@ -223,7 +231,7 @@
         super(_Worker, self).__init__()
         self.host = host
         self.worker_number = worker_number
-        self.name = 'worker/%d' % worker_number
+        self.name = 'worker/{}'.format(worker_number)
         self.log_messages = []
         self.log_level = log_level
         self._running_inline = running_inline

Modified: trunk/Tools/Scripts/webkitpy/common/wavediff.py (254339 => 254340)


--- trunk/Tools/Scripts/webkitpy/common/wavediff.py	2020-01-10 15:46:26 UTC (rev 254339)
+++ trunk/Tools/Scripts/webkitpy/common/wavediff.py	2020-01-10 16:16:19 UTC (rev 254340)
@@ -25,7 +25,7 @@
 import tempfile
 import wave
 
-from io import BytesIO
+from webkitpy.common.unicode_compatibility import BytesIO, StringIO
 
 
 class WaveDiff(object):
@@ -35,12 +35,12 @@
     _tolerance = 1
 
     def __init__(self, in1, in2):
-        if isinstance(in1, file):
-            waveFile1 = wave.open(in1, 'rb')
+        if isinstance(in1, str):
+            waveFile1 = wave.open(StringIO(in1), 'r')
         else:
             waveFile1 = wave.open(BytesIO(in1), 'rb')
-        if isinstance(in2, file):
-            waveFile1 = wave.open(in2, 'rb')
+        if isinstance(in2, str):
+            waveFile2 = wave.open(StringIO(in2), 'r')
         else:
             waveFile2 = wave.open(BytesIO(in2), 'rb')
 
@@ -66,12 +66,12 @@
 
         allData1 = self._readSamples(waveFile1, sampleWidth1, frameCount1 * channelCount1)
         allData2 = self._readSamples(waveFile2, sampleWidth2, frameCount2 * channelCount2)
-        results = map(self._diffSample, allData1, allData2, xrange(max(frameCount1 * channelCount1, frameCount2 * channelCount2)))
+        results = list(map(self._diffSample, allData1, allData2, range(max(frameCount1 * channelCount1, frameCount2 * channelCount2))))
 
         cumulativeSampleDiff = sum(results)
-        differingSampleCount = len(filter(bool, results))
+        differingSampleCount = len(list(filter(bool, results)))
         self._filesAreIdentical = not differingSampleCount
-        self._filesAreIdenticalWithinTolerance = not len(filter(lambda x: x > self._tolerance, results))
+        self._filesAreIdenticalWithinTolerance = not len(list(filter(lambda x: x > self._tolerance, results)))
 
         if differingSampleCount:
             self._diff += '\n'

Modified: trunk/Tools/Scripts/webkitpy/layout_tests/controllers/layout_test_finder.py (254339 => 254340)


--- trunk/Tools/Scripts/webkitpy/layout_tests/controllers/layout_test_finder.py	2020-01-10 15:46:26 UTC (rev 254339)
+++ trunk/Tools/Scripts/webkitpy/layout_tests/controllers/layout_test_finder.py	2020-01-10 16:16:19 UTC (rev 254340)
@@ -102,7 +102,7 @@
             except IOError as e:
                 if e.errno == errno.ENOENT:
                     _log.critical('')
-                    _log.critical('--test-list file "%s" not found' % file)
+                    _log.critical('--test-list file "{}" not found'.format(filenames))
                 raise
         return tests
 
@@ -176,7 +176,7 @@
             if rounded_tests % test_size != 0:
                 rounded_tests = (num_tests + test_size - (num_tests % test_size))
 
-            chunk_len = rounded_tests / test_size
+            chunk_len = rounded_tests // test_size
             slice_start = chunk_len * (chunk_num - 1)
             # It does not mind if we go over test_size.
 

Modified: trunk/Tools/Scripts/webkitpy/layout_tests/controllers/manager.py (254339 => 254340)


--- trunk/Tools/Scripts/webkitpy/layout_tests/controllers/manager.py	2020-01-10 15:46:26 UTC (rev 254339)
+++ trunk/Tools/Scripts/webkitpy/layout_tests/controllers/manager.py	2020-01-10 16:16:19 UTC (rev 254340)
@@ -145,9 +145,9 @@
 
     def _get_test_inputs(self, tests_to_run, repeat_each, iterations, device_type=None):
         test_inputs = []
-        for _ in xrange(iterations):
+        for _ in range(iterations):
             for test in tests_to_run:
-                for _ in xrange(repeat_each):
+                for _ in range(repeat_each):
                     test_inputs.append(self._test_input_for_file(test, device_type=device_type))
         return test_inputs
 

Modified: trunk/Tools/Scripts/webkitpy/layout_tests/controllers/single_test_runner.py (254339 => 254340)


--- trunk/Tools/Scripts/webkitpy/layout_tests/controllers/single_test_runner.py	2020-01-10 15:46:26 UTC (rev 254339)
+++ trunk/Tools/Scripts/webkitpy/layout_tests/controllers/single_test_runner.py	2020-01-10 16:16:19 UTC (rev 254340)
@@ -31,6 +31,7 @@
 import re
 import time
 
+from webkitpy.common.unicode_compatibility import decode_for
 from webkitpy.layout_tests.controllers import test_result_writer
 from webkitpy.port.driver import DriverInput, DriverOutput
 from webkitpy.layout_tests.models import test_expectations
@@ -214,7 +215,7 @@
         elif driver_output.error:
             _log.debug("%s %s output stderr lines:" % (self._worker_name, testname))
         for line in driver_output.error.splitlines():
-            _log.debug("  %s" % line)
+            _log.debug("  {}".format(decode_for(line, str)))
         return failures
 
     def _compare_output(self, expected_driver_output, driver_output):

Modified: trunk/Tools/Scripts/webkitpy/layout_tests/layout_package/json_layout_results_generator.py (254339 => 254340)


--- trunk/Tools/Scripts/webkitpy/layout_tests/layout_package/json_layout_results_generator.py	2020-01-10 15:46:26 UTC (rev 254339)
+++ trunk/Tools/Scripts/webkitpy/layout_tests/layout_package/json_layout_results_generator.py	2020-01-10 16:16:19 UTC (rev 254340)
@@ -31,6 +31,7 @@
 from webkitpy.layout_tests.layout_package import json_results_generator
 from webkitpy.layout_tests.models import test_expectations
 from webkitpy.layout_tests.models import test_failures
+from webkitpy.common.iteration_compatibility import itervalues
 
 
 class JSONLayoutResultsGenerator(json_results_generator.JSONResultsGenerator):
@@ -132,7 +133,7 @@
             self.FIXABLE)
 
         num_fixable = 0
-        for expectation in self._expectations.itervalues():
+        for expectation in itervalues(self._expectations):
             num_fixable += len(expectation.model().get_tests_with_timeline(test_expectations.NOW))
 
         self._insert_item_into_raw_list(results_for_builder, num_fixable, self.ALL_FIXABLE_COUNT)

Modified: trunk/Tools/Scripts/webkitpy/layout_tests/models/test_results.py (254339 => 254340)


--- trunk/Tools/Scripts/webkitpy/layout_tests/models/test_results.py	2020-01-10 15:46:26 UTC (rev 254339)
+++ trunk/Tools/Scripts/webkitpy/layout_tests/models/test_results.py	2020-01-10 16:16:19 UTC (rev 254340)
@@ -47,7 +47,7 @@
         self.failures = failures or []
         self.test_run_time = test_run_time or 0  # The time taken to execute the test itself.
         self.has_stderr = has_stderr
-        self.reftest_type = reftest_type or []
+        self.reftest_type = sorted(reftest_type or [])
         self.pid = pid
         self.references = references or []
 

Modified: trunk/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests_integrationtest.py (254339 => 254340)


--- trunk/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests_integrationtest.py	2020-01-10 15:46:26 UTC (rev 254339)
+++ trunk/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests_integrationtest.py	2020-01-10 16:16:19 UTC (rev 254340)
@@ -29,8 +29,10 @@
 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 import json
+import sys
 import unittest
 
+from webkitpy.common.unicode_compatibility import StringIO
 from webkitpy.common.system import outputcapture, path
 from webkitpy.common.system.crashlogs_unittest import make_mock_crash_report_darwin
 from webkitpy.common.system.systemhost import SystemHost
@@ -176,7 +178,8 @@
 
         # FIXME: Remove this when we fix test-webkitpy to work
         # properly on cygwin (bug 63846).
-        self.should_test_processes = not self._platform.is_win()
+        # FIXME: Multiprocessing doesn't do well when nested in Python 3 (https://bugs.webkit.org/show_bug.cgi?id=205280)
+        self.should_test_processes = not self._platform.is_win() and sys.version_info < (3, 0)
 
     def test_basic(self):
         options, args = parse_args(tests_included=True)
@@ -195,7 +198,7 @@
         _one_line_summary_ = "%d tests ran as expected, %d didn't:\n" % (
             details.initial_results.total - details.initial_results.expected_skips - len(details.initial_results.unexpected_results_by_name),
             len(details.initial_results.unexpected_results_by_name))
-        self.assertTrue(one_line_summary in logging_stream.buflist)
+        self.assertTrue(one_line_summary in logging_stream.getvalue())
 
         # Ensure the results were summarized properly.
         self.assertEqual(details.summarized_results['num_regressions'], details.exit_code)
@@ -219,7 +222,7 @@
         if self.should_test_processes:
             _, regular_output, _ = logging_run(
                 ['--debug-rwt-logging', '--child-processes', '2'], shared_port=False)
-            self.assertTrue(any(['Running 2 ' in line for line in regular_output.buflist]))
+            self.assertTrue(any(['Running 2 ' in line for line in regular_output.getvalue().splitlines()]))
 
     def test_child_processes_min(self):
         if self.should_test_processes:
@@ -226,7 +229,7 @@
             _, regular_output, _ = logging_run(
                 ['--debug-rwt-logging', '--child-processes', '2', '-i', 'passes/passes', 'passes'],
                 tests_included=True, shared_port=False)
-            self.assertTrue(any(['Running 1 ' in line for line in regular_output.buflist]))
+            self.assertTrue(any(['Running 1 ' in line for line in regular_output.getvalue().splitlines()]))
 
     def test_dryrun(self):
         tests_run = get_tests_run(['--dry-run'])
@@ -272,7 +275,7 @@
 
         if self.should_test_processes:
             _, regular_output, _ = logging_run(['failures/expected/keyboard.html', 'passes/text.html', '--child-processes', '2', '--force'], tests_included=True, shared_port=False)
-            self.assertTrue(any(['Interrupted, exiting' in line for line in regular_output.buflist]))
+            self.assertTrue(any(['Interrupted, exiting' in line for line in regular_output.getvalue().splitlines()]))
 
     def test_no_tests_found(self):
         details, err, _ = logging_run(['resources'], tests_included=True)
@@ -476,12 +479,57 @@
             tests_included=True, host=host)
         file_list = host.filesystem.written_files.keys()
         self.assertEqual(details.exit_code, 1)
-        expected_token = '"unexpected":{"text-image-checksum.html":{"report":"REGRESSION","expected":"PASS","actual":"IMAGE+TEXT","image_diff_percent":1},"missing_text.html":{"report":"MISSING","expected":"PASS","is_missing_text":true,"actual":"MISSING"}'
-        json_string = host.filesystem.read_text_file('/tmp/layout-test-results/full_results.json')
-        self.assertTrue(json_string.find(expected_token) != -1)
-        self.assertTrue(json_string.find('"num_regressions":1') != -1)
-        self.assertTrue(json_string.find('"num_flaky":0') != -1)
-        self.assertTrue(json_string.find('"num_missing":1') != -1)
+        expected_dictionary = {
+            'version': 4,
+            'fixable': 3,
+            'skipped': 0,
+            'num_passes': 0,
+            'num_flaky': 0,
+            'num_missing': 1,
+            'num_regressions': 1,
+            'uses_expectations_file': True,
+            'interrupted': False,
+            'layout_tests_dir': '/test.checkout/LayoutTests',
+            'has_pretty_patch': False,
+            'pixel_tests_enabled': True,
+            'other_crashes': {},
+            'date': '10:10AM on December 13, 2019',
+            'tests': {
+                'failures': {
+                    'expected': {
+                        'missing_image.html': {
+                            'expected': 'PASS MISSING',
+                            'actual': 'MISSING',
+                            'is_missing_image': True,
+                        },
+                    }, 'unexpected': {
+                        'missing_text.html': {
+                            'report': 'MISSING',
+                            'expected': 'PASS',
+                            'actual': 'MISSING',
+                            'is_missing_text': True,
+                        }, 'text-image-checksum.html': {
+                            'report': 'REGRESSION',
+                            'expected': 'PASS',
+                            'actual': 'IMAGE+TEXT',
+                            'image_diff_percent': 1,
+                        },
+                    },
+                },
+            },
+        }
+        actual_dictionary = json.loads(host.filesystem.read_text_file('/tmp/layout-test-results/full_results.json')[len('ADD_RESULTS('):-2])
+        self.assertEqual(
+            sorted(list(expected_dictionary['tests']['failures']['expected'])),
+            sorted(list(actual_dictionary['tests']['failures']['expected'])),
+        )
+        self.assertEqual(
+            sorted(list(expected_dictionary['tests']['failures']['unexpected'])),
+            sorted(list(actual_dictionary['tests']['failures']['unexpected'])),
+        )
+        self.assertEqual(expected_dictionary['num_regressions'], actual_dictionary['num_regressions'])
+        self.assertEqual(expected_dictionary['num_flaky'], actual_dictionary['num_flaky'])
+        self.assertEqual(expected_dictionary['num_missing'], actual_dictionary['num_missing'])
 
     def test_pixel_test_directories(self):
         host = MockHost()
@@ -517,7 +565,39 @@
     def test_crash_with_stderr(self):
         host = MockHost()
         _, regular_output, _ = logging_run(['failures/unexpected/crash-with-stderr.html'], tests_included=True, host=host)
-        self.assertTrue(host.filesystem.read_text_file('/tmp/layout-test-results/full_results.json').find('{"crash-with-stderr.html":{"report":"REGRESSION","expected":"PASS","actual":"CRASH","has_stderr":true}}') != -1)
+        actual_dictionary = json.loads(host.filesystem.read_text_file('/tmp/layout-test-results/full_results.json')[len('ADD_RESULTS('):-2])
+        expected_dictionary = {
+            'version': 4,
+            'fixable': 1,
+            'skipped': 0,
+            'num_passes': 0,
+            'num_flaky': 0,
+            'num_missing': 0,
+            'num_regressions': 1,
+            'uses_expectations_file': True,
+            'interrupted': False,
+            'layout_tests_dir': '/test.checkout/LayoutTests',
+            'has_pretty_patch': False,
+            'pixel_tests_enabled': True,
+            'other_crashes': {},
+            'date': '10:18AM on December 13, 2019',
+            'tests': {
+                'failures': {
+                    'unexpected': {
+                        'crash-with-stderr.html': {
+                            'has_stderr': True,
+                            'report': 'REGRESSION',
+                            'expected': 'PASS',
+                            'actual': 'CRASH',
+                        },
+                    },
+                },
+            },
+        }
+        self.assertEqual(
+            sorted(list(expected_dictionary['tests']['failures']['unexpected'])),
+            sorted(list(actual_dictionary['tests']['failures']['unexpected'])),
+        )
 
     def test_no_image_failure_with_image_diff(self):
         host = MockHost()
@@ -740,12 +820,67 @@
     def test_reftest_should_not_use_naming_convention_if_not_listed_in_reftestlist(self):
         host = MockHost()
         _, err, _ = logging_run(['--no-show-results', 'reftests/foo/'], tests_included=True, host=host)
-        json_string = host.filesystem.read_text_file('/tmp/layout-test-results/full_results.json')
-        self.assertTrue(json_string.find('"unlistedtest.html":{"report":"MISSING","expected":"PASS","is_missing_text":true,"actual":"MISSING","is_missing_image":true}') != -1)
-        self.assertTrue(json_string.find('"num_regressions":4') != -1)
-        self.assertTrue(json_string.find('"num_flaky":0') != -1)
-        self.assertTrue(json_string.find('"num_missing":1') != -1)
+        expected_dictionary = {
+            'version': 4,
+            'fixable': 5,
+            'skipped': 0,
+            'num_passes': 3,
+            'num_flaky': 0,
+            'num_missing': 1,
+            'num_regressions': 4,
+            'uses_expectations_file': True,
+            'interrupted': False,
+            'layout_tests_dir': '/test.checkout/LayoutTests',
+            'has_pretty_patch': False,
+            'pixel_tests_enabled': True,
+            'other_crashes': {},
+            'date': '10:27AM on December 13, 2019',
+            'tests': {
+                'reftests': {
+                    'foo': {
+                        'multiple-both-failure.html': {
+                            'reftest_type': ['!=', '=='],
+                            'report': 'REGRESSION',
+                            'expected': 'PASS',
+                            'actual': 'IMAGE',
+                        }, 'multiple-match-failure.html': {
+                            'reftest_type': ['=='],
+                            'report': 'REGRESSION',
+                            'expected': 'PASS',
+                            'actual': 'IMAGE',
+                            'image_diff_percent': 1,
+                        }, 'multiple-mismatch-failure.html': {
+                            'reftest_type': ['!='],
+                            'report': 'REGRESSION',
+                            'expected': 'PASS',
+                            'actual': 'IMAGE',
+                        }, 'test.html': {
+                            'reftest_type': ['=='],
+                            'report': 'REGRESSION',
+                            'expected': 'PASS',
+                            'actual': 'IMAGE',
+                            'image_diff_percent': None,
+                        }, 'unlistedtest.html': {
+                            'report': 'MISSING',
+                            'expected': 'PASS',
+                            'actual': 'MISSING',
+                            'is_missing_text': True,
+                            'is_missing_image': True,
+                        },
+                    },
+                },
+            },
+        }
+        actual_dictionary = json.loads(host.filesystem.read_text_file('/tmp/layout-test-results/full_results.json')[len('ADD_RESULTS('):-2])
 
+        self.assertEqual(
+            expected_dictionary['tests']['reftests']['foo']['unlistedtest.html'],
+            actual_dictionary['tests']['reftests']['foo']['unlistedtest.html'],
+        )
+        self.assertEqual(expected_dictionary['num_regressions'], actual_dictionary['num_regressions'])
+        self.assertEqual(expected_dictionary['num_flaky'], actual_dictionary['num_flaky'])
+        self.assertEqual(expected_dictionary['num_missing'], actual_dictionary['num_missing'])
+
     def test_additional_platform_directory(self):
         self.assertTrue(passing_run(['--additional-platform-directory', '/tmp/foo']))
         self.assertTrue(passing_run(['--additional-platform-directory', '/tmp/../foo']))
@@ -948,12 +1083,18 @@
         self.assertTrue("multiple-match-success.html" not in json["tests"]["reftests"]["foo"])
         self.assertTrue("multiple-mismatch-success.html" not in json["tests"]["reftests"]["foo"])
         self.assertTrue("multiple-both-success.html" not in json["tests"]["reftests"]["foo"])
-        self.assertEqual(json["tests"]["reftests"]["foo"]["multiple-match-failure.html"],
-            {"expected": "PASS", "actual": "IMAGE", "reftest_type": ["=="], "image_diff_percent": 1, "report": "REGRESSION"})
-        self.assertEqual(json["tests"]["reftests"]["foo"]["multiple-mismatch-failure.html"],
-            {"expected": "PASS", "actual": "IMAGE", "reftest_type": ["!="], "report": "REGRESSION"})
-        self.assertEqual(json["tests"]["reftests"]["foo"]["multiple-both-failure.html"],
-            {"expected": "PASS", "actual": "IMAGE", "reftest_type": ["==", "!="], "report": "REGRESSION"})
+        self.assertEqual(
+            json["tests"]["reftests"]["foo"]["multiple-match-failure.html"],
+            {"expected": "PASS", "actual": "IMAGE", "reftest_type": ["=="], "image_diff_percent": 1, "report": "REGRESSION"},
+        )
+        self.assertEqual(
+            json["tests"]["reftests"]["foo"]["multiple-mismatch-failure.html"],
+            {"expected": "PASS", "actual": "IMAGE", "reftest_type": ["!="], "report": "REGRESSION"},
+        )
+        self.assertEqual(
+            json["tests"]["reftests"]["foo"]["multiple-both-failure.html"],
+            {"expected": "PASS", "actual": "IMAGE", "reftest_type": sorted(["==", "!="]), "report": "REGRESSION"},
+        )
 
 
 class RebaselineTest(unittest.TestCase, StreamTestingMixin):

Modified: trunk/Tools/Scripts/webkitpy/layout_tests/views/metered_stream.py (254339 => 254340)


--- trunk/Tools/Scripts/webkitpy/layout_tests/views/metered_stream.py	2020-01-10 15:46:26 UTC (rev 254339)
+++ trunk/Tools/Scripts/webkitpy/layout_tests/views/metered_stream.py	2020-01-10 16:16:19 UTC (rev 254340)
@@ -109,6 +109,7 @@
 
         try:
             self._stream.write(timestamp_string + txt)
+            self._stream.flush()
         except UnicodeEncodeError:
             output = ''
             for c in timestamp_string + txt:
@@ -117,6 +118,7 @@
                 except UnicodeEncodeError:
                     output += '?'
             self._stream.write(output)
+            self._stream.flush()
 
     def writeln(self, txt, now=None, pid=None):
         self.write(self._ensure_newline(txt), now, pid)
@@ -125,6 +127,7 @@
         num_chars = len(self._last_partial_line)
         self._stream.write(self._erasure(self._last_partial_line))
         self._last_partial_line = ''
+        self._stream.flush()
 
     def flush(self):
         if self._last_partial_line:

Modified: trunk/Tools/Scripts/webkitpy/layout_tests/views/printing.py (254339 => 254340)


--- trunk/Tools/Scripts/webkitpy/layout_tests/views/printing.py	2020-01-10 15:46:26 UTC (rev 254339)
+++ trunk/Tools/Scripts/webkitpy/layout_tests/views/printing.py	2020-01-10 16:16:19 UTC (rev 254340)
@@ -239,7 +239,7 @@
         self._print_debug("Time to process slowest subdirectories:")
         min_seconds_to_print = 10
         for timing in timings:
-            if timing[0] > min_seconds_to_print:
+            if timing[1] > min_seconds_to_print:
                 self._print_debug("  %s took %s seconds to run %s tests." % timing)
         self._print_debug("")
 
@@ -254,10 +254,10 @@
         percentile99 = timings[int(.99 * num_tests)]
 
         if num_tests % 2 == 1:
-            median = timings[((num_tests - 1) / 2) - 1]
+            median = timings[(num_tests - 1) // 2 - 1]
         else:
-            lower = timings[num_tests / 2 - 1]
-            upper = timings[num_tests / 2]
+            lower = timings[num_tests // 2 - 1]
+            upper = timings[num_tests // 2]
             median = (float(lower + upper)) / 2
 
         mean = sum(timings) / num_tests

Modified: trunk/Tools/Scripts/webkitpy/port/darwin.py (254339 => 254340)


--- trunk/Tools/Scripts/webkitpy/port/darwin.py	2020-01-10 15:46:26 UTC (rev 254339)
+++ trunk/Tools/Scripts/webkitpy/port/darwin.py	2020-01-10 16:16:19 UTC (rev 254340)
@@ -98,7 +98,7 @@
         return self.host.filesystem.join(log_directory, 'CrashReporter')
 
     def _merge_crash_logs(self, logs, new_logs, crashed_processes):
-        for test, crash_log in new_logs.iteritems():
+        for test, crash_log in new_logs.items():
             try:
                 if test.split('-')[0] == 'Sandbox':
                     process_name = test.split('-')[1]

Modified: trunk/Tools/Scripts/webkitpy/port/device.py (254339 => 254340)


--- trunk/Tools/Scripts/webkitpy/port/device.py	2020-01-10 15:46:26 UTC (rev 254339)
+++ trunk/Tools/Scripts/webkitpy/port/device.py	2020-01-10 16:16:19 UTC (rev 254340)
@@ -116,3 +116,6 @@
 
     def __repr__(self):
         return u'{}'.format(self.platform_device)
+
+    def __hash__(self):
+        return hash(self.udid)

Modified: trunk/Tools/Scripts/webkitpy/port/device_port.py (254339 => 254340)


--- trunk/Tools/Scripts/webkitpy/port/device_port.py	2020-01-10 15:46:26 UTC (rev 254339)
+++ trunk/Tools/Scripts/webkitpy/port/device_port.py	2020-01-10 16:16:19 UTC (rev 254340)
@@ -95,7 +95,7 @@
             _log.debug('Skipping installation')
             return
 
-        for i in xrange(self.child_processes()):
+        for i in range(self.child_processes()):
             device = self.target_host(i)
             _log.debug(u'Installing to {}'.format(device))
             # Without passing DYLD_LIBRARY_PATH, libWebCoreTestSupport cannot be loaded and DRT/WKTR will crash pre-launch,
@@ -186,7 +186,7 @@
 
         self._install()
 
-        for i in xrange(self.child_processes()):
+        for i in range(self.child_processes()):
             host = self.target_host(i)
             host.prepare_for_testing(
                 self.ports_to_forward(),
@@ -201,7 +201,7 @@
         # Best effort to let every device teardown before throwing any exceptions here.
         # Failure to teardown devices can leave things in a bad state.
         exception_list = []
-        for i in xrange(self.child_processes()):
+        for i in range(self.child_processes()):
             device = self.target_host(i)
             if not device:
                 continue

Modified: trunk/Tools/Scripts/webkitpy/port/driver.py (254339 => 254340)


--- trunk/Tools/Scripts/webkitpy/port/driver.py	2020-01-10 15:46:26 UTC (rev 254339)
+++ trunk/Tools/Scripts/webkitpy/port/driver.py	2020-01-10 16:16:19 UTC (rev 254340)
@@ -39,6 +39,7 @@
 from os.path import normpath
 from webkitpy.common.system import path
 from webkitpy.common.system.profiler import ProfilerFactory
+from webkitpy.common.unicode_compatibility import encode_if_necessary, decode_for
 
 
 _log = logging.getLogger(__name__)
@@ -86,11 +87,11 @@
             test_time=0, measurements=None, timeout=False, error='', crashed_process_name='??',
             crashed_pid=None, crash_log=None, pid=None):
         # FIXME: Args could be renamed to better clarify what they do.
-        self.text = text
+        self.text = decode_for(text, str) if text else None
         self.image = image  # May be empty-string if the test crashes.
         self.image_hash = image_hash
         self.image_diff = None  # image_diff gets filled in after construction.
-        self.audio = audio  # Binary format is port-dependent.
+        self.audio = encode_if_necessary(audio) if audio else None  # Binary format is port-dependent.
         self.crash = crash
         self.crashed_process_name = crashed_process_name
         self.crashed_pid = crashed_pid
@@ -161,7 +162,7 @@
         # stderr output, as well as if we've seen #EOF on this driver instance.
         # FIXME: We should probably remove _read_first_block and _read_optional_image_block and
         # instead scope these locally in run_test.
-        self.error_from_test = str()
+        self.error_from_test = ''
         self.err_seen_eof = False
 
         self._server_name = self._port.driver_name()
@@ -196,7 +197,7 @@
         test_begin_time = time.time()
         self._driver_timed_out = False
         self._crash_report_from_driver = None
-        self.error_from_test = str()
+        self.error_from_test = ''
         self.err_seen_eof = False
 
         command = self._command_from_driver_input(driver_input)
@@ -214,6 +215,7 @@
         self._server_process.write(command)
         text, audio = self._read_first_block(deadline, driver_input.test_name)  # First block is either text or audio
         image, actual_image_hash = self._read_optional_image_block(deadline, driver_input.test_name)  # The second (optional) block is image data.
+        text = decode_for(text, str)
 
         crashed = self.has_crashed()
         timed_out = self._server_process.timed_out
@@ -227,9 +229,9 @@
             # In the timeout case, we kill the hung process as well.
             out, err = self._server_process.stop(self._port.driver_stop_timeout() if stop_when_done else 0.0)
             if out:
-                text += out
+                text += decode_for(out, str)
             if err:
-                self.error_from_test += err
+                self.error_from_test += decode_for(err, str)
             self._server_process = None
 
         crash_log = None
@@ -280,10 +282,10 @@
         child_processes = defaultdict(list)
 
         for line in output.splitlines():
-            m = re.match('^([^:]+): ([0-9]+)$', line)
+            m = re.match(b'^([^:]+): ([0-9]+)$', line)
             if m:
-                process_name = m.group(1)
-                process_id = m.group(2)
+                process_name = decode_for(m.group(1), str)
+                process_id = decode_for(m.group(2), str)
                 child_processes[process_name].append(process_id)
 
         return child_processes
@@ -540,10 +542,10 @@
         return cmd
 
     def _check_for_driver_timeout(self, out_line):
-        if out_line.startswith("#PID UNRESPONSIVE - "):
-            match = re.match('#PID UNRESPONSIVE - (\S+)', out_line)
-            child_process_name = match.group(1) if match else 'WebProcess'
-            match = re.search('pid (\d+)', out_line)
+        if out_line.startswith(b"#PID UNRESPONSIVE - "):
+            match = re.match(b'#PID UNRESPONSIVE - (\S+)', out_line)
+            child_process_name = decode_for(match.group(1), str) if match else 'WebProcess'
+            match = re.search(b'pid (\d+)', out_line)
             child_process_pid = int(match.group(1)) if match else None
             err_line = 'Wait on notifyDone timed out, process ' + child_process_name + ' pid = ' + str(child_process_pid)
             self.error_from_test += err_line
@@ -554,32 +556,32 @@
             self._driver_timed_out = True
 
     def _check_for_address_sanitizer_violation(self, error_line):
-        if "ERROR: AddressSanitizer" in error_line:
+        if b"ERROR: AddressSanitizer" in error_line:
             return True
 
     def _check_for_driver_crash_or_unresponsiveness(self, error_line):
-        crashed_check = error_line.rstrip('\r\n')
-        if crashed_check == "#CRASHED":
+        crashed_check = error_line.rstrip(b'\r\n')
+        if crashed_check == b"#CRASHED":
             self._crashed_process_name = self._server_process.process_name()
             self._crashed_pid = self._server_process.system_pid()
             return True
-        elif error_line.startswith("#CRASHED - "):
-            match = re.match('#CRASHED - (\S+)', error_line)
-            self._crashed_process_name = match.group(1) if match else 'WebProcess'
-            match = re.search('pid (\d+)', error_line)
+        elif error_line.startswith(b"#CRASHED - "):
+            match = re.match(b'#CRASHED - (\S+)', error_line)
+            self._crashed_process_name = decode_for(match.group(1), str) if match else 'WebProcess'
+            match = re.search(b'pid (\d+)', error_line)
             self._crashed_pid = int(match.group(1)) if match else None
             _log.debug('%s crash, pid = %s' % (self._crashed_process_name, str(self._crashed_pid)))
             return True
-        elif error_line.startswith("#PROCESS UNRESPONSIVE - "):
-            match = re.match('#PROCESS UNRESPONSIVE - (\S+)', error_line)
-            child_process_name = match.group(1) if match else 'WebProcess'
-            match = re.search('pid (\d+)', error_line)
+        elif error_line.startswith(b"#PROCESS UNRESPONSIVE - "):
+            match = re.match(b'#PROCESS UNRESPONSIVE - (\S+)', error_line)
+            child_process_name = decode_for(match.group(1), str) if match else 'WebProcess'
+            match = re.search(b'pid (\d+)', error_line)
             child_process_pid = int(match.group(1)) if match else None
             _log.debug('%s is unresponsive, pid = %s' % (child_process_name, str(child_process_pid)))
             self._driver_timed_out = True
             if child_process_pid:
                 self._port.sample_process(child_process_name, child_process_pid, self._target_host)
-            self.error_from_test += error_line
+            self.error_from_test += decode_for(error_line, str)
             self._server_process.write('#SAMPLE FINISHED\n', True)  # Must be able to ignore a broken pipe here, target process may already be closed.
             return True
         return self.has_crashed()
@@ -628,12 +630,12 @@
         # returns (image, actual_image_hash)
         block = self._read_block(deadline, test_name, wait_for_stderr_eof=True)
         if block.content and block.content_type == 'image/png':
-            return (block.decoded_content, block.content_hash)
+            return (block.decoded_content if block.encoding == 'base64' else block.content, block.content_hash)
         return (None, block.content_hash)
 
     def _read_header(self, block, line, header_text, header_attr, header_filter=None):
         if line.startswith(header_text) and getattr(block, header_attr) is None:
-            value = line.split()[1]
+            value = decode_for(line.split()[1], str)
             if header_filter:
                 value = header_filter(value)
             setattr(block, header_attr, value)
@@ -641,19 +643,23 @@
         return False
 
     def _process_stdout_line(self, block, line):
-        if (self._read_header(block, line, 'Content-Type: ', 'content_type')
-            or self._read_header(block, line, 'Content-Transfer-Encoding: ', 'encoding')
-            or self._read_header(block, line, 'Content-Length: ', '_content_length', int)
-            or self._read_header(block, line, 'ActualHash: ', 'content_hash')
-            or self._read_header(block, line, 'DumpMalloc: ', 'malloc')
-            or self._read_header(block, line, 'DumpJSHeap: ', 'js_heap')):
-            return
+        for header in [
+            (b'Content-Type: ', 'content_type', None),
+            (b'Content-Transfer-Encoding: ', 'encoding', None),
+            (b'Content-Length: ', '_content_length', int),
+            (b'ActualHash: ', 'content_hash', None),
+            (b'DumpMalloc: ', 'malloc', None),
+            (b'DumpJSHeap: ', 'js_heap', None),
+        ]:
+            if self._read_header(block, line, header[0], header[1], header[2]):
+                return
+
         # Note, we're not reading ExpectedHash: here, but we could.
         # If the line wasn't a header, we just append it to the content.
-        block.content += line
+        block.content = encode_if_necessary(block.content) + line
 
     def _strip_eof(self, line):
-        if line and line.endswith("#EOF\n"):
+        if line and line.endswith(b"#EOF\n"):
             return line[:-5], True
         return line, False
 
@@ -688,7 +694,7 @@
 
             if out_line:
                 self._check_for_driver_timeout(out_line)
-                if out_line[-1] != "\n":
+                if out_line[-1] != '\n' and out_line[-1] != 10:
                     _log.error("  %s -> Last character read from DRT stdout line was not a newline!  This indicates either a NRWT or DRT bug." % test_name)
                 content_length_before_header_check = block._content_length
                 self._process_stdout_line(block, out_line)
@@ -695,7 +701,7 @@
                 # FIXME: Unlike HTTP, DRT dumps the content right after printing a Content-Length header.
                 # Don't wait until we're done with headers, just read the binary blob right now.
                 if content_length_before_header_check != block._content_length:
-                    block.content = self._server_process.read_stdout(deadline, block._content_length)
+                    block.content = encode_if_necessary(self._server_process.read_stdout(deadline, block._content_length))
 
             if err_line:
                 if self._check_for_driver_crash_or_unresponsiveness(err_line):
@@ -702,20 +708,20 @@
                     break
                 elif self._check_for_address_sanitizer_violation(err_line):
                     asan_violation_detected = True
-                    self._crash_report_from_driver = ""
+                    self._crash_report_from_driver = b''
                     # ASan report starts with a nondescript line, we only detect the second line.
                     end_of_previous_error_line = self.error_from_test.rfind('\n', 0, -1)
                     if end_of_previous_error_line > 0:
                         self.error_from_test = self.error_from_test[:end_of_previous_error_line]
                     else:
-                        self.error_from_test = ""
+                        self.error_from_test = ''
                     # Symbolication can take a very long time, give it 10 extra minutes to finish.
                     # FIXME: This can likely be removed once <rdar://problem/18701447> is fixed.
                     deadline += 10 * 60 * 1000
                 if asan_violation_detected:
-                    self._crash_report_from_driver += err_line
+                    self._crash_report_from_driver += decode_for(err_line, str)
                 else:
-                    self.error_from_test += err_line
+                    self.error_from_test += decode_for(err_line, str)
 
         if asan_violation_detected and not self._crashed_process_name:
             self._crashed_process_name = self._server_process.process_name()
@@ -738,7 +744,7 @@
         self.content_hash = None
         self._content_length = None
         # Content is treated as binary data even though the text output is usually UTF-8.
-        self.content = str()  # FIXME: Should be bytearray() once we require Python 2.6.
+        self.content = b''
         self.decoded_content = None
         self.malloc = None
         self.js_heap = None
@@ -747,7 +753,10 @@
         if self.encoding == 'base64' and self.content is not None:
             self.decoded_content = base64.b64decode(self.content)
         else:
-            self.decoded_content = self.content
+            try:
+                self.decoded_content = decode_for(self.content, str)
+            except UnicodeDecodeError:
+                self.decoded_content = None
 
 
 class DriverProxy(object):

Modified: trunk/Tools/Scripts/webkitpy/port/driver_unittest.py (254339 => 254340)


--- trunk/Tools/Scripts/webkitpy/port/driver_unittest.py	2020-01-10 15:46:26 UTC (rev 254339)
+++ trunk/Tools/Scripts/webkitpy/port/driver_unittest.py	2020-01-10 16:16:19 UTC (rev 254340)
@@ -182,7 +182,7 @@
         content_block = driver._read_block(0, "")
         self.assertEqual(content_block.content_type, 'image/png')
         self.assertEqual(content_block.content_hash, 'actual')
-        self.assertEqual(content_block.content, '12345678\n')
+        self.assertEqual(content_block.content, b'12345678\n')
         self.assertEqual(content_block.decoded_content, '12345678\n')
         driver._server_process = None
 
@@ -201,7 +201,7 @@
         self.assertEqual(content_block.content_type, 'image/png')
         self.assertEqual(content_block.content_hash, 'actual')
         self.assertEqual(content_block.encoding, 'base64')
-        self.assertEqual(content_block.content, 'MTIzNDU2NzgK')
+        self.assertEqual(content_block.content, b'MTIzNDU2NzgK')
         self.assertEqual(content_block.decoded_content, b'12345678\n')
 
     def test_no_timeout(self):
@@ -247,43 +247,43 @@
             driver.stop()
 
         driver._server_process = FakeServerProcess(False)
-        assert_crash(driver, '', False, None, None)
+        assert_crash(driver, b'', False, None, None)
 
         driver._crashed_process_name = None
         driver._crashed_pid = None
         driver._server_process = FakeServerProcess(False)
         driver._driver_timed_out = False
-        assert_crash(driver, '#CRASHED\n', True, 'FakeServerProcess', 1234)
+        assert_crash(driver, b'#CRASHED\n', True, 'FakeServerProcess', 1234)
 
         driver._crashed_process_name = None
         driver._crashed_pid = None
         driver._server_process = FakeServerProcess(False)
         driver._driver_timed_out = False
-        assert_crash(driver, '#CRASHED - WebProcess\n', True, 'WebProcess', None)
+        assert_crash(driver, b'#CRASHED - WebProcess\n', True, 'WebProcess', None)
 
         driver._crashed_process_name = None
         driver._crashed_pid = None
         driver._server_process = FakeServerProcess(False)
         driver._driver_timed_out = False
-        assert_crash(driver, '#CRASHED - WebProcess (pid 8675)\n', True, 'WebProcess', 8675)
+        assert_crash(driver, b'#CRASHED - WebProcess (pid 8675)\n', True, 'WebProcess', 8675)
 
         driver._crashed_process_name = None
         driver._crashed_pid = None
         driver._server_process = FakeServerProcess(False)
         driver._driver_timed_out = False
-        assert_crash(driver, '#PROCESS UNRESPONSIVE - WebProcess (pid 8675)\n', True, None, None, True)
+        assert_crash(driver, b'#PROCESS UNRESPONSIVE - WebProcess (pid 8675)\n', True, None, None, True)
 
         driver._crashed_process_name = None
         driver._crashed_pid = None
         driver._server_process = FakeServerProcess(False)
         driver._driver_timed_out = False
-        assert_crash(driver, '#CRASHED - renderer (pid 8675)\n', True, 'renderer', 8675)
+        assert_crash(driver, b'#CRASHED - renderer (pid 8675)\n', True, 'renderer', 8675)
 
         driver._crashed_process_name = None
         driver._crashed_pid = None
         driver._server_process = FakeServerProcess(True)
         driver._driver_timed_out = False
-        assert_crash(driver, '', True, 'FakeServerProcess', 1234)
+        assert_crash(driver, b'', True, 'FakeServerProcess', 1234)
 
     def test_creating_a_port_does_not_write_to_the_filesystem(self):
         port = TestWebKitPort()

Modified: trunk/Tools/Scripts/webkitpy/port/image_diff.py (254339 => 254340)


--- trunk/Tools/Scripts/webkitpy/port/image_diff.py	2020-01-10 15:46:26 UTC (rev 254339)
+++ trunk/Tools/Scripts/webkitpy/port/image_diff.py	2020-01-10 16:16:19 UTC (rev 254340)
@@ -35,6 +35,7 @@
 import time
 
 from webkitpy.port import server_process
+from webkitpy.common.unicode_compatibility import decode_for
 
 
 _log = logging.getLogger(__name__)
@@ -57,9 +58,10 @@
             if not self._process:
                 self._start(tolerance)
             # Note that although we are handed 'old', 'new', ImageDiff wants 'new', 'old'.
-            self._process.write('Content-Length: %d\n%sContent-Length: %d\n%s' % (
-                len(actual_contents), actual_contents,
-                len(expected_contents), expected_contents))
+            self._process.write('Content-Length: {}\n'.format(len(actual_contents)))
+            self._process.write(actual_contents)
+            self._process.write('Content-Length: {}\n'.format(len(expected_contents)))
+            self._process.write(expected_contents)
             return self._read()
         except IOError as exception:
             return (None, 0, "Failed to compute an image diff: %s" % str(exception))
@@ -76,7 +78,7 @@
     def _read(self):
         deadline = time.time() + 2.0
         output = None
-        output_image = ""
+        output_image = b''
 
         while not self._process.timed_out and not self._process.has_crashed():
             output = self._process.read_stdout_line(deadline)
@@ -83,17 +85,17 @@
             if self._process.timed_out or self._process.has_crashed() or not output:
                 break
 
-            if output.startswith('diff'):  # This is the last line ImageDiff prints.
+            if output.startswith(b'diff'):  # This is the last line ImageDiff prints.
                 break
 
-            if output.startswith('Content-Length'):
-                m = re.match('Content-Length: (\d+)', output)
-                content_length = int(m.group(1))
+            if output.startswith(b'Content-Length'):
+                m = re.match(b'Content-Length: (\d+)', output)
+                content_length = int(decode_for(m.group(1), str))
                 output_image = self._process.read_stdout(deadline, content_length)
                 output = self._process.read_stdout_line(deadline)
                 break
 
-        stderr = self._process.pop_all_buffered_stderr()
+        stderr = decode_for(self._process.pop_all_buffered_stderr(), str)
         err_str = ''
         if stderr:
             err_str += "ImageDiff produced stderr output:\n" + stderr
@@ -103,11 +105,11 @@
             err_str += "ImageDiff crashed\n"
 
         diff_percent = 0
-        if output and output.startswith('diff'):
-            m = re.match('diff: (.+)% (passed|failed)', output)
-            if m.group(2) == 'passed':
+        if output and output.startswith(b'diff'):
+            m = re.match(b'diff: (.+)% (passed|failed)', output)
+            if m.group(2) == b'passed':
                 return (None, 0, None)
-            diff_percent = float(m.group(1))
+            diff_percent = float(decode_for(m.group(1), str))
 
         return (output_image, diff_percent, err_str or None)
 

Modified: trunk/Tools/Scripts/webkitpy/port/server_process.py (254339 => 254340)


--- trunk/Tools/Scripts/webkitpy/port/server_process.py	2020-01-10 15:46:26 UTC (rev 254339)
+++ trunk/Tools/Scripts/webkitpy/port/server_process.py	2020-01-10 16:16:19 UTC (rev 254340)
@@ -35,6 +35,9 @@
 import sys
 import time
 
+from webkitpy.common.system.executive import ScriptError
+from webkitpy.common.unicode_compatibility import encode_if_necessary
+
 # Note that although win32 python does provide an implementation of
 # the win32 select API, it only works on sockets, and not on the named pipes
 # used by subprocess, so we have to use the native APIs directly.
@@ -47,9 +50,7 @@
     import os
     import select
 
-from webkitpy.common.system.executive import ScriptError
 
-
 _log = logging.getLogger(__name__)
 
 
@@ -178,9 +179,9 @@
         if not self._proc:
             self._start()
         try:
-            self._proc.stdin.write(bytes)
+            self._proc.stdin.write(encode_if_necessary(bytes))
             self._proc.stdin.flush()
-        except IOError as e:
+        except (IOError, ValueError) as e:
             self.stop(0.0)
             # stop() calls _reset(), so we have to set crashed to True after calling stop()
             # unless we already know that this is a timeout.

Modified: trunk/Tools/Scripts/webkitpy/port/server_process_mock.py (254339 => 254340)


--- trunk/Tools/Scripts/webkitpy/port/server_process_mock.py	2020-01-10 15:46:26 UTC (rev 254339)
+++ trunk/Tools/Scripts/webkitpy/port/server_process_mock.py	2020-01-10 16:16:19 UTC (rev 254340)
@@ -26,11 +26,13 @@
 # (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 webkitpy.common.unicode_compatibility import encode_if_necessary
 
+
 class MockServerProcess(object):
     def __init__(self, port_obj=None, name=None, cmd=None, env=None, universal_newlines=False, lines=None, crashed=False, target_host=None):
         self.timed_out = False
-        self.lines = lines or []
+        self.lines = [encode_if_necessary(line) for line in (lines or [])]
         self.crashed = crashed
         self.writes = []
         self.cmd = cmd
@@ -52,7 +54,7 @@
     def read_stdout_line(self, deadline):
         if self.has_crashed():
             return None
-        return self.lines.pop(0) + "\n"
+        return self.lines.pop(0) + b'\n'
 
     def has_available_stdout(self):
         if self.has_crashed():
@@ -67,8 +69,8 @@
             self.lines.pop(0)
             remaining_size = size - len(first_line) - 1
             if not remaining_size:
-                return first_line + "\n"
-            return first_line + "\n" + self.read_stdout(deadline, remaining_size)
+                return first_line + b'\n'
+            return first_line + b'\n' + self.read_stdout(deadline, remaining_size)
         result = self.lines[0][:size]
         self.lines[0] = self.lines[0][size:]
         return result

Modified: trunk/Tools/Scripts/webkitpy/port/simulator_process.py (254339 => 254340)


--- trunk/Tools/Scripts/webkitpy/port/simulator_process.py	2020-01-10 15:46:26 UTC (rev 254339)
+++ trunk/Tools/Scripts/webkitpy/port/simulator_process.py	2020-01-10 16:16:19 UTC (rev 254340)
@@ -22,6 +22,7 @@
 
 
 import os
+import sys
 import time
 
 from webkitpy.common.timeout_context import Timeout
@@ -67,7 +68,9 @@
 
         def close(self):
             result = self._file.close()
-            self.socket.close()
+            # Closing the file implicitly closes the socket in Python 3
+            if sys.version_info < (3, 0):
+                self.socket.close()
             return result
 
     def __init__(self, port_obj, name, cmd, env=None, universal_newlines=False, treat_no_data_as_crash=False, target_host=None):
@@ -103,7 +106,7 @@
             stderr = None
             try:
                 # This order matches the client side connections in Tools/TestRunnerShared/IOSLayoutTestCommunication.cpp setUpIOSLayoutTestCommunication()
-                stdin = SimulatorProcess._accept_connection_create_file(self._target_host.listening_socket, 'w')
+                stdin = SimulatorProcess._accept_connection_create_file(self._target_host.listening_socket, 'wb')
                 stdout = SimulatorProcess._accept_connection_create_file(self._target_host.listening_socket, 'rb')
                 stderr = SimulatorProcess._accept_connection_create_file(self._target_host.listening_socket, 'rb')
             except:

Modified: trunk/Tools/Scripts/webkitpy/port/test.py (254339 => 254340)


--- trunk/Tools/Scripts/webkitpy/port/test.py	2020-01-10 15:46:26 UTC (rev 254339)
+++ trunk/Tools/Scripts/webkitpy/port/test.py	2020-01-10 16:16:19 UTC (rev 254340)
@@ -32,6 +32,7 @@
 from webkitpy.layout_tests.models.test_configuration import TestConfiguration
 from webkitpy.common.system.crashlogs import CrashLogs
 from webkitpy.common.version_name_map import PUBLIC_TABLE, VersionNameMap
+from webkitpy.common.unicode_compatibility import decode_for, encode_if_necessary
 
 
 # This sets basic expectations for a test. Each individual expectation
@@ -111,9 +112,9 @@
 
 
 def unit_test_list():
-    silent_audio = "RIFF2\x00\x00\x00WAVEfmt \x10\x00\x00\x00\x01\x00\x01\x00\x22\x56\x00\x00\x44\xAC\x00\x00\x02\x00\x10\x00data\x0E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
-    silent_audio_with_single_bit_difference = "RIFF2\x00\x00\x00WAVEfmt \x10\x00\x00\x00\x01\x00\x01\x00\x22\x56\x00\x00\x44\xAC\x00\x00\x02\x00\x10\x00data\x0E\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
-    audio2 = "RIFF2\x00\x00\x00WAVEfmt \x10\x00\x00\x00\x01\x00\x01\x00\x22\x56\x00\x00\x44\xAC\x00\x00\x02\x00\x10\x00data\x0E\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+    silent_audio = b"RIFF2\x00\x00\x00WAVEfmt \x10\x00\x00\x00\x01\x00\x01\x00\x22\x56\x00\x00\x44\xAC\x00\x00\x02\x00\x10\x00data\x0E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+    silent_audio_with_single_bit_difference = b"RIFF2\x00\x00\x00WAVEfmt \x10\x00\x00\x00\x01\x00\x01\x00\x22\x56\x00\x00\x44\xAC\x00\x00\x02\x00\x10\x00data\x0E\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+    audio2 = b"RIFF2\x00\x00\x00WAVEfmt \x10\x00\x00\x00\x01\x00\x01\x00\x22\x56\x00\x00\x44\xAC\x00\x00\x02\x00\x10\x00data\x0E\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
     tests = TestList()
     tests.add('failures/expected/crash.html', crash=True)
     tests.add('failures/expected/exception.html', exception=True)
@@ -434,15 +435,20 @@
         return 'Release'
 
     def diff_image(self, expected_contents, actual_contents, tolerance=None):
+        expected_contents = encode_if_necessary(expected_contents)
+        actual_contents = encode_if_necessary(actual_contents)
         diffed = actual_contents != expected_contents
         if not actual_contents and not expected_contents:
             return (None, 0, None)
         if not actual_contents or not expected_contents:
             return (True, 0, None)
-        if 'ref' in expected_contents:
+        if b'ref' in expected_contents:
             assert tolerance == 0
         if diffed:
-            return ("< %s\n---\n> %s\n" % (expected_contents, actual_contents), 1, None)
+            return ("< {}\n---\n> {}\n".format(
+                decode_for(expected_contents, str),
+                decode_for(actual_contents, str),
+            ), 1, None)
         return (None, 0, None)
 
     def layout_tests_dir(self):
@@ -563,7 +569,7 @@
         test_args = test_input.args or []
         test = self._port._tests[test_name]
         if test.keyboard:
-            raise KeyboardInterrupt
+            raise KeyboardInterrupt()
         if test.exception:
             raise ValueError('exception from ' + test_name)
         if test.hang:

Modified: trunk/Tools/Scripts/webkitpy/xcode/simulated_device.py (254339 => 254340)


--- trunk/Tools/Scripts/webkitpy/xcode/simulated_device.py	2020-01-10 15:46:26 UTC (rev 254339)
+++ trunk/Tools/Scripts/webkitpy/xcode/simulated_device.py	2020-01-10 16:16:19 UTC (rev 254340)
@@ -563,7 +563,7 @@
             _log.debug(u'{} has no service to check if the device is usable'.format(self.device_type.software_variant))
             return True
 
-        system_processes = self.executive.run_command([SimulatedDeviceManager.xcrun, 'simctl', 'spawn', self.udid, 'launchctl', 'print', 'system'], decode_output=False)
+        system_processes = self.executive.run_command([SimulatedDeviceManager.xcrun, 'simctl', 'spawn', self.udid, 'launchctl', 'print', 'system'], decode_output=True)
         if re.search(r'"{}"'.format(home_screen_service), system_processes) or re.search(r'A\s+{}'.format(home_screen_service), system_processes):
             return True
         return False
@@ -604,7 +604,7 @@
 
     def install_app(self, app_path, env=None):
         # Even after carousel is running, it takes a few seconds for watchOS to allow installs.
-        for i in xrange(self.NUM_INSTALL_RETRIES):
+        for i in range(self.NUM_INSTALL_RETRIES):
             exit_code = self.executive.run_command(['xcrun', 'simctl', 'install', self.udid, app_path], return_exit_code=True)
             if exit_code == 0:
                 return True
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to