| 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 import re | |
| 6 | |
| 7 from failure import Failure | |
| 8 | |
| 9 CHROMIUM_BUG_URL = "http://crbug.com/" | |
| 10 | |
| 11 | |
| 12 def ExtractFirstValue(string, regex): | |
| 13 m = re.search(regex, string) | |
| 14 if m and m.group(1): | |
| 15 return m.group(1) | |
| 16 return None | |
| 17 | |
| 18 # TODO(gwilson): Refactor HTML generation into a HTML templating system like | |
| 19 # Django templates. | |
| 20 | |
| 21 | |
| 22 class HTMLGenerator(object): | |
| 23 | |
| 24 def __init__(self, failures, output_dir, build, platform, | |
| 25 exclude_known_failures): | |
| 26 self.failures = failures | |
| 27 self.output_dir = output_dir | |
| 28 self.build = build | |
| 29 self.platform = platform | |
| 30 self.exclude_known_failures = exclude_known_failures | |
| 31 self.image_size = "200px" | |
| 32 | |
| 33 def GenerateHTML(self): | |
| 34 html = "" | |
| 35 html += """ | |
| 36 <html> | |
| 37 <head> | |
| 38 <style> | |
| 39 body { | |
| 40 font-family: sans-serif; | |
| 41 } | |
| 42 h2 { | |
| 43 } | |
| 44 .mainTable { | |
| 45 background: #666666; | |
| 46 } | |
| 47 .mainTable td , .mainTable th { | |
| 48 background: white; | |
| 49 } | |
| 50 .titlelink { | |
| 51 font-size: 18pt; | |
| 52 font-weight: bold; | |
| 53 } | |
| 54 .detail { | |
| 55 margin-left: 10px; | |
| 56 margin-top: 3px; | |
| 57 } | |
| 58 </style> | |
| 59 </head> | |
| 60 <body> | |
| 61 """ | |
| 62 title = "All failures" | |
| 63 if self.exclude_known_failures: | |
| 64 title = "Regressions" | |
| 65 | |
| 66 html += """ | |
| 67 <h1>%s for build %s (%s)</h1> | |
| 68 """ % (title, self.build, self.platform) | |
| 69 | |
| 70 test_number = 0 | |
| 71 | |
| 72 # TODO(gwilson): Refactor this to do a join() on an array of HTML, | |
| 73 # rather than appending strings in a loop. | |
| 74 for failure in self.failures: | |
| 75 test_number += 1 | |
| 76 html += """ | |
| 77 <table style="border: 1px solid black; width: 1200px; | |
| 78 -webkit-border-radius: 5px;" cellspacing="0" cellpadding="4"> | |
| 79 <tr> | |
| 80 <td style="background-color: #CDECDE; | |
| 81 border-bottom: 1px solid black;"> | |
| 82 <span class="titlelink">%s. %s</span></td></tr> | |
| 83 <tr><td> Last modified: <a href="%s">%s</a> | |
| 84 """ % (test_number, failure.test_path, failure.GetTestHome(), | |
| 85 failure.test_age) | |
| 86 html += "<div class='detail'>" | |
| 87 html += "<pre>%s</pre>" % \ | |
| 88 (self._GenerateLinkifiedTextExpectations(failure)) | |
| 89 | |
| 90 html += self._GenerateFlakinessHTML(failure) | |
| 91 | |
| 92 if failure.crashed: | |
| 93 html += "<div>Test <b>CRASHED</b></div>" | |
| 94 elif failure.timeout: | |
| 95 html += "<div>Test <b>TIMED OUT</b></div>" | |
| 96 else: | |
| 97 html += """ | |
| 98 <table class="mainTable" cellspacing=1 cellpadding=5> | |
| 99 <tr> | |
| 100 <th width='250'> </th> | |
| 101 <th width='200'>Expected</th> | |
| 102 <th width='200'>Actual</th> | |
| 103 <th width='200'>Difference</th> | |
| 104 <th width='200'>Upstream</th> | |
| 105 </tr> | |
| 106 """ | |
| 107 | |
| 108 if failure.text_diff_mismatch: | |
| 109 html += self._GenerateTextFailureHTML(failure) | |
| 110 | |
| 111 if failure.image_mismatch: | |
| 112 html += self._GenerateImageFailureHTML(failure) | |
| 113 | |
| 114 html += "</table>" | |
| 115 html += "</div></td></tr></table><br>" | |
| 116 html += """</body></html>""" | |
| 117 | |
| 118 # TODO(gwilson): Change this filename to be passed in as an argument. | |
| 119 html_filename = "%s/index-%s.html" % (self.output_dir, self.build) | |
| 120 htmlFile = open(html_filename, 'w') | |
| 121 htmlFile.write(html) | |
| 122 htmlFile.close() | |
| 123 return html_filename | |
| 124 | |
| 125 def _GenerateLinkifiedTextExpectations(self, failure): | |
| 126 if not failure.test_expectations_line: | |
| 127 return "" | |
| 128 bug_number = ExtractFirstValue(failure.test_expectations_line, | |
| 129 "BUG(\d+)") | |
| 130 if not bug_number or bug_number == "": | |
| 131 return "" | |
| 132 return failure.test_expectations_line.replace( | |
| 133 "BUG" + bug_number, | |
| 134 "<a href='%s%s'>BUG%s</a>" % (CHROMIUM_BUG_URL, bug_number, | |
| 135 bug_number)) | |
| 136 | |
| 137 # TODO(gwilson): Fix this so that it shows the last ten runs | |
| 138 # not just a "meter" of flakiness. | |
| 139 | |
| 140 def _GenerateFlakinessHTML(self, failure): | |
| 141 html = "" | |
| 142 if not failure.flakiness: | |
| 143 return html | |
| 144 html += """ | |
| 145 <table> | |
| 146 <tr> | |
| 147 <td>Flakiness: (%s)</td> | |
| 148 """ % (failure.flakiness) | |
| 149 | |
| 150 flaky_red = int(round(int(failure.flakiness) / 5)) | |
| 151 flaky_green = 10 - flaky_red | |
| 152 for i in range(0, flaky_green): | |
| 153 html += """ | |
| 154 <td style="background: green"> </td> | |
| 155 """ | |
| 156 for i in range(0, flaky_red): | |
| 157 html += """ | |
| 158 <td style="background: red"> </td> | |
| 159 """ | |
| 160 html += """ | |
| 161 </tr> | |
| 162 </table><br> | |
| 163 """ | |
| 164 return html | |
| 165 | |
| 166 def _GenerateTextFailureHTML(self, failure): | |
| 167 html = "" | |
| 168 if not failure.GetTextBaselineLocation(): | |
| 169 return """<tr><td colspan='5'>This test likely does not have any | |
| 170 TEXT baseline for this platform, or one could not | |
| 171 be found.</td></tr>""" | |
| 172 html += """ | |
| 173 <tr> | |
| 174 <td> | |
| 175 <b><a href="%s">Render Tree Dump</a></b><br> | |
| 176 <b>%s</b> baseline<br> | |
| 177 Age: %s<br> | |
| 178 </td> | |
| 179 """ % (failure.text_baseline_url, | |
| 180 failure.GetTextBaselineLocation(), | |
| 181 failure.text_baseline_age) | |
| 182 html += self._GenerateTextFailureTD(failure.GetExpectedTextFilename(), | |
| 183 "expected text") | |
| 184 html += self._GenerateTextFailureTD(failure.GetActualTextFilename(), | |
| 185 "actual text") | |
| 186 html += self._GenerateTextFailureTD(failure.GetTextDiffFilename(), | |
| 187 "text diff") | |
| 188 html += "<td> </td>" | |
| 189 html += "</tr>" | |
| 190 return html | |
| 191 | |
| 192 def _GenerateTextFailureTD(self, file_path, anchor_text): | |
| 193 return ("<td align=center>" | |
| 194 "<a href='./layout-test-results-%s/%s'>%s</a></td>") % ( | |
| 195 self.build, file_path, anchor_text) | |
| 196 | |
| 197 def _GenerateImageFailureHTML(self, failure): | |
| 198 if not failure.GetImageBaselineLocation(): | |
| 199 return """<tr><td colspan='5'>This test likely does not have any | |
| 200 IMAGE baseline for this platform, or one could not be | |
| 201 found.</td></tr>""" | |
| 202 html = """ | |
| 203 <tr> | |
| 204 <td><b><a href="%s">Pixel Dump</a></b><br> | |
| 205 <b>%s</b> baseline<br>Age: %s</td> | |
| 206 """ % (failure.image_baseline_url, | |
| 207 failure.GetImageBaselineLocation(), | |
| 208 failure.image_baseline_age) | |
| 209 html += self._GenerateImageFailureTD( | |
| 210 failure.GetExpectedImageFilename()) | |
| 211 html += self._GenerateImageFailureTD( | |
| 212 failure.GetActualImageFilename()) | |
| 213 html += self._GenerateImageFailureTD( | |
| 214 failure.GetImageDiffFilename()) | |
| 215 if (failure.image_baseline_upstream_local and | |
| 216 failure.image_baseline_upstream_local != ""): | |
| 217 html += self._GenerateImageFailureTD( | |
| 218 failure.GetImageUpstreamFilename()) | |
| 219 else: | |
| 220 html += """ | |
| 221 <td> </td> | |
| 222 """ | |
| 223 html += "</tr>" | |
| 224 return html | |
| 225 | |
| 226 def _GenerateImageFailureTD(self, filename): | |
| 227 return ("<td><a href='./layout-test-results-%s/%s'>" | |
| 228 "<img style='width: %s' src='./layout-test-results-%s/%s' />" | |
| 229 "</a></td>") % (self.build, filename, self.image_size, | |
| 230 self.build, filename) | |
| OLD | NEW |