webkitpy: Implement device type specific expected results (Part 2)
[WebKit-https.git] / Tools / Scripts / webkitpy / layout_tests / models / test_expectations_unittest.py
index 682930e..2046446 100644 (file)
@@ -1,5 +1,5 @@
-#!/usr/bin/python
 # Copyright (C) 2010 Google Inc. All rights reserved.
+# Copyright (C) 2013 Apple Inc. All rights reserved.
 #
 # Redistribution and use in source and binary forms, with or without
 # modification, are permitted provided that the following conditions are
 
 import unittest
 
-from webkitpy.layout_tests import port
-from webkitpy.layout_tests.port import base
+from collections import OrderedDict
+
+from webkitpy.common.host_mock import MockHost
+from webkitpy.common.system.outputcapture import OutputCapture
+
 from webkitpy.layout_tests.models.test_configuration import *
 from webkitpy.layout_tests.models.test_expectations import *
 from webkitpy.layout_tests.models.test_configuration import *
 
 
-class MockBugManager(object):
-    def close_bug(self, bug_id, reference_bug_id=None):
-        pass
-
-    def create_bug(self):
-        return "BUG_NEWLY_CREATED"
-
-
-class FunctionsTest(unittest.TestCase):
-    def test_result_was_expected(self):
-        # test basics
-        self.assertEquals(result_was_expected(PASS, set([PASS]),
-                                              False, False), True)
-        self.assertEquals(result_was_expected(TEXT, set([PASS]),
-                                              False, False), False)
-
-        # test handling of FAIL expectations
-        self.assertEquals(result_was_expected(IMAGE_PLUS_TEXT, set([FAIL]),
-                                              False, False), True)
-        self.assertEquals(result_was_expected(IMAGE, set([FAIL]),
-                                              False, False), True)
-        self.assertEquals(result_was_expected(TEXT, set([FAIL]),
-                                              False, False), True)
-        self.assertEquals(result_was_expected(CRASH, set([FAIL]),
-                                              False, False), False)
-
-        # test handling of SKIPped tests and results
-        self.assertEquals(result_was_expected(SKIP, set([CRASH]),
-                                              False, True), True)
-        self.assertEquals(result_was_expected(SKIP, set([CRASH]),
-                                              False, False), False)
-
-        # test handling of MISSING results and the REBASELINE modifier
-        self.assertEquals(result_was_expected(MISSING, set([PASS]),
-                                              True, False), True)
-        self.assertEquals(result_was_expected(MISSING, set([PASS]),
-                                              False, False), False)
-
-    def test_remove_pixel_failures(self):
-        self.assertEquals(remove_pixel_failures(set([TEXT])),
-                          set([TEXT]))
-        self.assertEquals(remove_pixel_failures(set([PASS])),
-                          set([PASS]))
-        self.assertEquals(remove_pixel_failures(set([IMAGE])),
-                          set([PASS]))
-        self.assertEquals(remove_pixel_failures(set([IMAGE_PLUS_TEXT])),
-                          set([TEXT]))
-        self.assertEquals(remove_pixel_failures(set([PASS, IMAGE, CRASH])),
-                          set([PASS, CRASH]))
-
-
 class Base(unittest.TestCase):
     # Note that all of these tests are written assuming the configuration
     # being tested is Windows XP, Release build.
 
-    def __init__(self, testFunc, setUp=None, tearDown=None, description=None):
-        self._port = port.get('test-win-xp', None)
-        self._fs = self._port._filesystem
+    def __init__(self, testFunc):
+        host = MockHost()
+        self._port = host.port_factory.get('test-win-xp', None)
         self._exp = None
         unittest.TestCase.__init__(self, testFunc)
 
-    def get_test(self, test_name):
-        # FIXME: Remove this routine and just reference test names directly.
-        return test_name
-
     def get_basic_tests(self):
-        return [self.get_test('failures/expected/text.html'),
-                self.get_test('failures/expected/image_checksum.html'),
-                self.get_test('failures/expected/crash.html'),
-                self.get_test('failures/expected/missing_text.html'),
-                self.get_test('failures/expected/image.html'),
-                self.get_test('passes/text.html')]
+        return ['failures/expected/text.html',
+                'failures/expected/image_checksum.html',
+                'failures/expected/crash.html',
+                'failures/expected/leak.html',
+                'failures/expected/flaky-leak.html',
+                'failures/expected/missing_text.html',
+                'failures/expected/image.html',
+                'failures/expected/reftest.html',
+                'failures/expected/leaky-reftest.html',
+                'passes/text.html']
 
     def get_basic_expectations(self):
         return """
-BUG_TEST : failures/expected/text.html = TEXT
-BUG_TEST WONTFIX SKIP : failures/expected/crash.html = CRASH
-BUG_TEST REBASELINE : failures/expected/missing_image.html = MISSING
-BUG_TEST WONTFIX : failures/expected/image_checksum.html = IMAGE
-BUG_TEST WONTFIX MAC : failures/expected/image.html = IMAGE
+Bug(test) failures/expected/text.html [ Failure ]
+Bug(test) failures/expected/crash.html [ WontFix ]
+Bug(test) failures/expected/leak.html [ Leak ]
+Bug(test) failures/expected/flaky-leak.html [ Failure Leak ]
+Bug(test) failures/expected/missing_image.html [ Rebaseline Missing ]
+Bug(test) failures/expected/image_checksum.html [ WontFix ]
+Bug(test) failures/expected/image.html [ WontFix Mac ]
+Bug(test) failures/expected/reftest.html [ ImageOnlyFailure ]
+Bug(test) failures/expected/leaky-reftest.html [ ImageOnlyFailure Leak ]
 """
 
     def parse_exp(self, expectations, overrides=None, is_lint_mode=False):
-        test_config = self._port.test_configuration()
-        self._exp = TestExpectations(self._port,
-             tests=self.get_basic_tests(),
-             expectations=expectations,
-             test_config=test_config,
-             is_lint_mode=is_lint_mode,
-             overrides=overrides)
+        expectations_dict = OrderedDict()
+        expectations_dict['expectations'] = expectations
+        if overrides:
+            expectations_dict['overrides'] = overrides
+        self._port.expectations_dict = lambda **kwargs: expectations_dict
+        expectations_to_lint = expectations_dict if is_lint_mode else None
+        self._exp = TestExpectations(self._port, self.get_basic_tests(), expectations_to_lint=expectations_to_lint)
+        self._exp.parse_all_expectations()
 
     def assert_exp(self, test, result):
-        self.assertEquals(self._exp.get_expectations(self.get_test(test)),
-                          set([result]))
+        self.assertEqual(self._exp.model().get_expectations(test), set([result]))
+
+    def assert_exp_set(self, test, result_set):
+        self.assertEqual(self._exp.model().get_expectations(test), result_set)
+
+    def assert_bad_expectations(self, expectations, overrides=None):
+        self.assertRaises(ParseError, self.parse_exp, expectations, is_lint_mode=True, overrides=overrides)
 
 
 class BasicTests(Base):
     def test_basic(self):
         self.parse_exp(self.get_basic_expectations())
-        self.assert_exp('failures/expected/text.html', TEXT)
-        self.assert_exp('failures/expected/image_checksum.html', IMAGE)
+        self.assert_exp('failures/expected/text.html', FAIL)
+        self.assert_exp('failures/expected/image_checksum.html', PASS)
+        self.assert_exp('failures/expected/reftest.html', IMAGE)
+        self.assert_exp_set('failures/expected/leaky-reftest.html', set([IMAGE, LEAK]))
+        self.assert_exp('failures/expected/leak.html', LEAK)
+        self.assert_exp_set('failures/expected/flaky-leak.html', set([FAIL, LEAK]))
         self.assert_exp('passes/text.html', PASS)
+        # self.assert_exp_set('passes/flaky-leak.html', set([PASS, LEAK]))
         self.assert_exp('failures/expected/image.html', PASS)
 
 
 class MiscTests(Base):
     def test_multiple_results(self):
-        self.parse_exp('BUGX : failures/expected/text.html = TEXT CRASH')
-        self.assertEqual(self._exp.get_expectations(
-            self.get_test('failures/expected/text.html')),
-            set([TEXT, CRASH]))
+        self.parse_exp('Bug(x) failures/expected/text.html [ Crash Failure ]')
+        self.assertEqual(self._exp.model().get_expectations(
+            'failures/expected/text.html'),
+            set([FAIL, CRASH]))
+
+    def test_result_was_expected(self):
+        # test basics
+        self.assertEqual(TestExpectations.result_was_expected(PASS, set([PASS]), test_needs_rebaselining=False, test_is_skipped=False), True)
+        self.assertEqual(TestExpectations.result_was_expected(FAIL, set([PASS]), test_needs_rebaselining=False, test_is_skipped=False), False)
+
+        # test handling of SKIPped tests and results
+        self.assertEqual(TestExpectations.result_was_expected(SKIP, set([CRASH]), test_needs_rebaselining=False, test_is_skipped=True), True)
+        self.assertEqual(TestExpectations.result_was_expected(SKIP, set([CRASH]), test_needs_rebaselining=False, test_is_skipped=False), False)
+
+        # test handling of MISSING results and the REBASELINE modifier
+        self.assertEqual(TestExpectations.result_was_expected(MISSING, set([PASS]), test_needs_rebaselining=True, test_is_skipped=False), True)
+        self.assertEqual(TestExpectations.result_was_expected(MISSING, set([PASS]), test_needs_rebaselining=False, test_is_skipped=False), False)
+
+    def test_remove_pixel_failures(self):
+        self.assertEqual(TestExpectations.remove_pixel_failures(set([FAIL])), set([FAIL]))
+        self.assertEqual(TestExpectations.remove_pixel_failures(set([PASS])), set([PASS]))
+        self.assertEqual(TestExpectations.remove_pixel_failures(set([IMAGE])), set([PASS]))
+        self.assertEqual(TestExpectations.remove_pixel_failures(set([FAIL])), set([FAIL]))
+        self.assertEqual(TestExpectations.remove_pixel_failures(set([PASS, IMAGE, CRASH])), set([PASS, CRASH]))
+
+    def test_remove_leak_failures(self):
+        self.assertEqual(TestExpectations.remove_leak_failures(set([FAIL])), set([FAIL]))
+        self.assertEqual(TestExpectations.remove_leak_failures(set([PASS])), set([PASS]))
+        self.assertEqual(TestExpectations.remove_leak_failures(set([LEAK])), set([PASS]))
+        self.assertEqual(TestExpectations.remove_leak_failures(set([PASS, LEAK])), set([PASS]))
+        self.assertEqual(TestExpectations.remove_leak_failures(set([FAIL, LEAK])), set([FAIL]))
+        self.assertEqual(TestExpectations.remove_leak_failures(set([PASS, IMAGE, LEAK, CRASH])), set([PASS, IMAGE, CRASH]))
+
+    def test_suffixes_for_expectations(self):
+        self.assertEqual(TestExpectations.suffixes_for_expectations(set([FAIL])), set(['txt', 'png', 'wav']))
+        self.assertEqual(TestExpectations.suffixes_for_expectations(set([IMAGE])), set(['png']))
+        self.assertEqual(TestExpectations.suffixes_for_expectations(set([FAIL, IMAGE, CRASH])), set(['txt', 'png', 'wav']))
+        self.assertEqual(TestExpectations.suffixes_for_expectations(set()), set())
 
     def test_category_expectations(self):
         # This test checks unknown tests are not present in the
         # expectations and that known test part of a test category is
         # present in the expectations.
