Merge TestExpectationSerializer into TestExpectationLine
[WebKit-https.git] / Tools / Scripts / webkitpy / layout_tests / models / test_expectations_unittest.py
1 #!/usr/bin/python
2 # Copyright (C) 2010 Google Inc. All rights reserved.
3 #
4 # Redistribution and use in source and binary forms, with or without
5 # modification, are permitted provided that the following conditions are
6 # met:
7 #
8 #     * Redistributions of source code must retain the above copyright
9 # notice, this list of conditions and the following disclaimer.
10 #     * Redistributions in binary form must reproduce the above
11 # copyright notice, this list of conditions and the following disclaimer
12 # in the documentation and/or other materials provided with the
13 # distribution.
14 #     * Neither the name of Google Inc. nor the names of its
15 # contributors may be used to endorse or promote products derived from
16 # this software without specific prior written permission.
17 #
18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30 import unittest
31
32 from webkitpy.common.host_mock import MockHost
33
34 from webkitpy.layout_tests.models.test_configuration import *
35 from webkitpy.layout_tests.models.test_expectations import *
36 from webkitpy.layout_tests.models.test_configuration import *
37
38 try:
39     from collections import OrderedDict
40 except ImportError:
41     # Needed for Python < 2.7
42     from webkitpy.thirdparty.ordered_dict import OrderedDict
43
44
45 class MockBugManager(object):
46     def close_bug(self, bug_id, reference_bug_id=None):
47         pass
48
49     def create_bug(self):
50         return "BUG_NEWLY_CREATED"
51
52
53 class Base(unittest.TestCase):
54     # Note that all of these tests are written assuming the configuration
55     # being tested is Windows XP, Release build.
56
57     def __init__(self, testFunc):
58         host = MockHost()
59         self._port = host.port_factory.get('test-win-xp', None)
60         self._exp = None
61         unittest.TestCase.__init__(self, testFunc)
62
63     def get_test(self, test_name):
64         # FIXME: Remove this routine and just reference test names directly.
65         return test_name
66
67     def get_basic_tests(self):
68         return [self.get_test('failures/expected/text.html'),
69                 self.get_test('failures/expected/image_checksum.html'),
70                 self.get_test('failures/expected/crash.html'),
71                 self.get_test('failures/expected/missing_text.html'),
72                 self.get_test('failures/expected/image.html'),
73                 self.get_test('passes/text.html')]
74
75     def get_basic_expectations(self):
76         return """
77 BUG_TEST : failures/expected/text.html = TEXT
78 BUG_TEST WONTFIX SKIP : failures/expected/crash.html = CRASH
79 BUG_TEST REBASELINE : failures/expected/missing_image.html = MISSING
80 BUG_TEST WONTFIX : failures/expected/image_checksum.html = IMAGE
81 BUG_TEST WONTFIX MAC : failures/expected/image.html = IMAGE
82 """
83
84     def parse_exp(self, expectations, overrides=None, is_lint_mode=False):
85         expectations_dict = OrderedDict()
86         expectations_dict['expectations'] = expectations
87         if overrides:
88             expectations_dict['overrides'] = overrides
89         self._port.expectations_dict = lambda: expectations_dict
90         self._exp = TestExpectations(self._port, self.get_basic_tests(), is_lint_mode)
91
92     def assert_exp(self, test, result):
93         self.assertEquals(self._exp.get_expectations(self.get_test(test)),
94                           set([result]))
95
96     def assert_bad_expectations(self, expectations, overrides=None):
97         self.assertRaises(ParseError, self.parse_exp, expectations, is_lint_mode=True, overrides=overrides)
98
99     def test_result_was_expected(self):
100         # test basics
101         self.assertEquals(TestExpectations.result_was_expected(PASS, set([PASS]), test_needs_rebaselining=False, test_is_skipped=False), True)
102         self.assertEquals(TestExpectations.result_was_expected(TEXT, set([PASS]), test_needs_rebaselining=False, test_is_skipped=False), False)
103
104         # test handling of SKIPped tests and results
105         self.assertEquals(TestExpectations.result_was_expected(SKIP, set([CRASH]), test_needs_rebaselining=False, test_is_skipped=True), True)
106         self.assertEquals(TestExpectations.result_was_expected(SKIP, set([CRASH]), test_needs_rebaselining=False, test_is_skipped=False), False)
107
108         # test handling of MISSING results and the REBASELINE modifier
109         self.assertEquals(TestExpectations.result_was_expected(MISSING, set([PASS]), test_needs_rebaselining=True, test_is_skipped=False), True)
110         self.assertEquals(TestExpectations.result_was_expected(MISSING, set([PASS]), test_needs_rebaselining=False, test_is_skipped=False), False)
111
112     def test_remove_pixel_failures(self):
113         self.assertEquals(TestExpectations.remove_pixel_failures(set([TEXT])), set([TEXT]))
114         self.assertEquals(TestExpectations.remove_pixel_failures(set([PASS])), set([PASS]))
115         self.assertEquals(TestExpectations.remove_pixel_failures(set([IMAGE])), set([PASS]))
116         self.assertEquals(TestExpectations.remove_pixel_failures(set([IMAGE_PLUS_TEXT])), set([TEXT]))
117         self.assertEquals(TestExpectations.remove_pixel_failures(set([PASS, IMAGE, CRASH])), set([PASS, CRASH]))
118
119     def test_suffixes_for_expectations(self):
120         self.assertEquals(TestExpectations.suffixes_for_expectations(set([TEXT])), set(['txt']))
121         self.assertEquals(TestExpectations.suffixes_for_expectations(set([IMAGE_PLUS_TEXT])), set(['txt', 'png']))
122         self.assertEquals(TestExpectations.suffixes_for_expectations(set([IMAGE])), set(['png']))
123         self.assertEquals(TestExpectations.suffixes_for_expectations(set([AUDIO])), set(['wav']))
124         self.assertEquals(TestExpectations.suffixes_for_expectations(set([TEXT, IMAGE, CRASH])), set(['txt', 'png']))
125         self.assertEquals(TestExpectations.suffixes_for_expectations(set()), set())
126
127
128 class BasicTests(Base):
129     def test_basic(self):
130         self.parse_exp(self.get_basic_expectations())
131         self.assert_exp('failures/expected/text.html', TEXT)
132         self.assert_exp('failures/expected/image_checksum.html', IMAGE)
133         self.assert_exp('passes/text.html', PASS)
134         self.assert_exp('failures/expected/image.html', PASS)
135
136
137 class MiscTests(Base):
138     def test_multiple_results(self):
139         self.parse_exp('BUGX : failures/expected/text.html = TEXT CRASH')
140         self.assertEqual(self._exp.get_expectations(
141             self.get_test('failures/expected/text.html')),
142             set([TEXT, CRASH]))
143
144     def test_category_expectations(self):
145         # This test checks unknown tests are not present in the
146         # expectations and that known test part of a test category is
147         # present in the expectations.
148         exp_str = """
149 BUGX WONTFIX : failures/expected = IMAGE
150 """
151         self.parse_exp(exp_str)
152         test_name = 'failures/expected/unknown-test.html'
153         unknown_test = self.get_test(test_name)
154         self.assertRaises(KeyError, self._exp.get_expectations,
155                           unknown_test)
156         self.assert_exp('failures/expected/crash.html', IMAGE)
157
158     def test_get_modifiers(self):
159         self.parse_exp(self.get_basic_expectations())
160         self.assertEqual(self._exp.get_modifiers(
161                          self.get_test('passes/text.html')), [])
162
163     def test_get_expectations_string(self):
164         self.parse_exp(self.get_basic_expectations())
165         self.assertEquals(self._exp.get_expectations_string(
166                           self.get_test('failures/expected/text.html')),
167                           'TEXT')
168
169     def test_expectation_to_string(self):
170         # Normal cases are handled by other tests.
171         self.parse_exp(self.get_basic_expectations())
172         self.assertRaises(ValueError, self._exp.expectation_to_string,
173                           -1)
174
175     def test_get_test_set(self):
176         # Handle some corner cases for this routine not covered by other tests.
177         self.parse_exp(self.get_basic_expectations())
178         s = self._exp.get_test_set(WONTFIX)
179         self.assertEqual(s,
180             set([self.get_test('failures/expected/crash.html'),
181                  self.get_test('failures/expected/image_checksum.html')]))
182         s = self._exp.get_test_set(WONTFIX, CRASH)
183         self.assertEqual(s,
184             set([self.get_test('failures/expected/crash.html')]))
185         s = self._exp.get_test_set(WONTFIX, CRASH, include_skips=False)
186         self.assertEqual(s, set([]))
187
188     def test_parse_warning(self):
189         try:
190             filesystem = self._port.host.filesystem
191             filesystem.write_text_file(filesystem.join(self._port.layout_tests_dir(), 'disabled-test.html-disabled'), 'content')
192             self.get_test('disabled-test.html-disabled'),
193             self.parse_exp("FOO : failures/expected/text.html = TEXT\n"
194                 "SKIP : failures/expected/image.html\n"
195                 "BUGRNIWA : non-existent-test.html = TEXT\n"
196                 "BUGRNIWA : disabled-test.html-disabled = IMAGE", is_lint_mode=True)
197             self.assertFalse(True, "ParseError wasn't raised")
198         except ParseError, e:
199             warnings = ("expectations:1 Test lacks BUG modifier. failures/expected/text.html\n"
200                         "expectations:1 Unrecognized modifier 'foo' failures/expected/text.html\n"
201                         "expectations:2 Missing expectations SKIP : failures/expected/image.html\n"
202                         "expectations:3 Path does not exist. non-existent-test.html")
203             self.assertEqual(str(e), warnings)
204
205         try:
206             self.parse_exp('SKIP : failures/expected/text.html = TEXT', is_lint_mode=True)
207             self.assertFalse(True, "ParseError wasn't raised")
208         except ParseError, e:
209             warnings = u'expectations:1 Test lacks BUG modifier. failures/expected/text.html'
210             self.assertEqual(str(e), warnings)
211
212     def test_error_on_different_platform(self):
213         # parse_exp uses a Windows port. Assert errors on Mac show up in lint mode.
214         self.assertRaises(ParseError, self.parse_exp,
215             'BUG_TEST MAC : failures/expected/text.html = TEXT\nBUG_TEST MAC : failures/expected/text.html = TEXT',
216             is_lint_mode=True)
217
218     def test_error_on_different_build_type(self):
219         # parse_exp uses a Release port. Assert errors on DEBUG show up in lint mode.
220         self.assertRaises(ParseError, self.parse_exp,
221             'BUG_TEST DEBUG : failures/expected/text.html = TEXT\nBUG_TEST DEBUG : failures/expected/text.html = TEXT',
222             is_lint_mode=True)
223
224     def test_overrides(self):
225         self.parse_exp("BUG_EXP: failures/expected/text.html = TEXT",
226                        "BUG_OVERRIDE : failures/expected/text.html = IMAGE")
227         self.assert_exp('failures/expected/text.html', IMAGE)
228
229     def test_overrides__directory(self):
230         self.parse_exp("BUG_EXP: failures/expected/text.html = TEXT",
231                        "BUG_OVERRIDE: failures/expected = CRASH")
232         self.assert_exp('failures/expected/text.html', CRASH)
233         self.assert_exp('failures/expected/image.html', CRASH)
234
235     def test_overrides__duplicate(self):
236         self.assert_bad_expectations("BUG_EXP: failures/expected/text.html = TEXT",
237                                      "BUG_OVERRIDE : failures/expected/text.html = IMAGE\n"
238                                      "BUG_OVERRIDE : failures/expected/text.html = CRASH\n")
239
240     def test_pixel_tests_flag(self):
241         def match(test, result, pixel_tests_enabled):
242             return self._exp.matches_an_expected_result(
243                 self.get_test(test), result, pixel_tests_enabled)
244
245         self.parse_exp(self.get_basic_expectations())
246         self.assertTrue(match('failures/expected/text.html', TEXT, True))
247         self.assertTrue(match('failures/expected/text.html', TEXT, False))
248         self.assertFalse(match('failures/expected/text.html', CRASH, True))
249         self.assertFalse(match('failures/expected/text.html', CRASH, False))
250         self.assertTrue(match('failures/expected/image_checksum.html', IMAGE,
251                               True))
252         self.assertTrue(match('failures/expected/image_checksum.html', PASS,
253                               False))
254         self.assertTrue(match('failures/expected/crash.html', SKIP, False))
255         self.assertTrue(match('passes/text.html', PASS, False))
256
257     def test_more_specific_override_resets_skip(self):
258         self.parse_exp("BUGX SKIP : failures/expected = TEXT\n"
259                        "BUGX : failures/expected/text.html = IMAGE\n")
260         self.assert_exp('failures/expected/text.html', IMAGE)
261         self.assertFalse(self._port._filesystem.join(self._port.layout_tests_dir(),
262                                                      'failures/expected/text.html') in
263                          self._exp.get_tests_with_result_type(SKIP))
264
265
266 class SkippedTests(Base):
267     def check(self, expectations, overrides, skips, lint=False):
268         port = MockHost().port_factory.get('qt')
269         port._filesystem.write_text_file(port._filesystem.join(port.layout_tests_dir(), 'failures/expected/text.html'), 'foo')
270         expectations_dict = OrderedDict()
271         expectations_dict['expectations'] = expectations
272         if overrides:
273             expectations_dict['overrides'] = overrides
274         port.expectations_dict = lambda: expectations_dict
275         port.skipped_layout_tests = lambda tests: set(skips)
276         exp = TestExpectations(port, ['failures/expected/text.html'], lint)
277
278         # Check that the expectation is for BUG_DUMMY SKIP : ... = PASS
279         self.assertEquals(exp.get_modifiers('failures/expected/text.html'),
280                           [TestExpectationParser.DUMMY_BUG_MODIFIER, TestExpectationParser.SKIP_MODIFIER, TestExpectationParser.WONTFIX_MODIFIER])
281         self.assertEquals(exp.get_expectations('failures/expected/text.html'), set([PASS]))
282
283     def test_skipped_tests_work(self):
284         self.check(expectations='', overrides=None, skips=['failures/expected/text.html'])
285
286     def test_duplicate_skipped_test_fails_lint(self):
287         self.assertRaises(ParseError, self.check, expectations='BUGX : failures/expected/text.html = text\n', overrides=None, skips=['failures/expected/text.html'], lint=True)
288
289     def test_skipped_file_overrides_expectations(self):
290         self.check(expectations='BUGX : failures/expected/text.html = TEXT\n', overrides=None,
291                    skips=['failures/expected/text.html'])
292
293     def test_skipped_dir_overrides_expectations(self):
294         self.check(expectations='BUGX : failures/expected/text.html = TEXT\n', overrides=None,
295                    skips=['failures/expected'])
296
297     def test_skipped_file_overrides_overrides(self):
298         self.check(expectations='', overrides='BUGX : failures/expected/text.html = TEXT\n',
299                    skips=['failures/expected/text.html'])
300
301     def test_skipped_dir_overrides_overrides(self):
302         self.check(expectations='', overrides='BUGX : failures/expected/text.html = TEXT\n',
303                    skips=['failures/expected'])
304
305
306 class ExpectationSyntaxTests(Base):
307     def test_missing_expectation(self):
308         # This is missing the expectation.
309         self.assert_bad_expectations('BUG_TEST: failures/expected/text.html')
310
311     def test_missing_colon(self):
312         # This is missing the modifiers and the ':'
313         self.assert_bad_expectations('failures/expected/text.html = TEXT')
314
315     def test_too_many_colons(self):
316         self.assert_bad_expectations('BUG_TEST: failures/expected/text.html = PASS :')
317
318     def test_too_many_equals_signs(self):
319         self.assert_bad_expectations('BUG_TEST: failures/expected/text.html = TEXT = IMAGE')
320
321     def test_unrecognized_expectation(self):
322         self.assert_bad_expectations('BUG_TEST: failures/expected/text.html = UNKNOWN')
323
324     def test_macro(self):
325         exp_str = """
326 BUG_TEST WIN : failures/expected/text.html = TEXT
327 """
328         self.parse_exp(exp_str)
329         self.assert_exp('failures/expected/text.html', TEXT)
330
331
332 class SemanticTests(Base):
333     def test_bug_format(self):
334         self.assertRaises(ParseError, self.parse_exp, 'BUG1234 : failures/expected/text.html = TEXT', is_lint_mode=True)
335
336     def test_bad_bugid(self):
337         try:
338             self.parse_exp('BUG1234 SLOW : failures/expected/text.html = TEXT', is_lint_mode=True)
339             self.fail('should have raised an error about a bad bug identifier')
340         except ParseError, exp:
341             self.assertEquals(len(exp.warnings), 1)
342
343     def test_missing_bugid(self):
344         self.parse_exp('SLOW : failures/expected/text.html = TEXT')
345         self.assertTrue(self._exp.has_warnings())
346
347     def test_slow_and_timeout(self):
348         # A test cannot be SLOW and expected to TIMEOUT.
349         self.assertRaises(ParseError, self.parse_exp,
350             'BUG_TEST SLOW : failures/expected/timeout.html = TIMEOUT', is_lint_mode=True)
351
352     def test_rebaseline(self):
353         # Can't lint a file w/ 'REBASELINE' in it.
354         self.assertRaises(ParseError, self.parse_exp,
355             'BUG_TEST REBASELINE : failures/expected/text.html = TEXT',
356             is_lint_mode=True)
357
358     def test_duplicates(self):
359         self.assertRaises(ParseError, self.parse_exp, """
360 BUG_EXP : failures/expected/text.html = TEXT
361 BUG_EXP : failures/expected/text.html = IMAGE""", is_lint_mode=True)
362
363         self.assertRaises(ParseError, self.parse_exp,
364             self.get_basic_expectations(), overrides="""
365 BUG_OVERRIDE : failures/expected/text.html = TEXT
366 BUG_OVERRIDE : failures/expected/text.html = IMAGE""", is_lint_mode=True)
367
368     def test_missing_file(self):
369         # This should log a non-fatal error.
370         self.parse_exp('BUG_TEST : missing_file.html = TEXT')
371         self.assertTrue(self._exp.has_warnings(), 1)
372
373
374 class PrecedenceTests(Base):
375     def test_file_over_directory(self):
376         # This tests handling precedence of specific lines over directories
377         # and tests expectations covering entire directories.
378         exp_str = """
379 BUGX : failures/expected/text.html = TEXT
380 BUGX WONTFIX : failures/expected = IMAGE
381 """
382         self.parse_exp(exp_str)
383         self.assert_exp('failures/expected/text.html', TEXT)
384         self.assert_exp('failures/expected/crash.html', IMAGE)
385
386         exp_str = """
387 BUGX WONTFIX : failures/expected = IMAGE
388 BUGX : failures/expected/text.html = TEXT
389 """
390         self.parse_exp(exp_str)
391         self.assert_exp('failures/expected/text.html', TEXT)
392         self.assert_exp('failures/expected/crash.html', IMAGE)
393
394     def test_ambiguous(self):
395         self.assert_bad_expectations("BUG_TEST RELEASE : passes/text.html = PASS\n"
396                                      "BUG_TEST WIN : passes/text.html = FAIL\n")
397
398     def test_more_modifiers(self):
399         self.assert_bad_expectations("BUG_TEST RELEASE : passes/text.html = PASS\n"
400                                      "BUG_TEST WIN RELEASE : passes/text.html = TEXT\n")
401
402     def test_order_in_file(self):
403         self.assert_bad_expectations("BUG_TEST WIN RELEASE : passes/text.html = TEXT\n"
404                                      "BUG_TEST RELEASE : passes/text.html = PASS\n")
405
406     def test_macro_overrides(self):
407         self.assert_bad_expectations("BUG_TEST WIN : passes/text.html = PASS\n"
408                                      "BUG_TEST XP : passes/text.html = TEXT\n")
409
410
411 class RemoveConfigurationsTest(Base):
412     def test_remove(self):
413         host = MockHost()
414         test_port = host.port_factory.get('test-win-xp', None)
415         test_port.test_exists = lambda test: True
416         test_port.test_isfile = lambda test: True
417
418         test_config = test_port.test_configuration()
419         test_port.expectations_dict = lambda: {"expectations": """BUGX LINUX WIN RELEASE : failures/expected/foo.html = TEXT
420 BUGY WIN MAC DEBUG : failures/expected/foo.html = CRASH
421 """}
422         expectations = TestExpectations(test_port, self.get_basic_tests())
423
424         actual_expectations = expectations.remove_configuration_from_test('failures/expected/foo.html', test_config)
425
426         self.assertEqual("""BUGX LINUX VISTA WIN7 RELEASE : failures/expected/foo.html = TEXT
427 BUGY WIN MAC DEBUG : failures/expected/foo.html = CRASH
428 """, actual_expectations)
429
430     def test_remove_line(self):
431         host = MockHost()
432         test_port = host.port_factory.get('test-win-xp', None)
433         test_port.test_exists = lambda test: True
434         test_port.test_isfile = lambda test: True
435
436         test_config = test_port.test_configuration()
437         test_port.expectations_dict = lambda: {'expectations': """BUGX WIN RELEASE : failures/expected/foo.html = TEXT
438 BUGY WIN DEBUG : failures/expected/foo.html = CRASH
439 """}
440         expectations = TestExpectations(test_port)
441
442         actual_expectations = expectations.remove_configuration_from_test('failures/expected/foo.html', test_config)
443         actual_expectations = expectations.remove_configuration_from_test('failures/expected/foo.html', host.port_factory.get('test-win-vista', None).test_configuration())
444         actual_expectations = expectations.remove_configuration_from_test('failures/expected/foo.html', host.port_factory.get('test-win-win7', None).test_configuration())
445
446         self.assertEqual("""BUGY WIN DEBUG : failures/expected/foo.html = CRASH
447 """, actual_expectations)
448
449
450 class RebaseliningTest(Base):
451     """Test rebaselining-specific functionality."""
452     def assertRemove(self, input_expectations, tests, expected_expectations, filename):
453         self.parse_exp(input_expectations, is_lint_mode=False)
454         actual_expectations = self._exp.remove_rebaselined_tests(tests, filename)
455         self.assertEqual(expected_expectations, actual_expectations)
456
457     def test_remove(self):
458         self.assertRemove('BUGX REBASELINE : failures/expected/text.html = TEXT\n'
459                           'BUGY : failures/expected/image.html = IMAGE\n'
460                           'BUGZ REBASELINE : failures/expected/crash.html = CRASH\n',
461                           ['failures/expected/text.html'],
462                           'BUGY : failures/expected/image.html = IMAGE\n'
463                           'BUGZ REBASELINE : failures/expected/crash.html = CRASH\n',
464                           'expectations')
465
466         # test that we don't remove lines from the expectations if we're asking for the overrides
467         self.assertRemove('BUGX REBASELINE : failures/expected/text.html = TEXT\n'
468                           'BUGY : failures/expected/image.html = IMAGE\n'
469                           'BUGZ REBASELINE : failures/expected/crash.html = CRASH\n',
470                           ['failures/expected/text.html'],
471                           'BUGX REBASELINE : failures/expected/text.html = TEXT\n'
472                           'BUGY : failures/expected/image.html = IMAGE\n'
473                           'BUGZ REBASELINE : failures/expected/crash.html = CRASH\n',
474                           'overrides')
475
476
477     def test_no_get_rebaselining_failures(self):
478         self.parse_exp(self.get_basic_expectations())
479         self.assertEqual(len(self._exp.get_rebaselining_failures()), 0)
480
481
482 class TestExpectationParserTests(unittest.TestCase):
483     def _tokenize(self, line):
484         return TestExpectationParser._tokenize_line('path', line, 0)
485
486     def test_tokenize_blank(self):
487         expectation = self._tokenize('')
488         self.assertEqual(expectation.comment, None)
489         self.assertEqual(len(expectation.warnings), 0)
490
491     def test_tokenize_missing_colon(self):
492         expectation = self._tokenize('Qux.')
493         self.assertEqual(str(expectation.warnings), '["Missing a \':\'"]')
494
495     def test_tokenize_extra_colon(self):
496         expectation = self._tokenize('FOO : : bar')
497         self.assertEqual(str(expectation.warnings), '["Extraneous \':\'"]')
498
499     def test_tokenize_empty_comment(self):
500         expectation = self._tokenize('//')
501         self.assertEqual(expectation.comment, '')
502         self.assertEqual(len(expectation.warnings), 0)
503
504     def test_tokenize_comment(self):
505         expectation = self._tokenize('//Qux.')
506         self.assertEqual(expectation.comment, 'Qux.')
507         self.assertEqual(len(expectation.warnings), 0)
508
509     def test_tokenize_missing_equal(self):
510         expectation = self._tokenize('FOO : bar')
511         self.assertEqual(str(expectation.warnings), "['Missing expectations\']")
512
513     def test_tokenize_extra_equal(self):
514         expectation = self._tokenize('FOO : bar = BAZ = Qux.')
515         self.assertEqual(str(expectation.warnings), '["Extraneous \'=\'"]')
516
517     def test_tokenize_valid(self):
518         expectation = self._tokenize('FOO : bar = BAZ')
519         self.assertEqual(expectation.comment, None)
520         self.assertEqual(len(expectation.warnings), 0)
521
522     def test_tokenize_valid_with_comment(self):
523         expectation = self._tokenize('FOO : bar = BAZ //Qux.')
524         self.assertEqual(expectation.comment, 'Qux.')
525         self.assertEqual(str(expectation.modifiers), "['FOO']")
526         self.assertEqual(str(expectation.expectations), "['BAZ']")
527         self.assertEqual(len(expectation.warnings), 0)
528
529     def test_tokenize_valid_with_multiple_modifiers(self):
530         expectation = self._tokenize('FOO1 FOO2 : bar = BAZ //Qux.')
531         self.assertEqual(expectation.comment, 'Qux.')
532         self.assertEqual(str(expectation.modifiers), "['FOO1', 'FOO2']")
533         self.assertEqual(str(expectation.expectations), "['BAZ']")
534         self.assertEqual(len(expectation.warnings), 0)
535
536     def test_parse_empty_string(self):
537         host = MockHost()
538         test_port = host.port_factory.get('test-win-xp', None)
539         test_port.test_exists = lambda test: True
540         full_test_list = []
541         expectation_line = self._tokenize('')
542         parser = TestExpectationParser(test_port, full_test_list, allow_rebaseline_modifier=False)
543         parser._parse_line(expectation_line)
544         self.assertFalse(expectation_line.is_invalid())
545
546
547 class TestExpectationSerializationTests(unittest.TestCase):
548     def __init__(self, testFunc):
549         host = MockHost()
550         test_port = host.port_factory.get('test-win-xp', None)
551         self._converter = TestConfigurationConverter(test_port.all_test_configurations(), test_port.configuration_specifier_macros())
552         unittest.TestCase.__init__(self, testFunc)
553
554     def _tokenize(self, line):
555         return TestExpectationParser._tokenize_line('path', line, 0)
556
557     def assert_round_trip(self, in_string, expected_string=None):
558         expectation = self._tokenize(in_string)
559         if expected_string is None:
560             expected_string = in_string
561         self.assertEqual(expected_string, expectation.to_string(self._converter))
562
563     def assert_list_round_trip(self, in_string, expected_string=None):
564         host = MockHost()
565         parser = TestExpectationParser(host.port_factory.get('test-win-xp', None), [], allow_rebaseline_modifier=False)
566         expectations = parser.parse('path', in_string)
567         if expected_string is None:
568             expected_string = in_string
569         self.assertEqual(expected_string, TestExpectations.list_to_string(expectations, self._converter))
570
571     def test_unparsed_to_string(self):
572         expectation = TestExpectationLine()
573
574         self.assertEqual(expectation.to_string(self._converter), '')
575         expectation.comment = 'Qux.'
576         self.assertEqual(expectation.to_string(self._converter), '//Qux.')
577         expectation.name = 'bar'
578         self.assertEqual(expectation.to_string(self._converter), ' : bar =  //Qux.')
579         expectation.modifiers = ['foo']
580         self.assertEqual(expectation.to_string(self._converter), 'FOO : bar =  //Qux.')
581         expectation.expectations = ['bAz']
582         self.assertEqual(expectation.to_string(self._converter), 'FOO : bar = BAZ //Qux.')
583         expectation.expectations = ['bAz1', 'baZ2']
584         self.assertEqual(expectation.to_string(self._converter), 'FOO : bar = BAZ1 BAZ2 //Qux.')
585         expectation.modifiers = ['foo1', 'foO2']
586         self.assertEqual(expectation.to_string(self._converter), 'FOO1 FOO2 : bar = BAZ1 BAZ2 //Qux.')
587         expectation.warnings.append('Oh the horror.')
588         self.assertEqual(expectation.to_string(self._converter), '')
589         expectation.original_string = 'Yes it is!'
590         self.assertEqual(expectation.to_string(self._converter), 'Yes it is!')
591
592     def test_unparsed_list_to_string(self):
593         expectation = TestExpectationLine()
594         expectation.comment = 'Qux.'
595         expectation.name = 'bar'
596         expectation.modifiers = ['foo']
597         expectation.expectations = ['bAz1', 'baZ2']
598         self.assertEqual(TestExpectations.list_to_string([expectation]), 'FOO : bar = BAZ1 BAZ2 //Qux.')
599
600     def test_parsed_to_string(self):
601         expectation_line = TestExpectationLine()
602         expectation_line.parsed_bug_modifiers = ['BUGX']
603         expectation_line.name = 'test/name/for/realz.html'
604         expectation_line.parsed_expectations = set([IMAGE])
605         self.assertEqual(expectation_line.to_string(self._converter), None)
606         expectation_line.matching_configurations = set([TestConfiguration('xp', 'x86', 'release')])
607         self.assertEqual(expectation_line.to_string(self._converter), 'BUGX XP RELEASE : test/name/for/realz.html = IMAGE')
608         expectation_line.matching_configurations = set([TestConfiguration('xp', 'x86', 'release'), TestConfiguration('xp', 'x86', 'debug')])
609         self.assertEqual(expectation_line.to_string(self._converter), 'BUGX XP : test/name/for/realz.html = IMAGE')
610
611     def test_serialize_parsed_expectations(self):
612         expectation_line = TestExpectationLine()
613         expectation_line.parsed_expectations = set([])
614         parsed_expectation_to_string = dict([[parsed_expectation, expectation_string] for expectation_string, parsed_expectation in TestExpectations.EXPECTATIONS.items()])
615         self.assertEqual(expectation_line._serialize_parsed_expectations(parsed_expectation_to_string), '')
616         expectation_line.parsed_expectations = set([IMAGE_PLUS_TEXT])
617         self.assertEqual(expectation_line._serialize_parsed_expectations(parsed_expectation_to_string), 'image+text')
618         expectation_line.parsed_expectations = set([PASS, IMAGE])
619         self.assertEqual(expectation_line._serialize_parsed_expectations(parsed_expectation_to_string), 'pass image')
620         expectation_line.parsed_expectations = set([TEXT, PASS])
621         self.assertEqual(expectation_line._serialize_parsed_expectations(parsed_expectation_to_string), 'pass text')
622
623     def test_serialize_parsed_modifier_string(self):
624         expectation_line = TestExpectationLine()
625         expectation_line.parsed_bug_modifiers = ['garden-o-matic']
626         expectation_line.parsed_modifiers = ['for', 'the']
627         self.assertEqual(expectation_line._serialize_parsed_modifiers(self._converter, []), 'garden-o-matic for the')
628         self.assertEqual(expectation_line._serialize_parsed_modifiers(self._converter, ['win']), 'garden-o-matic for the win')
629         expectation_line.parsed_bug_modifiers = []
630         expectation_line.parsed_modifiers = []
631         self.assertEqual(expectation_line._serialize_parsed_modifiers(self._converter, []), '')
632         self.assertEqual(expectation_line._serialize_parsed_modifiers(self._converter, ['win']), 'win')
633         expectation_line.parsed_bug_modifiers = ['garden-o-matic', 'total', 'is']
634         self.assertEqual(expectation_line._serialize_parsed_modifiers(self._converter, ['win']), 'garden-o-matic is total win')
635         expectation_line.parsed_bug_modifiers = []
636         expectation_line.parsed_modifiers = ['garden-o-matic', 'total', 'is']
637         self.assertEqual(expectation_line._serialize_parsed_modifiers(self._converter, ['win']), 'garden-o-matic is total win')
638
639     def test_format_line(self):
640         self.assertEqual(TestExpectationLine._format_line('modifiers', 'name', 'expectations', 'comment'), 'MODIFIERS : name = EXPECTATIONS //comment')
641         self.assertEqual(TestExpectationLine._format_line('modifiers', 'name', 'expectations', None), 'MODIFIERS : name = EXPECTATIONS')
642
643     def test_string_roundtrip(self):
644         self.assert_round_trip('')
645         self.assert_round_trip('FOO')
646         self.assert_round_trip(':')
647         self.assert_round_trip('FOO :')
648         self.assert_round_trip('FOO : bar')
649         self.assert_round_trip('  FOO :')
650         self.assert_round_trip('    FOO : bar')
651         self.assert_round_trip('FOO : bar = BAZ')
652         self.assert_round_trip('FOO : bar = BAZ //Qux.')
653         self.assert_round_trip('FOO : bar = BAZ // Qux.')
654         self.assert_round_trip('FOO : bar = BAZ // Qux.     ')
655         self.assert_round_trip('FOO : bar = BAZ //        Qux.     ')
656         self.assert_round_trip('FOO : : bar = BAZ')
657         self.assert_round_trip('FOO : : bar = BAZ')
658         self.assert_round_trip('FOO : : bar ==== BAZ')
659         self.assert_round_trip('=')
660         self.assert_round_trip('//')
661         self.assert_round_trip('// ')
662         self.assert_round_trip('// Foo')
663         self.assert_round_trip('// Foo')
664         self.assert_round_trip('// Foo :')
665         self.assert_round_trip('// Foo : =')
666
667     def test_list_roundtrip(self):
668         self.assert_list_round_trip('')
669         self.assert_list_round_trip('\n')
670         self.assert_list_round_trip('\n\n')
671         self.assert_list_round_trip('bar')
672         self.assert_list_round_trip('bar\n//Qux.')
673         self.assert_list_round_trip('bar\n//Qux.\n')
674
675     def test_reconstitute_only_these(self):
676         lines = []
677         reconstitute_only_these = []
678
679         def add_line(matching_configurations, reconstitute):
680             expectation_line = TestExpectationLine()
681             expectation_line.original_string = "Nay"
682             expectation_line.parsed_bug_modifiers = ['BUGX']
683             expectation_line.name = 'Yay'
684             expectation_line.parsed_expectations = set([IMAGE])
685             expectation_line.matching_configurations = matching_configurations
686             lines.append(expectation_line)
687             if reconstitute:
688                 reconstitute_only_these.append(expectation_line)
689
690         add_line(set([TestConfiguration('xp', 'x86', 'release')]), True)
691         add_line(set([TestConfiguration('xp', 'x86', 'release'), TestConfiguration('xp', 'x86', 'debug')]), False)
692         serialized = TestExpectations.list_to_string(lines, self._converter)
693         self.assertEquals(serialized, "BUGX XP RELEASE : Yay = IMAGE\nBUGX XP : Yay = IMAGE")
694         serialized = TestExpectations.list_to_string(lines, self._converter, reconstitute_only_these=reconstitute_only_these)
695         self.assertEquals(serialized, "BUGX XP RELEASE : Yay = IMAGE\nNay")
696
697     def test_string_whitespace_stripping(self):
698         self.assert_round_trip('\n', '')
699         self.assert_round_trip('  FOO : bar = BAZ', 'FOO : bar = BAZ')
700         self.assert_round_trip('FOO    : bar = BAZ', 'FOO : bar = BAZ')
701         self.assert_round_trip('FOO : bar = BAZ       // Qux.', 'FOO : bar = BAZ // Qux.')
702         self.assert_round_trip('FOO : bar =        BAZ // Qux.', 'FOO : bar = BAZ // Qux.')
703         self.assert_round_trip('FOO :       bar =    BAZ // Qux.', 'FOO : bar = BAZ // Qux.')
704         self.assert_round_trip('FOO :       bar     =    BAZ // Qux.', 'FOO : bar = BAZ // Qux.')
705
706
707 if __name__ == '__main__':
708     unittest.main()