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

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

Issue 20830003: Get rid of the distinction between modifiers and expectations. (Closed) Base URL: svn://svn.chromium.org/blink/trunk
Patch Set: clean up a couple things Created 7 years, 5 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 20 matching lines...) Expand all
31 """ 31 """
32 32
33 import logging 33 import logging
34 import re 34 import re
35 35
36 from webkitpy.layout_tests.models.test_configuration import TestConfigurationCon verter 36 from webkitpy.layout_tests.models.test_configuration import TestConfigurationCon verter
37 37
38 _log = logging.getLogger(__name__) 38 _log = logging.getLogger(__name__)
39 39
40 40
41 # Test expectation and modifier constants. 41 # Test expectation and specifier constants.
42 # 42 #
43 # FIXME: range() starts with 0 which makes if expectation checks harder 43 # FIXME: range() starts with 0 which makes if expectation checks harder
44 # as PASS is 0. 44 # as PASS is 0.
45 (PASS, FAIL, TEXT, IMAGE, IMAGE_PLUS_TEXT, AUDIO, TIMEOUT, CRASH, SKIP, WONTFIX, 45 (PASS, FAIL, TEXT, IMAGE, IMAGE_PLUS_TEXT, AUDIO, TIMEOUT, CRASH, SKIP, WONTFIX,
46 SLOW, REBASELINE, NEEDS_REBASELINE, NEEDS_MANUAL_REBASELINE, MISSING, FLAKY, NO W, NONE) = range(18) 46 SLOW, REBASELINE, NEEDS_REBASELINE, NEEDS_MANUAL_REBASELINE, MISSING, FLAKY, NO W, NONE) = range(18)
47 47
48 # FIXME: Perhas these two routines should be part of the Port instead? 48 # FIXME: Perhas these two routines should be part of the Port instead?
49 BASELINE_SUFFIX_LIST = ('png', 'wav', 'txt') 49 BASELINE_SUFFIX_LIST = ('png', 'wav', 'txt')
50 50
51 WEBKIT_BUG_PREFIX = 'webkit.org/b/' 51 WEBKIT_BUG_PREFIX = 'webkit.org/b/'
(...skipping 13 matching lines...) Expand all
65 def __str__(self): 65 def __str__(self):
66 return '\n'.join(map(str, self.warnings)) 66 return '\n'.join(map(str, self.warnings))
67 67
68 def __repr__(self): 68 def __repr__(self):
69 return 'ParseError(warnings=%s)' % self.warnings 69 return 'ParseError(warnings=%s)' % self.warnings
70 70
71 71
72 class TestExpectationParser(object): 72 class TestExpectationParser(object):
73 """Provides parsing facilities for lines in the test_expectation.txt file."" " 73 """Provides parsing facilities for lines in the test_expectation.txt file."" "
74 74
75 # FIXME: Rename these to *_KEYWORD as in MISSING_KEYWORD above, but make the case studdly-caps to match the actual file contents.
Dirk Pranke 2013/07/26 23:07:28 "studly" ?
75 REBASELINE_MODIFIER = 'rebaseline' 76 REBASELINE_MODIFIER = 'rebaseline'
76 NEEDS_REBASELINE_MODIFIER = 'needsrebaseline' 77 NEEDS_REBASELINE_MODIFIER = 'needsrebaseline'
77 NEEDS_MANUAL_REBASELINE_MODIFIER = 'needsmanualrebaseline' 78 NEEDS_MANUAL_REBASELINE_MODIFIER = 'needsmanualrebaseline'
78 PASS_EXPECTATION = 'pass' 79 PASS_EXPECTATION = 'pass'
79 SKIP_MODIFIER = 'skip' 80 SKIP_MODIFIER = 'skip'
80 SLOW_MODIFIER = 'slow' 81 SLOW_MODIFIER = 'slow'
81 WONTFIX_MODIFIER = 'wontfix' 82 WONTFIX_MODIFIER = 'wontfix'
82 83
83 TIMEOUT_EXPECTATION = 'timeout' 84 TIMEOUT_EXPECTATION = 'timeout'
84 85
85 MISSING_BUG_WARNING = 'Test lacks BUG modifier.' 86 MISSING_BUG_WARNING = 'Test lacks BUG specifier.'
86 87
87 def __init__(self, port, full_test_list, allow_rebaseline_modifier): 88 def __init__(self, port, full_test_list, allow_rebaseline):
88 self._port = port 89 self._port = port
89 self._test_configuration_converter = TestConfigurationConverter(set(port .all_test_configurations()), port.configuration_specifier_macros()) 90 self._test_configuration_converter = TestConfigurationConverter(set(port .all_test_configurations()), port.configuration_specifier_macros())
90 self._full_test_list = full_test_list 91 self._full_test_list = full_test_list
91 self._allow_rebaseline_modifier = allow_rebaseline_modifier 92 self._allow_rebaseline = allow_rebaseline
92 93
93 def parse(self, filename, expectations_string): 94 def parse(self, filename, expectations_string):
94 expectation_lines = [] 95 expectation_lines = []
95 line_number = 0 96 line_number = 0
96 for line in expectations_string.split("\n"): 97 for line in expectations_string.split("\n"):
97 line_number += 1 98 line_number += 1
98 test_expectation = self._tokenize_line(filename, line, line_number) 99 test_expectation = self._tokenize_line(filename, line, line_number)
99 self._parse_line(test_expectation) 100 self._parse_line(test_expectation)
100 expectation_lines.append(test_expectation) 101 expectation_lines.append(test_expectation)
101 return expectation_lines 102 return expectation_lines
(...skipping 10 matching lines...) Expand all
112 def expectation_line_for_test(self, test_name, expectations): 113 def expectation_line_for_test(self, test_name, expectations):
113 expectation_line = self._create_expectation_line(test_name, expectations , '<Bot TestExpectations>') 114 expectation_line = self._create_expectation_line(test_name, expectations , '<Bot TestExpectations>')
114 self._parse_line(expectation_line) 115 self._parse_line(expectation_line)
115 return expectation_line 116 return expectation_line
116 117
117 118
118 def expectation_for_skipped_test(self, test_name): 119 def expectation_for_skipped_test(self, test_name):
119 if not self._port.test_exists(test_name): 120 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) 121 _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>') 122 expectation_line = self._create_expectation_line(test_name, [TestExpecta tionParser.PASS_EXPECTATION], '<Skipped file>')
122 # FIXME: It's not clear what the expectations for a skipped test should be; the expectations 123 expectation_line.expectations = [TestExpectationParser.SKIP_MODIFIER, Te stExpectationParser.WONTFIX_MODIFIER]
123 # might be different for different entries in a Skipped file, or from th e command line, or from
124 # only running parts of the tests. It's also not clear if it matters muc h.
125 expectation_line.modifiers = [TestExpectationParser.SKIP_MODIFIER, TestE xpectationParser.WONTFIX_MODIFIER]
126 expectation_line.is_skipped_outside_expectations_file = True 124 expectation_line.is_skipped_outside_expectations_file = True
127 self._parse_line(expectation_line) 125 self._parse_line(expectation_line)
128 return expectation_line 126 return expectation_line
129 127
130 def _parse_line(self, expectation_line): 128 def _parse_line(self, expectation_line):
131 if not expectation_line.name: 129 if not expectation_line.name:
132 return 130 return
133 131
134 if not self._check_test_exists(expectation_line): 132 if not self._check_test_exists(expectation_line):
135 return 133 return
136 134
137 expectation_line.is_file = self._port.test_isfile(expectation_line.name) 135 expectation_line.is_file = self._port.test_isfile(expectation_line.name)
138 if expectation_line.is_file: 136 if expectation_line.is_file:
139 expectation_line.path = expectation_line.name 137 expectation_line.path = expectation_line.name
140 else: 138 else:
141 expectation_line.path = self._port.normalize_test_name(expectation_l ine.name) 139 expectation_line.path = self._port.normalize_test_name(expectation_l ine.name)
142 140
143 self._collect_matching_tests(expectation_line) 141 self._collect_matching_tests(expectation_line)
144 142
145 self._parse_modifiers(expectation_line) 143 self._parse_specifiers(expectation_line)
146 self._parse_expectations(expectation_line) 144 self._parse_expectations(expectation_line)
147 145
148 def _parse_modifiers(self, expectation_line): 146 def _parse_specifiers(self, expectation_line):
149 has_wontfix = False 147 parsed_specifiers = set([specifier.lower() for specifier in expectation_ line.specifiers])
150 parsed_specifiers = set()
151
152 modifiers = [modifier.lower() for modifier in expectation_line.modifiers ]
153 expectations = [expectation.lower() for expectation in expectation_line. expectations] 148 expectations = [expectation.lower() for expectation in expectation_line. expectations]
154 149
155 if self.SLOW_MODIFIER in modifiers and self.TIMEOUT_EXPECTATION in expec tations: 150 if self.SLOW_MODIFIER in expectations and self.TIMEOUT_EXPECTATION in ex pectations:
156 expectation_line.warnings.append('A test can not be both SLOW and TI MEOUT. If it times out indefinitely, then it should be just TIMEOUT.') 151 expectation_line.warnings.append('A test can not be both SLOW and TI MEOUT. If it times out indefinitely, then it should be just TIMEOUT.')
157 152
158 for modifier in expectation_line.modifiers: 153 if not expectation_line.bugs and self.WONTFIX_MODIFIER not in expectatio ns and self._port.warn_if_bug_missing_in_test_expectations():
159 # FIXME: Store the unmodified modifier.
160 modifier = modifier.lower()
161 if modifier in TestExpectations.MODIFIERS:
162 expectation_line.parsed_modifiers.append(modifier)
163 if modifier == self.WONTFIX_MODIFIER:
164 has_wontfix = True
165 else:
166 parsed_specifiers.add(modifier)
167
168 if not expectation_line.bugs and not has_wontfix and self._port.warn_if_ bug_missing_in_test_expectations():
169 expectation_line.warnings.append(self.MISSING_BUG_WARNING) 154 expectation_line.warnings.append(self.MISSING_BUG_WARNING)
170 155
171 if self._allow_rebaseline_modifier and self.REBASELINE_MODIFIER in modif iers: 156 if self._allow_rebaseline and self.REBASELINE_MODIFIER in expectations:
172 expectation_line.warnings.append('REBASELINE should only be used for running rebaseline.py. Cannot be checked in.') 157 expectation_line.warnings.append('REBASELINE should only be used for running rebaseline.py. Cannot be checked in.')
173 158
174 expectation_line.matching_configurations = self._test_configuration_conv erter.to_config_set(parsed_specifiers, expectation_line.warnings) 159 expectation_line.matching_configurations = self._test_configuration_conv erter.to_config_set(parsed_specifiers, expectation_line.warnings)
175 160
176 def _parse_expectations(self, expectation_line): 161 def _parse_expectations(self, expectation_line):
177 result = set() 162 result = set()
178 for part in expectation_line.expectations: 163 for part in expectation_line.expectations:
179 expectation = TestExpectations.expectation_from_string(part) 164 expectation = TestExpectations.expectation_from_string(part)
180 if expectation is None: # Careful, PASS is currently 0. 165 if expectation is None: # Careful, PASS is currently 0.
181 expectation_line.warnings.append('Unsupported expectation: %s' % part) 166 expectation_line.warnings.append('Unsupported expectation: %s' % part)
(...skipping 29 matching lines...) Expand all
211 if not expectation_line.is_file: 196 if not expectation_line.is_file:
212 # this is a test category, return all the tests of the category. 197 # this is a test category, return all the tests of the category.
213 expectation_line.matching_tests = [test for test in self._full_test_ list if test.startswith(expectation_line.path)] 198 expectation_line.matching_tests = [test for test in self._full_test_ list if test.startswith(expectation_line.path)]
214 return 199 return
215 200
216 # this is a test file, do a quick check if it's in the 201 # this is a test file, do a quick check if it's in the
217 # full test suite. 202 # full test suite.
218 if expectation_line.path in self._full_test_list: 203 if expectation_line.path in self._full_test_list:
219 expectation_line.matching_tests.append(expectation_line.path) 204 expectation_line.matching_tests.append(expectation_line.path)
220 205
221 # FIXME: Update the original modifiers and remove this once the old syntax i s gone. 206 # FIXME: Update the original specifiers and remove this once the old syntax is gone.
222 _configuration_tokens_list = [ 207 _configuration_tokens_list = [
223 'Mac', 'SnowLeopard', 'Lion', 'MountainLion', 208 'Mac', 'SnowLeopard', 'Lion', 'MountainLion',
224 'Win', 'XP', 'Win7', 209 'Win', 'XP', 'Win7',
225 'Linux', 210 'Linux',
226 'Android', 211 'Android',
227 'Release', 212 'Release',
228 'Debug', 213 'Debug',
229 ] 214 ]
230 215
231 _configuration_tokens = dict((token, token.upper()) for token in _configurat ion_tokens_list) 216 _configuration_tokens = dict((token, token.upper()) for token in _configurat ion_tokens_list)
232 _inverted_configuration_tokens = dict((value, name) for name, value in _conf iguration_tokens.iteritems()) 217 _inverted_configuration_tokens = dict((value, name) for name, value in _conf iguration_tokens.iteritems())
233 218
234 # FIXME: Update the original modifiers list and remove this once the old syn tax is gone. 219 # FIXME: Update the original specifiers list and remove this once the old sy ntax is gone.
235 _expectation_tokens = { 220 _expectation_tokens = {
236 'Crash': 'CRASH', 221 'Crash': 'CRASH',
237 'Failure': 'FAIL', 222 'Failure': 'FAIL',
238 'ImageOnlyFailure': 'IMAGE', 223 'ImageOnlyFailure': 'IMAGE',
239 MISSING_KEYWORD: 'MISSING', 224 MISSING_KEYWORD: 'MISSING',
240 'Pass': 'PASS', 225 'Pass': 'PASS',
241 'Rebaseline': 'REBASELINE', 226 'Rebaseline': 'REBASELINE',
242 NEEDS_REBASELINE_KEYWORD: NEEDS_REBASELINE_KEYWORD, 227 NEEDS_REBASELINE_KEYWORD: NEEDS_REBASELINE_KEYWORD,
243 NEEDS_MANUAL_REBASELINE_KEYWORD: NEEDS_MANUAL_REBASELINE_KEYWORD, 228 NEEDS_MANUAL_REBASELINE_KEYWORD: NEEDS_MANUAL_REBASELINE_KEYWORD,
244 'Skip': 'SKIP', 229 'Skip': 'SKIP',
245 'Slow': 'SLOW', 230 'Slow': 'SLOW',
246 'Timeout': 'TIMEOUT', 231 'Timeout': 'TIMEOUT',
247 'WontFix': 'WONTFIX', 232 'WontFix': 'WONTFIX',
248 } 233 }
249 234
250 _inverted_expectation_tokens = dict([(value, name) for name, value in _expec tation_tokens.iteritems()] + 235 _inverted_expectation_tokens = dict([(value, name) for name, value in _expec tation_tokens.iteritems()] +
251 [('TEXT', 'Failure'), ('IMAGE+TEXT', 'Fa ilure'), ('AUDIO', 'Failure')]) 236 [('TEXT', 'Failure'), ('IMAGE+TEXT', 'Fa ilure'), ('AUDIO', 'Failure')])
252 237
253 # FIXME: Seems like these should be classmethods on TestExpectationLine inst ead of TestExpectationParser. 238 # FIXME: Seems like these should be classmethods on TestExpectationLine inst ead of TestExpectationParser.
254 @classmethod 239 @classmethod
255 def _tokenize_line(cls, filename, expectation_string, line_number): 240 def _tokenize_line(cls, filename, expectation_string, line_number):
256 """Tokenizes a line from TestExpectations and returns an unparsed TestEx pectationLine instance using the old format. 241 """Tokenizes a line from TestExpectations and returns an unparsed TestEx pectationLine instance using the old format.
257 242
258 The new format for a test expectation line is: 243 The new format for a test expectation line is:
259 244
260 [[bugs] [ "[" <configuration modifiers> "]" <name> [ "[" <expectations> "]" ["#" <comment>] 245 [[bugs] [ "[" <configuration specifiers> "]" <name> [ "[" <expectations> "]" ["#" <comment>]
261 246
262 Any errant whitespace is not preserved. 247 Any errant whitespace is not preserved.
263 248
264 """ 249 """
265 expectation_line = TestExpectationLine() 250 expectation_line = TestExpectationLine()
266 expectation_line.original_string = expectation_string 251 expectation_line.original_string = expectation_string
267 expectation_line.filename = filename 252 expectation_line.filename = filename
268 expectation_line.line_numbers = str(line_number) 253 expectation_line.line_numbers = str(line_number)
269 254
270 comment_index = expectation_string.find("#") 255 comment_index = expectation_string.find("#")
271 if comment_index == -1: 256 if comment_index == -1:
272 comment_index = len(expectation_string) 257 comment_index = len(expectation_string)
273 else: 258 else:
274 expectation_line.comment = expectation_string[comment_index + 1:] 259 expectation_line.comment = expectation_string[comment_index + 1:]
275 260
276 remaining_string = re.sub(r"\s+", " ", expectation_string[:comment_index ].strip()) 261 remaining_string = re.sub(r"\s+", " ", expectation_string[:comment_index ].strip())
277 if len(remaining_string) == 0: 262 if len(remaining_string) == 0:
278 return expectation_line 263 return expectation_line
279 264
280 # special-case parsing this so that we fail immediately instead of treat ing this as a test name 265 # special-case parsing this so that we fail immediately instead of treat ing this as a test name
281 if remaining_string.startswith('//'): 266 if remaining_string.startswith('//'):
282 expectation_line.warnings = ['use "#" instead of "//" for comments'] 267 expectation_line.warnings = ['use "#" instead of "//" for comments']
283 return expectation_line 268 return expectation_line
284 269
285 bugs = [] 270 bugs = []
286 modifiers = [] 271 specifiers = []
287 name = None 272 name = None
288 expectations = [] 273 expectations = []
289 warnings = [] 274 warnings = []
290 has_unrecognized_expectation = False 275 has_unrecognized_expectation = False
291 276
292 tokens = remaining_string.split() 277 tokens = remaining_string.split()
293 state = 'start' 278 state = 'start'
294 for token in tokens: 279 for token in tokens:
295 if (token.startswith(WEBKIT_BUG_PREFIX) or 280 if (token.startswith(WEBKIT_BUG_PREFIX) or
296 token.startswith(CHROMIUM_BUG_PREFIX) or 281 token.startswith(CHROMIUM_BUG_PREFIX) or
(...skipping 28 matching lines...) Expand all
325 state = 'name' 310 state = 'name'
326 elif state == 'expectations': 311 elif state == 'expectations':
327 state = 'done' 312 state = 'done'
328 else: 313 else:
329 warnings.append('unexpected "]"') 314 warnings.append('unexpected "]"')
330 break 315 break
331 elif token in ('//', ':', '='): 316 elif token in ('//', ':', '='):
332 warnings.append('"%s" is not legal in the new TestExpectations s yntax.' % token) 317 warnings.append('"%s" is not legal in the new TestExpectations s yntax.' % token)
333 break 318 break
334 elif state == 'configuration': 319 elif state == 'configuration':
335 modifiers.append(cls._configuration_tokens.get(token, token)) 320 specifiers.append(cls._configuration_tokens.get(token, token))
336 elif state == 'expectations': 321 elif state == 'expectations':
337 if token in ('Rebaseline', 'Skip', 'Slow', 'WontFix'): 322 if token not in cls._expectation_tokens:
338 modifiers.append(token.upper())
339 elif token not in cls._expectation_tokens:
340 has_unrecognized_expectation = True 323 has_unrecognized_expectation = True
341 warnings.append('Unrecognized expectation "%s"' % token) 324 warnings.append('Unrecognized expectation "%s"' % token)
342 else: 325 else:
343 expectations.append(cls._expectation_tokens.get(token, token )) 326 expectations.append(cls._expectation_tokens.get(token, token ))
344 elif state == 'name_found': 327 elif state == 'name_found':
345 warnings.append('expecting "[", "#", or end of line instead of " %s"' % token) 328 warnings.append('expecting "[", "#", or end of line instead of " %s"' % token)
346 break 329 break
347 else: 330 else:
348 name = token 331 name = token
349 state = 'name_found' 332 state = 'name_found'
350 333
351 if not warnings: 334 if not warnings:
352 if not name: 335 if not name:
353 warnings.append('Did not find a test name.') 336 warnings.append('Did not find a test name.')
354 elif state not in ('name_found', 'done'): 337 elif state not in ('name_found', 'done'):
355 warnings.append('Missing a "]"') 338 warnings.append('Missing a "]"')
356 339
357 if 'WONTFIX' in modifiers and 'SKIP' not in modifiers and not expectatio ns: 340 if 'WONTFIX' in expectations and 'SKIP' not in expectations:
358 modifiers.append('SKIP') 341 expectations.append('SKIP')
359 342
360 if 'SKIP' in modifiers and expectations: 343 if ('SKIP' in expectations or 'WONTFIX' in expectations) and len(set(exp ectations) - set(['SKIP', 'WONTFIX'])):
361 # FIXME: This is really a semantic warning and shouldn't be here. Re move when we drop the old syntax. 344 warnings.append('A test marked Skip or WontFix must not have other e xpectations.')
362 warnings.append('A test marked Skip must not have other expectations .') 345
363 elif not expectations: 346 if not expectations and not has_unrecognized_expectation:
364 if not has_unrecognized_expectation and 'SKIP' not in modifiers and 'REBASELINE' not in modifiers and NEEDS_REBASELINE_KEYWORD not in modifiers and NEEDS_MANUAL_REBASELINE_KEYWORD not in modifiers and 'SLOW' not in modifiers: 347 warnings.append('Missing expectations.')
365 warnings.append('Missing expectations.')
366 expectations = ['PASS']
367 348
368 expectation_line.bugs = bugs 349 expectation_line.bugs = bugs
369 expectation_line.modifiers = modifiers 350 expectation_line.specifiers = specifiers
370 expectation_line.expectations = expectations 351 expectation_line.expectations = expectations
371 expectation_line.name = name 352 expectation_line.name = name
372 expectation_line.warnings = warnings 353 expectation_line.warnings = warnings
373 return expectation_line 354 return expectation_line
374 355
375 @classmethod 356 @classmethod
376 def _split_space_separated(cls, space_separated_string): 357 def _split_space_separated(cls, space_separated_string):
377 """Splits a space-separated string into an array.""" 358 """Splits a space-separated string into an array."""
378 return [part.strip() for part in space_separated_string.strip().split(' ')] 359 return [part.strip() for part in space_separated_string.strip().split(' ')]
379 360
380 361
381 class TestExpectationLine(object): 362 class TestExpectationLine(object):
382 """Represents a line in test expectations file.""" 363 """Represents a line in test expectations file."""
383 364
384 def __init__(self): 365 def __init__(self):
385 """Initializes a blank-line equivalent of an expectation.""" 366 """Initializes a blank-line equivalent of an expectation."""
386 self.original_string = None 367 self.original_string = None
387 self.filename = None # this is the path to the expectations file for th is line 368 self.filename = None # this is the path to the expectations file for th is line
388 self.line_numbers = None 369 self.line_numbers = None
389 self.name = None # this is the path in the line itself 370 self.name = None # this is the path in the line itself
390 self.path = None # this is the normpath of self.name 371 self.path = None # this is the normpath of self.name
391 self.bugs = [] 372 self.bugs = []
392 self.modifiers = [] 373 self.specifiers = []
393 self.parsed_modifiers = [] 374 self.parsed_specifiers = []
394 self.matching_configurations = set() 375 self.matching_configurations = set()
395 self.expectations = [] 376 self.expectations = []
396 self.parsed_expectations = set() 377 self.parsed_expectations = set()
397 self.comment = None 378 self.comment = None
398 self.matching_tests = [] 379 self.matching_tests = []
399 self.warnings = [] 380 self.warnings = []
400 self.is_skipped_outside_expectations_file = False 381 self.is_skipped_outside_expectations_file = False
401 382
402 def __eq__(self, other): 383 def __eq__(self, other):
403 return (self.original_string == other.original_string 384 return (self.original_string == other.original_string
404 and self.filename == other.filename 385 and self.filename == other.filename
405 and self.line_numbers == other.line_numbers 386 and self.line_numbers == other.line_numbers
406 and self.name == other.name 387 and self.name == other.name
407 and self.path == other.path 388 and self.path == other.path
408 and self.bugs == other.bugs 389 and self.bugs == other.bugs
409 and self.modifiers == other.modifiers 390 and self.specifiers == other.specifiers
410 and self.parsed_modifiers == other.parsed_modifiers 391 and self.parsed_specifiers == other.parsed_specifiers
411 and self.matching_configurations == other.matching_configurations 392 and self.matching_configurations == other.matching_configurations
412 and self.expectations == other.expectations 393 and self.expectations == other.expectations
413 and self.parsed_expectations == other.parsed_expectations 394 and self.parsed_expectations == other.parsed_expectations
414 and self.comment == other.comment 395 and self.comment == other.comment
415 and self.matching_tests == other.matching_tests 396 and self.matching_tests == other.matching_tests
416 and self.warnings == other.warnings 397 and self.warnings == other.warnings
417 and self.is_skipped_outside_expectations_file == other.is_skipped_ou tside_expectations_file) 398 and self.is_skipped_outside_expectations_file == other.is_skipped_ou tside_expectations_file)
418 399
419 def is_invalid(self): 400 def is_invalid(self):
420 return self.warnings and self.warnings != [TestExpectationParser.MISSING _BUG_WARNING] 401 return self.warnings and self.warnings != [TestExpectationParser.MISSING _BUG_WARNING]
(...skipping 29 matching lines...) Expand all
450 # We only care about filenames when we're linting, in which case the fil enames are the same. 431 # We only care about filenames when we're linting, in which case the fil enames are the same.
451 # Not clear that there's anything better to do when not linting and the filenames are different. 432 # Not clear that there's anything better to do when not linting and the filenames are different.
452 if model_all_expectations: 433 if model_all_expectations:
453 result.filename = line2.filename 434 result.filename = line2.filename
454 result.line_numbers = line1.line_numbers + "," + line2.line_numbers 435 result.line_numbers = line1.line_numbers + "," + line2.line_numbers
455 result.name = line1.name 436 result.name = line1.name
456 result.path = line1.path 437 result.path = line1.path
457 result.parsed_expectations = set(line1.parsed_expectations) | set(line2. parsed_expectations) 438 result.parsed_expectations = set(line1.parsed_expectations) | set(line2. parsed_expectations)
458 result.expectations = list(set(line1.expectations) | set(line2.expectati ons)) 439 result.expectations = list(set(line1.expectations) | set(line2.expectati ons))
459 result.bugs = list(set(line1.bugs) | set(line2.bugs)) 440 result.bugs = list(set(line1.bugs) | set(line2.bugs))
460 result.modifiers = list(set(line1.modifiers) | set(line2.modifiers)) 441 result.specifiers = list(set(line1.specifiers) | set(line2.specifiers))
461 result.parsed_modifiers = list(set(line1.parsed_modifiers) | set(line2.p arsed_modifiers)) 442 result.parsed_specifiers = list(set(line1.parsed_specifiers) | set(line2 .parsed_specifiers))
462 result.matching_configurations = set(line1.matching_configurations) | se t(line2.matching_configurations) 443 result.matching_configurations = set(line1.matching_configurations) | se t(line2.matching_configurations)
463 result.matching_tests = list(list(set(line1.matching_tests) | set(line2. matching_tests))) 444 result.matching_tests = list(list(set(line1.matching_tests) | set(line2. matching_tests)))
464 result.warnings = list(set(line1.warnings) | set(line2.warnings)) 445 result.warnings = list(set(line1.warnings) | set(line2.warnings))
465 result.is_skipped_outside_expectations_file = line1.is_skipped_outside_e xpectations_file or line2.is_skipped_outside_expectations_file 446 result.is_skipped_outside_expectations_file = line1.is_skipped_outside_e xpectations_file or line2.is_skipped_outside_expectations_file
466 return result 447 return result
467 448
468 def to_string(self, test_configuration_converter, include_modifiers=True, in clude_expectations=True, include_comment=True): 449 def to_string(self, test_configuration_converter, include_specifiers=True, i nclude_expectations=True, include_comment=True):
469 parsed_expectation_to_string = dict([[parsed_expectation, expectation_st ring] for expectation_string, parsed_expectation in TestExpectations.EXPECTATION S.items()]) 450 parsed_expectation_to_string = dict([[parsed_expectation, expectation_st ring] for expectation_string, parsed_expectation in TestExpectations.EXPECTATION S.items()])
470 451
471 if self.is_invalid(): 452 if self.is_invalid():
472 return self.original_string or '' 453 return self.original_string or ''
473 454
474 if self.name is None: 455 if self.name is None:
475 return '' if self.comment is None else "#%s" % self.comment 456 return '' if self.comment is None else "#%s" % self.comment
476 457
477 if test_configuration_converter and self.bugs: 458 if test_configuration_converter and self.bugs:
478 specifiers_list = test_configuration_converter.to_specifiers_list(se lf.matching_configurations) 459 specifiers_list = test_configuration_converter.to_specifiers_list(se lf.matching_configurations)
479 result = [] 460 result = []
480 for specifiers in specifiers_list: 461 for specifiers in specifiers_list:
481 # FIXME: this is silly that we join the modifiers and then immed iately split them. 462 # FIXME: this is silly that we join the specifiers and then imme diately split them.
482 modifiers = self._serialize_parsed_modifiers(test_configuration_ converter, specifiers).split() 463 specifiers = self._serialize_parsed_specifiers(test_configuratio n_converter, specifiers).split()
483 expectations = self._serialize_parsed_expectations(parsed_expect ation_to_string).split() 464 expectations = self._serialize_parsed_expectations(parsed_expect ation_to_string).split()
484 result.append(self._format_line(self.bugs, modifiers, self.name, expectations, self.comment)) 465 result.append(self._format_line(self.bugs, specifiers, self.name , expectations, self.comment))
485 return "\n".join(result) if result else None 466 return "\n".join(result) if result else None
486 467
487 return self._format_line(self.bugs, self.modifiers, self.name, self.expe ctations, self.comment, 468 return self._format_line(self.bugs, self.specifiers, self.name, self.exp ectations, self.comment,
488 include_modifiers, include_expectations, include_comment) 469 include_specifiers, include_expectations, include_comment)
489 470
490 def to_csv(self): 471 def to_csv(self):
491 # Note that this doesn't include the comments. 472 # Note that this doesn't include the comments.
492 return '%s,%s,%s,%s' % (self.name, ' '.join(self.bugs), ' '.join(self.mo difiers), ' '.join(self.expectations)) 473 return '%s,%s,%s,%s' % (self.name, ' '.join(self.bugs), ' '.join(self.sp ecifiers), ' '.join(self.expectations))
493 474
494 def _serialize_parsed_expectations(self, parsed_expectation_to_string): 475 def _serialize_parsed_expectations(self, parsed_expectation_to_string):
495 result = [] 476 result = []
496 for index in TestExpectations.EXPECTATION_ORDER: 477 for index in TestExpectations.EXPECTATION_ORDER:
497 if index in self.parsed_expectations: 478 if index in self.parsed_expectations:
498 result.append(parsed_expectation_to_string[index]) 479 result.append(parsed_expectation_to_string[index])
499 return ' '.join(result) 480 return ' '.join(result)
500 481
501 def _serialize_parsed_modifiers(self, test_configuration_converter, specifie rs): 482 def _serialize_parsed_specifiers(self, test_configuration_converter, specifi ers):
502 result = [] 483 result = []
503 result.extend(sorted(self.parsed_modifiers)) 484 result.extend(sorted(self.parsed_specifiers))
504 result.extend(test_configuration_converter.specifier_sorter().sort_speci fiers(specifiers)) 485 result.extend(test_configuration_converter.specifier_sorter().sort_speci fiers(specifiers))
505 return ' '.join(result) 486 return ' '.join(result)
506 487
507 @staticmethod 488 @staticmethod
508 def _filter_redundant_expectations(expectations): 489 def _filter_redundant_expectations(expectations):
509 if set(expectations) == set(['Pass', 'Skip']): 490 if set(expectations) == set(['Pass', 'Skip']):
510 return ['Skip'] 491 return ['Skip']
511 if set(expectations) == set(['Pass', 'Slow']): 492 if set(expectations) == set(['Pass', 'Slow']):
512 return ['Slow'] 493 return ['Slow']
513 return expectations 494 return expectations
514 495
515 @staticmethod 496 @staticmethod
516 def _format_line(bugs, modifiers, name, expectations, comment, include_modif iers=True, include_expectations=True, include_comment=True): 497 def _format_line(bugs, specifiers, name, expectations, comment, include_spec ifiers=True, include_expectations=True, include_comment=True):
517 new_modifiers = [] 498 new_specifiers = []
518 new_expectations = [] 499 new_expectations = []
519 for modifier in modifiers: 500 for specifier in specifiers:
520 # FIXME: Make this all work with the mixed-cased modifiers (e.g. Won tFix, Slow, etc). 501 # FIXME: Make this all work with the mixed-cased specifiers (e.g. Wo ntFix, Slow, etc).
521 modifier = modifier.upper() 502 specifier = specifier.upper()
522 if modifier in ('SLOW', 'SKIP', 'REBASELINE', NEEDS_REBASELINE_KEYWO RD, NEEDS_MANUAL_REBASELINE_KEYWORD, 'WONTFIX'): 503 new_specifiers.append(TestExpectationParser._inverted_configuration_ tokens.get(specifier, specifier))
523 new_expectations.append(TestExpectationParser._inverted_expectat ion_tokens.get(modifier))
524 else:
525 new_modifiers.append(TestExpectationParser._inverted_configurati on_tokens.get(modifier, modifier))
526 504
527 for expectation in expectations: 505 for expectation in expectations:
528 expectation = expectation.upper() 506 expectation = expectation.upper()
529 new_expectations.append(TestExpectationParser._inverted_expectation_ tokens.get(expectation, expectation)) 507 new_expectations.append(TestExpectationParser._inverted_expectation_ tokens.get(expectation, expectation))
530 508
531 result = '' 509 result = ''
532 if include_modifiers and (bugs or new_modifiers): 510 if include_specifiers and (bugs or new_specifiers):
533 if bugs: 511 if bugs:
534 result += ' '.join(bugs) + ' ' 512 result += ' '.join(bugs) + ' '
535 if new_modifiers: 513 if new_specifiers:
536 result += '[ %s ] ' % ' '.join(new_modifiers) 514 result += '[ %s ] ' % ' '.join(new_specifiers)
537 result += name 515 result += name
538 if include_expectations and new_expectations: 516 if include_expectations and new_expectations:
539 new_expectations = TestExpectationLine._filter_redundant_expectation s(new_expectations) 517 new_expectations = TestExpectationLine._filter_redundant_expectation s(new_expectations)
540 result += ' [ %s ]' % ' '.join(sorted(set(new_expectations))) 518 result += ' [ %s ]' % ' '.join(sorted(set(new_expectations)))
541 if include_comment and comment is not None: 519 if include_comment and comment is not None:
542 result += " #%s" % comment 520 result += " #%s" % comment
543 return result 521 return result
544 522
545 523
546 # FIXME: Refactor API to be a proper CRUD. 524 # FIXME: Refactor API to be a proper CRUD.
547 class TestExpectationsModel(object): 525 class TestExpectationsModel(object):
548 """Represents relational store of all expectations and provides CRUD semanti cs to manage it.""" 526 """Represents relational store of all expectations and provides CRUD semanti cs to manage it."""
549 527
550 def __init__(self, shorten_filename=None): 528 def __init__(self, shorten_filename=None):
551 # Maps a test to its list of expectations. 529 # Maps a test to its list of expectations.
552 self._test_to_expectations = {} 530 self._test_to_expectations = {}
553 531
554 # Maps a test to list of its modifiers (string values) 532 # Maps a test to list of its specifiers (string values)
555 self._test_to_modifiers = {} 533 self._test_to_specifiers = {}
556 534
557 # Maps a test to a TestExpectationLine instance. 535 # Maps a test to a TestExpectationLine instance.
558 self._test_to_expectation_line = {} 536 self._test_to_expectation_line = {}
559 537
560 self._modifier_to_tests = self._dict_of_sets(TestExpectations.MODIFIERS)
561 self._expectation_to_tests = self._dict_of_sets(TestExpectations.EXPECTA TIONS) 538 self._expectation_to_tests = self._dict_of_sets(TestExpectations.EXPECTA TIONS)
562 self._timeline_to_tests = self._dict_of_sets(TestExpectations.TIMELINES) 539 self._timeline_to_tests = self._dict_of_sets(TestExpectations.TIMELINES)
563 self._result_type_to_tests = self._dict_of_sets(TestExpectations.RESULT_ TYPES) 540 self._result_type_to_tests = self._dict_of_sets(TestExpectations.RESULT_ TYPES)
564 541
565 self._shorten_filename = shorten_filename or (lambda x: x) 542 self._shorten_filename = shorten_filename or (lambda x: x)
566 543
567 def _dict_of_sets(self, strings_to_constants): 544 def _dict_of_sets(self, strings_to_constants):
568 """Takes a dict of strings->constants and returns a dict mapping 545 """Takes a dict of strings->constants and returns a dict mapping
569 each constant to an empty set.""" 546 each constant to an empty set."""
570 d = {} 547 d = {}
571 for c in strings_to_constants.values(): 548 for c in strings_to_constants.values():
572 d[c] = set() 549 d[c] = set()
573 return d 550 return d
574 551
575 def get_test_set(self, modifier, expectation=None, include_skips=True): 552 def get_test_set(self, expectation, include_skips=True):
576 if expectation is None: 553 tests = self._expectation_to_tests[expectation]
577 tests = self._modifier_to_tests[modifier]
578 else:
579 tests = (self._expectation_to_tests[expectation] &
580 self._modifier_to_tests[modifier])
581
582 if not include_skips: 554 if not include_skips:
583 tests = tests - self.get_test_set(SKIP, expectation) 555 tests = tests - self.get_test_set(SKIP)
584
585 return tests 556 return tests
586 557
587 def get_test_set_for_keyword(self, keyword): 558 def get_test_set_for_keyword(self, keyword):
588 # FIXME: get_test_set() is an awkward public interface because it requir es
589 # callers to know the difference between modifiers and expectations. We
590 # should replace that with this where possible.
591 expectation_enum = TestExpectations.EXPECTATIONS.get(keyword.lower(), No ne) 559 expectation_enum = TestExpectations.EXPECTATIONS.get(keyword.lower(), No ne)
592 if expectation_enum is not None: 560 if expectation_enum is not None:
593 return self._expectation_to_tests[expectation_enum] 561 return self._expectation_to_tests[expectation_enum]
594 modifier_enum = TestExpectations.MODIFIERS.get(keyword.lower(), None)
595 if modifier_enum is not None:
596 return self._modifier_to_tests[modifier_enum]
597 562
598 # We must not have an index on this modifier.
599 matching_tests = set() 563 matching_tests = set()
600 for test, modifiers in self._test_to_modifiers.iteritems(): 564 for test, specifiers in self._test_to_specifiers.iteritems():
601 if keyword.lower() in modifiers: 565 if keyword.lower() in specifiers:
602 matching_tests.add(test) 566 matching_tests.add(test)
603 return matching_tests 567 return matching_tests
604 568
605 def get_tests_with_result_type(self, result_type): 569 def get_tests_with_result_type(self, result_type):
606 return self._result_type_to_tests[result_type] 570 return self._result_type_to_tests[result_type]
607 571
608 def get_tests_with_timeline(self, timeline): 572 def get_tests_with_timeline(self, timeline):
609 return self._timeline_to_tests[timeline] 573 return self._timeline_to_tests[timeline]
610 574
611 def get_modifiers(self, test):
612 """This returns modifiers for the given test (the modifiers plus the BUG XXXX identifier). This is used by the LTTF dashboard."""
613 return self._test_to_modifiers[test]
614
615 def has_modifier(self, test, modifier):
616 return test in self._modifier_to_tests[modifier]
617
618 def has_keyword(self, test, keyword):
619 return (keyword.upper() in self.get_expectations_string(test) or
620 keyword.lower() in self.get_modifiers(test))
621
622 def has_test(self, test): 575 def has_test(self, test):
623 return test in self._test_to_expectation_line 576 return test in self._test_to_expectation_line
624 577
625 def get_expectation_line(self, test): 578 def get_expectation_line(self, test):
626 return self._test_to_expectation_line.get(test) 579 return self._test_to_expectation_line.get(test)
627 580
628 def get_expectations(self, test): 581 def get_expectations(self, test):
629 return self._test_to_expectations[test] 582 return self._test_to_expectations[test]
630 583
631 def get_expectations_string(self, test): 584 def get_expectations_string(self, test):
632 """Returns the expectatons for the given test as an uppercase string. 585 """Returns the expectatons for the given test as an uppercase string.
633 If there are no expectations for the test, then "PASS" is returned.""" 586 If there are no expectations for the test, then "PASS" is returned."""
634 if self.get_expectation_line(test).is_skipped_outside_expectations_file: 587 if self.get_expectation_line(test).is_skipped_outside_expectations_file:
635 return 'NOTRUN' 588 return 'NOTRUN'
636 589
637 if self.has_modifier(test, WONTFIX):
638 return TestExpectationParser.WONTFIX_MODIFIER.upper()
639
640 if self.has_modifier(test, SKIP):
641 return TestExpectationParser.SKIP_MODIFIER.upper()
642
643 expectations = self.get_expectations(test) 590 expectations = self.get_expectations(test)
644 retval = [] 591 retval = []
645 592
593 # FIXME: WontFix should cause the test to get skipped without artificial ly adding SKIP to the expectations list.
594 if WONTFIX in expectations and SKIP in expectations:
595 expectations.remove(SKIP)
596
646 for expectation in expectations: 597 for expectation in expectations:
647 retval.append(self.expectation_to_string(expectation)) 598 retval.append(self.expectation_to_string(expectation))
648 599
649 return " ".join(retval) 600 return " ".join(retval)
650 601
651 def expectation_to_string(self, expectation): 602 def expectation_to_string(self, expectation):
652 """Return the uppercased string equivalent of a given expectation.""" 603 """Return the uppercased string equivalent of a given expectation."""
653 for item in TestExpectations.EXPECTATIONS.items(): 604 for item in TestExpectations.EXPECTATIONS.items():
654 if item[1] == expectation: 605 if item[1] == expectation:
655 return item[0].upper() 606 return item[0].upper()
656 for item in TestExpectations.MODIFIERS.items():
657 if item[1] == expectation:
658 return item[0].upper()
659 raise ValueError(expectation) 607 raise ValueError(expectation)
660 608
661 def remove_expectation_line(self, test): 609 def remove_expectation_line(self, test):
662 if not self.has_test(test): 610 if not self.has_test(test):
663 return 611 return
664 self._clear_expectations_for_test(test) 612 self._clear_expectations_for_test(test)
665 del self._test_to_expectation_line[test] 613 del self._test_to_expectation_line[test]
666 614
667 def add_expectation_line(self, expectation_line, 615 def add_expectation_line(self, expectation_line,
668 override_existing_matches=False, 616 override_existing_matches=False,
669 merge_existing_matches=False, 617 merge_existing_matches=False,
670 model_all_expectations=False): 618 model_all_expectations=False):
671 """Returns a list of warnings encountered while matching modifiers.""" 619 """Returns a list of warnings encountered while matching specifiers."""
672 620
673 if expectation_line.is_invalid(): 621 if expectation_line.is_invalid():
674 return 622 return
675 623
676 for test in expectation_line.matching_tests: 624 for test in expectation_line.matching_tests:
677 if (not (override_existing_matches or merge_existing_matches) 625 if (not (override_existing_matches or merge_existing_matches)
678 and self._already_seen_better_match(test, expectation_line)) : 626 and self._already_seen_better_match(test, expectation_line)) :
679 continue 627 continue
680 628
681 if merge_existing_matches or model_all_expectations: 629 if merge_existing_matches or model_all_expectations:
682 expectation_line = TestExpectationLine.merge_expectation_lines(s elf.get_expectation_line(test), expectation_line, model_all_expectations) 630 expectation_line = TestExpectationLine.merge_expectation_lines(s elf.get_expectation_line(test), expectation_line, model_all_expectations)
683 631
684 self._clear_expectations_for_test(test) 632 self._clear_expectations_for_test(test)
685 self._test_to_expectation_line[test] = expectation_line 633 self._test_to_expectation_line[test] = expectation_line
686 self._add_test(test, expectation_line) 634 self._add_test(test, expectation_line)
687 635
688 def _add_test(self, test, expectation_line): 636 def _add_test(self, test, expectation_line):
689 """Sets the expected state for a given test. 637 """Sets the expected state for a given test.
690 638
691 This routine assumes the test has not been added before. If it has, 639 This routine assumes the test has not been added before. If it has,
692 use _clear_expectations_for_test() to reset the state prior to 640 use _clear_expectations_for_test() to reset the state prior to
693 calling this.""" 641 calling this."""
694 self._test_to_expectations[test] = expectation_line.parsed_expectations 642 self._test_to_expectations[test] = expectation_line.parsed_expectations
695 for expectation in expectation_line.parsed_expectations: 643 for expectation in expectation_line.parsed_expectations:
696 self._expectation_to_tests[expectation].add(test) 644 self._expectation_to_tests[expectation].add(test)
697 645
698 self._test_to_modifiers[test] = expectation_line.modifiers 646 self._test_to_specifiers[test] = expectation_line.specifiers
699 for modifier in expectation_line.parsed_modifiers:
700 mod_value = TestExpectations.MODIFIERS[modifier]
701 self._modifier_to_tests[mod_value].add(test)
702 647
703 if TestExpectationParser.WONTFIX_MODIFIER in expectation_line.parsed_mod ifiers: 648 if WONTFIX in expectation_line.parsed_expectations:
704 self._timeline_to_tests[WONTFIX].add(test) 649 self._timeline_to_tests[WONTFIX].add(test)
705 else: 650 else:
706 self._timeline_to_tests[NOW].add(test) 651 self._timeline_to_tests[NOW].add(test)
707 652
708 if TestExpectationParser.SKIP_MODIFIER in expectation_line.parsed_modifi ers: 653 if SKIP in expectation_line.parsed_expectations:
709 self._result_type_to_tests[SKIP].add(test) 654 self._result_type_to_tests[SKIP].add(test)
710 elif expectation_line.parsed_expectations == set([PASS]): 655 elif expectation_line.parsed_expectations == set([PASS]):
711 self._result_type_to_tests[PASS].add(test) 656 self._result_type_to_tests[PASS].add(test)
712 elif expectation_line.is_flaky(): 657 elif expectation_line.is_flaky():
713 self._result_type_to_tests[FLAKY].add(test) 658 self._result_type_to_tests[FLAKY].add(test)
714 else: 659 else:
715 # FIXME: What is this? 660 # FIXME: What is this?
716 self._result_type_to_tests[FAIL].add(test) 661 self._result_type_to_tests[FAIL].add(test)
717 662
718 def _clear_expectations_for_test(self, test): 663 def _clear_expectations_for_test(self, test):
719 """Remove prexisting expectations for this test. 664 """Remove prexisting expectations for this test.
720 This happens if we are seeing a more precise path 665 This happens if we are seeing a more precise path
721 than a previous listing. 666 than a previous listing.
722 """ 667 """
723 if self.has_test(test): 668 if self.has_test(test):
724 self._test_to_expectations.pop(test, '') 669 self._test_to_expectations.pop(test, '')
725 self._remove_from_sets(test, self._expectation_to_tests) 670 self._remove_from_sets(test, self._expectation_to_tests)
726 self._remove_from_sets(test, self._modifier_to_tests)
727 self._remove_from_sets(test, self._timeline_to_tests) 671 self._remove_from_sets(test, self._timeline_to_tests)
728 self._remove_from_sets(test, self._result_type_to_tests) 672 self._remove_from_sets(test, self._result_type_to_tests)
729 673
730 def _remove_from_sets(self, test, dict_of_sets_of_tests): 674 def _remove_from_sets(self, test, dict_of_sets_of_tests):
731 """Removes the given test from the sets in the dictionary. 675 """Removes the given test from the sets in the dictionary.
732 676
733 Args: 677 Args:
734 test: test to look for 678 test: test to look for
735 dict: dict of sets of files""" 679 dict: dict of sets of files"""
736 for set_of_tests in dict_of_sets_of_tests.itervalues(): 680 for set_of_tests in dict_of_sets_of_tests.itervalues():
(...skipping 19 matching lines...) Expand all
756 700
757 if len(prev_expectation_line.path) > len(expectation_line.path): 701 if len(prev_expectation_line.path) > len(expectation_line.path):
758 # The previous path matched more of the test. 702 # The previous path matched more of the test.
759 return True 703 return True
760 704
761 if len(prev_expectation_line.path) < len(expectation_line.path): 705 if len(prev_expectation_line.path) < len(expectation_line.path):
762 # This path matches more of the test. 706 # This path matches more of the test.
763 return False 707 return False
764 708
765 # At this point we know we have seen a previous exact match on this 709 # At this point we know we have seen a previous exact match on this
766 # base path, so we need to check the two sets of modifiers. 710 # base path, so we need to check the two sets of specifiers.
767 711
768 # FIXME: This code was originally designed to allow lines that matched 712 # FIXME: This code was originally designed to allow lines that matched
769 # more modifiers to override lines that matched fewer modifiers. 713 # more specifiers to override lines that matched fewer specifiers.
770 # However, we currently view these as errors. 714 # However, we currently view these as errors.
771 # 715 #
772 # To use the "more modifiers wins" policy, change the errors for overrid es 716 # To use the "more specifiers wins" policy, change the errors for overri des
773 # to be warnings and return False". 717 # to be warnings and return False".
774 718
775 if prev_expectation_line.matching_configurations == expectation_line.mat ching_configurations: 719 if prev_expectation_line.matching_configurations == expectation_line.mat ching_configurations:
776 expectation_line.warnings.append('Duplicate or ambiguous entry lines %s:%s and %s:%s.' % ( 720 expectation_line.warnings.append('Duplicate or ambiguous entry lines %s:%s and %s:%s.' % (
777 self._shorten_filename(prev_expectation_line.filename), prev_exp ectation_line.line_numbers, 721 self._shorten_filename(prev_expectation_line.filename), prev_exp ectation_line.line_numbers,
778 self._shorten_filename(expectation_line.filename), expectation_l ine.line_numbers)) 722 self._shorten_filename(expectation_line.filename), expectation_l ine.line_numbers))
779 return True 723 return True
780 724
781 if prev_expectation_line.matching_configurations >= expectation_line.mat ching_configurations: 725 if prev_expectation_line.matching_configurations >= expectation_line.mat ching_configurations:
782 expectation_line.warnings.append('More specific entry for %s on line %s:%s overrides line %s:%s.' % (expectation_line.name, 726 expectation_line.warnings.append('More specific entry for %s on line %s:%s overrides line %s:%s.' % (expectation_line.name,
(...skipping 22 matching lines...) Expand all
805 """Test expectations consist of lines with specifications of what 749 """Test expectations consist of lines with specifications of what
806 to expect from layout test cases. The test cases can be directories 750 to expect from layout test cases. The test cases can be directories
807 in which case the expectations apply to all test cases in that 751 in which case the expectations apply to all test cases in that
808 directory and any subdirectory. The format is along the lines of: 752 directory and any subdirectory. The format is along the lines of:
809 753
810 LayoutTests/fast/js/fixme.js [ Failure ] 754 LayoutTests/fast/js/fixme.js [ Failure ]
811 LayoutTests/fast/js/flaky.js [ Failure Pass ] 755 LayoutTests/fast/js/flaky.js [ Failure Pass ]
812 LayoutTests/fast/js/crash.js [ Crash Failure Pass Timeout ] 756 LayoutTests/fast/js/crash.js [ Crash Failure Pass Timeout ]
813 ... 757 ...
814 758
815 To add modifiers: 759 To add specifiers:
816 LayoutTests/fast/js/no-good.js 760 LayoutTests/fast/js/no-good.js
817 [ Debug ] LayoutTests/fast/js/no-good.js [ Pass Timeout ] 761 [ Debug ] LayoutTests/fast/js/no-good.js [ Pass Timeout ]
818 [ Debug ] LayoutTests/fast/js/no-good.js [ Pass Skip Timeout ] 762 [ Debug ] LayoutTests/fast/js/no-good.js [ Pass Skip Timeout ]
819 [ Linux Debug ] LayoutTests/fast/js/no-good.js [ Pass Skip Timeout ] 763 [ Linux Debug ] LayoutTests/fast/js/no-good.js [ Pass Skip Timeout ]
820 [ Linux Win ] LayoutTests/fast/js/no-good.js [ Pass Skip Timeout ] 764 [ Linux Win ] LayoutTests/fast/js/no-good.js [ Pass Skip Timeout ]
821 765
822 Skip: Doesn't run the test. 766 Skip: Doesn't run the test.
823 Slow: The test takes a long time to run, but does not timeout indefinitely. 767 Slow: The test takes a long time to run, but does not timeout indefinitely.
824 WontFix: For tests that we never intend to pass on a given platform (treated like Skip). 768 WontFix: For tests that we never intend to pass on a given platform (treated like Skip).
825 769
826 Notes: 770 Notes:
827 -A test cannot be both SLOW and TIMEOUT 771 -A test cannot be both SLOW and TIMEOUT
828 -A test can be included twice, but not via the same path. 772 -A test can be included twice, but not via the same path.
829 -If a test is included twice, then the more precise path wins. 773 -If a test is included twice, then the more precise path wins.
830 -CRASH tests cannot be WONTFIX 774 -CRASH tests cannot be WONTFIX
831 """ 775 """
832 776
833 # FIXME: Update to new syntax once the old format is no longer supported. 777 # FIXME: Update to new syntax once the old format is no longer supported.
834 EXPECTATIONS = {'pass': PASS, 778 EXPECTATIONS = {'pass': PASS,
835 'audio': AUDIO, 779 'audio': AUDIO,
836 'fail': FAIL, 780 'fail': FAIL,
837 'image': IMAGE, 781 'image': IMAGE,
838 'image+text': IMAGE_PLUS_TEXT, 782 'image+text': IMAGE_PLUS_TEXT,
839 'text': TEXT, 783 'text': TEXT,
840 'timeout': TIMEOUT, 784 'timeout': TIMEOUT,
841 'crash': CRASH, 785 'crash': CRASH,
842 'missing': MISSING, 786 'missing': MISSING,
843 'skip': SKIP, 787 TestExpectationParser.SKIP_MODIFIER: SKIP,
844 'needsrebaseline': NEEDS_REBASELINE, 788 TestExpectationParser.NEEDS_REBASELINE_MODIFIER: NEEDS_REBAS ELINE,
845 'needsmanualrebaseline': NEEDS_MANUAL_REBASELINE} 789 TestExpectationParser.NEEDS_MANUAL_REBASELINE_MODIFIER: NEED S_MANUAL_REBASELINE,
790 TestExpectationParser.WONTFIX_MODIFIER: WONTFIX,
791 TestExpectationParser.SLOW_MODIFIER: SLOW,
792 TestExpectationParser.REBASELINE_MODIFIER: REBASELINE,
793 }
846 794
847 EXPECTATIONS_TO_STRING = dict((k, v) for (v, k) in EXPECTATIONS.iteritems()) 795 EXPECTATIONS_TO_STRING = dict((k, v) for (v, k) in EXPECTATIONS.iteritems())
848 796
849 # (aggregated by category, pass/fail/skip, type) 797 # (aggregated by category, pass/fail/skip, type)
850 EXPECTATION_DESCRIPTIONS = {SKIP: 'skipped', 798 EXPECTATION_DESCRIPTIONS = {SKIP: 'skipped',
851 PASS: 'passes', 799 PASS: 'passes',
852 FAIL: 'failures', 800 FAIL: 'failures',
853 IMAGE: 'image-only failures', 801 IMAGE: 'image-only failures',
854 TEXT: 'text-only failures', 802 TEXT: 'text-only failures',
855 IMAGE_PLUS_TEXT: 'image and text failures', 803 IMAGE_PLUS_TEXT: 'image and text failures',
856 AUDIO: 'audio failures', 804 AUDIO: 'audio failures',
857 CRASH: 'crashes', 805 CRASH: 'crashes',
858 TIMEOUT: 'timeouts', 806 TIMEOUT: 'timeouts',
859 MISSING: 'missing results'} 807 MISSING: 'missing results'}
860 808
861 EXPECTATION_ORDER = (PASS, CRASH, TIMEOUT, MISSING, FAIL, IMAGE, SKIP) 809 EXPECTATION_ORDER = (PASS, CRASH, TIMEOUT, MISSING, FAIL, IMAGE, SKIP)
862 810
811 NON_TEST_OUTCOME_EXPECTATIONS = (REBASELINE, SKIP, SLOW, WONTFIX)
812
863 BUILD_TYPES = ('debug', 'release') 813 BUILD_TYPES = ('debug', 'release')
864 814
865 MODIFIERS = {TestExpectationParser.SKIP_MODIFIER: SKIP,
866 TestExpectationParser.WONTFIX_MODIFIER: WONTFIX,
867 TestExpectationParser.SLOW_MODIFIER: SLOW,
868 TestExpectationParser.REBASELINE_MODIFIER: REBASELINE,
869 TestExpectationParser.NEEDS_REBASELINE_MODIFIER: NEEDS_REBASELI NE,
870 TestExpectationParser.NEEDS_MANUAL_REBASELINE_MODIFIER: NEEDS_M ANUAL_REBASELINE,
871 'none': NONE}
872
873 MODIFIERS_TO_STRING = dict((k, v) for (v, k) in MODIFIERS.iteritems())
874
875 TIMELINES = {TestExpectationParser.WONTFIX_MODIFIER: WONTFIX, 815 TIMELINES = {TestExpectationParser.WONTFIX_MODIFIER: WONTFIX,
876 'now': NOW} 816 'now': NOW}
877 817
878 RESULT_TYPES = {'skip': SKIP, 818 RESULT_TYPES = {'skip': SKIP,
879 'pass': PASS, 819 'pass': PASS,
880 'fail': FAIL, 820 'fail': FAIL,
881 'flaky': FLAKY} 821 'flaky': FLAKY}
882 822
883 @classmethod 823 @classmethod
884 def expectation_from_string(cls, string): 824 def expectation_from_string(cls, string):
885 assert(' ' not in string) # This only handles one expectation at a time . 825 assert(' ' not in string) # This only handles one expectation at a time .
886 return cls.EXPECTATIONS.get(string.lower()) 826 return cls.EXPECTATIONS.get(string.lower())
887 827
888 @staticmethod 828 @staticmethod
889 def result_was_expected(result, expected_results, test_needs_rebaselining): 829 def result_was_expected(result, expected_results, test_needs_rebaselining):
890 """Returns whether we got a result we were expecting. 830 """Returns whether we got a result we were expecting.
891 Args: 831 Args:
892 result: actual result of a test execution 832 result: actual result of a test execution
893 expected_results: set of results listed in test_expectations 833 expected_results: set of results listed in test_expectations
894 test_needs_rebaselining: whether test was marked as REBASELINE""" 834 test_needs_rebaselining: whether test was marked as REBASELINE"""
835 if not (set(expected_results) - (set(TestExpectations.NON_TEST_OUTCOME_E XPECTATIONS))):
836 expected_results = set([PASS])
837
895 if result in expected_results: 838 if result in expected_results:
896 return True 839 return True
897 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): 840 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):
898 return True 841 return True
899 if result in (TEXT, IMAGE_PLUS_TEXT, AUDIO) and (FAIL in expected_result s): 842 if result in (TEXT, IMAGE_PLUS_TEXT, AUDIO) and (FAIL in expected_result s):
900 return True 843 return True
901 if result == MISSING and test_needs_rebaselining: 844 if result == MISSING and test_needs_rebaselining:
902 return True 845 return True
903 if result == SKIP: 846 if result == SKIP:
904 return True 847 return True
(...skipping 80 matching lines...) Expand 10 before | Expand all | Expand 10 after
985 self._has_warnings = False 928 self._has_warnings = False
986 self._report_warnings() 929 self._report_warnings()
987 self._process_tests_without_expectations() 930 self._process_tests_without_expectations()
988 931
989 # TODO(ojan): Allow for removing skipped tests when getting the list of 932 # TODO(ojan): Allow for removing skipped tests when getting the list of
990 # tests to run, but not when getting metrics. 933 # tests to run, but not when getting metrics.
991 def model(self): 934 def model(self):
992 return self._model 935 return self._model
993 936
994 def get_needs_rebaseline_failures(self): 937 def get_needs_rebaseline_failures(self):
995 return self._model.get_test_set_for_keyword(TestExpectationParser.NEEDS_ REBASELINE_MODIFIER) 938 return self._model.get_test_set(NEEDS_REBASELINE)
996 939
997 def get_rebaselining_failures(self): 940 def get_rebaselining_failures(self):
998 return self._model.get_test_set(REBASELINE) 941 return self._model.get_test_set(REBASELINE)
999 942
1000 # FIXME: Change the callsites to use TestExpectationsModel and remove. 943 # FIXME: Change the callsites to use TestExpectationsModel and remove.
1001 def get_expectations(self, test): 944 def get_expectations(self, test):
1002 return self._model.get_expectations(test) 945 return self._model.get_expectations(test)
1003 946
1004 # FIXME: Change the callsites to use TestExpectationsModel and remove. 947 # FIXME: Change the callsites to use TestExpectationsModel and remove.
1005 def has_modifier(self, test, modifier):
1006 return self._model.has_modifier(test, modifier)
1007
1008 # FIXME: Change the callsites to use TestExpectationsModel and remove.
1009 def get_tests_with_result_type(self, result_type): 948 def get_tests_with_result_type(self, result_type):
1010 return self._model.get_tests_with_result_type(result_type) 949 return self._model.get_tests_with_result_type(result_type)
1011 950
1012 # FIXME: Change the callsites to use TestExpectationsModel and remove. 951 # FIXME: Change the callsites to use TestExpectationsModel and remove.
1013 def get_test_set(self, modifier, expectation=None, include_skips=True): 952 def get_test_set(self, expectation, include_skips=True):
1014 return self._model.get_test_set(modifier, expectation, include_skips) 953 return self._model.get_test_set(expectation, include_skips)
1015
1016 # FIXME: Change the callsites to use TestExpectationsModel and remove.
1017 def get_modifiers(self, test):
1018 return self._model.get_modifiers(test)
1019 954
1020 # FIXME: Change the callsites to use TestExpectationsModel and remove. 955 # FIXME: Change the callsites to use TestExpectationsModel and remove.
1021 def get_tests_with_timeline(self, timeline): 956 def get_tests_with_timeline(self, timeline):
1022 return self._model.get_tests_with_timeline(timeline) 957 return self._model.get_tests_with_timeline(timeline)
1023 958
1024 def get_expectations_string(self, test): 959 def get_expectations_string(self, test):
1025 return self._model.get_expectations_string(test) 960 return self._model.get_expectations_string(test)
1026 961
1027 def expectation_to_string(self, expectation): 962 def expectation_to_string(self, expectation):
1028 return self._model.expectation_to_string(expectation) 963 return self._model.expectation_to_string(expectation)
1029 964
1030 def matches_an_expected_result(self, test, result, pixel_tests_are_enabled): 965 def matches_an_expected_result(self, test, result, pixel_tests_are_enabled):
1031 expected_results = self._model.get_expectations(test) 966 expected_results = self._model.get_expectations(test)
1032 if not pixel_tests_are_enabled: 967 if not pixel_tests_are_enabled:
1033 expected_results = self.remove_pixel_failures(expected_results) 968 expected_results = self.remove_pixel_failures(expected_results)
1034 return self.result_was_expected(result, expected_results, self.is_rebase lining(test)) 969 return self.result_was_expected(result, expected_results, self.is_rebase lining(test))
1035 970
1036 def is_rebaselining(self, test): 971 def is_rebaselining(self, test):
1037 return self._model.has_modifier(test, REBASELINE) 972 return REBASELINE in self._model.get_expectations(test)
1038 973
1039 def _shorten_filename(self, filename): 974 def _shorten_filename(self, filename):
1040 if filename.startswith(self._port.path_from_webkit_base()): 975 if filename.startswith(self._port.path_from_webkit_base()):
1041 return self._port.host.filesystem.relpath(filename, self._port.path_ from_webkit_base()) 976 return self._port.host.filesystem.relpath(filename, self._port.path_ from_webkit_base())
1042 return filename 977 return filename
1043 978
1044 def _report_warnings(self): 979 def _report_warnings(self):
1045 warnings = [] 980 warnings = []
1046 for expectation in self._expectations: 981 for expectation in self._expectations:
1047 for warning in expectation.warnings: 982 for warning in expectation.warnings:
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after
1086 index = self._expectations.index(expectation) 1021 index = self._expectations.index(expectation)
1087 self._expectations.remove(expectation) 1022 self._expectations.remove(expectation)
1088 1023
1089 if index == len(self._expectations) or self._expectations[index].is_ whitespace_or_comment(): 1024 if index == len(self._expectations) or self._expectations[index].is_ whitespace_or_comment():
1090 while index and self._expectations[index - 1].is_whitespace_or_c omment(): 1025 while index and self._expectations[index - 1].is_whitespace_or_c omment():
1091 index = index - 1 1026 index = index - 1
1092 self._expectations.pop(index) 1027 self._expectations.pop(index)
1093 1028
1094 return self.list_to_string(self._expectations, self._parser._test_config uration_converter, modified_expectations) 1029 return self.list_to_string(self._expectations, self._parser._test_config uration_converter, modified_expectations)
1095 1030
1096 def remove_rebaselined_tests(self, except_these_tests, filename):
Dirk Pranke 2013/07/26 23:07:28 I was a bit surprised that this was gone, but I gu
1097 """Returns a copy of the expectations in the file with the tests removed ."""
1098 def without_rebaseline_modifier(expectation):
1099 return (expectation.filename == filename and
1100 not (not expectation.is_invalid() and
1101 expectation.name in except_these_tests and
1102 'rebaseline' in expectation.parsed_modifiers))
1103
1104 return self.list_to_string(filter(without_rebaseline_modifier, self._exp ectations), reconstitute_only_these=[])
1105
1106 def _add_expectations(self, expectation_list): 1031 def _add_expectations(self, expectation_list):
1107 for expectation_line in expectation_list: 1032 for expectation_line in expectation_list:
1108 if not expectation_line.expectations: 1033 if not expectation_line.expectations:
1109 continue 1034 continue
1110 1035
1111 if self._model_all_expectations or self._test_config in expectation_ line.matching_configurations: 1036 if self._model_all_expectations or self._test_config in expectation_ line.matching_configurations:
1112 self._model.add_expectation_line(expectation_line, model_all_exp ectations=self._model_all_expectations) 1037 self._model.add_expectation_line(expectation_line, model_all_exp ectations=self._model_all_expectations)
1113 1038
1114 def add_extra_skipped_tests(self, tests_to_skip): 1039 def add_extra_skipped_tests(self, tests_to_skip):
1115 if not tests_to_skip: 1040 if not tests_to_skip:
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after
1149 # If reconstitute_only_these is an empty list, we want to return ori ginal_string. 1074 # If reconstitute_only_these is an empty list, we want to return ori ginal_string.
1150 # So we need to compare reconstitute_only_these to None, not just ch eck if it's falsey. 1075 # So we need to compare reconstitute_only_these to None, not just ch eck if it's falsey.
1151 if reconstitute_only_these is None or expectation_line in reconstitu te_only_these: 1076 if reconstitute_only_these is None or expectation_line in reconstitu te_only_these:
1152 return expectation_line.to_string(test_configuration_converter) 1077 return expectation_line.to_string(test_configuration_converter)
1153 return expectation_line.original_string 1078 return expectation_line.original_string
1154 1079
1155 def nones_out(expectation_line): 1080 def nones_out(expectation_line):
1156 return expectation_line is not None 1081 return expectation_line is not None
1157 1082
1158 return "\n".join(filter(nones_out, map(serialize, expectation_lines))) 1083 return "\n".join(filter(nones_out, map(serialize, expectation_lines)))
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698