blob: b426032fb84f9be404c968197082dae5644cb5e9 [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';
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +010034var TEST_URL_BASE_PATH_FOR_BROWSING = 'http://src.chromium.org/viewvc/blink/trunk/LayoutTests/';
35var TEST_URL_BASE_PATH_FOR_XHR = 'http://src.chromium.org/blink/trunk/LayoutTests/';
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +000036var TEST_RESULTS_BASE_PATH = 'http://build.chromium.org/f/chromium/layout_test_results/';
37var GPU_RESULTS_BASE_PATH = 'http://chromium-browser-gpu-tests.commondatastorage.googleapis.com/runs/'
38
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +010039var RELEASE_TIMEOUT = 6;
40var DEBUG_TIMEOUT = 12;
41var SLOW_MULTIPLIER = 5;
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +000042
43// FIXME: Figure out how to make this not be hard-coded.
Torne (Richard Coles)5267f702013-06-11 10:57:24 +010044// Probably just include in the results.json files and get it from there.
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +000045var VIRTUAL_SUITES = {
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +010046 'virtual/gpu/fast/canvas': 'fast/canvas',
Torne (Richard Coles)5267f702013-06-11 10:57:24 +010047 'virtual/gpu/canvas/philip': 'canvas/philip',
48 'virtual/threaded/compositing/visibility': 'compositing/visibility',
49 'virtual/threaded/compositing/webgl': 'compositing/webgl',
50 'virtual/gpu/fast/hidpi': 'fast/hidpi',
51 'virtual/softwarecompositing': 'compositing',
52 'virtual/deferred/fast/images': 'fast/images',
53 'virtual/gpu/compositedscrolling/overflow': 'compositing/overflow',
54 'virtual/gpu/compositedscrolling/scrollbars': 'scrollbars',
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +000055};
56
Torne (Richard Coles)5267f702013-06-11 10:57:24 +010057var ACTUAL_RESULT_SUFFIXES = ['expected.txt', 'expected.png', 'actual.txt', 'actual.png', 'diff.txt', 'diff.png', 'wdiff.html', 'crash-log.txt'];
58
59var EXPECTATIONS_ORDER = ACTUAL_RESULT_SUFFIXES.filter(function(suffix) {
60 return !string.endsWith(suffix, 'png');
61}).map(function(suffix) {
62 return suffix.split('.')[0]
63});
64
Torne (Richard Coles)926b0012013-03-28 15:32:48 +000065var resourceLoader;
66
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +010067function generatePage(historyInstance)
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +000068{
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +010069 if (historyInstance.crossDashboardState.useTestData)
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +000070 return;
71
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +000072 document.body.innerHTML = '<div id="loading-ui">LOADING...</div>';
Torne (Richard Coles)926b0012013-03-28 15:32:48 +000073 resourceLoader.showErrors();
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +000074
75 // tests expands to all tests that match the CSV list.
76 // result expands to all tests that ever have the given result
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +010077 if (historyInstance.dashboardSpecificState.tests || historyInstance.dashboardSpecificState.result)
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +000078 generatePageForIndividualTests(individualTests());
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +000079 else
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +010080 generatePageForBuilder(historyInstance.dashboardSpecificState.builder || currentBuilderGroup().defaultBuilder());
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +000081
Torne (Richard Coles)926b0012013-03-28 15:32:48 +000082 for (var builder in currentBuilders())
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +000083 processTestResultsForBuilderAsync(builder);
84
85 postHeightChangedMessage();
86}
87
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +010088function handleValidHashParameter(historyInstance, key, value)
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +000089{
90 switch(key) {
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +010091 case 'result':
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +000092 case 'tests':
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +010093 history.validateParameter(historyInstance.dashboardSpecificState, key, value,
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +000094 function() {
Torne (Richard Coles)926b0012013-03-28 15:32:48 +000095 return string.isValidName(value);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +000096 });
97 return true;
98
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +000099 case 'builder':
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100100 history.validateParameter(historyInstance.dashboardSpecificState, key, value,
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000101 function() {
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000102 return value in currentBuilders();
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000103 });
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000104
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000105 return true;
106
107 case 'sortColumn':
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 // Get all possible headers since the actual used set of headers
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100111 // depends on the values in historyInstance.dashboardSpecificState, which are currently being set.
Ben Murdoch83750172013-07-24 10:36:59 +0100112 var getAllTableHeaders = true;
113 var headers = tableHeaders(getAllTableHeaders);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000114 for (var i = 0; i < headers.length; i++) {
115 if (value == sortColumnFromTableHeader(headers[i]))
116 return true;
117 }
118 return value == 'test' || value == 'builder';
119 });
120 return true;
121
122 case 'sortOrder':
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100123 history.validateParameter(historyInstance.dashboardSpecificState, key, value,
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000124 function() {
125 return value == FORWARD || value == BACKWARD;
126 });
127 return true;
128
129 case 'resultsHeight':
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000130 case 'revision':
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100131 history.validateParameter(historyInstance.dashboardSpecificState, key, Number(value),
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000132 function() {
133 return value.match(/^\d+$/);
134 });
135 return true;
136
137 case 'showChrome':
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000138 case 'showExpectations':
139 case 'showFlaky':
140 case 'showLargeExpectations':
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100141 case 'showNonFlaky':
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000142 case 'showSlow':
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100143 case 'showSkip':
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000144 case 'showUnexpectedPasses':
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100145 case 'showWontFix':
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100146 historyInstance.dashboardSpecificState[key] = value == 'true';
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000147 return true;
148
149 default:
150 return false;
151 }
152}
153
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100154// @param {Object} params New or modified query parameters as key: value.
155function handleQueryParameterChange(historyInstance, params)
156{
157 for (key in params) {
158 if (key == 'tests') {
159 // Entering cross-builder view, only keep valid keys for that view.
160 for (var currentKey in historyInstance.dashboardSpecificState) {
161 if (isInvalidKeyForCrossBuilderView(currentKey)) {
162 delete historyInstance.dashboardSpecificState[currentKey];
163 }
164 }
165 } else if (isInvalidKeyForCrossBuilderView(key)) {
166 delete historyInstance.dashboardSpecificState.tests;
167 delete historyInstance.dashboardSpecificState.result;
168 }
169 }
170
171 return true;
172}
173
174var defaultDashboardSpecificStateValues = {
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000175 sortOrder: BACKWARD,
176 sortColumn: 'flakiness',
177 showExpectations: false,
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100178 // FIXME: Show flaky tests by default if you have a builder picked.
179 // Ideally, we'd fix the dashboard to not pick a default builder and have
180 // you pick one. In the interim, this is a good way to make the default
181 // page load faster since we don't need to generate/layout a large table.
182 showFlaky: false,
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000183 showLargeExpectations: false,
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000184 showChrome: true,
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100185 showWontFix: false,
186 showNonFlaky: false,
187 showSkip: false,
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100188 showUnexpectedPasses: false,
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000189 resultsHeight: 300,
190 revision: null,
191 tests: '',
192 result: '',
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000193 builder: null
194};
195
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100196var DB_SPECIFIC_INVALIDATING_PARAMETERS = {
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000197 'tests' : 'builder',
198 'testType': 'builder',
199 'group': 'builder'
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000200};
201
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100202var flakinessConfig = {
203 defaultStateValues: defaultDashboardSpecificStateValues,
204 generatePage: generatePage,
205 handleValidHashParameter: handleValidHashParameter,
206 handleQueryParameterChange: handleQueryParameterChange,
207 invalidatingHashParameters: DB_SPECIFIC_INVALIDATING_PARAMETERS
208};
209
210// FIXME(jparent): Eventually remove all usage of global history object.
211var g_history = new history.History(flakinessConfig);
212g_history.parseCrossDashboardParameters();
213
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000214//////////////////////////////////////////////////////////////////////////////
215// GLOBALS
216//////////////////////////////////////////////////////////////////////////////
217
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000218var g_perBuilderFailures = {};
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000219// Maps test path to an array of {builder, testResults} objects.
220var g_testToResultsMap = {};
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000221
222function createResultsObjectForTest(test, builder)
223{
224 return {
225 test: test,
226 builder: builder,
227 // HTML for display of the results in the flakiness column
228 html: '',
229 flips: 0,
230 slowestTime: 0,
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000231 isFlaky: false,
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100232 bugs: [],
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000233 expectations : '',
234 rawResults: '',
235 // List of all the results the test actually has.
236 actualResults: []
237 };
238}
239
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000240var TestTrie = function(builders, resultsByBuilder)
241{
242 this._trie = {};
243
244 for (var builder in builders) {
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100245 if (!resultsByBuilder[builder]) {
246 console.warn("No results for builder: ", builder)
247 continue;
248 }
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000249 var testsForBuilder = resultsByBuilder[builder].tests;
250 for (var test in testsForBuilder)
251 this._addTest(test.split('/'), this._trie);
252 }
253}
254
255TestTrie.prototype.forEach = function(callback, startingTriePath)
256{
257 var testsTrie = this._trie;
258 if (startingTriePath) {
259 var splitPath = startingTriePath.split('/');
260 while (splitPath.length && testsTrie)
261 testsTrie = testsTrie[splitPath.shift()];
262 }
263
264 if (!testsTrie)
265 return;
266
267 function traverse(trie, triePath) {
268 if (trie == true)
269 callback(triePath);
270 else {
271 for (var member in trie)
272 traverse(trie[member], triePath ? triePath + '/' + member : member);
273 }
274 }
275 traverse(testsTrie, startingTriePath);
276}
277
278TestTrie.prototype._addTest = function(test, trie)
279{
280 var rootComponent = test.shift();
281 if (!test.length) {
282 if (!trie[rootComponent])
283 trie[rootComponent] = true;
284 return;
285 }
286
287 if (!trie[rootComponent] || trie[rootComponent] == true)
288 trie[rootComponent] = {};
289 this._addTest(test, trie[rootComponent]);
290}
291
292// Map of all tests to true values. This is just so we can have the list of
293// all tests across all the builders.
294var g_allTestsTrie;
295
296function getAllTestsTrie()
297{
298 if (!g_allTestsTrie)
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000299 g_allTestsTrie = new TestTrie(currentBuilders(), g_resultsByBuilder);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000300
301 return g_allTestsTrie;
302}
303
304// Returns an array of tests to be displayed in the individual tests view.
305// Note that a directory can be listed as a test, so we expand that into all
306// tests in the directory.
307function individualTests()
308{
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000309 if (g_history.dashboardSpecificState.result)
310 return allTestsWithResult(g_history.dashboardSpecificState.result);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000311
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000312 if (!g_history.dashboardSpecificState.tests)
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000313 return [];
314
315 return individualTestsForSubstringList();
316}
317
Ben Murdoch83750172013-07-24 10:36:59 +0100318function splitTestList()
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000319{
Ben Murdoch83750172013-07-24 10:36:59 +0100320 // Convert windows slashes to unix slashes and spaces/newlines to commas.
321 var tests = g_history.dashboardSpecificState.tests.replace(/\\/g, '/').replace('\n', ' ').replace(/\s+/g, ',');
322 return tests.split(',');
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000323}
324
325function individualTestsForSubstringList()
326{
Ben Murdoch83750172013-07-24 10:36:59 +0100327 var testList = splitTestList();
Torne (Richard Coles)521d96e2013-06-19 11:58:24 +0100328 // If listing a lot of tests, assume you've passed in an explicit list of tests
329 // instead of patterns to match against. The matching code below is super slow.
330 //
331 // Also, when showChrome is false, we're embedding the dashboard elsewhere and
332 // an explicit test list is passed in. In that case, we don't want
333 // a search for compositing/foo.html to also show virtual/softwarecompositing/foo.html.
334 if (testList.length > 10 || !g_history.dashboardSpecificState.showChrome)
335 return testList;
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000336
337 // Put the tests into an object first and then move them into an array
338 // as a way of deduping.
339 var testsMap = {};
340 for (var i = 0; i < testList.length; i++) {
341 var path = testList[i];
342
343 // Ignore whitespace entries as they'd match every test.
344 if (path.match(/^\s*$/))
345 continue;
346
347 var hasAnyMatches = false;
348 getAllTestsTrie().forEach(function(triePath) {
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000349 if (string.caseInsensitiveContains(triePath, path)) {
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000350 testsMap[triePath] = 1;
351 hasAnyMatches = true;
352 }
353 });
354
355 // If a path doesn't match any tests, then assume it's a full path
356 // to a test that passes on all builders.
357 if (!hasAnyMatches)
358 testsMap[path] = 1;
359 }
360
361 var testsArray = [];
362 for (var test in testsMap)
363 testsArray.push(test);
Torne (Richard Coles)521d96e2013-06-19 11:58:24 +0100364
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000365 return testsArray;
366}
367
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000368function allTestsWithResult(result)
369{
370 processTestRunsForAllBuilders();
371 var retVal = [];
372
373 getAllTestsTrie().forEach(function(triePath) {
374 for (var i = 0; i < g_testToResultsMap[triePath].length; i++) {
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100375 if (g_testToResultsMap[triePath][i].actualResults.indexOf(result.toUpperCase()) != -1) {
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000376 retVal.push(triePath);
377 break;
378 }
379 }
380 });
381
382 return retVal;
383}
384
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000385function processTestResultsForBuilderAsync(builder)
386{
387 setTimeout(function() { processTestRunsForBuilder(builder); }, 0);
388}
389
390function processTestRunsForAllBuilders()
391{
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000392 for (var builder in currentBuilders())
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000393 processTestRunsForBuilder(builder);
394}
395
396function processTestRunsForBuilder(builderName)
397{
398 if (g_perBuilderFailures[builderName])
399 return;
400
401 if (!g_resultsByBuilder[builderName]) {
402 console.error('No tests found for ' + builderName);
403 g_perBuilderFailures[builderName] = [];
404 return;
405 }
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100406
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000407 var failures = [];
408 var allTestsForThisBuilder = g_resultsByBuilder[builderName].tests;
409
410 for (var test in allTestsForThisBuilder) {
411 var resultsForTest = createResultsObjectForTest(test, builderName);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000412
413 var rawTest = g_resultsByBuilder[builderName].tests[test];
414 resultsForTest.rawTimes = rawTest.times;
415 var rawResults = rawTest.results;
416 resultsForTest.rawResults = rawResults;
417
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100418 if (rawTest.expected)
419 resultsForTest.expectations = rawTest.expected;
420
421 if (rawTest.bugs)
422 resultsForTest.bugs = rawTest.bugs;
423
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100424 var failureMap = g_resultsByBuilder[builderName][results.FAILURE_MAP];
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000425 // FIXME: Switch to resultsByBuild
426 var times = resultsForTest.rawTimes;
427 var numTimesSeen = 0;
428 var numResultsSeen = 0;
429 var resultsIndex = 0;
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100430 var resultsMap = {}
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000431
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100432 for (var i = 0; i < times.length; i++) {
433 numTimesSeen += times[i][results.RLE.LENGTH];
434
435 while (rawResults[resultsIndex] && numTimesSeen > (numResultsSeen + rawResults[resultsIndex][results.RLE.LENGTH])) {
436 numResultsSeen += rawResults[resultsIndex][results.RLE.LENGTH];
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000437 resultsIndex++;
438 }
439
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100440 if (rawResults && rawResults[resultsIndex]) {
441 var result = rawResults[resultsIndex][results.RLE.VALUE];
442 resultsMap[failureMap[result]] = true;
443 }
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000444
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100445 resultsForTest.slowestTime = Math.max(resultsForTest.slowestTime, times[i][results.RLE.VALUE]);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000446 }
447
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100448 resultsForTest.actualResults = Object.keys(resultsMap);
449
450 results.determineFlakiness(failureMap, rawResults, resultsForTest);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000451 failures.push(resultsForTest);
452
453 if (!g_testToResultsMap[test])
454 g_testToResultsMap[test] = [];
455 g_testToResultsMap[test].push(resultsForTest);
456 }
457
458 g_perBuilderFailures[builderName] = failures;
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000459}
460
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000461function linkHTMLToOpenWindow(url, text)
462{
463 return '<a href="' + url + '" target="_blank">' + text + '</a>';
464}
465
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000466// Returns whether the result for index'th result for testName on builder was
467// a failure.
468function isFailure(builder, testName, index)
469{
470 var currentIndex = 0;
471 var rawResults = g_resultsByBuilder[builder].tests[testName].results;
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100472 var failureMap = g_resultsByBuilder[builder][results.FAILURE_MAP];
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000473 for (var i = 0; i < rawResults.length; i++) {
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100474 currentIndex += rawResults[i][results.RLE.LENGTH];
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000475 if (currentIndex > index)
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100476 return results.isFailingResult(failureMap, rawResults[i][results.RLE.VALUE]);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000477 }
478 console.error('Index exceeds number of results: ' + index);
479}
480
481// Returns an array of indexes for all builds where this test failed.
482function indexesForFailures(builder, testName)
483{
484 var rawResults = g_resultsByBuilder[builder].tests[testName].results;
485 var buildNumbers = g_resultsByBuilder[builder].buildNumbers;
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100486 var failureMap = g_resultsByBuilder[builder][results.FAILURE_MAP];
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000487 var index = 0;
488 var failures = [];
489 for (var i = 0; i < rawResults.length; i++) {
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100490 var numResults = rawResults[i][results.RLE.LENGTH];
491 if (results.isFailingResult(failureMap, rawResults[i][results.RLE.VALUE])) {
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000492 for (var j = 0; j < numResults; j++)
493 failures.push(index + j);
494 }
495 index += numResults;
496 }
497 return failures;
498}
499
500// Returns the path to the failure log for this non-webkit test.
501function pathToFailureLog(testName)
502{
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000503 return '/steps/' + g_history.crossDashboardState.testType + '/logs/' + testName.split('.')[1]
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000504}
505
506function showPopupForBuild(e, builder, index, opt_testName)
507{
508 var html = '';
509
510 var time = g_resultsByBuilder[builder].secondsSinceEpoch[index];
511 if (time) {
512 var date = new Date(time * 1000);
513 html += date.toLocaleDateString() + ' ' + date.toLocaleTimeString();
514 }
515
516 var buildNumber = g_resultsByBuilder[builder].buildNumbers[index];
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100517 var master = builders.master(builder);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000518 var buildBasePath = master.logPath(builder, buildNumber);
519
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100520 html += '<ul><li>' + linkHTMLToOpenWindow(buildBasePath, 'Build log');
521
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100522 if (g_resultsByBuilder[builder][results.BLINK_REVISIONS])
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100523 html += '</li><li>Blink: ' + ui.html.blinkRevisionLink(g_resultsByBuilder[builder], index) + '</li>';
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000524
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100525 html += '</li><li>Chromium: ' + ui.html.chromiumRevisionLink(g_resultsByBuilder[builder], index) + '</li>';
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000526
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100527 var chromeRevision = g_resultsByBuilder[builder].chromeRevision[index];
528 if (chromeRevision && g_history.isLayoutTestResults()) {
529 html += '<li><a href="' + TEST_RESULTS_BASE_PATH + currentBuilders()[builder] +
530 '/' + chromeRevision + '/layout-test-results.zip">layout-test-results.zip</a></li>';
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000531 }
532
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000533 if (!g_history.isLayoutTestResults() && opt_testName && isFailure(builder, opt_testName, index))
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000534 html += '<li>' + linkHTMLToOpenWindow(buildBasePath + pathToFailureLog(opt_testName), 'Failure log') + '</li>';
535
536 html += '</ul>';
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000537 ui.popup.show(e.target, html);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000538}
539
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100540function classNameForFailureString(failure)
541{
542 return failure.replace(/(\+|\ )/, '');
543}
544
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000545function htmlForTestResults(test)
546{
547 var html = '';
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100548 var testResults = test.rawResults.concat();
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000549 var times = test.rawTimes.concat();
550 var builder = test.builder;
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100551 var master = builders.master(builder);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000552 var buildNumbers = g_resultsByBuilder[builder].buildNumbers;
553
554 var indexToReplaceCurrentResult = -1;
555 var indexToReplaceCurrentTime = -1;
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000556 for (var i = 0; i < buildNumbers.length; i++) {
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100557 var currentResultArray, currentTimeArray, innerHTML, resultString;
558
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000559 if (i > indexToReplaceCurrentResult) {
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100560 currentResultArray = testResults.shift();
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000561 if (currentResultArray) {
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100562 resultString = g_resultsByBuilder[builder][results.FAILURE_MAP][currentResultArray[results.RLE.VALUE]];
563 indexToReplaceCurrentResult += currentResultArray[results.RLE.LENGTH];
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000564 } else {
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100565 resultString = results.NO_DATA;
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000566 indexToReplaceCurrentResult += buildNumbers.length;
567 }
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000568 }
569
570 if (i > indexToReplaceCurrentTime) {
571 currentTimeArray = times.shift();
572 var currentTime = 0;
573 if (currentResultArray) {
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100574 currentTime = currentTimeArray[results.RLE.VALUE];
575 indexToReplaceCurrentTime += currentTimeArray[results.RLE.LENGTH];
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000576 } else
577 indexToReplaceCurrentTime += buildNumbers.length;
578
579 innerHTML = currentTime || '&nbsp;';
580 }
581
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100582 html += '<td title="' + resultString + '. Click for more info." class="results ' + classNameForFailureString(resultString) +
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100583 '" onclick=\'showPopupForBuild(event, "' + builder + '",' + i + ',"' + test.test + '")\'>' + innerHTML;
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000584 }
585 return html;
586}
587
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100588function shouldShowTest(testResult)
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000589{
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100590 if (!g_history.isLayoutTestResults())
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100591 return true;
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100592
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100593 if (testResult.expectations == 'WONTFIX')
594 return g_history.dashboardSpecificState.showWontFix;
595
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100596 if (testResult.expectations == results.SKIP)
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100597 return g_history.dashboardSpecificState.showSkip;
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000598
599 if (testResult.isFlaky)
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100600 return g_history.dashboardSpecificState.showFlaky;
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000601
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100602 return g_history.dashboardSpecificState.showNonFlaky;
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000603}
604
605function createBugHTML(test)
606{
607 var symptom = test.isFlaky ? 'flaky' : 'failing';
608 var title = encodeURIComponent('Layout Test ' + test.test + ' is ' + symptom);
609 var description = encodeURIComponent('The following layout test is ' + symptom + ' on ' +
610 '[insert platform]\n\n' + test.test + '\n\nProbable cause:\n\n' +
611 '[insert probable cause]');
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100612
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100613 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 +0100614 return '<a href="' + url + '">File new bug</a>';
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000615}
616
617function isCrossBuilderView()
618{
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100619 return g_history.dashboardSpecificState.tests || g_history.dashboardSpecificState.result;
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000620}
621
622function tableHeaders(opt_getAll)
623{
624 var headers = [];
625 if (isCrossBuilderView() || opt_getAll)
626 headers.push('builder');
627
628 if (!isCrossBuilderView() || opt_getAll)
629 headers.push('test');
630
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000631 if (g_history.isLayoutTestResults() || opt_getAll)
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100632 headers.push('bugs', 'expectations');
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000633
634 headers.push('slowest run', 'flakiness (numbers are runtimes in seconds)');
635 return headers;
636}
637
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100638function linkifyBugs(bugs)
639{
640 var html = '';
641 bugs.forEach(function(bug) {
642 var bugHtml;
643 if (string.startsWith(bug, 'Bug('))
644 bugHtml = bug;
645 else
646 bugHtml = '<a href="http://' + bug + '">' + bug + '</a>';
647 html += '<div>' + bugHtml + '</div>'
648 });
649 return html;
650}
651
Ben Murdoch83750172013-07-24 10:36:59 +0100652function htmlForSingleTestRow(test, showBuilderNames)
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000653{
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000654 var headers = tableHeaders();
655 var html = '';
656 for (var i = 0; i < headers.length; i++) {
657 var header = headers[i];
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000658 if (string.startsWith(header, 'test') || string.startsWith(header, 'builder')) {
Ben Murdoch83750172013-07-24 10:36:59 +0100659 var testCellClassName = 'test-link' + (showBuilderNames ? ' builder-name' : '');
660 var testCellHTML = showBuilderNames ? test.builder : '<span class="link" onclick="g_history.setQueryParameter(\'tests\',\'' + test.test +'\');">' + test.test + '</span>';
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000661 html += '<tr><td class="' + testCellClassName + '">' + testCellHTML;
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000662 } else if (string.startsWith(header, 'bugs'))
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100663 // FIXME: linkify bugs.
664 html += '<td class=options-container>' + (linkifyBugs(test.bugs) || createBugHTML(test));
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000665 else if (string.startsWith(header, 'expectations'))
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000666 html += '<td class=options-container>' + test.expectations;
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000667 else if (string.startsWith(header, 'slowest'))
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000668 html += '<td>' + (test.slowestTime ? test.slowestTime + 's' : '');
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000669 else if (string.startsWith(header, 'flakiness'))
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000670 html += htmlForTestResults(test);
671 }
672 return html;
673}
674
675function sortColumnFromTableHeader(headerText)
676{
677 return headerText.split(' ', 1)[0];
678}
679
680function htmlForTableColumnHeader(headerName, opt_fillColSpan)
681{
682 // Use the first word of the header title as the sortkey
683 var thisSortValue = sortColumnFromTableHeader(headerName);
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000684 var arrowHTML = thisSortValue == g_history.dashboardSpecificState.sortColumn ?
685 '<span class=' + g_history.dashboardSpecificState.sortOrder + '>' + (g_history.dashboardSpecificState.sortOrder == FORWARD ? '&uarr;' : '&darr;' ) + '</span>' : '';
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000686 return '<th sortValue=' + thisSortValue +
687 // Extend last th through all the rest of the columns.
688 (opt_fillColSpan ? ' colspan=10000' : '') +
689 // Extra span here is so flex boxing actually centers.
690 // There's probably a better way to do this with CSS only though.
691 '><div class=table-header-content><span></span>' + arrowHTML +
692 '<span class=header-text>' + headerName + '</span>' + arrowHTML + '</div></th>';
693}
694
695function htmlForTestTable(rowsHTML, opt_excludeHeaders)
696{
697 var html = '<table class=test-table>';
698 if (!opt_excludeHeaders) {
699 html += '<thead><tr>';
700 var headers = tableHeaders();
701 for (var i = 0; i < headers.length; i++)
702 html += htmlForTableColumnHeader(headers[i], i == headers.length - 1);
703 html += '</tr></thead>';
704 }
705 return html + '<tbody>' + rowsHTML + '</tbody></table>';
706}
707
708function appendHTML(html)
709{
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000710 // InnerHTML to a div that's not in the document. This is
711 // ~300ms faster in Safari 4 and Chrome 4 on mac.
712 var div = document.createElement('div');
713 div.innerHTML = html;
714 document.body.appendChild(div);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000715 postHeightChangedMessage();
716}
717
718function alphanumericCompare(column, reverse)
719{
720 return reversibleCompareFunction(function(a, b) {
721 // Put null entries at the bottom
722 var a = a[column] ? String(a[column]) : 'z';
723 var b = b[column] ? String(b[column]) : 'z';
724
725 if (a < b)
726 return -1;
727 else if (a == b)
728 return 0;
729 else
730 return 1;
731 }, reverse);
732}
733
734function numericSort(column, reverse)
735{
736 return reversibleCompareFunction(function(a, b) {
737 a = parseFloat(a[column]);
738 b = parseFloat(b[column]);
739 return a - b;
740 }, reverse);
741}
742
743function reversibleCompareFunction(compare, reverse)
744{
745 return function(a, b) {
746 return compare(reverse ? b : a, reverse ? a : b);
747 };
748}
749
750function changeSort(e)
751{
752 var target = e.currentTarget;
753 e.preventDefault();
754
755 var sortValue = target.getAttribute('sortValue');
756 while (target && target.tagName != 'TABLE')
757 target = target.parentNode;
758
759 var sort = 'sortColumn';
760 var orderKey = 'sortOrder';
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000761 if (sortValue == g_history.dashboardSpecificState[sort] && g_history.dashboardSpecificState[orderKey] == FORWARD)
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000762 order = BACKWARD;
763 else
764 order = FORWARD;
765
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000766 g_history.setQueryParameter(sort, sortValue, orderKey, order);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000767}
768
769function sortTests(tests, column, order)
770{
771 var resultsProperty, sortFunctionGetter;
772 if (column == 'flakiness') {
773 sortFunctionGetter = numericSort;
774 resultsProperty = 'flips';
775 } else if (column == 'slowest') {
776 sortFunctionGetter = numericSort;
777 resultsProperty = 'slowestTime';
778 } else {
779 sortFunctionGetter = alphanumericCompare;
780 resultsProperty = column;
781 }
782
783 tests.sort(sortFunctionGetter(resultsProperty, order == BACKWARD));
784}
785
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000786function htmlForIndividualTestOnAllBuilders(test)
787{
788 processTestRunsForAllBuilders();
789
790 var testResults = g_testToResultsMap[test];
791 if (!testResults)
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100792 return '<div class="not-found">Test not found. Either it does not exist, is skipped or passes on all recorded runs.</div>';
793
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000794 var html = '';
795 var shownBuilders = [];
796 for (var j = 0; j < testResults.length; j++) {
797 shownBuilders.push(testResults[j].builder);
Ben Murdoch83750172013-07-24 10:36:59 +0100798 var showBuilderNames = true;
799 html += htmlForSingleTestRow(testResults[j], showBuilderNames);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000800 }
801
802 var skippedBuilders = []
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000803 for (builder in currentBuilders()) {
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000804 if (shownBuilders.indexOf(builder) == -1)
805 skippedBuilders.push(builder);
806 }
807
808 var skippedBuildersHtml = '';
809 if (skippedBuilders.length) {
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100810 skippedBuildersHtml = '<div>The following builders either don\'t run this test (e.g. it\'s skipped) or all recorded runs passed:</div>' +
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000811 '<div class=skipped-builder-list><div class=skipped-builder>' + skippedBuilders.join('</div><div class=skipped-builder>') + '</div></div>';
812 }
813
814 return htmlForTestTable(html) + skippedBuildersHtml;
815}
816
817function htmlForIndividualTestOnAllBuildersWithResultsLinks(test)
818{
819 processTestRunsForAllBuilders();
820
821 var testResults = g_testToResultsMap[test];
822 var html = '';
823 html += htmlForIndividualTestOnAllBuilders(test);
824
825 html += '<div class=expectations test=' + test + '><div>' +
826 linkHTMLToToggleState('showExpectations', 'results')
827
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000828 if (g_history.isLayoutTestResults() || g_history.isGPUTestResults()) {
829 if (g_history.isLayoutTestResults())
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000830 html += ' | ' + linkHTMLToToggleState('showLargeExpectations', 'large thumbnails');
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000831 html += ' | <b>Only shows actual results/diffs from the most recent *failure* on each bot.</b>';
832 } else {
833 html += ' | <span>Results height:<input ' +
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000834 'onchange="g_history.setQueryParameter(\'resultsHeight\',this.value)" value="' +
835 g_history.dashboardSpecificState.resultsHeight + '" style="width:2.5em">px</span>';
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000836 }
837 html += '</div></div>';
838 return html;
839}
840
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000841function maybeAddPngChecksum(expectationDiv, pngUrl)
842{
843 // pngUrl gets served from the browser cache since we just loaded it in an
844 // <img> tag.
845 loader.request(pngUrl,
846 function(xhr) {
847 // Convert the first 2k of the response to a byte string.
848 var bytes = xhr.responseText.substring(0, 2048);
849 for (var position = 0; position < bytes.length; ++position)
850 bytes[position] = bytes[position] & 0xff;
851
852 // Look for the comment.
853 var commentKey = 'tEXtchecksum\x00';
854 var checksumPosition = bytes.indexOf(commentKey);
855 if (checksumPosition == -1)
856 return;
857
858 var checksum = bytes.substring(checksumPosition + commentKey.length, checksumPosition + commentKey.length + 32);
859 var checksumContainer = document.createElement('span');
860 checksumContainer.innerText = 'Embedded checksum: ' + checksum;
861 checksumContainer.setAttribute('class', 'pngchecksum');
862 expectationDiv.parentNode.appendChild(checksumContainer);
863 },
864 function(xhr) {},
865 true);
866}
867
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100868function getOrCreate(className, parent)
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000869{
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100870 var element = parent.querySelector('.' + className);
871 if (!element) {
872 element = document.createElement('div');
873 element.className = className;
874 parent.appendChild(element);
875 }
876 return element;
877}
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000878
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100879function handleExpectationsItemLoad(title, item, itemType, parent)
880{
881 item.className = 'expectation';
882 if (g_history.dashboardSpecificState.showLargeExpectations)
883 item.className += ' large';
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000884
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100885 var titleContainer = document.createElement('h3');
886 titleContainer.className = 'expectations-title';
887 titleContainer.textContent = title;
888
889 var itemContainer = document.createElement('span');
890 itemContainer.appendChild(titleContainer);
891 itemContainer.className = 'expectations-item ' + title;
892 itemContainer.appendChild(item);
893
894 // Separate text and image results into separate divs..
895 var typeContainer = getOrCreate(itemType, parent);
896
897 // Insert results in a consistent order.
898 var index = EXPECTATIONS_ORDER.indexOf(title);
899 while (index < EXPECTATIONS_ORDER.length) {
900 index++;
901 var elementAfter = typeContainer.querySelector('.' + EXPECTATIONS_ORDER[index]);
902 if (elementAfter) {
903 typeContainer.insertBefore(itemContainer, elementAfter);
904 break;
905 }
906 }
907 if (!itemContainer.parentNode)
908 typeContainer.appendChild(itemContainer);
909
910 handleFinishedLoadingExpectations(parent);
911}
912
913function addExpectationItem(expectationsContainers, parentContainer, url, opt_builder)
914{
915 // Group expectations by builder, putting test and reference files first.
916 var builder = opt_builder || "Test and reference files";
917 var container = expectationsContainers[builder];
918
919 if (!container) {
920 container = document.createElement('div');
921 container.className = 'expectations-container';
922 container.setAttribute('data-builder', builder);
923 parentContainer.appendChild(container);
924 expectationsContainers[builder] = container;
925 }
926
927 var numUnloaded = container.getAttribute('data-unloaded') || 0;
928 container.setAttribute('data-unloaded', ++numUnloaded);
929
930 var isImage = url.match(/\.png$/);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000931
932 var appendExpectationsItem = function(item) {
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100933 var itemType = isImage ? 'image' : 'text';
934 handleExpectationsItemLoad(expectationsTitle(url), item, itemType, container);
935 };
936
937 var handleLoadError = function() {
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000938 handleFinishedLoadingExpectations(container);
939 };
940
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100941 if (isImage) {
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100942 var dummyNode = document.createElement('img');
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000943 dummyNode.onload = function() {
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100944 var item = dummyNode;
945 maybeAddPngChecksum(item, url);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000946 appendExpectationsItem(item);
947 }
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100948 dummyNode.onerror = handleLoadError;
949 dummyNode.src = url;
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000950 } else {
951 loader.request(url,
952 function(xhr) {
953 var item = document.createElement('pre');
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100954 if (string.endsWith(url, '-wdiff.html'))
955 item.innerHTML = xhr.responseText;
956 else
957 item.textContent = xhr.responseText;
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000958 appendExpectationsItem(item);
959 },
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100960 handleLoadError);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000961 }
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000962}
963
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000964function handleFinishedLoadingExpectations(container)
965{
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100966 var numUnloaded = container.getAttribute('data-unloaded') - 1;
967 container.setAttribute('data-unloaded', numUnloaded);
968 if (numUnloaded)
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000969 return;
970
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100971 if (!container.firstChild) {
972 container.remove();
973 return;
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000974 }
975
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100976 var builder = container.getAttribute('data-builder');
977 if (!builder)
978 return;
979
980 var header = document.createElement('h2');
981 header.textContent = builder;
982 container.insertBefore(header, container.firstChild);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000983}
984
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100985function expectationsTitle(url)
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000986{
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100987 var matchingSuffixes = ACTUAL_RESULT_SUFFIXES.filter(function(suffix) {
988 return string.endsWith(url, suffix);
989 });
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000990
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100991 if (matchingSuffixes.length)
992 return matchingSuffixes[0].split('.')[0];
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000993
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100994 var parts = url.split('/');
995 return parts[parts.length - 1];
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000996}
997
998function loadExpectations(expectationsContainer)
999{
1000 var test = expectationsContainer.getAttribute('test');
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001001 if (g_history.isLayoutTestResults())
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001002 loadExpectationsLayoutTests(test, expectationsContainer);
1003 else {
Torne (Richard Coles)5267f702013-06-11 10:57:24 +01001004 var testResults = g_testToResultsMap[test];
1005 for (var i = 0; i < testResults.length; i++)
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001006 if (g_history.isGPUTestResults())
Torne (Richard Coles)5267f702013-06-11 10:57:24 +01001007 loadGPUResultsForBuilder(testResults[i].builder, test, expectationsContainer);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001008 else
Torne (Richard Coles)5267f702013-06-11 10:57:24 +01001009 loadNonWebKitResultsForBuilder(testResults[i].builder, test, expectationsContainer);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001010 }
1011}
1012
1013function gpuResultsPath(chromeRevision, builder)
1014{
1015 return chromeRevision + '_' + builder.replace(/[^A-Za-z0-9]+/g, '_');
1016}
1017
1018function loadGPUResultsForBuilder(builder, test, expectationsContainer)
1019{
1020 var container = document.createElement('div');
1021 container.className = 'expectations-container';
1022 container.innerHTML = '<div><b>' + builder + '</b></div>';
1023 expectationsContainer.appendChild(container);
1024
1025 var failureIndex = indexesForFailures(builder, test)[0];
1026
1027 var buildNumber = g_resultsByBuilder[builder].buildNumbers[failureIndex];
Torne (Richard Coles)5267f702013-06-11 10:57:24 +01001028 var pathToLog = builders.master(builder).logPath(builder, buildNumber) + pathToFailureLog(test);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001029
1030 var chromeRevision = g_resultsByBuilder[builder].chromeRevision[failureIndex];
1031 var resultsUrl = GPU_RESULTS_BASE_PATH + gpuResultsPath(chromeRevision, builder);
1032 var filename = test.split(/\./)[1] + '.png';
1033
1034 appendNonWebKitResults(container, pathToLog, 'non-webkit-results');
1035 appendNonWebKitResults(container, resultsUrl + '/gen/' + filename, 'gpu-test-results', 'Generated');
1036 appendNonWebKitResults(container, resultsUrl + '/ref/' + filename, 'gpu-test-results', 'Reference');
1037 appendNonWebKitResults(container, resultsUrl + '/diff/' + filename, 'gpu-test-results', 'Diff');
1038}
1039
1040function loadNonWebKitResultsForBuilder(builder, test, expectationsContainer)
1041{
1042 var failureIndexes = indexesForFailures(builder, test);
1043 var container = document.createElement('div');
1044 container.innerHTML = '<div><b>' + builder + '</b></div>';
1045 expectationsContainer.appendChild(container);
1046 for (var i = 0; i < failureIndexes.length; i++) {
1047 // FIXME: This doesn't seem to work anymore. Did the paths change?
Ben Murdoch83750172013-07-24 10:36:59 +01001048 // Once that's resolved, see if we need to try each gtest modifier prefix as well.
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001049 var buildNumber = g_resultsByBuilder[builder].buildNumbers[failureIndexes[i]];
Torne (Richard Coles)5267f702013-06-11 10:57:24 +01001050 var pathToLog = builders.master(builder).logPath(builder, buildNumber) + pathToFailureLog(test);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001051 appendNonWebKitResults(container, pathToLog, 'non-webkit-results');
1052 }
1053}
1054
1055function appendNonWebKitResults(container, url, itemClassName, opt_title)
1056{
1057 // Use a script tag to detect whether the URL 404s.
1058 // Need to use a script tag since the URL is cross-domain.
1059 var dummyNode = document.createElement('script');
1060 dummyNode.src = url;
1061
1062 dummyNode.onload = function() {
1063 var item = document.createElement('iframe');
1064 item.src = dummyNode.src;
1065 item.className = itemClassName;
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001066 item.style.height = g_history.dashboardSpecificState.resultsHeight + 'px';
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001067
1068 if (opt_title) {
1069 var childContainer = document.createElement('div');
1070 childContainer.style.display = 'inline-block';
1071 var title = document.createElement('div');
1072 title.textContent = opt_title;
1073 childContainer.appendChild(title);
1074 childContainer.appendChild(item);
1075 container.replaceChild(childContainer, dummyNode);
1076 } else
1077 container.replaceChild(item, dummyNode);
1078 }
1079 dummyNode.onerror = function() {
1080 container.removeChild(dummyNode);
1081 }
1082
1083 container.appendChild(dummyNode);
1084}
1085
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001086function lookupVirtualTestSuite(test) {
1087 for (var suite in VIRTUAL_SUITES) {
1088 if (test.indexOf(suite) != -1)
1089 return suite;
1090 }
1091 return '';
1092}
1093
1094function baseTest(test, suite) {
1095 base = VIRTUAL_SUITES[suite];
1096 return base ? test.replace(suite, base) : test;
1097}
1098
Torne (Richard Coles)5267f702013-06-11 10:57:24 +01001099function loadTestAndReferenceFiles(expectationsContainers, expectationsContainer, test) {
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001100 var testWithoutSuffix = test.substring(0, test.lastIndexOf('.'));
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001101 var reftest_html_file = testWithoutSuffix + "-expected.html";
1102 var reftest_mismatch_html_file = testWithoutSuffix + "-expected-mismatch.html";
Torne (Richard Coles)5267f702013-06-11 10:57:24 +01001103
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001104 var suite = lookupVirtualTestSuite(test);
Torne (Richard Coles)5267f702013-06-11 10:57:24 +01001105 if (suite) {
1106 loadTestAndReferenceFiles(expectationsContainers, expectationsContainer, baseTest(test, suite));
1107 return;
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001108 }
1109
Torne (Richard Coles)5267f702013-06-11 10:57:24 +01001110 addExpectationItem(expectationsContainers, expectationsContainer, TEST_URL_BASE_PATH_FOR_XHR + test);
1111 addExpectationItem(expectationsContainers, expectationsContainer, TEST_URL_BASE_PATH_FOR_XHR + reftest_html_file);
1112 addExpectationItem(expectationsContainers, expectationsContainer, TEST_URL_BASE_PATH_FOR_XHR + reftest_mismatch_html_file);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001113}
1114
1115function loadExpectationsLayoutTests(test, expectationsContainer)
1116{
1117 // Map from file extension to container div for expectations of that type.
1118 var expectationsContainers = {};
Torne (Richard Coles)5267f702013-06-11 10:57:24 +01001119 loadTestAndReferenceFiles(expectationsContainers, expectationsContainer, test);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001120
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001121 var testWithoutSuffix = test.substring(0, test.lastIndexOf('.'));
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001122
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001123 for (var builder in currentBuilders()) {
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +01001124 var actualResultsBase = TEST_RESULTS_BASE_PATH + currentBuilders()[builder] + '/results/layout-test-results/';
Torne (Richard Coles)5267f702013-06-11 10:57:24 +01001125 ACTUAL_RESULT_SUFFIXES.forEach(function(suffix) {{
1126 addExpectationItem(expectationsContainers, expectationsContainer, actualResultsBase + testWithoutSuffix + '-' + suffix, builder);
1127 }})
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001128 }
1129
1130 // Add a clearing element so floated elements don't bleed out of their
1131 // containing block.
1132 var br = document.createElement('br');
1133 br.style.clear = 'both';
1134 expectationsContainer.appendChild(br);
1135}
1136
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001137function appendExpectations()
1138{
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001139 var expectations = g_history.dashboardSpecificState.showExpectations ? document.getElementsByClassName('expectations') : [];
Torne (Richard Coles)521d96e2013-06-19 11:58:24 +01001140 g_chunkedActionState = {
1141 items: expectations,
1142 index: 0
1143 }
1144 performChunkedAction(function(expectation) {
1145 loadExpectations(expectation);
1146 postHeightChangedMessage();
1147 },
1148 hideLoadingUI,
1149 expectations);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001150}
1151
1152function hideLoadingUI()
1153{
1154 var loadingDiv = $('loading-ui');
1155 if (loadingDiv)
1156 loadingDiv.style.display = 'none';
1157 postHeightChangedMessage();
1158}
1159
1160function generatePageForIndividualTests(tests)
1161{
1162 console.log('Number of tests: ' + tests.length);
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001163 if (g_history.dashboardSpecificState.showChrome)
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001164 appendHTML(htmlForNavBar());
Torne (Richard Coles)521d96e2013-06-19 11:58:24 +01001165 performChunkedAction(function(test) {
1166 appendHTML(htmlForIndividualTest(test));
1167 },
1168 appendExpectations,
1169 tests);
Torne (Richard Coles)5267f702013-06-11 10:57:24 +01001170 if (g_history.dashboardSpecificState.showChrome) {
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001171 $('tests-input').value = g_history.dashboardSpecificState.tests;
Torne (Richard Coles)5267f702013-06-11 10:57:24 +01001172 $('result-input').value = g_history.dashboardSpecificState.result;
1173 }
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001174}
1175
Torne (Richard Coles)521d96e2013-06-19 11:58:24 +01001176var g_chunkedActionRequestId;
1177function performChunkedAction(action, onComplete, items, opt_index) {
1178 if (g_chunkedActionRequestId)
1179 cancelAnimationFrame(g_chunkedActionRequestId);
1180
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001181 var index = opt_index || 0;
Torne (Richard Coles)521d96e2013-06-19 11:58:24 +01001182 g_chunkedActionRequestId = requestAnimationFrame(function() {
1183 if (index < items.length) {
1184 action(items[index]);
1185 performChunkedAction(action, onComplete, items, ++index);
1186 } else {
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001187 onComplete();
Torne (Richard Coles)521d96e2013-06-19 11:58:24 +01001188 }
1189 });
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001190}
1191
Torne (Richard Coles)521d96e2013-06-19 11:58:24 +01001192function htmlForIndividualTest(test)
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001193{
Torne (Richard Coles)521d96e2013-06-19 11:58:24 +01001194 var testNameHtml = '';
1195 if (g_history.dashboardSpecificState.showChrome) {
1196 if (g_history.isLayoutTestResults()) {
1197 var suite = lookupVirtualTestSuite(test);
1198 var base = suite ? baseTest(test, suite) : test;
1199 var versionControlUrl = TEST_URL_BASE_PATH_FOR_BROWSING + base;
1200 testNameHtml += '<h2>' + linkHTMLToOpenWindow(versionControlUrl, test) + '</h2>';
1201 } else
1202 testNameHtml += '<h2>' + test + '</h2>';
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001203 }
Torne (Richard Coles)521d96e2013-06-19 11:58:24 +01001204
1205 return testNameHtml + htmlForIndividualTestOnAllBuildersWithResultsLinks(test);
1206}
1207
1208function setTestsParameter(input)
1209{
1210 g_history.setQueryParameter('tests', input.value);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001211}
1212
1213function htmlForNavBar()
1214{
1215 var extraHTML = '';
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001216 var html = ui.html.testTypeSwitcher(false, extraHTML, isCrossBuilderView());
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001217 html += '<div class=forms><form id=result-form ' +
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001218 'onsubmit="g_history.setQueryParameter(\'result\', result.value);' +
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001219 'return false;">Show all tests with result: ' +
1220 '<input name=result placeholder="e.g. CRASH" id=result-input>' +
Torne (Richard Coles)521d96e2013-06-19 11:58:24 +01001221 '</form><span>Show tests on all platforms: </span>' +
1222 // Use a textarea to avoid the 32k limit on the length of inputs.
1223 '<textarea name=tests ' +
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001224 'placeholder="Comma or space-separated list of tests or partial ' +
1225 'paths to show test results across all builders, e.g., ' +
Torne (Richard Coles)521d96e2013-06-19 11:58:24 +01001226 'foo/bar.html,foo/baz,domstorage" id=tests-input onchange="setTestsParameter(this)" ' +
1227 'onkeydown="if (event.keyCode == 13) { setTestsParameter(this); return false; }"></textarea>' +
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001228 '<span class=link onclick="showLegend()">Show legend [type ?]</span></div>';
1229 return html;
1230}
1231
1232function checkBoxToToggleState(key, text)
1233{
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001234 var stateEnabled = g_history.dashboardSpecificState[key];
1235 return '<label><input type=checkbox ' + (stateEnabled ? 'checked ' : '') + 'onclick="g_history.setQueryParameter(\'' + key + '\', ' + !stateEnabled + ')">' + text + '</label> ';
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001236}
1237
1238function linkHTMLToToggleState(key, linkText)
1239{
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001240 var stateEnabled = g_history.dashboardSpecificState[key];
1241 return '<span class=link onclick="g_history.setQueryParameter(\'' + key + '\', ' + !stateEnabled + ')">' + (stateEnabled ? 'Hide' : 'Show') + ' ' + linkText + '</span>';
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001242}
1243
1244function headerForTestTableHtml()
1245{
1246 return '<h2 style="display:inline-block">Failing tests</h2>' +
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +01001247 checkBoxToToggleState('showFlaky', 'Show flaky') +
1248 checkBoxToToggleState('showNonFlaky', 'Show non-flaky') +
1249 checkBoxToToggleState('showSkip', 'Show Skip') +
1250 checkBoxToToggleState('showWontFix', 'Show WontFix');
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001251}
1252
1253function generatePageForBuilder(builderName)
1254{
1255 processTestRunsForBuilder(builderName);
1256
Torne (Richard Coles)5267f702013-06-11 10:57:24 +01001257 var filteredResults = g_perBuilderFailures[builderName].filter(shouldShowTest);
1258 sortTests(filteredResults, g_history.dashboardSpecificState.sortColumn, g_history.dashboardSpecificState.sortOrder);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001259
1260 var testsHTML = '';
Torne (Richard Coles)5267f702013-06-11 10:57:24 +01001261 if (filteredResults.length) {
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001262 var tableRowsHTML = '';
Ben Murdoch83750172013-07-24 10:36:59 +01001263 var showBuilderNames = false;
Torne (Richard Coles)5267f702013-06-11 10:57:24 +01001264 for (var i = 0; i < filteredResults.length; i++)
Ben Murdoch83750172013-07-24 10:36:59 +01001265 tableRowsHTML += htmlForSingleTestRow(filteredResults[i], showBuilderNames)
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001266 testsHTML = htmlForTestTable(tableRowsHTML);
1267 } else {
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001268 if (g_history.isLayoutTestResults())
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +01001269 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 +00001270 else
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +01001271 testsHTML += '<div>No tests have failed!</div>';
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001272 }
1273
1274 var html = htmlForNavBar();
1275
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001276 if (g_history.isLayoutTestResults())
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +01001277 html += headerForTestTableHtml();
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001278
1279 html += '<br>' + testsHTML;
1280 appendHTML(html);
1281
1282 var ths = document.getElementsByTagName('th');
1283 for (var i = 0; i < ths.length; i++) {
1284 ths[i].addEventListener('click', changeSort, false);
1285 ths[i].className = "sortable";
1286 }
1287
1288 hideLoadingUI();
1289}
1290
1291var VALID_KEYS_FOR_CROSS_BUILDER_VIEW = {
1292 tests: 1,
1293 result: 1,
1294 showChrome: 1,
1295 showExpectations: 1,
1296 showLargeExpectations: 1,
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001297 resultsHeight: 1,
1298 revision: 1
1299};
1300
1301function isInvalidKeyForCrossBuilderView(key)
1302{
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001303 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 +00001304}
1305
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001306function hideLegend()
1307{
1308 var legend = $('legend');
1309 if (legend)
1310 legend.parentNode.removeChild(legend);
1311}
1312
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001313function showLegend()
1314{
1315 var legend = $('legend');
1316 if (!legend) {
1317 legend = document.createElement('div');
1318 legend.id = 'legend';
1319 document.body.appendChild(legend);
1320 }
1321
1322 var html = '<div id=legend-toggle onclick="hideLegend()">Hide ' +
1323 'legend [type esc]</div><div id=legend-contents>';
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +01001324
1325 // Just grab the first failureMap. Technically, different builders can have different maps if they
1326 // haven't all cycled after the map was changed, but meh.
Torne (Richard Coles)5267f702013-06-11 10:57:24 +01001327 var failureMap = g_resultsByBuilder[Object.keys(g_resultsByBuilder)[0]][results.FAILURE_MAP];
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +01001328 for (var expectation in failureMap) {
1329 var failureString = failureMap[expectation];
1330 html += '<div class=' + classNameForFailureString(failureString) + '>' + failureString + '</div>';
1331 }
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001332
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001333 if (g_history.isLayoutTestResults()) {
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001334 html += '</div><br style="clear:both">' +
Torne (Richard Coles)5267f702013-06-11 10:57:24 +01001335 '</div>';
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001336
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +01001337 html += '<div>RELEASE TIMEOUTS:</div>' +
1338 htmlForSlowTimes(RELEASE_TIMEOUT) +
1339 '<div>DEBUG TIMEOUTS:</div>' +
1340 htmlForSlowTimes(DEBUG_TIMEOUT);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001341 }
1342
1343 legend.innerHTML = html;
1344}
1345
1346function htmlForSlowTimes(minTime)
1347{
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +01001348 return '<ul><li>' + minTime + ' seconds</li><li>' +
1349 SLOW_MULTIPLIER * minTime + ' seconds if marked Slow in TestExpectations</li></ul>';
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001350}
1351
1352function postHeightChangedMessage()
1353{
1354 if (window == parent)
1355 return;
1356
1357 var root = document.documentElement;
1358 var height = root.offsetHeight;
1359 if (root.offsetWidth < root.scrollWidth) {
1360 // We have a horizontal scrollbar. Include it in the height.
1361 var dummyNode = document.createElement('div');
1362 dummyNode.style.overflow = 'scroll';
1363 document.body.appendChild(dummyNode);
1364 var scrollbarWidth = dummyNode.offsetHeight - dummyNode.clientHeight;
1365 document.body.removeChild(dummyNode);
1366 height += scrollbarWidth;
1367 }
1368 parent.postMessage({command: 'heightChanged', height: height}, '*')
1369}
1370
1371if (window != parent)
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001372 window.addEventListener('blur', ui.popup.hide);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001373
1374document.addEventListener('keydown', function(e) {
1375 if (e.keyIdentifier == 'U+003F' || e.keyIdentifier == 'U+00BF') {
1376 // WebKit MAC retursn 3F. WebKit WIN returns BF. This is a bug!
1377 // ? key
1378 showLegend();
1379 } else if (e.keyIdentifier == 'U+001B') {
1380 // escape key
1381 hideLegend();
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001382 ui.popup.hide();
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001383 }
1384}, false);
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001385
1386window.addEventListener('load', function() {
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +01001387 resourceLoader = new loader.Loader();
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001388 resourceLoader.load();
1389}, false);