Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 <!DOCTYPE HTML> | 1 <!DOCTYPE HTML> |
| 2 <html> | 2 <html> |
| 3 | 3 |
| 4 <head> | 4 <head> |
| 5 <title>Webkit Layout Test History</title> | 5 <title>Webkit Layout Test History</title> |
| 6 <script> | 6 <script> |
| 7 var pageLoadStartTime = Date.now(); | 7 var pageLoadStartTime = Date.now(); |
| 8 </script> | 8 </script> |
| 9 <style> | 9 <style> |
| 10 body { | 10 body { |
| 11 font-family: arial; | 11 font-family: arial; |
| 12 font-size: 13px; | 12 font-size: 13px; |
| 13 } | 13 } |
| 14 h2 { | 14 h2 { |
| 15 font-size: 16px; | 15 font-size: 16px; |
| 16 margin-bottom: .25em; | 16 margin-bottom: .25em; |
| 17 } | 17 } |
| 18 form { | 18 #max-results-form { |
| 19 margin: 3px 0; | 19 display: inline; |
| 20 } | |
| 21 #max-results-input { | |
| 22 width: 30px; | |
| 23 } | |
| 24 #tests-form { | |
| 20 display: -webkit-box; | 25 display: -webkit-box; |
| 21 } | 26 } |
| 22 form > * { | 27 #tests-form > * { |
| 23 display: -webkit-box; | 28 display: -webkit-box; |
| 24 } | 29 } |
| 25 form > div { | 30 #tests-form > div { |
| 26 -webkit-box-flex: 0; | 31 -webkit-box-flex: 0; |
| 27 } | 32 } |
| 28 form > input { | 33 #tests-input { |
| 29 -webkit-box-flex: 1; | 34 -webkit-box-flex: 1; |
| 30 } | 35 } |
| 31 .test-link { | 36 .test-link { |
| 32 white-space: normal; | 37 white-space: normal; |
| 33 } | 38 } |
| 34 .test-link, .options-container { | 39 .test-link, .options-container { |
| 35 padding: 0 2px; | 40 padding: 0 2px; |
| 36 } | 41 } |
| 37 .test-table { | 42 .test-table { |
| 38 white-space: nowrap; | 43 white-space: nowrap; |
| (...skipping 126 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 165 * | 170 * |
| 166 * Also, each column in the dashboard is sortable. | 171 * Also, each column in the dashboard is sortable. |
| 167 * | 172 * |
| 168 * Currently, only webkit tests are supported, but adding other test types | 173 * Currently, only webkit tests are supported, but adding other test types |
| 169 * should just require the following steps: | 174 * should just require the following steps: |
| 170 * -generate results.json and expectations.json for these tests | 175 * -generate results.json and expectations.json for these tests |
| 171 * -copy them to the appropriate location | 176 * -copy them to the appropriate location |
| 172 * -add the builder name to the list of builders below. | 177 * -add the builder name to the list of builders below. |
| 173 */ | 178 */ |
| 174 | 179 |
| 175 // Default to layout_tests. | 180 // CONSTANTS |
| 176 var testType = 'layout_test_results'; | 181 var FORWARD = 'forward'; |
| 177 var params = window.location.search.substring(1).split('&'); | 182 var BACKWARD = 'backward'; |
| 178 for (var i = 0; i < params.length; i++) { | 183 var TEST_URL_BASE_PATH = |
| 179 var thisParam = params[i].split('='); | 184 'http://trac.webkit.org/projects/webkit/browser/trunk/'; |
| 180 if (thisParam[0] == 'testtype') { | 185 var BUILDERS_BASE_PATH = |
| 181 testType = thisParam[1]; | 186 'http://build.chromium.org/buildbot/waterfall/builders/'; |
| 182 break; | 187 var EXPECTATIONS_MAP = { |
| 183 } | 188 'T': 'TIMEOUT', |
| 184 } | 189 'C': 'CRASH', |
| 185 | 190 'P': 'PASS', |
| 186 // Map of builderName (the name shown in the waterfall) | 191 'F': 'TEXT FAIL', |
| 187 // to builderPath (the path used in the builder's URL) | 192 'S': 'SIMPLIFIED', |
| 188 // TODO(ojan): Make this switch based off of the testType. | 193 'I': 'IMAGE', |
| 189 var builders = { | 194 'O': 'OTHER', |
| 190 'Webkit': 'webkit-rel', | 195 'N': 'NO DATA' |
| 191 'Webkit (dbg)(1)': 'webkit-dbg-1', | 196 }; |
| 192 'Webkit (dbg)(2)': 'webkit-dbg-2', | 197 var PLATFORMS = {'MAC': 'MAC', 'LINUX': 'LINUX', 'WIN': 'WIN'}; |
| 193 'Webkit (dbg)(3)': 'webkit-dbg-3', | 198 var BUILD_TYPES = {'DEBUG': 'DBG', 'RELEASE': 'RELEASE'}; |
| 194 'Webkit Linux': 'webkit-rel-linux', | 199 |
| 195 'Webkit Linux (dbg)(1)': 'webkit-dbg-linux-1', | 200 // GLOBALS |
| 196 'Webkit Linux (dbg)(2)': 'webkit-dbg-linux-2', | 201 // The DUMMYVALUE gets shifted off the array in the first call to |
| 197 'Webkit Linux (dbg)(3)': 'webkit-dbg-linux-3', | 202 // generatePage. |
| 198 'Webkit Mac10.5': 'webkit-rel-mac5', | 203 var tableHeaders = ['DUMMYVALUE', 'bugs', 'modifiers', 'expectations', |
| 199 'Webkit Mac10.5 (dbg)(1)': 'webkit-dbg-mac5-1', | 204 'missing', 'extra', 'slowest run', |
| 200 'Webkit Mac10.5 (dbg)(2)': 'webkit-dbg-mac5-2', | 205 'flakiness (numbers are runtimes in seconds)']; |
| 201 'Webkit Mac10.5 (dbg)(3)': 'webkit-dbg-mac5-3' | 206 var perBuilderPlatformAndBuildType = {}; |
| 207 var perBuilderFailures = {}; | |
| 208 // Map of builder to arrays of tests that are listed in the expectations file | |
| 209 // but have for that builder. | |
| 210 var perBuilderWithExpectationsButNoFailures = {}; | |
| 211 | |
| 212 | |
| 213 // Generic utility functions. | |
| 214 function $(id) { | |
| 215 return document.getElementById(id); | |
| 216 } | |
| 217 | |
| 218 function stringContains(a, b) { | |
| 219 return a.indexOf(b) != -1; | |
| 220 } | |
| 221 | |
| 222 function isValidName(str) { | |
| 223 return str.match(/[A-Za-z0-9\-\_,]/); | |
| 224 } | |
| 225 | |
| 226 function trimString(str) { | |
| 227 return str.replace(/^\s+|\s+$/g, ''); | |
| 228 } | |
| 229 | |
| 230 function anyKeyInString(object, string) { | |
| 231 for (var key in object) { | |
| 232 if (stringContains(string, key)) | |
| 233 return true; | |
| 234 } | |
| 235 return false; | |
| 236 } | |
| 237 | |
| 238 function validateParameter(state, key, value, validateFn) { | |
| 239 if (validateFn()) { | |
| 240 state[key] = value; | |
| 241 } else { | |
| 242 console.log(key + ' value is not valid: ' + value); | |
| 243 } | |
| 244 } | |
| 245 | |
| 246 /** | |
| 247 * Parses a string (e.g. window.location.hash) and calls | |
| 248 * validValueHandler(key, value) for each key-value pair in the string. | |
| 249 */ | |
| 250 function parseParameters(parameterStr, validValueHandler) { | |
| 251 var params = parameterStr.split('&'); | |
| 252 for (var i = 0; i < params.length; i++) { | |
| 253 var thisParam = params[i].split('='); | |
| 254 if (thisParam.length != 2) { | |
| 255 console.log('Invalid query parameter: ' + params[i]); | |
| 256 continue; | |
| 257 } | |
| 258 | |
| 259 var key = thisParam[0]; | |
| 260 var value = decodeURIComponent(thisParam[1]); | |
| 261 if (!validValueHandler(key, value)) | |
| 262 console.log('Invalid key: ' + key + ' value: ' + value); | |
| 263 } | |
| 264 } | |
| 265 | |
| 266 function appendScript(path) { | |
| 267 var script = document.createElement('script'); | |
| 268 script.src = path; | |
| 269 document.getElementsByTagName('head')[0].appendChild(script); | |
| 270 } | |
| 271 | |
| 272 | |
| 273 // Parse query parameters. | |
| 274 var queryState = {'debug': false, 'testType': 'layout_test_results'}; | |
| 275 | |
| 276 function handleValidQueryParameter(key, value) { | |
| 277 switch (key) { | |
| 278 case 'testType': | |
| 279 validateParameter(queryState, key, value, | |
| 280 function() { return isValidName(value); }); | |
| 281 | |
| 282 return true; | |
| 283 | |
| 284 case 'debug': | |
| 285 queryState[key] = value == 'true'; | |
| 286 | |
| 287 return true; | |
| 288 | |
| 289 default: | |
| 290 return false; | |
| 291 } | |
| 292 } | |
| 293 | |
| 294 parseParameters(window.location.search.substring(1), | |
| 295 handleValidQueryParameter); | |
| 296 if (queryState['debug']) { | |
| 297 // In debug mode point to the results.json and expectations.json in the | |
| 298 // local tree. Useful for debugging changes to the python JSON generator. | |
| 299 var builders = {'DUMMY_BUILDER_NAME': ''}; | |
| 300 var builderBase = '../../Debug/'; | |
| 301 queryState['testType'] = 'layout-test-results'; | |
| 302 } else { | |
| 303 // Map of builderName (the name shown in the waterfall) | |
| 304 // to builderPath (the path used in the builder's URL) | |
| 305 // TODO(ojan): Make this switch based off of the testType. | |
| 306 var builders = { | |
| 307 'Webkit': 'webkit-rel', | |
| 308 'Webkit (dbg)(1)': 'webkit-dbg-1', | |
| 309 'Webkit (dbg)(2)': 'webkit-dbg-2', | |
| 310 'Webkit (dbg)(3)': 'webkit-dbg-3', | |
| 311 'Webkit Linux': 'webkit-rel-linux', | |
| 312 'Webkit Linux (dbg)(1)': 'webkit-dbg-linux-1', | |
| 313 'Webkit Linux (dbg)(2)': 'webkit-dbg-linux-2', | |
| 314 'Webkit Linux (dbg)(3)': 'webkit-dbg-linux-3', | |
| 315 'Webkit Mac10.5': 'webkit-rel-mac5', | |
| 316 'Webkit Mac10.5 (dbg)(1)': 'webkit-dbg-mac5-1', | |
| 317 'Webkit Mac10.5 (dbg)(2)': 'webkit-dbg-mac5-2', | |
| 318 'Webkit Mac10.5 (dbg)(3)': 'webkit-dbg-mac5-3' | |
| 319 }; | |
| 320 var builderBase = 'http://build.chromium.org/buildbot/'; | |
| 321 } | |
| 322 | |
| 323 // Parse hash parameters. | |
| 324 // Permalinkable state of the page. | |
| 325 var currentState = {}; | |
| 326 | |
| 327 var defaultStateValues = { | |
| 328 sortOrder: BACKWARD, | |
| 329 sortColumn: 'flakiness', | |
| 330 showWontFix: false, | |
| 331 showCorrectExpectations: false, | |
| 332 showFlaky: true, | |
| 333 maxResults: 200 | |
| 202 }; | 334 }; |
| 203 | 335 |
| 336 for (var builder in builders) { | |
| 337 defaultStateValues.builder = builder; | |
| 338 break; | |
| 339 } | |
| 340 | |
| 341 function fillDefaultStateValues() { | |
| 342 // tests has no states with default values. | |
| 343 if (currentState.tests) | |
| 344 return; | |
| 345 | |
| 346 for (var state in defaultStateValues) { | |
| 347 if (!(state in currentState)) | |
| 348 currentState[state] = defaultStateValues[state]; | |
| 349 } | |
| 350 } | |
| 351 | |
| 352 function handleValidHashParameter(key, value) { | |
| 353 switch(key) { | |
| 354 case 'tests': | |
| 355 validateParameter(currentState, key, value, | |
| 356 function() { return isValidName(value); }); | |
|
arv (Not doing code reviews)
2009/09/09 23:46:25
function() {
return isValidName(value);
}
| |
| 357 | |
| 358 return true; | |
| 359 | |
| 360 case 'builder': | |
| 361 validateParameter(currentState, key, value, | |
| 362 function() { return value in builders; }); | |
| 363 | |
| 364 return true; | |
| 365 | |
| 366 case 'sortColumn': | |
| 367 validateParameter(currentState, key, value, | |
| 368 function() { | |
| 369 for (var i = 0; i < tableHeaders.length; i++) { | |
| 370 if (value == getSortColumnFromTableHeader(tableHeaders[i])) | |
| 371 return true; | |
| 372 } | |
| 373 return value == 'test'; | |
| 374 }); | |
| 375 | |
| 376 return true; | |
| 377 | |
| 378 case 'sortOrder': | |
| 379 validateParameter(currentState, key, value, | |
| 380 function() { return value == FORWARD || value == BACKWARD; }); | |
| 381 | |
| 382 return true; | |
| 383 | |
| 384 case 'maxResults': | |
| 385 validateParameter(currentState, key, value, | |
| 386 function() { return value.match(/^\d+$/) }); | |
| 387 | |
| 388 return true; | |
| 389 | |
| 390 case 'showWontFix': | |
| 391 case 'showCorrectExpectations': | |
| 392 case 'showFlaky': | |
| 393 currentState[key] = value == 'true'; | |
| 394 | |
| 395 return true; | |
| 396 | |
| 397 default: | |
| 398 return false; | |
| 399 } | |
| 400 } | |
| 401 | |
| 402 // Keep the location around for detecting changes to hash arguments | |
| 403 // manually typed into the URL bar. | |
| 404 var oldLocation; | |
| 405 | |
| 406 function parseAllParameters() { | |
| 407 oldLocation = window.location.href; | |
| 408 parseParameters(window.location.search.substring(1), | |
| 409 handleValidQueryParameter); | |
| 410 parseParameters(window.location.hash.substring(1), | |
| 411 handleValidHashParameter); | |
| 412 fillDefaultStateValues(); | |
| 413 } | |
| 414 | |
| 415 parseAllParameters(); | |
| 416 | |
| 417 // Append JSON script elements. | |
| 204 var resultsByBuilder = {}; | 418 var resultsByBuilder = {}; |
| 205 // Maps test path to an array of {builder, testResults} objects. | 419 // Maps test path to an array of {builder, testResults} objects. |
| 206 var testToResultsMap = {}; | 420 var testToResultsMap = {}; |
| 207 var expectationsByTest = {}; | 421 var expectationsByTest = {}; |
| 208 function ADD_RESULTS(builds) { | 422 function ADD_RESULTS(builds) { |
| 209 for (var builderName in builds) { | 423 for (var builderName in builds) { |
| 210 resultsByBuilder[builderName] = builds[builderName]; | 424 if (builderName != 'version') |
| 425 resultsByBuilder[builderName] = builds[builderName]; | |
| 211 } | 426 } |
| 212 | 427 |
| 213 generatePage(); | 428 generatePage(); |
| 214 } | 429 } |
| 215 var BUILDER_BASE = 'http://build.chromium.org/buildbot/'; | 430 |
| 216 function getPathToBuilderResultsFile(builderName) { | 431 function getPathToBuilderResultsFile(builderName) { |
| 217 return BUILDER_BASE + testType + '/' + builders[builderName] + '/'; | 432 return builderBase + queryState['testType'] + '/' + |
| 218 } | 433 builders[builderName] + '/'; |
| 434 } | |
| 435 | |
| 219 for (var builderName in builders) { | 436 for (var builderName in builders) { |
| 220 var script = document.createElement('script'); | 437 appendScript(getPathToBuilderResultsFile(builderName) + 'results.json'); |
| 221 script.src = getPathToBuilderResultsFile(builderName) + 'results.json'; | 438 } |
| 222 document.getElementsByTagName('head')[0].appendChild(script); | 439 |
| 223 } | |
| 224 | |
| 225 var script = document.createElement('script'); | |
| 226 // Grab expectations file from any builder. | 440 // Grab expectations file from any builder. |
| 227 script.src = getPathToBuilderResultsFile(builderName) + 'expectations.json'; | 441 appendScript(getPathToBuilderResultsFile(builderName) + 'expectations.json'); |
| 228 document.getElementsByTagName('head')[0].appendChild(script); | |
| 229 | 442 |
| 230 var expectationsLoaded = false; | 443 var expectationsLoaded = false; |
| 231 function ADD_EXPECTATIONS(expectations) { | 444 function ADD_EXPECTATIONS(expectations) { |
| 232 expectationsLoaded = true; | 445 expectationsLoaded = true; |
| 233 expectationsByTest = expectations; | 446 expectationsByTest = expectations; |
| 234 generatePage(); | 447 generatePage(); |
| 235 } | 448 } |
| 236 | 449 |
| 237 // CONSTANTS | |
| 238 var FORWARD = 'forward'; | |
| 239 var BACKWARD = 'backward'; | |
| 240 var TEST_URL_BASE_PATH = | |
| 241 'http://trac.webkit.org/projects/webkit/browser/trunk/'; | |
| 242 var BUILDERS_BASE_PATH = | |
| 243 'http://build.chromium.org/buildbot/waterfall/builders/'; | |
| 244 var EXPECTATIONS_MAP = { | |
| 245 'T': 'TIMEOUT', | |
| 246 'C': 'CRASH', | |
| 247 'P': 'PASS', | |
| 248 'F': 'TEXT FAIL', | |
| 249 'S': 'SIMPLIFIED', | |
| 250 'I': 'IMAGE', | |
| 251 'O': 'OTHER', | |
| 252 'N': 'NO DATA' | |
| 253 }; | |
| 254 var PLATFORMS = {'MAC': 'MAC', 'LINUX': 'LINUX', 'WIN': 'WIN'}; | |
| 255 var BUILD_TYPES = {'DEBUG': 'DBG', 'RELEASE': 'RELEASE'}; | |
| 256 | |
| 257 // GLOBALS | |
| 258 // The DUMMYVALUE gets shifted off the array in the first call to | |
| 259 // generatePage. | |
| 260 var tableHeaders = ['DUMMYVALUE', 'bugs', 'modifiers', 'expectations', | |
| 261 'missing', 'extra', 'slowest run', | |
| 262 'flakiness (numbers are runtimes in seconds)']; | |
| 263 var perBuilderPlatformAndBuildType = {}; | |
| 264 var oldLocation; | |
| 265 var perBuilderFailures = {}; | |
| 266 // Map of builder to arrays of tests that are listed in the expectations file | |
| 267 // but have for that builder. | |
| 268 var perBuilderWithExpectationsButNoFailures = {}; | |
| 269 | 450 |
| 270 function createResultsObjectForTest(test) { | 451 function createResultsObjectForTest(test) { |
| 271 return { | 452 return { |
| 272 test: test, | 453 test: test, |
| 273 // HTML for display of the results in the flakiness column | 454 // HTML for display of the results in the flakiness column |
| 274 html: '', | 455 html: '', |
| 275 flips: 0, | 456 flips: 0, |
| 276 slowestTime: 0, | 457 slowestTime: 0, |
| 277 meetsExpectations: true, | 458 meetsExpectations: true, |
| 278 isWontFix: false, | 459 isWontFix: false, |
| (...skipping 11 matching lines...) Expand all Loading... | |
| 290 }; | 471 }; |
| 291 } | 472 } |
| 292 | 473 |
| 293 function getMatchingElement(stringToMatch, elementsMap) { | 474 function getMatchingElement(stringToMatch, elementsMap) { |
| 294 for (var element in elementsMap) { | 475 for (var element in elementsMap) { |
| 295 if (stringContains(stringToMatch, elementsMap[element])) | 476 if (stringContains(stringToMatch, elementsMap[element])) |
| 296 return element; | 477 return element; |
| 297 } | 478 } |
| 298 } | 479 } |
| 299 | 480 |
| 300 function $(id) { | |
| 301 return document.getElementById(id); | |
| 302 } | |
| 303 | |
| 304 function stringContains(a, b) { | |
| 305 return a.indexOf(b) != -1; | |
| 306 } | |
| 307 | |
| 308 function trimString(str) { | |
| 309 return str.replace(/^\s+|\s+$/g, ''); | |
| 310 } | |
| 311 | |
| 312 function anyKeyInString(object, string) { | |
| 313 for (var key in object) { | |
| 314 if (stringContains(string, key)) | |
| 315 return true; | |
| 316 } | |
| 317 return false; | |
| 318 } | |
| 319 | |
| 320 /** | 481 /** |
| 321 * Returns whether the given string of modifiers applies to the platform and | 482 * Returns whether the given string of modifiers applies to the platform and |
| 322 * build type of the given builder. | 483 * build type of the given builder. |
| 323 */ | 484 */ |
| 324 function hasPlatformAndBuildType(builderName, modifiers) { | 485 function hasPlatformAndBuildType(builderName, modifiers) { |
| 325 var platformAndBuildType = getPlatFormAndBuildType(builderName); | 486 var platformAndBuildType = getPlatFormAndBuildType(builderName); |
| 326 var hasThisPlatform = stringContains(modifiers, | 487 var hasThisPlatform = stringContains(modifiers, |
| 327 platformAndBuildType.platform); | 488 platformAndBuildType.platform); |
| 328 var hasThisBuildType = stringContains(modifiers, | 489 var hasThisBuildType = stringContains(modifiers, |
| 329 platformAndBuildType.buildType); | 490 platformAndBuildType.buildType); |
| (...skipping 248 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 578 } | 739 } |
| 579 | 740 |
| 580 resultsForTest.bugsHTML += | 741 resultsForTest.bugsHTML += |
| 581 htmlArrays.bugs.join('<div class=separator></div>'); | 742 htmlArrays.bugs.join('<div class=separator></div>'); |
| 582 resultsForTest.expectationsHTML += | 743 resultsForTest.expectationsHTML += |
| 583 htmlArrays.expectations.join('<div class=separator></div>'); | 744 htmlArrays.expectations.join('<div class=separator></div>'); |
| 584 resultsForTest.modifiersHTML += | 745 resultsForTest.modifiersHTML += |
| 585 htmlArrays.modifiers.join('<div class=separator></div>'); | 746 htmlArrays.modifiers.join('<div class=separator></div>'); |
| 586 } | 747 } |
| 587 | 748 |
| 588 var rawResults = resultsByBuilder[builderName].tests[test].results; | 749 var rawTest = resultsByBuilder[builderName].tests[test]; |
| 750 resultsForTest.rawTimes = rawTest.times; | |
| 751 var rawResults = rawTest.results; | |
| 589 resultsForTest.rawResults = rawResults; | 752 resultsForTest.rawResults = rawResults; |
| 590 var results = rawResults.split(''); | 753 resultsForTest.flips = rawResults.length - 1; |
| 591 | 754 |
| 592 var unexpectedExpectations = []; | 755 var unexpectedExpectations = []; |
| 593 var resultsMap = {} | 756 var resultsMap = {} |
| 594 for (var i = 0; i < results.length - 1; i++) { | 757 for (var i = 0; i < rawResults.length; i++) { |
| 595 if (results[i] != results[i + 1]) | 758 var expectation = getExpectationsFileStringForResult(rawResults[i][1]); |
| 596 resultsForTest.flips++; | |
| 597 | |
| 598 var expectation = getExpectationsFileStringForResult(results[i]); | |
| 599 resultsMap[expectation] = true; | 759 resultsMap[expectation] = true; |
| 600 } | 760 } |
| 601 | 761 |
| 602 var expectationsArray = resultsForTest.expectations ? | 762 var expectationsArray = resultsForTest.expectations ? |
| 603 resultsForTest.expectations.split(' ') : []; | 763 resultsForTest.expectations.split(' ') : []; |
| 604 var extraExpectations = expectationsArray.filter( | 764 var extraExpectations = expectationsArray.filter( |
| 605 function(element) { | 765 function(element) { |
| 606 return element && !resultsMap[element] && | 766 return element && !resultsMap[element] && |
| 607 !stringContains(element, 'BUG'); | 767 !stringContains(element, 'BUG'); |
| 608 }); | 768 }); |
| 609 | 769 |
| 610 var missingExpectations = []; | 770 var missingExpectations = []; |
| 611 for (var result in resultsMap) { | 771 for (var result in resultsMap) { |
| 612 var hasExpectation = false; | 772 var hasExpectation = false; |
| 613 for (var i = 0; i < expectationsArray.length; i++) { | 773 for (var i = 0; i < expectationsArray.length; i++) { |
| 614 if (result == expectationsArray[i]) | 774 if (result == expectationsArray[i]) |
| 615 hasExpectation = true; | 775 hasExpectation = true; |
| 616 } | 776 } |
| 617 if (!hasExpectation) | 777 if (!hasExpectation) |
| 618 missingExpectations.push(result); | 778 missingExpectations.push(result); |
| 619 } | 779 } |
| 620 | 780 |
| 621 // TODO(ojan): Make this detect the case of a test that has NODATA, | 781 var times = resultsByBuilder[builderName].tests[test].times; |
| 622 // then fails for a few runs, then passes for the rest. We should | 782 for (var i = 0; i < times.length; i++) { |
| 623 // consider that as meetsExpectations since every new test will have | 783 resultsForTest.slowestTime = Math.max(resultsForTest.slowestTime, |
| 624 // that pattern. | 784 times[i][1]); |
| 785 } | |
| 786 | |
| 787 if (resultsForTest.slowestTime && | |
| 788 (!resultsForTest.expectations || | |
| 789 !stringContains(resultsForTest.expectations, 'TIMEOUT')) && | |
| 790 (!resultsForTest.modifiers || | |
| 791 !stringContains(resultsForTest.modifiers, 'SLOW'))) { | |
| 792 missingExpectations.push('SLOW'); | |
| 793 } | |
| 794 | |
| 625 resultsForTest.meetsExpectations = | 795 resultsForTest.meetsExpectations = |
| 626 !missingExpectations.length && !extraExpectations.length; | 796 !missingExpectations.length && !extraExpectations.length; |
| 627 resultsForTest.missing = missingExpectations.sort().join(' '); | 797 resultsForTest.missing = missingExpectations.sort().join(' '); |
| 628 resultsForTest.extra = extraExpectations.sort().join(' '); | 798 resultsForTest.extra = extraExpectations.sort().join(' '); |
| 629 | 799 |
| 630 var times = resultsByBuilder[builderName].tests[test].times; | |
| 631 resultsForTest.slowestTime = Math.max.apply(null, times) | |
| 632 | |
| 633 resultsForTest.html = getHtmlForTestResults(builderName, test); | |
| 634 | |
| 635 failures.push(resultsForTest); | 800 failures.push(resultsForTest); |
| 636 | 801 |
| 637 if (!testToResultsMap[test]) | 802 if (!testToResultsMap[test]) |
| 638 testToResultsMap[test] = []; | 803 testToResultsMap[test] = []; |
| 639 testToResultsMap[test].push( | 804 testToResultsMap[test].push( |
| 640 {builder: builderName, results: resultsForTest}); | 805 {builder: builderName, results: resultsForTest}); |
| 641 } | 806 } |
| 642 | 807 |
| 643 perBuilderFailures[builderName] = failures; | 808 perBuilderFailures[builderName] = failures; |
| 644 logTime('processTestRunsForBuilder: ' + builderName, start); | 809 logTime('processTestRunsForBuilder: ' + builderName, start); |
| (...skipping 20 matching lines...) Expand all Loading... | |
| 665 * are internal google bugs. | 830 * are internal google bugs. |
| 666 */ | 831 */ |
| 667 function getHtmlForBugs(bugs) { | 832 function getHtmlForBugs(bugs) { |
| 668 bugs = bugs.replace(/BUG(\d{4})(\ |$)/g, externalBugReplaceValue); | 833 bugs = bugs.replace(/BUG(\d{4})(\ |$)/g, externalBugReplaceValue); |
| 669 bugs = bugs.replace(/BUG(\d{5})(\ |$)/g, externalBugReplaceValue); | 834 bugs = bugs.replace(/BUG(\d{5})(\ |$)/g, externalBugReplaceValue); |
| 670 bugs = bugs.replace(/BUG(\d{6})(\ |$)/g, internalBugReplaceValue); | 835 bugs = bugs.replace(/BUG(\d{6})(\ |$)/g, internalBugReplaceValue); |
| 671 bugs = bugs.replace(/BUG(\d{7})(\ |$)/g, internalBugReplaceValue); | 836 bugs = bugs.replace(/BUG(\d{7})(\ |$)/g, internalBugReplaceValue); |
| 672 return bugs; | 837 return bugs; |
| 673 } | 838 } |
| 674 | 839 |
| 675 function didTestPassAllRuns(builderName, testPath) { | |
| 676 var numBuilds = resultsByBuilder[builderName].buildNumbers.length; | |
| 677 var passingResults = Array(numBuilds + 1).join('P'); | |
| 678 var results = resultsByBuilder[builderName].tests[testPath].results; | |
| 679 return results == passingResults; | |
| 680 } | |
| 681 | |
| 682 function loadBuilderPageForBuildNumber(builderName, buildNumber) { | 840 function loadBuilderPageForBuildNumber(builderName, buildNumber) { |
| 683 window.open(BUILDERS_BASE_PATH + builderName + '/builds/' + buildNumber); | 841 window.open(BUILDERS_BASE_PATH + builderName + '/builds/' + buildNumber); |
| 684 } | 842 } |
| 685 | 843 |
| 686 function getHtmlForTestResults(builderName, testPath) { | 844 function getHtmlForTestResults(test, builder) { |
| 687 var html = ''; | 845 var html = ''; |
| 688 var test = resultsByBuilder[builderName].tests[testPath]; | 846 var results = test.rawResults.concat(); |
| 689 var results = test.results.split(''); | 847 var times = test.rawTimes.concat(); |
| 690 var times = test.times; | 848 var buildNumbers = resultsByBuilder[builder].buildNumbers; |
| 691 var buildNumbers = resultsByBuilder[builderName].buildNumbers; | 849 |
| 692 for (var i = 0; i < results.length; i++) { | 850 var indexToReplaceCurrentResult = -1; |
| 851 var indexToReplaceCurrentTime = -1; | |
| 852 var currentResultArray, currentTimeArray, currentResult, innerHTML; | |
| 853 for (var i = 0; | |
| 854 i < buildNumbers.length && i < currentState.maxResults; | |
| 855 i++) { | |
| 856 if (i > indexToReplaceCurrentResult) { | |
| 857 currentResultArray = results.shift(); | |
| 858 if (currentResultArray) { | |
| 859 currentResult = currentResultArray[1]; | |
| 860 indexToReplaceCurrentResult += currentResultArray[0]; | |
| 861 } else { | |
| 862 currentResult = 'N'; | |
| 863 indexToReplaceCurrentResult += buildNumbers.length; | |
| 864 } | |
| 865 } | |
| 866 | |
| 867 if (i > indexToReplaceCurrentTime) { | |
| 868 currentTimeArray = times.shift(); | |
| 869 var currentTime = 0; | |
| 870 if (currentResultArray) { | |
| 871 currentTime = currentTimeArray[1]; | |
| 872 indexToReplaceCurrentTime += currentTimeArray[0]; | |
| 873 } else { | |
| 874 indexToReplaceCurrentTime += buildNumbers.length; | |
| 875 } | |
| 876 innerHTML = currentTime || ' '; | |
| 877 } | |
| 878 | |
| 693 var buildNumber = buildNumbers[i]; | 879 var buildNumber = buildNumbers[i]; |
| 694 var innerHTML = times[i] > 0 ? times[i] : ' '; | |
| 695 html += '<td title="Build:' + buildNumber + '" class="results ' + | 880 html += '<td title="Build:' + buildNumber + '" class="results ' + |
| 696 results[i] + '" onclick=\'loadBuilderPageForBuildNumber("' + | 881 currentResult + '" onclick=\'loadBuilderPageForBuildNumber("' + |
| 697 builderName + '","' + buildNumber + '")\'>' + innerHTML + '</td>'; | 882 builder + '","' + buildNumber + '")\'>' + innerHTML + '</td>'; |
| 698 } | 883 } |
| 699 return html; | 884 return html; |
| 700 } | 885 } |
| 701 | 886 |
| 702 function getHTMLForTestsWithExpectationsButNoFailures(builder) { | 887 function getHTMLForTestsWithExpectationsButNoFailures(builder) { |
| 703 var tests = perBuilderWithExpectationsButNoFailures[builder]; | 888 var tests = perBuilderWithExpectationsButNoFailures[builder]; |
| 704 if (!tests.length) | 889 if (!tests.length) |
| 705 return ''; | 890 return ''; |
| 706 | 891 |
| 707 var buildInfo = getPlatFormAndBuildType(builder); | 892 var buildInfo = getPlatFormAndBuildType(builder); |
| 708 return '<h2>Have expectations for ' + buildInfo.platform + '-' + | 893 return '<h2>Have expectations for ' + buildInfo.platform + '-' + |
| 709 buildInfo.buildType + ' but have not failed in last ' + | 894 buildInfo.buildType + ' but have not failed in last ' + |
| 710 resultsByBuilder[builderName].buildNumbers.length + | 895 resultsByBuilder[builderName].buildNumbers.length + |
| 711 ' runs.</h2><div id="passing-tests"><div>' + | 896 ' runs.</h2><div id="passing-tests"><div>' + |
| 712 tests.join('</div><div>') + '</div></div>'; | 897 tests.join('</div><div>') + '</div></div>'; |
| 713 } | 898 } |
| 714 | 899 |
| 715 /** | 900 /** |
| 716 * Returns true if a test should be considered flaky. Uses heuristics to | 901 * Returns true if a test should be considered flaky. Uses heuristics to |
| 717 * avoid common non-flaky cases. | 902 * avoid common non-flaky cases. |
| 718 */ | 903 */ |
| 719 function isTestFlaky(testResult) { | 904 function isTestFlaky(testResult) { |
| 720 return testResult.flips > 1 && !isFixedNewTest(testResult); | 905 return testResult.flips > 1 && !isFixedTest(testResult); |
| 721 } | 906 } |
| 722 | 907 |
| 723 /** | 908 /** |
| 724 * Returns whether this tests results match the heuristic for new tests that | 909 * Returns whether this tests results match the heuristic for new tests that |
| 725 * have been fixed. Specifically, a new test that fails a couple | 910 * have been fixed. Specifically, a new test that fails a couple |
| 726 * times and then passes from then on would have results like PPPPFFFFNNNNN. | 911 * times and then passes from then on would have results like PPPPFFFFNNNNN. |
| 727 * Where that middle part can be a series of F's, S's or I's for the | 912 * Where that middle part can be a series of F's, S's or I's for the |
| 728 * different types of failures. | 913 * different types of failures. |
| 729 */ | 914 */ |
| 730 function isFixedNewTest(testResult) { | 915 function isFixedTest(testResult) { |
| 731 if (testResult.isFixedNewTest === undefined) { | 916 if (testResult.isFixedTest === undefined) { |
| 732 testResult.isFixedNewTest = | 917 var results = testResult.rawResults; |
| 733 testResult.rawResults.match(/^P+(S+|F+|I+)N+$/); | 918 var isFixedTest = results[0][1] == 'P'; |
| 919 if (isFixedTest && results.length > 1) { | |
| 920 var secondResult = results[1][1]; | |
| 921 isFixedTest = secondResult == 'S' || secondResult == 'F' || | |
| 922 secondResult == 'I'; | |
| 923 } | |
| 924 if (isFixedTest && results.length > 2) { | |
| 925 isFixedTest = results.length == 3 && results[2][1] == 'N'; | |
| 926 } | |
| 927 testResult.isFixedTest = isFixedTest; | |
| 734 } | 928 } |
| 735 return testResult.isFixedNewTest; | 929 return testResult.isFixedTest; |
| 736 } | 930 } |
| 737 | 931 |
| 738 /** | 932 /** |
| 739 * Returns whether we should exclude test results from the test table. | 933 * Returns whether we should exclude test results from the test table. |
| 740 * Note that we never want to exclude tests when we're in the individual | 934 * Note that we never want to exclude tests when we're in the individual |
| 741 * tests view of the dashboard since the user is explicitly listing tests | 935 * tests view of the dashboard since the user is explicitly listing tests |
| 742 * to view. | 936 * to view. |
| 743 */ | 937 */ |
| 744 function shouldHideTest(testResult) { | 938 function shouldHideTest(testResult) { |
| 745 if (currentState.tests) | 939 if (currentState.tests) |
| 746 return false; | 940 return false; |
| 747 | 941 |
| 748 if (testResult.isWontFix && !currentState.showWontFix) | 942 if (testResult.isWontFix && !currentState.showWontFix) |
| 749 return true; | 943 return true; |
| 750 | 944 |
| 751 if ((testResult.meetsExpectations || isFixedNewTest(testResult)) && | 945 if ((testResult.meetsExpectations || isFixedTest(testResult)) && |
| 752 !currentState.showCorrectExpectations) { | 946 !currentState.showCorrectExpectations) { |
| 753 // Only hide flaky tests that match their expectations if showFlaky | 947 // Only hide flaky tests that match their expectations if showFlaky |
| 754 // is false. | 948 // is false. |
| 755 return !currentState.showFlaky || !isTestFlaky(testResult); | 949 return !currentState.showFlaky || !isTestFlaky(testResult); |
| 756 } | 950 } |
| 757 | 951 |
| 758 return !currentState.showFlaky && isTestFlaky(testResult); | 952 return !currentState.showFlaky && isTestFlaky(testResult); |
| 759 } | 953 } |
| 760 | 954 |
| 761 function getHTMLForSingleTestRow(test, opt_builder) { | 955 function getHTMLForSingleTestRow(test, builder, opt_isCrossBuilderView) { |
| 762 if (shouldHideTest(test)) { | 956 if (shouldHideTest(test)) { |
| 763 // The innerHTML call is considerably faster if we exclude the rows for | 957 // The innerHTML call is considerably faster if we exclude the rows for |
| 764 // items we're not showing than if we hide them using display:none. | 958 // items we're not showing than if we hide them using display:none. |
| 765 return ''; | 959 return ''; |
| 766 } | 960 } |
| 767 | 961 |
| 768 // If opt_builder is provided, we're just viewing a single test | 962 // If opt_isCrossBuilderView is true, we're just viewing a single test |
| 769 // with results for many builders, so the first column is builder names | 963 // with results for many builders, so the first column is builder names |
| 770 // instead of test paths. | 964 // instead of test paths. |
| 771 var testCellHTML = opt_builder ? opt_builder : | 965 var testCellHTML = opt_isCrossBuilderView ? builder : |
| 772 '<span class="link" onclick="setState(\'tests\', \'' + test.test + | 966 '<span class="link" onclick="setState(\'tests\', \'' + test.test + |
| 773 '\');return false;">' + test.test + '</span>'; | 967 '\');return false;">' + test.test + '</span>'; |
| 774 | 968 |
| 775 return '<tr class="' + | 969 return '<tr class="' + |
| 776 (test.meetsExpectations ? '' : 'wrong-expectations') + | 970 (test.meetsExpectations ? '' : 'wrong-expectations') + |
| 777 // TODO(ojan): If a test is a chrome/ or a pending/ test, point to | 971 // TODO(ojan): If a test is a chrome/ or a pending/ test, point to |
| 778 // src.chromium.org instead of trac.webkit.org. | 972 // src.chromium.org instead of trac.webkit.org. |
| 779 '"><td class=test-link>' + testCellHTML + | 973 '"><td class=test-link>' + testCellHTML + |
| 780 '</td><td class=options-container>' + test.bugsHTML + | 974 '</td><td class=options-container>' + test.bugsHTML + |
| 781 '</td><td class=options-container>' + test.modifiersHTML + | 975 '</td><td class=options-container>' + test.modifiersHTML + |
| 782 '</td><td class=options-container>' + test.expectationsHTML + | 976 '</td><td class=options-container>' + test.expectationsHTML + |
| 783 '</td><td>' + test.missing + | 977 '</td><td>' + test.missing + |
| 784 '</td><td>' + test.extra + | 978 '</td><td>' + test.extra + |
| 785 '</td><td>' + (test.slowestTime ? test.slowestTime + 's' : '') + | 979 '</td><td>' + (test.slowestTime ? test.slowestTime + 's' : '') + |
| 786 '</td>' + | 980 '</td>' + getHtmlForTestResults(test, builder) + '</tr>'; |
| 787 test.html + | |
| 788 '</tr>'; | |
| 789 } | 981 } |
| 790 | 982 |
| 791 function getSortColumnFromTableHeader(headerText) { | 983 function getSortColumnFromTableHeader(headerText) { |
| 792 return headerText.split(' ', 1)[0]; | 984 return headerText.split(' ', 1)[0]; |
| 793 } | 985 } |
| 794 | 986 |
| 795 function getHTMLForTestTable(rowsHTML) { | 987 function getHTMLForTestTable(rowsHTML) { |
| 796 var html = '<table class=test-table><thead><tr>'; | 988 var html = '<table class=test-table><thead><tr>'; |
| 797 for (var i = 0; i < tableHeaders.length; i++) { | 989 for (var i = 0; i < tableHeaders.length; i++) { |
| 798 // Use the first word of the header title as the sortkey | 990 // Use the first word of the header title as the sortkey |
| (...skipping 84 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 883 resultsProperty = 'slowestTime'; | 1075 resultsProperty = 'slowestTime'; |
| 884 } else { | 1076 } else { |
| 885 sortFunctionGetter = getAlphanumericCompare; | 1077 sortFunctionGetter = getAlphanumericCompare; |
| 886 resultsProperty = column; | 1078 resultsProperty = column; |
| 887 } | 1079 } |
| 888 | 1080 |
| 889 tests.sort(sortFunctionGetter(resultsProperty, order == BACKWARD)); | 1081 tests.sort(sortFunctionGetter(resultsProperty, order == BACKWARD)); |
| 890 } | 1082 } |
| 891 | 1083 |
| 892 function generatePage() { | 1084 function generatePage() { |
| 893 parseCurrentLocation(); | |
| 894 | |
| 895 // Only continue if all the JSON files have loaded. | 1085 // Only continue if all the JSON files have loaded. |
| 896 if (!expectationsLoaded) | 1086 if (!expectationsLoaded) |
| 897 return; | 1087 return; |
| 898 | 1088 |
| 899 for (var build in builders) { | 1089 for (var build in builders) { |
| 900 if (!resultsByBuilder[build]) | 1090 if (!resultsByBuilder[build]) |
| 901 return; | 1091 return; |
| 902 } | 1092 } |
| 903 | 1093 |
| 904 tableHeaders.shift(); | 1094 tableHeaders.shift(); |
| (...skipping 18 matching lines...) Expand all Loading... | |
| 923 'BUILDER DOES NOT RUN THAT TEST OR ALL RUNS OF THE TEST PASSED.</b>'; | 1113 'BUILDER DOES NOT RUN THAT TEST OR ALL RUNS OF THE TEST PASSED.</b>'; |
| 924 | 1114 |
| 925 for (var i = 0; i < tests.length; i++) { | 1115 for (var i = 0; i < tests.length; i++) { |
| 926 html += '<h2>' + tests[i] + '</h2>'; | 1116 html += '<h2>' + tests[i] + '</h2>'; |
| 927 | 1117 |
| 928 var testResults = testToResultsMap[tests[i]]; | 1118 var testResults = testToResultsMap[tests[i]]; |
| 929 if (testResults && testResults.length) { | 1119 if (testResults && testResults.length) { |
| 930 var tableRowsHTML = ''; | 1120 var tableRowsHTML = ''; |
| 931 for (var j = 0; j < testResults.length; j++) { | 1121 for (var j = 0; j < testResults.length; j++) { |
| 932 tableRowsHTML += getHTMLForSingleTestRow(testResults[j].results, | 1122 tableRowsHTML += getHTMLForSingleTestRow(testResults[j].results, |
| 933 testResults[j].builder); | 1123 testResults[j].builder, true); |
| 934 } | 1124 } |
| 935 html += getHTMLForTestTable(tableRowsHTML); | 1125 html += getHTMLForTestTable(tableRowsHTML); |
| 936 } else { | 1126 } else { |
| 937 html +='<div class="not-found">Test not found. Either it does not ' + | 1127 html +='<div class="not-found">Test not found. Either it does not ' + |
| 938 'exist or it passes on all platforms.</div>'; | 1128 'exist or it passes on all platforms.</div>'; |
| 939 } | 1129 } |
| 940 } | 1130 } |
| 941 setFullPageHTML(html); | 1131 setFullPageHTML(html); |
| 942 | 1132 |
| 943 $('tests-input').value = currentState.tests; | 1133 $('tests-input').value = currentState.tests; |
| 944 } | 1134 } |
| 945 | 1135 |
| 946 function getHTMLForNavBar(opt_builderName) { | 1136 function getHTMLForNavBar(opt_builderName) { |
| 947 var html = '<div id=builders>'; | 1137 var html = '<div id=builders>'; |
| 948 for (var builder in builders) { | 1138 for (var builder in builders) { |
| 949 var className = builder == opt_builderName ? 'current-builder' : 'link'; | 1139 var className = builder == opt_builderName ? 'current-builder' : 'link'; |
| 950 html += '<span class=' + className + | 1140 html += '<span class=' + className + |
| 951 ' onclick=\'setState("builder", "' + builder + '")\'>' + | 1141 ' onclick=\'setState("builder", "' + builder + '")\'>' + |
| 952 builder + '</span>'; | 1142 builder + '</span>'; |
| 953 } | 1143 } |
| 954 html += '</div>' + | 1144 html += '</div>' + |
| 955 '<form onsubmit="setState(\'tests\', tests.value);return false;">' + | 1145 '<form id=tests-form ' + |
| 1146 'onsubmit="setState(\'tests\', tests.value);return false;">' + | |
| 956 '<div>Show tests on all platforms (slow): </div><input name=tests ' + | 1147 '<div>Show tests on all platforms (slow): </div><input name=tests ' + |
| 957 'placeholder="LayoutTests/foo/bar.html,LayoutTests/foo/baz.html" ' + | 1148 'placeholder="LayoutTests/foo/bar.html,LayoutTests/foo/baz.html" ' + |
| 958 'id=tests-input></form><div id="loading-ui">LOADING...</div>' + | 1149 'id=tests-input></form><div id="loading-ui">LOADING...</div>' + |
| 959 '<div id=legend>'; | 1150 '<div id=legend>'; |
| 960 | 1151 |
| 961 for (var expectation in EXPECTATIONS_MAP) { | 1152 for (var expectation in EXPECTATIONS_MAP) { |
| 962 html += '<div class=' + expectation + '>' + | 1153 html += '<div class=' + expectation + '>' + |
| 963 EXPECTATIONS_MAP[expectation] + '</div>'; | 1154 EXPECTATIONS_MAP[expectation] + '</div>'; |
| 964 } | 1155 } |
| 965 return html + '<div class=wrong-expectations>WRONG EXPECTATIONS</div>' + | 1156 return html + '<div class=wrong-expectations>WRONG EXPECTATIONS</div>' + |
| 966 '</div>'; | 1157 '</div>'; |
| 967 } | 1158 } |
| 968 | 1159 |
| 969 function getLinkHTMLToToggleState(key, linkText) { | 1160 function getLinkHTMLToToggleState(key, linkText) { |
| 970 var isTrue = currentState[key]; | 1161 var isTrue = currentState[key]; |
| 971 return '<span class=link onclick="setState(\'' + key + '\', \'' + !isTrue + | 1162 return '<span class=link onclick="setState(\'' + key + '\', ' + !isTrue + |
| 972 '\')">' + (isTrue ? 'Hide' : 'Show') + ' ' + linkText + '</span> | '; | 1163 ')">' + (isTrue ? 'Hide' : 'Show') + ' ' + linkText + '</span> | '; |
| 973 } | 1164 } |
| 974 | 1165 |
| 975 function generatePageForBuilder(builderName) { | 1166 function generatePageForBuilder(builderName) { |
| 976 processTestRunsForBuilder(builderName); | 1167 processTestRunsForBuilder(builderName); |
| 977 | 1168 |
| 978 var tableRowsHTML = ''; | 1169 var tableRowsHTML = ''; |
| 979 var results = perBuilderFailures[builderName]; | 1170 var results = perBuilderFailures[builderName]; |
| 980 sortTests(results, currentState.sortColumn, currentState.sortOrder); | 1171 sortTests(results, currentState.sortColumn, currentState.sortOrder); |
| 981 for (var i = 0; i < results.length; i++) { | 1172 for (var i = 0; i < results.length; i++) { |
| 982 tableRowsHTML += getHTMLForSingleTestRow(results[i]); | 1173 tableRowsHTML += getHTMLForSingleTestRow(results[i], builderName); |
| 983 } | 1174 } |
| 984 | 1175 |
| 1176 var testsHTML = tableRowsHTML ? getHTMLForTestTable(tableRowsHTML) : | |
| 1177 '<div>No tests. Try showing tests with correct expectations.</div>'; | |
| 1178 | |
| 985 var html = getHTMLForNavBar(builderName) + | 1179 var html = getHTMLForNavBar(builderName) + |
| 986 getHTMLForTestsWithExpectationsButNoFailures(builderName) + | 1180 getHTMLForTestsWithExpectationsButNoFailures(builderName) + |
| 987 '<h2>Failing tests</h2><div>' + | 1181 '<h2>Failing tests</h2><div>' + |
| 988 getLinkHTMLToToggleState('showWontFix', 'WONTFIX tests') + | 1182 getLinkHTMLToToggleState('showWontFix', 'WONTFIX tests') + |
| 989 getLinkHTMLToToggleState('showCorrectExpectations', | 1183 getLinkHTMLToToggleState('showCorrectExpectations', |
| 990 'tests with correct expectations') + | 1184 'tests with correct expectations') + |
| 991 getLinkHTMLToToggleState('showFlaky', 'flaky tests') + | 1185 getLinkHTMLToToggleState('showFlaky', 'flaky tests') + |
| 1186 '<form id=max-results-form ' + | |
| 1187 'onsubmit="setState(\'maxResults\', maxResults.value);return false;"' + | |
| 1188 '><span>Max results to show: </span>' + | |
| 1189 '<input name=maxResults id=max-results-input></form> | ' + | |
| 992 '<b>All columns are sortable. | Skipped tests are not listed. | ' + | 1190 '<b>All columns are sortable. | Skipped tests are not listed. | ' + |
| 993 'Flakiness reader order is newer --> older runs.</b></div>' + | 1191 'Flakiness reader order is newer --> older runs.</b></div>' + |
| 994 getHTMLForTestTable(tableRowsHTML); | 1192 testsHTML; |
| 995 | 1193 |
| 996 setFullPageHTML(html); | 1194 setFullPageHTML(html); |
| 997 | 1195 |
| 998 var ths = document.getElementsByTagName('th'); | 1196 var ths = document.getElementsByTagName('th'); |
| 999 for (var i = 0; i < ths.length; i++) { | 1197 for (var i = 0; i < ths.length; i++) { |
| 1000 ths[i].addEventListener('click', changeSort, false); | 1198 ths[i].addEventListener('click', changeSort, false); |
| 1001 ths[i].className = "sortable"; | 1199 ths[i].className = "sortable"; |
| 1002 } | 1200 } |
| 1003 } | |
| 1004 | 1201 |
| 1005 // Permalinkable state of the page. | 1202 $('max-results-input').value = currentState.maxResults; |
| 1006 var currentState = {}; | |
| 1007 | |
| 1008 var defaultStateValues = { | |
| 1009 sortOrder: BACKWARD, | |
| 1010 sortColumn: 'flakiness', | |
| 1011 showWontFix: false, | |
| 1012 showCorrectExpectations: false, | |
| 1013 showFlaky: true | |
| 1014 }; | |
| 1015 | |
| 1016 for (var builder in builders) { | |
| 1017 defaultStateValues.builder = builder; | |
| 1018 break; | |
| 1019 } | |
| 1020 | |
| 1021 function fillDefaultStateValues() { | |
| 1022 // tests has no states with default values. | |
| 1023 if (currentState.tests) | |
| 1024 return; | |
| 1025 | |
| 1026 for (var state in defaultStateValues) { | |
| 1027 if (!(state in currentState)) | |
| 1028 currentState[state] = defaultStateValues[state]; | |
| 1029 } | |
| 1030 } | 1203 } |
| 1031 | 1204 |
| 1032 /** | 1205 /** |
| 1033 * Sets the page state and regenerates the page. Takes varargs of key, value | 1206 * Sets the page state and regenerates the page. Takes varargs of key, value |
| 1034 * pairs. | 1207 * pairs. |
| 1035 */ | 1208 */ |
| 1036 function setState(key, value) { | 1209 function setState(key, value) { |
| 1037 for (var i = 0; i < arguments.length; i = i + 2) { | 1210 for (var i = 0; i < arguments.length; i = i + 2) { |
| 1038 var key = arguments[i]; | 1211 var key = arguments[i]; |
| 1039 if (key == 'tests') { | 1212 if (key == 'tests') { |
| 1040 for (var state in currentState) { | 1213 for (var state in currentState) { |
| 1041 delete currentState[state]; | 1214 delete currentState[state]; |
| 1042 } | 1215 } |
| 1043 } else { | 1216 } else { |
| 1044 delete currentState.tests; | 1217 delete currentState.tests; |
| 1045 } | 1218 } |
| 1046 | 1219 |
| 1047 currentState[key] = arguments[i + 1]; | 1220 currentState[key] = arguments[i + 1]; |
| 1048 } | 1221 } |
| 1222 window.location.replace(getPermaLinkURL()); | |
| 1223 handleLocationChange(); | |
| 1224 } | |
| 1049 | 1225 |
| 1226 function handleLocationChange() { | |
| 1050 $('loading-ui').style.display = 'block'; | 1227 $('loading-ui').style.display = 'block'; |
| 1051 setTimeout(function() { | 1228 setTimeout(function() { |
| 1052 window.location.replace(getPermaLinkURL()); | 1229 oldLocation = window.location.href; |
| 1053 generatePage(); | 1230 generatePage(); |
| 1054 $('loading-ui').style.display = 'none'; | |
| 1055 }, 0); | 1231 }, 0); |
| 1056 } | 1232 } |
| 1057 | 1233 |
| 1058 function parseCurrentLocation() { | 1234 function getPermaLinkURL() { |
| 1059 oldLocation = window.location.href; | 1235 return window.location.pathname + '?' + joinParameters(queryState) + '#' + |
| 1060 var hash = window.location.hash; | 1236 joinParameters(currentState); |
| 1061 if (!hash) { | |
| 1062 fillDefaultStateValues(); | |
| 1063 return; | |
| 1064 } | |
| 1065 | |
| 1066 var hashParts = hash.slice(1).split('&'); | |
| 1067 var urlHasTests = false; | |
| 1068 for (var i = 0; i < hashParts.length; i++) { | |
| 1069 var itemParts = hashParts[i].split('='); | |
| 1070 if (itemParts.length != 2) { | |
| 1071 console.log("Invalid parameter: " + hashParts[i]); | |
| 1072 continue; | |
| 1073 } | |
| 1074 | |
| 1075 var key = itemParts[0]; | |
| 1076 var value = decodeURIComponent(itemParts[1]); | |
| 1077 switch(key) { | |
| 1078 case 'tests': | |
| 1079 validateParameter(key, value, | |
| 1080 function() { return value.match(/[A-Za-z0-9\-\_,]/); }); | |
| 1081 break; | |
| 1082 | |
| 1083 case 'builder': | |
| 1084 validateParameter(key, value, | |
| 1085 function() { return value in builders; }); | |
| 1086 break; | |
| 1087 | |
| 1088 case 'sortColumn': | |
| 1089 validateParameter(key, value, | |
| 1090 function() { | |
| 1091 for (var i = 0; i < tableHeaders.length; i++) { | |
| 1092 if (value == getSortColumnFromTableHeader(tableHeaders[i])) | |
| 1093 return true; | |
| 1094 } | |
| 1095 return value == 'test'; | |
| 1096 }); | |
| 1097 break; | |
| 1098 | |
| 1099 case 'sortOrder': | |
| 1100 validateParameter(key, value, | |
| 1101 function() { return value == FORWARD || value == BACKWARD; }); | |
| 1102 break; | |
| 1103 | |
| 1104 case 'showWontFix': | |
| 1105 case 'showCorrectExpectations': | |
| 1106 case 'showFlaky': | |
| 1107 currentState[key] = value == 'true'; | |
| 1108 break; | |
| 1109 | |
| 1110 default: | |
| 1111 console.log('Invalid key: ' + key + ' value: ' + value); | |
| 1112 } | |
| 1113 } | |
| 1114 fillDefaultStateValues(); | |
| 1115 } | 1237 } |
| 1116 | 1238 |
| 1117 function validateParameter(key, value, validateFn) { | 1239 function joinParameters(stateObject) { |
| 1118 if (validateFn()) { | 1240 var state = []; |
| 1119 currentState[key] = value; | 1241 for (var key in stateObject) { |
| 1120 } else { | 1242 state.push(key + '=' + encodeURIComponent(stateObject[key])); |
| 1121 console.log(key + ' value is not valid: ' + value); | |
| 1122 } | 1243 } |
| 1123 } | 1244 return state.join('&'); |
| 1124 | |
| 1125 function getPermaLinkURL() { | |
| 1126 var state = []; | |
| 1127 for (var key in currentState) { | |
| 1128 state.push(key + '=' + currentState[key]); | |
| 1129 } | |
| 1130 return window.location.pathname + '#' + state.join('&'); | |
| 1131 } | 1245 } |
| 1132 | 1246 |
| 1133 function logTime(msg, startTime) { | 1247 function logTime(msg, startTime) { |
| 1134 console.log(msg + ': ' + (Date.now() - startTime)); | 1248 console.log(msg + ': ' + (Date.now() - startTime)); |
| 1135 } | 1249 } |
| 1136 | 1250 |
| 1137 window.onload = function() { | 1251 window.onload = function() { |
| 1138 // This doesn't seem totally accurate as there is a race between | 1252 // This doesn't seem totally accurate as there is a race between |
| 1139 // onload firing and the last script tag being executed. | 1253 // onload firing and the last script tag being executed. |
| 1140 logTime('Time to load JS', pageLoadStartTime); | 1254 logTime('Time to load JS', pageLoadStartTime); |
| 1141 setInterval(function() { | 1255 setInterval(function() { |
| 1142 if (oldLocation != window.location) | 1256 if (oldLocation != window.location.href) { |
| 1143 generatePage(); | 1257 parseAllParameters(); |
| 1258 handleLocationChange(); | |
| 1259 } | |
| 1144 }, 100); | 1260 }, 100); |
| 1145 } | 1261 } |
| 1146 </script> | 1262 </script> |
| 1147 </head> | 1263 </head> |
| 1148 | 1264 |
| 1149 <body></body> | 1265 <body></body> |
| 1150 </html> | 1266 </html> |
| OLD | NEW |