-        exp_str = """
-BUGX WONTFIX : failures/expected = IMAGE
-"""
+        exp_str = 'Bug(x) failures/expected [ WontFix ]'
         self.parse_exp(exp_str)
         test_name = 'failures/expected/unknown-test.html'
-        unknown_test = self.get_test(test_name)
-        self.assertRaises(KeyError, self._exp.get_expectations,
+        unknown_test = test_name
+        self.assertRaises(KeyError, self._exp.model().get_expectations,
                           unknown_test)
-        self.assert_exp('failures/expected/crash.html', IMAGE)
+        self.assert_exp('failures/expected/crash.html', PASS)
 
     def test_get_modifiers(self):
         self.parse_exp(self.get_basic_expectations())
-        self.assertEqual(self._exp.get_modifiers(
-                         self.get_test('passes/text.html')), [])
+        self.assertEqual(self._exp.model().get_modifiers('passes/text.html'), [])
 
     def test_get_expectations_string(self):
         self.parse_exp(self.get_basic_expectations())
-        self.assertEquals(self._exp.get_expectations_string(
-                          self.get_test('failures/expected/text.html')),
-                          'TEXT')
+        self.assertEqual(self._exp.model().get_expectations_string('failures/expected/text.html'), 'FAIL')
 
     def test_expectation_to_string(self):
         # Normal cases are handled by other tests.
         self.parse_exp(self.get_basic_expectations())
-        self.assertRaises(ValueError, self._exp.expectation_to_string,
-                          -1)
+        self.assertRaises(ValueError, self._exp.model().expectation_to_string, -1)
 
     def test_get_test_set(self):
         # Handle some corner cases for this routine not covered by other tests.
         self.parse_exp(self.get_basic_expectations())
-        s = self._exp.get_test_set(WONTFIX)
-        self.assertEqual(s,
-            set([self.get_test('failures/expected/crash.html'),
-                 self.get_test('failures/expected/image_checksum.html')]))
-        s = self._exp.get_test_set(WONTFIX, CRASH)
+        s = self._exp.model().get_test_set(WONTFIX)
         self.assertEqual(s,
-            set([self.get_test('failures/expected/crash.html')]))
-        s = self._exp.get_test_set(WONTFIX, CRASH, include_skips=False)
-        self.assertEqual(s, set([]))
+            set(['failures/expected/crash.html',
+                 'failures/expected/image_checksum.html']))
 
-    def test_parse_error_fatal(self):
+    def test_parse_warning(self):
         try:
-            self.parse_exp("""FOO : failures/expected/text.html = TEXT
-SKIP : failures/expected/image.html""")
+            filesystem = self._port.host.filesystem
+            filesystem.write_text_file(filesystem.join(self._port.layout_tests_dir(), 'disabled-test.html-disabled'), 'content')
+            self.parse_exp("[ FOO ] failures/expected/text.html [ Failure ]\n"
+                "Bug(rniwa) non-existent-test.html [ Failure ]\n"
+                "Bug(rniwa) disabled-test.html-disabled [ ImageOnlyFailure ]", is_lint_mode=True)
             self.assertFalse(True, "ParseError wasn't raised")
-        except ParseError, e:
-            self.assertTrue(e.fatal)
-            exp_errors = [u"Line:1 Unrecognized modifier 'foo' failures/expected/text.html",
-                          u"Line:2 Missing expectations SKIP : failures/expected/image.html"]
-            self.assertEqual(str(e), '\n'.join(map(str, exp_errors)))
-            self.assertEqual(e.errors, exp_errors)
-
-    def test_parse_error_nonfatal(self):
+        except ParseError as e:
+            warnings = ("expectations:1 Unrecognized modifier 'foo' failures/expected/text.html\n"
+                        "expectations:2 Path does not exist. non-existent-test.html")
+            self.assertEqual(str(e), warnings)
+
+    def test_parse_warnings_are_logged_if_not_in_lint_mode(self):
+        oc = OutputCapture()
         try:
-            self.parse_exp('SKIP : failures/expected/text.html = TEXT',
-                           is_lint_mode=True)
-            self.assertFalse(True, "ParseError wasn't raised")
-        except ParseError, e:
-            self.assertFalse(e.fatal)
-            exp_errors = [u'Line:1 Test lacks BUG modifier. failures/expected/text.html']
-            self.assertEqual(str(e), '\n'.join(map(str, exp_errors)))
-            self.assertEqual(e.errors, exp_errors)
+            oc.capture_output()
+            self.parse_exp('-- this should be a syntax error', is_lint_mode=False)
+        finally:
+            _, _, logs = oc.restore_output()
+            self.assertNotEquals(logs, '')
+
+    def test_error_on_different_platform(self):
+        # parse_exp uses a Windows port. Assert errors on Mac show up in lint mode.
+        self.assertRaises(ParseError, self.parse_exp,
+            'Bug(test) [ Mac ] failures/expected/text.html [ Failure ]\nBug(test) [ Mac ] failures/expected/text.html [ Failure ]',
+            is_lint_mode=True)
+
+    def test_error_on_different_build_type(self):
+        # parse_exp uses a Release port. Assert errors on DEBUG show up in lint mode.
+        self.assertRaises(ParseError, self.parse_exp,
+            'Bug(test) [ Debug ] failures/expected/text.html [ Failure ]\nBug(test) [ Debug ] failures/expected/text.html [ Failure ]',
+            is_lint_mode=True)
 
     def test_overrides(self):
-        self.parse_exp("BUG_EXP: failures/expected/text.html = TEXT",
-                       "BUG_OVERRIDE : failures/expected/text.html = IMAGE")
+        self.parse_exp("Bug(exp) failures/expected/text.html [ Failure ]",
+                       "Bug(override) failures/expected/text.html [ ImageOnlyFailure ]")
         self.assert_exp('failures/expected/text.html', IMAGE)
 
+    def test_overrides__directory(self):
+        self.parse_exp("Bug(exp) failures/expected/text.html [ Failure ]",
+                       "Bug(override) failures/expected [ Crash ]")
+        self.assert_exp('failures/expected/text.html', CRASH)
+        self.assert_exp('failures/expected/image.html', CRASH)
+
     def test_overrides__duplicate(self):
-        self.assertRaises(ParseError, self.parse_exp,
-             "BUG_EXP: failures/expected/text.html = TEXT",
-             """
-BUG_OVERRIDE : failures/expected/text.html = IMAGE
-BUG_OVERRIDE : failures/expected/text.html = CRASH
-""")
+        self.assert_bad_expectations("Bug(exp) failures/expected/text.html [ Failure ]",
+                                     "Bug(override) failures/expected/text.html [ ImageOnlyFailure ]\n"
+                                     "Bug(override) failures/expected/text.html [ Crash ]\n")
 
     def test_pixel_tests_flag(self):
         def match(test, result, pixel_tests_enabled):
-            return self._exp.matches_an_expected_result(
-                self.get_test(test), result, pixel_tests_enabled)
+            expectations = self._exp.filtered_expectations_for_test(test, pixel_tests_enabled, False)
+            return self._exp.matches_an_expected_result(test, result, expectations)
 
         self.parse_exp(self.get_basic_expectations())
-        self.assertTrue(match('failures/expected/text.html', TEXT, True))
-        self.assertTrue(match('failures/expected/text.html', TEXT, False))
-        self.assertFalse(match('failures/expected/text.html', CRASH, True))
-        self.assertFalse(match('failures/expected/text.html', CRASH, False))
-        self.assertTrue(match('failures/expected/image_checksum.html', IMAGE,
-                              True))
-        self.assertTrue(match('failures/expected/image_checksum.html', PASS,
-                              False))
-        self.assertTrue(match('failures/expected/crash.html', SKIP, False))
-        self.assertTrue(match('passes/text.html', PASS, False))
+        pixel_tests_enabled = True
+        pixel_tests_disabled = False
+        self.assertTrue(match('failures/expected/text.html', FAIL, pixel_tests_enabled))
+        self.assertTrue(match('failures/expected/text.html', FAIL, pixel_tests_disabled))
+        self.assertFalse(match('failures/expected/text.html', CRASH, pixel_tests_enabled))
+        self.assertFalse(match('failures/expected/text.html', CRASH, pixel_tests_disabled))
+        self.assertTrue(match('failures/expected/image_checksum.html', PASS, pixel_tests_enabled))
+        self.assertTrue(match('failures/expected/image_checksum.html', PASS, pixel_tests_disabled))
+        self.assertTrue(match('failures/expected/crash.html', PASS, pixel_tests_disabled))
+        self.assertTrue(match('passes/text.html', PASS, pixel_tests_disabled))
+
+    def test_world_leaks_flag(self):
+        def match(test, result, pixel_tests_enabled, world_leaks_enabled):
+            expectations = self._exp.filtered_expectations_for_test(test, pixel_tests_enabled, world_leaks_enabled)
+            return self._exp.matches_an_expected_result(test, result, expectations)
+
+        pixel_tests_enabled = True
+        pixel_tests_disabled = False
+        world_leaks_enabled = True
+        world_leaks_disabled = False
+
+        self.parse_exp(self.get_basic_expectations())
+        self.assertTrue(match('failures/expected/leak.html', LEAK, pixel_tests_enabled, world_leaks_enabled))
+        self.assertTrue(match('failures/expected/leak.html', PASS, pixel_tests_enabled, world_leaks_disabled))
+        self.assertTrue(match('failures/expected/flaky-leak.html', FAIL, pixel_tests_enabled, world_leaks_disabled))
+
+        self.assertTrue(match('failures/expected/leaky-reftest.html', LEAK, pixel_tests_disabled, world_leaks_enabled))
+        self.assertTrue(match('failures/expected/leaky-reftest.html', PASS, pixel_tests_disabled, world_leaks_disabled))
+
+        self.assertTrue(match('failures/expected/leaky-reftest.html', IMAGE, pixel_tests_enabled, world_leaks_enabled))
+        self.assertTrue(match('failures/expected/leaky-reftest.html', LEAK, pixel_tests_enabled, world_leaks_enabled))
+        self.assertTrue(match('failures/expected/leaky-reftest.html', IMAGE, pixel_tests_enabled, world_leaks_disabled))
+
+        self.assertFalse(match('failures/expected/text.html', PASS, pixel_tests_enabled, world_leaks_enabled))
+        self.assertFalse(match('failures/expected/text.html', CRASH, pixel_tests_enabled, world_leaks_disabled))
+        self.assertTrue(match('passes/text.html', PASS, pixel_tests_enabled, world_leaks_disabled))
 
     def test_more_specific_override_resets_skip(self):
