086321d707d59f5c1aaa5731e3a611c493e6de49
[WebKit.git] / WebKitTools / Scripts / webkitpy / layout_tests / layout_package / test_expectations.py
1 #!/usr/bin/env python
2 # Copyright (C) 2010 Google Inc. All rights reserved.
3 #
4 # Redistribution and use in source and binary forms, with or without
5 # modification, are permitted provided that the following conditions are
6 # met:
7 #
8 #     * Redistributions of source code must retain the above copyright
9 # notice, this list of conditions and the following disclaimer.
10 #     * Redistributions in binary form must reproduce the above
11 # copyright notice, this list of conditions and the following disclaimer
12 # in the documentation and/or other materials provided with the
13 # distribution.
14 #     * Neither the name of Google Inc. nor the names of its
15 # contributors may be used to endorse or promote products derived from
16 # this software without specific prior written permission.
17 #
18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30 """A helper class for reading in and dealing with tests expectations
31 for layout tests.
32 """
33
34 import logging
35 import os
36 import re
37 import sys
38
39 import webkitpy.thirdparty.simplejson as simplejson
40
41 _log = logging.getLogger("webkitpy.layout_tests.layout_package."
42                          "test_expectations")
43
44 # Test expectation and modifier constants.
45 (PASS, FAIL, TEXT, IMAGE, IMAGE_PLUS_TEXT, TIMEOUT, CRASH, SKIP, WONTFIX,
46  DEFER, SLOW, REBASELINE, MISSING, FLAKY, NOW, NONE) = range(16)
47
48 # Test expectation file update action constants
49 (NO_CHANGE, REMOVE_TEST, REMOVE_PLATFORM, ADD_PLATFORMS_EXCEPT_THIS) = range(4)
50
51
52 def result_was_expected(result, expected_results, test_needs_rebaselining,
53                         test_is_skipped):
54     """Returns whether we got a result we were expecting.
55     Args:
56         result: actual result of a test execution
57         expected_results: set of results listed in test_expectations
58         test_needs_rebaselining: whether test was marked as REBASELINE
59         test_is_skipped: whether test was marked as SKIP"""
60     if result in expected_results:
61         return True
62     if result in (IMAGE, TEXT, IMAGE_PLUS_TEXT) and FAIL in expected_results:
63         return True
64     if result == MISSING and test_needs_rebaselining:
65         return True
66     if result == SKIP and test_is_skipped:
67         return True
68     return False
69
70
71 def remove_pixel_failures(expected_results):
72     """Returns a copy of the expected results for a test, except that we
73     drop any pixel failures and return the remaining expectations. For example,
74     if we're not running pixel tests, then tests expected to fail as IMAGE
75     will PASS."""
76     expected_results = expected_results.copy()
77     if IMAGE in expected_results:
78         expected_results.remove(IMAGE)
79         expected_results.add(PASS)
80     if IMAGE_PLUS_TEXT in expected_results:
81         expected_results.remove(IMAGE_PLUS_TEXT)
82         expected_results.add(TEXT)
83     return expected_results
84
85
86 class TestExpectations:
87     TEST_LIST = "test_expectations.txt"
88
89     def __init__(self, port, tests, expectations, test_platform_name,
90                  is_debug_mode, is_lint_mode, tests_are_present=True,
91                  overrides=None):
92         """Loads and parses the test expectations given in the string.
93         Args:
94             port: handle to object containing platform-specific functionality
95             test: list of all of the test files
96             expectations: test expectations as a string
97             test_platform_name: name of the platform to match expectations
98                 against. Note that this may be different than
99                 port.test_platform_name() when is_lint_mode is True.
100             is_debug_mode: whether to use the DEBUG or RELEASE modifiers
101                 in the expectations
102             is_lint_mode: If True, just parse the expectations string
103                 looking for errors.
104             tests_are_present: whether the test files exist in the file
105                 system and can be probed for. This is useful for distinguishing
106                 test files from directories, and is needed by the LTTF
107                 dashboard, where the files aren't actually locally present.
108             overrides: test expectations that are allowed to override any
109                 entries in |expectations|. This is used by callers
110                 that need to manage two sets of expectations (e.g., upstream
111                 and downstream expectations).
112         """
113         self._expected_failures = TestExpectationsFile(port, expectations,
114             tests, test_platform_name, is_debug_mode, is_lint_mode,
115             tests_are_present=tests_are_present, overrides=overrides)
116
117     # TODO(ojan): Allow for removing skipped tests when getting the list of
118     # tests to run, but not when getting metrics.
119     # TODO(ojan): Replace the Get* calls here with the more sane API exposed
120     # by TestExpectationsFile below. Maybe merge the two classes entirely?
121
122     def get_expectations_json_for_all_platforms(self):
123         return (
124             self._expected_failures.get_expectations_json_for_all_platforms())
125
126     def get_rebaselining_failures(self):
127         return (self._expected_failures.get_test_set(REBASELINE, FAIL) |
128                 self._expected_failures.get_test_set(REBASELINE, IMAGE) |
129                 self._expected_failures.get_test_set(REBASELINE, TEXT) |
130                 self._expected_failures.get_test_set(REBASELINE,
131                                                      IMAGE_PLUS_TEXT))
132
133     def get_options(self, test):
134         return self._expected_failures.get_options(test)
135
136     def get_expectations(self, test):
137         return self._expected_failures.get_expectations(test)
138
139     def get_expectations_string(self, test):
140         """Returns the expectatons for the given test as an uppercase string.
141         If there are no expectations for the test, then "PASS" is returned."""
142         expectations = self.get_expectations(test)
143         retval = []
144
145         for expectation in expectations:
146             retval.append(self.expectation_to_string(expectation))
147
148         return " ".join(retval)
149
150     def expectation_to_string(self, expectation):
151         """Return the uppercased string equivalent of a given expectation."""
152         for item in TestExpectationsFile.EXPECTATIONS.items():
153             if item[1] == expectation:
154                 return item[0].upper()
155         raise ValueError(expectation)
156
157     def get_tests_with_result_type(self, result_type):
158         return self._expected_failures.get_tests_with_result_type(result_type)
159
160     def get_tests_with_timeline(self, timeline):
161         return self._expected_failures.get_tests_with_timeline(timeline)
162
163     def matches_an_expected_result(self, test, result,
164                                    pixel_tests_are_enabled):
165         expected_results = self._expected_failures.get_expectations(test)
166         if not pixel_tests_are_enabled:
167             expected_results = remove_pixel_failures(expected_results)
168         return result_was_expected(result, expected_results,
169             self.is_rebaselining(test), self.has_modifier(test, SKIP))
170
171     def is_rebaselining(self, test):
172         return self._expected_failures.has_modifier(test, REBASELINE)
173
174     def has_modifier(self, test, modifier):
175         return self._expected_failures.has_modifier(test, modifier)
176
177     def remove_platform_from_expectations(self, tests, platform):
178         return self._expected_failures.remove_platform_from_expectations(
179             tests, platform)
180
181
182 def strip_comments(line):
183     """Strips comments from a line and return None if the line is empty
184     or else the contents of line with leading and trailing spaces removed
185     and all other whitespace collapsed"""
186
187     commentIndex = line.find('//')
188     if commentIndex is -1:
189         commentIndex = len(line)
190
191     line = re.sub(r'\s+', ' ', line[:commentIndex].strip())
192     if line == '':
193         return None
194     else:
195         return line
196
197
198 class ModifiersAndExpectations:
199     """A holder for modifiers and expectations on a test that serializes to
200     JSON."""
201
202     def __init__(self, modifiers, expectations):
203         self.modifiers = modifiers
204         self.expectations = expectations
205
206
207 class ExpectationsJsonEncoder(simplejson.JSONEncoder):
208     """JSON encoder that can handle ModifiersAndExpectations objects."""
209     def default(self, obj):
210         # A ModifiersAndExpectations object has two fields, each of which
211         # is a dict. Since JSONEncoders handle all the builtin types directly,
212         # the only time this routine should be called is on the top level
213         # object (i.e., the encoder shouldn't recurse).
214         assert isinstance(obj, ModifiersAndExpectations)
215         return {"modifiers": obj.modifiers,
216                 "expectations": obj.expectations}
217
218
219 class TestExpectationsFile:
220     """Test expectation files consist of lines with specifications of what
221     to expect from layout test cases. The test cases can be directories
222     in which case the expectations apply to all test cases in that
223     directory and any subdirectory. The format of the file is along the
224     lines of:
225
226       LayoutTests/fast/js/fixme.js = FAIL
227       LayoutTests/fast/js/flaky.js = FAIL PASS
228       LayoutTests/fast/js/crash.js = CRASH TIMEOUT FAIL PASS
229       ...
230
231     To add other options:
232       SKIP : LayoutTests/fast/js/no-good.js = TIMEOUT PASS
233       DEBUG : LayoutTests/fast/js/no-good.js = TIMEOUT PASS
234       DEBUG SKIP : LayoutTests/fast/js/no-good.js = TIMEOUT PASS
235       LINUX DEBUG SKIP : LayoutTests/fast/js/no-good.js = TIMEOUT PASS
236       DEFER LINUX WIN : LayoutTests/fast/js/no-good.js = TIMEOUT PASS
237
238     SKIP: Doesn't run the test.
239     SLOW: The test takes a long time to run, but does not timeout indefinitely.
240     WONTFIX: For tests that we never intend to pass on a given platform.
241     DEFER: Test does not count in our statistics for the current release.
242     DEBUG: Expectations apply only to the debug build.
243     RELEASE: Expectations apply only to release build.
244     LINUX/WIN/WIN-XP/WIN-VISTA/WIN-7/MAC: Expectations apply only to these
245         platforms.
246
247     Notes:
248       -A test cannot be both SLOW and TIMEOUT
249       -A test cannot be both DEFER and WONTFIX
250       -A test should only be one of IMAGE, TEXT, IMAGE+TEXT, or FAIL. FAIL is
251        a migratory state that currently means either IMAGE, TEXT, or
252        IMAGE+TEXT. Once we have finished migrating the expectations, we will
253        change FAIL to have the meaning of IMAGE+TEXT and remove the IMAGE+TEXT
254        identifier.
255       -A test can be included twice, but not via the same path.
256       -If a test is included twice, then the more precise path wins.
257       -CRASH tests cannot be DEFER or WONTFIX
258     """
259
260     EXPECTATIONS = {'pass': PASS,
261                     'fail': FAIL,
262                     'text': TEXT,
263                     'image': IMAGE,
264                     'image+text': IMAGE_PLUS_TEXT,
265                     'timeout': TIMEOUT,
266                     'crash': CRASH,
267                     'missing': MISSING}
268
269     EXPECTATION_DESCRIPTIONS = {SKIP: ('skipped', 'skipped'),
270                                 PASS: ('pass', 'passes'),
271                                 FAIL: ('failure', 'failures'),
272                                 TEXT: ('text diff mismatch',
273                                        'text diff mismatch'),
274                                 IMAGE: ('image mismatch', 'image mismatch'),
275                                 IMAGE_PLUS_TEXT: ('image and text mismatch',
276                                                   'image and text mismatch'),
277                                 CRASH: ('DumpRenderTree crash',
278                                         'DumpRenderTree crashes'),
279                                 TIMEOUT: ('test timed out', 'tests timed out'),
280                                 MISSING: ('no expected result found',
281                                           'no expected results found')}
282
283     EXPECTATION_ORDER = (PASS, CRASH, TIMEOUT, MISSING, IMAGE_PLUS_TEXT,
284        TEXT, IMAGE, FAIL, SKIP)
285
286     BUILD_TYPES = ('debug', 'release')
287
288     MODIFIERS = {'skip': SKIP,
289                  'wontfix': WONTFIX,
290                  'defer': DEFER,
291                  'slow': SLOW,
292                  'rebaseline': REBASELINE,
293                  'none': NONE}
294
295     TIMELINES = {'wontfix': WONTFIX,
296                  'now': NOW,
297                  'defer': DEFER}
298
299     RESULT_TYPES = {'skip': SKIP,
300                     'pass': PASS,
301                     'fail': FAIL,
302                     'flaky': FLAKY}
303
304     def __init__(self, port, expectations, full_test_list, test_platform_name,
305         is_debug_mode, is_lint_mode, suppress_errors=False,
306         tests_are_present=True, overrides=None):
307         """
308         expectations: Contents of the expectations file
309         full_test_list: The list of all tests to be run pending processing of
310             the expections for those tests.
311         test_platform_name: name of the platform to match expectations
312             against. Note that this may be different than
313             port.test_platform_name() when is_lint_mode is True.
314         is_debug_mode: Whether we testing a test_shell built debug mode.
315         is_lint_mode: Whether this is just linting test_expecatations.txt.
316         suppress_errors: Whether to suppress lint errors.
317         tests_are_present: Whether the test files are present in the local
318             filesystem. The LTTF Dashboard uses False here to avoid having to
319             keep a local copy of the tree.
320         overrides: test expectations that are allowed to override any
321             entries in |expectations|. This is used by callers
322             that need to manage two sets of expectations (e.g., upstream
323             and downstream expectations).
324         """
325
326         self._port = port
327         self._expectations = expectations
328         self._full_test_list = full_test_list
329         self._test_platform_name = test_platform_name
330         self._is_debug_mode = is_debug_mode
331         self._is_lint_mode = is_lint_mode
332         self._tests_are_present = tests_are_present
333         self._overrides = overrides
334         self._suppress_errors = suppress_errors
335         self._errors = []
336         self._non_fatal_errors = []
337
338         # Maps relative test paths as listed in the expectations file to a
339         # list of maps containing modifiers and expectations for each time
340         # the test is listed in the expectations file.
341         self._all_expectations = {}
342
343         # Maps a test to its list of expectations.
344         self._test_to_expectations = {}
345
346         # Maps a test to its list of options (string values)
347         self._test_to_options = {}
348
349         # Maps a test to its list of modifiers: the constants associated with
350         # the options minus any bug or platform strings
351         self._test_to_modifiers = {}
352
353         # Maps a test to the base path that it was listed with in the list.
354         self._test_list_paths = {}
355
356         self._modifier_to_tests = self._dict_of_sets(self.MODIFIERS)
357         self._expectation_to_tests = self._dict_of_sets(self.EXPECTATIONS)
358         self._timeline_to_tests = self._dict_of_sets(self.TIMELINES)
359         self._result_type_to_tests = self._dict_of_sets(self.RESULT_TYPES)
360
361         self._read(self._get_iterable_expectations(self._expectations),
362                    overrides_allowed=False)
363
364         # List of tests that are in the overrides file (used for checking for
365         # duplicates inside the overrides file itself). Note that just because
366         # a test is in this set doesn't mean it's necessarily overridding a
367         # expectation in the regular expectations; the test might not be
368         # mentioned in the regular expectations file at all.
369         self._overridding_tests = set()
370
371         if overrides:
372             self._read(self._get_iterable_expectations(self._overrides),
373                        overrides_allowed=True)
374
375         self._handle_any_read_errors()
376         self._process_tests_without_expectations()
377
378     def _handle_any_read_errors(self):
379         if not self._suppress_errors and (
380             len(self._errors) or len(self._non_fatal_errors)):
381             if self._is_debug_mode:
382                 build_type = 'DEBUG'
383             else:
384                 build_type = 'RELEASE'
385             _log.error('')
386             _log.error("FAILURES FOR PLATFORM: %s, BUILD_TYPE: %s" %
387                        (self._test_platform_name.upper(), build_type))
388
389             for error in self._non_fatal_errors:
390                 _log.error(error)
391             _log.error('')
392
393             if len(self._errors):
394                 raise SyntaxError('\n'.join(map(str, self._errors)))
395
396     def _process_tests_without_expectations(self):
397         expectations = set([PASS])
398         options = []
399         modifiers = []
400         if self._full_test_list:
401             for test in self._full_test_list:
402                 if not test in self._test_list_paths:
403                     self._add_test(test, modifiers, expectations, options,
404                         overrides_allowed=False)
405
406     def _dict_of_sets(self, strings_to_constants):
407         """Takes a dict of strings->constants and returns a dict mapping
408         each constant to an empty set."""
409         d = {}
410         for c in strings_to_constants.values():
411             d[c] = set()
412         return d
413
414     def _get_iterable_expectations(self, expectations_str):
415         """Returns an object that can be iterated over. Allows for not caring
416         about whether we're iterating over a file or a new-line separated
417         string."""
418         iterable = [x + "\n" for x in expectations_str.split("\n")]
419         # Strip final entry if it's empty to avoid added in an extra
420         # newline.
421         if iterable[-1] == "\n":
422             return iterable[:-1]
423         return iterable
424
425     def get_test_set(self, modifier, expectation=None, include_skips=True):
426         if expectation is None:
427             tests = self._modifier_to_tests[modifier]
428         else:
429             tests = (self._expectation_to_tests[expectation] &
430                 self._modifier_to_tests[modifier])
431
432         if not include_skips:
433             tests = tests - self.get_test_set(SKIP, expectation)
434
435         return tests
436
437     def get_tests_with_result_type(self, result_type):
438         return self._result_type_to_tests[result_type]
439
440     def get_tests_with_timeline(self, timeline):
441         return self._timeline_to_tests[timeline]
442
443     def get_options(self, test):
444         """This returns the entire set of options for the given test
445         (the modifiers plus the BUGXXXX identifier). This is used by the
446         LTTF dashboard."""
447         return self._test_to_options[test]
448
449     def has_modifier(self, test, modifier):
450         return test in self._modifier_to_tests[modifier]
451
452     def get_expectations(self, test):
453         return self._test_to_expectations[test]
454
455     def get_expectations_json_for_all_platforms(self):
456         # Specify separators in order to get compact encoding.
457         return ExpectationsJsonEncoder(separators=(',', ':')).encode(
458             self._all_expectations)
459
460     def get_non_fatal_errors(self):
461         return self._non_fatal_errors
462
463     def remove_platform_from_expectations(self, tests, platform):
464         """Returns a copy of the expectations with the tests matching the
465         platform remove.
466
467         If a test is in the test list and has an option that matches the given
468         platform, remove the matching platform and save the updated test back
469         to the file. If no other platforms remaining after removal, delete the
470         test from the file.
471
472         Args:
473           tests: list of tests that need to update..
474           platform: which platform option to remove.
475
476         Returns:
477           the updated string.
478         """
479
480         f_orig = self._get_iterable_expectations(self._expectations)
481         f_new = []
482
483         tests_removed = 0
484         tests_updated = 0
485         lineno = 0
486         for line in f_orig:
487             lineno += 1
488             action = self._get_platform_update_action(line, lineno, tests,
489                                                       platform)
490             if action == NO_CHANGE:
491                 # Save the original line back to the file
492                 _log.debug('No change to test: %s', line)
493                 f_new.append(line)
494             elif action == REMOVE_TEST:
495                 tests_removed += 1
496                 _log.info('Test removed: %s', line)
497             elif action == REMOVE_PLATFORM:
498                 parts = line.split(':')
499                 new_options = parts[0].replace(platform.upper() + ' ', '', 1)
500                 new_line = ('%s:%s' % (new_options, parts[1]))
501                 f_new.append(new_line)
502                 tests_updated += 1
503                 _log.info('Test updated: ')
504                 _log.info('  old: %s', line)
505                 _log.info('  new: %s', new_line)
506             elif action == ADD_PLATFORMS_EXCEPT_THIS:
507                 parts = line.split(':')
508                 new_options = parts[0]
509                 for p in self._port.test_platform_names():
510                     p = p.upper()
511                     # This is a temp solution for rebaselining tool.
512                     # Do not add tags WIN-7 and WIN-VISTA to test expectations
513                     # if the original line does not specify the platform
514                     # option.
515                     # TODO(victorw): Remove WIN-VISTA and WIN-7 once we have
516                     # reliable Win 7 and Win Vista buildbots setup.
517                     if not p in (platform.upper(), 'WIN-VISTA', 'WIN-7'):
518                         new_options += p + ' '
519                 new_line = ('%s:%s' % (new_options, parts[1]))
520                 f_new.append(new_line)
521                 tests_updated += 1
522                 _log.info('Test updated: ')
523                 _log.info('  old: %s', line)
524                 _log.info('  new: %s', new_line)
525             else:
526                 _log.error('Unknown update action: %d; line: %s',
527                            action, line)
528
529         _log.info('Total tests removed: %d', tests_removed)
530         _log.info('Total tests updated: %d', tests_updated)
531
532         return "".join(f_new)
533
534     def parse_expectations_line(self, line, lineno):
535         """Parses a line from test_expectations.txt and returns a tuple
536         with the test path, options as a list, expectations as a list."""
537         line = strip_comments(line)
538         if not line:
539             return (None, None, None)
540
541         options = []
542         if line.find(":") is -1:
543             test_and_expectation = line.split("=")
544         else:
545             parts = line.split(":")
546             options = self._get_options_list(parts[0])
547             test_and_expectation = parts[1].split('=')
548
549         test = test_and_expectation[0].strip()
550         if (len(test_and_expectation) is not 2):
551             self._add_error(lineno, "Missing expectations.",
552                            test_and_expectation)
553             expectations = None
554         else:
555             expectations = self._get_options_list(test_and_expectation[1])
556
557         return (test, options, expectations)
558
559     def _get_platform_update_action(self, line, lineno, tests, platform):
560         """Check the platform option and return the action needs to be taken.
561
562         Args:
563           line: current line in test expectations file.
564           lineno: current line number of line
565           tests: list of tests that need to update..
566           platform: which platform option to remove.
567
568         Returns:
569           NO_CHANGE: no change to the line (comments, test not in the list etc)
570           REMOVE_TEST: remove the test from file.
571           REMOVE_PLATFORM: remove this platform option from the test.
572           ADD_PLATFORMS_EXCEPT_THIS: add all the platforms except this one.
573         """
574         test, options, expectations = self.parse_expectations_line(line,
575                                                                    lineno)
576         if not test or test not in tests:
577             return NO_CHANGE
578
579         has_any_platform = False
580         for option in options:
581             if option in self._port.test_platform_names():
582                 has_any_platform = True
583                 if not option == platform:
584                     return REMOVE_PLATFORM
585
586         # If there is no platform specified, then it means apply to all
587         # platforms. Return the action to add all the platforms except this
588         # one.
589         if not has_any_platform:
590             return ADD_PLATFORMS_EXCEPT_THIS
591
592         return REMOVE_TEST
593
594     def _has_valid_modifiers_for_current_platform(self, options, lineno,
595         test_and_expectations, modifiers):
596         """Returns true if the current platform is in the options list or if
597         no platforms are listed and if there are no fatal errors in the
598         options list.
599
600         Args:
601           options: List of lowercase options.
602           lineno: The line in the file where the test is listed.
603           test_and_expectations: The path and expectations for the test.
604           modifiers: The set to populate with modifiers.
605         """
606         has_any_platform = False
607         has_bug_id = False
608         for option in options:
609             if option in self.MODIFIERS:
610                 modifiers.add(option)
611             elif option in self._port.test_platform_names():
612                 has_any_platform = True
613             elif option.startswith('bug'):
614                 has_bug_id = True
615             elif option not in self.BUILD_TYPES:
616                 self._add_error(lineno, 'Invalid modifier for test: %s' %
617                                 option, test_and_expectations)
618
619         if has_any_platform and not self._match_platform(options):
620             return False
621
622         if not has_bug_id and 'wontfix' not in options:
623             # TODO(ojan): Turn this into an AddError call once all the
624             # tests have BUG identifiers.
625             self._log_non_fatal_error(lineno, 'Test lacks BUG modifier.',
626                 test_and_expectations)
627
628         if 'release' in options or 'debug' in options:
629             if self._is_debug_mode and 'debug' not in options:
630                 return False
631             if not self._is_debug_mode and 'release' not in options:
632                 return False
633
634         if 'wontfix' in options and 'defer' in options:
635             self._add_error(lineno, 'Test cannot be both DEFER and WONTFIX.',
636                 test_and_expectations)
637
638         if self._is_lint_mode and 'rebaseline' in options:
639             self._add_error(lineno,
640                 'REBASELINE should only be used for running rebaseline.py. '
641                 'Cannot be checked in.', test_and_expectations)
642
643         return True
644
645     def _match_platform(self, options):
646         """Match the list of options against our specified platform. If any
647         of the options prefix-match self._platform, return True. This handles
648         the case where a test is marked WIN and the platform is WIN-VISTA.
649
650         Args:
651           options: list of options
652         """
653         for opt in options:
654             if self._test_platform_name.startswith(opt):
655                 return True
656         return False
657
658     def _add_to_all_expectations(self, test, options, expectations):
659         # Make all paths unix-style so the dashboard doesn't need to.
660         test = test.replace('\\', '/')
661         if not test in self._all_expectations:
662             self._all_expectations[test] = []
663         self._all_expectations[test].append(
664             ModifiersAndExpectations(options, expectations))
665
666     def _read(self, expectations, overrides_allowed):
667         """For each test in an expectations iterable, generate the
668         expectations for it."""
669         lineno = 0
670         for line in expectations:
671             lineno += 1
672
673             test_list_path, options, expectations = \
674                 self.parse_expectations_line(line, lineno)
675             if not expectations:
676                 continue
677
678             self._add_to_all_expectations(test_list_path,
679                                           " ".join(options).upper(),
680                                           " ".join(expectations).upper())
681
682             modifiers = set()
683             if options and not self._has_valid_modifiers_for_current_platform(
684                 options, lineno, test_list_path, modifiers):
685                 continue
686
687             expectations = self._parse_expectations(expectations, lineno,
688                 test_list_path)
689
690             if 'slow' in options and TIMEOUT in expectations:
691                 self._add_error(lineno,
692                     'A test can not be both slow and timeout. If it times out '
693                     'indefinitely, then it should be just timeout.',
694                     test_list_path)
695
696             full_path = os.path.join(self._port.layout_tests_dir(),
697                                      test_list_path)
698             full_path = os.path.normpath(full_path)
699             # WebKit's way of skipping tests is to add a -disabled suffix.
700             # So we should consider the path existing if the path or the
701             # -disabled version exists.
702             if (self._tests_are_present and not os.path.exists(full_path)
703                 and not os.path.exists(full_path + '-disabled')):
704                 # Log a non fatal error here since you hit this case any
705                 # time you update test_expectations.txt without syncing
706                 # the LayoutTests directory
707                 self._log_non_fatal_error(lineno, 'Path does not exist.',
708                                        test_list_path)
709                 continue
710
711             if not self._full_test_list:
712                 tests = [test_list_path]
713             else:
714                 tests = self._expand_tests(test_list_path)
715
716             self._add_tests(tests, expectations, test_list_path, lineno,
717                            modifiers, options, overrides_allowed)
718
719     def _get_options_list(self, listString):
720         return [part.strip().lower() for part in listString.strip().split(' ')]
721
722     def _parse_expectations(self, expectations, lineno, test_list_path):
723         result = set()
724         for part in expectations:
725             if not part in self.EXPECTATIONS:
726                 self._add_error(lineno, 'Unsupported expectation: %s' % part,
727                     test_list_path)
728                 continue
729             expectation = self.EXPECTATIONS[part]
730             result.add(expectation)
731         return result
732
733     def _expand_tests(self, test_list_path):
734         """Convert the test specification to an absolute, normalized
735         path and make sure directories end with the OS path separator."""
736         path = os.path.join(self._port.layout_tests_dir(), test_list_path)
737         path = os.path.normpath(path)
738         path = self._fix_dir(path)
739
740         result = []
741         for test in self._full_test_list:
742             if test.startswith(path):
743                 result.append(test)
744         return result
745
746     def _fix_dir(self, path):
747         """Check to see if the path points to a directory, and if so, append
748         the directory separator if necessary."""
749         if self._tests_are_present:
750             if os.path.isdir(path):
751                 path = os.path.join(path, '')
752         else:
753             # If we can't check the filesystem to see if this is a directory,
754             # we assume that files w/o an extension are directories.
755             # TODO(dpranke): What happens w/ LayoutTests/css2.1 ?
756             if os.path.splitext(path)[1] == '':
757                 path = os.path.join(path, '')
758         return path
759
760     def _add_tests(self, tests, expectations, test_list_path, lineno,
761                    modifiers, options, overrides_allowed):
762         for test in tests:
763             if self._already_seen_test(test, test_list_path, lineno,
764                                        overrides_allowed):
765                 continue
766
767             self._clear_expectations_for_test(test, test_list_path)
768             self._add_test(test, modifiers, expectations, options,
769                            overrides_allowed)
770
771     def _add_test(self, test, modifiers, expectations, options,
772                   overrides_allowed):
773         """Sets the expected state for a given test.
774
775         This routine assumes the test has not been added before. If it has,
776         use _ClearExpectationsForTest() to reset the state prior to
777         calling this.
778
779         Args:
780           test: test to add
781           modifiers: sequence of modifier keywords ('wontfix', 'slow', etc.)
782           expectations: sequence of expectations (PASS, IMAGE, etc.)
783           options: sequence of keywords and bug identifiers.
784           overrides_allowed: whether we're parsing the regular expectations
785               or the overridding expectations"""
786         self._test_to_expectations[test] = expectations
787         for expectation in expectations:
788             self._expectation_to_tests[expectation].add(test)
789
790         self._test_to_options[test] = options
791         self._test_to_modifiers[test] = set()
792         for modifier in modifiers:
793             mod_value = self.MODIFIERS[modifier]
794             self._modifier_to_tests[mod_value].add(test)
795             self._test_to_modifiers[test].add(mod_value)
796
797         if 'wontfix' in modifiers:
798             self._timeline_to_tests[WONTFIX].add(test)
799         elif 'defer' in modifiers:
800             self._timeline_to_tests[DEFER].add(test)
801         else:
802             self._timeline_to_tests[NOW].add(test)
803
804         if 'skip' in modifiers:
805             self._result_type_to_tests[SKIP].add(test)
806         elif expectations == set([PASS]):
807             self._result_type_to_tests[PASS].add(test)
808         elif len(expectations) > 1:
809             self._result_type_to_tests[FLAKY].add(test)
810         else:
811             self._result_type_to_tests[FAIL].add(test)
812
813         if overrides_allowed:
814             self._overridding_tests.add(test)
815
816     def _clear_expectations_for_test(self, test, test_list_path):
817         """Remove prexisting expectations for this test.
818         This happens if we are seeing a more precise path
819         than a previous listing.
820         """
821         if test in self._test_list_paths:
822             self._test_to_expectations.pop(test, '')
823             self._remove_from_sets(test, self._expectation_to_tests)
824             self._remove_from_sets(test, self._modifier_to_tests)
825             self._remove_from_sets(test, self._timeline_to_tests)
826             self._remove_from_sets(test, self._result_type_to_tests)
827
828         self._test_list_paths[test] = os.path.normpath(test_list_path)
829
830     def _remove_from_sets(self, test, dict):
831         """Removes the given test from the sets in the dictionary.
832
833         Args:
834           test: test to look for
835           dict: dict of sets of files"""
836         for set_of_tests in dict.itervalues():
837             if test in set_of_tests:
838                 set_of_tests.remove(test)
839
840     def _already_seen_test(self, test, test_list_path, lineno,
841                            allow_overrides):
842         """Returns true if we've already seen a more precise path for this test
843         than the test_list_path.
844         """
845         if not test in self._test_list_paths:
846             return False
847
848         prev_base_path = self._test_list_paths[test]
849         if (prev_base_path == os.path.normpath(test_list_path)):
850             if (not allow_overrides or test in self._overridding_tests):
851                 if allow_overrides:
852                     expectation_source = "override"
853                 else:
854                     expectation_source = "expectation"
855                 self._add_error(lineno, 'Duplicate %s.' % expectation_source,
856                                    test)
857                 return True
858             else:
859                 # We have seen this path, but that's okay because its
860                 # in the overrides and the earlier path was in the
861                 # expectations.
862                 return False
863
864         # Check if we've already seen a more precise path.
865         return prev_base_path.startswith(os.path.normpath(test_list_path))
866
867     def _add_error(self, lineno, msg, path):
868         """Reports an error that will prevent running the tests. Does not
869         immediately raise an exception because we'd like to aggregate all the
870         errors so they can all be printed out."""
871         self._errors.append('\nLine:%s %s %s' % (lineno, msg, path))
872
873     def _log_non_fatal_error(self, lineno, msg, path):
874         """Reports an error that will not prevent running the tests. These are
875         still errors, but not bad enough to warrant breaking test running."""
876         self._non_fatal_errors.append('Line:%s %s %s' % (lineno, msg, path))