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 |