Home | History | Annotate | Download | only in models
      1 # Copyright (C) 2010 Google Inc. All rights reserved.
      2 #
      3 # Redistribution and use in source and binary forms, with or without
      4 # modification, are permitted provided that the following conditions are
      5 # met:
      6 #
      7 #     * Redistributions of source code must retain the above copyright
      8 # notice, this list of conditions and the following disclaimer.
      9 #     * Redistributions in binary form must reproduce the above
     10 # copyright notice, this list of conditions and the following disclaimer
     11 # in the documentation and/or other materials provided with the
     12 # distribution.
     13 #     * Neither the name of Google Inc. nor the names of its
     14 # contributors may be used to endorse or promote products derived from
     15 # this software without specific prior written permission.
     16 #
     17 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     18 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     19 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     20 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     21 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     22 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     23 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     24 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     25 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     26 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     27 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     28 
     29 import webkitpy.thirdparty.unittest2 as unittest
     30 
     31 from webkitpy.common.host_mock import MockHost
     32 from webkitpy.common.system.outputcapture import OutputCapture
     33 
     34 from webkitpy.layout_tests.models.test_configuration import *
     35 from webkitpy.layout_tests.models.test_expectations import *
     36 
     37 try:
     38     from collections import OrderedDict
     39 except ImportError:
     40     # Needed for Python < 2.7
     41     from webkitpy.thirdparty.ordered_dict import OrderedDict
     42 
     43 
     44 class Base(unittest.TestCase):
     45     # Note that all of these tests are written assuming the configuration
     46     # being tested is Windows XP, Release build.
     47 
     48     def __init__(self, testFunc):
     49         host = MockHost()
     50         self._port = host.port_factory.get('test-win-xp', None)
     51         self._exp = None
     52         unittest.TestCase.__init__(self, testFunc)
     53 
     54     def get_test(self, test_name):
     55         # FIXME: Remove this routine and just reference test names directly.
     56         return test_name
     57 
     58     def get_basic_tests(self):
     59         return [self.get_test('failures/expected/text.html'),
     60                 self.get_test('failures/expected/image_checksum.html'),
     61                 self.get_test('failures/expected/crash.html'),
     62                 self.get_test('failures/expected/needsrebaseline.html'),
     63                 self.get_test('failures/expected/needsmanualrebaseline.html'),
     64                 self.get_test('failures/expected/missing_text.html'),
     65                 self.get_test('failures/expected/image.html'),
     66                 self.get_test('failures/expected/timeout.html'),
     67                 self.get_test('passes/text.html')]
     68 
     69 
     70     def get_basic_expectations(self):
     71         return """
     72 Bug(test) failures/expected/text.html [ Failure ]
     73 Bug(test) failures/expected/crash.html [ WontFix ]
     74 Bug(test) failures/expected/needsrebaseline.html [ NeedsRebaseline ]
     75 Bug(test) failures/expected/needsmanualrebaseline.html [ NeedsManualRebaseline ]
     76 Bug(test) failures/expected/missing_image.html [ Rebaseline Missing ]
     77 Bug(test) failures/expected/image_checksum.html [ WontFix ]
     78 Bug(test) failures/expected/image.html [ WontFix Mac ]
     79 """
     80 
     81     def parse_exp(self, expectations, overrides=None, is_lint_mode=False):
     82         expectations_dict = OrderedDict()
     83         expectations_dict['expectations'] = expectations
     84         if overrides:
     85             expectations_dict['overrides'] = overrides
     86         self._port.expectations_dict = lambda: expectations_dict
     87         expectations_to_lint = expectations_dict if is_lint_mode else None
     88         self._exp = TestExpectations(self._port, self.get_basic_tests(), expectations_dict=expectations_to_lint, is_lint_mode=is_lint_mode)
     89 
     90     def assert_exp_list(self, test, results):
     91         self.assertEqual(self._exp.get_expectations(self.get_test(test)), set(results))
     92 
     93     def assert_exp(self, test, result):
     94         self.assert_exp_list(test, [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 
    100 class BasicTests(Base):
    101     def test_basic(self):
    102         self.parse_exp(self.get_basic_expectations())
    103         self.assert_exp('failures/expected/text.html', FAIL)
    104         self.assert_exp_list('failures/expected/image_checksum.html', [WONTFIX, SKIP])
    105         self.assert_exp('passes/text.html', PASS)
    106         self.assert_exp('failures/expected/image.html', PASS)
    107 
    108 
    109 class MiscTests(Base):
    110     def test_multiple_results(self):
    111         self.parse_exp('Bug(x) failures/expected/text.html [ Crash Failure ]')
    112         self.assertEqual(self._exp.get_expectations(
    113             self.get_test('failures/expected/text.html')),
    114             set([FAIL, CRASH]))
    115 
    116     def test_result_was_expected(self):
    117         # test basics
    118         self.assertEqual(TestExpectations.result_was_expected(PASS, set([PASS]), test_needs_rebaselining=False), True)
    119         self.assertEqual(TestExpectations.result_was_expected(FAIL, set([PASS]), test_needs_rebaselining=False), False)
    120 
    121         # test handling of SKIPped tests and results
    122         self.assertEqual(TestExpectations.result_was_expected(SKIP, set([CRASH]), test_needs_rebaselining=False), True)
    123         self.assertEqual(TestExpectations.result_was_expected(SKIP, set([LEAK]), test_needs_rebaselining=False), True)
    124 
    125         # test handling of MISSING results and the REBASELINE specifier
    126         self.assertEqual(TestExpectations.result_was_expected(MISSING, set([PASS]), test_needs_rebaselining=True), True)
    127         self.assertEqual(TestExpectations.result_was_expected(MISSING, set([PASS]), test_needs_rebaselining=False), False)
    128 
    129         self.assertTrue(TestExpectations.result_was_expected(PASS, set([NEEDS_REBASELINE]), test_needs_rebaselining=False))
    130         self.assertTrue(TestExpectations.result_was_expected(MISSING, set([NEEDS_REBASELINE]), test_needs_rebaselining=False))
    131         self.assertTrue(TestExpectations.result_was_expected(TEXT, set([NEEDS_REBASELINE]), test_needs_rebaselining=False))
    132         self.assertTrue(TestExpectations.result_was_expected(IMAGE, set([NEEDS_REBASELINE]), test_needs_rebaselining=False))
    133         self.assertTrue(TestExpectations.result_was_expected(IMAGE_PLUS_TEXT, set([NEEDS_REBASELINE]), test_needs_rebaselining=False))
    134         self.assertTrue(TestExpectations.result_was_expected(AUDIO, set([NEEDS_REBASELINE]), test_needs_rebaselining=False))
    135         self.assertFalse(TestExpectations.result_was_expected(TIMEOUT, set([NEEDS_REBASELINE]), test_needs_rebaselining=False))
    136         self.assertFalse(TestExpectations.result_was_expected(CRASH, set([NEEDS_REBASELINE]), test_needs_rebaselining=False))
    137         self.assertFalse(TestExpectations.result_was_expected(LEAK, set([NEEDS_REBASELINE]), test_needs_rebaselining=False))
    138 
    139     def test_remove_pixel_failures(self):
    140         self.assertEqual(TestExpectations.remove_pixel_failures(set([FAIL])), set([FAIL]))
    141         self.assertEqual(TestExpectations.remove_pixel_failures(set([PASS])), set([PASS]))
    142         self.assertEqual(TestExpectations.remove_pixel_failures(set([IMAGE])), set([PASS]))
    143         self.assertEqual(TestExpectations.remove_pixel_failures(set([FAIL])), set([FAIL]))
    144         self.assertEqual(TestExpectations.remove_pixel_failures(set([PASS, IMAGE, CRASH])), set([PASS, CRASH]))
    145 
    146     def test_suffixes_for_expectations(self):
    147         self.assertEqual(TestExpectations.suffixes_for_expectations(set([FAIL])), set(['txt', 'png', 'wav']))
    148         self.assertEqual(TestExpectations.suffixes_for_expectations(set([IMAGE])), set(['png']))
    149         self.assertEqual(TestExpectations.suffixes_for_expectations(set([FAIL, IMAGE, CRASH])), set(['txt', 'png', 'wav']))
    150         self.assertEqual(TestExpectations.suffixes_for_expectations(set()), set())
    151 
    152     def test_category_expectations(self):
    153         # This test checks unknown tests are not present in the
    154         # expectations and that known test part of a test category is
    155         # present in the expectations.
    156         exp_str = 'Bug(x) failures/expected [ WontFix ]'
    157         self.parse_exp(exp_str)
    158         test_name = 'failures/expected/unknown-test.html'
    159         unknown_test = self.get_test(test_name)
    160         self.assertRaises(KeyError, self._exp.get_expectations,
    161                           unknown_test)
    162         self.assert_exp_list('failures/expected/crash.html', [WONTFIX, SKIP])
    163 
    164     def test_get_expectations_string(self):
    165         self.parse_exp(self.get_basic_expectations())
    166         self.assertEqual(self._exp.get_expectations_string(
    167                           self.get_test('failures/expected/text.html')),
    168                           'FAIL')
    169 
    170     def test_expectation_to_string(self):
    171         # Normal cases are handled by other tests.
    172         self.parse_exp(self.get_basic_expectations())
    173         self.assertRaises(ValueError, self._exp.expectation_to_string,
    174                           -1)
    175 
    176     def test_get_test_set(self):
    177         # Handle some corner cases for this routine not covered by other tests.
    178         self.parse_exp(self.get_basic_expectations())
    179         s = self._exp.get_test_set(WONTFIX)
    180         self.assertEqual(s,
    181             set([self.get_test('failures/expected/crash.html'),
    182                  self.get_test('failures/expected/image_checksum.html')]))
    183 
    184     def test_needs_rebaseline_reftest(self):
    185         try:
    186             filesystem = self._port.host.filesystem
    187             filesystem.write_text_file(filesystem.join(self._port.layout_tests_dir(), 'failures/expected/needsrebaseline.html'), 'content')
    188             filesystem.write_text_file(filesystem.join(self._port.layout_tests_dir(), 'failures/expected/needsrebaseline-expected.html'), 'content')
    189             filesystem.write_text_file(filesystem.join(self._port.layout_tests_dir(), 'failures/expected/needsmanualrebaseline.html'), 'content')
    190             filesystem.write_text_file(filesystem.join(self._port.layout_tests_dir(), 'failures/expected/needsmanualrebaseline-expected.html'), 'content')
    191             self.parse_exp("""Bug(user) failures/expected/needsrebaseline.html [ NeedsRebaseline ]
    192 Bug(user) failures/expected/needsmanualrebaseline.html [ NeedsManualRebaseline ]""", is_lint_mode=True)
    193             self.assertFalse(True, "ParseError wasn't raised")
    194         except ParseError, e:
    195             warnings = """expectations:1 A reftest cannot be marked as NeedsRebaseline/NeedsManualRebaseline failures/expected/needsrebaseline.html
    196 expectations:2 A reftest cannot be marked as NeedsRebaseline/NeedsManualRebaseline failures/expected/needsmanualrebaseline.html"""
    197             self.assertEqual(str(e), warnings)
    198 
    199     def test_parse_warning(self):
    200         try:
    201             filesystem = self._port.host.filesystem
    202             filesystem.write_text_file(filesystem.join(self._port.layout_tests_dir(), 'disabled-test.html-disabled'), 'content')
    203             self.get_test('disabled-test.html-disabled'),
    204             self.parse_exp("Bug(user) [ FOO ] failures/expected/text.html [ Failure ]\n"
    205                 "Bug(user) non-existent-test.html [ Failure ]\n"
    206                 "Bug(user) disabled-test.html-disabled [ ImageOnlyFailure ]", is_lint_mode=True)
    207             self.assertFalse(True, "ParseError wasn't raised")
    208         except ParseError, e:
    209             warnings = ("expectations:1 Unrecognized specifier 'foo' failures/expected/text.html\n"
    210                         "expectations:2 Path does not exist. non-existent-test.html")
    211             self.assertEqual(str(e), warnings)
    212 
    213     def test_parse_warnings_are_logged_if_not_in_lint_mode(self):
    214         oc = OutputCapture()
    215         try:
    216             oc.capture_output()
    217             self.parse_exp('-- this should be a syntax error', is_lint_mode=False)
    218         finally:
    219             _, _, logs = oc.restore_output()
    220             self.assertNotEquals(logs, '')
    221 
    222     def test_error_on_different_platform(self):
    223         # parse_exp uses a Windows port. Assert errors on Mac show up in lint mode.
    224         self.assertRaises(ParseError, self.parse_exp,
    225             'Bug(test) [ Mac ] failures/expected/text.html [ Failure ]\nBug(test) [ Mac ] failures/expected/text.html [ Failure ]',
    226             is_lint_mode=True)
    227 
    228     def test_error_on_different_build_type(self):
    229         # parse_exp uses a Release port. Assert errors on DEBUG show up in lint mode.
    230         self.assertRaises(ParseError, self.parse_exp,
    231             'Bug(test) [ Debug ] failures/expected/text.html [ Failure ]\nBug(test) [ Debug ] failures/expected/text.html [ Failure ]',
    232             is_lint_mode=True)
    233 
    234     def test_overrides(self):
    235         self.parse_exp("Bug(exp) failures/expected/text.html [ Failure ]",
    236                        "Bug(override) failures/expected/text.html [ ImageOnlyFailure ]")
    237         self.assert_exp_list('failures/expected/text.html', [FAIL, IMAGE])
    238 
    239     def test_overrides__directory(self):
    240         self.parse_exp("Bug(exp) failures/expected/text.html [ Failure ]",
    241                        "Bug(override) failures/expected [ Crash ]")
    242         self.assert_exp_list('failures/expected/text.html', [FAIL, CRASH])
    243         self.assert_exp_list('failures/expected/image.html', [CRASH])
    244 
    245     def test_overrides__duplicate(self):
    246         self.assert_bad_expectations("Bug(exp) failures/expected/text.html [ Failure ]",
    247                                      "Bug(override) failures/expected/text.html [ ImageOnlyFailure ]\n"
    248                                      "Bug(override) failures/expected/text.html [ Crash ]\n")
    249 
    250     def test_pixel_tests_flag(self):
    251         def match(test, result, pixel_tests_enabled):
    252             return self._exp.matches_an_expected_result(
    253                 self.get_test(test), result, pixel_tests_enabled, sanitizer_is_enabled=False)
    254 
    255         self.parse_exp(self.get_basic_expectations())
    256         self.assertTrue(match('failures/expected/text.html', FAIL, True))
    257         self.assertTrue(match('failures/expected/text.html', FAIL, False))
    258         self.assertFalse(match('failures/expected/text.html', CRASH, True))
    259         self.assertFalse(match('failures/expected/text.html', CRASH, False))
    260         self.assertTrue(match('failures/expected/image_checksum.html', PASS, True))
    261         self.assertTrue(match('failures/expected/image_checksum.html', PASS, False))
    262         self.assertTrue(match('failures/expected/crash.html', PASS, False))
    263         self.assertTrue(match('failures/expected/needsrebaseline.html', TEXT, True))
    264         self.assertFalse(match('failures/expected/needsrebaseline.html', CRASH, True))
    265         self.assertTrue(match('failures/expected/needsmanualrebaseline.html', TEXT, True))
    266         self.assertFalse(match('failures/expected/needsmanualrebaseline.html', CRASH, True))
    267         self.assertTrue(match('passes/text.html', PASS, False))
    268 
    269     def test_sanitizer_flag(self):
    270         def match(test, result):
    271             return self._exp.matches_an_expected_result(
    272                 self.get_test(test), result, pixel_tests_are_enabled=False, sanitizer_is_enabled=True)
    273 
    274         self.parse_exp("""
    275 Bug(test) failures/expected/crash.html [ Crash ]
    276 Bug(test) failures/expected/image.html [ ImageOnlyFailure ]
    277 Bug(test) failures/expected/text.html [ Failure ]
    278 Bug(test) failures/expected/timeout.html [ Timeout ]
    279 """)
    280         self.assertTrue(match('failures/expected/crash.html', CRASH))
    281         self.assertTrue(match('failures/expected/image.html', PASS))
    282         self.assertTrue(match('failures/expected/text.html', PASS))
    283         self.assertTrue(match('failures/expected/timeout.html', TIMEOUT))
    284 
    285     def test_more_specific_override_resets_skip(self):
    286         self.parse_exp("Bug(x) failures/expected [ Skip ]\n"
    287                        "Bug(x) failures/expected/text.html [ ImageOnlyFailure ]\n")
    288         self.assert_exp('failures/expected/text.html', IMAGE)
    289         self.assertFalse(self._port._filesystem.join(self._port.layout_tests_dir(),
    290                                                      'failures/expected/text.html') in
    291                          self._exp.get_tests_with_result_type(SKIP))
    292 
    293     def test_bot_test_expectations(self):
    294         """Test that expectations are merged rather than overridden when using flaky option 'unexpected'."""
    295         test_name1 = 'failures/expected/text.html'
    296         test_name2 = 'passes/text.html'
    297 
    298         expectations_dict = OrderedDict()
    299         expectations_dict['expectations'] = "Bug(x) %s [ ImageOnlyFailure ]\nBug(x) %s [ Slow ]\n" % (test_name1, test_name2)
    300         self._port.expectations_dict = lambda: expectations_dict
    301 
    302         expectations = TestExpectations(self._port, self.get_basic_tests())
    303         self.assertEqual(expectations.get_expectations(self.get_test(test_name1)), set([IMAGE]))
    304         self.assertEqual(expectations.get_expectations(self.get_test(test_name2)), set([SLOW]))
    305 
    306         def bot_expectations():
    307             return {test_name1: ['PASS', 'TIMEOUT'], test_name2: ['CRASH']}
    308         self._port.bot_expectations = bot_expectations
    309         self._port._options.ignore_flaky_tests = 'unexpected'
    310 
    311         expectations = TestExpectations(self._port, self.get_basic_tests())
    312         self.assertEqual(expectations.get_expectations(self.get_test(test_name1)), set([PASS, IMAGE, TIMEOUT]))
    313         self.assertEqual(expectations.get_expectations(self.get_test(test_name2)), set([CRASH, SLOW]))
    314 
    315 class SkippedTests(Base):
    316     def check(self, expectations, overrides, skips, lint=False, expected_results=[WONTFIX, SKIP, FAIL]):
    317         port = MockHost().port_factory.get('test-win-xp')
    318         port._filesystem.write_text_file(port._filesystem.join(port.layout_tests_dir(), 'failures/expected/text.html'), 'foo')
    319         expectations_dict = OrderedDict()
    320         expectations_dict['expectations'] = expectations
    321         if overrides:
    322             expectations_dict['overrides'] = overrides
    323         port.expectations_dict = lambda: expectations_dict
    324         port.skipped_layout_tests = lambda tests: set(skips)
    325         expectations_to_lint = expectations_dict if lint else None
    326         exp = TestExpectations(port, ['failures/expected/text.html'], expectations_dict=expectations_to_lint, is_lint_mode=lint)
    327         self.assertEqual(exp.get_expectations('failures/expected/text.html'), set(expected_results))
    328 
    329     def test_skipped_tests_work(self):
    330         self.check(expectations='', overrides=None, skips=['failures/expected/text.html'], expected_results=[WONTFIX, SKIP])
    331 
    332     def test_duplicate_skipped_test_fails_lint(self):
    333         self.assertRaises(ParseError, self.check, expectations='Bug(x) failures/expected/text.html [ Failure ]\n',
    334             overrides=None, skips=['failures/expected/text.html'], lint=True)
    335 
    336     def test_skipped_file_overrides_expectations(self):
    337         self.check(expectations='Bug(x) failures/expected/text.html [ Failure ]\n', overrides=None,
    338                    skips=['failures/expected/text.html'])
    339 
    340     def test_skipped_dir_overrides_expectations(self):
    341         self.check(expectations='Bug(x) failures/expected/text.html [ Failure ]\n', overrides=None,
    342                    skips=['failures/expected'])
    343 
    344     def test_skipped_file_overrides_overrides(self):
    345         self.check(expectations='', overrides='Bug(x) failures/expected/text.html [ Failure ]\n',
    346                    skips=['failures/expected/text.html'])
    347 
    348     def test_skipped_dir_overrides_overrides(self):
    349         self.check(expectations='', overrides='Bug(x) failures/expected/text.html [ Failure ]\n',
    350                    skips=['failures/expected'])
    351 
    352     def test_skipped_entry_dont_exist(self):
    353         port = MockHost().port_factory.get('test-win-xp')
    354         expectations_dict = OrderedDict()
    355         expectations_dict['expectations'] = ''
    356         port.expectations_dict = lambda: expectations_dict
    357         port.skipped_layout_tests = lambda tests: set(['foo/bar/baz.html'])
    358         capture = OutputCapture()
    359         capture.capture_output()
    360         exp = TestExpectations(port)
    361         _, _, logs = capture.restore_output()
    362         self.assertEqual('The following test foo/bar/baz.html from the Skipped list doesn\'t exist\n', logs)
    363 
    364     def test_expectations_string(self):
    365         self.parse_exp(self.get_basic_expectations())
    366         notrun = 'failures/expected/text.html'
    367         self._exp.add_extra_skipped_tests([notrun])
    368         self.assertEqual('NOTRUN', self._exp.get_expectations_string(notrun))
    369 
    370 
    371 class ExpectationSyntaxTests(Base):
    372     def test_unrecognized_expectation(self):
    373         self.assert_bad_expectations('Bug(test) failures/expected/text.html [ Unknown ]')
    374 
    375     def test_macro(self):
    376         exp_str = 'Bug(test) [ Win ] failures/expected/text.html [ Failure ]'
    377         self.parse_exp(exp_str)
    378         self.assert_exp('failures/expected/text.html', FAIL)
    379 
    380     def assert_tokenize_exp(self, line, bugs=None, specifiers=None, expectations=None, warnings=None, comment=None, name='foo.html'):
    381         bugs = bugs or []
    382         specifiers = specifiers or []
    383         expectations = expectations or []
    384         warnings = warnings or []
    385         filename = 'TestExpectations'
    386         line_number = '1'
    387         expectation_line = TestExpectationParser._tokenize_line(filename, line, line_number)
    388         self.assertEqual(expectation_line.warnings, warnings)
    389         self.assertEqual(expectation_line.name, name)
    390         self.assertEqual(expectation_line.filename, filename)
    391         self.assertEqual(expectation_line.line_numbers, line_number)
    392         if not warnings:
    393             self.assertEqual(expectation_line.specifiers, specifiers)
    394             self.assertEqual(expectation_line.expectations, expectations)
    395 
    396     def test_comments(self):
    397         self.assert_tokenize_exp("# comment", name=None, comment="# comment")
    398         self.assert_tokenize_exp("foo.html [ Pass ] # comment", comment="# comment", expectations=['PASS'], specifiers=[])
    399 
    400     def test_config_specifiers(self):
    401         self.assert_tokenize_exp('[ Mac ] foo.html [ Failure ] ', specifiers=['MAC'], expectations=['FAIL'])
    402 
    403     def test_unknown_config(self):
    404         self.assert_tokenize_exp('[ Foo ] foo.html [ Pass ]', specifiers=['Foo'], expectations=['PASS'])
    405 
    406     def test_unknown_expectation(self):
    407         self.assert_tokenize_exp('foo.html [ Audio ]', warnings=['Unrecognized expectation "Audio"'])
    408 
    409     def test_skip(self):
    410         self.assert_tokenize_exp('foo.html [ Skip ]', specifiers=[], expectations=['SKIP'])
    411 
    412     def test_slow(self):
    413         self.assert_tokenize_exp('foo.html [ Slow ]', specifiers=[], expectations=['SLOW'])
    414 
    415     def test_wontfix(self):
    416         self.assert_tokenize_exp('foo.html [ WontFix ]', specifiers=[], expectations=['WONTFIX', 'SKIP'])
    417         self.assert_tokenize_exp('foo.html [ WontFix ImageOnlyFailure ]', specifiers=[], expectations=['WONTFIX', 'SKIP'],
    418             warnings=['A test marked Skip or WontFix must not have other expectations.'])
    419 
    420     def test_blank_line(self):
    421         self.assert_tokenize_exp('', name=None)
    422 
    423     def test_warnings(self):
    424         self.assert_tokenize_exp('[ Mac ]', warnings=['Did not find a test name.', 'Missing expectations.'], name=None)
    425         self.assert_tokenize_exp('[ [', warnings=['unexpected "["', 'Missing expectations.'], name=None)
    426         self.assert_tokenize_exp('crbug.com/12345 ]', warnings=['unexpected "]"', 'Missing expectations.'], name=None)
    427 
    428         self.assert_tokenize_exp('foo.html crbug.com/12345 ]', warnings=['"crbug.com/12345" is not at the start of the line.', 'Missing expectations.'])
    429         self.assert_tokenize_exp('foo.html', warnings=['Missing expectations.'])
    430 
    431 
    432 class SemanticTests(Base):
    433     def test_bug_format(self):
    434         self.assertRaises(ParseError, self.parse_exp, 'BUG1234 failures/expected/text.html [ Failure ]', is_lint_mode=True)
    435 
    436     def test_bad_bugid(self):
    437         try:
    438             self.parse_exp('crbug/1234 failures/expected/text.html [ Failure ]', is_lint_mode=True)
    439             self.fail('should have raised an error about a bad bug identifier')
    440         except ParseError, exp:
    441             self.assertEqual(len(exp.warnings), 3)
    442 
    443     def test_missing_bugid(self):
    444         self.parse_exp('failures/expected/text.html [ Failure ]', is_lint_mode=False)
    445         self.assertFalse(self._exp.has_warnings())
    446 
    447         try:
    448             self.parse_exp('failures/expected/text.html [ Failure ]', is_lint_mode=True)
    449         except ParseError, exp:
    450             self.assertEqual(exp.warnings, ['expectations:1 Test lacks BUG specifier. failures/expected/text.html'])
    451 
    452     def test_skip_and_wontfix(self):
    453         # Skip is not allowed to have other expectations as well, because those
    454         # expectations won't be exercised and may become stale .
    455         self.parse_exp('failures/expected/text.html [ Failure Skip ]')
    456         self.assertTrue(self._exp.has_warnings())
    457 
    458         self.parse_exp('failures/expected/text.html [ Crash WontFix ]')
    459         self.assertTrue(self._exp.has_warnings())
    460 
    461         self.parse_exp('failures/expected/text.html [ Pass WontFix ]')
    462         self.assertTrue(self._exp.has_warnings())
    463 
    464     def test_rebaseline(self):
    465         # Can't lint a file w/ 'REBASELINE' in it.
    466         self.assertRaises(ParseError, self.parse_exp,
    467             'Bug(test) failures/expected/text.html [ Failure Rebaseline ]',
    468             is_lint_mode=True)
    469 
    470     def test_duplicates(self):
    471         self.assertRaises(ParseError, self.parse_exp, """
    472 Bug(exp) failures/expected/text.html [ Failure ]
    473 Bug(exp) failures/expected/text.html [ ImageOnlyFailure ]""", is_lint_mode=True)
    474 
    475         self.assertRaises(ParseError, self.parse_exp,
    476             self.get_basic_expectations(), overrides="""
    477 Bug(override) failures/expected/text.html [ Failure ]
    478 Bug(override) failures/expected/text.html [ ImageOnlyFailure ]""", is_lint_mode=True)
    479 
    480     def test_duplicate_with_line_before_preceding_line(self):
    481         self.assert_bad_expectations("""Bug(exp) [ Debug ] failures/expected/text.html [ Failure ]
    482 Bug(exp) [ Release ] failures/expected/text.html [ Failure ]
    483 Bug(exp) [ Debug ] failures/expected/text.html [ Failure ]
    484 """)
    485 
    486     def test_missing_file(self):
    487         self.parse_exp('Bug(test) missing_file.html [ Failure ]')
    488         self.assertTrue(self._exp.has_warnings(), 1)
    489 
    490 
    491 class PrecedenceTests(Base):
    492     def test_file_over_directory(self):
    493         # This tests handling precedence of specific lines over directories
    494         # and tests expectations covering entire directories.
    495         exp_str = """
    496 Bug(x) failures/expected/text.html [ Failure ]
    497 Bug(y) failures/expected [ WontFix ]
    498 """
    499         self.parse_exp(exp_str)
    500         self.assert_exp('failures/expected/text.html', FAIL)
    501         self.assert_exp_list('failures/expected/crash.html', [WONTFIX, SKIP])
    502 
    503         exp_str = """
    504 Bug(x) failures/expected [ WontFix ]
    505 Bug(y) failures/expected/text.html [ Failure ]
    506 """
    507         self.parse_exp(exp_str)
    508         self.assert_exp('failures/expected/text.html', FAIL)
    509         self.assert_exp_list('failures/expected/crash.html', [WONTFIX, SKIP])
    510 
    511     def test_ambiguous(self):
    512         self.assert_bad_expectations("Bug(test) [ Release ] passes/text.html [ Pass ]\n"
    513                                      "Bug(test) [ Win ] passes/text.html [ Failure ]\n")
    514 
    515     def test_more_specifiers(self):
    516         self.assert_bad_expectations("Bug(test) [ Release ] passes/text.html [ Pass ]\n"
    517                                      "Bug(test) [ Win Release ] passes/text.html [ Failure ]\n")
    518 
    519     def test_order_in_file(self):
    520         self.assert_bad_expectations("Bug(test) [ Win Release ] : passes/text.html [ Failure ]\n"
    521                                      "Bug(test) [ Release ] : passes/text.html [ Pass ]\n")
    522 
    523     def test_macro_overrides(self):
    524         self.assert_bad_expectations("Bug(test) [ Win ] passes/text.html [ Pass ]\n"
    525                                      "Bug(test) [ XP ] passes/text.html [ Failure ]\n")
    526 
    527 
    528 class RemoveConfigurationsTest(Base):
    529     def test_remove(self):
    530         host = MockHost()
    531         test_port = host.port_factory.get('test-win-xp', None)
    532         test_port.test_exists = lambda test: True
    533         test_port.test_isfile = lambda test: True
    534 
    535         test_config = test_port.test_configuration()
    536         test_port.expectations_dict = lambda: {"expectations": """Bug(x) [ Linux Win Release ] failures/expected/foo.html [ Failure ]
    537 Bug(y) [ Win Mac Debug ] failures/expected/foo.html [ Crash ]
    538 """}
    539         expectations = TestExpectations(test_port, self.get_basic_tests())
    540 
    541         actual_expectations = expectations.remove_configurations([('failures/expected/foo.html', test_config)])
    542 
    543         self.assertEqual("""Bug(x) [ Linux Win7 Release ] failures/expected/foo.html [ Failure ]
    544 Bug(y) [ Win Mac Debug ] failures/expected/foo.html [ Crash ]
    545 """, actual_expectations)
    546 
    547     def test_remove_needs_rebaseline(self):
    548         host = MockHost()
    549         test_port = host.port_factory.get('test-win-xp', None)
    550         test_port.test_exists = lambda test: True
    551         test_port.test_isfile = lambda test: True
    552 
    553         test_config = test_port.test_configuration()
    554         test_port.expectations_dict = lambda: {"expectations": """Bug(x) [ Win ] failures/expected/foo.html [ NeedsRebaseline ]
    555 """}
    556         expectations = TestExpectations(test_port, self.get_basic_tests())
    557 
    558         actual_expectations = expectations.remove_configurations([('failures/expected/foo.html', test_config)])
    559 
    560         self.assertEqual("""Bug(x) [ XP Debug ] failures/expected/foo.html [ NeedsRebaseline ]
    561 Bug(x) [ Win7 ] failures/expected/foo.html [ NeedsRebaseline ]
    562 """, actual_expectations)
    563 
    564     def test_remove_multiple_configurations(self):
    565         host = MockHost()
    566         test_port = host.port_factory.get('test-win-xp', None)
    567         test_port.test_exists = lambda test: True
    568         test_port.test_isfile = lambda test: True
    569 
    570         test_config = test_port.test_configuration()
    571         test_port.expectations_dict = lambda: {'expectations': """Bug(y) [ Win Debug ] failures/expected/foo.html [ Crash ]
    572 Bug(x) [ Win Release ] failures/expected/foo.html [ Failure ]
    573 """}
    574         expectations = TestExpectations(test_port)
    575 
    576         actual_expectations = expectations.remove_configurations([
    577             ('failures/expected/foo.html', test_config),
    578             ('failures/expected/foo.html', host.port_factory.get('test-win-win7', None).test_configuration()),
    579         ])
    580 
    581         self.assertEqual("""Bug(y) [ Win Debug ] failures/expected/foo.html [ Crash ]
    582 """, actual_expectations)
    583 
    584     def test_remove_line_with_comments(self):
    585         host = MockHost()
    586         test_port = host.port_factory.get('test-win-xp', None)
    587         test_port.test_exists = lambda test: True
    588         test_port.test_isfile = lambda test: True
    589 
    590         test_config = test_port.test_configuration()
    591         test_port.expectations_dict = lambda: {'expectations': """Bug(y) [ Win Debug ] failures/expected/foo.html [ Crash ]
    592 
    593  # This comment line should get stripped. As should the preceding line.
    594 Bug(x) [ Win Release ] failures/expected/foo.html [ Failure ]
    595 """}
    596         expectations = TestExpectations(test_port)
    597 
    598         actual_expectations = expectations.remove_configurations([('failures/expected/foo.html', test_config)])
    599         actual_expectations = expectations.remove_configurations([('failures/expected/foo.html', host.port_factory.get('test-win-win7', None).test_configuration())])
    600 
    601         self.assertEqual("""Bug(y) [ Win Debug ] failures/expected/foo.html [ Crash ]
    602 """, actual_expectations)
    603 
    604     def test_remove_line_with_comments_at_start(self):
    605         host = MockHost()
    606         test_port = host.port_factory.get('test-win-xp', None)
    607         test_port.test_exists = lambda test: True
    608         test_port.test_isfile = lambda test: True
    609 
    610         test_config = test_port.test_configuration()
    611         test_port.expectations_dict = lambda: {'expectations': """
    612  # This comment line should get stripped. As should the preceding line.
    613 Bug(x) [ Win Release ] failures/expected/foo.html [ Failure ]
    614 
    615 Bug(y) [ Win Debug ] failures/expected/foo.html [ Crash ]
    616 """}
    617         expectations = TestExpectations(test_port)
    618 
    619         actual_expectations = expectations.remove_configurations([('failures/expected/foo.html', test_config)])
    620         actual_expectations = expectations.remove_configurations([('failures/expected/foo.html', host.port_factory.get('test-win-win7', None).test_configuration())])
    621 
    622         self.assertEqual("""
    623 Bug(y) [ Win Debug ] failures/expected/foo.html [ Crash ]
    624 """, actual_expectations)
    625 
    626     def test_remove_line_with_comments_at_end_with_no_trailing_newline(self):
    627         host = MockHost()
    628         test_port = host.port_factory.get('test-win-xp', None)
    629         test_port.test_exists = lambda test: True
    630         test_port.test_isfile = lambda test: True
    631 
    632         test_config = test_port.test_configuration()
    633         test_port.expectations_dict = lambda: {'expectations': """Bug(y) [ Win Debug ] failures/expected/foo.html [ Crash ]
    634 
    635  # This comment line should get stripped. As should the preceding line.
    636 Bug(x) [ Win Release ] failures/expected/foo.html [ Failure ]"""}
    637         expectations = TestExpectations(test_port)
    638 
    639         actual_expectations = expectations.remove_configurations([('failures/expected/foo.html', test_config)])
    640         actual_expectations = expectations.remove_configurations([('failures/expected/foo.html', host.port_factory.get('test-win-win7', None).test_configuration())])
    641 
    642         self.assertEqual("""Bug(y) [ Win Debug ] failures/expected/foo.html [ Crash ]""", actual_expectations)
    643 
    644     def test_remove_line_leaves_comments_for_next_line(self):
    645         host = MockHost()
    646         test_port = host.port_factory.get('test-win-xp', None)
    647         test_port.test_exists = lambda test: True
    648         test_port.test_isfile = lambda test: True
    649 
    650         test_config = test_port.test_configuration()
    651         test_port.expectations_dict = lambda: {'expectations': """
    652  # This comment line should not get stripped.
    653 Bug(x) [ Win Release ] failures/expected/foo.html [ Failure ]
    654 Bug(y) [ Win Debug ] failures/expected/foo.html [ Crash ]
    655 """}
    656         expectations = TestExpectations(test_port)
    657 
    658         actual_expectations = expectations.remove_configurations([('failures/expected/foo.html', test_config)])
    659         actual_expectations = expectations.remove_configurations([('failures/expected/foo.html', host.port_factory.get('test-win-win7', None).test_configuration())])
    660 
    661         self.assertEqual("""
    662  # This comment line should not get stripped.
    663 Bug(y) [ Win Debug ] failures/expected/foo.html [ Crash ]
    664 """, actual_expectations)
    665 
    666     def test_remove_line_no_whitespace_lines(self):
    667         host = MockHost()
    668         test_port = host.port_factory.get('test-win-xp', None)
    669         test_port.test_exists = lambda test: True
    670         test_port.test_isfile = lambda test: True
    671 
    672         test_config = test_port.test_configuration()
    673         test_port.expectations_dict = lambda: {'expectations': """
    674  # This comment line should get stripped.
    675 Bug(x) [ Win Release ] failures/expected/foo.html [ Failure ]
    676  # This comment line should not get stripped.
    677 Bug(y) [ Win Debug ] failures/expected/foo.html [ Crash ]
    678 """}
    679         expectations = TestExpectations(test_port)
    680 
    681         actual_expectations = expectations.remove_configurations([('failures/expected/foo.html', test_config)])
    682         actual_expectations = expectations.remove_configurations([('failures/expected/foo.html', host.port_factory.get('test-win-win7', None).test_configuration())])
    683 
    684         self.assertEqual(""" # This comment line should not get stripped.
    685 Bug(y) [ Win Debug ] failures/expected/foo.html [ Crash ]
    686 """, actual_expectations)
    687 
    688     def test_remove_first_line(self):
    689         host = MockHost()
    690         test_port = host.port_factory.get('test-win-xp', None)
    691         test_port.test_exists = lambda test: True
    692         test_port.test_isfile = lambda test: True
    693 
    694         test_config = test_port.test_configuration()
    695         test_port.expectations_dict = lambda: {'expectations': """Bug(x) [ Win Release ] failures/expected/foo.html [ Failure ]
    696  # This comment line should not get stripped.
    697 Bug(y) [ Win Debug ] failures/expected/foo.html [ Crash ]
    698 """}
    699         expectations = TestExpectations(test_port)
    700 
    701         actual_expectations = expectations.remove_configurations([('failures/expected/foo.html', test_config)])
    702         actual_expectations = expectations.remove_configurations([('failures/expected/foo.html', host.port_factory.get('test-win-win7', None).test_configuration())])
    703 
    704         self.assertEqual(""" # This comment line should not get stripped.
    705 Bug(y) [ Win Debug ] failures/expected/foo.html [ Crash ]
    706 """, actual_expectations)
    707 
    708     def test_remove_flaky_line(self):
    709         host = MockHost()
    710         test_port = host.port_factory.get('test-win-xp', None)
    711         test_port.test_exists = lambda test: True
    712         test_port.test_isfile = lambda test: True
    713 
    714         test_config = test_port.test_configuration()
    715         test_port.expectations_dict = lambda: {'expectations': """Bug(x) [ Win ] failures/expected/foo.html [ Failure Timeout ]
    716 Bug(y) [ Mac ] failures/expected/foo.html [ Crash ]
    717 """}
    718         expectations = TestExpectations(test_port)
    719 
    720         actual_expectations = expectations.remove_configurations([('failures/expected/foo.html', test_config)])
    721         actual_expectations = expectations.remove_configurations([('failures/expected/foo.html', host.port_factory.get('test-win-win7', None).test_configuration())])
    722 
    723         self.assertEqual("""Bug(x) [ Win Debug ] failures/expected/foo.html [ Failure Timeout ]
    724 Bug(y) [ Mac ] failures/expected/foo.html [ Crash ]
    725 """, actual_expectations)
    726 
    727 
    728 class RebaseliningTest(Base):
    729     def test_get_rebaselining_failures(self):
    730         # Make sure we find a test as needing a rebaseline even if it is not marked as a failure.
    731         self.parse_exp('Bug(x) failures/expected/text.html [ Rebaseline ]\n')
    732         self.assertEqual(len(self._exp.get_rebaselining_failures()), 1)
    733 
    734         self.parse_exp(self.get_basic_expectations())
    735         self.assertEqual(len(self._exp.get_rebaselining_failures()), 0)
    736 
    737 
    738 class TestExpectationsParserTests(unittest.TestCase):
    739     def __init__(self, testFunc):
    740         host = MockHost()
    741         test_port = host.port_factory.get('test-win-xp', None)
    742         self._converter = TestConfigurationConverter(test_port.all_test_configurations(), test_port.configuration_specifier_macros())
    743         unittest.TestCase.__init__(self, testFunc)
    744         self._parser = TestExpectationParser(host.port_factory.get('test-win-xp', None), [], is_lint_mode=False)
    745 
    746     def test_expectation_line_for_test(self):
    747         # This is kind of a silly test, but it at least ensures that we don't throw an error.
    748         test_name = 'foo/test.html'
    749         expectations = set(["PASS", "IMAGE"])
    750 
    751         expectation_line = TestExpectationLine()
    752         expectation_line.original_string = test_name
    753         expectation_line.name = test_name
    754         expectation_line.filename = '<Bot TestExpectations>'
    755         expectation_line.line_numbers = '0'
    756         expectation_line.expectations = expectations
    757         self._parser._parse_line(expectation_line)
    758 
    759         self.assertEqual(self._parser.expectation_line_for_test(test_name, expectations), expectation_line)
    760 
    761 
    762 class TestExpectationSerializationTests(unittest.TestCase):
    763     def __init__(self, testFunc):
    764         host = MockHost()
    765         test_port = host.port_factory.get('test-win-xp', None)
    766         self._converter = TestConfigurationConverter(test_port.all_test_configurations(), test_port.configuration_specifier_macros())
    767         unittest.TestCase.__init__(self, testFunc)
    768 
    769     def _tokenize(self, line):
    770         return TestExpectationParser._tokenize_line('path', line, 0)
    771 
    772     def assert_round_trip(self, in_string, expected_string=None):
    773         expectation = self._tokenize(in_string)
    774         if expected_string is None:
    775             expected_string = in_string
    776         self.assertEqual(expected_string, expectation.to_string(self._converter))
    777 
    778     def assert_list_round_trip(self, in_string, expected_string=None):
    779         host = MockHost()
    780         parser = TestExpectationParser(host.port_factory.get('test-win-xp', None), [], is_lint_mode=False)
    781         expectations = parser.parse('path', in_string)
    782         if expected_string is None:
    783             expected_string = in_string
    784         self.assertEqual(expected_string, TestExpectations.list_to_string(expectations, self._converter))
    785 
    786     def test_unparsed_to_string(self):
    787         expectation = TestExpectationLine()
    788 
    789         self.assertEqual(expectation.to_string(self._converter), '')
    790         expectation.comment = ' Qux.'
    791         self.assertEqual(expectation.to_string(self._converter), '# Qux.')
    792         expectation.name = 'bar'
    793         self.assertEqual(expectation.to_string(self._converter), 'bar # Qux.')
    794         expectation.specifiers = ['foo']
    795         # FIXME: case should be preserved here but we can't until we drop the old syntax.
    796         self.assertEqual(expectation.to_string(self._converter), '[ FOO ] bar # Qux.')
    797         expectation.expectations = ['bAz']
    798         self.assertEqual(expectation.to_string(self._converter), '[ FOO ] bar [ BAZ ] # Qux.')
    799         expectation.expectations = ['bAz1', 'baZ2']
    800         self.assertEqual(expectation.to_string(self._converter), '[ FOO ] bar [ BAZ1 BAZ2 ] # Qux.')
    801         expectation.specifiers = ['foo1', 'foO2']
    802         self.assertEqual(expectation.to_string(self._converter), '[ FOO1 FOO2 ] bar [ BAZ1 BAZ2 ] # Qux.')
    803         expectation.warnings.append('Oh the horror.')
    804         self.assertEqual(expectation.to_string(self._converter), '')
    805         expectation.original_string = 'Yes it is!'
    806         self.assertEqual(expectation.to_string(self._converter), 'Yes it is!')
    807 
    808     def test_unparsed_list_to_string(self):
    809         expectation = TestExpectationLine()
    810         expectation.comment = 'Qux.'
    811         expectation.name = 'bar'
    812         expectation.specifiers = ['foo']
    813         expectation.expectations = ['bAz1', 'baZ2']
    814         # FIXME: case should be preserved here but we can't until we drop the old syntax.
    815         self.assertEqual(TestExpectations.list_to_string([expectation]), '[ FOO ] bar [ BAZ1 BAZ2 ] #Qux.')
    816 
    817     def test_parsed_to_string(self):
    818         expectation_line = TestExpectationLine()
    819         expectation_line.bugs = ['Bug(x)']
    820         expectation_line.name = 'test/name/for/realz.html'
    821         expectation_line.parsed_expectations = set([IMAGE])
    822         self.assertEqual(expectation_line.to_string(self._converter), None)
    823         expectation_line.matching_configurations = set([TestConfiguration('xp', 'x86', 'release')])
    824         self.assertEqual(expectation_line.to_string(self._converter), 'Bug(x) [ XP Release ] test/name/for/realz.html [ ImageOnlyFailure ]')
    825         expectation_line.matching_configurations = set([TestConfiguration('xp', 'x86', 'release'), TestConfiguration('xp', 'x86', 'debug')])
    826         self.assertEqual(expectation_line.to_string(self._converter), 'Bug(x) [ XP ] test/name/for/realz.html [ ImageOnlyFailure ]')
    827 
    828     def test_serialize_parsed_expectations(self):
    829         expectation_line = TestExpectationLine()
    830         expectation_line.parsed_expectations = set([])
    831         parsed_expectation_to_string = dict([[parsed_expectation, expectation_string] for expectation_string, parsed_expectation in TestExpectations.EXPECTATIONS.items()])
    832         self.assertEqual(expectation_line._serialize_parsed_expectations(parsed_expectation_to_string), '')
    833         expectation_line.parsed_expectations = set([FAIL])
    834         self.assertEqual(expectation_line._serialize_parsed_expectations(parsed_expectation_to_string), 'fail')
    835         expectation_line.parsed_expectations = set([PASS, IMAGE])
    836         self.assertEqual(expectation_line._serialize_parsed_expectations(parsed_expectation_to_string), 'image pass')
    837         expectation_line.parsed_expectations = set([FAIL, PASS])
    838         self.assertEqual(expectation_line._serialize_parsed_expectations(parsed_expectation_to_string), 'pass fail')
    839 
    840     def test_serialize_parsed_specifier_string(self):
    841         expectation_line = TestExpectationLine()
    842         expectation_line.bugs = ['garden-o-matic']
    843         expectation_line.parsed_specifiers = ['the', 'for']
    844         self.assertEqual(expectation_line._serialize_parsed_specifiers(self._converter, []), 'for the')
    845         self.assertEqual(expectation_line._serialize_parsed_specifiers(self._converter, ['win']), 'for the win')
    846         expectation_line.bugs = []
    847         expectation_line.parsed_specifiers = []
    848         self.assertEqual(expectation_line._serialize_parsed_specifiers(self._converter, []), '')
    849         self.assertEqual(expectation_line._serialize_parsed_specifiers(self._converter, ['win']), 'win')
    850 
    851     def test_format_line(self):
    852         self.assertEqual(TestExpectationLine._format_line([], ['MODIFIERS'], 'name', ['EXPECTATIONS'], 'comment'), '[ MODIFIERS ] name [ EXPECTATIONS ] #comment')
    853         self.assertEqual(TestExpectationLine._format_line([], ['MODIFIERS'], 'name', ['EXPECTATIONS'], None), '[ MODIFIERS ] name [ EXPECTATIONS ]')
    854 
    855     def test_string_roundtrip(self):
    856         self.assert_round_trip('')
    857         self.assert_round_trip('[')
    858         self.assert_round_trip('FOO [')
    859         self.assert_round_trip('FOO ] bar')
    860         self.assert_round_trip('  FOO [')
    861         self.assert_round_trip('    [ FOO ] ')
    862         self.assert_round_trip('[ FOO ] bar [ BAZ ]')
    863         self.assert_round_trip('[ FOO ] bar [ BAZ ] # Qux.')
    864         self.assert_round_trip('[ FOO ] bar [ BAZ ] # Qux.')
    865         self.assert_round_trip('[ FOO ] bar [ BAZ ] # Qux.     ')
    866         self.assert_round_trip('[ FOO ] bar [ BAZ ] #        Qux.     ')
    867         self.assert_round_trip('[ FOO ] ] ] bar BAZ')
    868         self.assert_round_trip('[ FOO ] ] ] bar [ BAZ ]')
    869         self.assert_round_trip('FOO ] ] bar ==== BAZ')
    870         self.assert_round_trip('=')
    871         self.assert_round_trip('#')
    872         self.assert_round_trip('# ')
    873         self.assert_round_trip('# Foo')
    874         self.assert_round_trip('# Foo')
    875         self.assert_round_trip('# Foo :')
    876         self.assert_round_trip('# Foo : =')
    877 
    878     def test_list_roundtrip(self):
    879         self.assert_list_round_trip('')
    880         self.assert_list_round_trip('\n')
    881         self.assert_list_round_trip('\n\n')
    882         self.assert_list_round_trip('bar')
    883         self.assert_list_round_trip('bar\n# Qux.')
    884         self.assert_list_round_trip('bar\n# Qux.\n')
    885 
    886     def test_reconstitute_only_these(self):
    887         lines = []
    888         reconstitute_only_these = []
    889 
    890         def add_line(matching_configurations, reconstitute):
    891             expectation_line = TestExpectationLine()
    892             expectation_line.original_string = "Nay"
    893             expectation_line.bugs = ['Bug(x)']
    894             expectation_line.name = 'Yay'
    895             expectation_line.parsed_expectations = set([IMAGE])
    896             expectation_line.matching_configurations = matching_configurations
    897             lines.append(expectation_line)
    898             if reconstitute:
    899                 reconstitute_only_these.append(expectation_line)
    900 
    901         add_line(set([TestConfiguration('xp', 'x86', 'release')]), True)
    902         add_line(set([TestConfiguration('xp', 'x86', 'release'), TestConfiguration('xp', 'x86', 'debug')]), False)
    903         serialized = TestExpectations.list_to_string(lines, self._converter)
    904         self.assertEqual(serialized, "Bug(x) [ XP Release ] Yay [ ImageOnlyFailure ]\nBug(x) [ XP ] Yay [ ImageOnlyFailure ]")
    905         serialized = TestExpectations.list_to_string(lines, self._converter, reconstitute_only_these=reconstitute_only_these)
    906         self.assertEqual(serialized, "Bug(x) [ XP Release ] Yay [ ImageOnlyFailure ]\nNay")
    907 
    908     def disabled_test_string_whitespace_stripping(self):
    909         # FIXME: Re-enable this test once we rework the code to no longer support the old syntax.
    910         self.assert_round_trip('\n', '')
    911         self.assert_round_trip('  [ FOO ] bar [ BAZ ]', '[ FOO ] bar [ BAZ ]')
    912         self.assert_round_trip('[ FOO ]    bar [ BAZ ]', '[ FOO ] bar [ BAZ ]')
    913         self.assert_round_trip('[ FOO ] bar [ BAZ ]       # Qux.', '[ FOO ] bar [ BAZ ] # Qux.')
    914         self.assert_round_trip('[ FOO ] bar [        BAZ ]  # Qux.', '[ FOO ] bar [ BAZ ] # Qux.')
    915         self.assert_round_trip('[ FOO ]       bar [    BAZ ]  # Qux.', '[ FOO ] bar [ BAZ ] # Qux.')
    916         self.assert_round_trip('[ FOO ]       bar     [    BAZ ]  # Qux.', '[ FOO ] bar [ BAZ ] # Qux.')
    917