initial import
[vuplus_webkit] / Tools / BuildSlaveSupport / build.webkit.org-config / public_html / TestFailures / scripts / ViewController.js
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, stillFetchingData) {
64             if (self._navigationID !== navigationID) {
65                 // The user has navigated somewhere else. Stop loading data about 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, buildName, 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 exited 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, passingBuildName, failingTestNames);
106                     item.appendChild(self._domForNewAndExistingBugs(builder, failingTestNames, 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.localeCompare(b.tester.name) });
133             list.removeAllChildren();
134             latestBuildInfos.forEach(function(buildInfo) {
135                 var link = document.createElement('a');
136                 link.href = '#/' + buildInfo.tester.name;
137                 link.appendChild(document.createTextNode(buildInfo.tester.name));
138
139                 var item = document.createElement('li');
140                 item.appendChild(link);
141                 if (buildInfo.tooManyFailures)
142                     item.appendChild(document.createTextNode(' (too many failures/crashes/timeouts)'));
143                 else
144                     item.appendChild(document.createTextNode(' (' + buildInfo.failureCount + ' failing test' + (buildInfo.failureCount > 1 ? 's' : '') + ')'));
145                 list.appendChild(item);
146             });
147         }
148
149         var self = this;
150         this._buildbot.getTesters(function(testers) {
151             if (self._navigationID !== navigationID) {
152                 // The user has navigated somewhere else.
153                 return;
154             }
155             testers.forEach(function(tester) {
156                 tester.getMostRecentCompletedBuildNumber(function(buildNumber) {
157                     if (self._navigationID !== navigationID)
158                         return;
159                     if (buildNumber < 0)
160                         return;
161                     tester.getNumberOfFailingTests(buildNumber, function(failureCount, tooManyFailures) {
162                         if (self._navigationID !== navigationID)
163                             return;
164                         if (failureCount <= 0)
165                             return;
166                         latestBuildInfos.push({ tester: tester, failureCount: failureCount, tooManyFailures: tooManyFailures });
167                         updateList();
168                     });
169                 });
170             });
171         });
172     },
173
174     _domForRegressionRange: function(builder, failingBuildName, passingBuildName, failingTestNames) {
175         var result = document.createDocumentFragment();
176
177         var dlItems = [
178             [document.createTextNode('Failed'), this._domForBuildName(builder, failingBuildName)],
179         ];
180         if (passingBuildName)
181             dlItems.push([document.createTextNode('Passed'), this._domForBuildName(builder, passingBuildName)]);
182         result.appendChild(createDefinitionList(dlItems));
183
184         if (!passingBuildName)
185             return result;
186
187         var firstSuspectRevision = this._buildbot.parseBuildName(passingBuildName).revision + 1;
188         var lastSuspectRevision = this._buildbot.parseBuildName(failingBuildName).revision;
189
190         if (firstSuspectRevision === lastSuspectRevision)
191             return result;
192
193         var suspectsContainer = document.createElement('div');
194         result.appendChild(suspectsContainer);
195
196         var link = document.createElement('a');
197         result.appendChild(link);
198
199         link.href = trac.logURL('trunk', firstSuspectRevision, lastSuspectRevision, true);
200         link.appendChild(document.createTextNode('View regression range in Trac'));
201
202         suspectsContainer.appendChild(document.createTextNode('Searching for suspect revisions\u2026'));
203
204         // FIXME: Maybe some of this code should go in LayoutTestHistoryAnalyzer, or some other class?
205         var self = this;
206         trac.commitDataForRevisionRange('trunk', firstSuspectRevision, lastSuspectRevision, function(commits) {
207             var failingTestNamesWithoutExtensions = failingTestNames.map(removePathExtension);
208             var suspectCommits = commits.filter(function(commit) {
209                 return failingTestNamesWithoutExtensions.some(function(testName) {
210                     return commit.message.contains(testName);
211                 });
212             });
213
214             suspectsContainer.removeAllChildren();
215
216             if (!suspectCommits.length)
217                 return;
218
219             var title = 'Suspect revision' + (suspectCommits.length > 1 ? 's' : '') + ':';
220             suspectsContainer.appendChild(document.createTextNode(title));
221
222             var list = document.createElement('ul');
223             suspectsContainer.appendChild(list);
224             list.className = 'suspect-revisions-list';
225
226             function compareCommits(a, b) {
227                 return b.revision - a.revision;
228             }
229
230             list.appendChildren(sorted(suspectCommits, compareCommits).map(function(commit) {
231                 var item = document.createElement('li');
232                 var link = document.createElement('a');
233                 item.appendChild(link);
234
235                 link.href = trac.changesetURL(commit.revision);
236                 link.appendChild(document.createTextNode(commit.title))
237
238                 return item;
239             }));
240         });
241
242         return result;
243     },
244
245     _domForAuxiliaryUIElements: function() {
246         var aside = document.createElement('aside');
247         aside.appendChild(document.createTextNode('Something not working? Have an idea to improve this page? '));
248         var link = document.createElement('a');
249         aside.appendChild(link);
250
251         link.appendChild(document.createTextNode('File a bug!'));
252         var queryParameters = {
253             product: 'WebKit',
254             component: 'Tools / Tests',
255             version: '528+ (Nightly build)',
256             bug_file_loc: location.href,
257             cc: 'aroben@apple.com',
258             short_desc: 'TestFailures page needs more unicorns!',
259         };
260         link.href = addQueryParametersToURL(config.kBugzillaURL + '/enter_bug.cgi', queryParameters);
261         link.target = '_blank';
262
263         return aside;
264     },
265
266     _domForBuildName: function(builder, buildName) {
267         var parsed = this._buildbot.parseBuildName(buildName);
268
269         var sourceLink = document.createElement('a');
270         sourceLink.href = 'http://trac.webkit.org/changeset/' + parsed.revision;
271         sourceLink.appendChild(document.createTextNode('r' + parsed.revision));
272
273         var buildLink = document.createElement('a');
274         buildLink.href = builder.buildURL(parsed.buildNumber);
275         buildLink.appendChild(document.createTextNode(parsed.buildNumber));
276
277         var resultsLink = document.createElement('a');
278         resultsLink.href = builder.resultsPageURL(buildName);
279         resultsLink.appendChild(document.createTextNode('results.html'));
280
281         var result = document.createDocumentFragment();
282         result.appendChild(sourceLink);
283         result.appendChild(document.createTextNode(' ('));
284         result.appendChild(buildLink);
285         result.appendChild(document.createTextNode(') ('));
286         result.appendChild(resultsLink);
287         result.appendChild(document.createTextNode(')'));
288
289         return result;
290     },
291
292     _domForFailedTest: function(builder, buildName, testName, testResult) {
293         var result = document.createDocumentFragment();
294         result.appendChild(document.createTextNode(testName + ': '));
295         result.appendChild(this._domForFailureDiagnosis(builder, buildName, testName, testResult));
296         return result;
297     },
298
299     _domForFailureDiagnosis: function(builder, buildName, testName, testResult) {
300         var diagnosticInfo = builder.failureDiagnosisTextAndURL(buildName, testName, testResult);
301         if (!diagnosticInfo)
302             return document.createTextNode(testResult.failureType);
303
304         var textAndCrashingSymbol = document.createDocumentFragment();
305         textAndCrashingSymbol.appendChild(document.createTextNode(diagnosticInfo.text));
306         if (testResult.crashingSymbol) {
307             var code = document.createElement('code');
308             code.appendChild(document.createTextNode(testResult.crashingSymbol));
309             textAndCrashingSymbol.appendChild(document.createTextNode(' ('));
310             textAndCrashingSymbol.appendChild(code);
311             textAndCrashingSymbol.appendChild(document.createTextNode(')'));
312         }
313
314         if (!('url' in diagnosticInfo))
315             return textAndCrashingSymbol;
316
317         var link = document.createElement('a');
318         link.href = diagnosticInfo.url;
319         link.appendChild(textAndCrashingSymbol);
320         return link;
321     },
322
323     _domForNewAndExistingBugs: function(tester, failingTests, bugForm) {
324         var result = document.createDocumentFragment();
325
326         var container = document.createElement('p');
327         result.appendChild(container);
328
329         container.className = 'existing-and-new-bugs';
330
331         var bugsContainer = document.createElement('div');
332         container.appendChild(bugsContainer);
333
334         bugsContainer.appendChild(document.createTextNode('Searching for bugs related to ' + (failingTests.length > 1 ? 'these tests' : 'this test') + '\u2026'));
335
336         bugzilla.quickSearch('ALL ' + failingTests.join('|'), function(bugs) {
337             if (!bugs.length) {
338                 bugsContainer.parentNode.removeChild(bugsContainer);
339                 return;
340             }
341
342             while (bugsContainer.firstChild)
343                 bugsContainer.removeChild(bugsContainer.firstChild);
344
345             bugsContainer.appendChild(document.createTextNode('Existing bugs related to ' + (failingTests.length > 1 ? 'these tests' : 'this test') + ':'));
346
347             var list = document.createElement('ul');
348             bugsContainer.appendChild(list);
349
350             list.className = 'existing-bugs-list';
351
352             function bugToListItem(bug) {
353                 var link = document.createElement('a');
354                 link.href = bug.url;
355                 link.appendChild(document.createTextNode(bug.title));
356
357                 var item = document.createElement('li');
358                 item.appendChild(link);
359
360                 return item;
361             }
362
363             var openBugs = bugs.filter(function(bug) { return bugzilla.isOpenStatus(bug.status) });
364             var closedBugs = bugs.filter(function(bug) { return !bugzilla.isOpenStatus(bug.status) });
365
366             list.appendChildren(openBugs.map(bugToListItem));
367
368             if (!closedBugs.length)
369                 return;
370
371             var item = document.createElement('li');
372             list.appendChild(item);
373
374             item.appendChild(document.createTextNode('Closed bugs:'));
375
376             var closedList = document.createElement('ul');
377             item.appendChild(closedList);
378
379             closedList.appendChildren(closedBugs.map(bugToListItem));
380         });
381
382         var form = bugForm.domElement();
383         result.appendChild(form);
384
385         var link = document.createElement('a');
386         container.appendChild(link);
387
388         link.addEventListener('click', function(event) { form.submit(); event.preventDefault(); });
389         link.href = '#';
390         link.appendChild(document.createTextNode('File bug for ' + (failingTests.length > 1 ? 'these failures' : 'this failure')));
391
392         return result;
393     },
394
395     _domForPossiblyFlakyTests: function(builder, possiblyFlakyTestData, allBuilds) {
396         var result = document.createDocumentFragment();
397         var flakyTests = Object.keys(possiblyFlakyTestData);
398         if (!flakyTests.length)
399             return result;
400
401         var flakyHeader = document.createElement('h2');
402         result.appendChild(flakyHeader);
403         flakyHeader.appendChild(document.createTextNode('Possibly Flaky Tests'));
404
405         var flakyList = document.createElement('ol');
406         result.appendChild(flakyList);
407
408         flakyList.id = 'possibly-flaky-tests';
409
410         var self = this;
411         flakyList.appendChildren(sorted(flakyTests).map(function(testName) {
412             var item = document.createElement('li');
413
414             var disclosureTriangle = document.createElement('span');
415             item.appendChild(disclosureTriangle);
416
417             disclosureTriangle.className = 'disclosure-triangle';
418             const blackRightPointingSmallTriangle = '\u25b8';
419             disclosureTriangle.appendChild(document.createTextNode(blackRightPointingSmallTriangle));
420
421             var failures = possiblyFlakyTestData[testName];
422
423             item.appendChild(document.createTextNode(testName + ' (failed ' + failures.length + ' out of ' + allBuilds.length + ' times)'));
424
425             var container = document.createElement('div');
426             item.appendChild(container);
427
428             container.className = 'expandable';
429
430             disclosureTriangle.addEventListener('click', function() {
431                 item.toggleStyleClass('expanded');
432                 if (!item.hasStyleClass('expanded'))
433                     return;
434
435                 if (!container.firstChild) {
436                     var failureList = document.createElement('ol');
437                     container.appendChild(failureList);
438
439                     failureList.className = 'flakiness-examples-list';
440
441                     failureList.appendChildren(failures.map(function(historyItem) {
442                         var item = document.createElement('li');
443                         item.appendChild(self._domForBuildName(builder, historyItem.build));
444                         item.appendChild(document.createTextNode(': '));
445                         item.appendChild(self._domForFailureDiagnosis(builder, historyItem.build, testName, historyItem.result));
446                         return item;
447                     }));
448
449                     var failingBuildNames = failures.map(function(historyItem) { return historyItem.build });
450                     var bugForm = new FlakyTestBugForm(builder, failingBuildNames, testName, allBuilds.last(), allBuilds[0], allBuilds.length);
451                     container.appendChild(self._domForNewAndExistingBugs(builder, [testName], bugForm));
452                 }
453             });
454
455             return item;
456         }));
457
458         return result;
459     },
460
461     _setTitle: function(title) {
462         document.title = title;
463         this._header.textContent = title;
464     },
465 };