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