1# Copyright (C) 2010 Google Inc. All rights reserved.
2#
3# Redistribution and use in source and binary forms, with or without
4# modification, are permitted provided that the following conditions are
5# met:
6#
7#     * Redistributions of source code must retain the above copyright
8# notice, this list of conditions and the following disclaimer.
9#     * Redistributions in binary form must reproduce the above
10# copyright notice, this list of conditions and the following disclaimer
11# in the documentation and/or other materials provided with the
12# distribution.
13#     * Neither the Google name nor the names of its
14# contributors may be used to endorse or promote products derived from
15# this software without specific prior written permission.
16#
17# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29import base64
30import copy
31import sys
32import time
33
34from webkitpy.layout_tests.port import DeviceFailure, Driver, DriverOutput, Port
35from webkitpy.layout_tests.port.base import VirtualTestSuite
36from webkitpy.layout_tests.models.test_configuration import TestConfiguration
37from webkitpy.layout_tests.models import test_run_results
38from webkitpy.common.system.filesystem_mock import MockFileSystem
39from webkitpy.common.system.crashlogs import CrashLogs
40
41
42# This sets basic expectations for a test. Each individual expectation
43# can be overridden by a keyword argument in TestList.add().
44class TestInstance(object):
45    def __init__(self, name):
46        self.name = name
47        self.base = name[(name.rfind("/") + 1):name.rfind(".")]
48        self.crash = False
49        self.web_process_crash = False
50        self.exception = False
51        self.keyboard = False
52        self.error = ''
53        self.timeout = False
54        self.is_reftest = False
55        self.device_failure = False
56        self.leak = False
57
58        # The values of each field are treated as raw byte strings. They
59        # will be converted to unicode strings where appropriate using
60        # FileSystem.read_text_file().
61        self.actual_text = self.base + '-txt'
62        self.actual_checksum = self.base + '-checksum'
63
64        # We add the '\x8a' for the image file to prevent the value from
65        # being treated as UTF-8 (the character is invalid)
66        self.actual_image = self.base + '\x8a' + '-png' + 'tEXtchecksum\x00' + self.actual_checksum
67
68        self.expected_text = self.actual_text
69        self.expected_image = self.actual_image
70
71        self.actual_audio = None
72        self.expected_audio = None
73
74
75# This is an in-memory list of tests, what we want them to produce, and
76# what we want to claim are the expected results.
77class TestList(object):
78    def __init__(self):
79        self.tests = {}
80
81    def add(self, name, **kwargs):
82        test = TestInstance(name)
83        for key, value in kwargs.items():
84            test.__dict__[key] = value
85        self.tests[name] = test
86
87    def add_reftest(self, name, reference_name, same_image, crash=False):
88        self.add(name, actual_checksum='xxx', actual_image='XXX', is_reftest=True, crash=crash)
89        if same_image:
90            self.add(reference_name, actual_checksum='xxx', actual_image='XXX', is_reftest=True)
91        else:
92            self.add(reference_name, actual_checksum='yyy', actual_image='YYY', is_reftest=True)
93
94    def keys(self):
95        return self.tests.keys()
96
97    def __contains__(self, item):
98        return item in self.tests
99
100    def __getitem__(self, item):
101        return self.tests[item]
102
103#
104# These numbers may need to be updated whenever we add or delete tests. This includes virtual tests.
105#
106TOTAL_TESTS = 113
107TOTAL_SKIPS = 29
108
109UNEXPECTED_PASSES = 1
110UNEXPECTED_FAILURES = 26
111
112def unit_test_list():
113    tests = TestList()
114    tests.add('failures/expected/crash.html', crash=True)
115    tests.add('failures/expected/exception.html', exception=True)
116    tests.add('failures/expected/device_failure.html', device_failure=True)
117    tests.add('failures/expected/timeout.html', timeout=True)
118    tests.add('failures/expected/leak.html', leak=True)
119    tests.add('failures/expected/missing_text.html', expected_text=None)
120    tests.add('failures/expected/needsrebaseline.html', actual_text='needsrebaseline text')
121    tests.add('failures/expected/needsmanualrebaseline.html', actual_text='needsmanualrebaseline text')
122    tests.add('failures/expected/image.html',
123              actual_image='image_fail-pngtEXtchecksum\x00checksum_fail',
124              expected_image='image-pngtEXtchecksum\x00checksum-png')
125    tests.add('failures/expected/image_checksum.html',
126              actual_checksum='image_checksum_fail-checksum',
127              actual_image='image_checksum_fail-png')
128    tests.add('failures/expected/audio.html',
129              actual_audio=base64.b64encode('audio_fail-wav'), expected_audio='audio-wav',
130              actual_text=None, expected_text=None,
131              actual_image=None, expected_image=None,
132              actual_checksum=None)
133    tests.add('failures/expected/keyboard.html', keyboard=True)
134    tests.add('failures/expected/missing_check.html',
135              expected_image='missing_check-png')
136    tests.add('failures/expected/missing_image.html', expected_image=None)
137    tests.add('failures/expected/missing_audio.html', expected_audio=None,
138              actual_text=None, expected_text=None,
139              actual_image=None, expected_image=None,
140              actual_checksum=None)
141    tests.add('failures/expected/missing_text.html', expected_text=None)
142    tests.add('failures/expected/newlines_leading.html',
143              expected_text="\nfoo\n", actual_text="foo\n")
144    tests.add('failures/expected/newlines_trailing.html',
145              expected_text="foo\n\n", actual_text="foo\n")
146    tests.add('failures/expected/newlines_with_excess_CR.html',
147              expected_text="foo\r\r\r\n", actual_text="foo\n")
148    tests.add('failures/expected/text.html', actual_text='text_fail-png')
149    tests.add('failures/expected/crash_then_text.html')
150    tests.add('failures/expected/skip_text.html', actual_text='text diff')
151    tests.add('failures/flaky/text.html')
152    tests.add('failures/unexpected/missing_text.html', expected_text=None)
153    tests.add('failures/unexpected/missing_check.html', expected_image='missing-check-png')
154    tests.add('failures/unexpected/missing_image.html', expected_image=None)
155    tests.add('failures/unexpected/missing_render_tree_dump.html', actual_text="""layer at (0,0) size 800x600
156  RenderView at (0,0) size 800x600
157layer at (0,0) size 800x34
158  RenderBlock {HTML} at (0,0) size 800x34
159    RenderBody {BODY} at (8,8) size 784x18
160      RenderText {#text} at (0,0) size 133x18
161        text run at (0,0) width 133: "This is an image test!"
162""", expected_text=None)
163    tests.add('failures/unexpected/crash.html', crash=True)
164    tests.add('failures/unexpected/crash-with-stderr.html', crash=True,
165              error="mock-std-error-output")
166    tests.add('failures/unexpected/web-process-crash-with-stderr.html', web_process_crash=True,
167              error="mock-std-error-output")
168    tests.add('failures/unexpected/pass.html')
169    tests.add('failures/unexpected/text-checksum.html',
170              actual_text='text-checksum_fail-txt',
171              actual_checksum='text-checksum_fail-checksum')
172    tests.add('failures/unexpected/text-image-checksum.html',
173              actual_text='text-image-checksum_fail-txt',
174              actual_image='text-image-checksum_fail-pngtEXtchecksum\x00checksum_fail',
175              actual_checksum='text-image-checksum_fail-checksum')
176    tests.add('failures/unexpected/checksum-with-matching-image.html',
177              actual_checksum='text-image-checksum_fail-checksum')
178    tests.add('failures/unexpected/skip_pass.html')
179    tests.add('failures/unexpected/text.html', actual_text='text_fail-txt')
180    tests.add('failures/unexpected/text_then_crash.html')
181    tests.add('failures/unexpected/timeout.html', timeout=True)
182    tests.add('failures/unexpected/leak.html', leak=True)
183    tests.add('http/tests/passes/text.html')
184    tests.add('http/tests/passes/image.html')
185    tests.add('http/tests/ssl/text.html')
186    tests.add('passes/args.html')
187    tests.add('passes/error.html', error='stuff going to stderr')
188    tests.add('passes/image.html')
189    tests.add('passes/audio.html',
190              actual_audio=base64.b64encode('audio-wav'), expected_audio='audio-wav',
191              actual_text=None, expected_text=None,
192              actual_image=None, expected_image=None,
193              actual_checksum=None)
194    tests.add('passes/platform_image.html')
195    tests.add('passes/checksum_in_image.html',
196              expected_image='tEXtchecksum\x00checksum_in_image-checksum')
197    tests.add('passes/skipped/skip.html')
198
199    # Note that here the checksums don't match but the images do, so this test passes "unexpectedly".
200    # See https://bugs.webkit.org/show_bug.cgi?id=69444 .
201    tests.add('failures/unexpected/checksum.html', actual_checksum='checksum_fail-checksum')
202
203    # Text output files contain "\r\n" on Windows.  This may be
204    # helpfully filtered to "\r\r\n" by our Python/Cygwin tooling.
205    tests.add('passes/text.html',
206              expected_text='\nfoo\n\n', actual_text='\nfoo\r\n\r\r\n')
207
208    # For reftests.
209    tests.add_reftest('passes/reftest.html', 'passes/reftest-expected.html', same_image=True)
210
211    # This adds a different virtual reference to ensure that that also works.
212    tests.add('virtual/virtual_passes/passes/reftest-expected.html', actual_checksum='xxx', actual_image='XXX', is_reftest=True)
213
214    tests.add_reftest('passes/mismatch.html', 'passes/mismatch-expected-mismatch.html', same_image=False)
215    tests.add_reftest('passes/svgreftest.svg', 'passes/svgreftest-expected.svg', same_image=True)
216    tests.add_reftest('passes/xhtreftest.xht', 'passes/xhtreftest-expected.html', same_image=True)
217    tests.add_reftest('passes/phpreftest.php', 'passes/phpreftest-expected-mismatch.svg', same_image=False)
218    tests.add_reftest('failures/expected/reftest.html', 'failures/expected/reftest-expected.html', same_image=False)
219    tests.add_reftest('failures/expected/mismatch.html', 'failures/expected/mismatch-expected-mismatch.html', same_image=True)
220    tests.add_reftest('failures/unexpected/crash-reftest.html', 'failures/unexpected/crash-reftest-expected.html', same_image=True, crash=True)
221    tests.add_reftest('failures/unexpected/reftest.html', 'failures/unexpected/reftest-expected.html', same_image=False)
222    tests.add_reftest('failures/unexpected/mismatch.html', 'failures/unexpected/mismatch-expected-mismatch.html', same_image=True)
223    tests.add('failures/unexpected/reftest-nopixel.html', actual_checksum=None, actual_image=None, is_reftest=True)
224    tests.add('failures/unexpected/reftest-nopixel-expected.html', actual_checksum=None, actual_image=None, is_reftest=True)
225    tests.add('reftests/foo/test.html')
226    tests.add('reftests/foo/test-ref.html')
227
228    tests.add('reftests/foo/multiple-match-success.html', actual_checksum='abc', actual_image='abc')
229    tests.add('reftests/foo/multiple-match-failure.html', actual_checksum='abc', actual_image='abc')
230    tests.add('reftests/foo/multiple-mismatch-success.html', actual_checksum='abc', actual_image='abc')
231    tests.add('reftests/foo/multiple-mismatch-failure.html', actual_checksum='abc', actual_image='abc')
232    tests.add('reftests/foo/multiple-both-success.html', actual_checksum='abc', actual_image='abc')
233    tests.add('reftests/foo/multiple-both-failure.html', actual_checksum='abc', actual_image='abc')
234
235    tests.add('reftests/foo/matching-ref.html', actual_checksum='abc', actual_image='abc')
236    tests.add('reftests/foo/mismatching-ref.html', actual_checksum='def', actual_image='def')
237    tests.add('reftests/foo/second-mismatching-ref.html', actual_checksum='ghi', actual_image='ghi')
238
239    # The following files shouldn't be treated as reftests
240    tests.add_reftest('reftests/foo/unlistedtest.html', 'reftests/foo/unlistedtest-expected.html', same_image=True)
241    tests.add('reftests/foo/reference/bar/common.html')
242    tests.add('reftests/foo/reftest/bar/shared.html')
243
244    tests.add('websocket/tests/passes/text.html')
245
246    # For testing that we don't run tests under platform/. Note that these don't contribute to TOTAL_TESTS.
247    tests.add('platform/test-mac-leopard/http/test.html')
248    tests.add('platform/test-win-win7/http/test.html')
249
250    # For testing if perf tests are running in a locked shard.
251    tests.add('perf/foo/test.html')
252    tests.add('perf/foo/test-ref.html')
253
254    # For testing --pixel-test-directories.
255    tests.add('failures/unexpected/pixeldir/image_in_pixeldir.html',
256        actual_image='image_in_pixeldir-pngtEXtchecksum\x00checksum_fail',
257        expected_image='image_in_pixeldir-pngtEXtchecksum\x00checksum-png')
258    tests.add('failures/unexpected/image_not_in_pixeldir.html',
259        actual_image='image_not_in_pixeldir-pngtEXtchecksum\x00checksum_fail',
260        expected_image='image_not_in_pixeldir-pngtEXtchecksum\x00checksum-png')
261
262    # For testing that virtual test suites don't expand names containing themselves
263    # See webkit.org/b/97925 and base_unittest.PortTest.test_tests().
264    tests.add('passes/test-virtual-passes.html')
265    tests.add('passes/virtual_passes/test-virtual-passes.html')
266
267    return tests
268
269
270# Here we use a non-standard location for the layout tests, to ensure that
271# this works. The path contains a '.' in the name because we've seen bugs
272# related to this before.
273
274LAYOUT_TEST_DIR = '/test.checkout/LayoutTests'
275PERF_TEST_DIR = '/test.checkout/PerformanceTests'
276
277
278# Here we synthesize an in-memory filesystem from the test list
279# in order to fully control the test output and to demonstrate that
280# we don't need a real filesystem to run the tests.
281def add_unit_tests_to_mock_filesystem(filesystem):
282    # Add the test_expectations file.
283    filesystem.maybe_make_directory('/mock-checkout/LayoutTests')
284    if not filesystem.exists('/mock-checkout/LayoutTests/TestExpectations'):
285        filesystem.write_text_file('/mock-checkout/LayoutTests/TestExpectations', """
286Bug(test) failures/expected/crash.html [ Crash ]
287Bug(test) failures/expected/crash_then_text.html [ Failure ]
288Bug(test) failures/expected/image.html [ ImageOnlyFailure ]
289Bug(test) failures/expected/needsrebaseline.html [ NeedsRebaseline ]
290Bug(test) failures/expected/needsmanualrebaseline.html [ NeedsManualRebaseline ]
291Bug(test) failures/expected/audio.html [ Failure ]
292Bug(test) failures/expected/image_checksum.html [ ImageOnlyFailure ]
293Bug(test) failures/expected/mismatch.html [ ImageOnlyFailure ]
294Bug(test) failures/expected/missing_check.html [ Missing Pass ]
295Bug(test) failures/expected/missing_image.html [ Missing Pass ]
296Bug(test) failures/expected/missing_audio.html [ Missing Pass ]
297Bug(test) failures/expected/missing_text.html [ Missing Pass ]
298Bug(test) failures/expected/newlines_leading.html [ Failure ]
299Bug(test) failures/expected/newlines_trailing.html [ Failure ]
300Bug(test) failures/expected/newlines_with_excess_CR.html [ Failure ]
301Bug(test) failures/expected/reftest.html [ ImageOnlyFailure ]
302Bug(test) failures/expected/text.html [ Failure ]
303Bug(test) failures/expected/timeout.html [ Timeout ]
304Bug(test) failures/expected/keyboard.html [ WontFix ]
305Bug(test) failures/expected/exception.html [ WontFix ]
306Bug(test) failures/expected/device_failure.html [ WontFix ]
307Bug(test) failures/expected/leak.html [ Leak ]
308Bug(test) failures/unexpected/pass.html [ Failure ]
309Bug(test) passes/skipped/skip.html [ Skip ]
310Bug(test) passes/text.html [ Pass ]
311""")
312
313    filesystem.maybe_make_directory(LAYOUT_TEST_DIR + '/reftests/foo')
314    filesystem.write_text_file(LAYOUT_TEST_DIR + '/reftests/foo/reftest.list', """
315== test.html test-ref.html
316
317== multiple-match-success.html mismatching-ref.html
318== multiple-match-success.html matching-ref.html
319== multiple-match-failure.html mismatching-ref.html
320== multiple-match-failure.html second-mismatching-ref.html
321!= multiple-mismatch-success.html mismatching-ref.html
322!= multiple-mismatch-success.html second-mismatching-ref.html
323!= multiple-mismatch-failure.html mismatching-ref.html
324!= multiple-mismatch-failure.html matching-ref.html
325== multiple-both-success.html matching-ref.html
326== multiple-both-success.html mismatching-ref.html
327!= multiple-both-success.html second-mismatching-ref.html
328== multiple-both-failure.html matching-ref.html
329!= multiple-both-failure.html second-mismatching-ref.html
330!= multiple-both-failure.html matching-ref.html
331""")
332
333    # FIXME: This test was only being ignored because of missing a leading '/'.
334    # Fixing the typo causes several tests to assert, so disabling the test entirely.
335    # Add in a file should be ignored by port.find_test_files().
336    #files[LAYOUT_TEST_DIR + '/userscripts/resources/iframe.html'] = 'iframe'
337
338    def add_file(test, suffix, contents):
339        dirname = filesystem.join(LAYOUT_TEST_DIR, test.name[0:test.name.rfind('/')])
340        base = test.base
341        filesystem.maybe_make_directory(dirname)
342        filesystem.write_binary_file(filesystem.join(dirname, base + suffix), contents)
343
344    # Add each test and the expected output, if any.
345    test_list = unit_test_list()
346    for test in test_list.tests.values():
347        add_file(test, test.name[test.name.rfind('.'):], '')
348        if test.is_reftest:
349            continue
350        if test.actual_audio:
351            add_file(test, '-expected.wav', test.expected_audio)
352            continue
353        add_file(test, '-expected.txt', test.expected_text)
354        add_file(test, '-expected.png', test.expected_image)
355
356    filesystem.write_text_file(filesystem.join(LAYOUT_TEST_DIR, 'virtual', 'virtual_passes', 'passes', 'args-expected.txt'), 'args-txt --virtual-arg')
357    # Clear the list of written files so that we can watch what happens during testing.
358    filesystem.clear_written_files()
359
360
361class TestPort(Port):
362    port_name = 'test'
363    default_port_name = 'test-mac-leopard'
364
365    """Test implementation of the Port interface."""
366    ALL_BASELINE_VARIANTS = (
367        'test-linux-x86_64',
368        'test-mac-snowleopard', 'test-mac-leopard',
369        'test-win-win7', 'test-win-xp',
370    )
371
372    FALLBACK_PATHS = {
373        'xp':          ['test-win-win7', 'test-win-xp'],
374        'win7':        ['test-win-win7'],
375        'leopard':     ['test-mac-leopard', 'test-mac-snowleopard'],
376        'snowleopard': ['test-mac-snowleopard'],
377        'lucid':       ['test-linux-x86_64', 'test-win-win7'],
378    }
379
380    @classmethod
381    def determine_full_port_name(cls, host, options, port_name):
382        if port_name == 'test':
383            return TestPort.default_port_name
384        return port_name
385
386    def __init__(self, host, port_name=None, **kwargs):
387        Port.__init__(self, host, port_name or TestPort.default_port_name, **kwargs)
388        self._tests = unit_test_list()
389        self._flakes = set()
390
391        # FIXME: crbug.com/279494. This needs to be in the "real layout tests
392        # dir" in a mock filesystem, rather than outside of the checkout, so
393        # that tests that want to write to a TestExpectations file can share
394        # this between "test" ports and "real" ports.  This is the result of
395        # rebaseline_unittest.py having tests that refer to "real" port names
396        # and real builders instead of fake builders that point back to the
397        # test ports. rebaseline_unittest.py needs to not mix both "real" ports
398        # and "test" ports
399
400        self._generic_expectations_path = '/mock-checkout/LayoutTests/TestExpectations'
401        self._results_directory = None
402
403        self._operating_system = 'mac'
404        if self._name.startswith('test-win'):
405            self._operating_system = 'win'
406        elif self._name.startswith('test-linux'):
407            self._operating_system = 'linux'
408
409        version_map = {
410            'test-win-xp': 'xp',
411            'test-win-win7': 'win7',
412            'test-mac-leopard': 'leopard',
413            'test-mac-snowleopard': 'snowleopard',
414            'test-linux-x86_64': 'lucid',
415        }
416        self._version = version_map[self._name]
417
418    def repository_paths(self):
419        """Returns a list of (repository_name, repository_path) tuples of its depending code base."""
420        # FIXME: We override this just to keep the perf tests happy.
421        return [('blink', self.layout_tests_dir())]
422
423    def buildbot_archives_baselines(self):
424        return self._name != 'test-win-xp'
425
426    def default_pixel_tests(self):
427        return True
428
429    def _path_to_driver(self):
430        # This routine shouldn't normally be called, but it is called by
431        # the mock_drt Driver. We return something, but make sure it's useless.
432        return 'MOCK _path_to_driver'
433
434    def default_child_processes(self):
435        return 1
436
437    def check_build(self, needs_http, printer):
438        return test_run_results.OK_EXIT_STATUS
439
440    def check_sys_deps(self, needs_http):
441        return test_run_results.OK_EXIT_STATUS
442
443    def default_configuration(self):
444        return 'Release'
445
446    def diff_image(self, expected_contents, actual_contents):
447        diffed = actual_contents != expected_contents
448        if not actual_contents and not expected_contents:
449            return (None, None)
450        if not actual_contents or not expected_contents:
451            return (True, None)
452        if diffed:
453            return ("< %s\n---\n> %s\n" % (expected_contents, actual_contents), None)
454        return (None, None)
455
456    def layout_tests_dir(self):
457        return LAYOUT_TEST_DIR
458
459    def perf_tests_dir(self):
460        return PERF_TEST_DIR
461
462    def webkit_base(self):
463        return '/test.checkout'
464
465    def _skipped_tests_for_unsupported_features(self, test_list):
466        return set(['failures/expected/skip_text.html',
467                    'failures/unexpected/skip_pass.html',
468                    'virtual/skipped/failures/expected'])
469
470    def name(self):
471        return self._name
472
473    def operating_system(self):
474        return self._operating_system
475
476    def _path_to_wdiff(self):
477        return None
478
479    def default_results_directory(self):
480        return '/tmp/layout-test-results'
481
482    def setup_test_run(self):
483        pass
484
485    def _driver_class(self):
486        return TestDriver
487
488    def start_http_server(self, additional_dirs, number_of_drivers):
489        pass
490
491    def start_websocket_server(self):
492        pass
493
494    def acquire_http_lock(self):
495        pass
496
497    def stop_http_server(self):
498        pass
499
500    def stop_websocket_server(self):
501        pass
502
503    def release_http_lock(self):
504        pass
505
506    def path_to_apache(self):
507        return "/usr/sbin/httpd"
508
509    def path_to_apache_config_file(self):
510        return self._filesystem.join(self.layout_tests_dir(), 'http', 'conf', 'httpd.conf')
511
512    def path_to_generic_test_expectations_file(self):
513        return self._generic_expectations_path
514
515    def _port_specific_expectations_files(self):
516        return [self._filesystem.join(self._webkit_baseline_path(d), 'TestExpectations') for d in ['test', 'test-win-xp']]
517
518    def all_test_configurations(self):
519        """Returns a sequence of the TestConfigurations the port supports."""
520        # By default, we assume we want to test every graphics type in
521        # every configuration on every system.
522        test_configurations = []
523        for version, architecture in self._all_systems():
524            for build_type in self._all_build_types():
525                test_configurations.append(TestConfiguration(
526                    version=version,
527                    architecture=architecture,
528                    build_type=build_type))
529        return test_configurations
530
531    def _all_systems(self):
532        return (('leopard', 'x86'),
533                ('snowleopard', 'x86'),
534                ('xp', 'x86'),
535                ('win7', 'x86'),
536                ('lucid', 'x86'),
537                ('lucid', 'x86_64'))
538
539    def _all_build_types(self):
540        return ('debug', 'release')
541
542    def configuration_specifier_macros(self):
543        """To avoid surprises when introducing new macros, these are intentionally fixed in time."""
544        return {'mac': ['leopard', 'snowleopard'], 'win': ['xp', 'win7'], 'linux': ['lucid']}
545
546    def all_baseline_variants(self):
547        return self.ALL_BASELINE_VARIANTS
548
549    def virtual_test_suites(self):
550        return [
551            VirtualTestSuite(prefix='virtual_passes', base='passes', args=['--virtual-arg']),
552            VirtualTestSuite(prefix='skipped', base='failures/expected', args=['--virtual-arg2']),
553        ]
554
555
556class TestDriver(Driver):
557    """Test/Dummy implementation of the driver interface."""
558    next_pid = 1
559
560    def __init__(self, *args, **kwargs):
561        super(TestDriver, self).__init__(*args, **kwargs)
562        self.started = False
563        self.pid = 0
564
565    def cmd_line(self, pixel_tests, per_test_args):
566        pixel_tests_flag = '-p' if pixel_tests else ''
567        return [self._port._path_to_driver()] + [pixel_tests_flag] + self._port.get_option('additional_drt_flag', []) + per_test_args
568
569    def run_test(self, driver_input, stop_when_done):
570        if not self.started:
571            self.started = True
572            self.pid = TestDriver.next_pid
573            TestDriver.next_pid += 1
574
575        start_time = time.time()
576        test_name = driver_input.test_name
577        test_args = driver_input.args or []
578        test = self._port._tests[test_name]
579        if test.keyboard:
580            raise KeyboardInterrupt
581        if test.exception:
582            raise ValueError('exception from ' + test_name)
583        if test.device_failure:
584            raise DeviceFailure('device failure in ' + test_name)
585
586        audio = None
587        actual_text = test.actual_text
588        crash = test.crash
589        web_process_crash = test.web_process_crash
590
591        if 'flaky/text.html' in test_name and not test_name in self._port._flakes:
592            self._port._flakes.add(test_name)
593            actual_text = 'flaky text failure'
594
595        if 'crash_then_text.html' in test_name:
596            if test_name in self._port._flakes:
597                actual_text = 'text failure'
598            else:
599                self._port._flakes.add(test_name)
600                crashed_process_name = self._port.driver_name()
601                crashed_pid = 1
602                crash = True
603
604        if 'text_then_crash.html' in test_name:
605            if test_name in self._port._flakes:
606                crashed_process_name = self._port.driver_name()
607                crashed_pid = 1
608                crash = True
609            else:
610                self._port._flakes.add(test_name)
611                actual_text = 'text failure'
612
613        if actual_text and test_args and test_name == 'passes/args.html':
614            actual_text = actual_text + ' ' + ' '.join(test_args)
615
616        if test.actual_audio:
617            audio = base64.b64decode(test.actual_audio)
618        crashed_process_name = None
619        crashed_pid = None
620        if crash:
621            crashed_process_name = self._port.driver_name()
622            crashed_pid = 1
623        elif web_process_crash:
624            crashed_process_name = 'WebProcess'
625            crashed_pid = 2
626
627        crash_log = ''
628        if crashed_process_name:
629            crash_logs = CrashLogs(self._port.host)
630            crash_log = crash_logs.find_newest_log(crashed_process_name, None) or ''
631
632        if stop_when_done:
633            self.stop()
634
635        if test.actual_checksum == driver_input.image_hash:
636            image = None
637        else:
638            image = test.actual_image
639        return DriverOutput(actual_text, image, test.actual_checksum, audio,
640            crash=(crash or web_process_crash), crashed_process_name=crashed_process_name,
641            crashed_pid=crashed_pid, crash_log=crash_log,
642            test_time=time.time() - start_time, timeout=test.timeout, error=test.error, pid=self.pid,
643            leak=test.leak)
644
645    def stop(self):
646        self.started = False
647