Changeset 90920 in webkit
- Timestamp:
- Jul 13, 2011 8:49:08 AM (13 years ago)
- Location:
- trunk/Tools
- Files:
-
- 4 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/Tools/ChangeLog
r90918 r90920 1 2011-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 1 16 2011-07-13 Xan Lopez <xlopez@igalia.com> 2 17 -
trunk/Tools/Scripts/webkitpy/layout_tests/models/test_expectations.py
r90823 r90920 89 89 90 90 91 # FIXME: This method is no longer used here in this module. Remove remaining callsite in manager.py and this method. 91 92 def strip_comments(line): 92 93 """Strips comments from a line and return None if the line is empty … … 136 137 return {"modifiers": obj.modifiers, 137 138 "expectations": obj.expectations} 139 140 141 class 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 161 class 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 233 class 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 247 class 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) 138 264 139 265 … … 241 367 self._port = port 242 368 self._fs = port._filesystem 243 self._expectations = expectations244 369 self._full_test_list = tests 245 370 self._test_config = test_config … … 275 400 self._result_type_to_tests = self._dict_of_sets(self.RESULT_TYPES) 276 401 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) 279 406 280 407 # List of tests that are in the overrides file (used for checking for … … 286 413 287 414 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 290 418 291 419 self._handle_any_read_errors() … … 363 491 return d 364 492 365 def _get_iterable_expectations(self, expectations_str):366 """Returns an object that can be iterated over. Allows for not caring367 about whether we're iterating over a file or a new-line separated368 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 extra371 # newline.372 if iterable[-1] == "\n":373 return iterable[:-1]374 return iterable375 376 493 def get_test_set(self, modifier, expectation=None, include_skips=True): 377 494 if expectation is None: … … 415 532 """Returns a copy of the expectations with the tests removed.""" 416 533 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) 450 538 451 539 def _add_to_all_expectations(self, test, options, expectations): … … 455 543 ModifiersAndExpectations(options, expectations)) 456 544 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 469 555 if not expectations: 470 return 556 return False 471 557 472 558 self._add_to_all_expectations(test_list_path, … … 477 563 test_list_path) 478 564 if num_matches == ModifierMatcher.NO_MATCH: 479 return 565 return False 480 566 481 567 expectations = self._parse_expectations(expectations, lineno, … … 486 572 487 573 if self._check_path_does_not_exist(lineno, test_list_path): 488 return 574 return False 489 575 490 576 if not self._full_test_list: … … 495 581 modifiers = [o for o in options if o in self.MODIFIERS] 496 582 self._add_tests(tests, expectations, test_list_path, lineno, modifiers, num_matches, options, overrides_allowed) 583 584 return True 497 585 498 586 def _get_options_list(self, listString): -
trunk/Tools/Scripts/webkitpy/layout_tests/models/test_expectations_unittest.py
r90532 r90920 196 196 self.assertTrue(e.fatal) 197 197 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'"] 199 199 self.assertEqual(str(e), '\n'.join(map(str, exp_errors))) 200 200 self.assertEqual(e.errors, exp_errors) … … 462 462 463 463 464 class 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 540 class 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 602 class 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 611 class 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 464 636 if __name__ == '__main__': 465 637 unittest.main() -
trunk/Tools/Scripts/webkitpy/style/checkers/test_expectations_unittest.py
r90549 r90920 123 123 self.assert_lines_lint( 124 124 ["missing expectations"], 125 "Missing a ':' missing expectations[test/expectations] [5]")125 "Missing a ':' in 'missing expectations' [test/expectations] [5]") 126 126 self.assert_lines_lint( 127 127 ["SLOW : passes/text.html = TIMEOUT"],
Note: See TracChangeset
for help on using the changeset viewer.