-        self.parse_exp("BUGX SKIP : failures/expected = TEXT\n"
-                       "BUGX : failures/expected/text.html = IMAGE\n")
+        self.parse_exp("Bug(x) failures/expected [ Skip ]\n"
+                       "Bug(x) failures/expected/text.html [ ImageOnlyFailure ]\n")
         self.assert_exp('failures/expected/text.html', IMAGE)
         self.assertFalse(self._port._filesystem.join(self._port.layout_tests_dir(),
                                                      'failures/expected/text.html') in
-                         self._exp.get_tests_with_result_type(SKIP))
+                         self._exp.model().get_tests_with_result_type(SKIP))
+
+
+class SkippedTests(Base):
+    def check(self, expectations, overrides, skips, lint=False):
+        port = MockHost().port_factory.get('mac')
+        port._filesystem.write_text_file(port._filesystem.join(port.layout_tests_dir(), 'failures/expected/text.html'), 'foo')
+        expectations_dict = OrderedDict()
+        expectations_dict['expectations'] = expectations
+        if overrides:
+            expectations_dict['overrides'] = overrides
+        port.expectations_dict = lambda **kwargs: expectations_dict
+        port.skipped_layout_tests = lambda tests, **kwargs: set(skips)
+        expectations_to_lint = expectations_dict if lint else None
+        exp = TestExpectations(port, ['failures/expected/text.html'], expectations_to_lint=expectations_to_lint)
+        exp.parse_all_expectations()
+
+        # Check that the expectation is for BUG_DUMMY SKIP : ... [ Pass ]
+        self.assertEqual(exp.model().get_modifiers('failures/expected/text.html'),
+                          [TestExpectationParser.DUMMY_BUG_MODIFIER, TestExpectationParser.SKIP_MODIFIER, TestExpectationParser.WONTFIX_MODIFIER])
+        self.assertEqual(exp.model().get_expectations('failures/expected/text.html'), set([PASS]))
+
+    def test_skipped_tests_work(self):
+        self.check(expectations='', overrides=None, skips=['failures/expected/text.html'])
+
+    def test_duplicate_skipped_test_fails_lint(self):
+        self.assertRaises(ParseError, self.check, expectations='Bug(x) failures/expected/text.html [ Failure ]\n', overrides=None, skips=['failures/expected/text.html'], lint=True)
+
+    def test_skipped_file_overrides_expectations(self):
+        self.check(expectations='Bug(x) failures/expected/text.html [ Failure ]\n', overrides=None,
+                   skips=['failures/expected/text.html'])
+
+    def test_skipped_dir_overrides_expectations(self):
+        self.check(expectations='Bug(x) failures/expected/text.html [ Failure ]\n', overrides=None,
+                   skips=['failures/expected'])
+
+    def test_skipped_file_overrides_overrides(self):
+        self.check(expectations='', overrides='Bug(x) failures/expected/text.html [ Failure ]\n',
+                   skips=['failures/expected/text.html'])
+
+    def test_skipped_dir_overrides_overrides(self):
+        self.check(expectations='', overrides='Bug(x) failures/expected/text.html [ Failure ]\n',
+                   skips=['failures/expected'])
+
+    def test_skipped_entry_dont_exist(self):
+        port = MockHost().port_factory.get('mac')
+        expectations_dict = OrderedDict()
+        expectations_dict['expectations'] = ''
+        port.expectations_dict = lambda **kwargs: expectations_dict
+        port.skipped_layout_tests = lambda tests, **kwargs: set(['foo/bar/baz.html'])
+        capture = OutputCapture()
+        capture.capture_output()
+        exp = TestExpectations(port)
+        exp.parse_all_expectations()
+        _, _, logs = capture.restore_output()
+        self.assertEqual('The following test foo/bar/baz.html from the Skipped list doesn\'t exist\n', logs)
+
 
 class ExpectationSyntaxTests(Base):
-    def test_missing_expectation(self):
-        # This is missing the expectation.
-        self.assertRaises(ParseError, self.parse_exp,
-                          'BUG_TEST: failures/expected/text.html')
+    def test_unrecognized_expectation(self):
+        self.assert_bad_expectations('Bug(test) failures/expected/text.html [ Unknown ]')
 
-    def test_missing_colon(self):
-        # This is missing the modifiers and the ':'
-        self.assertRaises(ParseError, self.parse_exp,
-                          'failures/expected/text.html = TEXT')
+    def test_macro(self):
+        exp_str = 'Bug(test) [ Win ] failures/expected/text.html [ Failure ]'
+        self.parse_exp(exp_str)
+        self.assert_exp('failures/expected/text.html', FAIL)
 
-    def disabled_test_too_many_colons(self):
-        # FIXME: Enable this test and fix the underlying bug.
-        self.assertRaises(ParseError, self.parse_exp,
-                          'BUG_TEST: failures/expected/text.html = PASS :')
+    def assert_tokenize_exp(self, line, bugs=None, modifiers=None, expectations=None, warnings=None, comment=None, name='foo.html'):
+        bugs = bugs or []
+        modifiers = modifiers or []
+        expectations = expectations or []
+        warnings = warnings or []
+        filename = 'TestExpectations'
+        line_number = 1
+        expectation_line = TestExpectationParser._tokenize_line(filename, line, line_number)
+        self.assertEqual(expectation_line.warnings, warnings)
+        self.assertEqual(expectation_line.name, name)
+        self.assertEqual(expectation_line.filename, filename)
+        self.assertEqual(expectation_line.line_number, line_number)
+        if not warnings:
+            self.assertEqual(expectation_line.modifiers, modifiers)
+            self.assertEqual(expectation_line.expectations, expectations)
 
-    def test_too_many_equals_signs(self):
-        self.assertRaises(ParseError, self.parse_exp,
-                          'BUG_TEST: failures/expected/text.html = TEXT = IMAGE')
+    def test_bare_name(self):
+        self.assert_tokenize_exp('foo.html', modifiers=['SKIP'], expectations=['PASS'])
 
-    def test_unrecognized_expectation(self):
-        self.assertRaises(ParseError, self.parse_exp,
-                          'BUG_TEST: failures/expected/text.html = UNKNOWN')
+    def test_bare_name_and_bugs(self):
+        self.assert_tokenize_exp('webkit.org/b/12345 foo.html', modifiers=['BUGWK12345', 'SKIP'], expectations=['PASS'])
+        self.assert_tokenize_exp('Bug(dpranke) foo.html', modifiers=['BUGDPRANKE', 'SKIP'], expectations=['PASS'])
+        self.assert_tokenize_exp('webkit.org/b/12345 webkit.org/b/34567 foo.html', modifiers=['BUGWK12345', 'BUGWK34567', 'SKIP'], expectations=['PASS'])
 
-    def test_macro(self):
-        exp_str = """
-BUG_TEST WIN : failures/expected/text.html = TEXT
-"""
-        self.parse_exp(exp_str)
-        self.assert_exp('failures/expected/text.html', TEXT)
+    def test_comments(self):
+        self.assert_tokenize_exp("# comment", name=None, comment="# comment")
+        self.assert_tokenize_exp("foo.html # comment", comment="# comment", expectations=['PASS'], modifiers=['SKIP'])
+
+    def test_config_modifiers(self):
+        self.assert_tokenize_exp('[ Mac ] foo.html', modifiers=['MAC', 'SKIP'], expectations=['PASS'])
+        self.assert_tokenize_exp('[ Mac Vista ] foo.html', modifiers=['MAC', 'VISTA', 'SKIP'], expectations=['PASS'])
+        self.assert_tokenize_exp('[ Mac ] foo.html [ Failure ] ', modifiers=['MAC'], expectations=['FAIL'])
+
+    def test_unknown_config(self):
+        self.assert_tokenize_exp('[ Foo ] foo.html ', modifiers=['Foo', 'SKIP'], expectations=['PASS'])
+
+    def test_unknown_expectation(self):
+        self.assert_tokenize_exp('foo.html [ Audio ]', warnings=['Unrecognized expectation "Audio"'])
+
+    def test_skip(self):
+        self.assert_tokenize_exp('foo.html [ Skip ]', modifiers=['SKIP'], expectations=['PASS'])
+
+    def test_slow(self):
+        self.assert_tokenize_exp('foo.html [ Slow ]', modifiers=['SLOW'], expectations=['PASS'])
+
+    def test_leak(self):
+        self.assert_tokenize_exp('foo.html [ Leak ]', modifiers=[], expectations=['LEAK'])
+
+    def test_wontfix(self):
+        self.assert_tokenize_exp('foo.html [ WontFix ]', modifiers=['WONTFIX', 'SKIP'], expectations=['PASS'])
+        self.assert_tokenize_exp('foo.html [ WontFix ImageOnlyFailure ]', modifiers=['WONTFIX'], expectations=['IMAGE'])
+        self.assert_tokenize_exp('foo.html [ WontFix Pass Failure ]', modifiers=['WONTFIX'], expectations=['PASS', 'FAIL'])
+
+    def test_blank_line(self):
+        self.assert_tokenize_exp('', name=None)
+
+    def test_warnings(self):
+        self.assert_tokenize_exp('[ Mac ]', warnings=['Did not find a test name.'], name=None)
+        self.assert_tokenize_exp('[ [', warnings=['unexpected "["'], name=None)
+        self.assert_tokenize_exp('webkit.org/b/12345 ]', warnings=['unexpected "]"'], name=None)
+
+        self.assert_tokenize_exp('foo.html webkit.org/b/12345 ]', warnings=['"webkit.org/b/12345" is not at the start of the line.'])
 
 
 class SemanticTests(Base):
     def test_bug_format(self):
-        self.assertRaises(ParseError, self.parse_exp, 'BUG1234 : failures/expected/text.html = TEXT')
+        self.assertRaises(ParseError, self.parse_exp, 'BUG1234 failures/expected/text.html [ Failure ]', is_lint_mode=True)
+
+    def test_bad_bugid(self):
+        try:
+            self.parse_exp('BUG1234 failures/expected/text.html [ Failure ]', is_lint_mode=True)
+            self.fail('should have raised an error about a bad bug identifier')
+        except ParseError as exp:
+            self.assertEqual(len(exp.warnings), 1)
 
     def test_missing_bugid(self):
-        # This should log a non-fatal error.
-        self.parse_exp('SLOW : failures/expected/text.html = TEXT')
+        self.parse_exp('failures/expected/text.html [ Failure ]')
+        self.assertFalse(self._exp.has_warnings())
+
+        self._port.warn_if_bug_missing_in_test_expectations = lambda: True
+
+        self.parse_exp('failures/expected/text.html [ Failure ]')
+        line = self._exp._model.get_expectation_line('failures/expected/text.html')
+        self.assertFalse(line.is_invalid())
+        self.assertEqual(line.warnings, ['Test lacks BUG modifier.'])
+
+    def test_skip_and_wontfix(self):
+        # Skip is not allowed to have other expectations as well, because those
+        # expectations won't be exercised and may become stale .
+        self.parse_exp('failures/expected/text.html [ Failure Skip ]')
         self.assertTrue(self._exp.has_warnings())
 
