2 # Copyright (C) 2010 Google Inc. All rights reserved.
4 # Redistribution and use in source and binary forms, with or without
5 # modification, are permitted provided that the following conditions are
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
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.
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.
30 """Classes for failures that occur during tests."""
33 import test_expectations
38 def determine_result_type(failure_list):
39 """Takes a set of test_failures and returns which result type best fits
40 the list of failures. "Best fits" means we use the worst type of failure.
43 one of the test_expectations result types - PASS, TEXT, CRASH, etc."""
45 if not failure_list or len(failure_list) == 0:
46 return test_expectations.PASS
48 failure_types = [type(f) for f in failure_list]
49 if FailureCrash in failure_types:
50 return test_expectations.CRASH
51 elif FailureTimeout in failure_types:
52 return test_expectations.TIMEOUT
53 elif (FailureMissingResult in failure_types or
54 FailureMissingImage in failure_types or
55 FailureMissingImageHash in failure_types):
56 return test_expectations.MISSING
58 is_text_failure = FailureTextMismatch in failure_types
59 is_image_failure = (FailureImageHashIncorrect in failure_types or
60 FailureImageHashMismatch in failure_types)
61 if is_text_failure and is_image_failure:
62 return test_expectations.IMAGE_PLUS_TEXT
64 return test_expectations.TEXT
65 elif is_image_failure:
66 return test_expectations.IMAGE
68 raise ValueError("unclassifiable set of failures: "
72 class TestFailure(object):
73 """Abstract base class that defines the failure interface."""
77 """Creates a TestFailure object from the specified string."""
78 return cPickle.loads(s)
82 """Returns a string describing the failure in more detail."""
83 raise NotImplementedError
85 def __eq__(self, other):
86 return self.__class__.__name__ == other.__class__.__name__
88 def __ne__(self, other):
89 return self.__class__.__name__ != other.__class__.__name__
92 """Returns the string/JSON representation of a TestFailure."""
93 return cPickle.dumps(self)
95 def result_html_output(self, filename):
96 """Returns an HTML string to be included on the results.html page."""
97 raise NotImplementedError
99 def should_kill_dump_render_tree(self):
100 """Returns True if we should kill DumpRenderTree before the next
104 def relative_output_filename(self, filename, modifier):
105 """Returns a relative filename inside the output dir that contains
108 For example, if filename is fast\dom\foo.html and modifier is
109 "-expected.txt", the return value is fast\dom\foo-expected.txt
112 filename: relative filename to test file
113 modifier: a string to replace the extension of filename with
116 The relative windows path to the output filename
118 return os.path.splitext(filename)[0] + modifier
121 class FailureWithType(TestFailure):
122 """Base class that produces standard HTML output based on the test type.
124 Subclasses may commonly choose to override the ResultHtmlOutput, but still
125 use the standard OutputLinks.
129 TestFailure.__init__(self)
131 # Filename suffixes used by ResultHtmlOutput.
134 def output_links(self, filename, out_names):
135 """Returns a string holding all applicable output file links.
138 filename: the test filename, used to construct the result file names
139 out_names: list of filename suffixes for the files. If three or more
140 suffixes are in the list, they should be [actual, expected, diff,
141 wdiff]. Two suffixes should be [actual, expected], and a
142 single item is the [actual] filename suffix.
143 If out_names is empty, returns the empty string.
145 # FIXME: Seems like a bad idea to separate the display name data
146 # from the path data by hard-coding the display name here
147 # and passing in the path information via out_names.
149 # FIXME: Also, we don't know for sure that these files exist,
150 # and we shouldn't be creating links to files that don't exist
151 # (for example, if we don't actually have wdiff output).
153 uris = [self.relative_output_filename(filename, fn) for
156 links.append("<a href='%s'>expected</a>" % uris[1])
158 links.append("<a href='%s'>actual</a>" % uris[0])
160 links.append("<a href='%s'>diff</a>" % uris[2])
162 links.append("<a href='%s'>wdiff</a>" % uris[3])
164 links.append("<a href='%s'>pretty diff</a>" % uris[4])
165 return ' '.join(links)
167 def result_html_output(self, filename):
168 return self.message() + self.output_links(filename, self.OUT_FILENAMES)
171 class FailureTimeout(TestFailure):
172 """Test timed out. We also want to restart DumpRenderTree if this
177 return "Test timed out"
179 def result_html_output(self, filename):
180 return "<strong>%s</strong>" % self.message()
182 def should_kill_dump_render_tree(self):
186 class FailureCrash(TestFailure):
187 """Test shell crashed."""
191 return "Test shell crashed"
193 def result_html_output(self, filename):
194 # FIXME: create a link to the minidump file
195 stack = self.relative_output_filename(filename, "-stack.txt")
196 return "<strong>%s</strong> <a href=%s>stack</a>" % (self.message(),
199 def should_kill_dump_render_tree(self):
203 class FailureMissingResult(FailureWithType):
204 """Expected result was missing."""
205 OUT_FILENAMES = ("-actual.txt",)
209 return "No expected results found"
211 def result_html_output(self, filename):
212 return ("<strong>%s</strong>" % self.message() +
213 self.output_links(filename, self.OUT_FILENAMES))
216 class FailureTextMismatch(FailureWithType):
217 """Text diff output failed."""
218 # Filename suffixes used by ResultHtmlOutput.
219 # FIXME: Why don't we use the constants from TestTypeBase here?
220 OUT_FILENAMES = ("-actual.txt", "-expected.txt", "-diff.txt",
221 "-wdiff.html", "-pretty-diff.html")
225 return "Text diff mismatch"
228 class FailureMissingImageHash(FailureWithType):
229 """Actual result hash was missing."""
230 # Chrome doesn't know to display a .checksum file as text, so don't bother
231 # putting in a link to the actual result.
235 return "No expected image hash found"
237 def result_html_output(self, filename):
238 return "<strong>%s</strong>" % self.message()
241 class FailureMissingImage(FailureWithType):
242 """Actual result image was missing."""
243 OUT_FILENAMES = ("-actual.png",)
247 return "No expected image found"
249 def result_html_output(self, filename):
250 return ("<strong>%s</strong>" % self.message() +
251 self.output_links(filename, self.OUT_FILENAMES))
254 class FailureImageHashMismatch(FailureWithType):
255 """Image hashes didn't match."""
256 OUT_FILENAMES = ("-actual.png", "-expected.png", "-diff.png")
260 # We call this a simple image mismatch to avoid confusion, since
261 # we link to the PNGs rather than the checksums.
262 return "Image mismatch"
265 class FailureImageHashIncorrect(FailureWithType):
266 """Actual result hash is incorrect."""
267 # Chrome doesn't know to display a .checksum file as text, so don't bother
268 # putting in a link to the actual result.
272 return "Images match, expected image hash incorrect. "
274 def result_html_output(self, filename):
275 return "<strong>%s</strong>" % self.message()
277 # Convenient collection of all failure classes for anything that might
278 # need to enumerate over them all.
279 ALL_FAILURE_CLASSES = (FailureTimeout, FailureCrash, FailureMissingResult,
280 FailureTextMismatch, FailureMissingImageHash,
281 FailureMissingImage, FailureImageHashMismatch,
282 FailureImageHashIncorrect)