| OLD | NEW |
| (Empty) |
| 1 # Copyright (c) 2006-2009 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. | |
| 4 | |
| 5 """Classes for failures that occur during tests.""" | |
| 6 | |
| 7 import os | |
| 8 import test_expectations | |
| 9 | |
| 10 | |
| 11 def DetermineResultType(failure_list): | |
| 12 """Takes a set of test_failures and returns which result type best fits | |
| 13 the list of failures. "Best fits" means we use the worst type of failure. | |
| 14 | |
| 15 Returns: | |
| 16 one of the test_expectations result types - PASS, TEXT, CRASH, etc.""" | |
| 17 | |
| 18 if not failure_list or len(failure_list) == 0: | |
| 19 return test_expectations.PASS | |
| 20 | |
| 21 failure_types = [type(f) for f in failure_list] | |
| 22 if FailureCrash in failure_types: | |
| 23 return test_expectations.CRASH | |
| 24 elif FailureTimeout in failure_types: | |
| 25 return test_expectations.TIMEOUT | |
| 26 elif (FailureMissingResult in failure_types or | |
| 27 FailureMissingImage in failure_types or | |
| 28 FailureMissingImageHash in failure_types): | |
| 29 return test_expectations.MISSING | |
| 30 else: | |
| 31 is_text_failure = FailureTextMismatch in failure_types | |
| 32 is_image_failure = (FailureImageHashIncorrect in failure_types or | |
| 33 FailureImageHashMismatch in failure_types) | |
| 34 if is_text_failure and is_image_failure: | |
| 35 return test_expectations.IMAGE_PLUS_TEXT | |
| 36 elif is_text_failure: | |
| 37 return test_expectations.TEXT | |
| 38 elif is_image_failure: | |
| 39 return test_expectations.IMAGE | |
| 40 else: | |
| 41 raise ValueError("unclassifiable set of failures: " | |
| 42 + str(failure_types)) | |
| 43 | |
| 44 | |
| 45 class TestFailure(object): | |
| 46 """Abstract base class that defines the failure interface.""" | |
| 47 | |
| 48 @staticmethod | |
| 49 def Message(): | |
| 50 """Returns a string describing the failure in more detail.""" | |
| 51 raise NotImplemented | |
| 52 | |
| 53 def ResultHtmlOutput(self, filename): | |
| 54 """Returns an HTML string to be included on the results.html page.""" | |
| 55 raise NotImplemented | |
| 56 | |
| 57 def ShouldKillTestShell(self): | |
| 58 """Returns True if we should kill the test shell before the next | |
| 59 test.""" | |
| 60 return False | |
| 61 | |
| 62 def RelativeOutputFilename(self, filename, modifier): | |
| 63 """Returns a relative filename inside the output dir that contains | |
| 64 modifier. | |
| 65 | |
| 66 For example, if filename is fast\dom\foo.html and modifier is | |
| 67 "-expected.txt", the return value is fast\dom\foo-expected.txt | |
| 68 | |
| 69 Args: | |
| 70 filename: relative filename to test file | |
| 71 modifier: a string to replace the extension of filename with | |
| 72 | |
| 73 Return: | |
| 74 The relative windows path to the output filename | |
| 75 """ | |
| 76 return os.path.splitext(filename)[0] + modifier | |
| 77 | |
| 78 | |
| 79 class FailureWithType(TestFailure): | |
| 80 """Base class that produces standard HTML output based on the test type. | |
| 81 | |
| 82 Subclasses may commonly choose to override the ResultHtmlOutput, but still | |
| 83 use the standard OutputLinks. | |
| 84 """ | |
| 85 | |
| 86 def __init__(self, test_type): | |
| 87 TestFailure.__init__(self) | |
| 88 # TODO(ojan): This class no longer needs to know the test_type. | |
| 89 self._test_type = test_type | |
| 90 | |
| 91 # Filename suffixes used by ResultHtmlOutput. | |
| 92 OUT_FILENAMES = [] | |
| 93 | |
| 94 def OutputLinks(self, filename, out_names): | |
| 95 """Returns a string holding all applicable output file links. | |
| 96 | |
| 97 Args: | |
| 98 filename: the test filename, used to construct the result file names | |
| 99 out_names: list of filename suffixes for the files. If three or more | |
| 100 suffixes are in the list, they should be [actual, expected, diff, | |
| 101 wdiff]. Two suffixes should be [actual, expected], and a | |
| 102 single item is the [actual] filename suffix. | |
| 103 If out_names is empty, returns the empty string. | |
| 104 """ | |
| 105 links = [''] | |
| 106 uris = [self.RelativeOutputFilename(filename, fn) for fn in out_names] | |
| 107 if len(uris) > 1: | |
| 108 links.append("<a href='%s'>expected</a>" % uris[1]) | |
| 109 if len(uris) > 0: | |
| 110 links.append("<a href='%s'>actual</a>" % uris[0]) | |
| 111 if len(uris) > 2: | |
| 112 links.append("<a href='%s'>diff</a>" % uris[2]) | |
| 113 if len(uris) > 3: | |
| 114 links.append("<a href='%s'>wdiff</a>" % uris[3]) | |
| 115 return ' '.join(links) | |
| 116 | |
| 117 def ResultHtmlOutput(self, filename): | |
| 118 return self.Message() + self.OutputLinks(filename, self.OUT_FILENAMES) | |
| 119 | |
| 120 | |
| 121 class FailureTimeout(TestFailure): | |
| 122 """Test timed out. We also want to restart the test shell if this | |
| 123 happens.""" | |
| 124 | |
| 125 @staticmethod | |
| 126 def Message(): | |
| 127 return "Test timed out" | |
| 128 | |
| 129 def ResultHtmlOutput(self, filename): | |
| 130 return "<strong>%s</strong>" % self.Message() | |
| 131 | |
| 132 def ShouldKillTestShell(self): | |
| 133 return True | |
| 134 | |
| 135 | |
| 136 class FailureCrash(TestFailure): | |
| 137 """Test shell crashed.""" | |
| 138 | |
| 139 @staticmethod | |
| 140 def Message(): | |
| 141 return "Test shell crashed" | |
| 142 | |
| 143 def ResultHtmlOutput(self, filename): | |
| 144 # TODO(tc): create a link to the minidump file | |
| 145 stack = self.RelativeOutputFilename(filename, "-stack.txt") | |
| 146 return "<strong>%s</strong> <a href=%s>stack</a>" % (self.Message(), | |
| 147 stack) | |
| 148 | |
| 149 def ShouldKillTestShell(self): | |
| 150 return True | |
| 151 | |
| 152 | |
| 153 class FailureMissingResult(FailureWithType): | |
| 154 """Expected result was missing.""" | |
| 155 OUT_FILENAMES = ["-actual.txt"] | |
| 156 | |
| 157 @staticmethod | |
| 158 def Message(): | |
| 159 return "No expected results found" | |
| 160 | |
| 161 def ResultHtmlOutput(self, filename): | |
| 162 return ("<strong>%s</strong>" % self.Message() + | |
| 163 self.OutputLinks(filename, self.OUT_FILENAMES)) | |
| 164 | |
| 165 | |
| 166 class FailureTextMismatch(FailureWithType): | |
| 167 """Text diff output failed.""" | |
| 168 # Filename suffixes used by ResultHtmlOutput. | |
| 169 OUT_FILENAMES = ["-actual.txt", "-expected.txt", "-diff.txt"] | |
| 170 OUT_FILENAMES_WDIFF = ["-actual.txt", "-expected.txt", "-diff.txt", | |
| 171 "-wdiff.html"] | |
| 172 | |
| 173 def __init__(self, test_type, has_wdiff): | |
| 174 FailureWithType.__init__(self, test_type) | |
| 175 if has_wdiff: | |
| 176 self.OUT_FILENAMES = self.OUT_FILENAMES_WDIFF | |
| 177 | |
| 178 @staticmethod | |
| 179 def Message(): | |
| 180 return "Text diff mismatch" | |
| 181 | |
| 182 | |
| 183 class FailureMissingImageHash(FailureWithType): | |
| 184 """Actual result hash was missing.""" | |
| 185 # Chrome doesn't know to display a .checksum file as text, so don't bother | |
| 186 # putting in a link to the actual result. | |
| 187 OUT_FILENAMES = [] | |
| 188 | |
| 189 @staticmethod | |
| 190 def Message(): | |
| 191 return "No expected image hash found" | |
| 192 | |
| 193 def ResultHtmlOutput(self, filename): | |
| 194 return "<strong>%s</strong>" % self.Message() | |
| 195 | |
| 196 | |
| 197 class FailureMissingImage(FailureWithType): | |
| 198 """Actual result image was missing.""" | |
| 199 OUT_FILENAMES = ["-actual.png"] | |
| 200 | |
| 201 @staticmethod | |
| 202 def Message(): | |
| 203 return "No expected image found" | |
| 204 | |
| 205 def ResultHtmlOutput(self, filename): | |
| 206 return ("<strong>%s</strong>" % self.Message() + | |
| 207 self.OutputLinks(filename, self.OUT_FILENAMES)) | |
| 208 | |
| 209 | |
| 210 class FailureImageHashMismatch(FailureWithType): | |
| 211 """Image hashes didn't match.""" | |
| 212 OUT_FILENAMES = ["-actual.png", "-expected.png", "-diff.png"] | |
| 213 | |
| 214 @staticmethod | |
| 215 def Message(): | |
| 216 # We call this a simple image mismatch to avoid confusion, since | |
| 217 # we link to the PNGs rather than the checksums. | |
| 218 return "Image mismatch" | |
| 219 | |
| 220 | |
| 221 class FailureFuzzyFailure(FailureWithType): | |
| 222 """Image hashes didn't match.""" | |
| 223 OUT_FILENAMES = ["-actual.png", "-expected.png"] | |
| 224 | |
| 225 @staticmethod | |
| 226 def Message(): | |
| 227 return "Fuzzy image match also failed" | |
| 228 | |
| 229 | |
| 230 class FailureImageHashIncorrect(FailureWithType): | |
| 231 """Actual result hash is incorrect.""" | |
| 232 # Chrome doesn't know to display a .checksum file as text, so don't bother | |
| 233 # putting in a link to the actual result. | |
| 234 OUT_FILENAMES = [] | |
| 235 | |
| 236 @staticmethod | |
| 237 def Message(): | |
| 238 return "Images match, expected image hash incorrect. " | |
| 239 | |
| 240 def ResultHtmlOutput(self, filename): | |
| 241 return "<strong>%s</strong>" % self.Message() | |
| OLD | NEW |