+        self.parse_exp('failures/expected/text.html [ Crash WontFix ]')
+        self.assertFalse(self._exp.has_warnings())
+
+        self.parse_exp('failures/expected/text.html [ Pass WontFix ]')
+        self.assertFalse(self._exp.has_warnings())
+
     def test_slow_and_timeout(self):
         # A test cannot be SLOW and expected to TIMEOUT.
         self.assertRaises(ParseError, self.parse_exp,
-            'BUG_TEST SLOW : failures/expected/timeout.html = TIMEOUT')
+            'Bug(test) failures/expected/timeout.html [ Slow Timeout ]', is_lint_mode=True)
 
     def test_rebaseline(self):
         # Can't lint a file w/ 'REBASELINE' in it.
         self.assertRaises(ParseError, self.parse_exp,
-            'BUG_TEST REBASELINE : failures/expected/text.html = TEXT',
+            'Bug(test) failures/expected/text.html [ Failure Rebaseline ]',
             is_lint_mode=True)
 
     def test_duplicates(self):
         self.assertRaises(ParseError, self.parse_exp, """
-BUG_EXP : failures/expected/text.html = TEXT
-BUG_EXP : failures/expected/text.html = IMAGE""")
+Bug(exp) failures/expected/text.html [ Failure ]
+Bug(exp) failures/expected/text.html [ ImageOnlyFailure ]""", is_lint_mode=True)
 
         self.assertRaises(ParseError, self.parse_exp,
             self.get_basic_expectations(), overrides="""
-BUG_OVERRIDE : failures/expected/text.html = TEXT
-BUG_OVERRIDE : failures/expected/text.html = IMAGE""", )
+Bug(override) failures/expected/text.html [ Failure ]
+Bug(override) failures/expected/text.html [ ImageOnlyFailure ]""", is_lint_mode=True)
 
     def test_missing_file(self):
-        # This should log a non-fatal error.
-        self.parse_exp('BUG_TEST : missing_file.html = TEXT')
+        self.parse_exp('Bug(test) missing_file.html [ Failure ]')
         self.assertTrue(self._exp.has_warnings(), 1)
 
 
@@ -326,256 +477,247 @@ class PrecedenceTests(Base):
         # This tests handling precedence of specific lines over directories
         # and tests expectations covering entire directories.
         exp_str = """
-BUGX : failures/expected/text.html = TEXT
-BUGX WONTFIX : failures/expected = IMAGE
+Bug(x) failures/expected/text.html [ Failure ]
+Bug(y) failures/expected [ WontFix ]
 """
         self.parse_exp(exp_str)
-        self.assert_exp('failures/expected/text.html', TEXT)
-        self.assert_exp('failures/expected/crash.html', IMAGE)
+        self.assert_exp('failures/expected/text.html', FAIL)
+        self.assert_exp('failures/expected/crash.html', PASS)
 
         exp_str = """
-BUGX WONTFIX : failures/expected = IMAGE
-BUGX : failures/expected/text.html = TEXT
+Bug(x) failures/expected [ WontFix ]
+Bug(y) failures/expected/text.html [ Failure ]
 """
         self.parse_exp(exp_str)
-        self.assert_exp('failures/expected/text.html', TEXT)
-        self.assert_exp('failures/expected/crash.html', IMAGE)
+        self.assert_exp('failures/expected/text.html', FAIL)
+        self.assert_exp('failures/expected/crash.html', PASS)
 
     def test_ambiguous(self):
-        self.assertRaises(ParseError, self.parse_exp, """
-BUG_TEST RELEASE : passes/text.html = PASS
-BUG_TEST WIN : passes/text.html = FAIL
-""")
+        self.assert_bad_expectations("Bug(test) [ Release ] passes/text.html [ Pass ]\n"
+                                     "Bug(test) [ Win ] passes/text.html [ Failure ]\n")
 
     def test_more_modifiers(self):
-        exp_str = """
-BUG_TEST RELEASE : passes/text.html = PASS
-BUG_TEST WIN RELEASE : passes/text.html = TEXT
-"""
-        self.assertRaises(ParseError, self.parse_exp, exp_str)
+        self.assert_bad_expectations("Bug(test) [ Release ] passes/text.html [ Pass ]\n"
+                                     "Bug(test) [ Win Release ] passes/text.html [ Failure ]\n")
 
     def test_order_in_file(self):
-        exp_str = """
-BUG_TEST WIN RELEASE : passes/text.html = TEXT
-BUG_TEST RELEASE : passes/text.html = PASS
-"""
-        self.assertRaises(ParseError, self.parse_exp, exp_str)
+        self.assert_bad_expectations("Bug(test) [ Win Release ] : passes/text.html [ Failure ]\n"
+                                     "Bug(test) [ Release ] : passes/text.html [ Pass ]\n")
 
     def test_macro_overrides(self):
-        exp_str = """
-BUG_TEST WIN : passes/text.html = PASS
-BUG_TEST XP : passes/text.html = TEXT
-"""
-        self.assertRaises(ParseError, self.parse_exp, exp_str)
+        self.assert_bad_expectations("Bug(test) [ Win ] passes/text.html [ Pass ]\n"
+                                     "Bug(test) [ XP ] passes/text.html [ Failure ]\n")
+
+
+class RemoveConfigurationsTest(Base):
+    def test_remove(self):
+        host = MockHost()
+        test_port = host.port_factory.get('test-win-xp', None)
+        test_port.test_exists = lambda test: True
+        test_port.test_isfile = lambda test: True
+
+        test_config = test_port.test_configuration()
+        test_port.expectations_dict = lambda **kwargs: {"expectations": """Bug(x) [ Linux Win Release ] failures/expected/foo.html [ Failure ]
+Bug(y) [ Win Mac Debug ] failures/expected/foo.html [ Crash ]
+"""}
+        expectations = TestExpectations(test_port, self.get_basic_tests())
+        expectations.parse_all_expectations()
+
+        actual_expectations = expectations.remove_configuration_from_test('failures/expected/foo.html', test_config)
+
+        self.assertEqual("""Bug(x) [ 7SP0 Linux Vista Release ] failures/expected/foo.html [ Failure ]
+Bug(y) [ Win Mac Debug ] failures/expected/foo.html [ Crash ]
+""", actual_expectations)
+
+    def test_remove_line(self):
+        host = MockHost()
+        test_port = host.port_factory.get('test-win-xp', None)
+        test_port.test_exists = lambda test: True
+        test_port.test_isfile = lambda test: True
+
+        test_config = test_port.test_configuration()
+        test_port.expectations_dict = lambda **kwargs: {'expectations': """Bug(x) [ Win Release ] failures/expected/foo.html [ Failure ]
+Bug(y) [ Win Debug ] failures/expected/foo.html [ Crash ]
+"""}
+        expectations = TestExpectations(test_port)
+        expectations.parse_all_expectations()
+
+        actual_expectations = expectations.remove_configuration_from_test('failures/expected/foo.html', test_config)
+        actual_expectations = expectations.remove_configuration_from_test('failures/expected/foo.html', host.port_factory.get('test-win-vista', None).test_configuration())
+        actual_expectations = expectations.remove_configuration_from_test('failures/expected/foo.html', host.port_factory.get('test-win-7sp0', None).test_configuration())
+
+        self.assertEqual("""Bug(y) [ Win Debug ] failures/expected/foo.html [ Crash ]
+""", actual_expectations)
 
 
 class RebaseliningTest(Base):
     """Test rebaselining-specific functionality."""
-    def assertRemove(self, input_expectations, tests, expected_expectations):
-        self.parse_exp(input_expectations)
-        actual_expectations = self._exp.remove_rebaselined_tests(tests)
+    def assertRemove(self, input_expectations, input_overrides, tests, expected_expectations, expected_overrides):
+        self.parse_exp(input_expectations, is_lint_mode=False, overrides=input_overrides)
+        actual_expectations = self._exp.remove_rebaselined_tests(tests, 'expectations')
         self.assertEqual(expected_expectations, actual_expectations)
+        actual_overrides = self._exp.remove_rebaselined_tests(tests, 'overrides')
+        self.assertEqual(expected_overrides, actual_overrides)
 
     def test_remove(self):
-        self.assertRemove('BUGX REBASELINE : failures/expected/text.html = TEXT\n'
-                          'BUGY : failures/expected/image.html = IMAGE\n'
-                          'BUGZ REBASELINE : failures/expected/crash.html = CRASH\n',
+        self.assertRemove('Bug(x) failures/expected/text.html [ Failure Rebaseline ]\n'
+                          'Bug(y) failures/expected/image.html [ ImageOnlyFailure Rebaseline ]\n'
+                          'Bug(z) failures/expected/crash.html [ Crash ]\n',
+                          'Bug(x0) failures/expected/image.html [ Crash ]\n',
                           ['failures/expected/text.html'],
-                          'BUGY : failures/expected/image.html = IMAGE\n'
-                          'BUGZ REBASELINE : failures/expected/crash.html = CRASH\n')
+                          'Bug(y) failures/expected/image.html [ ImageOnlyFailure Rebaseline ]\n'
+                          'Bug(z) failures/expected/crash.html [ Crash ]\n',
+                          'Bug(x0) failures/expected/image.html [ Crash ]\n')
+
+        # Ensure that we don't modify unrelated lines, even if we could rewrite them.
+        # i.e., the second line doesn't get rewritten to "Bug(y) failures/expected/skip.html"
+        self.assertRemove('Bug(x) failures/expected/text.html [ Failure Rebaseline ]\n'
+                          'Bug(Y) failures/expected/image.html [ Skip   ]\n'
+                          'Bug(z) failures/expected/crash.html\n',
+                          '',
+                          ['failures/expected/text.html'],
+                          'Bug(Y) failures/expected/image.html [ Skip   ]\n'
+                          'Bug(z) failures/expected/crash.html\n',
+                          '')
+
+    def test_get_rebaselining_failures(self):
+        # Make sure we find a test as needing a rebaseline even if it is not marked as a failure.
+        self.parse_exp('Bug(x) failures/expected/text.html [ Rebaseline ]\n')
+        self.assertEqual(len(self._exp.get_rebaselining_failures()), 1)
 
-    def test_no_get_rebaselining_failures(self):
         self.parse_exp(self.get_basic_expectations())
         self.assertEqual(len(self._exp.get_rebaselining_failures()), 0)
 
 
