Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Tools / Scripts / webkitpy / layout_tests / run_webkit_tests_unittest.py
1 # Copyright (C) 2010 Google Inc. All rights reserved.
2 # Copyright (C) 2010 Gabor Rapcsanyi (rgabor@inf.u-szeged.hu), University of Szeged
3 # Copyright (C) 2011 Apple Inc. All rights reserved.
4 #
5 # Redistribution and use in source and binary forms, with or without
6 # modification, are permitted provided that the following conditions are
7 # met:
8 #
9 #     * Redistributions of source code must retain the above copyright
10 # notice, this list of conditions and the following disclaimer.
11 #     * Redistributions in binary form must reproduce the above
12 # copyright notice, this list of conditions and the following disclaimer
13 # in the documentation and/or other materials provided with the
14 # distribution.
15 #     * Neither the name of Google Inc. nor the names of its
16 # contributors may be used to endorse or promote products derived from
17 # this software without specific prior written permission.
18 #
19 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
31 import Queue
32 import StringIO
33 import codecs
34 import json
35 import logging
36 import os
37 import platform
38 import re
39 import sys
40 import thread
41 import time
42 import threading
43 import unittest
44
45 from webkitpy.common.system import outputcapture, path
46 from webkitpy.common.system.crashlogs_unittest import make_mock_crash_report_darwin
47 from webkitpy.common.system.systemhost import SystemHost
48 from webkitpy.common.host import Host
49 from webkitpy.common.host_mock import MockHost
50
51 from webkitpy.layout_tests import port
52 from webkitpy.layout_tests import run_webkit_tests
53 from webkitpy.layout_tests.models import test_run_results
54 from webkitpy.layout_tests.port import Port
55 from webkitpy.layout_tests.port import test
56 from webkitpy.test.skip import skip_if
57 from webkitpy.tool import grammar
58 from webkitpy.tool.mocktool import MockOptions
59
60
61 def parse_args(extra_args=None, tests_included=False, new_results=False, print_nothing=True):
62     extra_args = extra_args or []
63     args = []
64     if not '--platform' in extra_args:
65         args.extend(['--platform', 'test'])
66     if not new_results:
67         args.append('--no-new-test-results')
68
69     if not '--child-processes' in extra_args:
70         args.extend(['--child-processes', 1])
71     args.extend(extra_args)
72     if not tests_included:
73         # We use the glob to test that globbing works.
74         args.extend(['passes',
75                      'http/tests',
76                      'websocket/tests',
77                      'failures/expected/*'])
78     return run_webkit_tests.parse_args(args)
79
80
81 def passing_run(extra_args=None, port_obj=None, tests_included=False, host=None, shared_port=True):
82     options, parsed_args = parse_args(extra_args, tests_included)
83     if not port_obj:
84         host = host or MockHost()
85         port_obj = host.port_factory.get(port_name=options.platform, options=options)
86
87     if shared_port:
88         port_obj.host.port_factory.get = lambda *args, **kwargs: port_obj
89
90     logging_stream = StringIO.StringIO()
91     run_details = run_webkit_tests.run(port_obj, options, parsed_args, logging_stream=logging_stream)
92     return run_details.exit_code == 0
93
94
95 def logging_run(extra_args=None, port_obj=None, tests_included=False, host=None, new_results=False, shared_port=True):
96     options, parsed_args = parse_args(extra_args=extra_args,
97                                       tests_included=tests_included,
98                                       print_nothing=False, new_results=new_results)
99     host = host or MockHost()
100     if not port_obj:
101         port_obj = host.port_factory.get(port_name=options.platform, options=options)
102
103     run_details, output = run_and_capture(port_obj, options, parsed_args, shared_port)
104     return (run_details, output, host.user)
105
106
107 def run_and_capture(port_obj, options, parsed_args, shared_port=True):
108     if shared_port:
109         port_obj.host.port_factory.get = lambda *args, **kwargs: port_obj
110     oc = outputcapture.OutputCapture()
111     try:
112         oc.capture_output()
113         logging_stream = StringIO.StringIO()
114         run_details = run_webkit_tests.run(port_obj, options, parsed_args, logging_stream=logging_stream)
115     finally:
116         oc.restore_output()
117     return (run_details, logging_stream)
118
119
120 def get_tests_run(args, host=None, port_obj=None):
121     results = get_test_results(args, host=host, port_obj=port_obj)
122     return [result.test_name for result in results]
123
124
125 def get_test_batches(args, host=None):
126     results = get_test_results(args, host)
127     batches = []
128     batch = []
129     current_pid = None
130     for result in results:
131         if batch and result.pid != current_pid:
132             batches.append(batch)
133             batch = []
134         batch.append(result.test_name)
135     if batch:
136         batches.append(batch)
137     return batches
138
139
140 def get_test_results(args, host=None, port_obj=None):
141     options, parsed_args = parse_args(args, tests_included=True)
142
143     host = host or MockHost()
144     port_obj = port_obj or host.port_factory.get(port_name=options.platform, options=options)
145
146     oc = outputcapture.OutputCapture()
147     oc.capture_output()
148     logging_stream = StringIO.StringIO()
149     try:
150         run_details = run_webkit_tests.run(port_obj, options, parsed_args, logging_stream=logging_stream)
151     finally:
152         oc.restore_output()
153
154     all_results = []
155     if run_details.initial_results:
156         all_results.extend(run_details.initial_results.all_results)
157
158     if run_details.retry_results:
159         all_results.extend(run_details.retry_results.all_results)
160     return all_results
161
162
163 def parse_full_results(full_results_text):
164     json_to_eval = full_results_text.replace("ADD_RESULTS(", "").replace(");", "")
165     compressed_results = json.loads(json_to_eval)
166     return compressed_results
167
168
169 class StreamTestingMixin(object):
170     def assertContains(self, stream, string):
171         self.assertTrue(string in stream.getvalue())
172
173     def assertEmpty(self, stream):
174         self.assertFalse(stream.getvalue())
175
176     def assertNotEmpty(self, stream):
177         self.assertTrue(stream.getvalue())
178
179
180 class RunTest(unittest.TestCase, StreamTestingMixin):
181     def setUp(self):
182         # A real PlatformInfo object is used here instead of a
183         # MockPlatformInfo because we need to actually check for
184         # Windows and Mac to skip some tests.
185         self._platform = SystemHost().platform
186
187         # FIXME: Remove this when we fix test-webkitpy to work
188         # properly on cygwin (bug 63846).
189         self.should_test_processes = not self._platform.is_win()
190
191     def test_basic(self):
192         options, args = parse_args(tests_included=True)
193         logging_stream = StringIO.StringIO()
194         host = MockHost()
195         port_obj = host.port_factory.get(options.platform, options)
196         details = run_webkit_tests.run(port_obj, options, args, logging_stream)
197
198         # These numbers will need to be updated whenever we add new tests.
199         self.assertEqual(details.initial_results.total, test.TOTAL_TESTS)
200         self.assertEqual(details.initial_results.expected_skips, test.TOTAL_SKIPS)
201         self.assertEqual(len(details.initial_results.unexpected_results_by_name), test.UNEXPECTED_PASSES + test.UNEXPECTED_FAILURES)
202         self.assertEqual(details.exit_code, test.UNEXPECTED_FAILURES)
203         self.assertEqual(details.retry_results.total, test.UNEXPECTED_FAILURES)
204
205         expected_tests = details.initial_results.total - details.initial_results.expected_skips - len(details.initial_results.unexpected_results_by_name)
206         expected_summary_str = ''
207         if details.initial_results.expected_failures > 0:
208             expected_summary_str = " (%d passed, %d didn't)" % (expected_tests - details.initial_results.expected_failures, details.initial_results.expected_failures)
209         one_line_summary = "%d tests ran as expected%s, %d didn't:\n" % (
210             expected_tests,
211             expected_summary_str,
212             len(details.initial_results.unexpected_results_by_name))
213         self.assertTrue(one_line_summary in logging_stream.buflist)
214
215         # Ensure the results were summarized properly.
216         self.assertEqual(details.summarized_failing_results['num_regressions'], details.exit_code)
217
218         # Ensure the results were written out and displayed.
219         failing_results_text = host.filesystem.read_text_file('/tmp/layout-test-results/failing_results.json')
220         json_to_eval = failing_results_text.replace("ADD_RESULTS(", "").replace(");", "")
221         self.assertEqual(json.loads(json_to_eval), details.summarized_failing_results)
222
223         full_results_text = host.filesystem.read_text_file('/tmp/layout-test-results/full_results.json')
224         self.assertEqual(json.loads(full_results_text), details.summarized_full_results)
225
226         self.assertEqual(host.user.opened_urls, [path.abspath_to_uri(MockHost().platform, '/tmp/layout-test-results/results.html')])
227
228     def test_batch_size(self):
229         batch_tests_run = get_test_batches(['--batch-size', '2'])
230         for batch in batch_tests_run:
231             self.assertTrue(len(batch) <= 2, '%s had too many tests' % ', '.join(batch))
232
233     def test_max_locked_shards(self):
234         # Tests for the default of using one locked shard even in the case of more than one child process.
235         if not self.should_test_processes:
236             return
237         save_env_webkit_test_max_locked_shards = None
238         if "WEBKIT_TEST_MAX_LOCKED_SHARDS" in os.environ:
239             save_env_webkit_test_max_locked_shards = os.environ["WEBKIT_TEST_MAX_LOCKED_SHARDS"]
240             del os.environ["WEBKIT_TEST_MAX_LOCKED_SHARDS"]
241         _, regular_output, _ = logging_run(['--debug-rwt-logging', '--child-processes', '2'], shared_port=False)
242         try:
243             self.assertTrue(any(['1 locked' in line for line in regular_output.buflist]))
244         finally:
245             if save_env_webkit_test_max_locked_shards:
246                 os.environ["WEBKIT_TEST_MAX_LOCKED_SHARDS"] = save_env_webkit_test_max_locked_shards
247
248     def test_child_processes_2(self):
249         if self.should_test_processes:
250             _, regular_output, _ = logging_run(
251                 ['--debug-rwt-logging', '--child-processes', '2'], shared_port=False)
252             self.assertTrue(any(['Running 2 ' in line for line in regular_output.buflist]))
253
254     def test_child_processes_min(self):
255         if self.should_test_processes:
256             _, regular_output, _ = logging_run(
257                 ['--debug-rwt-logging', '--child-processes', '2', '-i', 'passes/virtual_passes', 'passes'],
258                 tests_included=True, shared_port=False)
259             self.assertTrue(any(['Running 1 ' in line for line in regular_output.buflist]))
260
261     def test_dryrun(self):
262         tests_run = get_tests_run(['--dry-run'])
263         self.assertEqual(tests_run, [])
264
265         tests_run = get_tests_run(['-n'])
266         self.assertEqual(tests_run, [])
267
268     def test_enable_sanitizer(self):
269         self.assertTrue(passing_run(['--enable-sanitizer', 'failures/expected/text.html']))
270
271     def test_exception_raised(self):
272         # Exceptions raised by a worker are treated differently depending on
273         # whether they are in-process or out. inline exceptions work as normal,
274         # which allows us to get the full stack trace and traceback from the
275         # worker. The downside to this is that it could be any error, but this
276         # is actually useful in testing.
277         #
278         # Exceptions raised in a separate process are re-packaged into
279         # WorkerExceptions (a subclass of BaseException), which have a string capture of the stack which can
280         # be printed, but don't display properly in the unit test exception handlers.
281         self.assertRaises(BaseException, logging_run,
282             ['failures/expected/exception.html', '--child-processes', '1'], tests_included=True)
283
284         if self.should_test_processes:
285             self.assertRaises(BaseException, logging_run,
286                 ['--child-processes', '2', '--skipped=ignore', 'failures/expected/exception.html', 'passes/text.html'], tests_included=True, shared_port=False)
287
288     def test_device_failure(self):
289         # Test that we handle a device going offline during a test properly.
290         details, regular_output, _ = logging_run(['failures/expected/device_failure.html'], tests_included=True)
291         self.assertEqual(details.exit_code, 0)
292         self.assertTrue('worker/0 has failed' in regular_output.getvalue())
293
294     def test_full_results_html(self):
295         host = MockHost()
296         details, _, _ = logging_run(['--full-results-html'], host=host)
297         self.assertEqual(details.exit_code, 0)
298         self.assertEqual(len(host.user.opened_urls), 1)
299
300     def test_keyboard_interrupt(self):
301         # Note that this also tests running a test marked as SKIP if
302         # you specify it explicitly.
303         details, _, _ = logging_run(['failures/expected/keyboard.html', '--child-processes', '1'], tests_included=True)
304         self.assertEqual(details.exit_code, test_run_results.INTERRUPTED_EXIT_STATUS)
305
306         if self.should_test_processes:
307             _, regular_output, _ = logging_run(['failures/expected/keyboard.html', 'passes/text.html', '--child-processes', '2', '--skipped=ignore'], tests_included=True, shared_port=False)
308             self.assertTrue(any(['Interrupted, exiting' in line for line in regular_output.buflist]))
309
310     def test_no_tests_found(self):
311         details, err, _ = logging_run(['resources'], tests_included=True)
312         self.assertEqual(details.exit_code, test_run_results.NO_TESTS_EXIT_STATUS)
313         self.assertContains(err, 'No tests to run.\n')
314
315     def test_no_tests_found_2(self):
316         details, err, _ = logging_run(['foo'], tests_included=True)
317         self.assertEqual(details.exit_code, test_run_results.NO_TESTS_EXIT_STATUS)
318         self.assertContains(err, 'No tests to run.\n')
319
320     def test_no_tests_found_3(self):
321         details, err, _ = logging_run(['--run-chunk', '5:400', 'foo/bar.html'], tests_included=True)
322         self.assertEqual(details.exit_code, test_run_results.NO_TESTS_EXIT_STATUS)
323         self.assertContains(err, 'No tests to run.\n')
324
325     def test_natural_order(self):
326         tests_to_run = ['passes/audio.html', 'failures/expected/text.html', 'failures/expected/missing_text.html', 'passes/args.html']
327         tests_run = get_tests_run(['--order=natural'] + tests_to_run)
328         self.assertEqual(['failures/expected/missing_text.html', 'failures/expected/text.html', 'passes/args.html', 'passes/audio.html'], tests_run)
329
330     def test_natural_order_test_specified_multiple_times(self):
331         tests_to_run = ['passes/args.html', 'passes/audio.html', 'passes/audio.html', 'passes/args.html']
332         tests_run = get_tests_run(['--order=natural'] + tests_to_run)
333         self.assertEqual(['passes/args.html', 'passes/args.html', 'passes/audio.html', 'passes/audio.html'], tests_run)
334
335     def test_random_order(self):
336         tests_to_run = ['passes/audio.html', 'failures/expected/text.html', 'failures/expected/missing_text.html', 'passes/args.html']
337         tests_run = get_tests_run(['--order=random'] + tests_to_run)
338         self.assertEqual(sorted(tests_to_run), sorted(tests_run))
339
340     def test_random_daily_seed_order(self):
341         tests_to_run = ['passes/audio.html', 'failures/expected/text.html', 'failures/expected/missing_text.html', 'passes/args.html']
342         tests_run = get_tests_run(['--order=random-seeded'] + tests_to_run)
343         self.assertEqual(sorted(tests_to_run), sorted(tests_run))
344
345     def test_random_order_test_specified_multiple_times(self):
346         tests_to_run = ['passes/args.html', 'passes/audio.html', 'passes/audio.html', 'passes/args.html']
347         tests_run = get_tests_run(['--order=random'] + tests_to_run)
348         self.assertEqual(tests_run.count('passes/audio.html'), 2)
349         self.assertEqual(tests_run.count('passes/args.html'), 2)
350
351     def test_no_order(self):
352         tests_to_run = ['passes/audio.html', 'failures/expected/text.html', 'failures/expected/missing_text.html', 'passes/args.html']
353         tests_run = get_tests_run(['--order=none'] + tests_to_run)
354         self.assertEqual(tests_to_run, tests_run)
355
356     def test_no_order_test_specified_multiple_times(self):
357         tests_to_run = ['passes/args.html', 'passes/audio.html', 'passes/audio.html', 'passes/args.html']
358         tests_run = get_tests_run(['--order=none'] + tests_to_run)
359         self.assertEqual(tests_to_run, tests_run)
360
361     def test_no_order_with_directory_entries_in_natural_order(self):
362         tests_to_run = ['http/tests/ssl', 'perf/foo', 'http/tests/passes']
363         tests_run = get_tests_run(['--order=none'] + tests_to_run)
364         self.assertEqual(tests_run, ['http/tests/ssl/text.html', 'perf/foo/test.html', 'http/tests/passes/image.html', 'http/tests/passes/text.html'])
365
366     def test_repeat_each(self):
367         tests_to_run = ['passes/image.html', 'passes/text.html']
368         tests_run = get_tests_run(['--repeat-each', '2'] + tests_to_run)
369         self.assertEqual(tests_run, ['passes/image.html', 'passes/image.html', 'passes/text.html', 'passes/text.html'])
370
371     def test_ignore_flag(self):
372         # Note that passes/image.html is expected to be run since we specified it directly.
373         tests_run = get_tests_run(['-i', 'passes', 'passes/image.html'])
374         self.assertFalse('passes/text.html' in tests_run)
375         self.assertTrue('passes/image.html' in tests_run)
376
377     def test_skipped_flag(self):
378         tests_run = get_tests_run(['passes'])
379         self.assertFalse('passes/skipped/skip.html' in tests_run)
380         num_tests_run_by_default = len(tests_run)
381
382         # Check that nothing changes when we specify skipped=default.
383         self.assertEqual(len(get_tests_run(['--skipped=default', 'passes'])),
384                           num_tests_run_by_default)
385
386         # Now check that we run one more test (the skipped one).
387         tests_run = get_tests_run(['--skipped=ignore', 'passes'])
388         self.assertTrue('passes/skipped/skip.html' in tests_run)
389         self.assertEqual(len(tests_run), num_tests_run_by_default + 1)
390
391         # Now check that we only run the skipped test.
392         self.assertEqual(get_tests_run(['--skipped=only', 'passes']), ['passes/skipped/skip.html'])
393
394         # Now check that we don't run anything.
395         self.assertEqual(get_tests_run(['--skipped=always', 'passes/skipped/skip.html']), [])
396
397     def test_iterations(self):
398         tests_to_run = ['passes/image.html', 'passes/text.html']
399         tests_run = get_tests_run(['--iterations', '2'] + tests_to_run)
400         self.assertEqual(tests_run, ['passes/image.html', 'passes/text.html', 'passes/image.html', 'passes/text.html'])
401
402     def test_repeat_each_iterations_num_tests(self):
403         # The total number of tests should be: number_of_tests *
404         # repeat_each * iterations
405         host = MockHost()
406         _, err, _ = logging_run(
407             ['--iterations', '2', '--repeat-each', '4', '--debug-rwt-logging', 'passes/text.html', 'failures/expected/text.html'],
408             tests_included=True, host=host)
409         self.assertContains(err, "All 16 tests ran as expected (8 passed, 8 didn't).\n")
410
411     def test_run_chunk(self):
412         # Test that we actually select the right chunk
413         all_tests_run = get_tests_run(['passes', 'failures'])
414         chunk_tests_run = get_tests_run(['--run-chunk', '1:4', 'passes', 'failures'])
415         self.assertEqual(all_tests_run[4:8], chunk_tests_run)
416
417         # Test that we wrap around if the number of tests is not evenly divisible by the chunk size
418         tests_to_run = ['passes/error.html', 'passes/image.html', 'passes/platform_image.html', 'passes/text.html']
419         chunk_tests_run = get_tests_run(['--run-chunk', '1:3'] + tests_to_run)
420         self.assertEqual(['passes/text.html', 'passes/error.html', 'passes/image.html'], chunk_tests_run)
421
422     def test_run_part(self):
423         # Test that we actually select the right part
424         tests_to_run = ['passes/error.html', 'passes/image.html', 'passes/platform_image.html', 'passes/text.html']
425         tests_run = get_tests_run(['--run-part', '1:2'] + tests_to_run)
426         self.assertEqual(['passes/error.html', 'passes/image.html'], tests_run)
427
428         # Test that we wrap around if the number of tests is not evenly divisible by the chunk size
429         # (here we end up with 3 parts, each with 2 tests, and we only have 4 tests total, so the
430         # last part repeats the first two tests).
431         chunk_tests_run = get_tests_run(['--run-part', '3:3'] + tests_to_run)
432         self.assertEqual(['passes/error.html', 'passes/image.html'], chunk_tests_run)
433
434     def test_run_singly(self):
435         batch_tests_run = get_test_batches(['--run-singly'])
436         for batch in batch_tests_run:
437             self.assertEqual(len(batch), 1, '%s had too many tests' % ', '.join(batch))
438
439     def test_skip_failing_tests(self):
440         # This tests that we skip both known failing and known flaky tests. Because there are
441         # no known flaky tests in the default test_expectations, we add additional expectations.
442         host = MockHost()
443         host.filesystem.write_text_file('/tmp/overrides.txt', 'Bug(x) passes/image.html [ ImageOnlyFailure Pass ]\n')
444
445         batches = get_test_batches(['--skip-failing-tests', '--additional-expectations', '/tmp/overrides.txt'], host=host)
446         has_passes_text = False
447         for batch in batches:
448             self.assertFalse('failures/expected/text.html' in batch)
449             self.assertFalse('passes/image.html' in batch)
450             has_passes_text = has_passes_text or ('passes/text.html' in batch)
451         self.assertTrue(has_passes_text)
452
453     def test_single_file(self):
454         tests_run = get_tests_run(['passes/text.html'])
455         self.assertEqual(tests_run, ['passes/text.html'])
456
457     def test_single_file_with_prefix(self):
458         tests_run = get_tests_run(['LayoutTests/passes/text.html'])
459         self.assertEqual(['passes/text.html'], tests_run)
460
461     def test_single_skipped_file(self):
462         tests_run = get_tests_run(['failures/expected/keybaord.html'])
463         self.assertEqual([], tests_run)
464
465     def test_stderr_is_saved(self):
466         host = MockHost()
467         self.assertTrue(passing_run(host=host))
468         self.assertEqual(host.filesystem.read_text_file('/tmp/layout-test-results/passes/error-stderr.txt'),
469                           'stuff going to stderr')
470
471     def test_test_list(self):
472         host = MockHost()
473         filename = '/tmp/foo.txt'
474         host.filesystem.write_text_file(filename, 'passes/text.html')
475         tests_run = get_tests_run(['--test-list=%s' % filename], host=host)
476         self.assertEqual(['passes/text.html'], tests_run)
477         host.filesystem.remove(filename)
478         details, err, user = logging_run(['--test-list=%s' % filename], tests_included=True, host=host)
479         self.assertEqual(details.exit_code, test_run_results.NO_TESTS_EXIT_STATUS)
480         self.assertNotEmpty(err)
481
482     def test_test_list_with_prefix(self):
483         host = MockHost()
484         filename = '/tmp/foo.txt'
485         host.filesystem.write_text_file(filename, 'LayoutTests/passes/text.html')
486         tests_run = get_tests_run(['--test-list=%s' % filename], host=host)
487         self.assertEqual(['passes/text.html'], tests_run)
488
489     def test_smoke_test(self):
490         host = MockHost()
491         smoke_test_filename = test.LAYOUT_TEST_DIR + '/SmokeTests'
492         host.filesystem.write_text_file(smoke_test_filename, 'passes/text.html\n')
493
494         # Test the default smoke testing.
495         tests_run = get_tests_run(['--smoke'], host=host)
496         self.assertEqual(['passes/text.html'], tests_run)
497
498         # Test running the smoke tests plus some manually-specified tests.
499         tests_run = get_tests_run(['--smoke', 'passes/image.html'], host=host)
500         self.assertEqual(['passes/image.html', 'passes/text.html'], tests_run)
501
502         # Test running the smoke tests plus some manually-specified tests.
503         tests_run = get_tests_run(['--no-smoke', 'passes/image.html'], host=host)
504         self.assertEqual(['passes/image.html'], tests_run)
505
506         # Test that we don't run just the smoke tests by default on a normal test port.
507         tests_run = get_tests_run([], host=host)
508         self.assertNotEqual(['passes/text.html'], tests_run)
509
510         # Create a port that does run only the smoke tests by default, and verify that works as expected.
511         port_obj = host.port_factory.get('test')
512         port_obj.default_smoke_test_only = lambda: True
513         tests_run = get_tests_run([], host=host, port_obj=port_obj)
514         self.assertEqual(['passes/text.html'], tests_run)
515
516         # Verify that --no-smoke continues to work on a smoke-by-default port.
517         tests_run = get_tests_run(['--no-smoke'], host=host, port_obj=port_obj)
518         self.assertNotEqual(['passes/text.html'], tests_run)
519
520     def test_missing_and_unexpected_results(self):
521         # Test that we update expectations in place. If the expectation
522         # is missing, update the expected generic location.
523         host = MockHost()
524         details, err, _ = logging_run(['--no-show-results', '--retry-failures',
525             'failures/expected/missing_image.html',
526             'failures/unexpected/missing_text.html',
527             'failures/unexpected/text-image-checksum.html'],
528             tests_included=True, host=host)
529         file_list = host.filesystem.written_files.keys()
530         self.assertEqual(details.exit_code, 2)
531         json_string = host.filesystem.read_text_file('/tmp/layout-test-results/full_results.json')
532         self.assertTrue(json_string.find('"text-image-checksum.html":{"expected":"PASS","actual":"IMAGE+TEXT","is_unexpected":true') != -1)
533         self.assertTrue(json_string.find('"missing_text.html":{"expected":"PASS","is_missing_text":true,"actual":"MISSING","is_unexpected":true') != -1)
534         self.assertTrue(json_string.find('"num_regressions":2') != -1)
535         self.assertTrue(json_string.find('"num_flaky":0') != -1)
536
537     def test_different_failure_on_retry(self):
538         # This tests that if a test fails two different ways -- both unexpected
539         # -- we treat it as a failure rather than a flaky result.  We use the
540         # initial failure for simplicity and consistency w/ the flakiness
541         # dashboard, even if the second failure is worse.
542
543         details, err, _ = logging_run(['--retry-failures', 'failures/unexpected/text_then_crash.html'], tests_included=True)
544         self.assertEqual(details.exit_code, 1)
545         self.assertEqual(details.summarized_failing_results['tests']['failures']['unexpected']['text_then_crash.html']['actual'],
546                          'TEXT CRASH')
547
548         # If we get a test that fails two different ways -- but the second one is expected --
549         # we should treat it as a flaky result and report the initial unexpected failure type
550         # to the dashboard. However, the test should be considered passing.
551         details, err, _ = logging_run(['--retry-failures', 'failures/expected/crash_then_text.html'], tests_included=True)
552         self.assertEqual(details.exit_code, 0)
553         self.assertEqual(details.summarized_failing_results['tests']['failures']['expected']['crash_then_text.html']['actual'],
554                          'CRASH FAIL')
555
556     def test_pixel_test_directories(self):
557         host = MockHost()
558
559         """Both tests have failing checksum. We include only the first in pixel tests so only that should fail."""
560         args = ['--pixel-tests', '--retry-failures', '--pixel-test-directory', 'failures/unexpected/pixeldir',
561                 'failures/unexpected/pixeldir/image_in_pixeldir.html',
562                 'failures/unexpected/image_not_in_pixeldir.html']
563         details, err, _ = logging_run(extra_args=args, host=host, tests_included=True)
564
565         self.assertEqual(details.exit_code, 1)
566         expected_token = '"pixeldir":{"image_in_pixeldir.html":{"expected":"PASS","actual":"IMAGE","is_unexpected":true'
567         json_string = host.filesystem.read_text_file('/tmp/layout-test-results/full_results.json')
568         self.assertTrue(json_string.find(expected_token) != -1)
569
570     def test_crash_with_stderr(self):
571         host = MockHost()
572         _, regular_output, _ = logging_run(['failures/unexpected/crash-with-stderr.html'], tests_included=True, host=host)
573         self.assertTrue(host.filesystem.read_text_file('/tmp/layout-test-results/full_results.json').find('{"crash-with-stderr.html":{"expected":"PASS","actual":"CRASH","has_stderr":true,"is_unexpected":true') != -1)
574
575     def test_no_image_failure_with_image_diff(self):
576         host = MockHost()
577         _, regular_output, _ = logging_run(['failures/unexpected/checksum-with-matching-image.html'], tests_included=True, host=host)
578         self.assertTrue(host.filesystem.read_text_file('/tmp/layout-test-results/full_results.json').find('"num_regressions":0') != -1)
579
580     def test_exit_after_n_failures_upload(self):
581         host = MockHost()
582         details, regular_output, user = logging_run(
583            ['failures/unexpected/text-image-checksum.html', 'passes/text.html', '--exit-after-n-failures', '1'],
584            tests_included=True, host=host)
585
586         # By returning False, we know that the incremental results were generated and then deleted.
587         self.assertFalse(host.filesystem.exists('/tmp/layout-test-results/incremental_results.json'))
588
589         self.assertEqual(details.exit_code, test_run_results.EARLY_EXIT_STATUS)
590
591         # This checks that passes/text.html is considered SKIPped.
592         self.assertTrue('"skipped":1' in host.filesystem.read_text_file('/tmp/layout-test-results/full_results.json'))
593
594         # This checks that we told the user we bailed out.
595         self.assertTrue('Exiting early after 1 failures. 1 tests run.\n' in regular_output.getvalue())
596
597         # This checks that neither test ran as expected.
598         # FIXME: This log message is confusing; tests that were skipped should be called out separately.
599         self.assertTrue('0 tests ran as expected, 2 didn\'t:\n' in regular_output.getvalue())
600
601     def test_exit_after_n_failures(self):
602         # Unexpected failures should result in tests stopping.
603         tests_run = get_tests_run(['failures/unexpected/text-image-checksum.html', 'passes/text.html', '--exit-after-n-failures', '1'])
604         self.assertEqual(['failures/unexpected/text-image-checksum.html'], tests_run)
605
606         # But we'll keep going for expected ones.
607         tests_run = get_tests_run(['failures/expected/text.html', 'passes/text.html', '--exit-after-n-failures', '1'])
608         self.assertEqual(['failures/expected/text.html', 'passes/text.html'], tests_run)
609
610     def test_exit_after_n_crashes(self):
611         # Unexpected crashes should result in tests stopping.
612         tests_run = get_tests_run(['failures/unexpected/crash.html', 'passes/text.html', '--exit-after-n-crashes-or-timeouts', '1'])
613         self.assertEqual(['failures/unexpected/crash.html'], tests_run)
614
615         # Same with timeouts.
616         tests_run = get_tests_run(['failures/unexpected/timeout.html', 'passes/text.html', '--exit-after-n-crashes-or-timeouts', '1'])
617         self.assertEqual(['failures/unexpected/timeout.html'], tests_run)
618
619         # But we'll keep going for expected ones.
620         tests_run = get_tests_run(['failures/expected/crash.html', 'passes/text.html', '--exit-after-n-crashes-or-timeouts', '1'])
621         self.assertEqual(['failures/expected/crash.html', 'passes/text.html'], tests_run)
622
623     def test_results_directory_absolute(self):
624         # We run a configuration that should fail, to generate output, then
625         # look for what the output results url was.
626
627         host = MockHost()
628         with host.filesystem.mkdtemp() as tmpdir:
629             _, _, user = logging_run(['--results-directory=' + str(tmpdir)], tests_included=True, host=host)
630             self.assertEqual(user.opened_urls, [path.abspath_to_uri(host.platform, host.filesystem.join(tmpdir, 'results.html'))])
631
632     def test_results_directory_default(self):
633         # We run a configuration that should fail, to generate output, then
634         # look for what the output results url was.
635
636         # This is the default location.
637         _, _, user = logging_run(tests_included=True)
638         self.assertEqual(user.opened_urls, [path.abspath_to_uri(MockHost().platform, '/tmp/layout-test-results/results.html')])
639
640     def test_results_directory_relative(self):
641         # We run a configuration that should fail, to generate output, then
642         # look for what the output results url was.
643         host = MockHost()
644         host.filesystem.maybe_make_directory('/tmp/cwd')
645         host.filesystem.chdir('/tmp/cwd')
646         _, _, user = logging_run(['--results-directory=foo'], tests_included=True, host=host)
647         self.assertEqual(user.opened_urls, [path.abspath_to_uri(host.platform, '/tmp/cwd/foo/results.html')])
648
649     def test_retrying_default_value(self):
650         host = MockHost()
651         details, err, _ = logging_run(['--debug-rwt-logging', 'failures/unexpected/text-image-checksum.html'], tests_included=True, host=host)
652         self.assertEqual(details.exit_code, 1)
653         self.assertFalse('Retrying' in err.getvalue())
654
655         host = MockHost()
656         details, err, _ = logging_run(['--debug-rwt-logging', 'failures/unexpected'], tests_included=True, host=host)
657         self.assertEqual(details.exit_code, test.UNEXPECTED_FAILURES - 7)  # FIXME: This should be a constant in test.py .
658         self.assertTrue('Retrying' in err.getvalue())
659
660     def test_retrying_default_value_test_list(self):
661         host = MockHost()
662         filename = '/tmp/foo.txt'
663         host.filesystem.write_text_file(filename, 'failures/unexpected/text-image-checksum.html\nfailures/unexpected/crash.html')
664         details, err, _ = logging_run(['--debug-rwt-logging', '--test-list=%s' % filename], tests_included=True, host=host)
665         self.assertEqual(details.exit_code, 2)
666         self.assertFalse('Retrying' in err.getvalue())
667
668         host = MockHost()
669         filename = '/tmp/foo.txt'
670         host.filesystem.write_text_file(filename, 'failures')
671         details, err, _ = logging_run(['--debug-rwt-logging', '--test-list=%s' % filename], tests_included=True, host=host)
672         self.assertEqual(details.exit_code, test.UNEXPECTED_FAILURES - 7)
673         self.assertTrue('Retrying' in err.getvalue())
674
675     def test_retrying_and_flaky_tests(self):
676         host = MockHost()
677         details, err, _ = logging_run(['--debug-rwt-logging', '--retry-failures', 'failures/flaky'], tests_included=True, host=host)
678         self.assertEqual(details.exit_code, 0)
679         self.assertTrue('Retrying' in err.getvalue())
680         self.assertTrue(host.filesystem.exists('/tmp/layout-test-results/failures/flaky/text-actual.txt'))
681         self.assertFalse(host.filesystem.exists('/tmp/layout-test-results/retries/failures/flaky/text-actual.txt'))
682         self.assertEqual(len(host.user.opened_urls), 0)
683
684         # Now we test that --clobber-old-results does remove the old entries and the old retries,
685         # and that we don't retry again.
686         host = MockHost()
687         details, err, _ = logging_run(['--no-retry-failures', '--clobber-old-results', 'failures/flaky'], tests_included=True, host=host)
688         self.assertEqual(details.exit_code, 1)
689         self.assertTrue('Clobbering old results' in err.getvalue())
690         self.assertTrue('flaky/text.html' in err.getvalue())
691         self.assertTrue(host.filesystem.exists('/tmp/layout-test-results/failures/flaky/text-actual.txt'))
692         self.assertFalse(host.filesystem.exists('retries'))
693         self.assertEqual(len(host.user.opened_urls), 1)
694
695     def test_retrying_crashed_tests(self):
696         host = MockHost()
697         details, err, _ = logging_run(['--retry-failures', 'failures/unexpected/crash.html'], tests_included=True, host=host)
698         self.assertEqual(details.exit_code, 1)
699         self.assertTrue('Retrying' in err.getvalue())
700
701     def test_retrying_leak_tests(self):
702         host = MockHost()
703         details, err, _ = logging_run(['--retry-failures', 'failures/unexpected/leak.html'], tests_included=True, host=host)
704         self.assertEqual(details.exit_code, 1)
705         self.assertTrue('Retrying' in err.getvalue())
706
707     def test_retrying_force_pixel_tests(self):
708         host = MockHost()
709         details, err, _ = logging_run(['--no-pixel-tests', '--retry-failures', 'failures/unexpected/text-image-checksum.html'], tests_included=True, host=host)
710         self.assertEqual(details.exit_code, 1)
711         self.assertTrue('Retrying' in err.getvalue())
712         self.assertTrue(host.filesystem.exists('/tmp/layout-test-results/failures/unexpected/text-image-checksum-actual.txt'))
713         self.assertFalse(host.filesystem.exists('/tmp/layout-test-results/failures/unexpected/text-image-checksum-actual.png'))
714         self.assertTrue(host.filesystem.exists('/tmp/layout-test-results/retries/failures/unexpected/text-image-checksum-actual.txt'))
715         self.assertTrue(host.filesystem.exists('/tmp/layout-test-results/retries/failures/unexpected/text-image-checksum-actual.png'))
716         json_string = host.filesystem.read_text_file('/tmp/layout-test-results/full_results.json')
717         json = parse_full_results(json_string)
718         self.assertEqual(json["tests"]["failures"]["unexpected"]["text-image-checksum.html"],
719             {"expected": "PASS", "actual": "TEXT IMAGE+TEXT", "is_unexpected": True})
720         self.assertFalse(json["pixel_tests_enabled"])
721         self.assertEqual(details.enabled_pixel_tests_in_retry, True)
722
723     def test_retrying_uses_retries_directory(self):
724         host = MockHost()
725         details, err, _ = logging_run(['--debug-rwt-logging', '--retry-failures', 'failures/unexpected/text-image-checksum.html'], tests_included=True, host=host)
726         self.assertEqual(details.exit_code, 1)
727         self.assertTrue(host.filesystem.exists('/tmp/layout-test-results/failures/unexpected/text-image-checksum-actual.txt'))
728         self.assertTrue(host.filesystem.exists('/tmp/layout-test-results/retries/failures/unexpected/text-image-checksum-actual.txt'))
729
730     def test_run_order__inline(self):
731         # These next tests test that we run the tests in ascending alphabetical
732         # order per directory. HTTP tests are sharded separately from other tests,
733         # so we have to test both.
734         tests_run = get_tests_run(['-i', 'passes/virtual_passes', 'passes'])
735         self.assertEqual(tests_run, sorted(tests_run))
736
737         tests_run = get_tests_run(['http/tests/passes'])
738         self.assertEqual(tests_run, sorted(tests_run))
739
740     def test_virtual(self):
741         self.assertTrue(passing_run(['passes/text.html', 'passes/args.html',
742                                      'virtual/passes/text.html', 'virtual/passes/args.html']))
743
744     def test_reftest_run(self):
745         tests_run = get_tests_run(['passes/reftest.html'])
746         self.assertEqual(['passes/reftest.html'], tests_run)
747
748     def test_reftest_run_reftests_if_pixel_tests_are_disabled(self):
749         tests_run = get_tests_run(['--no-pixel-tests', 'passes/reftest.html'])
750         self.assertEqual(['passes/reftest.html'], tests_run)
751
752     def test_reftest_expected_html_should_be_ignored(self):
753         tests_run = get_tests_run(['passes/reftest-expected.html'])
754         self.assertEqual([], tests_run)
755
756     def test_reftest_driver_should_run_expected_html(self):
757         tests_run = get_test_results(['passes/reftest.html'])
758         self.assertEqual(tests_run[0].references, ['passes/reftest-expected.html'])
759
760     def test_reftest_driver_should_run_expected_mismatch_html(self):
761         tests_run = get_test_results(['passes/mismatch.html'])
762         self.assertEqual(tests_run[0].references, ['passes/mismatch-expected-mismatch.html'])
763
764     def test_reftest_should_not_use_naming_convention_if_not_listed_in_reftestlist(self):
765         host = MockHost()
766         _, err, _ = logging_run(['--no-show-results', 'reftests/foo/'], tests_included=True, host=host)
767         results = parse_full_results(host.filesystem.read_text_file('/tmp/layout-test-results/full_results.json'))
768
769         self.assertEqual(results["tests"]["reftests"]["foo"]["unlistedtest.html"]["actual"], "MISSING"),
770         self.assertEqual(results["num_regressions"], 5)
771         self.assertEqual(results["num_flaky"], 0)
772
773     def test_reftest_crash(self):
774         test_results = get_test_results(['failures/unexpected/crash-reftest.html'])
775         # The list of references should be empty since the test crashed and we didn't run any references.
776         self.assertEqual(test_results[0].references, [])
777
778     def test_reftest_with_virtual_reference(self):
779         _, err, _ = logging_run(['--details', 'virtual/virtual_passes/passes/reftest.html'], tests_included=True)
780         self.assertTrue('ref: virtual/virtual_passes/passes/reftest-expected.html' in err.getvalue())
781
782     def test_additional_platform_directory(self):
783         self.assertTrue(passing_run(['--additional-platform-directory', '/tmp/foo']))
784         self.assertTrue(passing_run(['--additional-platform-directory', '/tmp/../foo']))
785         self.assertTrue(passing_run(['--additional-platform-directory', '/tmp/foo', '--additional-platform-directory', '/tmp/bar']))
786         self.assertTrue(passing_run(['--additional-platform-directory', 'foo']))
787
788     def test_additional_expectations(self):
789         host = MockHost()
790         host.filesystem.write_text_file('/tmp/overrides.txt', 'Bug(x) failures/unexpected/mismatch.html [ ImageOnlyFailure ]\n')
791         self.assertTrue(passing_run(['--additional-expectations', '/tmp/overrides.txt', 'failures/unexpected/mismatch.html'],
792                                     tests_included=True, host=host))
793
794     @staticmethod
795     def has_test_of_type(tests, type):
796         return [test for test in tests if type in test]
797
798     def test_platform_directories_ignored_when_searching_for_tests(self):
799         tests_run = get_tests_run(['--platform', 'test-mac-leopard'])
800         self.assertFalse('platform/test-mac-leopard/http/test.html' in tests_run)
801         self.assertFalse('platform/test-win-win7/http/test.html' in tests_run)
802
803     def test_platform_directories_not_searched_for_additional_tests(self):
804         tests_run = get_tests_run(['--platform', 'test-mac-leopard', 'http'])
805         self.assertFalse('platform/test-mac-leopard/http/test.html' in tests_run)
806         self.assertFalse('platform/test-win-win7/http/test.html' in tests_run)
807
808     def test_output_diffs(self):
809         # Test to ensure that we don't generate -wdiff.html or -pretty.html if wdiff and PrettyPatch
810         # aren't available.
811         host = MockHost()
812         _, err, _ = logging_run(['--pixel-tests', 'failures/unexpected/text-image-checksum.html'], tests_included=True, host=host)
813         written_files = host.filesystem.written_files
814         self.assertTrue(any(path.endswith('-diff.txt') for path in written_files.keys()))
815         self.assertFalse(any(path.endswith('-wdiff.html') for path in written_files.keys()))
816         self.assertFalse(any(path.endswith('-pretty-diff.html') for path in written_files.keys()))
817
818         full_results_text = host.filesystem.read_text_file('/tmp/layout-test-results/full_results.json')
819         full_results = json.loads(full_results_text.replace("ADD_RESULTS(", "").replace(");", ""))
820         self.assertEqual(full_results['has_wdiff'], False)
821         self.assertEqual(full_results['has_pretty_patch'], False)
822
823     def test_unsupported_platform(self):
824         stdout = StringIO.StringIO()
825         stderr = StringIO.StringIO()
826         res = run_webkit_tests.main(['--platform', 'foo'], stdout, stderr)
827
828         self.assertEqual(res, test_run_results.UNEXPECTED_ERROR_EXIT_STATUS)
829         self.assertEqual(stdout.getvalue(), '')
830         self.assertTrue('unsupported platform' in stderr.getvalue())
831
832     def test_build_check(self):
833         # By using a port_name for a different platform than the one we're running on, the build check should always fail.
834         if sys.platform == 'darwin':
835             port_name = 'linux-x86'
836         else:
837             port_name = 'mac-lion'
838         out = StringIO.StringIO()
839         err = StringIO.StringIO()
840         self.assertEqual(run_webkit_tests.main(['--platform', port_name, 'fast/harness/results.html'], out, err), test_run_results.UNEXPECTED_ERROR_EXIT_STATUS)
841
842     def test_verbose_in_child_processes(self):
843         # When we actually run multiple processes, we may have to reconfigure logging in the
844         # child process (e.g., on win32) and we need to make sure that works and we still
845         # see the verbose log output. However, we can't use logging_run() because using
846         # outputcapture to capture stdout and stderr latter results in a nonpicklable host.
847
848         # Test is flaky on Windows: https://bugs.webkit.org/show_bug.cgi?id=98559
849         if not self.should_test_processes:
850             return
851
852         options, parsed_args = parse_args(['--verbose', '--fully-parallel', '--child-processes', '2', 'passes/text.html', 'passes/image.html'], tests_included=True, print_nothing=False)
853         host = MockHost()
854         port_obj = host.port_factory.get(port_name=options.platform, options=options)
855         logging_stream = StringIO.StringIO()
856         run_webkit_tests.run(port_obj, options, parsed_args, logging_stream=logging_stream)
857         self.assertTrue('text.html passed' in logging_stream.getvalue())
858         self.assertTrue('image.html passed' in logging_stream.getvalue())
859
860     def disabled_test_driver_logging(self):
861         # FIXME: Figure out how to either use a mock-test port to
862         # get output or mack mock ports work again.
863         host = Host()
864         _, err, _ = logging_run(['--platform', 'mock-win', '--driver-logging', 'fast/harness/results.html'],
865                                 tests_included=True, host=host)
866         self.assertTrue('OUT:' in err.getvalue())
867
868     def test_write_full_results_to(self):
869         host = MockHost()
870         details, _, _ = logging_run(['--write-full-results-to', '/tmp/full_results.json'], host=host)
871         self.assertEqual(details.exit_code, 0)
872         self.assertTrue(host.filesystem.exists('/tmp/full_results.json'))
873
874
875 class EndToEndTest(unittest.TestCase):
876     def test_reftest_with_two_notrefs(self):
877         # Test that we update expectations in place. If the expectation
878         # is missing, update the expected generic location.
879         host = MockHost()
880         _, _, _ = logging_run(['--no-show-results', 'reftests/foo/'], tests_included=True, host=host)
881         file_list = host.filesystem.written_files.keys()
882
883         json_string = host.filesystem.read_text_file('/tmp/layout-test-results/failing_results.json')
884         json = parse_full_results(json_string)
885         self.assertTrue("multiple-match-success.html" not in json["tests"]["reftests"]["foo"])
886         self.assertTrue("multiple-mismatch-success.html" not in json["tests"]["reftests"]["foo"])
887         self.assertTrue("multiple-both-success.html" not in json["tests"]["reftests"]["foo"])
888
889         self.assertEqual(json["tests"]["reftests"]["foo"]["multiple-match-failure.html"],
890             {"expected": "PASS", "actual": "IMAGE", "reftest_type": ["=="], "is_unexpected": True})
891         self.assertEqual(json["tests"]["reftests"]["foo"]["multiple-mismatch-failure.html"],
892             {"expected": "PASS", "actual": "IMAGE", "reftest_type": ["!="], "is_unexpected": True})
893         self.assertEqual(json["tests"]["reftests"]["foo"]["multiple-both-failure.html"],
894             {"expected": "PASS", "actual": "IMAGE", "reftest_type": ["==", "!="], "is_unexpected": True})
895
896
897 class RebaselineTest(unittest.TestCase, StreamTestingMixin):
898     def assertBaselines(self, file_list, file, extensions, err):
899         "assert that the file_list contains the baselines."""
900         for ext in extensions:
901             baseline = file + "-expected" + ext
902             baseline_msg = 'Writing new expected result "%s"\n' % baseline
903             self.assertTrue(any(f.find(baseline) != -1 for f in file_list))
904             self.assertContains(err, baseline_msg)
905
906     # FIXME: Add tests to ensure that we're *not* writing baselines when we're not
907     # supposed to be.
908
909     def test_reset_results(self):
910         # Test that we update expectations in place. If the expectation
911         # is missing, update the expected generic location.
912         host = MockHost()
913         details, err, _ = logging_run(
914             ['--pixel-tests', '--reset-results', 'passes/image.html', 'failures/expected/missing_image.html'],
915             tests_included=True, host=host, new_results=True)
916         file_list = host.filesystem.written_files.keys()
917         self.assertEqual(details.exit_code, 0)
918         self.assertEqual(len(file_list), 8)
919         self.assertBaselines(file_list, "passes/image", [".txt", ".png"], err)
920         self.assertBaselines(file_list, "failures/expected/missing_image", [".txt", ".png"], err)
921
922     def test_missing_results(self):
923         # Test that we update expectations in place. If the expectation
924         # is missing, update the expected generic location.
925         host = MockHost()
926         details, err, _ = logging_run(['--no-show-results',
927             'failures/unexpected/missing_text.html',
928             'failures/unexpected/missing_image.html',
929             'failures/unexpected/missing_render_tree_dump.html'],
930             tests_included=True, host=host, new_results=True)
931         file_list = host.filesystem.written_files.keys()
932         self.assertEqual(details.exit_code, 3)
933         self.assertEqual(len(file_list), 10)
934         self.assertBaselines(file_list, "failures/unexpected/missing_text", [".txt"], err)
935         self.assertBaselines(file_list, "platform/test/failures/unexpected/missing_image", [".png"], err)
936         self.assertBaselines(file_list, "platform/test/failures/unexpected/missing_render_tree_dump", [".txt"], err)
937
938     def test_missing_results_not_added_if_expected_missing(self):
939         # Test that we update expectations in place. If the expectation
940         # is missing, update the expected generic location.
941         host = MockHost()
942         options, parsed_args = run_webkit_tests.parse_args([])
943
944         port = test.TestPort(host, options=options)
945         host.filesystem.write_text_file(port.path_to_generic_test_expectations_file(), """
946 Bug(foo) failures/unexpected/missing_text.html [ Missing ]
947 Bug(foo) failures/unexpected/missing_image.html [ NeedsRebaseline ]
948 Bug(foo) failures/unexpected/missing_audio.html [ NeedsManualRebaseline ]
949 Bug(foo) failures/unexpected/missing_render_tree_dump.html [ Missing ]
950 """)
951         details, err, _ = logging_run(['--no-show-results',
952             'failures/unexpected/missing_text.html',
953             'failures/unexpected/missing_image.html',
954             'failures/unexpected/missing_audio.html',
955             'failures/unexpected/missing_render_tree_dump.html'],
956             tests_included=True, host=host, new_results=True,  port_obj=port)
957         file_list = host.filesystem.written_files.keys()
958         self.assertEqual(details.exit_code, 0)
959         self.assertEqual(len(file_list), 7)
960         self.assertFalse(any('failures/unexpected/missing_text-expected' in file for file in file_list))
961         self.assertFalse(any('failures/unexpected/missing_image-expected' in file for file in file_list))
962         self.assertFalse(any('failures/unexpected/missing_render_tree_dump-expected' in file for file in file_list))
963
964     def test_missing_results_not_added_if_expected_missing_and_reset_results(self):
965         # Test that we update expectations in place. If the expectation
966         # is missing, update the expected generic location.
967         host = MockHost()
968         options, parsed_args = run_webkit_tests.parse_args(['--pixel-tests', '--reset-results'])
969
970         port = test.TestPort(host, options=options)
971         host.filesystem.write_text_file(port.path_to_generic_test_expectations_file(), """
972 Bug(foo) failures/unexpected/missing_text.html [ Missing ]
973 Bug(foo) failures/unexpected/missing_image.html [ NeedsRebaseline ]
974 Bug(foo) failures/unexpected/missing_audio.html [ NeedsManualRebaseline ]
975 Bug(foo) failures/unexpected/missing_render_tree_dump.html [ Missing ]
976 """)
977         details, err, _ = logging_run(['--pixel-tests', '--reset-results',
978             'failures/unexpected/missing_text.html',
979             'failures/unexpected/missing_image.html',
980             'failures/unexpected/missing_audio.html',
981             'failures/unexpected/missing_render_tree_dump.html'],
982             tests_included=True, host=host, new_results=True,  port_obj=port)
983         file_list = host.filesystem.written_files.keys()
984         self.assertEqual(details.exit_code, 0)
985         self.assertEqual(len(file_list), 11)
986         self.assertBaselines(file_list, "failures/unexpected/missing_text", [".txt"], err)
987         self.assertBaselines(file_list, "failures/unexpected/missing_image", [".png"], err)
988         self.assertBaselines(file_list, "failures/unexpected/missing_render_tree_dump", [".txt"], err)
989
990     def test_new_baseline(self):
991         # Test that we update the platform expectations in the version-specific directories
992         # for both existing and new baselines.
993         host = MockHost()
994         details, err, _ = logging_run(
995             ['--pixel-tests', '--new-baseline', 'passes/image.html', 'failures/expected/missing_image.html'],
996             tests_included=True, host=host, new_results=True)
997         file_list = host.filesystem.written_files.keys()
998         self.assertEqual(details.exit_code, 0)
999         self.assertEqual(len(file_list), 8)
1000         self.assertBaselines(file_list,
1001             "platform/test-mac-leopard/passes/image", [".txt", ".png"], err)
1002         self.assertBaselines(file_list,
1003             "platform/test-mac-leopard/failures/expected/missing_image", [".txt", ".png"], err)
1004
1005
1006 class PortTest(unittest.TestCase):
1007     def assert_mock_port_works(self, port_name, args=[]):
1008         self.assertTrue(passing_run(args + ['--platform', 'mock-' + port_name, 'fast/harness/results.html'], tests_included=True, host=Host()))
1009
1010     def disabled_test_mac_lion(self):
1011         self.assert_mock_port_works('mac-lion')
1012
1013
1014 class MainTest(unittest.TestCase):
1015     def test_exception_handling(self):
1016         orig_run_fn = run_webkit_tests.run
1017
1018         # unused args pylint: disable=W0613
1019         def interrupting_run(port, options, args, stderr):
1020             raise KeyboardInterrupt
1021
1022         def successful_run(port, options, args, stderr):
1023
1024             class FakeRunDetails(object):
1025                 exit_code = test_run_results.UNEXPECTED_ERROR_EXIT_STATUS
1026
1027             return FakeRunDetails()
1028
1029         def exception_raising_run(port, options, args, stderr):
1030             assert False
1031
1032         stdout = StringIO.StringIO()
1033         stderr = StringIO.StringIO()
1034         try:
1035             run_webkit_tests.run = interrupting_run
1036             res = run_webkit_tests.main([], stdout, stderr)
1037             self.assertEqual(res, test_run_results.INTERRUPTED_EXIT_STATUS)
1038
1039             run_webkit_tests.run = successful_run
1040             res = run_webkit_tests.main(['--platform', 'test'], stdout, stderr)
1041             self.assertEqual(res, test_run_results.UNEXPECTED_ERROR_EXIT_STATUS)
1042
1043             run_webkit_tests.run = exception_raising_run
1044             res = run_webkit_tests.main([], stdout, stderr)
1045             self.assertEqual(res, test_run_results.UNEXPECTED_ERROR_EXIT_STATUS)
1046         finally:
1047             run_webkit_tests.run = orig_run_fn
1048
1049     def test_buildbot_results_are_printed_on_early_exit(self):
1050         # unused args pylint: disable=W0613
1051         stdout = StringIO.StringIO()
1052         stderr = StringIO.StringIO()
1053         res = run_webkit_tests.main(['--platform', 'test', '--exit-after-n-failures', '1',
1054                                      'failures/unexpected/missing_text.html',
1055                                      'failures/unexpected/missing_image.html'],
1056                                     stdout, stderr)
1057         self.assertEqual(res, test_run_results.EARLY_EXIT_STATUS)
1058         self.assertEqual(stdout.getvalue(),
1059                 ('\n'
1060                  'Regressions: Unexpected missing results (1)\n'
1061                  '  failures/unexpected/missing_image.html [ Missing ]\n\n'))