chromium/third_party/blink/tools/blinkpy/w3c/wpt_results_processor_unittest.py

# Copyright 2022 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import base64
import contextlib
import json
import re
import textwrap
import typing
from unittest import mock

from blinkpy.common.host_mock import MockHost as BlinkMockHost
from blinkpy.common.path_finder import PathFinder
from blinkpy.common.system.log_testing import LoggingTestCase
from blinkpy.web_tests.models.typ_types import ResultType
from blinkpy.web_tests.port.factory_mock import MockPortFactory
from blinkpy.w3c.wpt_results_processor import (
    EventProcessingError,
    StreamShutdown,
    TestType,
    WPTResultsProcessor,
)


class WPTResultsProcessorTest(LoggingTestCase):
    def setUp(self):
        super().setUp()
        self.host = BlinkMockHost()
        self.host.port_factory = MockPortFactory(self.host)
        self.fs = self.host.filesystem
        self.path_finder = PathFinder(self.fs)
        port = self.host.port_factory.get('test-linux-trusty')
        port.set_option_default('manifest_update', False)
        port.set_option_default('test_types', typing.get_args(TestType))

        # Create a testing manifest containing any test files that we
        # might interact with.
        self.fs.write_text_file(
            self.path_finder.path_from_wpt_tests('MANIFEST.json'),
            json.dumps({
                'items': {
                    'reftest': {
                        'reftest.html': [
                            'c3f2fb6f436da59d43aeda0a7e8a018084557033',
                            [None, [['/reftest-ref.html', '==']], {}],
                        ],
                        'reftest-multiple.html': [
                            'c3f2fb6f436da59d43aeda0a7e8a018084557033',
                            [
                                None,
                                [['/reftest-ref.html', '=='],
                                 ['/reftest-mismatch.html', '!=']], {}
                            ],
                        ],
                    },
                    'testharness': {
                        'test.html': [
                            'd933fd981d4a33ba82fb2b000234859bdda1494e',
                            [None, {}]
                        ],
                        'timeout.html': [
                            'd933fd981d4a33ba82fb2b000234859bdda1494e',
                            [None, {}]
                        ],
                        'variant.html': [
                            'b8db5972284d1ac6bbda0da81621d9bca5d04ee7',
                            ['variant.html?foo=bar/abc', {}],
                            ['variant.html?foo=baz', {}],
                        ],
                    },
                    'wdspec': {
                        'test.py': [
                            '61acc923e8eb3f6883d09bb4bfa220d7f757bbb8',
                            [None, {}]
                        ],
                    },
                },
            }))
        self.fs.write_text_file(
            self.path_finder.path_from_web_tests('wpt_internal',
                                                 'MANIFEST.json'),
            json.dumps({
                'items': {
                    'reftest': {
                        'reftest.html': [
                            'c3f2fb6f436da59d43aeda0a7e8a018084557033',
                            [
                                None,
                                [['/wpt_internal/reftest-ref.html', '==']], {}
                            ],
                        ],
                    },
                    'testharness': {
                        'dir': {
                            'multiglob.https.any.js': [
                                'd6498c3e388e0c637830fa080cca78b0ab0e5305',
                                ['dir/multiglob.https.any.html', {}],
                                ['dir/multiglob.https.any.worker.html', {}],
                            ],
                        },
                    },
                },
            }))
        self.fs.write_text_file(
            self.path_finder.path_from_web_tests('VirtualTestSuites'),
            json.dumps([{
                'prefix': 'fake-vts',
                'platforms': ['Linux'],
                'bases': ['external/wpt/reftest-multiple.html'],
                'args': ['--enable-features=FakeFeature'],
            }]))
        self.fs.write_text_file(
            self.path_finder.path_from_blink_tools('blinkpy', 'web_tests',
                                                   'results.html'),
            'results-viewer-body')
        self.fs.write_text_file(
            self.path_finder.path_from_blink_tools('blinkpy', 'web_tests',
                                                   'results.html.version'),
            'Version=1.0')
        self.wpt_report = {
            'run_info': {
                'os': 'linux',
                'version': '18.04',
                'product': 'chrome',
                'revision': '57a5dfb2d7d6253fbb7dbd7c43e7588f9339f431',
                'used_upstream': True,
            },
            'results': [{
                'test':
                '/a/b.html',
                'subtests': [{
                    'name': 'subtest',
                    'status': 'FAIL',
                    'message': 'remove this message',
                    'expected': 'PASS',
                    'known_intermittent': [],
                }],
                'status':
                'OK',
                'expected':
                'OK',
                'message':
                'remove this message from the compact version',
                'duration':
                1000,
                'known_intermittent': ['CRASH'],
            }],
        }

        self.processor = WPTResultsProcessor(
            self.fs,
            port,
            artifacts_dir=self.fs.join('/mock-checkout', 'out', 'Default',
                                       'layout-test-results'),
            sink=mock.Mock(batch_results=contextlib.nullcontext))
        self.processor.sink.host = port.typ_host()

    def _event(self, **fields):
        self.processor.process_event({
            'pid': 16000,
            'thread': 'TestRunner',
            'source': 'web-platform-tests',
            'time': 1000,
            **fields
        })

    def test_report_expected_pass(self):
        self._event(action='test_start', time=1000, test='/reftest.html')
        self._event(action='test_end',
                    time=3000,
                    test='/reftest.html',
                    status='PASS')

        self.assertEqual(self.processor.num_initial_failures, 0)
        report_mock = self.processor.sink.report_individual_test_result
        report_mock.assert_called_once_with(
            test_name_prefix='',
            result=mock.ANY,
            artifact_output_dir=self.fs.join('/mock-checkout', 'out',
                                             'Default'),
            expectations=None,
            test_file_location=self.path_finder.path_from_web_tests(
                'external', 'wpt', 'reftest.html'),
            html_summary=None)

        result = report_mock.call_args.kwargs['result']
        self.assertEqual(result.name, 'external/wpt/reftest.html')
        self.assertEqual(result.actual, 'PASS')
        self.assertEqual(result.expected, {'PASS'})
        self.assertFalse(result.unexpected)
        self.assertAlmostEqual(result.took, 2)
        self.assertEqual(result.artifacts, {})

    def test_report_unexpected_fail(self):
        self.fs.write_text_file(
            self.path_finder.path_from_web_tests('TestExpectations'),
            textwrap.dedent("""\
                # results: [ Pass Timeout ]
                wpt_internal/reftest.html [ Pass Timeout ]
                """))
        self._event(action='test_start',
                    time=1000,
                    test='/wpt_internal/reftest.html')
        self._event(action='test_end',
                    time=1500,
                    test='/wpt_internal/reftest.html',
                    status='FAIL',
                    expected='PASS',
                    known_intermittent=['TIMEOUT'])

        self.assertEqual(self.processor.num_initial_failures, 1)
        report_mock = self.processor.sink.report_individual_test_result
        report_mock.assert_called_once_with(
            test_name_prefix='',
            result=mock.ANY,
            artifact_output_dir=self.fs.join('/mock-checkout', 'out',
                                             'Default'),
            expectations=None,
            test_file_location=self.path_finder.path_from_web_tests(
                'wpt_internal', 'reftest.html'),
            html_summary=mock.ANY)

        result = report_mock.call_args.kwargs['result']
        self.assertEqual(result.name, 'wpt_internal/reftest.html')
        self.assertEqual(result.actual, 'FAIL')
        self.assertEqual(result.expected, {'PASS', 'TIMEOUT'})
        self.assertTrue(result.unexpected)
        self.assertAlmostEqual(result.took, 0.5)
        # `{expected,actual}_text` is not produced for WPT reftests.
        self.assertEqual(result.artifacts, {})
        summary = report_mock.call_args.kwargs['html_summary']
        self.assertRegex(
            summary,
            'This WPT was run against .*chrome.* using .*chromedriver')
        self.assertRegex(
            summary, 'See .*these instructions.* about running '
            'these tests locally and triaging failures')
        self.assertNotIn('wpt.fyi', summary)

    def test_report_pass_on_retry(self):
        self._event(action='suite_start', time=0)
        self._event(action='test_start',
                    test='/variant.html?foo=bar/abc',
                    time=1000)
        self._event(action='test_end',
                    test='/variant.html?foo=bar/abc',
                    time=2000,
                    status='ERROR',
                    expected='OK')
        self._event(action='suite_end', time=3000)
        self._event(action='suite_start', time=4000)
        self._event(action='test_start',
                    test='/variant.html?foo=bar/abc',
                    time=5000)
        self._event(action='test_status',
                    test='/variant.html?foo=bar/abc',
                    time=5500,
                    status='PASS',
                    subtest='subtest',
                    message='only compare message on failure')
        self._event(action='test_end',
                    test='/variant.html?foo=bar/abc',
                    time=6000,
                    status='OK',
                    message='message')
        self._event(action='suite_end', time=7000)

        self.assertEqual(self.processor.num_initial_failures, 1)
        report_mock = self.processor.sink.report_individual_test_result
        report_mock.assert_has_calls([
            mock.call(test_name_prefix='',
                      result=mock.ANY,
                      artifact_output_dir=self.fs.join('/mock-checkout', 'out',
                                                       'Default'),
                      expectations=None,
                      test_file_location=self.path_finder.path_from_web_tests(
                          'external', 'wpt', 'variant.html'),
                      html_summary=mock.ANY),
        ] * 2)

        fail, ok = [
            call.kwargs['result'] for call in report_mock.call_args_list
        ]
        self.assertEqual(fail.name, 'external/wpt/variant.html?foo=bar/abc')
        self.assertEqual(fail.actual, 'FAIL')
        self.assertEqual(fail.expected, {'PASS'})
        self.assertTrue(fail.unexpected)
        self.assertEqual(
            fail.artifacts, {
                'actual_text': [
                    self.fs.join('layout-test-results', 'external', 'wpt',
                                 'variant_foo=bar_abc-actual.txt'),
                ],
            })
        self.assertEqual(ok.name, 'external/wpt/variant.html?foo=bar/abc')
        self.assertEqual(ok.actual, 'PASS')
        self.assertEqual(ok.expected, {'PASS'})
        self.assertFalse(ok.unexpected)
        self.assertEqual(
            ok.artifacts, {
                'crash_log': [
                    self.fs.join('layout-test-results', 'retry_1', 'external',
                                 'wpt', 'variant_foo=bar_abc-crash-log.txt'),
                ],
            })
        fail_summary = report_mock.call_args_list[0].kwargs['html_summary']
        self.assertRegex(
            fail_summary,
            'https://wpt.fyi/results/variant.html%3Ffoo%3Dbar%2Fabc')

    def test_report_subtest_fail_all_expected(self):
        """All (sub)tests running expectedly is reported as expected pass."""
        self.fs.write_text_file(
            self.path_finder.path_from_wpt_tests('test-expected.txt'),
            textwrap.dedent("""\
                This is a testharness.js-based test.
                [FAIL] fail
                [PASS] pass
                Harness: the test ran to completion.
                """))
        self._event(action='test_start', test='/test.html')
        self._event(action='test_status',
                    test='/test.html',
                    subtest='fail',
                    status='FAIL')
        self._event(action='test_status',
                    test='/test.html',
                    subtest='pass',
                    status='PASS')
        self._event(action='test_end', test='/test.html', status='OK')

        result = self.processor.sink.report_individual_test_result.call_args.kwargs[
            'result']
        self.assertEqual(result.name, 'external/wpt/test.html')
        self.assertEqual(result.actual, 'PASS')
        self.assertEqual(result.expected, {'PASS'})
        self.assertFalse(result.unexpected)

    def test_report_subtest_unexpected_pass(self):
        """Unexpected subtest pass should be promoted to an unexpected failure."""
        self._event(action='test_start', test='/test.html')
        self._event(action='test_status',
                    test='/test.html',
                    subtest='fail',
                    status='FAIL')
        self._event(action='test_status',
                    test='/test.html',
                    subtest='pass',
                    status='PASS',
                    expected='FAIL')
        self._event(action='test_end', test='/test.html', status='OK')

        result = self.processor.sink.report_individual_test_result.call_args.kwargs[
            'result']
        self.assertEqual(result.name, 'external/wpt/test.html')
        self.assertEqual(result.actual, 'FAIL')
        self.assertEqual(result.expected, {'PASS'})
        self.assertTrue(result.unexpected)
        self.assertEqual(
            result.artifacts, {
                'actual_text': [
                    self.fs.join('layout-test-results', 'external', 'wpt',
                                 'test-actual.txt'),
                ],
            })
        self.assertEqual(
            self.fs.read_text_file(
                self.fs.join('/mock-checkout', 'out', 'Default',
                             'layout-test-results', 'external', 'wpt',
                             'test-actual.txt')),
            textwrap.dedent("""\
                This is a testharness.js-based test.
                [FAIL] fail
                Harness: the test ran to completion.
                """))

    def test_report_subtest_unexpected_fail(self):
        self._event(action='test_start', test='/test.html')
        self._event(action='test_status',
                    test='/test.html',
                    subtest='pass before',
                    status='PASS')
        self._event(action='test_status',
                    test='/test.html',
                    subtest='unexpected fail',
                    status='FAIL',
                    expected='PASS')
        self._event(action='test_status',
                    test='/test.html',
                    subtest='expected fail',
                    status='FAIL')
        self._event(action='test_status',
                    test='/test.html',
                    subtest='unexpected pass after',
                    status='PASS',
                    expected='FAIL')
        self._event(action='test_end', test='/test.html', status='ERROR')

        result = self.processor.sink.report_individual_test_result.call_args.kwargs[
            'result']
        self.assertEqual(result.name, 'external/wpt/test.html')
        self.assertEqual(result.actual, 'FAIL')
        self.assertEqual(result.expected, {'PASS'})
        self.assertTrue(result.unexpected)

    def test_report_unexpected_fail_for_harness_mismatch(self):
        self.fs.write_text_file(
            self.path_finder.path_from_wpt_tests('test-expected.txt'),
            textwrap.dedent("""\
                This is a testharness.js-based test.
                Harness Error. harness_status.status = 1 , harness_status.message = ignore this
                Harness: the test ran to completion.
                """))
        self._event(action='test_start', test='/test.html')
        self._event(action='test_end',
                    test='/test.html',
                    status='OK',
                    expected='ERROR')

        result = self.processor.sink.report_individual_test_result.call_args.kwargs[
            'result']
        self.assertEqual(result.name, 'external/wpt/test.html')
        self.assertEqual(result.actual, 'FAIL')
        self.assertEqual(result.expected, {'PASS'})
        self.assertTrue(result.unexpected)

    def test_report_unexpected_fail_for_unknown_subtests(self):
        self.fs.write_text_file(
            self.path_finder.path_from_web_tests(
                'external/wpt/test-expected.txt'),
            textwrap.dedent("""\
                This is a testharness.js-based test.
                [FAIL] does-not-exist
                Harness: the test ran to completion.
                """))
        self._event(action='test_start', test='/test.html')
        self._event(action='test_status',
                    test='/test.html',
                    subtest='implicit-pass',
                    status='PASS')
        self._event(action='test_end', test='/test.html', status='OK')

        result = self.processor.sink.report_individual_test_result.call_args.kwargs[
            'result']
        self.assertEqual(result.name, 'external/wpt/test.html')
        self.assertEqual(result.actual, 'FAIL')
        self.assertEqual(result.expected, {'PASS'})
        self.assertTrue(result.unexpected)

    def test_report_unexpected_fail_for_different_types(self):
        self._event(action='test_start', test='/reftest.html')
        self._event(action='test_end',
                    test='/reftest.html',
                    status='ERROR',
                    expected='FAIL')

        result = self.processor.sink.report_individual_test_result.call_args.kwargs[
            'result']
        self.assertEqual(result.name, 'external/wpt/reftest.html')
        # The unexpected flag is still set because the failures are of different
        # types.
        self.assertEqual(result.actual, 'FAIL')
        self.assertEqual(result.expected, {'PASS'})
        self.assertTrue(result.unexpected)

    def test_report_unexpected_timeout(self):
        self._event(action='test_start', test='/timeout.html')
        self._event(action='test_status',
                    test='/timeout.html',
                    subtest='timeout',
                    status='TIMEOUT',
                    expected='PASS')
        self._event(action='test_status',
                    test='/timeout.html',
                    subtest='notrun',
                    status='NOTRUN',
                    expected='PASS')
        self._event(action='process_output',
                    command='chromedriver',
                    data='Log this line',
                    process='101')
        self._event(action='test_end',
                    test='/timeout.html',
                    status='TIMEOUT',
                    expected='OK',
                    extra={'browser_pid': 101})

        result = self.processor.sink.report_individual_test_result.call_args.kwargs[
            'result']
        self.assertEqual(result.name, 'external/wpt/timeout.html')
        self.assertEqual(result.actual, 'TIMEOUT')
        self.assertEqual(result.expected, {'PASS'})
        self.assertTrue(result.unexpected)
        # Timeouts and crashes shouldn't output `{actual,expected}_*` artifacts.
        self.assertEqual(
            result.artifacts, {
                'stderr': [
                    self.fs.join('layout-test-results', 'external', 'wpt',
                                 'timeout-stderr.txt'),
                ],
            })
        self.assertEqual(
            self.fs.read_text_file(
                self.fs.join('/mock-checkout', 'out', 'Default',
                             'layout-test-results', 'external', 'wpt',
                             'timeout-stderr.txt')), 'Log this line\n')

    def test_report_expected_timeout_with_unexpected_fails(self):
        self.fs.write_text_file(
            self.path_finder.path_from_web_tests('TestExpectations'),
            textwrap.dedent("""\
                # results: [ Pass Timeout ]
                external/wpt/timeout.html [ Timeout ]
                """))
        self._event(action='test_start', test='/timeout.html')
        self._event(action='test_status',
                    test='/timeout.html',
                    subtest='unexpected fail',
                    status='FAIL',
                    expected='PASS')
        self._event(action='test_status',
                    test='/timeout.html',
                    subtest='timeout',
                    status='TIMEOUT')
        self._event(action='test_end', test='/timeout.html', status='TIMEOUT')

        result = self.processor.sink.report_individual_test_result.call_args.kwargs[
            'result']
        self.assertEqual(result.name, 'external/wpt/timeout.html')
        self.assertEqual(result.actual, 'TIMEOUT')
        self.assertEqual(result.expected, {'TIMEOUT'})
        self.assertFalse(result.unexpected)

    def test_report_skip(self):
        self.fs.write_text_file(
            self.path_finder.path_from_web_tests('NeverFixTests'),
            textwrap.dedent("""\
                # results: [ Skip ]
                external/wpt/reftest.html [ Skip ]
                """))
        self._event(action='test_start', test='/reftest.html')
        self._event(action='test_end', test='/reftest.html', status='SKIP')

        result = self.processor.sink.report_individual_test_result.call_args.kwargs[
            'result']
        self.assertEqual(result.name, 'external/wpt/reftest.html')
        self.assertEqual(result.actual, 'SKIP')
        self.assertEqual(result.expected, {'SKIP'})
        self.assertFalse(result.unexpected)

    def test_extract_text(self):
        self.fs.write_text_file(
            self.path_finder.path_from_wpt_tests(
                'variant_foo=baz-expected.txt'),
            textwrap.dedent("""\
                This is a testharness.js-based test.
                [FAIL] subtest
                  expected-message
                Harness: the test ran to completion.

                """))
        with self.fs.patch_builtins():
            self._event(action='test_start', test='/variant.html?foo=baz')
            self._event(action='test_status',
                        test='/variant.html?foo=baz',
                        subtest='subtest',
                        status='PRECONDITION_FAILED',
                        expected='FAIL',
                        message='actual-message')
            self._event(action='test_end',
                        test='/variant.html?foo=baz',
                        status='OK')

        self.assertEqual(
            self.fs.read_text_file(
                self.fs.join('/mock-checkout', 'out', 'Default',
                             'layout-test-results', 'external', 'wpt',
                             'variant_foo=baz-actual.txt')),
            textwrap.dedent("""\
                This is a testharness.js-based test.
                [PRECONDITION_FAILED] subtest
                  actual-message
                Harness: the test ran to completion.
                """))
        self.assertEqual(
            self.fs.read_text_file(
                self.fs.join('/mock-checkout', 'out', 'Default',
                             'layout-test-results', 'external', 'wpt',
                             'variant_foo=baz-expected.txt')),
            textwrap.dedent("""\
                This is a testharness.js-based test.
                [FAIL] subtest
                  expected-message
                Harness: the test ran to completion.
                """))
        diff_lines = self.fs.read_text_file(
            self.fs.join('/mock-checkout', 'out', 'Default',
                         'layout-test-results', 'external', 'wpt',
                         'variant_foo=baz-diff.txt')).splitlines()
        self.assertIn('-  expected-message', diff_lines)
        self.assertIn('+  actual-message', diff_lines)
        pretty_diff = self.fs.read_text_file(
            self.fs.join('/mock-checkout', 'out', 'Default',
                         'layout-test-results', 'external', 'wpt',
                         'variant_foo=baz-pretty-diff.html'))
        self.assertIn('expected-message', pretty_diff)
        self.assertIn('actual-message', pretty_diff)

    def test_extract_text_multiglobal(self):
        self.fs.write_text_file(
            self.path_finder.path_from_web_tests(
                'wpt_internal', 'dir',
                'multiglob.https.any.worker-expected.txt'),
            textwrap.dedent("""\
                This is a testharness.js-based test.
                [FAIL] subtest
                Harness: the test ran to completion.
                """))
        with self.fs.patch_builtins():
            self._event(
                action='test_start',
                test='/wpt_internal/dir/multiglob.https.any.worker.html')
            self._event(
                action='test_end',
                test='/wpt_internal/dir/multiglob.https.any.worker.html',
                status='ERROR',
                expected='OK',
                message="Uncaught SyntaxError: Unexpected token ')'")
        self.assertEqual(
            self.fs.read_text_file(
                self.fs.join('/mock-checkout', 'out', 'Default',
                             'layout-test-results', 'wpt_internal', 'dir',
                             'multiglob.https.any.worker-actual.txt')),
            textwrap.dedent("""\
                This is a testharness.js-based test.
                Harness Error. harness_status.status = 1 , harness_status.message = Uncaught SyntaxError: Unexpected token ')'
                Harness: the test ran to completion.
                """))
        self.assertEqual(
            self.fs.read_text_file(
                self.fs.join('/mock-checkout', 'out', 'Default',
                             'layout-test-results', 'wpt_internal', 'dir',
                             'multiglob.https.any.worker-expected.txt')),
            textwrap.dedent("""\
                This is a testharness.js-based test.
                [FAIL] subtest
                Harness: the test ran to completion.
                """))

    def test_extract_text_all_pass(self):
        with self.fs.patch_builtins():
            self._event(action='test_start', test='/variant.html?foo=baz')
            self._event(action='test_status',
                        test='/variant.html?foo=baz',
                        subtest='passing subtest',
                        status='PASS')
            self._event(action='test_end',
                        test='/variant.html?foo=baz',
                        status='OK')
        self.assertFalse(
            self.fs.exists(
                self.fs.join('/mock-checkout', 'out', 'Default',
                             'layout-test-results', 'external', 'wpt',
                             'variant_foo=baz-actual.txt')))

    def test_extract_text_reset_results_testharness(self):
        self.processor.reset_results = True
        with self.fs.patch_builtins():
            self._event(action='test_start', test='/variant.html?foo=baz')
            self._event(action='test_status',
                        test='/variant.html?foo=baz',
                        subtest='passing subtest',
                        status='PASS')
            self._event(action='test_end',
                        test='/variant.html?foo=baz',
                        status='OK')
        self.assertEqual(
            self.fs.read_text_file(
                self.path_finder.path_from_web_tests(
                    'platform', 'test-linux-trusty', 'external', 'wpt',
                    'variant_foo=baz-expected.txt')),
            textwrap.dedent("""\
                This is a testharness.js-based test.
                All subtests passed and are omitted for brevity.
                See https://chromium.googlesource.com/chromium/src/+/HEAD/docs/testing/writing_web_tests.md#Text-Test-Baselines for details.
                Harness: the test ran to completion.
                """))

    def test_extract_text_reset_results_wdspec(self):
        self.processor.reset_results = True
        with self.fs.patch_builtins():
            self._event(action='test_start', test='/test.py')
            self._event(action='test_status',
                        test='/test.py',
                        subtest='passing subtest',
                        status='PASS')
            self._event(action='test_end', test='/test.py', status='OK')
        self.assertEqual(
            self.fs.read_text_file(
                self.path_finder.path_from_web_tests('platform',
                                                     'test-linux-trusty',
                                                     'external', 'wpt',
                                                     'test-expected.txt')),
            textwrap.dedent("""\
                This is a wdspec test.
                All subtests passed and are omitted for brevity.
                See https://chromium.googlesource.com/chromium/src/+/HEAD/docs/testing/writing_web_tests.md#Text-Test-Baselines for details.
                Harness: the test ran to completion.
                """))

    def test_extract_text_repeat_within_suite(self):
        """Extract only the last artifacts from `--repeat-each`."""
        self.processor.repeat_each = 2
        self._event(action='test_start', test='/test.html')
        self._event(action='test_end',
                    test='/test.html',
                    status='ERROR',
                    message='error 1')
        self._event(action='test_start', test='/test.html')
        self._event(action='test_end',
                    test='/test.html',
                    status='ERROR',
                    message='error 2')

        results_dir = self.fs.join('/mock-checkout', 'out', 'Default',
                                   'layout-test-results')
        output = self.fs.read_text_file(
            self.fs.join(results_dir, 'external', 'wpt', 'test-actual.txt'))
        self.assertIn('error 2', output)
        self.assertNotIn('error 1', output)
        self.assertFalse(self.fs.exists(self.fs.join(results_dir, 'retry_1')))

    def test_extract_screenshots(self):
        self._event(action='test_start', test='/reftest.html')
        self._event(action='test_end',
                    test='/reftest.html',
                    status='FAIL',
                    expected='PASS',
                    extra={
                        'reftest_screenshots': [{
                            'url': '/reftest.html',
                            'screenshot': 'abcd',
                        }, '==', {
                            'url': '/reftest-ref.html',
                            'screenshot': 'bcde',
                        }],
                    })
        self.assertEqual(
            self.fs.read_binary_file(
                self.fs.join('/mock-checkout', 'out', 'Default',
                             'layout-test-results', 'external', 'wpt',
                             'reftest-actual.png')), base64.b64decode('abcd'))
        self.assertEqual(
            self.fs.read_binary_file(
                self.fs.join('/mock-checkout', 'out', 'Default',
                             'layout-test-results', 'external',
                             'wpt', 'reftest-expected.png')),
            base64.b64decode('bcde'))
        self.assertEqual(
            self.fs.read_binary_file(
                self.fs.join('/mock-checkout', 'out', 'Default',
                             'layout-test-results', 'external', 'wpt',
                             'reftest-diff.png')), '\n'.join([
                                 '< bcde',
                                 '---',
                                 '> abcd',
                             ]))

    def test_extract_screenshots_match_and_mismatch(self):
        self._event(action='test_start',
                    test='/reftest-multiple.html',
                    subsuite='fake-vts')
        self._event(action='test_end',
                    test='/reftest-multiple.html',
                    subsuite='fake-vts',
                    status='FAIL',
                    expected='PASS',
                    extra={
                        'reftest_screenshots': [{
                            'url': '/reftest-ref.html',
                            'screenshot': 'abcd',
                        }, '!=', {
                            'url': '/reftest-mismatch.html',
                            'screenshot': 'abcd',
                        }],
                    })
        self.assertEqual(
            self.fs.read_binary_file(
                self.fs.join('/mock-checkout', 'out', 'Default',
                             'layout-test-results', 'virtual', 'fake-vts',
                             'external', 'wpt',
                             'reftest-multiple-actual.png')),
            base64.b64decode('abcd'))
        self.assertEqual(
            self.fs.read_binary_file(
                self.fs.join('/mock-checkout', 'out', 'Default',
                             'layout-test-results', 'virtual', 'fake-vts',
                             'external', 'wpt',
                             'reftest-multiple-expected.png')),
            base64.b64decode('abcd'))
        self.assertFalse(
            self.fs.exists(
                self.fs.join('/mock-checkout', 'out', 'Default',
                             'layout-test-results', 'virtual', 'fake-vts',
                             'external', 'wpt', 'reftest-multiple-diff.png')))

    def test_extract_screenshots_for_wpt_internal(self):
        self._event(action='test_start', test='/wpt_internal/reftest.html')
        self._event(action='test_end',
                    test='/wpt_internal/reftest.html',
                    status='FAIL',
                    expected='PASS',
                    extra={
                        'reftest_screenshots': [{
                            'url': '/wpt_internal/reftest.html',
                            'screenshot': 'abcd',
                        }, '==', {
                            'url': '/wpt_internal/reftest-ref.html',
                            'screenshot': 'bcde',
                        }],
                    })
        self.assertEqual(
            self.fs.read_binary_file(
                self.fs.join('/mock-checkout', 'out', 'Default',
                             'layout-test-results', 'wpt_internal',
                             'reftest-actual.png')), base64.b64decode('abcd'))
        self.assertEqual(
            self.fs.read_binary_file(
                self.fs.join('/mock-checkout', 'out', 'Default',
                             'layout-test-results',
                             'wpt_internal', 'reftest-expected.png')),
            base64.b64decode('bcde'))
        self.assertEqual(
            self.fs.read_binary_file(
                self.fs.join('/mock-checkout', 'out', 'Default',
                             'layout-test-results', 'wpt_internal',
                             'reftest-diff.png')), '\n'.join([
                                 '< bcde',
                                 '---',
                                 '> abcd',
                             ]))

    def test_no_diff_artifacts_on_pass(self):
        self.fs.write_text_file(
            self.path_finder.path_from_wpt_tests('test.html.ini'),
            textwrap.dedent("""\
                [test.html]
                  expected: OK
                """))
        with self.fs.patch_builtins():
            self._event(action='test_start', test='/test.html')
            self._event(action='test_end', test='/test.html', status='OK')
            self._event(action='test_start', test='/reftest.html')
            self._event(action='test_end',
                        test='/reftest.html',
                        status='PASS',
                        extra={
                            'reftest_screenshots': [{
                                'url': '/reftest.html',
                                'screenshot': 'abcd',
                            }, {
                                'url': '/reftest-ref.html',
                                'screenshot': 'bcde'
                            }],
                        })
        for filename in (
                'test-expected.txt',
                'test-actual.txt',
                'test-diff.txt',
                'test-pretty-diff.html',
                'reftest-expected.png',
                'reftest-actual.png',
                'reftest-diff.png',
        ):
            self.assertFalse(
                self.fs.exists(
                    self.fs.join('/mock-checkout', 'out', 'Default',
                                 'layout-test-results', filename)))

    def test_extract_logs_interleaved(self):
        self.processor.browser_outputs.capacity = 2
        self._event(action='process_output',
                    command='chromedriver --port=101',
                    data='Running test.html',
                    process='101')
        self._event(action='process_output',
                    command='git rev-parse HEAD',
                    data='Do not log; unrelated executable',
                    process='99999')
        self._event(action='test_start', test='/test.html')
        self._event(action='test_start', test='/timeout.html')
        self._event(action='process_output',
                    command='chromedriver --port=101',
                    data='cp1252 cannot represent \u03c0, the Greek letter',
                    process='101')
        self._event(action='process_output',
                    command='chromedriver --port=202',
                    data='Running timeout.html',
                    process='202')
        self._event(action='test_end',
                    test='/test.html',
                    status='OK',
                    message='Remote-end stacktrace:\n\n#0 0xffff <unknown>\n',
                    extra={'browser_pid': 101})
        self._event(action='test_end',
                    test='/timeout.html',
                    status='TIMEOUT',
                    extra={'browser_pid': 202})
        self._event(action='process_output',
                    command='chromedriver --port=101',
                    data='Do not log; this event occurs after `test_end`',
                    process='101')

        self.assertEqual(
            self.fs.read_text_file(
                self.fs.join('/mock-checkout', 'out', 'Default',
                             'layout-test-results', 'external', 'wpt',
                             'test-crash-log.txt')),
            textwrap.dedent("""\
                Remote-end stacktrace:

                #0 0xffff <unknown>
                """))
        self.assertEqual(
            self.fs.read_text_file(
                self.fs.join('/mock-checkout', 'out', 'Default',
                             'layout-test-results', 'external', 'wpt',
                             'test-stderr.txt')),
            textwrap.dedent("""\
                Running test.html
                cp1252 cannot represent \u03c0, the Greek letter
                """))
        self.assertEqual(
            self.fs.read_text_file(
                self.fs.join('/mock-checkout', 'out', 'Default',
                             'layout-test-results', 'external', 'wpt',
                             'timeout-stderr.txt')),
            textwrap.dedent("""\
                Running timeout.html
                """))

    def test_extract_leak_log(self):
        leak_counters = {'live_documents': (1, 1), 'live_nodes': (4, 5)}
        self._event(action='test_start', test='/test.html')
        self._event(action='test_end',
                    test='/test.html',
                    status='CRASH',
                    expected='OK',
                    extra={'leak_counters': leak_counters})

        log_path = self.fs.join('/mock-checkout', 'out', 'Default',
                                'layout-test-results', 'external', 'wpt',
                                'test-leak-log.txt')
        self.assertEqual(json.loads(self.fs.read_text_file(log_path)), {
            'live_documents': [1, 1],
            'live_nodes': [4, 5],
        })

    def test_extract_command(self):
        self._event(action='test_start', test='/test.html')
        self._event(
            action='process_output',
            command='chromedriver',
            data=('[INFO] Launching chrome: /path/to/chrome --headless=new '
                  '--host-resolver-rules=MAP * ^NOTFOUND --switch data:,'),
            process='101')
        self._event(action='test_end',
                    test='/test.html',
                    status='OK',
                    extra={'browser_pid': 101})
        self._event(action='test_start', test='/timeout.html')
        self._event(action='test_end',
                    test='/timeout.html',
                    status='OK',
                    extra={'browser_pid': 101})
        self._event(action='test_start', test='/reftest.html')
        self._event(action='test_end',
                    test='/reftest.html',
                    status='PASS',
                    extra={'browser_pid': 202})

        self.assertEqual(
            self.fs.read_text_file(
                self.fs.join('/mock-checkout', 'out', 'Default',
                             'layout-test-results', 'external', 'wpt',
                             'test-command.txt')),
            "/path/to/chrome '--host-resolver-rules=MAP * ^NOTFOUND' --switch "
            'http://web-platform.test:8001/test.html')
        self.assertEqual(
            self.fs.read_text_file(
                self.fs.join('/mock-checkout', 'out', 'Default',
                             'layout-test-results', 'external', 'wpt',
                             'timeout-command.txt')),
            "/path/to/chrome '--host-resolver-rules=MAP * ^NOTFOUND' --switch "
            'http://web-platform.test:8001/timeout.html',
            'Command should be copied from the previous test.')
        self.assertFalse(
            self.fs.exists(
                self.fs.join('/mock-checkout', 'out', 'Default',
                             'layout-test-results', 'external', 'wpt',
                             'reftest-command.txt')),
            'Command has not been observed for this browser yet.')

    def test_unknown_event(self):
        self._event(action='unknown', time=1000)
        (message, ) = self.logMessages()
        self.assertRegex(message,
                         "WARNING: 'unknown' event received, but not handled")

    def test_unknown_test(self):
        with self.assertRaises(EventProcessingError):
            self._event(action='test_start',
                        test='/does-not-exist-in-manifest.html')

    def test_unstarted_test(self):
        with self.assertRaises(EventProcessingError):
            self._event(action='test_end', test='/reftest.html', status='PASS')

    def test_shutdown(self):
        with self.assertRaises(StreamShutdown):
            self._event(action='shutdown')

    def test_shutdown_with_incomplete_tests(self):
        self._event(action='suite_start',
                    tests={
                        'fake-vts:/': ['/reftest-multiple.html'],
                        '/wpt_internal': ['/wpt_internal/reftest.html'],
                        '/wpt_internal/dir':
                        ['/wpt_internal/dir/multiglob.https.any.html'],
                    })
        self._event(action='test_start', test='/wpt_internal/reftest.html')
        self._event(action='test_end',
                    test='/wpt_internal/reftest.html',
                    status='PASS')
        self._event(action='test_start',
                    test='/reftest-multiple.html',
                    subsuite='fake-vts')
        with self.assertRaises(StreamShutdown):
            self._event(action='shutdown')

        self.assertLog([
            'WARNING: 2 test(s) never completed.\n',
        ])
        report_mock = self.processor.sink.report_individual_test_result
        results = {
            args.kwargs['result']
            for args in report_mock.call_args_list
        }
        unexpected_skips = {
            result.name: result
            for result in results
            if result.actual == ResultType.Skip and result.unexpected
        }
        self.assertEqual(
            set(unexpected_skips), {
                'virtual/fake-vts/external/wpt/reftest-multiple.html',
                'wpt_internal/dir/multiglob.https.any.html',
            })
        for test_name, result in unexpected_skips.items():
            self.assertEqual(result.took, 0, test_name)

    def test_early_exit_from_failures(self):
        self.fs.write_text_file(
            self.path_finder.path_from_wpt_tests(
                'variant_foo=baz-expected.txt'),
            textwrap.dedent("""\
                This is a testharness.js-based test.
                Harness Error. harness_status.status = 1 , harness_status.message = ignore this
                Harness: the test ran to completion.
                """))
        self.processor.failure_threshold = 2
        with mock.patch('os.kill') as kill_mock:
            self._event(action='test_start', test='/variant.html?foo=bar/abc')
            self._event(action='test_end',
                        test='/variant.html?foo=bar/abc',
                        status='ERROR',
                        expected='OK')
            self._event(action='test_start', test='/variant.html?foo=baz')
            self._event(action='test_end',
                        test='/variant.html?foo=baz',
                        status='ERROR')
            kill_mock.assert_not_called()
            self._event(action='test_start', test='/reftest.html')
            self._event(action='test_end',
                        test='/reftest.html',
                        status='FAIL',
                        expected='PASS')
            kill_mock.assert_called_once()

    def test_early_exit_from_crashes_and_timeouts(self):
        self.processor.crash_timeout_threshold = 2
        with mock.patch('os.kill') as kill_mock:
            self._event(action='test_start', test='/variant.html?foo=bar/abc')
            self._event(action='test_end',
                        test='/variant.html?foo=bar/abc',
                        status='ERROR',
                        expected='OK')
            self._event(action='test_start', test='/variant.html?foo=baz')
            self._event(action='test_end',
                        test='/variant.html?foo=baz',
                        status='TIMEOUT',
                        expected='OK')
            kill_mock.assert_not_called()
            self._event(action='test_start', test='/reftest.html')
            self._event(action='test_end',
                        test='/reftest.html',
                        status='CRASH',
                        expected='OK')
            kill_mock.assert_called_once()

    def test_process_json(self):
        """Ensure that various JSONs are written to the correct locations."""
        diff_stats = {'maxDifference': 100, 'maxPixels': 3}
        with mock.patch.object(self.processor.port,
                               'diff_image',
                               return_value=(..., diff_stats, ...)):
            for _ in range(2):
                self._event(action='suite_start',
                            tests={'/': ['/reftest.html']})
                self._event(action='test_start', test='/reftest.html')
                self._event(action='process_output',
                            process='101',
                            command='chromedriver --port=101',
                            data='[101:101:INFO] This is Chrome version 125')
                self._event(action='test_end',
                            test='/reftest.html',
                            status='FAIL',
                            expected='PASS',
                            extra={
                                'reftest_screenshots': [{
                                    'url': '/reftest-ref.html',
                                    'screenshot': 'abcd',
                                }, '==', {
                                    'url': '/reftest.html',
                                    'screenshot': 'bcde',
                                }],
                                'browser_pid':
                                101,
                            })
                self._event(action='test_start', test='/test.html')
                self._event(action='test_end', test='/test.html', status='OK')
                self._event(action='suite_end')
        self.processor.process_results_json()

        full_json = json.loads(
            self.fs.read_text_file(
                self.fs.join('/mock-checkout', 'out', 'Default',
                             'layout-test-results', 'full_results.json')))
        self.assertEqual(full_json['num_regressions'], 1)
        unexpected_fail = full_json['tests']['external']['wpt']['reftest.html']
        self.assertTrue(unexpected_fail['has_stderr'])
        self.assertEqual(unexpected_fail['artifacts']['stderr'], [
            self.fs.join('layout-test-results', 'external', 'wpt',
                         'reftest-stderr.txt'),
            self.fs.join('layout-test-results', 'retry_1', 'external', 'wpt',
                         'reftest-stderr.txt'),
        ])
        self.assertEqual(unexpected_fail['image_diff_stats'], diff_stats)

        path_to_failing_results = self.fs.join('/mock-checkout', 'out',
                                               'Default',
                                               'layout-test-results',
                                               'failing_results.json')
        failing_results_match = re.fullmatch(
            'ADD_RESULTS\((?P<json>.*)\);',
            self.fs.read_text_file(path_to_failing_results))
        self.assertIsNotNone(failing_results_match)
        failing_results = json.loads(failing_results_match['json'])
        self.assertIn('external', failing_results['tests'])
        self.assertIn('wpt', failing_results['tests']['external'])
        self.assertIn('reftest.html',
                      failing_results['tests']['external']['wpt'])
        self.assertNotIn('test.html',
                         failing_results['tests']['external']['wpt'])
        self.assertRegex(self.fs.read_text_file(path_to_failing_results),
                         'ADD_RESULTS\(.*\);$')

    def test_trim_json_to_regressions(self):
        results = {
            'tests': {
                'test.html': {
                    'expected': 'PASS',
                    'actual': 'PASS',
                    'artifacts': {
                        'wpt_actual_status': ['OK'],
                    },
                },
                'variant.html?foo=bar/abc': {
                    'expected': 'FAIL',
                    'actual': 'PASS',
                    'artifacts': {
                        'wpt_actual_status': ['OK'],
                    },
                    'is_unexpected': True,
                },
                'variant.html?foo=baz': {
                    'expected': 'PASS',
                    'actual': 'FAIL',
                    'artifacts': {
                        'wpt_actual_status': ['ERROR'],
                    },
                    'is_unexpected': True,
                    'is_regression': True,
                },
            },
            'path_delimiter': '/',
        }
        self.processor.trim_to_regressions(results['tests'])

        self.assertNotIn('test.html', results['tests'])
        self.assertNotIn('variant.html?foo=bar/abc', results['tests'])
        self.assertIn('variant.html?foo=baz', results['tests'])

    def test_process_wpt_report(self):
        report_src = self.fs.join('/mock-checkout', 'out', 'Default',
                                  'wpt_report.json')
        self.fs.write_text_file(report_src, json.dumps(self.wpt_report))
        self.processor.process_wpt_report(report_src)
        report_dest = self.fs.join('/mock-checkout', 'out', 'Default',
                                   'layout-test-results', 'wpt_report.json')
        self.processor.sink.report_invocation_level_artifacts.assert_called_once_with(
            {
                'wpt_report.json': {
                    'filePath': report_dest,
                },
            })
        report = json.loads(self.fs.read_text_file(report_dest))
        self.assertEqual(report['run_info'], self.wpt_report['run_info'])
        self.assertEqual(report['results'], self.wpt_report['results'])