blob: 1cc98e7f2907215a8cee987094225120fa7eedf5 [file] [log] [blame]
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001// Copyright (C) 2012 Google Inc. All rights reserved.
2//
3// Redistribution and use in source and binary forms, with or without
4// modification, are permitted provided that the following conditions are
5// met:
6//
7// * Redistributions of source code must retain the above copyright
8// notice, this list of conditions and the following disclaimer.
9// * Redistributions in binary form must reproduce the above
10// copyright notice, this list of conditions and the following disclaimer
11// in the documentation and/or other materials provided with the
12// distribution.
13// * Neither the name of Google Inc. nor the names of its
14// contributors may be used to endorse or promote products derived from
15// this software without specific prior written permission.
16//
17// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29//////////////////////////////////////////////////////////////////////////////
30// CONSTANTS
31//////////////////////////////////////////////////////////////////////////////
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +000032var FORWARD = 'forward';
33var BACKWARD = 'backward';
34var GTEST_MODIFIERS = ['FLAKY', 'FAILS', 'MAYBE', 'DISABLED'];
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +010035var TEST_URL_BASE_PATH_FOR_BROWSING = 'http://src.chromium.org/viewvc/blink/trunk/LayoutTests/';
36var TEST_URL_BASE_PATH_FOR_XHR = 'http://src.chromium.org/blink/trunk/LayoutTests/';
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +000037var TEST_RESULTS_BASE_PATH = 'http://build.chromium.org/f/chromium/layout_test_results/';
38var GPU_RESULTS_BASE_PATH = 'http://chromium-browser-gpu-tests.commondatastorage.googleapis.com/runs/'
39
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +010040var RELEASE_TIMEOUT = 6;
41var DEBUG_TIMEOUT = 12;
42var SLOW_MULTIPLIER = 5;
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +000043var CHUNK_SIZE = 25;
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +000044
45// FIXME: Figure out how to make this not be hard-coded.
46var VIRTUAL_SUITES = {
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +010047 'virtual/gpu/fast/canvas': 'fast/canvas',
48 'virtual/gpu/canvas/philip': 'canvas/philip'
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +000049};
50
Torne (Richard Coles)926b0012013-03-28 15:32:48 +000051var resourceLoader;
52
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +010053function generatePage(historyInstance)
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +000054{
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +010055 if (historyInstance.crossDashboardState.useTestData)
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +000056 return;
57
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +000058 document.body.innerHTML = '<div id="loading-ui">LOADING...</div>';
Torne (Richard Coles)926b0012013-03-28 15:32:48 +000059 resourceLoader.showErrors();
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +000060
61 // tests expands to all tests that match the CSV list.
62 // result expands to all tests that ever have the given result
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +010063 if (historyInstance.dashboardSpecificState.tests || historyInstance.dashboardSpecificState.result)
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +000064 generatePageForIndividualTests(individualTests());
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +000065 else
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +010066 generatePageForBuilder(historyInstance.dashboardSpecificState.builder || currentBuilderGroup().defaultBuilder());
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +000067
Torne (Richard Coles)926b0012013-03-28 15:32:48 +000068 for (var builder in currentBuilders())
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +000069 processTestResultsForBuilderAsync(builder);
70
71 postHeightChangedMessage();
72}
73
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +010074function handleValidHashParameter(historyInstance, key, value)
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +000075{
76 switch(key) {
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +010077 case 'result':
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +000078 case 'tests':
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +010079 history.validateParameter(historyInstance.dashboardSpecificState, key, value,
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +000080 function() {
Torne (Richard Coles)926b0012013-03-28 15:32:48 +000081 return string.isValidName(value);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +000082 });
83 return true;
84
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +000085 case 'builder':
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +010086 history.validateParameter(historyInstance.dashboardSpecificState, key, value,
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +000087 function() {
Torne (Richard Coles)926b0012013-03-28 15:32:48 +000088 return value in currentBuilders();
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +000089 });
Torne (Richard Coles)926b0012013-03-28 15:32:48 +000090
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +000091 return true;
92
93 case 'sortColumn':
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +010094 history.validateParameter(historyInstance.dashboardSpecificState, key, value,
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +000095 function() {
96 // Get all possible headers since the actual used set of headers
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +010097 // depends on the values in historyInstance.dashboardSpecificState, which are currently being set.
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +000098 var headers = tableHeaders(true);
99 for (var i = 0; i < headers.length; i++) {
100 if (value == sortColumnFromTableHeader(headers[i]))
101 return true;
102 }
103 return value == 'test' || value == 'builder';
104 });
105 return true;
106
107 case 'sortOrder':
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100108 history.validateParameter(historyInstance.dashboardSpecificState, key, value,
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000109 function() {
110 return value == FORWARD || value == BACKWARD;
111 });
112 return true;
113
114 case 'resultsHeight':
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000115 case 'revision':
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100116 history.validateParameter(historyInstance.dashboardSpecificState, key, Number(value),
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000117 function() {
118 return value.match(/^\d+$/);
119 });
120 return true;
121
122 case 'showChrome':
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000123 case 'showExpectations':
124 case 'showFlaky':
125 case 'showLargeExpectations':
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100126 case 'showNonFlaky':
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000127 case 'showSlow':
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100128 case 'showSkip':
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000129 case 'showUnexpectedPasses':
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100130 case 'showWontFix':
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100131 historyInstance.dashboardSpecificState[key] = value == 'true';
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000132 return true;
133
134 default:
135 return false;
136 }
137}
138
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100139// @param {Object} params New or modified query parameters as key: value.
140function handleQueryParameterChange(historyInstance, params)
141{
142 for (key in params) {
143 if (key == 'tests') {
144 // Entering cross-builder view, only keep valid keys for that view.
145 for (var currentKey in historyInstance.dashboardSpecificState) {
146 if (isInvalidKeyForCrossBuilderView(currentKey)) {
147 delete historyInstance.dashboardSpecificState[currentKey];
148 }
149 }
150 } else if (isInvalidKeyForCrossBuilderView(key)) {
151 delete historyInstance.dashboardSpecificState.tests;
152 delete historyInstance.dashboardSpecificState.result;
153 }
154 }
155
156 return true;
157}
158
159var defaultDashboardSpecificStateValues = {
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000160 sortOrder: BACKWARD,
161 sortColumn: 'flakiness',
162 showExpectations: false,
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100163 // FIXME: Show flaky tests by default if you have a builder picked.
164 // Ideally, we'd fix the dashboard to not pick a default builder and have
165 // you pick one. In the interim, this is a good way to make the default
166 // page load faster since we don't need to generate/layout a large table.
167 showFlaky: false,
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000168 showLargeExpectations: false,
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000169 showChrome: true,
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100170 showWontFix: false,
171 showNonFlaky: false,
172 showSkip: false,
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100173 showUnexpectedPasses: false,
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000174 resultsHeight: 300,
175 revision: null,
176 tests: '',
177 result: '',
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000178 builder: null
179};
180
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100181var DB_SPECIFIC_INVALIDATING_PARAMETERS = {
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000182 'tests' : 'builder',
183 'testType': 'builder',
184 'group': 'builder'
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000185};
186
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100187var flakinessConfig = {
188 defaultStateValues: defaultDashboardSpecificStateValues,
189 generatePage: generatePage,
190 handleValidHashParameter: handleValidHashParameter,
191 handleQueryParameterChange: handleQueryParameterChange,
192 invalidatingHashParameters: DB_SPECIFIC_INVALIDATING_PARAMETERS
193};
194
195// FIXME(jparent): Eventually remove all usage of global history object.
196var g_history = new history.History(flakinessConfig);
197g_history.parseCrossDashboardParameters();
198
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000199//////////////////////////////////////////////////////////////////////////////
200// GLOBALS
201//////////////////////////////////////////////////////////////////////////////
202
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000203var g_perBuilderFailures = {};
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000204// Maps test path to an array of {builder, testResults} objects.
205var g_testToResultsMap = {};
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000206
207function createResultsObjectForTest(test, builder)
208{
209 return {
210 test: test,
211 builder: builder,
212 // HTML for display of the results in the flakiness column
213 html: '',
214 flips: 0,
215 slowestTime: 0,
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000216 isFlaky: false,
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100217 bugs: [],
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000218 expectations : '',
219 rawResults: '',
220 // List of all the results the test actually has.
221 actualResults: []
222 };
223}
224
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000225var TestTrie = function(builders, resultsByBuilder)
226{
227 this._trie = {};
228
229 for (var builder in builders) {
230 var testsForBuilder = resultsByBuilder[builder].tests;
231 for (var test in testsForBuilder)
232 this._addTest(test.split('/'), this._trie);
233 }
234}
235
236TestTrie.prototype.forEach = function(callback, startingTriePath)
237{
238 var testsTrie = this._trie;
239 if (startingTriePath) {
240 var splitPath = startingTriePath.split('/');
241 while (splitPath.length && testsTrie)
242 testsTrie = testsTrie[splitPath.shift()];
243 }
244
245 if (!testsTrie)
246 return;
247
248 function traverse(trie, triePath) {
249 if (trie == true)
250 callback(triePath);
251 else {
252 for (var member in trie)
253 traverse(trie[member], triePath ? triePath + '/' + member : member);
254 }
255 }
256 traverse(testsTrie, startingTriePath);
257}
258
259TestTrie.prototype._addTest = function(test, trie)
260{
261 var rootComponent = test.shift();
262 if (!test.length) {
263 if (!trie[rootComponent])
264 trie[rootComponent] = true;
265 return;
266 }
267
268 if (!trie[rootComponent] || trie[rootComponent] == true)
269 trie[rootComponent] = {};
270 this._addTest(test, trie[rootComponent]);
271}
272
273// Map of all tests to true values. This is just so we can have the list of
274// all tests across all the builders.
275var g_allTestsTrie;
276
277function getAllTestsTrie()
278{
279 if (!g_allTestsTrie)
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000280 g_allTestsTrie = new TestTrie(currentBuilders(), g_resultsByBuilder);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000281
282 return g_allTestsTrie;
283}
284
285// Returns an array of tests to be displayed in the individual tests view.
286// Note that a directory can be listed as a test, so we expand that into all
287// tests in the directory.
288function individualTests()
289{
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000290 if (g_history.dashboardSpecificState.result)
291 return allTestsWithResult(g_history.dashboardSpecificState.result);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000292
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000293 if (!g_history.dashboardSpecificState.tests)
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000294 return [];
295
296 return individualTestsForSubstringList();
297}
298
299function substringList()
300{
301 // Convert windows slashes to unix slashes.
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000302 var tests = g_history.dashboardSpecificState.tests.replace(/\\/g, '/');
303 var separator = string.contains(tests, ' ') ? ' ' : ',';
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000304 var testList = tests.split(separator);
305
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000306 if (g_history.isLayoutTestResults())
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000307 return testList;
308
309 var testListWithoutModifiers = [];
310 testList.forEach(function(path) {
311 GTEST_MODIFIERS.forEach(function(modifier) {
312 path = path.replace('.' + modifier + '_', '.');
313 });
314 testListWithoutModifiers.push(path);
315 });
316 return testListWithoutModifiers;
317}
318
319function individualTestsForSubstringList()
320{
321 var testList = substringList();
322
323 // Put the tests into an object first and then move them into an array
324 // as a way of deduping.
325 var testsMap = {};
326 for (var i = 0; i < testList.length; i++) {
327 var path = testList[i];
328
329 // Ignore whitespace entries as they'd match every test.
330 if (path.match(/^\s*$/))
331 continue;
332
333 var hasAnyMatches = false;
334 getAllTestsTrie().forEach(function(triePath) {
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000335 if (string.caseInsensitiveContains(triePath, path)) {
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000336 testsMap[triePath] = 1;
337 hasAnyMatches = true;
338 }
339 });
340
341 // If a path doesn't match any tests, then assume it's a full path
342 // to a test that passes on all builders.
343 if (!hasAnyMatches)
344 testsMap[path] = 1;
345 }
346
347 var testsArray = [];
348 for (var test in testsMap)
349 testsArray.push(test);
350 return testsArray;
351}
352
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000353function allTestsWithResult(result)
354{
355 processTestRunsForAllBuilders();
356 var retVal = [];
357
358 getAllTestsTrie().forEach(function(triePath) {
359 for (var i = 0; i < g_testToResultsMap[triePath].length; i++) {
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100360 if (g_testToResultsMap[triePath][i].actualResults.indexOf(result.toUpperCase()) != -1) {
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000361 retVal.push(triePath);
362 break;
363 }
364 }
365 });
366
367 return retVal;
368}
369
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000370function processTestResultsForBuilderAsync(builder)
371{
372 setTimeout(function() { processTestRunsForBuilder(builder); }, 0);
373}
374
375function processTestRunsForAllBuilders()
376{
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000377 for (var builder in currentBuilders())
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000378 processTestRunsForBuilder(builder);
379}
380
381function processTestRunsForBuilder(builderName)
382{
383 if (g_perBuilderFailures[builderName])
384 return;
385
386 if (!g_resultsByBuilder[builderName]) {
387 console.error('No tests found for ' + builderName);
388 g_perBuilderFailures[builderName] = [];
389 return;
390 }
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000391
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000392 var failures = [];
393 var allTestsForThisBuilder = g_resultsByBuilder[builderName].tests;
394
395 for (var test in allTestsForThisBuilder) {
396 var resultsForTest = createResultsObjectForTest(test, builderName);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000397
398 var rawTest = g_resultsByBuilder[builderName].tests[test];
399 resultsForTest.rawTimes = rawTest.times;
400 var rawResults = rawTest.results;
401 resultsForTest.rawResults = rawResults;
402
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100403 if (rawTest.expected)
404 resultsForTest.expectations = rawTest.expected;
405
406 if (rawTest.bugs)
407 resultsForTest.bugs = rawTest.bugs;
408
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000409 // FIXME: Switch to resultsByBuild
410 var times = resultsForTest.rawTimes;
411 var numTimesSeen = 0;
412 var numResultsSeen = 0;
413 var resultsIndex = 0;
414 var currentResult;
415 for (var i = 0; i < times.length; i++) {
416 numTimesSeen += times[i][RLE.LENGTH];
417
418 while (rawResults[resultsIndex] && numTimesSeen > (numResultsSeen + rawResults[resultsIndex][RLE.LENGTH])) {
419 numResultsSeen += rawResults[resultsIndex][RLE.LENGTH];
420 resultsIndex++;
421 }
422
423 if (rawResults && rawResults[resultsIndex])
424 currentResult = rawResults[resultsIndex][RLE.VALUE];
425
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100426 resultsForTest.slowestTime = Math.max(resultsForTest.slowestTime, times[i][RLE.VALUE]);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000427 }
428
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100429 determineFlakiness(g_resultsByBuilder[builderName][FAILURE_MAP_KEY], resultsForTest);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000430 failures.push(resultsForTest);
431
432 if (!g_testToResultsMap[test])
433 g_testToResultsMap[test] = [];
434 g_testToResultsMap[test].push(resultsForTest);
435 }
436
437 g_perBuilderFailures[builderName] = failures;
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000438}
439
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100440function determineFlakiness(failureMap, resultsForTest)
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000441{
442 // Heuristic for determining whether expectations apply to a given test:
443 // -If a test result happens < MIN_RUNS_FOR_FLAKE, then consider it a flaky
444 // result and include it in the list of expected results.
445 // -Otherwise, grab the first contiguous set of runs with the same result
446 // for >= MIN_RUNS_FOR_FLAKE and ignore all following runs >=
447 // MIN_RUNS_FOR_FLAKE.
448 // This lets us rule out common cases of a test changing expectations for
449 // a few runs, then being fixed or otherwise modified in a non-flaky way.
450 var rawResults = resultsForTest.rawResults;
451
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000452 // Only consider flake if it doesn't happen twice in a row.
453 var MIN_RUNS_FOR_FLAKE = 2;
454 var resultsMap = {}
455 var numResultsSeen = 0;
456 var haveSeenNonFlakeResult = false;
457 var numRealResults = 0;
458
459 var seenResults = {};
460 for (var i = 0; i < rawResults.length; i++) {
461 var numResults = rawResults[i][RLE.LENGTH];
462 numResultsSeen += numResults;
463
464 var result = rawResults[i][RLE.VALUE];
465
466 var hasMinRuns = numResults >= MIN_RUNS_FOR_FLAKE;
467 if (haveSeenNonFlakeResult && hasMinRuns)
468 continue;
469 else if (hasMinRuns)
470 haveSeenNonFlakeResult = true;
471 else if (!seenResults[result]) {
472 // Only consider a short-lived result if we've seen it more than once.
473 // Otherwise, we include lots of false-positives due to tests that fail
474 // for a couple runs and then start passing.
475 seenResults[result] = true;
476 continue;
477 }
478
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100479 var expectation = failureMap[result];
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000480 resultsMap[expectation] = true;
481 numRealResults++;
482 }
483
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100484 resultsForTest.actualResults = Object.keys(resultsMap);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000485 resultsForTest.flips = i - 1;
486 resultsForTest.isFlaky = numRealResults > 1;
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000487}
488
489function linkHTMLToOpenWindow(url, text)
490{
491 return '<a href="' + url + '" target="_blank">' + text + '</a>';
492}
493
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000494// Returns whether the result for index'th result for testName on builder was
495// a failure.
496function isFailure(builder, testName, index)
497{
498 var currentIndex = 0;
499 var rawResults = g_resultsByBuilder[builder].tests[testName].results;
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100500 var failureMap = g_resultsByBuilder[builder][FAILURE_MAP_KEY];
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000501 for (var i = 0; i < rawResults.length; i++) {
502 currentIndex += rawResults[i][RLE.LENGTH];
503 if (currentIndex > index)
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100504 return isFailingResult(failureMap, rawResults[i][RLE.VALUE]);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000505 }
506 console.error('Index exceeds number of results: ' + index);
507}
508
509// Returns an array of indexes for all builds where this test failed.
510function indexesForFailures(builder, testName)
511{
512 var rawResults = g_resultsByBuilder[builder].tests[testName].results;
513 var buildNumbers = g_resultsByBuilder[builder].buildNumbers;
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100514 var failureMap = g_resultsByBuilder[builder][FAILURE_MAP_KEY];
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000515 var index = 0;
516 var failures = [];
517 for (var i = 0; i < rawResults.length; i++) {
518 var numResults = rawResults[i][RLE.LENGTH];
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100519 if (isFailingResult(failureMap, rawResults[i][RLE.VALUE])) {
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000520 for (var j = 0; j < numResults; j++)
521 failures.push(index + j);
522 }
523 index += numResults;
524 }
525 return failures;
526}
527
528// Returns the path to the failure log for this non-webkit test.
529function pathToFailureLog(testName)
530{
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000531 return '/steps/' + g_history.crossDashboardState.testType + '/logs/' + testName.split('.')[1]
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000532}
533
534function showPopupForBuild(e, builder, index, opt_testName)
535{
536 var html = '';
537
538 var time = g_resultsByBuilder[builder].secondsSinceEpoch[index];
539 if (time) {
540 var date = new Date(time * 1000);
541 html += date.toLocaleDateString() + ' ' + date.toLocaleTimeString();
542 }
543
544 var buildNumber = g_resultsByBuilder[builder].buildNumbers[index];
545 var master = builderMaster(builder);
546 var buildBasePath = master.logPath(builder, buildNumber);
547
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100548 html += '<ul><li>' + linkHTMLToOpenWindow(buildBasePath, 'Build log');
549
550 if (g_resultsByBuilder[builder][BLINK_REVISIONS_KEY])
551 html += '</li><li>Blink: ' + ui.html.blinkRevisionLink(g_resultsByBuilder[builder], index) + '</li>';
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000552
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100553 html += '</li><li>Chromium: ' + ui.html.chromiumRevisionLink(g_resultsByBuilder[builder], index) + '</li>';
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000554
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100555 var chromeRevision = g_resultsByBuilder[builder].chromeRevision[index];
556 if (chromeRevision && g_history.isLayoutTestResults()) {
557 html += '<li><a href="' + TEST_RESULTS_BASE_PATH + currentBuilders()[builder] +
558 '/' + chromeRevision + '/layout-test-results.zip">layout-test-results.zip</a></li>';
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000559 }
560
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000561 if (!g_history.isLayoutTestResults() && opt_testName && isFailure(builder, opt_testName, index))
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000562 html += '<li>' + linkHTMLToOpenWindow(buildBasePath + pathToFailureLog(opt_testName), 'Failure log') + '</li>';
563
564 html += '</ul>';
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000565 ui.popup.show(e.target, html);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000566}
567
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100568function classNameForFailureString(failure)
569{
570 return failure.replace(/(\+|\ )/, '');
571}
572
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000573function htmlForTestResults(test)
574{
575 var html = '';
576 var results = test.rawResults.concat();
577 var times = test.rawTimes.concat();
578 var builder = test.builder;
579 var master = builderMaster(builder);
580 var buildNumbers = g_resultsByBuilder[builder].buildNumbers;
581
582 var indexToReplaceCurrentResult = -1;
583 var indexToReplaceCurrentTime = -1;
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000584 for (var i = 0; i < buildNumbers.length; i++) {
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100585 var currentResultArray, currentTimeArray, innerHTML, resultString;
586
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000587 if (i > indexToReplaceCurrentResult) {
588 currentResultArray = results.shift();
589 if (currentResultArray) {
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100590 resultString = g_resultsByBuilder[builder][FAILURE_MAP_KEY][currentResultArray[RLE.VALUE]];
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000591 indexToReplaceCurrentResult += currentResultArray[RLE.LENGTH];
592 } else {
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100593 resultString = NO_DATA;
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000594 indexToReplaceCurrentResult += buildNumbers.length;
595 }
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000596 }
597
598 if (i > indexToReplaceCurrentTime) {
599 currentTimeArray = times.shift();
600 var currentTime = 0;
601 if (currentResultArray) {
602 currentTime = currentTimeArray[RLE.VALUE];
603 indexToReplaceCurrentTime += currentTimeArray[RLE.LENGTH];
604 } else
605 indexToReplaceCurrentTime += buildNumbers.length;
606
607 innerHTML = currentTime || '&nbsp;';
608 }
609
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100610 html += '<td title="' + resultString + '. Click for more info." class="results ' + classNameForFailureString(resultString) +
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100611 '" onclick=\'showPopupForBuild(event, "' + builder + '",' + i + ',"' + test.test + '")\'>' + innerHTML;
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000612 }
613 return html;
614}
615
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100616function shouldShowTest(testResult)
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000617{
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100618 if (!g_history.isLayoutTestResults())
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100619 return true;
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100620
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100621 if (testResult.expectations == 'WONTFIX')
622 return g_history.dashboardSpecificState.showWontFix;
623
624 if (testResult.expectations == 'SKIP')
625 return g_history.dashboardSpecificState.showSkip;
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000626
627 if (testResult.isFlaky)
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100628 return g_history.dashboardSpecificState.showFlaky;
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000629
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100630 return g_history.dashboardSpecificState.showNonFlaky;
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000631}
632
633function createBugHTML(test)
634{
635 var symptom = test.isFlaky ? 'flaky' : 'failing';
636 var title = encodeURIComponent('Layout Test ' + test.test + ' is ' + symptom);
637 var description = encodeURIComponent('The following layout test is ' + symptom + ' on ' +
638 '[insert platform]\n\n' + test.test + '\n\nProbable cause:\n\n' +
639 '[insert probable cause]');
640
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100641 url = 'https://code.google.com/p/chromium/issues/entry?template=Layout%20Test%20Failure&summary=' + title + '&comment=' + description;
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100642 return '<a href="' + url + '">File new bug</a>';
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000643}
644
645function isCrossBuilderView()
646{
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100647 return g_history.dashboardSpecificState.tests || g_history.dashboardSpecificState.result;
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000648}
649
650function tableHeaders(opt_getAll)
651{
652 var headers = [];
653 if (isCrossBuilderView() || opt_getAll)
654 headers.push('builder');
655
656 if (!isCrossBuilderView() || opt_getAll)
657 headers.push('test');
658
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000659 if (g_history.isLayoutTestResults() || opt_getAll)
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100660 headers.push('bugs', 'expectations');
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000661
662 headers.push('slowest run', 'flakiness (numbers are runtimes in seconds)');
663 return headers;
664}
665
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100666function linkifyBugs(bugs)
667{
668 var html = '';
669 bugs.forEach(function(bug) {
670 var bugHtml;
671 if (string.startsWith(bug, 'Bug('))
672 bugHtml = bug;
673 else
674 bugHtml = '<a href="http://' + bug + '">' + bug + '</a>';
675 html += '<div>' + bugHtml + '</div>'
676 });
677 return html;
678}
679
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000680function htmlForSingleTestRow(test)
681{
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000682 var headers = tableHeaders();
683 var html = '';
684 for (var i = 0; i < headers.length; i++) {
685 var header = headers[i];
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000686 if (string.startsWith(header, 'test') || string.startsWith(header, 'builder')) {
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000687 // If isCrossBuilderView() is true, we're just viewing a single test
688 // with results for many builders, so the first column is builder names
689 // instead of test paths.
690 var testCellClassName = 'test-link' + (isCrossBuilderView() ? ' builder-name' : '');
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000691 var testCellHTML = isCrossBuilderView() ? test.builder : '<span class="link" onclick="g_history.setQueryParameter(\'tests\',\'' + test.test +'\');">' + test.test + '</span>';
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000692
693 html += '<tr><td class="' + testCellClassName + '">' + testCellHTML;
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000694 } else if (string.startsWith(header, 'bugs'))
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100695 // FIXME: linkify bugs.
696 html += '<td class=options-container>' + (linkifyBugs(test.bugs) || createBugHTML(test));
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000697 else if (string.startsWith(header, 'expectations'))
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000698 html += '<td class=options-container>' + test.expectations;
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000699 else if (string.startsWith(header, 'slowest'))
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000700 html += '<td>' + (test.slowestTime ? test.slowestTime + 's' : '');
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000701 else if (string.startsWith(header, 'flakiness'))
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000702 html += htmlForTestResults(test);
703 }
704 return html;
705}
706
707function sortColumnFromTableHeader(headerText)
708{
709 return headerText.split(' ', 1)[0];
710}
711
712function htmlForTableColumnHeader(headerName, opt_fillColSpan)
713{
714 // Use the first word of the header title as the sortkey
715 var thisSortValue = sortColumnFromTableHeader(headerName);
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000716 var arrowHTML = thisSortValue == g_history.dashboardSpecificState.sortColumn ?
717 '<span class=' + g_history.dashboardSpecificState.sortOrder + '>' + (g_history.dashboardSpecificState.sortOrder == FORWARD ? '&uarr;' : '&darr;' ) + '</span>' : '';
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000718 return '<th sortValue=' + thisSortValue +
719 // Extend last th through all the rest of the columns.
720 (opt_fillColSpan ? ' colspan=10000' : '') +
721 // Extra span here is so flex boxing actually centers.
722 // There's probably a better way to do this with CSS only though.
723 '><div class=table-header-content><span></span>' + arrowHTML +
724 '<span class=header-text>' + headerName + '</span>' + arrowHTML + '</div></th>';
725}
726
727function htmlForTestTable(rowsHTML, opt_excludeHeaders)
728{
729 var html = '<table class=test-table>';
730 if (!opt_excludeHeaders) {
731 html += '<thead><tr>';
732 var headers = tableHeaders();
733 for (var i = 0; i < headers.length; i++)
734 html += htmlForTableColumnHeader(headers[i], i == headers.length - 1);
735 html += '</tr></thead>';
736 }
737 return html + '<tbody>' + rowsHTML + '</tbody></table>';
738}
739
740function appendHTML(html)
741{
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000742 // InnerHTML to a div that's not in the document. This is
743 // ~300ms faster in Safari 4 and Chrome 4 on mac.
744 var div = document.createElement('div');
745 div.innerHTML = html;
746 document.body.appendChild(div);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000747 postHeightChangedMessage();
748}
749
750function alphanumericCompare(column, reverse)
751{
752 return reversibleCompareFunction(function(a, b) {
753 // Put null entries at the bottom
754 var a = a[column] ? String(a[column]) : 'z';
755 var b = b[column] ? String(b[column]) : 'z';
756
757 if (a < b)
758 return -1;
759 else if (a == b)
760 return 0;
761 else
762 return 1;
763 }, reverse);
764}
765
766function numericSort(column, reverse)
767{
768 return reversibleCompareFunction(function(a, b) {
769 a = parseFloat(a[column]);
770 b = parseFloat(b[column]);
771 return a - b;
772 }, reverse);
773}
774
775function reversibleCompareFunction(compare, reverse)
776{
777 return function(a, b) {
778 return compare(reverse ? b : a, reverse ? a : b);
779 };
780}
781
782function changeSort(e)
783{
784 var target = e.currentTarget;
785 e.preventDefault();
786
787 var sortValue = target.getAttribute('sortValue');
788 while (target && target.tagName != 'TABLE')
789 target = target.parentNode;
790
791 var sort = 'sortColumn';
792 var orderKey = 'sortOrder';
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000793 if (sortValue == g_history.dashboardSpecificState[sort] && g_history.dashboardSpecificState[orderKey] == FORWARD)
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000794 order = BACKWARD;
795 else
796 order = FORWARD;
797
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000798 g_history.setQueryParameter(sort, sortValue, orderKey, order);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000799}
800
801function sortTests(tests, column, order)
802{
803 var resultsProperty, sortFunctionGetter;
804 if (column == 'flakiness') {
805 sortFunctionGetter = numericSort;
806 resultsProperty = 'flips';
807 } else if (column == 'slowest') {
808 sortFunctionGetter = numericSort;
809 resultsProperty = 'slowestTime';
810 } else {
811 sortFunctionGetter = alphanumericCompare;
812 resultsProperty = column;
813 }
814
815 tests.sort(sortFunctionGetter(resultsProperty, order == BACKWARD));
816}
817
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000818function htmlForIndividualTestOnAllBuilders(test)
819{
820 processTestRunsForAllBuilders();
821
822 var testResults = g_testToResultsMap[test];
823 if (!testResults)
824 return '<div class="not-found">Test not found. Either it does not exist, is skipped or passes on all platforms.</div>';
825
826 var html = '';
827 var shownBuilders = [];
828 for (var j = 0; j < testResults.length; j++) {
829 shownBuilders.push(testResults[j].builder);
830 html += htmlForSingleTestRow(testResults[j]);
831 }
832
833 var skippedBuilders = []
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000834 for (builder in currentBuilders()) {
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000835 if (shownBuilders.indexOf(builder) == -1)
836 skippedBuilders.push(builder);
837 }
838
839 var skippedBuildersHtml = '';
840 if (skippedBuilders.length) {
841 skippedBuildersHtml = '<div>The following builders either don\'t run this test (e.g. it\'s skipped) or all runs passed:</div>' +
842 '<div class=skipped-builder-list><div class=skipped-builder>' + skippedBuilders.join('</div><div class=skipped-builder>') + '</div></div>';
843 }
844
845 return htmlForTestTable(html) + skippedBuildersHtml;
846}
847
848function htmlForIndividualTestOnAllBuildersWithResultsLinks(test)
849{
850 processTestRunsForAllBuilders();
851
852 var testResults = g_testToResultsMap[test];
853 var html = '';
854 html += htmlForIndividualTestOnAllBuilders(test);
855
856 html += '<div class=expectations test=' + test + '><div>' +
857 linkHTMLToToggleState('showExpectations', 'results')
858
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000859 if (g_history.isLayoutTestResults() || g_history.isGPUTestResults()) {
860 if (g_history.isLayoutTestResults())
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000861 html += ' | ' + linkHTMLToToggleState('showLargeExpectations', 'large thumbnails');
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000862 html += ' | <b>Only shows actual results/diffs from the most recent *failure* on each bot.</b>';
863 } else {
864 html += ' | <span>Results height:<input ' +
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000865 'onchange="g_history.setQueryParameter(\'resultsHeight\',this.value)" value="' +
866 g_history.dashboardSpecificState.resultsHeight + '" style="width:2.5em">px</span>';
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000867 }
868 html += '</div></div>';
869 return html;
870}
871
872function getExpectationsContainer(expectationsContainers, parentContainer, expectationsType)
873{
874 if (!expectationsContainers[expectationsType]) {
875 var container = document.createElement('div');
876 container.className = 'expectations-container';
877 parentContainer.appendChild(container);
878 expectationsContainers[expectationsType] = container;
879 }
880 return expectationsContainers[expectationsType];
881}
882
883function ensureTrailingSlash(path)
884{
885 if (path.match(/\/$/))
886 return path;
887 return path + '/';
888}
889
890function maybeAddPngChecksum(expectationDiv, pngUrl)
891{
892 // pngUrl gets served from the browser cache since we just loaded it in an
893 // <img> tag.
894 loader.request(pngUrl,
895 function(xhr) {
896 // Convert the first 2k of the response to a byte string.
897 var bytes = xhr.responseText.substring(0, 2048);
898 for (var position = 0; position < bytes.length; ++position)
899 bytes[position] = bytes[position] & 0xff;
900
901 // Look for the comment.
902 var commentKey = 'tEXtchecksum\x00';
903 var checksumPosition = bytes.indexOf(commentKey);
904 if (checksumPosition == -1)
905 return;
906
907 var checksum = bytes.substring(checksumPosition + commentKey.length, checksumPosition + commentKey.length + 32);
908 var checksumContainer = document.createElement('span');
909 checksumContainer.innerText = 'Embedded checksum: ' + checksum;
910 checksumContainer.setAttribute('class', 'pngchecksum');
911 expectationDiv.parentNode.appendChild(checksumContainer);
912 },
913 function(xhr) {},
914 true);
915}
916
917// Adds a specific expectation. If it's an image, it's only added on the
918// image's onload handler. If it's a text file, then a script tag is appended
919// as a hack to see if the file 404s (necessary since it's cross-domain).
920// Once all the expectations for a specific type have loaded or errored
921// (e.g. all the text results), then we go through and identify which platform
922// uses which expectation.
923//
924// @param {Object} expectationsContainers Map from expectations type to
925// container DIV.
926// @param {Element} parentContainer Container element for
927// expectationsContainer divs.
928// @param {string} platform Platform string. Empty string for non-platform
929// specific expectations.
930// @param {string} path Relative path to the expectation.
931// @param {string} base Base path for the expectation URL.
932// @param {string} opt_builder Builder whose actual results this expectation
933// points to.
934// @param {string} opt_suite "virtual suite" that the test belongs to, if any.
935function addExpectationItem(expectationsContainers, parentContainer, platform, path, base, opt_builder, opt_suite)
936{
937 var parts = path.split('.')
938 var fileExtension = parts[parts.length - 1];
939 if (fileExtension == 'html')
940 fileExtension = 'txt';
941
942 var container = getExpectationsContainer(expectationsContainers, parentContainer, fileExtension);
943 var isImage = path.match(/\.png$/);
944
945 // FIXME: Stop using script tags once all the places we pull from support CORS.
946 var platformPart = platform ? ensureTrailingSlash(platform) : '';
947 var suitePart = opt_suite ? ensureTrailingSlash(opt_suite) : '';
948
949 var childContainer = document.createElement('span');
950 childContainer.className = 'unloaded';
951
952 var appendExpectationsItem = function(item) {
953 childContainer.appendChild(expectationsTitle(platformPart + suitePart, path, opt_builder));
954 childContainer.className = 'expectations-item';
955 item.className = 'expectation ' + fileExtension;
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000956 if (g_history.dashboardSpecificState.showLargeExpectations)
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000957 item.className += ' large';
958 childContainer.appendChild(item);
959 handleFinishedLoadingExpectations(container);
960 };
961
962 var url = base + platformPart + path;
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100963 if (isImage) {
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000964 var dummyNode = document.createElement(isImage ? 'img' : 'script');
965 dummyNode.src = url;
966 dummyNode.onload = function() {
967 var item;
968 if (isImage) {
969 item = dummyNode;
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100970 maybeAddPngChecksum(item, url);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000971 } else {
972 item = document.createElement('iframe');
973 item.src = url;
974 }
975 appendExpectationsItem(item);
976 }
977 dummyNode.onerror = function() {
978 childContainer.parentNode.removeChild(childContainer);
979 handleFinishedLoadingExpectations(container);
980 }
981
982 // Append script elements now so that they load. Images load without being
983 // appended to the DOM.
984 if (!isImage)
985 childContainer.appendChild(dummyNode);
986 } else {
987 loader.request(url,
988 function(xhr) {
989 var item = document.createElement('pre');
990 item.innerText = xhr.responseText;
991 appendExpectationsItem(item);
992 },
993 function(xhr) {/* Do nothing on errors since they're expected */});
994 }
995
996 container.appendChild(childContainer);
997}
998
999
1000// Identifies which expectations are used on which platform once all the
1001// expectations of a given type have loaded (e.g. the container for png
1002// expectations for this test had no child elements with the class
1003// "unloaded").
1004//
1005// @param {string} container Element containing the expectations for a given
1006// test and a given type (e.g. png).
1007function handleFinishedLoadingExpectations(container)
1008{
1009 if (container.getElementsByClassName('unloaded').length)
1010 return;
1011
1012 var titles = container.getElementsByClassName('expectations-title');
1013 for (var platform in g_fallbacksMap) {
1014 var fallbacks = g_fallbacksMap[platform];
1015 var winner = null;
1016 var winningIndex = -1;
1017 for (var i = 0; i < titles.length; i++) {
1018 var title = titles[i];
1019
1020 if (!winner && title.platform == "") {
1021 winner = title;
1022 continue;
1023 }
1024
1025 var rawPlatform = title.platform && title.platform.replace('platform/', '');
1026 for (var j = 0; j < fallbacks.length; j++) {
1027 if ((winningIndex == -1 || winningIndex > j) && rawPlatform == fallbacks[j]) {
1028 winningIndex = j;
1029 winner = title;
1030 break;
1031 }
1032 }
1033 }
1034 if (winner)
1035 winner.getElementsByClassName('platforms')[0].innerHTML += '<div class=used-platform>' + platform + '</div>';
1036 else {
1037 console.log('No expectations identified for this test. This means ' +
1038 'there is a logic bug in the dashboard for which expectations a ' +
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +01001039 'platform uses or src.chromium.org is giving 5XXs.');
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001040 }
1041 }
1042
1043 consolidateUsedPlatforms(container);
1044}
1045
1046// Consolidate platforms when all sub-platforms for a given platform are represented.
1047// e.g., if all of the WIN- platforms are there, replace them with just WIN.
1048function consolidateUsedPlatforms(container)
1049{
1050 var allPlatforms = Object.keys(g_fallbacksMap);
1051
1052 var platformElements = container.getElementsByClassName('platforms');
1053 for (var i = 0, platformsLength = platformElements.length; i < platformsLength; i++) {
1054 var usedPlatforms = platformElements[i].getElementsByClassName('used-platform');
1055 if (!usedPlatforms.length)
1056 continue;
1057
1058 var platforms = {};
1059 platforms['MAC'] = {};
1060 platforms['WIN'] = {};
1061 platforms['LINUX'] = {};
1062 allPlatforms.forEach(function(platform) {
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001063 if (string.startsWith(platform, 'MAC'))
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001064 platforms['MAC'][platform] = 1;
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001065 else if (string.startsWith(platform, 'WIN'))
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001066 platforms['WIN'][platform] = 1;
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001067 else if (string.startsWith(platform, 'LINUX'))
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001068 platforms['LINUX'][platform] = 1;
1069 });
1070
1071 for (var j = 0, usedPlatformsLength = usedPlatforms.length; j < usedPlatformsLength; j++) {
1072 for (var platform in platforms)
1073 delete platforms[platform][usedPlatforms[j].textContent];
1074 }
1075
1076 for (var platform in platforms) {
1077 if (!Object.keys(platforms[platform]).length) {
1078 var nodesToRemove = [];
1079 for (var j = 0, usedPlatformsLength = usedPlatforms.length; j < usedPlatformsLength; j++) {
1080 var usedPlatform = usedPlatforms[j];
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001081 if (string.startsWith(usedPlatform.textContent, platform))
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001082 nodesToRemove.push(usedPlatform);
1083 }
1084
1085 nodesToRemove.forEach(function(element) { element.parentNode.removeChild(element); });
1086 platformElements[i].insertAdjacentHTML('afterBegin', '<div class=used-platform>' + platform + '</div>');
1087 }
1088 }
1089 }
1090}
1091
1092function addExpectations(expectationsContainers, container, base,
1093 platform, text, png, reftest_html_file, reftest_mismatch_html_file, suite)
1094{
1095 var builder = '';
1096 addExpectationItem(expectationsContainers, container, platform, text, base, builder, suite);
1097 addExpectationItem(expectationsContainers, container, platform, png, base, builder, suite);
1098 addExpectationItem(expectationsContainers, container, platform, reftest_html_file, base, builder, suite);
1099 addExpectationItem(expectationsContainers, container, platform, reftest_mismatch_html_file, base, builder, suite);
1100}
1101
1102function expectationsTitle(platform, path, builder)
1103{
1104 var header = document.createElement('h3');
1105 header.className = 'expectations-title';
1106
1107 var innerHTML;
1108 if (builder) {
1109 var resultsType;
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001110 if (string.endsWith(path, '-crash-log.txt'))
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001111 resultsType = 'STACKTRACE';
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001112 else if (string.endsWith(path, '-actual.txt') || string.endsWith(path, '-actual.png'))
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001113 resultsType = 'ACTUAL RESULTS';
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001114 else if (string.endsWith(path, '-wdiff.html'))
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001115 resultsType = 'WDIFF';
1116 else
1117 resultsType = 'DIFF';
1118
1119 innerHTML = resultsType + ': ' + builder;
1120 } else if (platform === "") {
1121 var parts = path.split('/');
1122 innerHTML = parts[parts.length - 1];
1123 } else
1124 innerHTML = platform || path;
1125
1126 header.innerHTML = '<div class=title>' + innerHTML +
1127 '</div><div style="float:left">&nbsp;</div>' +
1128 '<div class=platforms style="float:right"></div>';
1129 header.platform = platform;
1130 return header;
1131}
1132
1133function loadExpectations(expectationsContainer)
1134{
1135 var test = expectationsContainer.getAttribute('test');
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001136 if (g_history.isLayoutTestResults())
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001137 loadExpectationsLayoutTests(test, expectationsContainer);
1138 else {
1139 var results = g_testToResultsMap[test];
1140 for (var i = 0; i < results.length; i++)
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001141 if (g_history.isGPUTestResults())
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001142 loadGPUResultsForBuilder(results[i].builder, test, expectationsContainer);
1143 else
1144 loadNonWebKitResultsForBuilder(results[i].builder, test, expectationsContainer);
1145 }
1146}
1147
1148function gpuResultsPath(chromeRevision, builder)
1149{
1150 return chromeRevision + '_' + builder.replace(/[^A-Za-z0-9]+/g, '_');
1151}
1152
1153function loadGPUResultsForBuilder(builder, test, expectationsContainer)
1154{
1155 var container = document.createElement('div');
1156 container.className = 'expectations-container';
1157 container.innerHTML = '<div><b>' + builder + '</b></div>';
1158 expectationsContainer.appendChild(container);
1159
1160 var failureIndex = indexesForFailures(builder, test)[0];
1161
1162 var buildNumber = g_resultsByBuilder[builder].buildNumbers[failureIndex];
1163 var pathToLog = builderMaster(builder).logPath(builder, buildNumber) + pathToFailureLog(test);
1164
1165 var chromeRevision = g_resultsByBuilder[builder].chromeRevision[failureIndex];
1166 var resultsUrl = GPU_RESULTS_BASE_PATH + gpuResultsPath(chromeRevision, builder);
1167 var filename = test.split(/\./)[1] + '.png';
1168
1169 appendNonWebKitResults(container, pathToLog, 'non-webkit-results');
1170 appendNonWebKitResults(container, resultsUrl + '/gen/' + filename, 'gpu-test-results', 'Generated');
1171 appendNonWebKitResults(container, resultsUrl + '/ref/' + filename, 'gpu-test-results', 'Reference');
1172 appendNonWebKitResults(container, resultsUrl + '/diff/' + filename, 'gpu-test-results', 'Diff');
1173}
1174
1175function loadNonWebKitResultsForBuilder(builder, test, expectationsContainer)
1176{
1177 var failureIndexes = indexesForFailures(builder, test);
1178 var container = document.createElement('div');
1179 container.innerHTML = '<div><b>' + builder + '</b></div>';
1180 expectationsContainer.appendChild(container);
1181 for (var i = 0; i < failureIndexes.length; i++) {
1182 // FIXME: This doesn't seem to work anymore. Did the paths change?
1183 // Once that's resolved, see if we need to try each GTEST_MODIFIERS prefix as well.
1184 var buildNumber = g_resultsByBuilder[builder].buildNumbers[failureIndexes[i]];
1185 var pathToLog = builderMaster(builder).logPath(builder, buildNumber) + pathToFailureLog(test);
1186 appendNonWebKitResults(container, pathToLog, 'non-webkit-results');
1187 }
1188}
1189
1190function appendNonWebKitResults(container, url, itemClassName, opt_title)
1191{
1192 // Use a script tag to detect whether the URL 404s.
1193 // Need to use a script tag since the URL is cross-domain.
1194 var dummyNode = document.createElement('script');
1195 dummyNode.src = url;
1196
1197 dummyNode.onload = function() {
1198 var item = document.createElement('iframe');
1199 item.src = dummyNode.src;
1200 item.className = itemClassName;
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001201 item.style.height = g_history.dashboardSpecificState.resultsHeight + 'px';
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001202
1203 if (opt_title) {
1204 var childContainer = document.createElement('div');
1205 childContainer.style.display = 'inline-block';
1206 var title = document.createElement('div');
1207 title.textContent = opt_title;
1208 childContainer.appendChild(title);
1209 childContainer.appendChild(item);
1210 container.replaceChild(childContainer, dummyNode);
1211 } else
1212 container.replaceChild(item, dummyNode);
1213 }
1214 dummyNode.onerror = function() {
1215 container.removeChild(dummyNode);
1216 }
1217
1218 container.appendChild(dummyNode);
1219}
1220
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001221function lookupVirtualTestSuite(test) {
1222 for (var suite in VIRTUAL_SUITES) {
1223 if (test.indexOf(suite) != -1)
1224 return suite;
1225 }
1226 return '';
1227}
1228
1229function baseTest(test, suite) {
1230 base = VIRTUAL_SUITES[suite];
1231 return base ? test.replace(suite, base) : test;
1232}
1233
1234function loadBaselinesForTest(expectationsContainers, expectationsContainer, test) {
1235 var testWithoutSuffix = test.substring(0, test.lastIndexOf('.'));
1236 var text = testWithoutSuffix + "-expected.txt";
1237 var png = testWithoutSuffix + "-expected.png";
1238 var reftest_html_file = testWithoutSuffix + "-expected.html";
1239 var reftest_mismatch_html_file = testWithoutSuffix + "-expected-mismatch.html";
1240 var suite = lookupVirtualTestSuite(test);
1241
1242 if (!suite)
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +01001243 addExpectationItem(expectationsContainers, expectationsContainer, null, test, TEST_URL_BASE_PATH_FOR_XHR);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001244
1245 addExpectations(expectationsContainers, expectationsContainer,
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +01001246 TEST_URL_BASE_PATH_FOR_XHR, '', text, png, reftest_html_file, reftest_mismatch_html_file, suite);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001247
1248 var fallbacks = allFallbacks();
1249 for (var i = 0; i < fallbacks.length; i++) {
1250 var fallback = 'platform/' + fallbacks[i];
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +01001251 addExpectations(expectationsContainers, expectationsContainer, TEST_URL_BASE_PATH_FOR_XHR, fallback, text, png,
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001252 reftest_html_file, reftest_mismatch_html_file, suite);
1253 }
1254
1255 if (suite)
1256 loadBaselinesForTest(expectationsContainers, expectationsContainer, baseTest(test, suite));
1257}
1258
1259function loadExpectationsLayoutTests(test, expectationsContainer)
1260{
1261 // Map from file extension to container div for expectations of that type.
1262 var expectationsContainers = {};
1263
1264 var revisionContainer = document.createElement('div');
1265 revisionContainer.textContent = "Showing results for: "
1266 expectationsContainer.appendChild(revisionContainer);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001267 loadBaselinesForTest(expectationsContainers, expectationsContainer, test);
1268
1269 var testWithoutSuffix = test.substring(0, test.lastIndexOf('.'));
1270 var actualResultSuffixes = ['-actual.txt', '-actual.png', '-crash-log.txt', '-diff.txt', '-wdiff.html', '-diff.png'];
1271
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001272 for (var builder in currentBuilders()) {
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +01001273 var actualResultsBase = TEST_RESULTS_BASE_PATH + currentBuilders()[builder] + '/results/layout-test-results/';
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001274
1275 for (var i = 0; i < actualResultSuffixes.length; i++) {
1276 addExpectationItem(expectationsContainers, expectationsContainer, null,
1277 testWithoutSuffix + actualResultSuffixes[i], actualResultsBase, builder);
1278 }
1279 }
1280
1281 // Add a clearing element so floated elements don't bleed out of their
1282 // containing block.
1283 var br = document.createElement('br');
1284 br.style.clear = 'both';
1285 expectationsContainer.appendChild(br);
1286}
1287
1288var g_allFallbacks;
1289
1290// Returns the reverse sorted, deduped list of all platform fallback
1291// directories.
1292function allFallbacks()
1293{
1294 if (!g_allFallbacks) {
1295 var holder = {};
1296 for (var platform in g_fallbacksMap) {
1297 var fallbacks = g_fallbacksMap[platform];
1298 for (var i = 0; i < fallbacks.length; i++)
1299 holder[fallbacks[i]] = 1;
1300 }
1301
1302 g_allFallbacks = [];
1303 for (var fallback in holder)
1304 g_allFallbacks.push(fallback);
1305
1306 g_allFallbacks.sort(function(a, b) {
1307 if (a == b)
1308 return 0;
1309 return a < b;
1310 });
1311 }
1312 return g_allFallbacks;
1313}
1314
1315function appendExpectations()
1316{
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001317 var expectations = g_history.dashboardSpecificState.showExpectations ? document.getElementsByClassName('expectations') : [];
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001318 // Loading expectations is *very* slow. Use a large timeout to avoid
1319 // totally hanging the renderer.
1320 performChunkedAction(expectations, function(chunk) {
1321 for (var i = 0, len = chunk.length; i < len; i++)
1322 loadExpectations(chunk[i]);
1323 postHeightChangedMessage();
1324
1325 }, hideLoadingUI, 10000);
1326}
1327
1328function hideLoadingUI()
1329{
1330 var loadingDiv = $('loading-ui');
1331 if (loadingDiv)
1332 loadingDiv.style.display = 'none';
1333 postHeightChangedMessage();
1334}
1335
1336function generatePageForIndividualTests(tests)
1337{
1338 console.log('Number of tests: ' + tests.length);
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001339 if (g_history.dashboardSpecificState.showChrome)
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001340 appendHTML(htmlForNavBar());
1341 performChunkedAction(tests, function(chunk) {
1342 appendHTML(htmlForIndividualTests(chunk));
1343 }, appendExpectations, 500);
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001344 if (g_history.dashboardSpecificState.showChrome)
1345 $('tests-input').value = g_history.dashboardSpecificState.tests;
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001346}
1347
1348function performChunkedAction(tests, handleChunk, onComplete, timeout, opt_index) {
1349 var index = opt_index || 0;
1350 setTimeout(function() {
1351 var chunk = Array.prototype.slice.call(tests, index * CHUNK_SIZE, (index + 1) * CHUNK_SIZE);
1352 if (chunk.length) {
1353 handleChunk(chunk);
1354 performChunkedAction(tests, handleChunk, onComplete, timeout, ++index);
1355 } else
1356 onComplete();
1357 // No need for a timeout on the first chunked action.
1358 }, index ? timeout : 0);
1359}
1360
1361function htmlForIndividualTests(tests)
1362{
1363 var testsHTML = [];
1364 for (var i = 0; i < tests.length; i++) {
1365 var test = tests[i];
1366 var testNameHtml = '';
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001367 if (g_history.dashboardSpecificState.showChrome || tests.length > 1) {
1368 if (g_history.isLayoutTestResults()) {
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001369 var suite = lookupVirtualTestSuite(test);
1370 var base = suite ? baseTest(test, suite) : test;
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +01001371 var versionControlUrl = TEST_URL_BASE_PATH_FOR_BROWSING + base;
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +01001372 testNameHtml += '<h2>' + linkHTMLToOpenWindow(versionControlUrl, test) + '</h2>';
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001373 } else
1374 testNameHtml += '<h2>' + test + '</h2>';
1375 }
1376
1377 testsHTML.push(testNameHtml + htmlForIndividualTestOnAllBuildersWithResultsLinks(test));
1378 }
1379 return testsHTML.join('<hr>');
1380}
1381
1382function htmlForNavBar()
1383{
1384 var extraHTML = '';
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001385 var html = ui.html.testTypeSwitcher(false, extraHTML, isCrossBuilderView());
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001386 html += '<div class=forms><form id=result-form ' +
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001387 'onsubmit="g_history.setQueryParameter(\'result\', result.value);' +
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001388 'return false;">Show all tests with result: ' +
1389 '<input name=result placeholder="e.g. CRASH" id=result-input>' +
1390 '</form><form id=tests-form ' +
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001391 'onsubmit="g_history.setQueryParameter(\'tests\', tests.value);' +
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001392 'return false;"><span>Show tests on all platforms: </span>' +
1393 '<input name=tests ' +
1394 'placeholder="Comma or space-separated list of tests or partial ' +
1395 'paths to show test results across all builders, e.g., ' +
1396 'foo/bar.html,foo/baz,domstorage" id=tests-input></form>' +
1397 '<span class=link onclick="showLegend()">Show legend [type ?]</span></div>';
1398 return html;
1399}
1400
1401function checkBoxToToggleState(key, text)
1402{
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001403 var stateEnabled = g_history.dashboardSpecificState[key];
1404 return '<label><input type=checkbox ' + (stateEnabled ? 'checked ' : '') + 'onclick="g_history.setQueryParameter(\'' + key + '\', ' + !stateEnabled + ')">' + text + '</label> ';
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001405}
1406
1407function linkHTMLToToggleState(key, linkText)
1408{
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001409 var stateEnabled = g_history.dashboardSpecificState[key];
1410 return '<span class=link onclick="g_history.setQueryParameter(\'' + key + '\', ' + !stateEnabled + ')">' + (stateEnabled ? 'Hide' : 'Show') + ' ' + linkText + '</span>';
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001411}
1412
1413function headerForTestTableHtml()
1414{
1415 return '<h2 style="display:inline-block">Failing tests</h2>' +
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +01001416 checkBoxToToggleState('showFlaky', 'Show flaky') +
1417 checkBoxToToggleState('showNonFlaky', 'Show non-flaky') +
1418 checkBoxToToggleState('showSkip', 'Show Skip') +
1419 checkBoxToToggleState('showWontFix', 'Show WontFix');
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001420}
1421
1422function generatePageForBuilder(builderName)
1423{
1424 processTestRunsForBuilder(builderName);
1425
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +01001426 var results = g_perBuilderFailures[builderName].filter(shouldShowTest);
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001427 sortTests(results, g_history.dashboardSpecificState.sortColumn, g_history.dashboardSpecificState.sortOrder);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001428
1429 var testsHTML = '';
1430 if (results.length) {
1431 var tableRowsHTML = '';
1432 for (var i = 0; i < results.length; i++)
1433 tableRowsHTML += htmlForSingleTestRow(results[i])
1434 testsHTML = htmlForTestTable(tableRowsHTML);
1435 } else {
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001436 if (g_history.isLayoutTestResults())
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +01001437 testsHTML += '<div>Fill in one of the text inputs or checkboxes above to show failures.</div>';
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001438 else
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +01001439 testsHTML += '<div>No tests have failed!</div>';
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001440 }
1441
1442 var html = htmlForNavBar();
1443
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001444 if (g_history.isLayoutTestResults())
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +01001445 html += headerForTestTableHtml();
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001446
1447 html += '<br>' + testsHTML;
1448 appendHTML(html);
1449
1450 var ths = document.getElementsByTagName('th');
1451 for (var i = 0; i < ths.length; i++) {
1452 ths[i].addEventListener('click', changeSort, false);
1453 ths[i].className = "sortable";
1454 }
1455
1456 hideLoadingUI();
1457}
1458
1459var VALID_KEYS_FOR_CROSS_BUILDER_VIEW = {
1460 tests: 1,
1461 result: 1,
1462 showChrome: 1,
1463 showExpectations: 1,
1464 showLargeExpectations: 1,
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001465 resultsHeight: 1,
1466 revision: 1
1467};
1468
1469function isInvalidKeyForCrossBuilderView(key)
1470{
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001471 return !(key in VALID_KEYS_FOR_CROSS_BUILDER_VIEW) && !(key in history.DEFAULT_CROSS_DASHBOARD_STATE_VALUES);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001472}
1473
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001474function hideLegend()
1475{
1476 var legend = $('legend');
1477 if (legend)
1478 legend.parentNode.removeChild(legend);
1479}
1480
1481var g_fallbacksMap = {};
1482g_fallbacksMap['WIN-XP'] = ['chromium-win-xp', 'chromium-win', 'chromium'];
1483g_fallbacksMap['WIN-7'] = ['chromium-win', 'chromium'];
1484g_fallbacksMap['MAC-SNOWLEOPARD'] = ['chromium-mac-snowleopard', 'chromium-mac', 'chromium'];
1485g_fallbacksMap['MAC-LION'] = ['chromium-mac', 'chromium'];
1486g_fallbacksMap['LINUX-32'] = ['chromium-linux-x86', 'chromium-linux', 'chromium-win', 'chromium'];
1487g_fallbacksMap['LINUX-64'] = ['chromium-linux', 'chromium-win', 'chromium'];
1488
1489function htmlForFallbackHelp(fallbacks)
1490{
1491 return '<ol class=fallback-list><li>' + fallbacks.join('</li><li>') + '</li></ol>';
1492}
1493
1494function showLegend()
1495{
1496 var legend = $('legend');
1497 if (!legend) {
1498 legend = document.createElement('div');
1499 legend.id = 'legend';
1500 document.body.appendChild(legend);
1501 }
1502
1503 var html = '<div id=legend-toggle onclick="hideLegend()">Hide ' +
1504 'legend [type esc]</div><div id=legend-contents>';
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +01001505
1506 // Just grab the first failureMap. Technically, different builders can have different maps if they
1507 // haven't all cycled after the map was changed, but meh.
1508 var failureMap = g_resultsByBuilder[Object.keys(g_resultsByBuilder)[0]][FAILURE_MAP_KEY];
1509 for (var expectation in failureMap) {
1510 var failureString = failureMap[expectation];
1511 html += '<div class=' + classNameForFailureString(failureString) + '>' + failureString + '</div>';
1512 }
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001513
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001514 if (g_history.isLayoutTestResults()) {
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001515 html += '</div><br style="clear:both">' +
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +01001516 '</div><h3>Test expectations fallback order.</h3>';
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001517
1518 for (var platform in g_fallbacksMap)
1519 html += '<div class=fallback-header>' + platform + '</div>' + htmlForFallbackHelp(g_fallbacksMap[platform]);
1520
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +01001521 html += '<div>RELEASE TIMEOUTS:</div>' +
1522 htmlForSlowTimes(RELEASE_TIMEOUT) +
1523 '<div>DEBUG TIMEOUTS:</div>' +
1524 htmlForSlowTimes(DEBUG_TIMEOUT);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001525 }
1526
1527 legend.innerHTML = html;
1528}
1529
1530function htmlForSlowTimes(minTime)
1531{
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +01001532 return '<ul><li>' + minTime + ' seconds</li><li>' +
1533 SLOW_MULTIPLIER * minTime + ' seconds if marked Slow in TestExpectations</li></ul>';
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001534}
1535
1536function postHeightChangedMessage()
1537{
1538 if (window == parent)
1539 return;
1540
1541 var root = document.documentElement;
1542 var height = root.offsetHeight;
1543 if (root.offsetWidth < root.scrollWidth) {
1544 // We have a horizontal scrollbar. Include it in the height.
1545 var dummyNode = document.createElement('div');
1546 dummyNode.style.overflow = 'scroll';
1547 document.body.appendChild(dummyNode);
1548 var scrollbarWidth = dummyNode.offsetHeight - dummyNode.clientHeight;
1549 document.body.removeChild(dummyNode);
1550 height += scrollbarWidth;
1551 }
1552 parent.postMessage({command: 'heightChanged', height: height}, '*')
1553}
1554
1555if (window != parent)
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001556 window.addEventListener('blur', ui.popup.hide);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001557
1558document.addEventListener('keydown', function(e) {
1559 if (e.keyIdentifier == 'U+003F' || e.keyIdentifier == 'U+00BF') {
1560 // WebKit MAC retursn 3F. WebKit WIN returns BF. This is a bug!
1561 // ? key
1562 showLegend();
1563 } else if (e.keyIdentifier == 'U+001B') {
1564 // escape key
1565 hideLegend();
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001566 ui.popup.hide();
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001567 }
1568}, false);
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001569
1570window.addEventListener('load', function() {
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +01001571 resourceLoader = new loader.Loader();
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001572 resourceLoader.load();
1573}, false);