Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1589)

Side by Side Diff: Tools/Scripts/webkitpy/layout_tests/models/test_expectations.py

Issue 546133003: Reformat webkitpy.layout_tests w/ format-webkitpy. (Closed) Base URL: svn://svn.chromium.org/blink/trunk
Patch Set: Created 6 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 # Copyright (C) 2010 Google Inc. All rights reserved. 1 # Copyright (C) 2010 Google Inc. All rights reserved.
2 # 2 #
3 # Redistribution and use in source and binary forms, with or without 3 # Redistribution and use in source and binary forms, with or without
4 # modification, are permitted provided that the following conditions are 4 # modification, are permitted provided that the following conditions are
5 # met: 5 # met:
6 # 6 #
7 # * Redistributions of source code must retain the above copyright 7 # * Redistributions of source code must retain the above copyright
8 # notice, this list of conditions and the following disclaimer. 8 # notice, this list of conditions and the following disclaimer.
9 # * Redistributions in binary form must reproduce the above 9 # * Redistributions in binary form must reproduce the above
10 # copyright notice, this list of conditions and the following disclaimer 10 # copyright notice, this list of conditions and the following disclaimer
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after
50 50
51 WEBKIT_BUG_PREFIX = 'webkit.org/b/' 51 WEBKIT_BUG_PREFIX = 'webkit.org/b/'
52 CHROMIUM_BUG_PREFIX = 'crbug.com/' 52 CHROMIUM_BUG_PREFIX = 'crbug.com/'
53 V8_BUG_PREFIX = 'code.google.com/p/v8/issues/detail?id=' 53 V8_BUG_PREFIX = 'code.google.com/p/v8/issues/detail?id='
54 NAMED_BUG_PREFIX = 'Bug(' 54 NAMED_BUG_PREFIX = 'Bug('
55 55
56 MISSING_KEYWORD = 'Missing' 56 MISSING_KEYWORD = 'Missing'
57 NEEDS_REBASELINE_KEYWORD = 'NeedsRebaseline' 57 NEEDS_REBASELINE_KEYWORD = 'NeedsRebaseline'
58 NEEDS_MANUAL_REBASELINE_KEYWORD = 'NeedsManualRebaseline' 58 NEEDS_MANUAL_REBASELINE_KEYWORD = 'NeedsManualRebaseline'
59 59
60
60 class ParseError(Exception): 61 class ParseError(Exception):
62
61 def __init__(self, warnings): 63 def __init__(self, warnings):
62 super(ParseError, self).__init__() 64 super(ParseError, self).__init__()
63 self.warnings = warnings 65 self.warnings = warnings
64 66
65 def __str__(self): 67 def __str__(self):
66 return '\n'.join(map(str, self.warnings)) 68 return '\n'.join(map(str, self.warnings))
67 69
68 def __repr__(self): 70 def __repr__(self):
69 return 'ParseError(warnings=%s)' % self.warnings 71 return 'ParseError(warnings=%s)' % self.warnings
70 72
71 73
72 class TestExpectationParser(object): 74 class TestExpectationParser(object):
75
73 """Provides parsing facilities for lines in the test_expectation.txt file."" " 76 """Provides parsing facilities for lines in the test_expectation.txt file."" "
74 77
75 # FIXME: Rename these to *_KEYWORD as in MISSING_KEYWORD above, but make the case studdly-caps to match the actual file contents. 78 # FIXME: Rename these to *_KEYWORD as in MISSING_KEYWORD above, but make
79 # the case studdly-caps to match the actual file contents.
76 REBASELINE_MODIFIER = 'rebaseline' 80 REBASELINE_MODIFIER = 'rebaseline'
77 NEEDS_REBASELINE_MODIFIER = 'needsrebaseline' 81 NEEDS_REBASELINE_MODIFIER = 'needsrebaseline'
78 NEEDS_MANUAL_REBASELINE_MODIFIER = 'needsmanualrebaseline' 82 NEEDS_MANUAL_REBASELINE_MODIFIER = 'needsmanualrebaseline'
79 PASS_EXPECTATION = 'pass' 83 PASS_EXPECTATION = 'pass'
80 SKIP_MODIFIER = 'skip' 84 SKIP_MODIFIER = 'skip'
81 SLOW_MODIFIER = 'slow' 85 SLOW_MODIFIER = 'slow'
82 WONTFIX_MODIFIER = 'wontfix' 86 WONTFIX_MODIFIER = 'wontfix'
83 87
84 TIMEOUT_EXPECTATION = 'timeout' 88 TIMEOUT_EXPECTATION = 'timeout'
85 89
86 MISSING_BUG_WARNING = 'Test lacks BUG specifier.' 90 MISSING_BUG_WARNING = 'Test lacks BUG specifier.'
87 91
88 def __init__(self, port, full_test_list, is_lint_mode): 92 def __init__(self, port, full_test_list, is_lint_mode):
89 self._port = port 93 self._port = port
90 self._test_configuration_converter = TestConfigurationConverter(set(port .all_test_configurations()), port.configuration_specifier_macros()) 94 self._test_configuration_converter = TestConfigurationConverter(
95 set(port.all_test_configurations()), port.configuration_specifier_ma cros())
91 self._full_test_list = full_test_list 96 self._full_test_list = full_test_list
92 self._is_lint_mode = is_lint_mode 97 self._is_lint_mode = is_lint_mode
93 98
94 def parse(self, filename, expectations_string): 99 def parse(self, filename, expectations_string):
95 expectation_lines = [] 100 expectation_lines = []
96 line_number = 0 101 line_number = 0
97 for line in expectations_string.split("\n"): 102 for line in expectations_string.split('\n'):
98 line_number += 1 103 line_number += 1
99 test_expectation = self._tokenize_line(filename, line, line_number) 104 test_expectation = self._tokenize_line(filename, line, line_number)
100 self._parse_line(test_expectation) 105 self._parse_line(test_expectation)
101 expectation_lines.append(test_expectation) 106 expectation_lines.append(test_expectation)
102 return expectation_lines 107 return expectation_lines
103 108
104 def _create_expectation_line(self, test_name, expectations, file_name): 109 def _create_expectation_line(self, test_name, expectations, file_name):
105 expectation_line = TestExpectationLine() 110 expectation_line = TestExpectationLine()
106 expectation_line.original_string = test_name 111 expectation_line.original_string = test_name
107 expectation_line.name = test_name 112 expectation_line.name = test_name
108 expectation_line.filename = file_name 113 expectation_line.filename = file_name
109 expectation_line.expectations = expectations 114 expectation_line.expectations = expectations
110 return expectation_line 115 return expectation_line
111 116
112 def expectation_line_for_test(self, test_name, expectations): 117 def expectation_line_for_test(self, test_name, expectations):
113 expectation_line = self._create_expectation_line(test_name, expectations , '<Bot TestExpectations>') 118 expectation_line = self._create_expectation_line(test_name, expectations , '<Bot TestExpectations>')
114 self._parse_line(expectation_line) 119 self._parse_line(expectation_line)
115 return expectation_line 120 return expectation_line
116 121
117
118 def expectation_for_skipped_test(self, test_name): 122 def expectation_for_skipped_test(self, test_name):
119 if not self._port.test_exists(test_name): 123 if not self._port.test_exists(test_name):
120 _log.warning('The following test %s from the Skipped list doesn\'t e xist' % test_name) 124 _log.warning('The following test %s from the Skipped list doesn\'t e xist' % test_name)
121 expectation_line = self._create_expectation_line(test_name, [TestExpecta tionParser.PASS_EXPECTATION], '<Skipped file>') 125 expectation_line = self._create_expectation_line(test_name, [TestExpecta tionParser.PASS_EXPECTATION], '<Skipped file>')
122 expectation_line.expectations = [TestExpectationParser.SKIP_MODIFIER, Te stExpectationParser.WONTFIX_MODIFIER] 126 expectation_line.expectations = [TestExpectationParser.SKIP_MODIFIER, Te stExpectationParser.WONTFIX_MODIFIER]
123 expectation_line.is_skipped_outside_expectations_file = True 127 expectation_line.is_skipped_outside_expectations_file = True
124 self._parse_line(expectation_line) 128 self._parse_line(expectation_line)
125 return expectation_line 129 return expectation_line
126 130
127 def _parse_line(self, expectation_line): 131 def _parse_line(self, expectation_line):
(...skipping 12 matching lines...) Expand all
140 self._collect_matching_tests(expectation_line) 144 self._collect_matching_tests(expectation_line)
141 145
142 self._parse_specifiers(expectation_line) 146 self._parse_specifiers(expectation_line)
143 self._parse_expectations(expectation_line) 147 self._parse_expectations(expectation_line)
144 148
145 def _parse_specifiers(self, expectation_line): 149 def _parse_specifiers(self, expectation_line):
146 if self._is_lint_mode: 150 if self._is_lint_mode:
147 self._lint_line(expectation_line) 151 self._lint_line(expectation_line)
148 152
149 parsed_specifiers = set([specifier.lower() for specifier in expectation_ line.specifiers]) 153 parsed_specifiers = set([specifier.lower() for specifier in expectation_ line.specifiers])
150 expectation_line.matching_configurations = self._test_configuration_conv erter.to_config_set(parsed_specifiers, expectation_line.warnings) 154 expectation_line.matching_configurations = self._test_configuration_conv erter.to_config_set(
155 parsed_specifiers,
156 expectation_line.warnings)
151 157
152 def _lint_line(self, expectation_line): 158 def _lint_line(self, expectation_line):
153 expectations = [expectation.lower() for expectation in expectation_line. expectations] 159 expectations = [expectation.lower() for expectation in expectation_line. expectations]
154 if not expectation_line.bugs and self.WONTFIX_MODIFIER not in expectatio ns: 160 if not expectation_line.bugs and self.WONTFIX_MODIFIER not in expectatio ns:
155 expectation_line.warnings.append(self.MISSING_BUG_WARNING) 161 expectation_line.warnings.append(self.MISSING_BUG_WARNING)
156 if self.REBASELINE_MODIFIER in expectations: 162 if self.REBASELINE_MODIFIER in expectations:
157 expectation_line.warnings.append('REBASELINE should only be used for running rebaseline.py. Cannot be checked in.') 163 expectation_line.warnings.append('REBASELINE should only be used for running rebaseline.py. Cannot be checked in.')
158 164
159 if self.NEEDS_REBASELINE_MODIFIER in expectations or self.NEEDS_MANUAL_R EBASELINE_MODIFIER in expectations: 165 if self.NEEDS_REBASELINE_MODIFIER in expectations or self.NEEDS_MANUAL_R EBASELINE_MODIFIER in expectations:
160 for test in expectation_line.matching_tests: 166 for test in expectation_line.matching_tests:
(...skipping 88 matching lines...) Expand 10 before | Expand all | Expand 10 after
249 [[bugs] [ "[" <configuration specifiers> "]" <name> [ "[" <expectations> "]" ["#" <comment>] 255 [[bugs] [ "[" <configuration specifiers> "]" <name> [ "[" <expectations> "]" ["#" <comment>]
250 256
251 Any errant whitespace is not preserved. 257 Any errant whitespace is not preserved.
252 258
253 """ 259 """
254 expectation_line = TestExpectationLine() 260 expectation_line = TestExpectationLine()
255 expectation_line.original_string = expectation_string 261 expectation_line.original_string = expectation_string
256 expectation_line.filename = filename 262 expectation_line.filename = filename
257 expectation_line.line_numbers = str(line_number) 263 expectation_line.line_numbers = str(line_number)
258 264
259 comment_index = expectation_string.find("#") 265 comment_index = expectation_string.find('#')
260 if comment_index == -1: 266 if comment_index == -1:
261 comment_index = len(expectation_string) 267 comment_index = len(expectation_string)
262 else: 268 else:
263 expectation_line.comment = expectation_string[comment_index + 1:] 269 expectation_line.comment = expectation_string[comment_index + 1:]
264 270
265 remaining_string = re.sub(r"\s+", " ", expectation_string[:comment_index ].strip()) 271 remaining_string = re.sub(r"\s+", ' ', expectation_string[:comment_index ].strip())
266 if len(remaining_string) == 0: 272 if len(remaining_string) == 0:
267 return expectation_line 273 return expectation_line
268 274
269 # special-case parsing this so that we fail immediately instead of treat ing this as a test name 275 # special-case parsing this so that we fail immediately instead of treat ing this as a test name
270 if remaining_string.startswith('//'): 276 if remaining_string.startswith('//'):
271 expectation_line.warnings = ['use "#" instead of "//" for comments'] 277 expectation_line.warnings = ['use "#" instead of "//" for comments']
272 return expectation_line 278 return expectation_line
273 279
274 bugs = [] 280 bugs = []
275 specifiers = [] 281 specifiers = []
276 name = None 282 name = None
277 expectations = [] 283 expectations = []
278 warnings = [] 284 warnings = []
279 has_unrecognized_expectation = False 285 has_unrecognized_expectation = False
280 286
281 tokens = remaining_string.split() 287 tokens = remaining_string.split()
282 state = 'start' 288 state = 'start'
283 for token in tokens: 289 for token in tokens:
284 if (token.startswith(WEBKIT_BUG_PREFIX) or 290 if (token.startswith(WEBKIT_BUG_PREFIX) or
285 token.startswith(CHROMIUM_BUG_PREFIX) or 291 token.startswith(CHROMIUM_BUG_PREFIX) or
286 token.startswith(V8_BUG_PREFIX) or 292 token.startswith(V8_BUG_PREFIX) or
287 token.startswith(NAMED_BUG_PREFIX)): 293 token.startswith(NAMED_BUG_PREFIX)):
288 if state != 'start': 294 if state != 'start':
289 warnings.append('"%s" is not at the start of the line.' % to ken) 295 warnings.append('"%s" is not at the start of the line.' % to ken)
290 break 296 break
291 if token.startswith(WEBKIT_BUG_PREFIX): 297 if token.startswith(WEBKIT_BUG_PREFIX):
292 bugs.append(token) 298 bugs.append(token)
293 elif token.startswith(CHROMIUM_BUG_PREFIX): 299 elif token.startswith(CHROMIUM_BUG_PREFIX):
294 bugs.append(token) 300 bugs.append(token)
295 elif token.startswith(V8_BUG_PREFIX): 301 elif token.startswith(V8_BUG_PREFIX):
296 bugs.append(token) 302 bugs.append(token)
297 else: 303 else:
(...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after
357 expectation_line.warnings = warnings 363 expectation_line.warnings = warnings
358 return expectation_line 364 return expectation_line
359 365
360 @classmethod 366 @classmethod
361 def _split_space_separated(cls, space_separated_string): 367 def _split_space_separated(cls, space_separated_string):
362 """Splits a space-separated string into an array.""" 368 """Splits a space-separated string into an array."""
363 return [part.strip() for part in space_separated_string.strip().split(' ')] 369 return [part.strip() for part in space_separated_string.strip().split(' ')]
364 370
365 371
366 class TestExpectationLine(object): 372 class TestExpectationLine(object):
373
367 """Represents a line in test expectations file.""" 374 """Represents a line in test expectations file."""
368 375
369 def __init__(self): 376 def __init__(self):
370 """Initializes a blank-line equivalent of an expectation.""" 377 """Initializes a blank-line equivalent of an expectation."""
371 self.original_string = None 378 self.original_string = None
372 self.filename = None # this is the path to the expectations file for th is line 379 self.filename = None # this is the path to the expectations file for th is line
373 self.line_numbers = "0" 380 self.line_numbers = '0'
374 self.name = None # this is the path in the line itself 381 self.name = None # this is the path in the line itself
375 self.path = None # this is the normpath of self.name 382 self.path = None # this is the normpath of self.name
376 self.bugs = [] 383 self.bugs = []
377 self.specifiers = [] 384 self.specifiers = []
378 self.parsed_specifiers = [] 385 self.parsed_specifiers = []
379 self.matching_configurations = set() 386 self.matching_configurations = set()
380 self.expectations = [] 387 self.expectations = []
381 self.parsed_expectations = set() 388 self.parsed_expectations = set()
382 self.comment = None 389 self.comment = None
383 self.matching_tests = [] 390 self.matching_tests = []
384 self.warnings = [] 391 self.warnings = []
385 self.is_skipped_outside_expectations_file = False 392 self.is_skipped_outside_expectations_file = False
386 393
387 def __eq__(self, other): 394 def __eq__(self, other):
388 return (self.original_string == other.original_string 395 return (self.original_string == other.original_string
389 and self.filename == other.filename 396 and self.filename == other.filename
390 and self.line_numbers == other.line_numbers 397 and self.line_numbers == other.line_numbers
391 and self.name == other.name 398 and self.name == other.name
392 and self.path == other.path 399 and self.path == other.path
393 and self.bugs == other.bugs 400 and self.bugs == other.bugs
394 and self.specifiers == other.specifiers 401 and self.specifiers == other.specifiers
395 and self.parsed_specifiers == other.parsed_specifiers 402 and self.parsed_specifiers == other.parsed_specifiers
396 and self.matching_configurations == other.matching_configurations 403 and self.matching_configurations == other.matching_configuration s
397 and self.expectations == other.expectations 404 and self.expectations == other.expectations
398 and self.parsed_expectations == other.parsed_expectations 405 and self.parsed_expectations == other.parsed_expectations
399 and self.comment == other.comment 406 and self.comment == other.comment
400 and self.matching_tests == other.matching_tests 407 and self.matching_tests == other.matching_tests
401 and self.warnings == other.warnings 408 and self.warnings == other.warnings
402 and self.is_skipped_outside_expectations_file == other.is_skipped_ou tside_expectations_file) 409 and self.is_skipped_outside_expectations_file == other.is_skippe d_outside_expectations_file)
403 410
404 def is_invalid(self): 411 def is_invalid(self):
405 return bool(self.warnings and self.warnings != [TestExpectationParser.MI SSING_BUG_WARNING]) 412 return bool(self.warnings and self.warnings != [TestExpectationParser.MI SSING_BUG_WARNING])
406 413
407 def is_flaky(self): 414 def is_flaky(self):
408 return len(self.parsed_expectations) > 1 415 return len(self.parsed_expectations) > 1
409 416
410 def is_whitespace_or_comment(self): 417 def is_whitespace_or_comment(self):
411 return bool(re.match("^\s*$", self.original_string.split('#')[0])) 418 return bool(re.match('^\s*$', self.original_string.split('#')[0]))
412 419
413 @staticmethod 420 @staticmethod
414 def create_passing_expectation(test): 421 def create_passing_expectation(test):
415 expectation_line = TestExpectationLine() 422 expectation_line = TestExpectationLine()
416 expectation_line.name = test 423 expectation_line.name = test
417 expectation_line.path = test 424 expectation_line.path = test
418 expectation_line.parsed_expectations = set([PASS]) 425 expectation_line.parsed_expectations = set([PASS])
419 expectation_line.expectations = set(['PASS']) 426 expectation_line.expectations = set(['PASS'])
420 expectation_line.matching_tests = [test] 427 expectation_line.matching_tests = [test]
421 return expectation_line 428 return expectation_line
422 429
423 @staticmethod 430 @staticmethod
424 def merge_expectation_lines(line1, line2, model_all_expectations): 431 def merge_expectation_lines(line1, line2, model_all_expectations):
425 """Merges the expectations of line2 into line1 and returns a fresh objec t.""" 432 """Merges the expectations of line2 into line1 and returns a fresh objec t."""
426 if line1 is None: 433 if line1 is None:
427 return line2 434 return line2
428 if line2 is None: 435 if line2 is None:
429 return line1 436 return line1
430 if model_all_expectations and line1.filename != line2.filename: 437 if model_all_expectations and line1.filename != line2.filename:
431 return line2 438 return line2
432 439
433 # Don't merge original_string or comment. 440 # Don't merge original_string or comment.
434 result = TestExpectationLine() 441 result = TestExpectationLine()
435 # We only care about filenames when we're linting, in which case the fil enames are the same. 442 # We only care about filenames when we're linting, in which case the fil enames are the same.
436 # Not clear that there's anything better to do when not linting and the filenames are different. 443 # Not clear that there's anything better to do when not linting and the filenames are different.
437 if model_all_expectations: 444 if model_all_expectations:
438 result.filename = line2.filename 445 result.filename = line2.filename
439 result.line_numbers = line1.line_numbers + "," + line2.line_numbers 446 result.line_numbers = line1.line_numbers + ',' + line2.line_numbers
440 result.name = line1.name 447 result.name = line1.name
441 result.path = line1.path 448 result.path = line1.path
442 result.parsed_expectations = set(line1.parsed_expectations) | set(line2. parsed_expectations) 449 result.parsed_expectations = set(line1.parsed_expectations) | set(line2. parsed_expectations)
443 result.expectations = list(set(line1.expectations) | set(line2.expectati ons)) 450 result.expectations = list(set(line1.expectations) | set(line2.expectati ons))
444 result.bugs = list(set(line1.bugs) | set(line2.bugs)) 451 result.bugs = list(set(line1.bugs) | set(line2.bugs))
445 result.specifiers = list(set(line1.specifiers) | set(line2.specifiers)) 452 result.specifiers = list(set(line1.specifiers) | set(line2.specifiers))
446 result.parsed_specifiers = list(set(line1.parsed_specifiers) | set(line2 .parsed_specifiers)) 453 result.parsed_specifiers = list(set(line1.parsed_specifiers) | set(line2 .parsed_specifiers))
447 result.matching_configurations = set(line1.matching_configurations) | se t(line2.matching_configurations) 454 result.matching_configurations = set(line1.matching_configurations) | se t(line2.matching_configurations)
448 result.matching_tests = list(list(set(line1.matching_tests) | set(line2. matching_tests))) 455 result.matching_tests = list(list(set(line1.matching_tests) | set(line2. matching_tests)))
449 result.warnings = list(set(line1.warnings) | set(line2.warnings)) 456 result.warnings = list(set(line1.warnings) | set(line2.warnings))
450 result.is_skipped_outside_expectations_file = line1.is_skipped_outside_e xpectations_file or line2.is_skipped_outside_expectations_file 457 result.is_skipped_outside_expectations_file = line1.is_skipped_outside_e xpectations_file or line2.is_skipped_outside_expectations_file
451 return result 458 return result
452 459
453 def to_string(self, test_configuration_converter, include_specifiers=True, i nclude_expectations=True, include_comment=True): 460 def to_string(self, test_configuration_converter, include_specifiers=True, i nclude_expectations=True, include_comment=True):
454 parsed_expectation_to_string = dict([[parsed_expectation, expectation_st ring] for expectation_string, parsed_expectation in TestExpectations.EXPECTATION S.items()]) 461 parsed_expectation_to_string = dict([[parsed_expectation, expectation_st ring]
462 for expectation_string, parsed_expe ctation in TestExpectations.EXPECTATIONS.items()])
455 463
456 if self.is_invalid(): 464 if self.is_invalid():
457 return self.original_string or '' 465 return self.original_string or ''
458 466
459 if self.name is None: 467 if self.name is None:
460 return '' if self.comment is None else "#%s" % self.comment 468 return '' if self.comment is None else '#%s' % self.comment
461 469
462 if test_configuration_converter and self.bugs: 470 if test_configuration_converter and self.bugs:
463 specifiers_list = test_configuration_converter.to_specifiers_list(se lf.matching_configurations) 471 specifiers_list = test_configuration_converter.to_specifiers_list(se lf.matching_configurations)
464 result = [] 472 result = []
465 for specifiers in specifiers_list: 473 for specifiers in specifiers_list:
466 # FIXME: this is silly that we join the specifiers and then imme diately split them. 474 # FIXME: this is silly that we join the specifiers and then imme diately split them.
467 specifiers = self._serialize_parsed_specifiers(test_configuratio n_converter, specifiers).split() 475 specifiers = self._serialize_parsed_specifiers(test_configuratio n_converter, specifiers).split()
468 expectations = self._serialize_parsed_expectations(parsed_expect ation_to_string).split() 476 expectations = self._serialize_parsed_expectations(parsed_expect ation_to_string).split()
469 result.append(self._format_line(self.bugs, specifiers, self.name , expectations, self.comment)) 477 result.append(self._format_line(self.bugs, specifiers, self.name , expectations, self.comment))
470 return "\n".join(result) if result else None 478 return '\n'.join(result) if result else None
471 479
472 return self._format_line(self.bugs, self.specifiers, self.name, self.exp ectations, self.comment, 480 return self._format_line(self.bugs, self.specifiers, self.name, self.exp ectations, self.comment,
473 include_specifiers, include_expectations, include_comment) 481 include_specifiers, include_expectations, inclu de_comment)
474 482
475 def to_csv(self): 483 def to_csv(self):
476 # Note that this doesn't include the comments. 484 # Note that this doesn't include the comments.
477 return '%s,%s,%s,%s' % (self.name, ' '.join(self.bugs), ' '.join(self.sp ecifiers), ' '.join(self.expectations)) 485 return '%s,%s,%s,%s' % (self.name, ' '.join(self.bugs), ' '.join(self.sp ecifiers), ' '.join(self.expectations))
478 486
479 def _serialize_parsed_expectations(self, parsed_expectation_to_string): 487 def _serialize_parsed_expectations(self, parsed_expectation_to_string):
480 result = [] 488 result = []
481 for index in TestExpectations.EXPECTATIONS.values(): 489 for index in TestExpectations.EXPECTATIONS.values():
482 if index in self.parsed_expectations: 490 if index in self.parsed_expectations:
483 result.append(parsed_expectation_to_string[index]) 491 result.append(parsed_expectation_to_string[index])
484 return ' '.join(result) 492 return ' '.join(result)
485 493
486 def _serialize_parsed_specifiers(self, test_configuration_converter, specifi ers): 494 def _serialize_parsed_specifiers(self, test_configuration_converter, specifi ers):
487 result = [] 495 result = []
488 result.extend(sorted(self.parsed_specifiers)) 496 result.extend(sorted(self.parsed_specifiers))
489 result.extend(test_configuration_converter.specifier_sorter().sort_speci fiers(specifiers)) 497 result.extend(test_configuration_converter.specifier_sorter().sort_speci fiers(specifiers))
490 return ' '.join(result) 498 return ' '.join(result)
491 499
492 @staticmethod 500 @staticmethod
493 def _filter_redundant_expectations(expectations): 501 def _filter_redundant_expectations(expectations):
494 if set(expectations) == set(['Pass', 'Skip']): 502 if set(expectations) == set(['Pass', 'Skip']):
495 return ['Skip'] 503 return ['Skip']
496 if set(expectations) == set(['Pass', 'Slow']): 504 if set(expectations) == set(['Pass', 'Slow']):
497 return ['Slow'] 505 return ['Slow']
498 return expectations 506 return expectations
499 507
500 @staticmethod 508 @staticmethod
501 def _format_line(bugs, specifiers, name, expectations, comment, include_spec ifiers=True, include_expectations=True, include_comment=True): 509 def _format_line(bugs, specifiers, name, expectations, comment, include_spec ifiers=True,
510 include_expectations=True, include_comment=True):
502 new_specifiers = [] 511 new_specifiers = []
503 new_expectations = [] 512 new_expectations = []
504 for specifier in specifiers: 513 for specifier in specifiers:
505 # FIXME: Make this all work with the mixed-cased specifiers (e.g. Wo ntFix, Slow, etc). 514 # FIXME: Make this all work with the mixed-cased specifiers (e.g. Wo ntFix, Slow, etc).
506 specifier = specifier.upper() 515 specifier = specifier.upper()
507 new_specifiers.append(TestExpectationParser._inverted_configuration_ tokens.get(specifier, specifier)) 516 new_specifiers.append(TestExpectationParser._inverted_configuration_ tokens.get(specifier, specifier))
508 517
509 for expectation in expectations: 518 for expectation in expectations:
510 expectation = expectation.upper() 519 expectation = expectation.upper()
511 new_expectations.append(TestExpectationParser._inverted_expectation_ tokens.get(expectation, expectation)) 520 new_expectations.append(TestExpectationParser._inverted_expectation_ tokens.get(expectation, expectation))
512 521
513 result = '' 522 result = ''
514 if include_specifiers and (bugs or new_specifiers): 523 if include_specifiers and (bugs or new_specifiers):
515 if bugs: 524 if bugs:
516 result += ' '.join(bugs) + ' ' 525 result += ' '.join(bugs) + ' '
517 if new_specifiers: 526 if new_specifiers:
518 result += '[ %s ] ' % ' '.join(new_specifiers) 527 result += '[ %s ] ' % ' '.join(new_specifiers)
519 result += name 528 result += name
520 if include_expectations and new_expectations: 529 if include_expectations and new_expectations:
521 new_expectations = TestExpectationLine._filter_redundant_expectation s(new_expectations) 530 new_expectations = TestExpectationLine._filter_redundant_expectation s(new_expectations)
522 result += ' [ %s ]' % ' '.join(sorted(set(new_expectations))) 531 result += ' [ %s ]' % ' '.join(sorted(set(new_expectations)))
523 if include_comment and comment is not None: 532 if include_comment and comment is not None:
524 result += " #%s" % comment 533 result += ' #%s' % comment
525 return result 534 return result
526 535
527 536
528 # FIXME: Refactor API to be a proper CRUD. 537 # FIXME: Refactor API to be a proper CRUD.
529 class TestExpectationsModel(object): 538 class TestExpectationsModel(object):
539
530 """Represents relational store of all expectations and provides CRUD semanti cs to manage it.""" 540 """Represents relational store of all expectations and provides CRUD semanti cs to manage it."""
531 541
532 def __init__(self, shorten_filename=None): 542 def __init__(self, shorten_filename=None):
533 # Maps a test to its list of expectations. 543 # Maps a test to its list of expectations.
534 self._test_to_expectations = {} 544 self._test_to_expectations = {}
535 545
536 # Maps a test to list of its specifiers (string values) 546 # Maps a test to list of its specifiers (string values)
537 self._test_to_specifiers = {} 547 self._test_to_specifiers = {}
538 548
539 # Maps a test to a TestExpectationLine instance. 549 # Maps a test to a TestExpectationLine instance.
(...skipping 14 matching lines...) Expand all
554 564
555 def _merge_dict_of_sets(self, self_dict, other_dict): 565 def _merge_dict_of_sets(self, self_dict, other_dict):
556 for key in other_dict: 566 for key in other_dict:
557 self_dict[key] |= other_dict[key] 567 self_dict[key] |= other_dict[key]
558 568
559 def merge_model(self, other): 569 def merge_model(self, other):
560 self._merge_test_map(self._test_to_expectations, other._test_to_expectat ions) 570 self._merge_test_map(self._test_to_expectations, other._test_to_expectat ions)
561 571
562 for test, line in other._test_to_expectation_line.items(): 572 for test, line in other._test_to_expectation_line.items():
563 if test in self._test_to_expectation_line: 573 if test in self._test_to_expectation_line:
564 line = TestExpectationLine.merge_expectation_lines(self._test_to _expectation_line[test], line, model_all_expectations=False) 574 line = TestExpectationLine.merge_expectation_lines(
575 self._test_to_expectation_line[test],
576 line,
577 model_all_expectations=False)
565 self._test_to_expectation_line[test] = line 578 self._test_to_expectation_line[test] = line
566 579
567 self._merge_dict_of_sets(self._expectation_to_tests, other._expectation_ to_tests) 580 self._merge_dict_of_sets(self._expectation_to_tests, other._expectation_ to_tests)
568 self._merge_dict_of_sets(self._timeline_to_tests, other._timeline_to_tes ts) 581 self._merge_dict_of_sets(self._timeline_to_tests, other._timeline_to_tes ts)
569 self._merge_dict_of_sets(self._result_type_to_tests, other._result_type_ to_tests) 582 self._merge_dict_of_sets(self._result_type_to_tests, other._result_type_ to_tests)
570 583
571 def _dict_of_sets(self, strings_to_constants): 584 def _dict_of_sets(self, strings_to_constants):
572 """Takes a dict of strings->constants and returns a dict mapping 585 """Takes a dict of strings->constants and returns a dict mapping
573 each constant to an empty set.""" 586 each constant to an empty set."""
574 d = {} 587 d = {}
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after
617 expectations = self.get_expectations(test) 630 expectations = self.get_expectations(test)
618 retval = [] 631 retval = []
619 632
620 # FIXME: WontFix should cause the test to get skipped without artificial ly adding SKIP to the expectations list. 633 # FIXME: WontFix should cause the test to get skipped without artificial ly adding SKIP to the expectations list.
621 if WONTFIX in expectations and SKIP in expectations: 634 if WONTFIX in expectations and SKIP in expectations:
622 expectations.remove(SKIP) 635 expectations.remove(SKIP)
623 636
624 for expectation in expectations: 637 for expectation in expectations:
625 retval.append(self.expectation_to_string(expectation)) 638 retval.append(self.expectation_to_string(expectation))
626 639
627 return " ".join(retval) 640 return ' '.join(retval)
628 641
629 def expectation_to_string(self, expectation): 642 def expectation_to_string(self, expectation):
630 """Return the uppercased string equivalent of a given expectation.""" 643 """Return the uppercased string equivalent of a given expectation."""
631 for item in TestExpectations.EXPECTATIONS.items(): 644 for item in TestExpectations.EXPECTATIONS.items():
632 if item[1] == expectation: 645 if item[1] == expectation:
633 return item[0].upper() 646 return item[0].upper()
634 raise ValueError(expectation) 647 raise ValueError(expectation)
635 648
636 def remove_expectation_line(self, test): 649 def remove_expectation_line(self, test):
637 if not self.has_test(test): 650 if not self.has_test(test):
638 return 651 return
639 self._clear_expectations_for_test(test) 652 self._clear_expectations_for_test(test)
640 del self._test_to_expectation_line[test] 653 del self._test_to_expectation_line[test]
641 654
642 def add_expectation_line(self, expectation_line, 655 def add_expectation_line(self, expectation_line,
643 model_all_expectations=False): 656 model_all_expectations=False):
644 """Returns a list of warnings encountered while matching specifiers.""" 657 """Returns a list of warnings encountered while matching specifiers."""
645 658
646 if expectation_line.is_invalid(): 659 if expectation_line.is_invalid():
647 return 660 return
648 661
649 for test in expectation_line.matching_tests: 662 for test in expectation_line.matching_tests:
650 if self._already_seen_better_match(test, expectation_line): 663 if self._already_seen_better_match(test, expectation_line):
651 continue 664 continue
652 665
653 if model_all_expectations: 666 if model_all_expectations:
654 expectation_line = TestExpectationLine.merge_expectation_lines(s elf.get_expectation_line(test), expectation_line, model_all_expectations) 667 expectation_line = TestExpectationLine.merge_expectation_lines(
668 self.get_expectation_line(test),
669 expectation_line,
670 model_all_expectations)
655 671
656 self._clear_expectations_for_test(test) 672 self._clear_expectations_for_test(test)
657 self._test_to_expectation_line[test] = expectation_line 673 self._test_to_expectation_line[test] = expectation_line
658 self._add_test(test, expectation_line) 674 self._add_test(test, expectation_line)
659 675
660 def _add_test(self, test, expectation_line): 676 def _add_test(self, test, expectation_line):
661 """Sets the expected state for a given test. 677 """Sets the expected state for a given test.
662 678
663 This routine assumes the test has not been added before. If it has, 679 This routine assumes the test has not been added before. If it has,
664 use _clear_expectations_for_test() to reset the state prior to 680 use _clear_expectations_for_test() to reset the state prior to
(...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after
741 # to be warnings and return False". 757 # to be warnings and return False".
742 758
743 if prev_expectation_line.matching_configurations == expectation_line.mat ching_configurations: 759 if prev_expectation_line.matching_configurations == expectation_line.mat ching_configurations:
744 expectation_line.warnings.append('Duplicate or ambiguous entry lines %s:%s and %s:%s.' % ( 760 expectation_line.warnings.append('Duplicate or ambiguous entry lines %s:%s and %s:%s.' % (
745 self._shorten_filename(prev_expectation_line.filename), prev_exp ectation_line.line_numbers, 761 self._shorten_filename(prev_expectation_line.filename), prev_exp ectation_line.line_numbers,
746 self._shorten_filename(expectation_line.filename), expectation_l ine.line_numbers)) 762 self._shorten_filename(expectation_line.filename), expectation_l ine.line_numbers))
747 return True 763 return True
748 764
749 if prev_expectation_line.matching_configurations >= expectation_line.mat ching_configurations: 765 if prev_expectation_line.matching_configurations >= expectation_line.mat ching_configurations:
750 expectation_line.warnings.append('More specific entry for %s on line %s:%s overrides line %s:%s.' % (expectation_line.name, 766 expectation_line.warnings.append('More specific entry for %s on line %s:%s overrides line %s:%s.' % (expectation_line.name,
751 self._shorten_filename(prev_expectation_line.filename), prev_exp ectation_line.line_numbers, 767 self._shorten_filename(
752 self._shorten_filename(expectation_line.filename), expectation_l ine.line_numbers)) 768 prev_expectation_line.filename), prev_expec tation_line.line_numbers,
769 self._shorten_filename(expectation_line.filenam e), expectation_line.line_numbers))
753 # FIXME: return False if we want more specific to win. 770 # FIXME: return False if we want more specific to win.
754 return True 771 return True
755 772
756 if prev_expectation_line.matching_configurations <= expectation_line.mat ching_configurations: 773 if prev_expectation_line.matching_configurations <= expectation_line.mat ching_configurations:
757 expectation_line.warnings.append('More specific entry for %s on line %s:%s overrides line %s:%s.' % (expectation_line.name, 774 expectation_line.warnings.append('More specific entry for %s on line %s:%s overrides line %s:%s.' % (expectation_line.name,
758 self._shorten_filename(expectation_line.filename), expectation_l ine.line_numbers, 775 self._shorten_filename(
759 self._shorten_filename(prev_expectation_line.filename), prev_exp ectation_line.line_numbers)) 776 expectation_line.filename), expectation_lin e.line_numbers,
777 self._shorten_filename(prev_expectation_line.fi lename), prev_expectation_line.line_numbers))
760 return True 778 return True
761 779
762 if prev_expectation_line.matching_configurations & expectation_line.matc hing_configurations: 780 if prev_expectation_line.matching_configurations & expectation_line.matc hing_configurations:
763 expectation_line.warnings.append('Entries for %s on lines %s:%s and %s:%s match overlapping sets of configurations.' % (expectation_line.name, 781 expectation_line.warnings.append('Entries for %s on lines %s:%s and %s:%s match overlapping sets of configurations.' % (expectation_line.name,
764 self._shorten_filename(prev_expectation_line.filename), prev_exp ectation_line.line_numbers, 782 self._shorten_filename(
765 self._shorten_filename(expectation_line.filename), expectation_l ine.line_numbers)) 783 prev_expectation_line.fi lename), prev_expectation_line.line_numbers,
784 self._shorten_filename(expec tation_line.filename), expectation_line.line_numbers))
766 return True 785 return True
767 786
768 # Configuration sets are disjoint, then. 787 # Configuration sets are disjoint, then.
769 return False 788 return False
770 789
771 790
772 class TestExpectations(object): 791 class TestExpectations(object):
792
773 """Test expectations consist of lines with specifications of what 793 """Test expectations consist of lines with specifications of what
774 to expect from layout test cases. The test cases can be directories 794 to expect from layout test cases. The test cases can be directories
775 in which case the expectations apply to all test cases in that 795 in which case the expectations apply to all test cases in that
776 directory and any subdirectory. The format is along the lines of: 796 directory and any subdirectory. The format is along the lines of:
777 797
778 LayoutTests/fast/js/fixme.js [ Failure ] 798 LayoutTests/fast/js/fixme.js [ Failure ]
779 LayoutTests/fast/js/flaky.js [ Failure Pass ] 799 LayoutTests/fast/js/flaky.js [ Failure Pass ]
780 LayoutTests/fast/js/crash.js [ Crash Failure Pass Timeout ] 800 LayoutTests/fast/js/crash.js [ Crash Failure Pass Timeout ]
781 ... 801 ...
782 802
(...skipping 25 matching lines...) Expand all
808 'timeout': TIMEOUT, 828 'timeout': TIMEOUT,
809 'crash': CRASH, 829 'crash': CRASH,
810 'leak': LEAK, 830 'leak': LEAK,
811 'missing': MISSING, 831 'missing': MISSING,
812 TestExpectationParser.SKIP_MODIFIER: SKIP, 832 TestExpectationParser.SKIP_MODIFIER: SKIP,
813 TestExpectationParser.NEEDS_REBASELINE_MODIFIER: NEEDS_REBAS ELINE, 833 TestExpectationParser.NEEDS_REBASELINE_MODIFIER: NEEDS_REBAS ELINE,
814 TestExpectationParser.NEEDS_MANUAL_REBASELINE_MODIFIER: NEED S_MANUAL_REBASELINE, 834 TestExpectationParser.NEEDS_MANUAL_REBASELINE_MODIFIER: NEED S_MANUAL_REBASELINE,
815 TestExpectationParser.WONTFIX_MODIFIER: WONTFIX, 835 TestExpectationParser.WONTFIX_MODIFIER: WONTFIX,
816 TestExpectationParser.SLOW_MODIFIER: SLOW, 836 TestExpectationParser.SLOW_MODIFIER: SLOW,
817 TestExpectationParser.REBASELINE_MODIFIER: REBASELINE, 837 TestExpectationParser.REBASELINE_MODIFIER: REBASELINE,
818 } 838 }
819 839
820 EXPECTATIONS_TO_STRING = dict((k, v) for (v, k) in EXPECTATIONS.iteritems()) 840 EXPECTATIONS_TO_STRING = dict((k, v) for (v, k) in EXPECTATIONS.iteritems())
821 841
822 # (aggregated by category, pass/fail/skip, type) 842 # (aggregated by category, pass/fail/skip, type)
823 EXPECTATION_DESCRIPTIONS = {SKIP: 'skipped', 843 EXPECTATION_DESCRIPTIONS = {SKIP: 'skipped',
824 PASS: 'passes', 844 PASS: 'passes',
825 FAIL: 'failures', 845 FAIL: 'failures',
826 IMAGE: 'image-only failures', 846 IMAGE: 'image-only failures',
827 TEXT: 'text-only failures', 847 TEXT: 'text-only failures',
828 IMAGE_PLUS_TEXT: 'image and text failures', 848 IMAGE_PLUS_TEXT: 'image and text failures',
(...skipping 25 matching lines...) Expand all
854 """Returns whether we got a result we were expecting. 874 """Returns whether we got a result we were expecting.
855 Args: 875 Args:
856 result: actual result of a test execution 876 result: actual result of a test execution
857 expected_results: set of results listed in test_expectations 877 expected_results: set of results listed in test_expectations
858 test_needs_rebaselining: whether test was marked as REBASELINE""" 878 test_needs_rebaselining: whether test was marked as REBASELINE"""
859 if not (set(expected_results) - (set(TestExpectations.NON_TEST_OUTCOME_E XPECTATIONS))): 879 if not (set(expected_results) - (set(TestExpectations.NON_TEST_OUTCOME_E XPECTATIONS))):
860 expected_results = set([PASS]) 880 expected_results = set([PASS])
861 881
862 if result in expected_results: 882 if result in expected_results:
863 return True 883 return True
864 if result in (PASS, TEXT, IMAGE, IMAGE_PLUS_TEXT, AUDIO, MISSING) and (N EEDS_REBASELINE in expected_results or NEEDS_MANUAL_REBASELINE in expected_resul ts): 884 if result in (PASS, TEXT, IMAGE, IMAGE_PLUS_TEXT, AUDIO, MISSING) and (
885 NEEDS_REBASELINE in expected_results or NEEDS_MANUAL_REBASELINE in expected_results):
865 return True 886 return True
866 if result in (TEXT, IMAGE_PLUS_TEXT, AUDIO) and (FAIL in expected_result s): 887 if result in (TEXT, IMAGE_PLUS_TEXT, AUDIO) and (FAIL in expected_result s):
867 return True 888 return True
868 if result == MISSING and test_needs_rebaselining: 889 if result == MISSING and test_needs_rebaselining:
869 return True 890 return True
870 if result == SKIP: 891 if result == SKIP:
871 return True 892 return True
872 return False 893 return False
873 894
874 @staticmethod 895 @staticmethod
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after
920 suffixes.add('wav') 941 suffixes.add('wav')
921 if 'MISSING' in expectations: 942 if 'MISSING' in expectations:
922 suffixes.add('txt') 943 suffixes.add('txt')
923 suffixes.add('png') 944 suffixes.add('png')
924 suffixes.add('wav') 945 suffixes.add('wav')
925 return suffixes 946 return suffixes
926 947
927 # FIXME: This constructor does too much work. We should move the actual pars ing of 948 # FIXME: This constructor does too much work. We should move the actual pars ing of
928 # the expectations into separate routines so that linting and handling overr ides 949 # the expectations into separate routines so that linting and handling overr ides
929 # can be controlled separately, and the constructor can be more of a no-op. 950 # can be controlled separately, and the constructor can be more of a no-op.
930 def __init__(self, port, tests=None, include_overrides=True, expectations_di ct=None, model_all_expectations=False, is_lint_mode=False): 951 def __init__(self, port, tests=None, include_overrides=True, expectations_di ct=None,
952 model_all_expectations=False, is_lint_mode=False):
931 self._full_test_list = tests 953 self._full_test_list = tests
932 self._test_config = port.test_configuration() 954 self._test_config = port.test_configuration()
933 self._is_lint_mode = is_lint_mode 955 self._is_lint_mode = is_lint_mode
934 self._model_all_expectations = self._is_lint_mode or model_all_expectati ons 956 self._model_all_expectations = self._is_lint_mode or model_all_expectati ons
935 self._model = TestExpectationsModel(self._shorten_filename) 957 self._model = TestExpectationsModel(self._shorten_filename)
936 self._parser = TestExpectationParser(port, tests, self._is_lint_mode) 958 self._parser = TestExpectationParser(port, tests, self._is_lint_mode)
937 self._port = port 959 self._port = port
938 self._skipped_tests_warnings = [] 960 self._skipped_tests_warnings = []
939 self._expectations = [] 961 self._expectations = []
940 962
(...skipping 71 matching lines...) Expand 10 before | Expand all | Expand 10 after
1012 def _shorten_filename(self, filename): 1034 def _shorten_filename(self, filename):
1013 if filename.startswith(self._port.path_from_webkit_base()): 1035 if filename.startswith(self._port.path_from_webkit_base()):
1014 return self._port.host.filesystem.relpath(filename, self._port.path_ from_webkit_base()) 1036 return self._port.host.filesystem.relpath(filename, self._port.path_ from_webkit_base())
1015 return filename 1037 return filename
1016 1038
1017 def _report_warnings(self): 1039 def _report_warnings(self):
1018 warnings = [] 1040 warnings = []
1019 for expectation in self._expectations: 1041 for expectation in self._expectations:
1020 for warning in expectation.warnings: 1042 for warning in expectation.warnings:
1021 warnings.append('%s:%s %s %s' % (self._shorten_filename(expectat ion.filename), expectation.line_numbers, 1043 warnings.append('%s:%s %s %s' % (self._shorten_filename(expectat ion.filename), expectation.line_numbers,
1022 warning, expectation.name if expectation.expecta tions else expectation.original_string)) 1044 warning, expectation.name if ex pectation.expectations else expectation.original_string))
1023 1045
1024 if warnings: 1046 if warnings:
1025 self._has_warnings = True 1047 self._has_warnings = True
1026 if self._is_lint_mode: 1048 if self._is_lint_mode:
1027 raise ParseError(warnings) 1049 raise ParseError(warnings)
1028 _log.warning('--lint-test-files warnings:') 1050 _log.warning('--lint-test-files warnings:')
1029 for warning in warnings: 1051 for warning in warnings:
1030 _log.warning(warning) 1052 _log.warning(warning)
1031 _log.warning('') 1053 _log.warning('')
1032 1054
(...skipping 83 matching lines...) Expand 10 before | Expand all | Expand 10 after
1116 def serialize(expectation_line): 1138 def serialize(expectation_line):
1117 # If reconstitute_only_these is an empty list, we want to return ori ginal_string. 1139 # If reconstitute_only_these is an empty list, we want to return ori ginal_string.
1118 # So we need to compare reconstitute_only_these to None, not just ch eck if it's falsey. 1140 # So we need to compare reconstitute_only_these to None, not just ch eck if it's falsey.
1119 if reconstitute_only_these is None or expectation_line in reconstitu te_only_these: 1141 if reconstitute_only_these is None or expectation_line in reconstitu te_only_these:
1120 return expectation_line.to_string(test_configuration_converter) 1142 return expectation_line.to_string(test_configuration_converter)
1121 return expectation_line.original_string 1143 return expectation_line.original_string
1122 1144
1123 def nones_out(expectation_line): 1145 def nones_out(expectation_line):
1124 return expectation_line is not None 1146 return expectation_line is not None
1125 1147
1126 return "\n".join(filter(nones_out, map(serialize, expectation_lines))) 1148 return '\n'.join(filter(nones_out, map(serialize, expectation_lines)))
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698