-class TestExpectationParserTests(unittest.TestCase):
-    def test_tokenize_blank(self):
-        expectation = TestExpectationParser.tokenize('')
-        self.assertEqual(expectation.is_malformed(), False)
-        self.assertEqual(expectation.comment, None)
-        self.assertEqual(len(expectation.errors), 0)
-
-    def test_tokenize_missing_colon(self):
-        expectation = TestExpectationParser.tokenize('Qux.')
-        self.assertEqual(expectation.is_malformed(), True)
-        self.assertEqual(str(expectation.errors), '["Missing a \':\'"]')
-
-    def test_tokenize_extra_colon(self):
-        expectation = TestExpectationParser.tokenize('FOO : : bar')
-        self.assertEqual(expectation.is_malformed(), True)
-        self.assertEqual(str(expectation.errors), '["Extraneous \':\'"]')
-
-    def test_tokenize_empty_comment(self):
-        expectation = TestExpectationParser.tokenize('//')
-        self.assertEqual(expectation.is_malformed(), False)
-        self.assertEqual(expectation.comment, '')
-        self.assertEqual(len(expectation.errors), 0)
-
-    def test_tokenize_comment(self):
-        expectation = TestExpectationParser.tokenize('//Qux.')
-        self.assertEqual(expectation.is_malformed(), False)
-        self.assertEqual(expectation.comment, 'Qux.')
-        self.assertEqual(len(expectation.errors), 0)
-
-    def test_tokenize_missing_equal(self):
-        expectation = TestExpectationParser.tokenize('FOO : bar')
-        self.assertEqual(expectation.is_malformed(), True)
-        self.assertEqual(str(expectation.errors), "['Missing expectations\']")
-
-    def test_tokenize_extra_equal(self):
-        expectation = TestExpectationParser.tokenize('FOO : bar = BAZ = Qux.')
-        self.assertEqual(expectation.is_malformed(), True)
-        self.assertEqual(str(expectation.errors), '["Extraneous \'=\'"]')
-
-    def test_tokenize_valid(self):
-        expectation = TestExpectationParser.tokenize('FOO : bar = BAZ')
-        self.assertEqual(expectation.is_malformed(), False)
-        self.assertEqual(expectation.comment, None)
-        self.assertEqual(len(expectation.errors), 0)
-
-    def test_tokenize_valid_with_comment(self):
-        expectation = TestExpectationParser.tokenize('FOO : bar = BAZ //Qux.')
-        self.assertEqual(expectation.is_malformed(), False)
-        self.assertEqual(expectation.comment, 'Qux.')
-        self.assertEqual(str(expectation.modifiers), '[\'foo\']')
-        self.assertEqual(str(expectation.expectations), '[\'baz\']')
-        self.assertEqual(len(expectation.errors), 0)
-
-    def test_tokenize_valid_with_multiple_modifiers(self):
-        expectation = TestExpectationParser.tokenize('FOO1 FOO2 : bar = BAZ //Qux.')
-        self.assertEqual(expectation.is_malformed(), False)
-        self.assertEqual(expectation.comment, 'Qux.')
-        self.assertEqual(str(expectation.modifiers), '[\'foo1\', \'foo2\']')
-        self.assertEqual(str(expectation.expectations), '[\'baz\']')
-        self.assertEqual(len(expectation.errors), 0)
-
-    def test_parse_empty_string(self):
-        test_port = port.get('test-win-xp', None)
-        test_port.test_exists = lambda test: True
-        test_config = test_port.test_configuration()
-        full_test_list = []
-        expectation_line = TestExpectationParser.tokenize('')
-        parser = TestExpectationParser(test_port, full_test_list, allow_rebaseline_modifier=False)
-        parser.parse(expectation_line)
-        self.assertFalse(expectation_line.is_invalid())
-
-
-class TestExpectationSerializerTests(unittest.TestCase):
+class TestExpectationSerializationTests(unittest.TestCase):
     def __init__(self, testFunc):
-        test_port = port.get('test-win-xp', None)
+        host = MockHost()
+        test_port = host.port_factory.get('test-win-xp', None)
         self._converter = TestConfigurationConverter(test_port.all_test_configurations(), test_port.configuration_specifier_macros())
-        self._serializer = TestExpectationSerializer(self._converter)
         unittest.TestCase.__init__(self, testFunc)
 
+    def _tokenize(self, line):
+        return TestExpectationParser._tokenize_line('path', line, 0)
+
     def assert_round_trip(self, in_string, expected_string=None):
-        expectation = TestExpectationParser.tokenize(in_string)
+        expectation = self._tokenize(in_string)
         if expected_string is None:
             expected_string = in_string
-        self.assertEqual(expected_string, self._serializer.to_string(expectation))
+        self.assertEqual(expected_string, expectation.to_string(self._converter))
 
     def assert_list_round_trip(self, in_string, expected_string=None):
-        expectations = TestExpectationParser.tokenize_list(in_string)
+        host = MockHost()
+        parser = TestExpectationParser(host.port_factory.get('test-win-xp', None), [], allow_rebaseline_modifier=False)
+        expectations = parser.parse('path', in_string)
         if expected_string is None:
             expected_string = in_string
-        self.assertEqual(expected_string, TestExpectationSerializer.list_to_string(expectations, self._converter))
+        self.assertEqual(expected_string, TestExpectations.list_to_string(expectations, self._converter))
 
     def test_unparsed_to_string(self):
         expectation = TestExpectationLine()
 
-        self.assertEqual(self._serializer.to_string(expectation), '')
-        expectation.comment = 'Qux.'
-        self.assertEqual(self._serializer.to_string(expectation), '//Qux.')
+        self.assertEqual(expectation.to_string(self._converter), '')
+        expectation.comment = ' Qux.'
+        self.assertEqual(expectation.to_string(self._converter), '# Qux.')
         expectation.name = 'bar'
-        self.assertEqual(self._serializer.to_string(expectation), ' : bar =  //Qux.')
+        self.assertEqual(expectation.to_string(self._converter), 'bar # Qux.')
         expectation.modifiers = ['foo']
-        self.assertEqual(self._serializer.to_string(expectation), 'FOO : bar =  //Qux.')
+        # FIXME: case should be preserved here but we can't until we drop the old syntax.
+        self.assertEqual(expectation.to_string(self._converter), '[ FOO ] bar # Qux.')
         expectation.expectations = ['bAz']
-        self.assertEqual(self._serializer.to_string(expectation), 'FOO : bar = BAZ //Qux.')
+        self.assertEqual(expectation.to_string(self._converter), '[ FOO ] bar [ BAZ ] # Qux.')
         expectation.expectations = ['bAz1', 'baZ2']
-        self.assertEqual(self._serializer.to_string(expectation), 'FOO : bar = BAZ1 BAZ2 //Qux.')
+        self.assertEqual(expectation.to_string(self._converter), '[ FOO ] bar [ BAZ1 BAZ2 ] # Qux.')
         expectation.modifiers = ['foo1', 'foO2']
-        self.assertEqual(self._serializer.to_string(expectation), 'FOO1 FOO2 : bar = BAZ1 BAZ2 //Qux.')
-        expectation.errors.append('Oh the horror.')
-        self.assertEqual(self._serializer.to_string(expectation), '')
+        self.assertEqual(expectation.to_string(self._converter), '[ FOO1 FOO2 ] bar [ BAZ1 BAZ2 ] # Qux.')
+        expectation.warnings.append('Oh the horror.')
+        self.assertEqual(expectation.to_string(self._converter), '')
         expectation.original_string = 'Yes it is!'
-        self.assertEqual(self._serializer.to_string(expectation), 'Yes it is!')
+        self.assertEqual(expectation.to_string(self._converter), 'Yes it is!')
+
+    def test_unparsed_list_to_string(self):
+        expectation = TestExpectationLine()
+        expectation.comment = 'Qux.'
+        expectation.name = 'bar'
+        expectation.modifiers = ['foo']
+        expectation.expectations = ['bAz1', 'baZ2']
+        # FIXME: case should be preserved here but we can't until we drop the old syntax.
+        self.assertEqual(TestExpectations.list_to_string([expectation]), '[ FOO ] bar [ BAZ1 BAZ2 ] #Qux.')
 
     def test_parsed_to_string(self):
         expectation_line = TestExpectationLine()
         expectation_line.parsed_bug_modifiers = ['BUGX']
         expectation_line.name = 'test/name/for/realz.html'
         expectation_line.parsed_expectations = set([IMAGE])
-        self.assertEqual(self._serializer.to_string(expectation_line), None)
-        expectation_line.matching_configurations = set([TestConfiguration('xp', 'x86', 'release', 'cpu')])
-        self.assertEqual(self._serializer.to_string(expectation_line), 'BUGX XP RELEASE CPU : test/name/for/realz.html = IMAGE')
-        expectation_line.matching_configurations = set([TestConfiguration('xp', 'x86', 'release', 'cpu'), TestConfiguration('xp', 'x86', 'release', 'gpu')])
-        self.assertEqual(self._serializer.to_string(expectation_line), 'BUGX XP RELEASE : test/name/for/realz.html = IMAGE')
-        expectation_line.matching_configurations = set([TestConfiguration('xp', 'x86', 'release', 'cpu'), TestConfiguration('xp', 'x86', 'debug', 'gpu')])
-        self.assertEqual(self._serializer.to_string(expectation_line), 'BUGX XP RELEASE CPU : test/name/for/realz.html = IMAGE\nBUGX XP DEBUG GPU : test/name/for/realz.html = IMAGE')
-
-    def test_parsed_expectations_string(self):
+        self.assertEqual(expectation_line.to_string(self._converter), None)
+        expectation_line.matching_configurations = set([TestConfiguration('xp', 'x86', 'release')])
+        self.assertEqual(expectation_line.to_string(self._converter), 'Bug(x) [ XP Release ] test/name/for/realz.html [ ImageOnlyFailure ]')
+        expectation_line.matching_configurations = set([TestConfiguration('xp', 'x86', 'release'), TestConfiguration('xp', 'x86', 'debug')])
+        self.assertEqual(expectation_line.to_string(self._converter), 'Bug(x) [ XP ] test/name/for/realz.html [ ImageOnlyFailure ]')
+
+    def test_serialize_parsed_expectations(self):
         expectation_line = TestExpectationLine()
         expectation_line.parsed_expectations = set([])
-        self.assertEqual(self._serializer._parsed_expectations_string(expectation_line), '')
-        expectation_line.parsed_expectations = set([IMAGE_PLUS_TEXT])
-        self.assertEqual(self._serializer._parsed_expectations_string(expectation_line), 'image+text')
-        expectation_line.parsed_expectations = set([PASS, FAIL])
-        self.assertEqual(self._serializer._parsed_expectations_string(expectation_line), 'pass fail')
+        parsed_expectation_to_string = dict([[parsed_expectation, expectation_string] for expectation_string, parsed_expectation in TestExpectations.EXPECTATIONS.items()])
+        self.assertEqual(expectation_line._serialize_parsed_expectations(parsed_expectation_to_string), '')
+        expectation_line.parsed_expectations = set([FAIL])
+        self.assertEqual(expectation_line._serialize_parsed_expectations(parsed_expectation_to_string), 'fail')
+        expectation_line.parsed_expectations = set([PASS, IMAGE])
+        self.assertEqual(expectation_line._serialize_parsed_expectations(parsed_expectation_to_string), 'pass image')
         expectation_line.parsed_expectations = set([FAIL, PASS])
-        self.assertEqual(self._serializer._parsed_expectations_string(expectation_line), 'pass fail')
+        self.assertEqual(expectation_line._serialize_parsed_expectations(parsed_expectation_to_string), 'pass fail')
 
-    def test_parsed_modifier_string(self):
+    def test_serialize_parsed_modifier_string(self):
         expectation_line = TestExpectationLine()
         expectation_line.parsed_bug_modifiers = ['garden-o-matic']
         expectation_line.parsed_modifiers = ['for', 'the']
