OLD | NEW |
| (Empty) |
1 /* | |
2 * Copyright (C) 2011 Apple Inc. All rights reserved. | |
3 * | |
4 * Redistribution and use in source and binary forms, with or without | |
5 * modification, are permitted provided that the following conditions | |
6 * are met: | |
7 * 1. Redistributions of source code must retain the above copyright | |
8 * notice, this list of conditions and the following disclaimer. | |
9 * 2. Redistributions in binary form must reproduce the above copyright | |
10 * notice, this list of conditions and the following disclaimer in the | |
11 * documentation and/or other materials provided with the distribution. | |
12 * | |
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' | |
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, | |
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR | |
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS | |
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF | |
23 * THE POSSIBILITY OF SUCH DAMAGE. | |
24 */ | |
25 | |
26 function ViewController(buildbot) { | |
27 this._buildbot = buildbot; | |
28 this._navigationID = 0; | |
29 | |
30 var self = this; | |
31 addEventListener('load', function() { self.loaded() }, false); | |
32 addEventListener('hashchange', function() { self.parseHash(location.hash) },
false); | |
33 } | |
34 | |
35 ViewController.prototype = { | |
36 loaded: function() { | |
37 this._header = document.createElement('h1'); | |
38 document.body.appendChild(this._header); | |
39 this._mainContentElement = document.createElement('div'); | |
40 document.body.appendChild(this._mainContentElement); | |
41 document.body.appendChild(this._domForAuxiliaryUIElements()); | |
42 | |
43 this.parseHash(location.hash); | |
44 }, | |
45 | |
46 parseHash: function(hash) { | |
47 ++this._navigationID; | |
48 | |
49 var match = /#\/(.*)/.exec(hash); | |
50 if (match) | |
51 this._displayBuilder(this._buildbot.builderNamed(decodeURIComponent(
match[1]))); | |
52 else | |
53 this._displayTesters(); | |
54 }, | |
55 | |
56 _displayBuilder: function(builder) { | |
57 this._setTitle(builder.name); | |
58 this._mainContentElement.removeAllChildren(); | |
59 | |
60 var navigationID = this._navigationID; | |
61 | |
62 var self = this; | |
63 (new LayoutTestHistoryAnalyzer(builder)).start(function(data, stillFetch
ingData) { | |
64 if (self._navigationID !== navigationID) { | |
65 // The user has navigated somewhere else. Stop loading data abou
t this tester. | |
66 return false; | |
67 } | |
68 | |
69 var list = document.createElement('ol'); | |
70 list.id = 'failure-history'; | |
71 | |
72 var buildNames = Object.keys(data.history) | |
73 buildNames.forEach(function(buildName, buildIndex, buildNameArray) { | |
74 var failingTestNames = Object.keys(data.history[buildName].tests
); | |
75 if (!failingTestNames.length) | |
76 return; | |
77 | |
78 var item = document.createElement('li'); | |
79 list.appendChild(item); | |
80 | |
81 var testList = document.createElement('ol'); | |
82 item.appendChild(testList); | |
83 | |
84 testList.className = 'test-list'; | |
85 for (var testName in data.history[buildName].tests) { | |
86 var testItem = document.createElement('li'); | |
87 testItem.appendChild(self._domForFailedTest(builder, buildNa
me, testName, data.history[buildName].tests[testName])); | |
88 testList.appendChild(testItem); | |
89 } | |
90 | |
91 if (data.history[buildName].tooManyFailures) { | |
92 var p = document.createElement('p'); | |
93 p.className = 'info'; | |
94 p.appendChild(document.createTextNode('run-webkit-tests exit
ed early due to too many failures/crashes/timeouts')); | |
95 item.appendChild(p); | |
96 } | |
97 | |
98 var passingBuildName; | |
99 if (buildIndex + 1 < buildNameArray.length) | |
100 passingBuildName = buildNameArray[buildIndex + 1]; | |
101 | |
102 item.appendChild(self._domForRegressionRange(builder, buildName,
passingBuildName, failingTestNames)); | |
103 | |
104 if (passingBuildName || !stillFetchingData) { | |
105 var bugForm = new FailingTestsBugForm(builder, buildName, pa
ssingBuildName, failingTestNames); | |
106 item.appendChild(self._domForNewAndExistingBugs(builder, fai
lingTestNames, bugForm)) | |
107 } | |
108 }); | |
109 | |
110 self._mainContentElement.removeAllChildren(); | |
111 self._mainContentElement.appendChild(list); | |
112 self._mainContentElement.appendChild(self._domForPossiblyFlakyTests(
builder, data.possiblyFlaky, buildNames)); | |
113 | |
114 if (!stillFetchingData) | |
115 PersistentCache.prune(); | |
116 | |
117 return true; | |
118 }); | |
119 }, | |
120 | |
121 _displayTesters: function() { | |
122 this._setTitle('Testers'); | |
123 this._mainContentElement.removeAllChildren(); | |
124 | |
125 var list = document.createElement('ul'); | |
126 this._mainContentElement.appendChild(list); | |
127 | |
128 var latestBuildInfos = []; | |
129 var navigationID = this._navigationID; | |
130 | |
131 function updateList() { | |
132 latestBuildInfos.sort(function(a, b) { return a.tester.name.localeCo
mpare(b.tester.name) }); | |
133 list.removeAllChildren(); | |
134 latestBuildInfos.forEach(function(buildInfo) { | |
135 var link = base.createLinkNode('#/' + buildInfo.tester.name, bui
ldInfo.tester.name) | |
136 var item = document.createElement('li'); | |
137 item.appendChild(link); | |
138 if (buildInfo.tooManyFailures) | |
139 item.appendChild(document.createTextNode(' (too many failure
s/crashes/timeouts)')); | |
140 else | |
141 item.appendChild(document.createTextNode(' (' + buildInfo.fa
ilureCount + ' failing test' + (buildInfo.failureCount > 1 ? 's' : '') + ')')); | |
142 list.appendChild(item); | |
143 }); | |
144 } | |
145 | |
146 var self = this; | |
147 this._buildbot.getTesters(function(testers) { | |
148 if (self._navigationID !== navigationID) { | |
149 // The user has navigated somewhere else. | |
150 return; | |
151 } | |
152 testers.forEach(function(tester) { | |
153 tester.getMostRecentCompletedBuildNumber(function(buildNumber) { | |
154 if (self._navigationID !== navigationID) | |
155 return; | |
156 if (buildNumber < 0) | |
157 return; | |
158 tester.getNumberOfFailingTests(buildNumber, function(failure
Count, tooManyFailures) { | |
159 if (self._navigationID !== navigationID) | |
160 return; | |
161 if (failureCount <= 0) | |
162 return; | |
163 latestBuildInfos.push({ tester: tester, failureCount: fa
ilureCount, tooManyFailures: tooManyFailures }); | |
164 updateList(); | |
165 }); | |
166 }); | |
167 }); | |
168 }); | |
169 }, | |
170 | |
171 _domForRegressionRange: function(builder, failingBuildName, passingBuildName
, failingTestNames) { | |
172 var result = document.createDocumentFragment(); | |
173 | |
174 var dlItems = [ | |
175 [document.createTextNode('Failed'), this._domForBuildName(builder, f
ailingBuildName)], | |
176 ]; | |
177 if (passingBuildName) | |
178 dlItems.push([document.createTextNode('Passed'), this._domForBuildNa
me(builder, passingBuildName)]); | |
179 result.appendChild(createDescriptionList(dlItems)); | |
180 | |
181 if (!passingBuildName) | |
182 return result; | |
183 | |
184 var firstSuspectRevision = this._buildbot.parseBuildName(passingBuildNam
e).revision + 1; | |
185 var lastSuspectRevision = this._buildbot.parseBuildName(failingBuildName
).revision; | |
186 | |
187 if (firstSuspectRevision === lastSuspectRevision) | |
188 return result; | |
189 | |
190 var suspectsContainer = document.createElement('div'); | |
191 result.appendChild(suspectsContainer); | |
192 | |
193 var link = base.createLinkNode(trac.logURL('trunk', firstSuspectRevision
, lastSuspectRevision, true), 'View regression range in Trac'); | |
194 result.appendChild(link); | |
195 | |
196 suspectsContainer.appendChild(document.createTextNode('Searching for sus
pect revisions\u2026')); | |
197 | |
198 // FIXME: Maybe some of this code should go in LayoutTestHistoryAnalyzer
, or some other class? | |
199 var self = this; | |
200 trac.commitDataForRevisionRange('trunk', firstSuspectRevision, lastSuspe
ctRevision, function(commits) { | |
201 var failingTestNamesWithoutExtensions = failingTestNames.map(removeP
athExtension); | |
202 var suspectCommits = commits.filter(function(commit) { | |
203 return failingTestNamesWithoutExtensions.some(function(testName)
{ | |
204 return commit.message.contains(testName); | |
205 }); | |
206 }); | |
207 | |
208 suspectsContainer.removeAllChildren(); | |
209 | |
210 if (!suspectCommits.length) | |
211 return; | |
212 | |
213 var title = 'Suspect revision' + (suspectCommits.length > 1 ? 's' :
'') + ':'; | |
214 suspectsContainer.appendChild(document.createTextNode(title)); | |
215 | |
216 var list = document.createElement('ul'); | |
217 suspectsContainer.appendChild(list); | |
218 list.className = 'suspect-revisions-list'; | |
219 | |
220 function compareCommits(a, b) { | |
221 return b.revision - a.revision; | |
222 } | |
223 | |
224 list.appendChildren(sorted(suspectCommits, compareCommits).map(funct
ion(commit) { | |
225 var item = document.createElement('li'); | |
226 item.appendChild(base.createLinkNode(trac.changesetURL(commit.re
vision), commit.title)); | |
227 return item; | |
228 })); | |
229 }); | |
230 | |
231 return result; | |
232 }, | |
233 | |
234 _domForAuxiliaryUIElements: function() { | |
235 var aside = document.createElement('aside'); | |
236 aside.appendChild(document.createTextNode('Something not working? Have a
n idea to improve this page? ')); | |
237 var queryParameters = { | |
238 product: 'WebKit', | |
239 component: 'Tools / Tests', | |
240 version: '528+ (Nightly build)', | |
241 bug_file_loc: location.href, | |
242 cc: 'aroben@apple.com', | |
243 short_desc: 'TestFailures page needs more unicorns!', | |
244 }; | |
245 aside.appendChild(base.createLinkNode(addQueryParametersToURL(config.kBu
gzillaURL + '/enter_bug.cgi', queryParameters), 'File a bug!', '_blank')); | |
246 return aside; | |
247 }, | |
248 | |
249 _domForBuildName: function(builder, buildName) { | |
250 var parsed = this._buildbot.parseBuildName(buildName); | |
251 | |
252 var sourceLink = base.createLinkNode('http://trac.webkit.org/changeset/'
+ parsed.revision, 'r' + parsed.revision); | |
253 var buildLink = base.createLinkNode(builder.buildURL(parsed.buildNumber)
, parsed.buildNumber); | |
254 var resultsLink = base.createLinkNode(builder.resultsPageURL(buildName),
'results.html'); | |
255 | |
256 var result = document.createDocumentFragment(); | |
257 result.appendChild(sourceLink); | |
258 result.appendChild(document.createTextNode(' (')); | |
259 result.appendChild(buildLink); | |
260 result.appendChild(document.createTextNode(') (')); | |
261 result.appendChild(resultsLink); | |
262 result.appendChild(document.createTextNode(')')); | |
263 | |
264 return result; | |
265 }, | |
266 | |
267 _domForFailedTest: function(builder, buildName, testName, testResult) { | |
268 var result = document.createDocumentFragment(); | |
269 result.appendChild(document.createTextNode(testName + ': ')); | |
270 result.appendChild(this._domForFailureDiagnosis(builder, buildName, test
Name, testResult)); | |
271 return result; | |
272 }, | |
273 | |
274 _domForFailureDiagnosis: function(builder, buildName, testName, testResult)
{ | |
275 var diagnosticInfo = builder.failureDiagnosisTextAndURL(buildName, testN
ame, testResult); | |
276 if (!diagnosticInfo) | |
277 return document.createTextNode(testResult.failureType); | |
278 | |
279 var textAndCrashingSymbol = document.createDocumentFragment(); | |
280 textAndCrashingSymbol.appendChild(document.createTextNode(diagnosticInfo
.text)); | |
281 if (testResult.crashingSymbol) { | |
282 var code = document.createElement('code'); | |
283 code.appendChild(document.createTextNode(testResult.crashingSymbol))
; | |
284 textAndCrashingSymbol.appendChild(document.createTextNode(' (')); | |
285 textAndCrashingSymbol.appendChild(code); | |
286 textAndCrashingSymbol.appendChild(document.createTextNode(')')); | |
287 } | |
288 | |
289 if (!('url' in diagnosticInfo)) | |
290 return textAndCrashingSymbol; | |
291 | |
292 return base.createLinkNode(diagnosticInfo.url, textAndCrashingSymbol); | |
293 }, | |
294 | |
295 _domForNewAndExistingBugs: function(tester, failingTests, bugForm) { | |
296 var result = document.createDocumentFragment(); | |
297 | |
298 var container = document.createElement('p'); | |
299 result.appendChild(container); | |
300 | |
301 container.className = 'existing-and-new-bugs'; | |
302 | |
303 var bugsContainer = document.createElement('div'); | |
304 container.appendChild(bugsContainer); | |
305 | |
306 bugsContainer.appendChild(document.createTextNode('Searching for bugs re
lated to ' + (failingTests.length > 1 ? 'these tests' : 'this test') + '\u2026')
); | |
307 | |
308 bugzilla.quickSearch('ALL ' + failingTests.join('|'), function(bugs) { | |
309 if (!bugs.length) { | |
310 bugsContainer.parentNode.removeChild(bugsContainer); | |
311 return; | |
312 } | |
313 | |
314 while (bugsContainer.firstChild) | |
315 bugsContainer.removeChild(bugsContainer.firstChild); | |
316 | |
317 bugsContainer.appendChild(document.createTextNode('Existing bugs rel
ated to ' + (failingTests.length > 1 ? 'these tests' : 'this test') + ':')); | |
318 | |
319 var list = document.createElement('ul'); | |
320 bugsContainer.appendChild(list); | |
321 | |
322 list.className = 'existing-bugs-list'; | |
323 | |
324 function bugToListItem(bug) { | |
325 var link = base.createLinkNode(bug.url, bug.title); | |
326 var item = document.createElement('li'); | |
327 item.appendChild(link); | |
328 | |
329 return item; | |
330 } | |
331 | |
332 var openBugs = bugs.filter(function(bug) { return bugzilla.isOpenSta
tus(bug.status) }); | |
333 var closedBugs = bugs.filter(function(bug) { return !bugzilla.isOpen
Status(bug.status) }); | |
334 | |
335 list.appendChildren(openBugs.map(bugToListItem)); | |
336 | |
337 if (!closedBugs.length) | |
338 return; | |
339 | |
340 var item = document.createElement('li'); | |
341 list.appendChild(item); | |
342 | |
343 item.appendChild(document.createTextNode('Closed bugs:')); | |
344 | |
345 var closedList = document.createElement('ul'); | |
346 item.appendChild(closedList); | |
347 | |
348 closedList.appendChildren(closedBugs.map(bugToListItem)); | |
349 }); | |
350 | |
351 var form = bugForm.domElement(); | |
352 result.appendChild(form); | |
353 | |
354 var linkText = 'File bug for ' + (failingTests.length > 1 ? 'these failu
res' : 'this failure'); | |
355 var link = base.createLinkNode('#', linkText); | |
356 link.addEventListener('click', function(event) { form.submit(); event.pr
eventDefault(); }); | |
357 | |
358 container.appendChild(link); | |
359 return result; | |
360 }, | |
361 | |
362 _domForPossiblyFlakyTests: function(builder, possiblyFlakyTestData, allBuild
s) { | |
363 var result = document.createDocumentFragment(); | |
364 var flakyTests = Object.keys(possiblyFlakyTestData); | |
365 if (!flakyTests.length) | |
366 return result; | |
367 | |
368 var flakyHeader = document.createElement('h2'); | |
369 result.appendChild(flakyHeader); | |
370 flakyHeader.appendChild(document.createTextNode('Possibly Flaky Tests'))
; | |
371 | |
372 var flakyList = document.createElement('ol'); | |
373 result.appendChild(flakyList); | |
374 | |
375 flakyList.id = 'possibly-flaky-tests'; | |
376 | |
377 var self = this; | |
378 flakyList.appendChildren(sorted(flakyTests).map(function(testName) { | |
379 var item = document.createElement('li'); | |
380 | |
381 var disclosureTriangle = document.createElement('span'); | |
382 item.appendChild(disclosureTriangle); | |
383 | |
384 disclosureTriangle.className = 'disclosure-triangle'; | |
385 const blackRightPointingSmallTriangle = '\u25b8'; | |
386 disclosureTriangle.appendChild(document.createTextNode(blackRightPoi
ntingSmallTriangle)); | |
387 | |
388 var failures = possiblyFlakyTestData[testName]; | |
389 | |
390 item.appendChild(document.createTextNode(testName + ' (failed ' + fa
ilures.length + ' out of ' + allBuilds.length + ' times)')); | |
391 | |
392 var container = document.createElement('div'); | |
393 item.appendChild(container); | |
394 | |
395 container.className = 'expandable'; | |
396 | |
397 disclosureTriangle.addEventListener('click', function() { | |
398 item.toggleStyleClass('expanded'); | |
399 if (!item.hasStyleClass('expanded')) | |
400 return; | |
401 | |
402 if (!container.firstChild) { | |
403 var failureList = document.createElement('ol'); | |
404 container.appendChild(failureList); | |
405 | |
406 failureList.className = 'flakiness-examples-list'; | |
407 | |
408 failureList.appendChildren(failures.map(function(historyItem
) { | |
409 var item = document.createElement('li'); | |
410 item.appendChild(self._domForBuildName(builder, historyI
tem.build)); | |
411 item.appendChild(document.createTextNode(': ')); | |
412 item.appendChild(self._domForFailureDiagnosis(builder, h
istoryItem.build, testName, historyItem.result)); | |
413 return item; | |
414 })); | |
415 | |
416 var failingBuildNames = failures.map(function(historyItem) {
return historyItem.build }); | |
417 var bugForm = new FlakyTestBugForm(builder, failingBuildName
s, testName, allBuilds.last(), allBuilds[0], allBuilds.length); | |
418 container.appendChild(self._domForNewAndExistingBugs(builder
, [testName], bugForm)); | |
419 } | |
420 }); | |
421 | |
422 return item; | |
423 })); | |
424 | |
425 return result; | |
426 }, | |
427 | |
428 _setTitle: function(title) { | |
429 document.title = title; | |
430 this._header.textContent = title; | |
431 }, | |
432 }; | |
OLD | NEW |