| 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 |