-        self.assertEqual(self._serializer._parsed_modifier_string(expectation_line, []), 'garden-o-matic for the')
-        self.assertEqual(self._serializer._parsed_modifier_string(expectation_line, ['win']), 'garden-o-matic for the win')
+        self.assertEqual(expectation_line._serialize_parsed_modifiers(self._converter, []), 'garden-o-matic for the')
+        self.assertEqual(expectation_line._serialize_parsed_modifiers(self._converter, ['win']), 'garden-o-matic for the win')
         expectation_line.parsed_bug_modifiers = []
         expectation_line.parsed_modifiers = []
-        self.assertEqual(self._serializer._parsed_modifier_string(expectation_line, []), '')
-        self.assertEqual(self._serializer._parsed_modifier_string(expectation_line, ['win']), 'win')
+        self.assertEqual(expectation_line._serialize_parsed_modifiers(self._converter, []), '')
+        self.assertEqual(expectation_line._serialize_parsed_modifiers(self._converter, ['win']), 'win')
         expectation_line.parsed_bug_modifiers = ['garden-o-matic', 'total', 'is']
-        self.assertEqual(self._serializer._parsed_modifier_string(expectation_line, ['win']), 'garden-o-matic is total win')
+        self.assertEqual(expectation_line._serialize_parsed_modifiers(self._converter, ['win']), 'garden-o-matic is total win')
         expectation_line.parsed_bug_modifiers = []
         expectation_line.parsed_modifiers = ['garden-o-matic', 'total', 'is']
-        self.assertEqual(self._serializer._parsed_modifier_string(expectation_line, ['win']), 'garden-o-matic is total win')
+        self.assertEqual(expectation_line._serialize_parsed_modifiers(self._converter, ['win']), 'garden-o-matic is total win')
 
-    def test_format_result(self):
-        self.assertEqual(TestExpectationSerializer._format_result('modifiers', 'name', 'expectations', 'comment'), 'MODIFIERS : name = EXPECTATIONS //comment')
-        self.assertEqual(TestExpectationSerializer._format_result('modifiers', 'name', 'expectations', None), 'MODIFIERS : name = EXPECTATIONS')
+    def test_format_line(self):
+        self.assertEqual(TestExpectationLine._format_line(['MODIFIERS'], 'name', ['EXPECTATIONS'], 'comment'), '[ MODIFIERS ] name [ EXPECTATIONS ] #comment')
+        self.assertEqual(TestExpectationLine._format_line(['MODIFIERS'], 'name', ['EXPECTATIONS'], None), '[ MODIFIERS ] name [ EXPECTATIONS ]')
 
     def test_string_roundtrip(self):
         self.assert_round_trip('')
         self.assert_round_trip('FOO')
-        self.assert_round_trip(':')
-        self.assert_round_trip('FOO :')
-        self.assert_round_trip('FOO : bar')
-        self.assert_round_trip('  FOO :')
-        self.assert_round_trip('    FOO : bar')
-        self.assert_round_trip('FOO : bar = BAZ')
-        self.assert_round_trip('FOO : bar = BAZ //Qux.')
-        self.assert_round_trip('FOO : bar = BAZ // Qux.')
-        self.assert_round_trip('FOO : bar = BAZ // Qux.     ')
-        self.assert_round_trip('FOO : bar = BAZ //        Qux.     ')
-        self.assert_round_trip('FOO : : bar = BAZ')
-        self.assert_round_trip('FOO : : bar = BAZ')
-        self.assert_round_trip('FOO : : bar ==== BAZ')
+        self.assert_round_trip('[')
+        self.assert_round_trip('FOO [')
+        self.assert_round_trip('FOO ] bar')
+        self.assert_round_trip('  FOO [')
+        self.assert_round_trip('    [ FOO ] ')
+        self.assert_round_trip('[ FOO ] bar [ BAZ ]')
+        self.assert_round_trip('[ FOO ] bar [ BAZ ] # Qux.')
+        self.assert_round_trip('[ FOO ] bar [ BAZ ] # Qux.')
+        self.assert_round_trip('[ FOO ] bar [ BAZ ] # Qux.     ')
+        self.assert_round_trip('[ FOO ] bar [ BAZ ] #        Qux.     ')
+        self.assert_round_trip('[ FOO ] ] ] bar BAZ')
+        self.assert_round_trip('[ FOO ] ] ] bar [ BAZ ]')
+        self.assert_round_trip('FOO ] ] bar ==== BAZ')
         self.assert_round_trip('=')
-        self.assert_round_trip('//')
-        self.assert_round_trip('// ')
-        self.assert_round_trip('// Foo')
-        self.assert_round_trip('// Foo')
-        self.assert_round_trip('// Foo :')
-        self.assert_round_trip('// Foo : =')
+        self.assert_round_trip('#')
+        self.assert_round_trip('# ')
+        self.assert_round_trip('# Foo')
+        self.assert_round_trip('# Foo')
+        self.assert_round_trip('# Foo :')
+        self.assert_round_trip('# Foo : =')
 
     def test_list_roundtrip(self):
         self.assert_list_round_trip('')
         self.assert_list_round_trip('\n')
         self.assert_list_round_trip('\n\n')
         self.assert_list_round_trip('bar')
-        self.assert_list_round_trip('bar\n//Qux.')
-        self.assert_list_round_trip('bar\n//Qux.\n')
+        self.assert_list_round_trip('bar\nQux.')
+        self.assert_list_round_trip('bar\nQux.\n')
 
     def test_reconstitute_only_these(self):
         lines = []
@@ -592,348 +734,19 @@ class TestExpectationSerializerTests(unittest.TestCase):
             if reconstitute:
                 reconstitute_only_these.append(expectation_line)
 
-        add_line(set([TestConfiguration('xp', 'x86', 'release', 'cpu')]), False)
-        add_line(set([TestConfiguration('xp', 'x86', 'release', 'cpu'), TestConfiguration('xp', 'x86', 'release', 'gpu')]), True)
-        add_line(set([TestConfiguration('xp', 'x86', 'release', 'cpu'), TestConfiguration('xp', 'x86', 'debug', 'gpu')]), False)
-        serialized = TestExpectationSerializer.list_to_string(lines, self._converter)
-        self.assertEquals(serialized, "BUGX XP RELEASE CPU : Yay = IMAGE\nBUGX XP RELEASE : Yay = IMAGE\nBUGX XP RELEASE CPU : Yay = IMAGE\nBUGX XP DEBUG GPU : Yay = IMAGE")
-        serialized = TestExpectationSerializer.list_to_string(lines, self._converter, reconstitute_only_these=reconstitute_only_these)
-        self.assertEquals(serialized, "Nay\nBUGX XP RELEASE : Yay = IMAGE\nNay")
+        add_line(set([TestConfiguration('xp', 'x86', 'release')]), True)
+        add_line(set([TestConfiguration('xp', 'x86', 'release'), TestConfiguration('xp', 'x86', 'debug')]), False)
+        serialized = TestExpectations.list_to_string(lines, self._converter)
+        self.assertEqual(serialized, "Bug(x) [ XP Release ] Yay [ ImageOnlyFailure ]\nBug(x) [ XP ] Yay [ ImageOnlyFailure ]")
+        serialized = TestExpectations.list_to_string(lines, self._converter, reconstitute_only_these=reconstitute_only_these)
+        self.assertEqual(serialized, "Bug(x) [ XP Release ] Yay [ ImageOnlyFailure ]\nNay")
 
-    def test_string_whitespace_stripping(self):
+    def disabled_test_string_whitespace_stripping(self):
+        # FIXME: Re-enable this test once we rework the code to no longer support the old syntax.
         self.assert_round_trip('\n', '')
