blob: d8aa932cc9bd5e368e560616df5c5c07141e23c6 [file] [log] [blame]
# 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