Changeset 90920 in webkit


Ignore:
Timestamp:
Jul 13, 2011 8:49:08 AM (13 years ago)
Author:
Dimitri Glazkov
Message:

Extract model-like TestExpectationLine and TestExpectationFile from TestExpectations.
https://bugs.webkit.org/show_bug.cgi?id=64386

This is the first step in converting TestExpectations to a real model.

  • TestExpectationsLine represents a line in the test_expectations.txt file, and
  • TestExpectationsFile represents the file, which is a collection of lines.

Reviewed by Adam Barth.

  • Scripts/webkitpy/layout_tests/models/test_expectations.py:
  • Scripts/webkitpy/layout_tests/models/test_expectations_unittest.py:
  • Scripts/webkitpy/style/checkers/test_expectations_unittest.py:
Location:
trunk/Tools
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • trunk/Tools/ChangeLog

    r90918 r90920  
     12011-07-12  Dimitri Glazkov  <dglazkov@chromium.org>
     2
     3        Extract model-like TestExpectationLine and TestExpectationFile from TestExpectations.
     4        https://bugs.webkit.org/show_bug.cgi?id=64386
     5
     6        This is the first step in converting TestExpectations to a real model.
     7        * TestExpectationsLine represents a line in the test_expectations.txt file, and
     8        * TestExpectationsFile represents the file, which is a collection of lines.
     9
     10        Reviewed by Adam Barth.
     11
     12        * Scripts/webkitpy/layout_tests/models/test_expectations.py:
     13        * Scripts/webkitpy/layout_tests/models/test_expectations_unittest.py:
     14        * Scripts/webkitpy/style/checkers/test_expectations_unittest.py:
     15
    1162011-07-13  Xan Lopez  <xlopez@igalia.com>
    217
  • trunk/Tools/Scripts/webkitpy/layout_tests/models/test_expectations.py

    r90823 r90920  
    8989
    9090
     91# FIXME: This method is no longer used here in this module. Remove remaining callsite in manager.py and this method.
    9192def strip_comments(line):
    9293    """Strips comments from a line and return None if the line is empty
     
    136137        return {"modifiers": obj.modifiers,
    137138                "expectations": obj.expectations}
     139
     140
     141class TestExpectationSerializer:
     142    """Provides means of serializing TestExpectationLine instances."""
     143    @classmethod
     144    def to_string(cls, expectation):
     145        result = []
     146        if expectation.malformed:
     147            result.append(expectation.comment)
     148        else:
     149            if expectation.name is None:
     150                if expectation.comment is not None:
     151                    result.append('//')
     152                    result.append(expectation.comment)
     153            else:
     154                result.append("%s : %s = %s" % (" ".join(expectation.modifiers).upper(), expectation.name, " ".join(expectation.expectations).upper()))
     155                if expectation.comment is not None:
     156                    result.append(" //%s" % (expectation.comment))
     157
     158        return ''.join(result)
     159
     160
     161class TestExpectationParser:
     162    """Provides parsing facilities for lines in the test_expectation.txt file."""
     163
     164    @classmethod
     165    def parse(cls, expectation_string):
     166        """Parses a line from test_expectations.txt and returns a tuple, containing a TestExpectationLine instance
     167        and a list syntax erros, if any.
     168
     169        The format of test expectation is:
     170
     171        [[<modifiers>] : <name> = <expectations>][ //<comment>]
     172
     173        Any errant whitespace is not preserved.
     174
     175        """
     176        result = TestExpectationLine()
     177        errors = []
     178        (modifiers, name, expectations, comment) = cls._split_expectation_string(expectation_string, errors)
     179        if len(errors) > 0:
     180            result.malformed = True
     181            result.comment = expectation_string
     182            result.valid = False
     183        else:
     184            result.malformed = False
     185            result.comment = comment
     186            result.valid = True
     187            result.name = name
     188            # FIXME: Modifiers should be its own class eventually.
     189            if modifiers is not None:
     190                result.modifiers = cls._split_space_separated(modifiers)
     191            # FIXME: Expectations should be its own class eventually.
     192            if expectations is not None:
     193                result.expectations = cls._split_space_separated(expectations)
     194
     195        return (result, errors)
     196
     197    @classmethod
     198    def _split_expectation_string(cls, line, errors):
     199        """Splits line into a string of modifiers, a test name, a string of expectations, and a comment,
     200        returning them as a tuple. In case parsing error, returns empty tuple.
     201        """
     202        comment_index = line.find("//")
     203        comment = ''
     204        if comment_index == -1:
     205            comment_index = len(line)
     206            comment = None
     207        else:
     208            comment = line[comment_index + 2:]
     209
     210        line = re.sub(r"\s+", " ", line[:comment_index].strip())
     211        if len(line) == 0:
     212            return (None, None, None, comment)
     213
     214        parts = line.split(':')
     215        if len(parts) != 2:
     216            errors.append(("Missing a ':' in" if len(parts) < 2 else "Extraneous ':' in", "'" + line + "'"))
     217            return (None, None, None, None)
     218
     219        test_and_expectation = parts[1].split('=')
     220        if len(test_and_expectation) != 2:
     221            errors.append(("Missing expectations in" if len(test_and_expectation) < 2 else "Extraneous '=' in", "'" + line + "'"))
     222            return (None, None, None, None)
     223
     224        return (parts[0].strip(), test_and_expectation[0].strip(), test_and_expectation[1].strip(), comment)
     225
     226    @classmethod
     227    def _split_space_separated(cls, space_separated_string):
     228        """Splits a space-separated string into an array."""
     229        # FIXME: Lower-casing is necessary to support legacy code. Need to eliminate.
     230        return [part.strip().lower() for part in space_separated_string.strip().split(' ')]
     231
     232
     233class TestExpectationLine:
     234    """Represents a line in test expectations file."""
     235
     236    def __init__(self):
     237        """Initializes a blank-line equivalent of an expectation."""
     238        self.name = None
     239        self.modifiers = []
     240        self.expectations = []
     241        self.comment = None
     242        # FIXME: Should valid and malformed be a single state flag? Probably not, since "malformed" is also "not valid".
     243        self.valid = False
     244        self.malformed = False
     245
     246
     247class TestExpectationsFile:
     248    """Represents a test expectation file, which is a mutable collection of comments and test expectations."""
     249
     250    def __init__(self):
     251        self._expectations = []
     252
     253    def __iter__(self):
     254        return self._expectations.__iter__()
     255
     256    def append(self, expectations_string, validator):
     257        """Add a TestExpectationLine for each item in expectations_string."""
     258        line_number = 0
     259        for line in expectations_string.split("\n"):
     260            expectation, errors = TestExpectationParser.parse(line)
     261            line_number += 1
     262            expectation.valid = validator.validate(line_number, expectation, errors)
     263            self._expectations.append(expectation)
    138264
    139265
     
    241367        self._port = port
    242368        self._fs = port._filesystem
    243         self._expectations = expectations
    244369        self._full_test_list = tests
    245370        self._test_config = test_config
     
    275400        self._result_type_to_tests = self._dict_of_sets(self.RESULT_TYPES)
    276401
    277         self._read(self._get_iterable_expectations(self._expectations),
    278                    overrides_allowed=False)
     402        self._matcher = ModifierMatcher(self._test_config)
     403        self._expectations = TestExpectationsFile()
     404        self._overrides_allowed = False
     405        self._expectations.append(expectations, self)
    279406
    280407        # List of tests that are in the overrides file (used for checking for
     
    286413
    287414        if overrides:
    288             self._read(self._get_iterable_expectations(self._overrides),
    289                        overrides_allowed=True)
     415            self._overrides_allowed = True
     416            self._expectations.append(self._overrides, self)
     417            self._overrides_allowed = False
    290418
    291419        self._handle_any_read_errors()
     
    363491        return d
    364492
    365     def _get_iterable_expectations(self, expectations_str):
    366         """Returns an object that can be iterated over. Allows for not caring
    367         about whether we're iterating over a file or a new-line separated
    368         string."""
    369         iterable = [x + "\n" for x in expectations_str.split("\n")]
    370         # Strip final entry if it's empty to avoid added in an extra
    371         # newline.
    372         if iterable[-1] == "\n":
    373             return iterable[:-1]
    374         return iterable
    375 
    376493    def get_test_set(self, modifier, expectation=None, include_skips=True):
    377494        if expectation is None:
     
    415532        """Returns a copy of the expectations with the tests removed."""
    416533        lines = []
    417         for (lineno, line) in enumerate(self._get_iterable_expectations(self._expectations)):
    418             test, options, _ = self.parse_expectations_line(line, lineno)
    419             if not (test and test in tests and 'rebaseline' in options):
    420                 lines.append(line)
    421         return ''.join(lines)
    422 
    423     def parse_expectations_line(self, line, lineno):
    424         """Parses a line from test_expectations.txt and returns a tuple
    425         with the test path, options as a list, expectations as a list."""
    426         line = strip_comments(line)
    427         if not line:
    428             return (None, None, None)
    429 
    430         options = []
    431         if line.find(":") is -1:
    432             self._add_error(lineno, "Missing a ':'", line)
    433             return (None, None, None)
    434 
    435         parts = line.split(':')
    436 
    437         # FIXME: verify that there is exactly one colon in the line.
    438 
    439         options = self._get_options_list(parts[0])
    440         test_and_expectation = parts[1].split('=')
    441         test = test_and_expectation[0].strip()
    442         if (len(test_and_expectation) is not 2):
    443             self._add_error(lineno, "Missing expectations.",
    444                            test_and_expectation)
    445             expectations = None
    446         else:
    447             expectations = self._get_options_list(test_and_expectation[1])
    448 
    449         return (test, options, expectations)
     534        for expectation in self._expectations:
     535            if not (expectation.valid and expectation.name in tests and "rebaseline" in expectation.modifiers):
     536                lines.append(TestExpectationSerializer.to_string(expectation))
     537        return "\n".join(lines)
    450538
    451539    def _add_to_all_expectations(self, test, options, expectations):
     
    455543            ModifiersAndExpectations(options, expectations))
    456544
    457     def _read(self, expectations, overrides_allowed):
    458         """For each test in an expectations iterable, generate the
    459         expectations for it."""
    460         lineno = 0
    461         matcher = ModifierMatcher(self._test_config)
    462         for line in expectations:
    463             lineno += 1
    464             self._process_line(line, lineno, matcher, overrides_allowed)
    465 
    466     def _process_line(self, line, lineno, matcher, overrides_allowed):
    467         test_list_path, options, expectations = \
    468             self.parse_expectations_line(line, lineno)
     545    def validate(self, lineno, expectation, syntax_errors):
     546        test_list_path = expectation.name
     547        options = expectation.modifiers
     548        expectations = expectation.expectations
     549        matcher = self._matcher
     550        overrides_allowed = self._overrides_allowed
     551
     552        for (message, source) in syntax_errors:
     553            self._add_error(lineno, message, source)
     554
    469555        if not expectations:
    470             return
     556            return False
    471557
    472558        self._add_to_all_expectations(test_list_path,
     
    477563                                          test_list_path)
    478564        if num_matches == ModifierMatcher.NO_MATCH:
    479             return
     565            return False
    480566
    481567        expectations = self._parse_expectations(expectations, lineno,
     
    486572
    487573        if self._check_path_does_not_exist(lineno, test_list_path):
    488             return
     574            return False
    489575
    490576        if not self._full_test_list:
     
    495581        modifiers = [o for o in options if o in self.MODIFIERS]
    496582        self._add_tests(tests, expectations, test_list_path, lineno, modifiers, num_matches, options, overrides_allowed)
     583
     584        return True
    497585
    498586    def _get_options_list(self, listString):
  • trunk/Tools/Scripts/webkitpy/layout_tests/models/test_expectations_unittest.py

    r90532 r90920  
    196196            self.assertTrue(e.fatal)
    197197            exp_errors = [u"Line:1 Unrecognized option 'foo' failures/expected/text.html",
    198                           u"Line:2 Missing expectations. [' failures/expected/image.html']"]
     198                          u"Line:2 Missing expectations in 'SKIP : failures/expected/image.html'"]
    199199            self.assertEqual(str(e), '\n'.join(map(str, exp_errors)))
    200200            self.assertEqual(e.errors, exp_errors)
     
    462462
    463463
     464class TestExpectationParserTests(unittest.TestCase):
     465    def test_blank(self):
     466        (expectation, errors) = TestExpectationParser.parse('')
     467        self.assertEqual(expectation.malformed, False)
     468        self.assertEqual(expectation.valid, True)
     469        self.assertEqual(expectation.comment, None)
     470        self.assertEqual(len(errors), 0)
     471
     472    def test_missing_colon(self):
     473        (expectation, errors) = TestExpectationParser.parse('Qux.')
     474        self.assertEqual(expectation.malformed, True)
     475        self.assertEqual(expectation.valid, False)
     476        self.assertEqual(expectation.comment, 'Qux.')
     477        self.assertEqual(str(errors), '[("Missing a \':\' in", "\'Qux.\'")]')
     478
     479    def test_extra_colon(self):
     480        (expectation, errors) = TestExpectationParser.parse('FOO : : bar')
     481        self.assertEqual(expectation.malformed, True)
     482        self.assertEqual(expectation.valid, False)
     483        self.assertEqual(expectation.comment, 'FOO : : bar')
     484        self.assertEqual(str(errors), '[("Extraneous \':\' in", "\'FOO : : bar\'")]')
     485
     486    def test_empty_comment(self):
     487        (expectation, errors) = TestExpectationParser.parse('//')
     488        self.assertEqual(expectation.malformed, False)
     489        self.assertEqual(expectation.valid, True)
     490        self.assertEqual(expectation.comment, '')
     491        self.assertEqual(len(errors), 0)
     492
     493    def test_comment(self):
     494        (expectation, errors) = TestExpectationParser.parse('//Qux.')
     495        self.assertEqual(expectation.malformed, False)
     496        self.assertEqual(expectation.valid, True)
     497        self.assertEqual(expectation.comment, 'Qux.')
     498        self.assertEqual(len(errors), 0)
     499
     500    def test_missing_equal(self):
     501        (expectation, errors) = TestExpectationParser.parse('FOO : bar')
     502        self.assertEqual(expectation.malformed, True)
     503        self.assertEqual(expectation.valid, False)
     504        self.assertEqual(expectation.comment, 'FOO : bar')
     505        self.assertEqual(str(errors), '[(\'Missing expectations in\', "\'FOO : bar\'")]')
     506
     507    def test_extra_equal(self):
     508        (expectation, errors) = TestExpectationParser.parse('FOO : bar = BAZ = Qux.')
     509        self.assertEqual(expectation.malformed, True)
     510        self.assertEqual(expectation.valid, False)
     511        self.assertEqual(expectation.comment, 'FOO : bar = BAZ = Qux.')
     512        self.assertEqual(str(errors), '[("Extraneous \'=\' in", "\'FOO : bar = BAZ = Qux.\'")]')
     513
     514    def test_valid(self):
     515        (expectation, errors) = TestExpectationParser.parse('FOO : bar = BAZ')
     516        self.assertEqual(expectation.malformed, False)
     517        self.assertEqual(expectation.valid, True)
     518        self.assertEqual(expectation.comment, None)
     519        self.assertEqual(len(errors), 0)
     520
     521    def test_valid_with_comment(self):
     522        (expectation, errors) = TestExpectationParser.parse('FOO : bar = BAZ //Qux.')
     523        self.assertEqual(expectation.malformed, False)
     524        self.assertEqual(expectation.valid, True)
     525        self.assertEqual(expectation.comment, 'Qux.')
     526        self.assertEqual(str(expectation.modifiers), '[\'foo\']')
     527        self.assertEqual(str(expectation.expectations), '[\'baz\']')
     528        self.assertEqual(len(errors), 0)
     529
     530    def test_valid_with_multiple_modifiers(self):
     531        (expectation, errors) = TestExpectationParser.parse('FOO1 FOO2 : bar = BAZ //Qux.')
     532        self.assertEqual(expectation.malformed, False)
     533        self.assertEqual(expectation.valid, True)
     534        self.assertEqual(expectation.comment, 'Qux.')
     535        self.assertEqual(str(expectation.modifiers), '[\'foo1\', \'foo2\']')
     536        self.assertEqual(str(expectation.expectations), '[\'baz\']')
     537        self.assertEqual(len(errors), 0)
     538
     539
     540class TestExpectationSerializerTests(unittest.TestCase):
     541    def assert_round_trip(self, in_string, expected_string=None):
     542        (expectation, _) = TestExpectationParser.parse(in_string)
     543        if expected_string is None:
     544            expected_string = in_string
     545        self.assertEqual(expected_string, TestExpectationSerializer.to_string(expectation))
     546
     547    def assert_to_string(self, expectation, expected_string):
     548        self.assertEqual(TestExpectationSerializer.to_string(expectation), expected_string)
     549
     550    def test_string_serializer(self):
     551        expectation = TestExpectationLine()
     552        self.assert_to_string(expectation, '')
     553        expectation.comment = 'Qux.'
     554        self.assert_to_string(expectation, '//Qux.')
     555        expectation.name = 'bar'
     556        self.assert_to_string(expectation, ' : bar =  //Qux.')
     557        expectation.modifiers = ['foo']
     558        self.assert_to_string(expectation, 'FOO : bar =  //Qux.')
     559        expectation.expectations = ['bAz']
     560        self.assert_to_string(expectation, 'FOO : bar = BAZ //Qux.')
     561        expectation.expectations = ['bAz1', 'baZ2']
     562        self.assert_to_string(expectation, 'FOO : bar = BAZ1 BAZ2 //Qux.')
     563        expectation.modifiers = ['foo1', 'foO2']
     564        self.assert_to_string(expectation, 'FOO1 FOO2 : bar = BAZ1 BAZ2 //Qux.')
     565        expectation.malformed = True
     566        self.assert_to_string(expectation, 'Qux.')
     567
     568    def test_string_roundtrip(self):
     569        self.assert_round_trip('')
     570        self.assert_round_trip('FOO')
     571        self.assert_round_trip(':')
     572        self.assert_round_trip('FOO :')
     573        self.assert_round_trip('FOO : bar')
     574        self.assert_round_trip('  FOO :')
     575        self.assert_round_trip('    FOO : bar')
     576        self.assert_round_trip('FOO : bar = BAZ')
     577        self.assert_round_trip('FOO : bar = BAZ //Qux.')
     578        self.assert_round_trip('FOO : bar = BAZ // Qux.')
     579        self.assert_round_trip('FOO : bar = BAZ // Qux.     ')
     580        self.assert_round_trip('FOO : bar = BAZ //        Qux.     ')
     581        self.assert_round_trip('FOO : : bar = BAZ')
     582        self.assert_round_trip('FOO : : bar = BAZ')
     583        self.assert_round_trip('FOO : : bar ==== BAZ')
     584        self.assert_round_trip('=')
     585        self.assert_round_trip('//')
     586        self.assert_round_trip('// ')
     587        self.assert_round_trip('// Foo')
     588        self.assert_round_trip('// Foo')
     589        self.assert_round_trip('// Foo :')
     590        self.assert_round_trip('// Foo : =')
     591
     592    def test_string_whitespace_stripping(self):
     593        self.assert_round_trip('\n', '')
     594        self.assert_round_trip('  FOO : bar = BAZ', 'FOO : bar = BAZ')
     595        self.assert_round_trip('FOO    : bar = BAZ', 'FOO : bar = BAZ')
     596        self.assert_round_trip('FOO : bar = BAZ       // Qux.', 'FOO : bar = BAZ // Qux.')
     597        self.assert_round_trip('FOO : bar =        BAZ // Qux.', 'FOO : bar = BAZ // Qux.')
     598        self.assert_round_trip('FOO :       bar =    BAZ // Qux.', 'FOO : bar = BAZ // Qux.')
     599        self.assert_round_trip('FOO :       bar     =    BAZ // Qux.', 'FOO : bar = BAZ // Qux.')
     600
     601
     602class TestValidator:
     603    def __init__(self):
     604        self.line_numbers = []
     605
     606    def validate(self, line_number, expectation, errors):
     607        self.line_numbers.append(line_number)
     608        return line_number % 2 == 0
     609
     610
     611class TestExpectationsFileTests(unittest.TestCase):
     612    def test_line_number_increment(self):
     613        validator = TestValidator()
     614        expectations = TestExpectationsFile()
     615        expectations.append('// Bar\nFOO : bar = BAZ\n\n', validator)
     616        self.assertEqual(str(validator.line_numbers), '[1, 2, 3, 4]')
     617
     618    def test_iterator(self):
     619        validator = TestValidator()
     620        expectations = TestExpectationsFile()
     621        expectations.append('\n\n\n\n', validator)
     622        line_number = 0
     623        for expectation in expectations:
     624            line_number += 1
     625        self.assertEqual(line_number, 5)
     626
     627    def test_validator_feedback(self):
     628        validator = TestValidator()
     629        expectations = TestExpectationsFile()
     630        expectations.append('FOO : bar1 = BAZ\nFOO : bar2 = BAZ\nFOO : bar3 = BAZ', validator)
     631        line_number = 0
     632        for expectation in expectations:
     633            line_number += 1
     634            self.assertEqual(line_number % 2 == 0, expectation.valid)
     635
    464636if __name__ == '__main__':
    465637    unittest.main()
  • trunk/Tools/Scripts/webkitpy/style/checkers/test_expectations_unittest.py

    r90549 r90920  
    123123        self.assert_lines_lint(
    124124            ["missing expectations"],
    125             "Missing a ':' missing expectations  [test/expectations] [5]")
     125            "Missing a ':' in 'missing expectations'  [test/expectations] [5]")
    126126        self.assert_lines_lint(
    127127            ["SLOW : passes/text.html = TIMEOUT"],
Note: See TracChangeset for help on using the changeset viewer.