| # Copyright 2013 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import fnmatch |
| import urlparse |
| |
| # Valid expectation conditions are: |
| # |
| # Operating systems: |
| # win, xp, vista, win7, win8, win10, mac, leopard, snowleopard, |
| # lion, mountainlion, mavericks, yosemite, linux, chromeos, |
| # android |
| # |
| # Browser types: |
| # android-webview-shell, android-content-shell, debug, release |
| # |
| # Sample usage in SetExpectations in subclasses: |
| # self.Fail('gl-enable-vertex-attrib.html', |
| # ['mac', 'release'], bug=123) |
| |
| WIN_CONDITIONS = ['xp', 'vista', 'win7', 'win8', 'win10'] |
| MAC_CONDITIONS = ['leopard', 'snowleopard', 'lion', 'mountainlion', |
| 'mavericks', 'yosemite'] |
| |
| OS_CONDITIONS = ['win', 'mac', 'linux', 'chromeos', 'android'] + \ |
| WIN_CONDITIONS + MAC_CONDITIONS |
| |
| BROWSER_TYPE_CONDITIONS = [ |
| 'android-webview-shell', 'android-content-shell', 'debug', 'release'] |
| |
| class Expectation(object): |
| """Represents a single test expectation for a page. |
| |
| Supports conditions based on operating system (e.g., win, mac) and |
| browser type (e.g. 'debug', 'release'). |
| |
| Subclass this class and call super.__init__ last in your constructor |
| in order to add new user-defined conditions. The conditions are |
| parsed at the end of this class's constructor, so be careful not to |
| overwrite the results of the constructor call! |
| """ |
| |
| def __init__(self, expectation, pattern, conditions=None, bug=None): |
| self.expectation = expectation.lower() |
| self.pattern = pattern |
| self.bug = bug |
| |
| self.os_conditions = [] |
| self.browser_conditions = [] |
| |
| if conditions: |
| for c in conditions: |
| self.ParseCondition(c) |
| |
| def ParseCondition(self, condition): |
| """Parses a single test expectation condition. |
| |
| Can be overridden to handle new types of conditions. Call the |
| superclass's implementation of ParseCondition at the end of your |
| subclass if you don't handle the condition. The base |
| implementation will raise an exception if the condition is |
| unsupported. |
| |
| Valid expectation conditions are: |
| |
| Operating systems: |
| win, xp, vista, win7, mac, leopard, snowleopard, lion, |
| mountainlion, mavericks, yosemite, linux, chromeos, android |
| |
| Browser types: |
| android-webview-shell, android-content-shell, debug, release |
| |
| Sample usage in SetExpectations in subclasses: |
| self.Fail('gl-enable-vertex-attrib.html', |
| ['mac', 'release'], bug=123) |
| """ |
| cl = condition.lower() |
| if cl in OS_CONDITIONS: |
| self.os_conditions.append(cl) |
| elif cl in BROWSER_TYPE_CONDITIONS: |
| self.browser_conditions.append(condition) |
| else: |
| raise ValueError('Unknown expectation condition: "%s"' % cl) |
| |
| |
| class TestExpectations(object): |
| """A class which defines the expectations for a page set test execution""" |
| |
| def __init__(self): |
| self._expectations = [] |
| self._skip_matching_names = False |
| self._built_expectation_cache = True |
| self._ClearExpectationsCache() |
| self.SetExpectations() |
| |
| def SetExpectations(self): |
| """Called on creation. Override to set up custom expectations.""" |
| pass |
| |
| def Fail(self, pattern, conditions=None, bug=None): |
| self._Expect('fail', pattern, conditions, bug) |
| |
| def Skip(self, pattern, conditions=None, bug=None): |
| self._Expect('skip', pattern, conditions, bug) |
| |
| def _Expect(self, expectation, pattern, conditions=None, bug=None): |
| self._AddExpectation(self.CreateExpectation(expectation, pattern, |
| conditions, bug)) |
| |
| def _AddExpectation(self, expectation): |
| '''Call this to add an expectation to the set. |
| |
| For use only by this class and subclasses. Do not call this directly.''' |
| self._expectations.append(expectation) |
| self._ClearExpectationsCache() |
| |
| |
| def CreateExpectation(self, expectation, pattern, conditions=None, |
| bug=None): |
| return Expectation(expectation, pattern, conditions, bug) |
| |
| def _ClearExpectationsCache(self): |
| if self._built_expectation_cache: |
| # Only those expectations which contain no wildcard characters |
| # (those which the fnmatch module would expand). |
| self._expectations_by_pattern = {} |
| # The remaining expectations which require expansion. |
| self._expectations_with_wildcards = [] |
| self._built_expectation_cache = False |
| |
| def ClearExpectationsCacheForTesting(self): |
| '''For use only by unit tests.''' |
| self._ClearExpectationsCache() |
| |
| def _HasWildcardCharacters(self, input_string): |
| # Could make this more precise. |
| return '*' in input_string or '+' in input_string |
| |
| def _BuildExpectationsCache(self, browser, page): |
| # Turn off name matching while building the cache. |
| self._skip_matching_names = True |
| for e in self._expectations: |
| if self.ExpectationAppliesToPage(e, browser, page): |
| if self._HasWildcardCharacters(e.pattern): |
| self._expectations_with_wildcards.append(e) |
| else: |
| if e.pattern in self._expectations_by_pattern: |
| print "WARNING: Non-wildcard pattern collision for", e.pattern |
| self._expectations_by_pattern[e.pattern] = e |
| self._built_expectation_cache = True |
| self._skip_matching_names = False |
| |
| def _GetNormalizedURL(self, url, browser): |
| # Telemetry uses backslashes in its file:// URLs on Windows, |
| # breaking matching of test expectations. |
| if not browser.platform.GetOSName() == 'win': |
| return url |
| return url.replace('\\', '/') |
| |
| def _GetURLPath(self, url): |
| components = urlparse.urlsplit(url) |
| # For compatibility, the file:// scheme must be treated specially. |
| # The top-level directory shows up in the netloc portion of the URL. |
| if components[0] == 'file': |
| url_path = components[1] + components[2] |
| else: |
| url_path = components[2] |
| # Chop any leading slash since the expectations used by this class |
| # assume that. |
| if url_path and url_path[0] == '/': |
| url_path = url_path[1:] |
| # Python's urlsplit doesn't seem to handle query arguments for |
| # file:// URLs properly. Split them off manually. |
| query_index = url_path.find('?') |
| if query_index > 0: |
| url_path = url_path[0:query_index] |
| return url_path |
| |
| def _GetExpectationObjectForPage(self, browser, page): |
| if not self._built_expectation_cache: |
| self._BuildExpectationsCache(browser, page) |
| # First attempt to look up by the page's URL or name. |
| e = None |
| # Relative URL (common case). |
| url = self._GetNormalizedURL(page.url, browser) |
| url_path = self._GetURLPath(url) |
| if url_path: |
| e = self._expectations_by_pattern.get(url_path) |
| if e: |
| return e |
| e = self._expectations_by_pattern.get(url) |
| if e: |
| return e |
| if page.name: |
| e = self._expectations_by_pattern.get(page.name) |
| if e: |
| return e |
| # Fall back to scanning through the expectations containing |
| # wildcards. |
| for e in self._expectations_with_wildcards: |
| if self.ExpectationAppliesToPage(e, browser, page): |
| return e |
| return None |
| |
| def GetAllNonWildcardExpectations(self): |
| return [e for e in self._expectations |
| if not self._HasWildcardCharacters(e.pattern)] |
| |
| |
| def GetExpectationForPage(self, browser, page): |
| '''Fetches the expectation that applies to the given page. |
| |
| The implementation of this function performs significant caching |
| based on the browser's parameters, which are expected to remain |
| unchanged from call to call. If this is not true, the method |
| ClearExpectationsCacheForTesting is available to clear the cache; |
| but file a bug if this is needed for any reason but testing. |
| ''' |
| e = self._GetExpectationObjectForPage(browser, page) |
| if e: |
| return e.expectation |
| return 'pass' |
| |
| def ExpectationAppliesToPage(self, expectation, browser, page): |
| """Defines whether the given expectation applies to the given page. |
| |
| Override this in subclasses to add more conditions. Call the |
| superclass's implementation first, and return false if it returns |
| false. Subclasses must not consult the page's name or URL; that is |
| the responsibility of the base class. |
| |
| Args: |
| expectation: an instance of a subclass of Expectation, created |
| by a call to CreateExpectation. |
| browser: the currently running browser. |
| page: the page to be run. |
| """ |
| # While building the expectations cache we need to match |
| # everything except the page's name or URL. |
| if not self._skip_matching_names: |
| # Relative URL. |
| if not fnmatch.fnmatch(self._GetURLPath(page.url), |
| expectation.pattern): |
| # Absolute URL. |
| if not fnmatch.fnmatch(page.url, |
| expectation.pattern): |
| # Name. |
| if not (page.name and fnmatch.fnmatch(page.name, |
| expectation.pattern)): |
| return False |
| |
| platform = browser.platform |
| os_matches = (not expectation.os_conditions or |
| platform.GetOSName() in expectation.os_conditions or |
| platform.GetOSVersionName() in expectation.os_conditions) |
| |
| browser_matches = ( |
| (not expectation.browser_conditions) or |
| browser.browser_type in expectation.browser_conditions) |
| |
| return os_matches and browser_matches |