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 |