-        self.assert_round_trip('  FOO : bar = BAZ', 'FOO : bar = BAZ')
-        self.assert_round_trip('FOO    : bar = BAZ', 'FOO : bar = BAZ')
-        self.assert_round_trip('FOO : bar = BAZ       // Qux.', 'FOO : bar = BAZ // Qux.')
-        self.assert_round_trip('FOO : bar =        BAZ // Qux.', 'FOO : bar = BAZ // Qux.')
-        self.assert_round_trip('FOO :       bar =    BAZ // Qux.', 'FOO : bar = BAZ // Qux.')
-        self.assert_round_trip('FOO :       bar     =    BAZ // Qux.', 'FOO : bar = BAZ // Qux.')
-
-
-class TestExpectationEditorTests(unittest.TestCase):
-    WIN_RELEASE_CPU_CONFIGS = set([
-        TestConfiguration('vista', 'x86', 'release', 'cpu'),
-        TestConfiguration('win7', 'x86', 'release', 'cpu'),
-        TestConfiguration('xp', 'x86', 'release', 'cpu'),
-    ])
-
-    RELEASE_CONFIGS = set([
-        TestConfiguration('vista', 'x86', 'release', 'cpu'),
-        TestConfiguration('win7', 'x86', 'release', 'cpu'),
-        TestConfiguration('xp', 'x86', 'release', 'cpu'),
-        TestConfiguration('vista', 'x86', 'release', 'gpu'),
-        TestConfiguration('win7', 'x86', 'release', 'gpu'),
-        TestConfiguration('xp', 'x86', 'release', 'gpu'),
-        TestConfiguration('snowleopard', 'x86', 'release', 'cpu'),
-        TestConfiguration('leopard', 'x86', 'release', 'cpu'),
-        TestConfiguration('snowleopard', 'x86', 'release', 'gpu'),
-        TestConfiguration('leopard', 'x86', 'release', 'gpu'),
-        TestConfiguration('lucid', 'x86', 'release', 'cpu'),
-        TestConfiguration('lucid', 'x86_64', 'release', 'cpu'),
-        TestConfiguration('lucid', 'x86', 'release', 'gpu'),
-        TestConfiguration('lucid', 'x86_64', 'release', 'gpu'),
-    ])
-
-    def __init__(self, testFunc):
-        self.test_port = port.get('test-win-xp', None)
-        self.full_test_list = ['failures/expected/keyboard.html', 'failures/expected/audio.html']
-        unittest.TestCase.__init__(self, testFunc)
-
-    def make_parsed_expectation_lines(self, in_string):
-        expectation_lines = TestExpectationParser.tokenize_list(in_string)
-        parser = TestExpectationParser(self.test_port, self.full_test_list, allow_rebaseline_modifier=False)
-        for expectation_line in expectation_lines:
-            self.assertFalse(expectation_line.is_invalid())
-            parser.parse(expectation_line)
-        return expectation_lines
-
-    def assert_remove_roundtrip(self, in_string, test, expected_string, remove_flakes=False):
-        test_config_set = set([self.test_port.test_configuration()])
-        expectation_lines = self.make_parsed_expectation_lines(in_string)
-        editor = TestExpectationsEditor(expectation_lines, MockBugManager())
-        editor.remove_expectation(test, test_config_set, remove_flakes)
-        converter = TestConfigurationConverter(self.test_port.all_test_configurations(), self.test_port.configuration_specifier_macros())
-        result = TestExpectationSerializer.list_to_string(expectation_lines, converter)
-        self.assertEquals(result, expected_string)
-
-    def assert_update_roundtrip(self, in_string, test, expectation_set, expected_string, expected_update_count, remove_flakes=False, parsed_bug_modifiers=None, test_configs=None):
-        test_config_set = test_configs or set([self.test_port.test_configuration()])
-        expectation_lines = self.make_parsed_expectation_lines(in_string)
-        editor = TestExpectationsEditor(expectation_lines, MockBugManager())
-        updated_expectation_lines = editor.update_expectation(test, test_config_set, expectation_set, parsed_bug_modifiers=parsed_bug_modifiers)
-        for updated_expectation_line in updated_expectation_lines:
-            self.assertTrue(updated_expectation_line in expectation_lines)
-        self.assertEquals(len(updated_expectation_lines), expected_update_count)
-        converter = TestConfigurationConverter(self.test_port.all_test_configurations(), self.test_port.configuration_specifier_macros())
-        result = TestExpectationSerializer.list_to_string(expectation_lines, converter)
-        self.assertEquals(result, expected_string)
-
-    def test_remove_expectation(self):
-        self.assert_remove_roundtrip("""
-BUGX1 XP DEBUG : failures/expected/keyboard.html = IMAGE
-BUGX2 WIN : failures/expected/audio.html = IMAGE""", 'failures/expected/hang.html', """
-BUGX1 XP DEBUG : failures/expected/keyboard.html = IMAGE
-BUGX2 WIN : failures/expected/audio.html = IMAGE""")
-
-        self.assert_remove_roundtrip("""
-BUGX1 XP DEBUG : failures/expected/keyboard.html = IMAGE
-BUGX2 WIN : failures/expected/audio.html = IMAGE""", 'failures/expected/keyboard.html', """
-BUGX1 XP DEBUG : failures/expected/keyboard.html = IMAGE
-BUGX2 WIN : failures/expected/audio.html = IMAGE""")
-
-        self.assert_remove_roundtrip("""
-BUGX1 XP DEBUG : failures/expected/keyboard.html = IMAGE
-BUGX2 WIN : failures/expected/audio.html = IMAGE""", 'failures/expected/keyboard.html', """
-BUGX1 XP DEBUG : failures/expected/keyboard.html = IMAGE
-BUGX2 WIN : failures/expected/audio.html = IMAGE""")
-
-        self.assert_remove_roundtrip("""
-BUGX1 MAC : failures/expected/keyboard.html = IMAGE
-BUGX2 WIN : failures/expected/audio.html = IMAGE""", 'failures/expected/keyboard.html',  """
-BUGX1 MAC : failures/expected/keyboard.html = IMAGE
-BUGX2 WIN : failures/expected/audio.html = IMAGE""")
-
-        self.assert_remove_roundtrip("""
-BUGX1 XP RELEASE CPU : failures/expected/keyboard.html = IMAGE
-BUGX2 WIN : failures/expected/audio.html = IMAGE""", 'failures/expected/keyboard.html', """
-BUGX2 WIN : failures/expected/audio.html = IMAGE""")
-
-        self.assert_remove_roundtrip("""
-BUGX1 WIN : failures/expected/keyboard.html = IMAGE
-BUGX2 WIN : failures/expected/audio.html = IMAGE""", 'failures/expected/keyboard.html', """
-BUGX1 XP RELEASE GPU : failures/expected/keyboard.html = IMAGE
-BUGX1 XP DEBUG : failures/expected/keyboard.html = IMAGE
-BUGX1 VISTA WIN7 : failures/expected/keyboard.html = IMAGE
-BUGX2 WIN : failures/expected/audio.html = IMAGE""")
-
-        self.assert_remove_roundtrip("""
-BUGX1 XP : failures/expected/keyboard.html = IMAGE
-BUGX2 WIN : failures/expected/audio.html = IMAGE""", 'failures/expected/keyboard.html', """
-BUGX1 XP DEBUG CPU : failures/expected/keyboard.html = IMAGE
-BUGX1 XP GPU : failures/expected/keyboard.html = IMAGE
-BUGX2 WIN : failures/expected/audio.html = IMAGE""")
-
-        self.assert_remove_roundtrip("""
-BUGX1 : failures/expected/keyboard.html = IMAGE
-BUGX2 WIN : failures/expected/audio.html = IMAGE""", 'failures/expected/keyboard.html', """
-BUGX1 XP RELEASE GPU : failures/expected/keyboard.html = IMAGE
-BUGX1 XP DEBUG : failures/expected/keyboard.html = IMAGE
-BUGX1 LINUX MAC VISTA WIN7 : failures/expected/keyboard.html = IMAGE
-BUGX2 WIN : failures/expected/audio.html = IMAGE""")
-
-        self.assert_remove_roundtrip("""
-BUGX1 WIN : failures/expected = PASS
-BUGX2 XP RELEASE : failures/expected/keyboard.html = IMAGE""", 'failures/expected/keyboard.html', """
-BUGX1 WIN : failures/expected = PASS
-BUGX2 XP RELEASE GPU : failures/expected/keyboard.html = IMAGE""")
-
-        self.assert_remove_roundtrip("""
-BUGX1 XP RELEASE CPU : failures/expected/keyboard.html = IMAGE
-BUGX2 XP DEBUG CPU : failures/expected/keyboard.html = IMAGE""", 'failures/expected/keyboard.html', """
-BUGX2 XP DEBUG CPU : failures/expected/keyboard.html = IMAGE""")
-
-        self.assert_remove_roundtrip("""
-BUGX1 WIN : failures/expected = FAIL""", 'failures/expected/keyboard.html', """
-BUGX1 WIN : failures/expected = FAIL""")
-
-        self.assert_remove_roundtrip("""
-BUGX1 XP RELEASE CPU : failures/expected/keyboard.html = IMAGE PASS
-BUGX2 XP DEBUG CPU : failures/expected/keyboard.html = IMAGE""", 'failures/expected/keyboard.html', """
-BUGX1 XP RELEASE CPU : failures/expected/keyboard.html = PASS IMAGE
-BUGX2 XP DEBUG CPU : failures/expected/keyboard.html = IMAGE""")
-
-        self.assert_remove_roundtrip("""
-BUGX1 XP RELEASE CPU : failures/expected/keyboard.html = IMAGE PASS
-BUGX2 XP DEBUG CPU : failures/expected/keyboard.html = IMAGE""", 'failures/expected/keyboard.html', """
-BUGX2 XP DEBUG CPU : failures/expected/keyboard.html = IMAGE""", remove_flakes=True)
-
-    def test_remove_expectation_multiple(self):
-        in_string = """
-BUGX1 WIN : failures/expected/keyboard.html = IMAGE
-BUGX2 WIN : failures/expected/audio.html = IMAGE"""
-        expectation_lines = self.make_parsed_expectation_lines(in_string)
-        converter = TestConfigurationConverter(self.test_port.all_test_configurations(), self.test_port.configuration_specifier_macros())
-        editor = TestExpectationsEditor(expectation_lines, MockBugManager())
-        test = "failures/expected/keyboard.html"
-
-        editor.remove_expectation(test, set([TestConfiguration('xp', 'x86', 'release', 'cpu')]))
-        self.assertEquals(TestExpectationSerializer.list_to_string(expectation_lines, converter), """
-BUGX1 XP RELEASE GPU : failures/expected/keyboard.html = IMAGE
-BUGX1 XP DEBUG : failures/expected/keyboard.html = IMAGE
-BUGX1 VISTA WIN7 : failures/expected/keyboard.html = IMAGE
-BUGX2 WIN : failures/expected/audio.html = IMAGE""")
-
-        editor.remove_expectation(test, set([TestConfiguration('xp', 'x86', 'debug', 'cpu')]))
-        self.assertEquals(TestExpectationSerializer.list_to_string(expectation_lines, converter), """
-BUGX1 XP GPU : failures/expected/keyboard.html = IMAGE
-BUGX1 VISTA WIN7 : failures/expected/keyboard.html = IMAGE
-BUGX2 WIN : failures/expected/audio.html = IMAGE""")
-
-        editor.remove_expectation(test, set([TestConfiguration('vista', 'x86', 'debug', 'gpu'), TestConfiguration('win7', 'x86', 'release', 'gpu')]))
-        self.assertEquals(TestExpectationSerializer.list_to_string(expectation_lines, converter), """
-BUGX1 VISTA DEBUG CPU : failures/expected/keyboard.html = IMAGE
-BUGX1 WIN7 DEBUG GPU : failures/expected/keyboard.html = IMAGE
-BUGX1 WIN7 CPU : failures/expected/keyboard.html = IMAGE
-BUGX1 XP GPU : failures/expected/keyboard.html = IMAGE
-BUGX1 VISTA RELEASE : failures/expected/keyboard.html = IMAGE
-BUGX2 WIN : failures/expected/audio.html = IMAGE""")
-
-        editor.remove_expectation(test, set([TestConfiguration('xp', 'x86', 'debug', 'gpu'), TestConfiguration('xp', 'x86', 'release', 'gpu')]))
-        self.assertEquals(TestExpectationSerializer.list_to_string(expectation_lines, converter), """
-BUGX1 VISTA DEBUG CPU : failures/expected/keyboard.html = IMAGE
-BUGX1 WIN7 RELEASE CPU : failures/expected/keyboard.html = IMAGE
-BUGX1 WIN7 DEBUG : failures/expected/keyboard.html = IMAGE
-BUGX1 VISTA RELEASE : failures/expected/keyboard.html = IMAGE
-BUGX2 WIN : failures/expected/audio.html = IMAGE""")
-
-        editor.remove_expectation(test, set([TestConfiguration('vista', 'x86', 'debug', 'cpu'), TestConfiguration('vista', 'x86', 'debug', 'gpu'), TestConfiguration('vista', 'x86', 'release', 'gpu')]))
-        self.assertEquals(TestExpectationSerializer.list_to_string(expectation_lines, converter), """
-BUGX1 WIN7 DEBUG : failures/expected/keyboard.html = IMAGE
-BUGX1 VISTA WIN7 RELEASE CPU : failures/expected/keyboard.html = IMAGE
-BUGX2 WIN : failures/expected/audio.html = IMAGE""")
-
-        editor.remove_expectation(test, set(self.test_port.all_test_configurations()))
-        self.assertEquals(TestExpectationSerializer.list_to_string(expectation_lines, converter), """
-BUGX2 WIN : failures/expected/audio.html = IMAGE""")
-
-        test = "failures/expected/audio.html"
-
-        editor.remove_expectation(test, set(self.test_port.all_test_configurations()))
-        self.assertEquals(TestExpectationSerializer.list_to_string(expectation_lines, converter), "")
-
-    def test_update_expectation(self):
-        self.assert_update_roundtrip("""
-BUGX1 XP RELEASE CPU : failures/expected/keyboard.html = TEXT""", 'failures/expected/keyboard.html', set([IMAGE]), """
-BUG_NEWLY_CREATED XP RELEASE CPU : failures/expected/keyboard.html = IMAGE""", 1)
-
-        self.assert_update_roundtrip("""
-BUGX1 XP RELEASE CPU : failures/expected/keyboard.html = TEXT""", 'failures/expected/keyboard.html', set([PASS]), '', 1)
-
-        self.assert_update_roundtrip("""
-BUGX1 XP RELEASE CPU : failures/expected = TEXT""", 'failures/expected/keyboard.html', set([IMAGE]), """
-BUGX1 XP RELEASE CPU : failures/expected = TEXT
-BUG_NEWLY_CREATED XP RELEASE CPU : failures/expected/keyboard.html = IMAGE""", 1)
-
-        self.assert_update_roundtrip("""
-BUGX1 XP RELEASE CPU : failures/expected = TEXT""", 'failures/expected/keyboard.html', set([PASS]), """
-BUGX1 XP RELEASE CPU : failures/expected = TEXT
-BUG_NEWLY_CREATED XP RELEASE CPU : failures/expected/keyboard.html = PASS""", 1)
-
-        self.assert_update_roundtrip("""
-BUGX1 XP RELEASE CPU : failures/expected/keyboard.html = TEXT""", 'failures/expected/keyboard.html', set([TEXT]), """
-BUGX1 XP RELEASE CPU : failures/expected/keyboard.html = TEXT""", 0)
-
-        self.assert_update_roundtrip("""
-BUGX1 XP RELEASE CPU : failures/expected/keyboard.html = TEXT""", 'failures/expected/keyboard.html', set([IMAGE]), """
-BUGAWESOME XP RELEASE CPU : failures/expected/keyboard.html = IMAGE""", 1, parsed_bug_modifiers=['BUGAWESOME'])
-
-        self.assert_update_roundtrip("""
-BUGX1 XP RELEASE : failures/expected/keyboard.html = TEXT""", 'failures/expected/keyboard.html', set([IMAGE]), """
-BUGX1 XP RELEASE GPU : failures/expected/keyboard.html = TEXT
-BUG_NEWLY_CREATED XP RELEASE CPU : failures/expected/keyboard.html = IMAGE""", 2)
-
-        self.assert_update_roundtrip("""
-BUGX1 XP RELEASE : failures/expected/keyboard.html = TEXT""", 'failures/expected/keyboard.html', set([PASS]), """
-BUGX1 XP RELEASE GPU : failures/expected/keyboard.html = TEXT""", 1)
-
-        self.assert_update_roundtrip("""
-BUGX1 XP RELEASE : failures/expected/keyboard.html = TEXT""", 'failures/expected/keyboard.html', set([IMAGE]), """
-BUGX1 XP RELEASE GPU : failures/expected/keyboard.html = TEXT
-BUGAWESOME XP RELEASE CPU : failures/expected/keyboard.html = IMAGE""", 2, parsed_bug_modifiers=['BUGAWESOME'])
-
-        self.assert_update_roundtrip("""
-BUGX1 WIN : failures/expected/keyboard.html = TEXT""", 'failures/expected/keyboard.html', set([IMAGE]), """
-BUGX1 XP DEBUG CPU : failures/expected/keyboard.html = TEXT
-BUGX1 XP GPU : failures/expected/keyboard.html = TEXT
-BUGX1 VISTA WIN7 : failures/expected/keyboard.html = TEXT
-BUG_NEWLY_CREATED XP RELEASE CPU : failures/expected/keyboard.html = IMAGE""", 2)
-
-        self.assert_update_roundtrip("""
-BUGX1 WIN : failures/expected/keyboard.html = TEXT""", 'failures/expected/keyboard.html', set([PASS]), """
-BUGX1 XP DEBUG CPU : failures/expected/keyboard.html = TEXT
-BUGX1 XP GPU : failures/expected/keyboard.html = TEXT
-BUGX1 VISTA WIN7 : failures/expected/keyboard.html = TEXT""", 1)
-
-        self.assert_update_roundtrip("""
-BUGX1 WIN : failures/expected/keyboard.html = TEXT""", 'failures/expected/keyboard.html', set([IMAGE]), """
-BUGX1 XP DEBUG CPU : failures/expected/keyboard.html = TEXT
-BUGX1 XP GPU : failures/expected/keyboard.html = TEXT
-BUGX1 VISTA WIN7 : failures/expected/keyboard.html = TEXT
-BUG_NEWLY_CREATED XP RELEASE CPU : failures/expected/keyboard.html = IMAGE""", 2)
-
-        self.assert_update_roundtrip("""
-BUGX1 XP RELEASE CPU: failures/expected/keyboard.html = TEXT""", 'failures/expected/keyboard.html', set([IMAGE]), """
-BUG_NEWLY_CREATED WIN RELEASE CPU : failures/expected/keyboard.html = IMAGE""", 2, test_configs=self.WIN_RELEASE_CPU_CONFIGS)
-
-        self.assert_update_roundtrip("""
-BUGX1 XP RELEASE CPU: failures/expected/keyboard.html = TEXT""", 'failures/expected/keyboard.html', set([PASS]), '', 1, test_configs=self.WIN_RELEASE_CPU_CONFIGS)
-
-        self.assert_update_roundtrip("""
-BUGX1 RELEASE CPU: failures/expected/keyboard.html = TEXT""", 'failures/expected/keyboard.html', set([IMAGE]), """
-BUGX1 LINUX MAC RELEASE CPU : failures/expected/keyboard.html = TEXT
-BUG_NEWLY_CREATED WIN RELEASE CPU : failures/expected/keyboard.html = IMAGE""", 2, test_configs=self.WIN_RELEASE_CPU_CONFIGS)
-
-        self.assert_update_roundtrip("""
-BUGX1 MAC : failures/expected/keyboard.html = TEXT""", 'failures/expected/keyboard.html', set([IMAGE]), """
-BUGX1 MAC : failures/expected/keyboard.html = TEXT
-BUG_NEWLY_CREATED WIN RELEASE CPU : failures/expected/keyboard.html = IMAGE""", 1, test_configs=self.WIN_RELEASE_CPU_CONFIGS)
-
-    def test_update_expectation_relative(self):
-        self.assert_update_roundtrip("""
-BUGX1 XP RELEASE : failures/expected/keyboard.html = TEXT
-BUGX2 MAC : failures/expected/audio.html = TEXT""", 'failures/expected/keyboard.html', set([IMAGE]), """
-BUGX1 XP RELEASE GPU : failures/expected/keyboard.html = TEXT
-BUGAWESOME XP RELEASE CPU : failures/expected/keyboard.html = IMAGE
-BUGX2 MAC : failures/expected/audio.html = TEXT""", 2, parsed_bug_modifiers=['BUGAWESOME'])
-
-    def test_update_expectation_multiple(self):
-        in_string = """
-BUGX1 WIN : failures/expected/keyboard.html = IMAGE
-BUGX2 WIN : failures/expected/audio.html = IMAGE"""
-        expectation_lines = self.make_parsed_expectation_lines(in_string)
-        converter = TestConfigurationConverter(self.test_port.all_test_configurations(), self.test_port.configuration_specifier_macros())
-        editor = TestExpectationsEditor(expectation_lines, MockBugManager())
-        test = "failures/expected/keyboard.html"
-
-        editor.update_expectation(test, set([TestConfiguration('xp', 'x86', 'release', 'cpu')]), set([IMAGE_PLUS_TEXT]), ['BUG_UPDATE1'])
-        self.assertEquals(TestExpectationSerializer.list_to_string(expectation_lines, converter), """
-BUGX1 XP DEBUG CPU : failures/expected/keyboard.html = IMAGE
-BUGX1 XP GPU : failures/expected/keyboard.html = IMAGE
-BUGX1 VISTA WIN7 : failures/expected/keyboard.html = IMAGE
-BUG_UPDATE1 XP RELEASE CPU : failures/expected/keyboard.html = IMAGE+TEXT
-BUGX2 WIN : failures/expected/audio.html = IMAGE""")
-
-        editor.update_expectation(test, set([TestConfiguration('xp', 'x86', 'debug', 'cpu')]), set([TEXT]), ['BUG_UPDATE2'])
-        self.assertEquals(TestExpectationSerializer.list_to_string(expectation_lines, converter), """
-BUGX1 XP GPU : failures/expected/keyboard.html = IMAGE
-BUGX1 VISTA WIN7 : failures/expected/keyboard.html = IMAGE
-BUG_UPDATE2 XP DEBUG CPU : failures/expected/keyboard.html = TEXT
-BUG_UPDATE1 XP RELEASE CPU : failures/expected/keyboard.html = IMAGE+TEXT
-BUGX2 WIN : failures/expected/audio.html = IMAGE""")
-
-        editor.update_expectation(test, self.WIN_RELEASE_CPU_CONFIGS, set([CRASH]), ['BUG_UPDATE3'])
-        self.assertEquals(TestExpectationSerializer.list_to_string(expectation_lines, converter), """
-BUGX1 VISTA DEBUG CPU : failures/expected/keyboard.html = IMAGE
-BUGX1 WIN7 RELEASE GPU : failures/expected/keyboard.html = IMAGE
-BUGX1 WIN7 DEBUG : failures/expected/keyboard.html = IMAGE
-BUGX1 VISTA XP GPU : failures/expected/keyboard.html = IMAGE
-BUG_UPDATE2 XP DEBUG CPU : failures/expected/keyboard.html = TEXT
-BUG_UPDATE3 WIN RELEASE CPU : failures/expected/keyboard.html = CRASH
-BUGX2 WIN : failures/expected/audio.html = IMAGE""")
-
-        editor.update_expectation(test, self.RELEASE_CONFIGS, set([FAIL]), ['BUG_UPDATE4'])
-        self.assertEquals(TestExpectationSerializer.list_to_string(expectation_lines, converter), """
-BUGX1 XP DEBUG GPU : failures/expected/keyboard.html = IMAGE
-BUGX1 VISTA WIN7 DEBUG : failures/expected/keyboard.html = IMAGE
-BUG_UPDATE2 XP DEBUG CPU : failures/expected/keyboard.html = TEXT
-BUG_UPDATE4 RELEASE : failures/expected/keyboard.html = FAIL
-BUGX2 WIN : failures/expected/audio.html = IMAGE""")
-
-        editor.update_expectation(test, set(self.test_port.all_test_configurations()), set([TIMEOUT]), ['BUG_UPDATE5'])
-        self.assertEquals(TestExpectationSerializer.list_to_string(expectation_lines, converter), """
-BUG_UPDATE5 : failures/expected/keyboard.html = TIMEOUT
-BUGX2 WIN : failures/expected/audio.html = IMAGE""")
-
-
-if __name__ == '__main__':
-    unittest.main()
+        self.assert_round_trip('  [ FOO ] bar [ BAZ ]', '[ FOO ] bar [ BAZ ]')
+        self.assert_round_trip('[ FOO ]    bar [ BAZ ]', '[ FOO ] bar [ BAZ ]')
+        self.assert_round_trip('[ FOO ] bar [ BAZ ]       # Qux.', '[ FOO ] bar [ BAZ ] # Qux.')
+        self.assert_round_trip('[ FOO ] bar [        BAZ ]  # Qux.', '[ FOO ] bar [ BAZ ] # Qux.')
+        self.assert_round_trip('[ FOO ]       bar [    BAZ ]  # Qux.', '[ FOO ] bar [ BAZ ] # Qux.')
+        self.assert_round_trip('[ FOO ]       bar     [    BAZ ]  # Qux.', '[ FOO ] bar [ BAZ ] # Qux.')