1 # Copyright 2013 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
8 # Valid expectation conditions are:
11 # win, xp, vista, win7, win8, win10, mac, leopard, snowleopard,
12 # lion, mountainlion, mavericks, yosemite, linux, chromeos,
16 # android-webview-shell, android-content-shell, debug, release
18 # Sample usage in SetExpectations in subclasses:
19 # self.Fail('gl-enable-vertex-attrib.html',
20 # ['mac', 'release'], bug=123)
22 OS_CONDITIONS
= ['win', 'xp', 'vista', 'win7', 'win8', 'win10',
23 'mac', 'leopard', 'snowleopard', 'lion', 'mountainlion',
24 'mavericks', 'yosemite', 'linux', 'chromeos', 'android']
26 BROWSER_TYPE_CONDITIONS
= [
27 'android-webview-shell', 'android-content-shell', 'debug', 'release' ]
29 class Expectation(object):
30 """Represents a single test expectation for a page.
32 Supports conditions based on operating system (e.g., win, mac) and
33 browser type (e.g. 'debug', 'release').
35 Subclass this class and call super.__init__ last in your constructor
36 in order to add new user-defined conditions. The conditions are
37 parsed at the end of this class's constructor, so be careful not to
38 overwrite the results of the constructor call!
41 def __init__(self
, expectation
, pattern
, conditions
=None, bug
=None):
42 self
.expectation
= expectation
.lower()
43 self
.pattern
= pattern
46 self
.os_conditions
= []
47 self
.browser_conditions
= []
51 self
.ParseCondition(c
)
53 def ParseCondition(self
, condition
):
54 """Parses a single test expectation condition.
56 Can be overridden to handle new types of conditions. Call the
57 superclass's implementation of ParseCondition at the end of your
58 subclass if you don't handle the condition. The base
59 implementation will raise an exception if the condition is
62 Valid expectation conditions are:
65 win, xp, vista, win7, mac, leopard, snowleopard, lion,
66 mountainlion, mavericks, yosemite, linux, chromeos, android
69 android-webview-shell, android-content-shell, debug, release
71 Sample usage in SetExpectations in subclasses:
72 self.Fail('gl-enable-vertex-attrib.html',
73 ['mac', 'release'], bug=123)
75 cl
= condition
.lower()
76 if cl
in OS_CONDITIONS
:
77 self
.os_conditions
.append(cl
)
78 elif cl
in BROWSER_TYPE_CONDITIONS
:
79 self
.browser_conditions
.append(condition
)
81 raise ValueError('Unknown expectation condition: "%s"' % cl
)
84 class TestExpectations(object):
85 """A class which defines the expectations for a page set test execution"""
88 self
._expectations
= []
89 self
._skip
_matching
_names
= False
90 self
._built
_expectation
_cache
= True
91 self
._ClearExpectationsCache
()
92 self
.SetExpectations()
94 def SetExpectations(self
):
95 """Called on creation. Override to set up custom expectations."""
98 def Fail(self
, pattern
, conditions
=None, bug
=None):
99 self
._Expect
('fail', pattern
, conditions
, bug
)
101 def Skip(self
, pattern
, conditions
=None, bug
=None):
102 self
._Expect
('skip', pattern
, conditions
, bug
)
104 def _Expect(self
, expectation
, pattern
, conditions
=None, bug
=None):
105 self
._AddExpectation
(self
.CreateExpectation(expectation
, pattern
,
108 def _AddExpectation(self
, expectation
):
109 '''Call this to add an expectation to the set.
111 For use only by this class and subclasses. Do not call this directly.'''
112 self
._expectations
.append(expectation
)
113 self
._ClearExpectationsCache
()
116 def CreateExpectation(self
, expectation
, pattern
, conditions
=None,
118 return Expectation(expectation
, pattern
, conditions
, bug
)
120 def _ClearExpectationsCache(self
):
121 if self
._built
_expectation
_cache
:
122 # Only those expectations which contain no wildcard characters
123 # (those which the fnmatch module would expand).
124 self
._expectations
_by
_pattern
= {}
125 # The remaining expectations which require expansion.
126 self
._expectations
_with
_wildcards
= []
127 self
._built
_expectation
_cache
= False
129 def ClearExpectationsCacheForTesting(self
):
130 '''For use only by unit tests.'''
131 self
._ClearExpectationsCache
()
133 def _HasWildcardCharacters(self
, input_string
):
134 # Could make this more precise.
135 return '*' in input_string
or '+' in input_string
137 def _BuildExpectationsCache(self
, browser
, page
):
138 # Turn off name matching while building the cache.
139 self
._skip
_matching
_names
= True
140 for e
in self
._expectations
:
141 if self
.ExpectationAppliesToPage(e
, browser
, page
):
142 if (self
._HasWildcardCharacters
(e
.pattern
)):
143 self
._expectations
_with
_wildcards
.append(e
)
145 self
._expectations
_by
_pattern
[e
.pattern
] = e
146 self
._built
_expectation
_cache
= True
147 self
._skip
_matching
_names
= False
149 def _GetNormalizedURL(self
, url
, browser
):
150 # Telemetry uses backslashes in its file:// URLs on Windows,
151 # breaking matching of test expectations.
152 if not browser
.platform
.GetOSName() == 'win':
154 return url
.replace('\\', '/')
156 def _GetURLPath(self
, url
):
157 components
= urlparse
.urlsplit(url
)
158 # For compatibility, the file:// scheme must be treated specially.
159 # The top-level directory shows up in the netloc portion of the URL.
160 if components
[0] == 'file':
161 url_path
= components
[1] + components
[2]
163 url_path
= components
[2]
164 # Chop any leading slash since the expectations used by this class
166 if (url_path
and url_path
[0] == '/'):
167 url_path
= url_path
[1:]
170 def _GetExpectationObjectForPage(self
, browser
, page
):
171 if not self
._built
_expectation
_cache
:
172 self
._BuildExpectationsCache
(browser
, page
)
173 # First attempt to look up by the page's URL or name.
175 # Relative URL (common case).
176 url
= self
._GetNormalizedURL
(page
.url
, browser
)
177 url_path
= self
._GetURLPath
(url
)
179 e
= self
._expectations
_by
_pattern
.get(url_path
)
182 e
= self
._expectations
_by
_pattern
.get(url
)
186 e
= self
._expectations
_by
_pattern
.get(page
.name
)
189 # Fall back to scanning through the expectations containing
191 for e
in self
._expectations
_with
_wildcards
:
192 if self
.ExpectationAppliesToPage(e
, browser
, page
):
196 def GetExpectationForPage(self
, browser
, page
):
197 '''Fetches the expectation that applies to the given page.
199 The implementation of this function performs significant caching
200 based on the browser's parameters, which are expected to remain
201 unchanged from call to call. If this is not true, the method
202 ClearExpectationsCacheForTesting is available to clear the cache;
203 but file a bug if this is needed for any reason but testing.
205 e
= self
._GetExpectationObjectForPage
(browser
, page
)
210 def ExpectationAppliesToPage(self
, expectation
, browser
, page
):
211 """Defines whether the given expectation applies to the given page.
213 Override this in subclasses to add more conditions. Call the
214 superclass's implementation first, and return false if it returns
215 false. Subclasses must not consult the page's name or URL; that is
216 the responsibility of the base class.
219 expectation: an instance of a subclass of Expectation, created
220 by a call to CreateExpectation.
221 browser: the currently running browser.
222 page: the page to be run.
224 # While building the expectations cache we need to match
225 # everything except the page's name or URL.
226 if not self
._skip
_matching
_names
:
228 if not fnmatch
.fnmatch(self
._GetURLPath
(page
.url
),
229 expectation
.pattern
):
231 if not fnmatch
.fnmatch(page
.url
,
232 expectation
.pattern
):
234 if not (page
.name
and fnmatch
.fnmatch(page
.name
,
235 expectation
.pattern
)):
238 platform
= browser
.platform
239 os_matches
= (not expectation
.os_conditions
or
240 platform
.GetOSName() in expectation
.os_conditions
or
241 platform
.GetOSVersionName() in expectation
.os_conditions
)
244 (not expectation
.browser_conditions
) or
245 browser
.browser_type
in expectation
.browser_conditions
)
247 return os_matches
and browser_matches