blob: c944b4cfed61e57d21a8c25ef712b9a8a1279cd1 [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 +000043
44// FIXME: Figure out how to make this not be hard-coded.
Torne (Richard Coles)5267f702013-06-11 10:57:24 +010045// Probably just include in the results.json files and get it from there.
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +000046var VIRTUAL_SUITES = {
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +010047 'virtual/gpu/fast/canvas': 'fast/canvas',
Torne (Richard Coles)5267f702013-06-11 10:57:24 +010048 'virtual/gpu/canvas/philip': 'canvas/philip',
49 'virtual/threaded/compositing/visibility': 'compositing/visibility',
50 'virtual/threaded/compositing/webgl': 'compositing/webgl',
51 'virtual/gpu/fast/hidpi': 'fast/hidpi',
52 'virtual/softwarecompositing': 'compositing',
53 'virtual/deferred/fast/images': 'fast/images',
54 'virtual/gpu/compositedscrolling/overflow': 'compositing/overflow',
55 'virtual/gpu/compositedscrolling/scrollbars': 'scrollbars',
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +000056};
57
Torne (Richard Coles)5267f702013-06-11 10:57:24 +010058var ACTUAL_RESULT_SUFFIXES = ['expected.txt', 'expected.png', 'actual.txt', 'actual.png', 'diff.txt', 'diff.png', 'wdiff.html', 'crash-log.txt'];
59
60var EXPECTATIONS_ORDER = ACTUAL_RESULT_SUFFIXES.filter(function(suffix) {
61 return !string.endsWith(suffix, 'png');
62}).map(function(suffix) {
63 return suffix.split('.')[0]
64});
65
Torne (Richard Coles)926b0012013-03-28 15:32:48 +000066var resourceLoader;
67
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +010068function generatePage(historyInstance)
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +000069{
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +010070 if (historyInstance.crossDashboardState.useTestData)
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +000071 return;
72
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +000073 document.body.innerHTML = '<div id="loading-ui">LOADING...</div>';
Torne (Richard Coles)926b0012013-03-28 15:32:48 +000074 resourceLoader.showErrors();
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +000075
76 // tests expands to all tests that match the CSV list.
77 // result expands to all tests that ever have the given result
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +010078 if (historyInstance.dashboardSpecificState.tests || historyInstance.dashboardSpecificState.result)
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +000079 generatePageForIndividualTests(individualTests());
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +000080 else
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +010081 generatePageForBuilder(historyInstance.dashboardSpecificState.builder || currentBuilderGroup().defaultBuilder());
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +000082
Torne (Richard Coles)926b0012013-03-28 15:32:48 +000083 for (var builder in currentBuilders())
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +000084 processTestResultsForBuilderAsync(builder);
85
86 postHeightChangedMessage();
87}
88
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +010089function handleValidHashParameter(historyInstance, key, value)
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +000090{
91 switch(key) {
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +010092 case 'result':
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +000093 case 'tests':
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() {
Torne (Richard Coles)926b0012013-03-28 15:32:48 +000096 return string.isValidName(value);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +000097 });
98 return true;
99
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000100 case 'builder':
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100101 history.validateParameter(historyInstance.dashboardSpecificState, key, value,
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000102 function() {
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000103 return value in currentBuilders();
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000104 });
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000105
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000106 return true;
107
108 case 'sortColumn':
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100109 history.validateParameter(historyInstance.dashboardSpecificState, key, value,
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000110 function() {
111 // Get all possible headers since the actual used set of headers
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100112 // depends on the values in historyInstance.dashboardSpecificState, which are currently being set.
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000113 var headers = tableHeaders(true);
114 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
318function substringList()
319{
320 // Convert windows slashes to unix slashes.
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000321 var tests = g_history.dashboardSpecificState.tests.replace(/\\/g, '/');
322 var separator = string.contains(tests, ' ') ? ' ' : ',';
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000323 var testList = tests.split(separator);
324
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000325 if (g_history.isLayoutTestResults())
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000326 return testList;
327
328 var testListWithoutModifiers = [];
329 testList.forEach(function(path) {
330 GTEST_MODIFIERS.forEach(function(modifier) {
331 path = path.replace('.' + modifier + '_', '.');
332 });
333 testListWithoutModifiers.push(path);
334 });
335 return testListWithoutModifiers;
336}
337
338function individualTestsForSubstringList()
339{
340 var testList = substringList();
Torne (Richard Coles)521d96e2013-06-19 11:58:24 +0100341 // If listing a lot of tests, assume you've passed in an explicit list of tests
342 // instead of patterns to match against. The matching code below is super slow.
343 //
344 // Also, when showChrome is false, we're embedding the dashboard elsewhere and
345 // an explicit test list is passed in. In that case, we don't want
346 // a search for compositing/foo.html to also show virtual/softwarecompositing/foo.html.
347 if (testList.length > 10 || !g_history.dashboardSpecificState.showChrome)
348 return testList;
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000349
350 // Put the tests into an object first and then move them into an array
351 // as a way of deduping.
352 var testsMap = {};
353 for (var i = 0; i < testList.length; i++) {
354 var path = testList[i];
355
356 // Ignore whitespace entries as they'd match every test.
357 if (path.match(/^\s*$/))
358 continue;
359
360 var hasAnyMatches = false;
361 getAllTestsTrie().forEach(function(triePath) {
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000362 if (string.caseInsensitiveContains(triePath, path)) {
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000363 testsMap[triePath] = 1;
364 hasAnyMatches = true;
365 }
366 });
367
368 // If a path doesn't match any tests, then assume it's a full path
369 // to a test that passes on all builders.
370 if (!hasAnyMatches)
371 testsMap[path] = 1;
372 }
373
374 var testsArray = [];
375 for (var test in testsMap)
376 testsArray.push(test);
Torne (Richard Coles)521d96e2013-06-19 11:58:24 +0100377
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000378 return testsArray;
379}
380
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000381function allTestsWithResult(result)
382{
383 processTestRunsForAllBuilders();
384 var retVal = [];
385
386 getAllTestsTrie().forEach(function(triePath) {
387 for (var i = 0; i < g_testToResultsMap[triePath].length; i++) {
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100388 if (g_testToResultsMap[triePath][i].actualResults.indexOf(result.toUpperCase()) != -1) {
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000389 retVal.push(triePath);
390 break;
391 }
392 }
393 });
394
395 return retVal;
396}
397
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000398function processTestResultsForBuilderAsync(builder)
399{
400 setTimeout(function() { processTestRunsForBuilder(builder); }, 0);
401}
402
403function processTestRunsForAllBuilders()
404{
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000405 for (var builder in currentBuilders())
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000406 processTestRunsForBuilder(builder);
407}
408
409function processTestRunsForBuilder(builderName)
410{
411 if (g_perBuilderFailures[builderName])
412 return;
413
414 if (!g_resultsByBuilder[builderName]) {
415 console.error('No tests found for ' + builderName);
416 g_perBuilderFailures[builderName] = [];
417 return;
418 }
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100419
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000420 var failures = [];
421 var allTestsForThisBuilder = g_resultsByBuilder[builderName].tests;
422
423 for (var test in allTestsForThisBuilder) {
424 var resultsForTest = createResultsObjectForTest(test, builderName);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000425
426 var rawTest = g_resultsByBuilder[builderName].tests[test];
427 resultsForTest.rawTimes = rawTest.times;
428 var rawResults = rawTest.results;
429 resultsForTest.rawResults = rawResults;
430
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100431 if (rawTest.expected)
432 resultsForTest.expectations = rawTest.expected;
433
434 if (rawTest.bugs)
435 resultsForTest.bugs = rawTest.bugs;
436
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100437 var failureMap = g_resultsByBuilder[builderName][results.FAILURE_MAP];
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000438 // FIXME: Switch to resultsByBuild
439 var times = resultsForTest.rawTimes;
440 var numTimesSeen = 0;
441 var numResultsSeen = 0;
442 var resultsIndex = 0;
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100443 var resultsMap = {}
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000444
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100445 for (var i = 0; i < times.length; i++) {
446 numTimesSeen += times[i][results.RLE.LENGTH];
447
448 while (rawResults[resultsIndex] && numTimesSeen > (numResultsSeen + rawResults[resultsIndex][results.RLE.LENGTH])) {
449 numResultsSeen += rawResults[resultsIndex][results.RLE.LENGTH];
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000450 resultsIndex++;
451 }
452
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100453 if (rawResults && rawResults[resultsIndex]) {
454 var result = rawResults[resultsIndex][results.RLE.VALUE];
455 resultsMap[failureMap[result]] = true;
456 }
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000457
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100458 resultsForTest.slowestTime = Math.max(resultsForTest.slowestTime, times[i][results.RLE.VALUE]);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000459 }
460
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100461 resultsForTest.actualResults = Object.keys(resultsMap);
462
463 results.determineFlakiness(failureMap, rawResults, resultsForTest);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000464 failures.push(resultsForTest);
465
466 if (!g_testToResultsMap[test])
467 g_testToResultsMap[test] = [];
468 g_testToResultsMap[test].push(resultsForTest);
469 }
470
471 g_perBuilderFailures[builderName] = failures;
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000472}
473
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000474function linkHTMLToOpenWindow(url, text)
475{
476 return '<a href="' + url + '" target="_blank">' + text + '</a>';
477}
478
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000479// Returns whether the result for index'th result for testName on builder was
480// a failure.
481function isFailure(builder, testName, index)
482{
483 var currentIndex = 0;
484 var rawResults = g_resultsByBuilder[builder].tests[testName].results;
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100485 var failureMap = g_resultsByBuilder[builder][results.FAILURE_MAP];
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000486 for (var i = 0; i < rawResults.length; i++) {
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100487 currentIndex += rawResults[i][results.RLE.LENGTH];
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000488 if (currentIndex > index)
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100489 return results.isFailingResult(failureMap, rawResults[i][results.RLE.VALUE]);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000490 }
491 console.error('Index exceeds number of results: ' + index);
492}
493
494// Returns an array of indexes for all builds where this test failed.
495function indexesForFailures(builder, testName)
496{
497 var rawResults = g_resultsByBuilder[builder].tests[testName].results;
498 var buildNumbers = g_resultsByBuilder[builder].buildNumbers;
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100499 var failureMap = g_resultsByBuilder[builder][results.FAILURE_MAP];
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000500 var index = 0;
501 var failures = [];
502 for (var i = 0; i < rawResults.length; i++) {
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100503 var numResults = rawResults[i][results.RLE.LENGTH];
504 if (results.isFailingResult(failureMap, rawResults[i][results.RLE.VALUE])) {
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000505 for (var j = 0; j < numResults; j++)
506 failures.push(index + j);
507 }
508 index += numResults;
509 }
510 return failures;
511}
512
513// Returns the path to the failure log for this non-webkit test.
514function pathToFailureLog(testName)
515{
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000516 return '/steps/' + g_history.crossDashboardState.testType + '/logs/' + testName.split('.')[1]
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000517}
518
519function showPopupForBuild(e, builder, index, opt_testName)
520{
521 var html = '';
522
523 var time = g_resultsByBuilder[builder].secondsSinceEpoch[index];
524 if (time) {
525 var date = new Date(time * 1000);
526 html += date.toLocaleDateString() + ' ' + date.toLocaleTimeString();
527 }
528
529 var buildNumber = g_resultsByBuilder[builder].buildNumbers[index];
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100530 var master = builders.master(builder);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000531 var buildBasePath = master.logPath(builder, buildNumber);
532
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100533 html += '<ul><li>' + linkHTMLToOpenWindow(buildBasePath, 'Build log');
534
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100535 if (g_resultsByBuilder[builder][results.BLINK_REVISIONS])
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100536 html += '</li><li>Blink: ' + ui.html.blinkRevisionLink(g_resultsByBuilder[builder], index) + '</li>';
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000537
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100538 html += '</li><li>Chromium: ' + ui.html.chromiumRevisionLink(g_resultsByBuilder[builder], index) + '</li>';
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000539
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100540 var chromeRevision = g_resultsByBuilder[builder].chromeRevision[index];
541 if (chromeRevision && g_history.isLayoutTestResults()) {
542 html += '<li><a href="' + TEST_RESULTS_BASE_PATH + currentBuilders()[builder] +
543 '/' + chromeRevision + '/layout-test-results.zip">layout-test-results.zip</a></li>';
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000544 }
545
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000546 if (!g_history.isLayoutTestResults() && opt_testName && isFailure(builder, opt_testName, index))
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000547 html += '<li>' + linkHTMLToOpenWindow(buildBasePath + pathToFailureLog(opt_testName), 'Failure log') + '</li>';
548
549 html += '</ul>';
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000550 ui.popup.show(e.target, html);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000551}
552
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100553function classNameForFailureString(failure)
554{
555 return failure.replace(/(\+|\ )/, '');
556}
557
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000558function htmlForTestResults(test)
559{
560 var html = '';
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100561 var testResults = test.rawResults.concat();
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000562 var times = test.rawTimes.concat();
563 var builder = test.builder;
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100564 var master = builders.master(builder);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000565 var buildNumbers = g_resultsByBuilder[builder].buildNumbers;
566
567 var indexToReplaceCurrentResult = -1;
568 var indexToReplaceCurrentTime = -1;
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000569 for (var i = 0; i < buildNumbers.length; i++) {
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100570 var currentResultArray, currentTimeArray, innerHTML, resultString;
571
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000572 if (i > indexToReplaceCurrentResult) {
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100573 currentResultArray = testResults.shift();
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000574 if (currentResultArray) {
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100575 resultString = g_resultsByBuilder[builder][results.FAILURE_MAP][currentResultArray[results.RLE.VALUE]];
576 indexToReplaceCurrentResult += currentResultArray[results.RLE.LENGTH];
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000577 } else {
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100578 resultString = results.NO_DATA;
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000579 indexToReplaceCurrentResult += buildNumbers.length;
580 }
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000581 }
582
583 if (i > indexToReplaceCurrentTime) {
584 currentTimeArray = times.shift();
585 var currentTime = 0;
586 if (currentResultArray) {
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100587 currentTime = currentTimeArray[results.RLE.VALUE];
588 indexToReplaceCurrentTime += currentTimeArray[results.RLE.LENGTH];
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000589 } else
590 indexToReplaceCurrentTime += buildNumbers.length;
591
592 innerHTML = currentTime || '&nbsp;';
593 }
594
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100595 html += '<td title="' + resultString + '. Click for more info." class="results ' + classNameForFailureString(resultString) +
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100596 '" onclick=\'showPopupForBuild(event, "' + builder + '",' + i + ',"' + test.test + '")\'>' + innerHTML;
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000597 }
598 return html;
599}
600
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100601function shouldShowTest(testResult)
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000602{
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100603 if (!g_history.isLayoutTestResults())
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100604 return true;
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100605
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100606 if (testResult.expectations == 'WONTFIX')
607 return g_history.dashboardSpecificState.showWontFix;
608
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100609 if (testResult.expectations == results.SKIP)
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100610 return g_history.dashboardSpecificState.showSkip;
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000611
612 if (testResult.isFlaky)
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100613 return g_history.dashboardSpecificState.showFlaky;
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000614
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100615 return g_history.dashboardSpecificState.showNonFlaky;
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000616}
617
618function createBugHTML(test)
619{
620 var symptom = test.isFlaky ? 'flaky' : 'failing';
621 var title = encodeURIComponent('Layout Test ' + test.test + ' is ' + symptom);
622 var description = encodeURIComponent('The following layout test is ' + symptom + ' on ' +
623 '[insert platform]\n\n' + test.test + '\n\nProbable cause:\n\n' +
624 '[insert probable cause]');
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100625
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100626 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 +0100627 return '<a href="' + url + '">File new bug</a>';
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000628}
629
630function isCrossBuilderView()
631{
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100632 return g_history.dashboardSpecificState.tests || g_history.dashboardSpecificState.result;
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000633}
634
635function tableHeaders(opt_getAll)
636{
637 var headers = [];
638 if (isCrossBuilderView() || opt_getAll)
639 headers.push('builder');
640
641 if (!isCrossBuilderView() || opt_getAll)
642 headers.push('test');
643
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000644 if (g_history.isLayoutTestResults() || opt_getAll)
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100645 headers.push('bugs', 'expectations');
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000646
647 headers.push('slowest run', 'flakiness (numbers are runtimes in seconds)');
648 return headers;
649}
650
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100651function linkifyBugs(bugs)
652{
653 var html = '';
654 bugs.forEach(function(bug) {
655 var bugHtml;
656 if (string.startsWith(bug, 'Bug('))
657 bugHtml = bug;
658 else
659 bugHtml = '<a href="http://' + bug + '">' + bug + '</a>';
660 html += '<div>' + bugHtml + '</div>'
661 });
662 return html;
663}
664
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000665function htmlForSingleTestRow(test)
666{
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000667 var headers = tableHeaders();
668 var html = '';
669 for (var i = 0; i < headers.length; i++) {
670 var header = headers[i];
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000671 if (string.startsWith(header, 'test') || string.startsWith(header, 'builder')) {
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000672 // If isCrossBuilderView() is true, we're just viewing a single test
673 // with results for many builders, so the first column is builder names
674 // instead of test paths.
675 var testCellClassName = 'test-link' + (isCrossBuilderView() ? ' builder-name' : '');
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000676 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 +0000677
678 html += '<tr><td class="' + testCellClassName + '">' + testCellHTML;
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000679 } else if (string.startsWith(header, 'bugs'))
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100680 // FIXME: linkify bugs.
681 html += '<td class=options-container>' + (linkifyBugs(test.bugs) || createBugHTML(test));
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000682 else if (string.startsWith(header, 'expectations'))
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000683 html += '<td class=options-container>' + test.expectations;
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000684 else if (string.startsWith(header, 'slowest'))
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000685 html += '<td>' + (test.slowestTime ? test.slowestTime + 's' : '');
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000686 else if (string.startsWith(header, 'flakiness'))
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000687 html += htmlForTestResults(test);
688 }
689 return html;
690}
691
692function sortColumnFromTableHeader(headerText)
693{
694 return headerText.split(' ', 1)[0];
695}
696
697function htmlForTableColumnHeader(headerName, opt_fillColSpan)
698{
699 // Use the first word of the header title as the sortkey
700 var thisSortValue = sortColumnFromTableHeader(headerName);
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000701 var arrowHTML = thisSortValue == g_history.dashboardSpecificState.sortColumn ?
702 '<span class=' + g_history.dashboardSpecificState.sortOrder + '>' + (g_history.dashboardSpecificState.sortOrder == FORWARD ? '&uarr;' : '&darr;' ) + '</span>' : '';
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000703 return '<th sortValue=' + thisSortValue +
704 // Extend last th through all the rest of the columns.
705 (opt_fillColSpan ? ' colspan=10000' : '') +
706 // Extra span here is so flex boxing actually centers.
707 // There's probably a better way to do this with CSS only though.
708 '><div class=table-header-content><span></span>' + arrowHTML +
709 '<span class=header-text>' + headerName + '</span>' + arrowHTML + '</div></th>';
710}
711
712function htmlForTestTable(rowsHTML, opt_excludeHeaders)
713{
714 var html = '<table class=test-table>';
715 if (!opt_excludeHeaders) {
716 html += '<thead><tr>';
717 var headers = tableHeaders();
718 for (var i = 0; i < headers.length; i++)
719 html += htmlForTableColumnHeader(headers[i], i == headers.length - 1);
720 html += '</tr></thead>';
721 }
722 return html + '<tbody>' + rowsHTML + '</tbody></table>';
723}
724
725function appendHTML(html)
726{
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000727 // InnerHTML to a div that's not in the document. This is
728 // ~300ms faster in Safari 4 and Chrome 4 on mac.
729 var div = document.createElement('div');
730 div.innerHTML = html;
731 document.body.appendChild(div);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000732 postHeightChangedMessage();
733}
734
735function alphanumericCompare(column, reverse)
736{
737 return reversibleCompareFunction(function(a, b) {
738 // Put null entries at the bottom
739 var a = a[column] ? String(a[column]) : 'z';
740 var b = b[column] ? String(b[column]) : 'z';
741
742 if (a < b)
743 return -1;
744 else if (a == b)
745 return 0;
746 else
747 return 1;
748 }, reverse);
749}
750
751function numericSort(column, reverse)
752{
753 return reversibleCompareFunction(function(a, b) {
754 a = parseFloat(a[column]);
755 b = parseFloat(b[column]);
756 return a - b;
757 }, reverse);
758}
759
760function reversibleCompareFunction(compare, reverse)
761{
762 return function(a, b) {
763 return compare(reverse ? b : a, reverse ? a : b);
764 };
765}
766
767function changeSort(e)
768{
769 var target = e.currentTarget;
770 e.preventDefault();
771
772 var sortValue = target.getAttribute('sortValue');
773 while (target && target.tagName != 'TABLE')
774 target = target.parentNode;
775
776 var sort = 'sortColumn';
777 var orderKey = 'sortOrder';
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000778 if (sortValue == g_history.dashboardSpecificState[sort] && g_history.dashboardSpecificState[orderKey] == FORWARD)
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000779 order = BACKWARD;
780 else
781 order = FORWARD;
782
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000783 g_history.setQueryParameter(sort, sortValue, orderKey, order);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000784}
785
786function sortTests(tests, column, order)
787{
788 var resultsProperty, sortFunctionGetter;
789 if (column == 'flakiness') {
790 sortFunctionGetter = numericSort;
791 resultsProperty = 'flips';
792 } else if (column == 'slowest') {
793 sortFunctionGetter = numericSort;
794 resultsProperty = 'slowestTime';
795 } else {
796 sortFunctionGetter = alphanumericCompare;
797 resultsProperty = column;
798 }
799
800 tests.sort(sortFunctionGetter(resultsProperty, order == BACKWARD));
801}
802
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000803function htmlForIndividualTestOnAllBuilders(test)
804{
805 processTestRunsForAllBuilders();
806
807 var testResults = g_testToResultsMap[test];
808 if (!testResults)
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100809 return '<div class="not-found">Test not found. Either it does not exist, is skipped or passes on all recorded runs.</div>';
810
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000811 var html = '';
812 var shownBuilders = [];
813 for (var j = 0; j < testResults.length; j++) {
814 shownBuilders.push(testResults[j].builder);
815 html += htmlForSingleTestRow(testResults[j]);
816 }
817
818 var skippedBuilders = []
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000819 for (builder in currentBuilders()) {
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000820 if (shownBuilders.indexOf(builder) == -1)
821 skippedBuilders.push(builder);
822 }
823
824 var skippedBuildersHtml = '';
825 if (skippedBuilders.length) {
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100826 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 +0000827 '<div class=skipped-builder-list><div class=skipped-builder>' + skippedBuilders.join('</div><div class=skipped-builder>') + '</div></div>';
828 }
829
830 return htmlForTestTable(html) + skippedBuildersHtml;
831}
832
833function htmlForIndividualTestOnAllBuildersWithResultsLinks(test)
834{
835 processTestRunsForAllBuilders();
836
837 var testResults = g_testToResultsMap[test];
838 var html = '';
839 html += htmlForIndividualTestOnAllBuilders(test);
840
841 html += '<div class=expectations test=' + test + '><div>' +
842 linkHTMLToToggleState('showExpectations', 'results')
843
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000844 if (g_history.isLayoutTestResults() || g_history.isGPUTestResults()) {
845 if (g_history.isLayoutTestResults())
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000846 html += ' | ' + linkHTMLToToggleState('showLargeExpectations', 'large thumbnails');
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000847 html += ' | <b>Only shows actual results/diffs from the most recent *failure* on each bot.</b>';
848 } else {
849 html += ' | <span>Results height:<input ' +
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000850 'onchange="g_history.setQueryParameter(\'resultsHeight\',this.value)" value="' +
851 g_history.dashboardSpecificState.resultsHeight + '" style="width:2.5em">px</span>';
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000852 }
853 html += '</div></div>';
854 return html;
855}
856
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000857function maybeAddPngChecksum(expectationDiv, pngUrl)
858{
859 // pngUrl gets served from the browser cache since we just loaded it in an
860 // <img> tag.
861 loader.request(pngUrl,
862 function(xhr) {
863 // Convert the first 2k of the response to a byte string.
864 var bytes = xhr.responseText.substring(0, 2048);
865 for (var position = 0; position < bytes.length; ++position)
866 bytes[position] = bytes[position] & 0xff;
867
868 // Look for the comment.
869 var commentKey = 'tEXtchecksum\x00';
870 var checksumPosition = bytes.indexOf(commentKey);
871 if (checksumPosition == -1)
872 return;
873
874 var checksum = bytes.substring(checksumPosition + commentKey.length, checksumPosition + commentKey.length + 32);
875 var checksumContainer = document.createElement('span');
876 checksumContainer.innerText = 'Embedded checksum: ' + checksum;
877 checksumContainer.setAttribute('class', 'pngchecksum');
878 expectationDiv.parentNode.appendChild(checksumContainer);
879 },
880 function(xhr) {},
881 true);
882}
883
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100884function getOrCreate(className, parent)
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000885{
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100886 var element = parent.querySelector('.' + className);
887 if (!element) {
888 element = document.createElement('div');
889 element.className = className;
890 parent.appendChild(element);
891 }
892 return element;
893}
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000894
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100895function handleExpectationsItemLoad(title, item, itemType, parent)
896{
897 item.className = 'expectation';
898 if (g_history.dashboardSpecificState.showLargeExpectations)
899 item.className += ' large';
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000900
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100901 var titleContainer = document.createElement('h3');
902 titleContainer.className = 'expectations-title';
903 titleContainer.textContent = title;
904
905 var itemContainer = document.createElement('span');
906 itemContainer.appendChild(titleContainer);
907 itemContainer.className = 'expectations-item ' + title;
908 itemContainer.appendChild(item);
909
910 // Separate text and image results into separate divs..
911 var typeContainer = getOrCreate(itemType, parent);
912
913 // Insert results in a consistent order.
914 var index = EXPECTATIONS_ORDER.indexOf(title);
915 while (index < EXPECTATIONS_ORDER.length) {
916 index++;
917 var elementAfter = typeContainer.querySelector('.' + EXPECTATIONS_ORDER[index]);
918 if (elementAfter) {
919 typeContainer.insertBefore(itemContainer, elementAfter);
920 break;
921 }
922 }
923 if (!itemContainer.parentNode)
924 typeContainer.appendChild(itemContainer);
925
926 handleFinishedLoadingExpectations(parent);
927}
928
929function addExpectationItem(expectationsContainers, parentContainer, url, opt_builder)
930{
931 // Group expectations by builder, putting test and reference files first.
932 var builder = opt_builder || "Test and reference files";
933 var container = expectationsContainers[builder];
934
935 if (!container) {
936 container = document.createElement('div');
937 container.className = 'expectations-container';
938 container.setAttribute('data-builder', builder);
939 parentContainer.appendChild(container);
940 expectationsContainers[builder] = container;
941 }
942
943 var numUnloaded = container.getAttribute('data-unloaded') || 0;
944 container.setAttribute('data-unloaded', ++numUnloaded);
945
946 var isImage = url.match(/\.png$/);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000947
948 var appendExpectationsItem = function(item) {
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100949 var itemType = isImage ? 'image' : 'text';
950 handleExpectationsItemLoad(expectationsTitle(url), item, itemType, container);
951 };
952
953 var handleLoadError = function() {
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000954 handleFinishedLoadingExpectations(container);
955 };
956
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +0100957 if (isImage) {
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100958 var dummyNode = document.createElement('img');
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000959 dummyNode.onload = function() {
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100960 var item = dummyNode;
961 maybeAddPngChecksum(item, url);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000962 appendExpectationsItem(item);
963 }
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100964 dummyNode.onerror = handleLoadError;
965 dummyNode.src = url;
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000966 } else {
967 loader.request(url,
968 function(xhr) {
969 var item = document.createElement('pre');
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100970 if (string.endsWith(url, '-wdiff.html'))
971 item.innerHTML = xhr.responseText;
972 else
973 item.textContent = xhr.responseText;
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000974 appendExpectationsItem(item);
975 },
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100976 handleLoadError);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000977 }
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000978}
979
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000980function handleFinishedLoadingExpectations(container)
981{
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100982 var numUnloaded = container.getAttribute('data-unloaded') - 1;
983 container.setAttribute('data-unloaded', numUnloaded);
984 if (numUnloaded)
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000985 return;
986
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100987 if (!container.firstChild) {
988 container.remove();
989 return;
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000990 }
991
Torne (Richard Coles)5267f702013-06-11 10:57:24 +0100992 var builder = container.getAttribute('data-builder');
993 if (!builder)
994 return;
995
996 var header = document.createElement('h2');
997 header.textContent = builder;
998 container.insertBefore(header, container.firstChild);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000999}
1000
Torne (Richard Coles)5267f702013-06-11 10:57:24 +01001001function expectationsTitle(url)
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001002{
Torne (Richard Coles)5267f702013-06-11 10:57:24 +01001003 var matchingSuffixes = ACTUAL_RESULT_SUFFIXES.filter(function(suffix) {
1004 return string.endsWith(url, suffix);
1005 });
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001006
Torne (Richard Coles)5267f702013-06-11 10:57:24 +01001007 if (matchingSuffixes.length)
1008 return matchingSuffixes[0].split('.')[0];
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001009
Torne (Richard Coles)5267f702013-06-11 10:57:24 +01001010 var parts = url.split('/');
1011 return parts[parts.length - 1];
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001012}
1013
1014function loadExpectations(expectationsContainer)
1015{
1016 var test = expectationsContainer.getAttribute('test');
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001017 if (g_history.isLayoutTestResults())
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001018 loadExpectationsLayoutTests(test, expectationsContainer);
1019 else {
Torne (Richard Coles)5267f702013-06-11 10:57:24 +01001020 var testResults = g_testToResultsMap[test];
1021 for (var i = 0; i < testResults.length; i++)
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001022 if (g_history.isGPUTestResults())
Torne (Richard Coles)5267f702013-06-11 10:57:24 +01001023 loadGPUResultsForBuilder(testResults[i].builder, test, expectationsContainer);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001024 else
Torne (Richard Coles)5267f702013-06-11 10:57:24 +01001025 loadNonWebKitResultsForBuilder(testResults[i].builder, test, expectationsContainer);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001026 }
1027}
1028
1029function gpuResultsPath(chromeRevision, builder)
1030{
1031 return chromeRevision + '_' + builder.replace(/[^A-Za-z0-9]+/g, '_');
1032}
1033
1034function loadGPUResultsForBuilder(builder, test, expectationsContainer)
1035{
1036 var container = document.createElement('div');
1037 container.className = 'expectations-container';
1038 container.innerHTML = '<div><b>' + builder + '</b></div>';
1039 expectationsContainer.appendChild(container);
1040
1041 var failureIndex = indexesForFailures(builder, test)[0];
1042
1043 var buildNumber = g_resultsByBuilder[builder].buildNumbers[failureIndex];
Torne (Richard Coles)5267f702013-06-11 10:57:24 +01001044 var pathToLog = builders.master(builder).logPath(builder, buildNumber) + pathToFailureLog(test);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001045
1046 var chromeRevision = g_resultsByBuilder[builder].chromeRevision[failureIndex];
1047 var resultsUrl = GPU_RESULTS_BASE_PATH + gpuResultsPath(chromeRevision, builder);
1048 var filename = test.split(/\./)[1] + '.png';
1049
1050 appendNonWebKitResults(container, pathToLog, 'non-webkit-results');
1051 appendNonWebKitResults(container, resultsUrl + '/gen/' + filename, 'gpu-test-results', 'Generated');
1052 appendNonWebKitResults(container, resultsUrl + '/ref/' + filename, 'gpu-test-results', 'Reference');
1053 appendNonWebKitResults(container, resultsUrl + '/diff/' + filename, 'gpu-test-results', 'Diff');
1054}
1055
1056function loadNonWebKitResultsForBuilder(builder, test, expectationsContainer)
1057{
1058 var failureIndexes = indexesForFailures(builder, test);
1059 var container = document.createElement('div');
1060 container.innerHTML = '<div><b>' + builder + '</b></div>';
1061 expectationsContainer.appendChild(container);
1062 for (var i = 0; i < failureIndexes.length; i++) {
1063 // FIXME: This doesn't seem to work anymore. Did the paths change?
1064 // Once that's resolved, see if we need to try each GTEST_MODIFIERS prefix as well.
1065 var buildNumber = g_resultsByBuilder[builder].buildNumbers[failureIndexes[i]];
Torne (Richard Coles)5267f702013-06-11 10:57:24 +01001066 var pathToLog = builders.master(builder).logPath(builder, buildNumber) + pathToFailureLog(test);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001067 appendNonWebKitResults(container, pathToLog, 'non-webkit-results');
1068 }
1069}
1070
1071function appendNonWebKitResults(container, url, itemClassName, opt_title)
1072{
1073 // Use a script tag to detect whether the URL 404s.
1074 // Need to use a script tag since the URL is cross-domain.
1075 var dummyNode = document.createElement('script');
1076 dummyNode.src = url;
1077
1078 dummyNode.onload = function() {
1079 var item = document.createElement('iframe');
1080 item.src = dummyNode.src;
1081 item.className = itemClassName;
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001082 item.style.height = g_history.dashboardSpecificState.resultsHeight + 'px';
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001083
1084 if (opt_title) {
1085 var childContainer = document.createElement('div');
1086 childContainer.style.display = 'inline-block';
1087 var title = document.createElement('div');
1088 title.textContent = opt_title;
1089 childContainer.appendChild(title);
1090 childContainer.appendChild(item);
1091 container.replaceChild(childContainer, dummyNode);
1092 } else
1093 container.replaceChild(item, dummyNode);
1094 }
1095 dummyNode.onerror = function() {
1096 container.removeChild(dummyNode);
1097 }
1098
1099 container.appendChild(dummyNode);
1100}
1101
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001102function lookupVirtualTestSuite(test) {
1103 for (var suite in VIRTUAL_SUITES) {
1104 if (test.indexOf(suite) != -1)
1105 return suite;
1106 }
1107 return '';
1108}
1109
1110function baseTest(test, suite) {
1111 base = VIRTUAL_SUITES[suite];
1112 return base ? test.replace(suite, base) : test;
1113}
1114
Torne (Richard Coles)5267f702013-06-11 10:57:24 +01001115function loadTestAndReferenceFiles(expectationsContainers, expectationsContainer, test) {
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001116 var testWithoutSuffix = test.substring(0, test.lastIndexOf('.'));
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001117 var reftest_html_file = testWithoutSuffix + "-expected.html";
1118 var reftest_mismatch_html_file = testWithoutSuffix + "-expected-mismatch.html";
Torne (Richard Coles)5267f702013-06-11 10:57:24 +01001119
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001120 var suite = lookupVirtualTestSuite(test);
Torne (Richard Coles)5267f702013-06-11 10:57:24 +01001121 if (suite) {
1122 loadTestAndReferenceFiles(expectationsContainers, expectationsContainer, baseTest(test, suite));
1123 return;
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001124 }
1125
Torne (Richard Coles)5267f702013-06-11 10:57:24 +01001126 addExpectationItem(expectationsContainers, expectationsContainer, TEST_URL_BASE_PATH_FOR_XHR + test);
1127 addExpectationItem(expectationsContainers, expectationsContainer, TEST_URL_BASE_PATH_FOR_XHR + reftest_html_file);
1128 addExpectationItem(expectationsContainers, expectationsContainer, TEST_URL_BASE_PATH_FOR_XHR + reftest_mismatch_html_file);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001129}
1130
1131function loadExpectationsLayoutTests(test, expectationsContainer)
1132{
1133 // Map from file extension to container div for expectations of that type.
1134 var expectationsContainers = {};
Torne (Richard Coles)5267f702013-06-11 10:57:24 +01001135 loadTestAndReferenceFiles(expectationsContainers, expectationsContainer, test);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001136
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001137 var testWithoutSuffix = test.substring(0, test.lastIndexOf('.'));
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001138
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001139 for (var builder in currentBuilders()) {
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +01001140 var actualResultsBase = TEST_RESULTS_BASE_PATH + currentBuilders()[builder] + '/results/layout-test-results/';
Torne (Richard Coles)5267f702013-06-11 10:57:24 +01001141 ACTUAL_RESULT_SUFFIXES.forEach(function(suffix) {{
1142 addExpectationItem(expectationsContainers, expectationsContainer, actualResultsBase + testWithoutSuffix + '-' + suffix, builder);
1143 }})
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001144 }
1145
1146 // Add a clearing element so floated elements don't bleed out of their
1147 // containing block.
1148 var br = document.createElement('br');
1149 br.style.clear = 'both';
1150 expectationsContainer.appendChild(br);
1151}
1152
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001153function appendExpectations()
1154{
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001155 var expectations = g_history.dashboardSpecificState.showExpectations ? document.getElementsByClassName('expectations') : [];
Torne (Richard Coles)521d96e2013-06-19 11:58:24 +01001156 g_chunkedActionState = {
1157 items: expectations,
1158 index: 0
1159 }
1160 performChunkedAction(function(expectation) {
1161 loadExpectations(expectation);
1162 postHeightChangedMessage();
1163 },
1164 hideLoadingUI,
1165 expectations);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001166}
1167
1168function hideLoadingUI()
1169{
1170 var loadingDiv = $('loading-ui');
1171 if (loadingDiv)
1172 loadingDiv.style.display = 'none';
1173 postHeightChangedMessage();
1174}
1175
1176function generatePageForIndividualTests(tests)
1177{
1178 console.log('Number of tests: ' + tests.length);
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001179 if (g_history.dashboardSpecificState.showChrome)
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001180 appendHTML(htmlForNavBar());
Torne (Richard Coles)521d96e2013-06-19 11:58:24 +01001181 performChunkedAction(function(test) {
1182 appendHTML(htmlForIndividualTest(test));
1183 },
1184 appendExpectations,
1185 tests);
Torne (Richard Coles)5267f702013-06-11 10:57:24 +01001186 if (g_history.dashboardSpecificState.showChrome) {
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001187 $('tests-input').value = g_history.dashboardSpecificState.tests;
Torne (Richard Coles)5267f702013-06-11 10:57:24 +01001188 $('result-input').value = g_history.dashboardSpecificState.result;
1189 }
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001190}
1191
Torne (Richard Coles)521d96e2013-06-19 11:58:24 +01001192var g_chunkedActionRequestId;
1193function performChunkedAction(action, onComplete, items, opt_index) {
1194 if (g_chunkedActionRequestId)
1195 cancelAnimationFrame(g_chunkedActionRequestId);
1196
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001197 var index = opt_index || 0;
Torne (Richard Coles)521d96e2013-06-19 11:58:24 +01001198 g_chunkedActionRequestId = requestAnimationFrame(function() {
1199 if (index < items.length) {
1200 action(items[index]);
1201 performChunkedAction(action, onComplete, items, ++index);
1202 } else {
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001203 onComplete();
Torne (Richard Coles)521d96e2013-06-19 11:58:24 +01001204 }
1205 });
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001206}
1207
Torne (Richard Coles)521d96e2013-06-19 11:58:24 +01001208function htmlForIndividualTest(test)
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001209{
Torne (Richard Coles)521d96e2013-06-19 11:58:24 +01001210 var testNameHtml = '';
1211 if (g_history.dashboardSpecificState.showChrome) {
1212 if (g_history.isLayoutTestResults()) {
1213 var suite = lookupVirtualTestSuite(test);
1214 var base = suite ? baseTest(test, suite) : test;
1215 var versionControlUrl = TEST_URL_BASE_PATH_FOR_BROWSING + base;
1216 testNameHtml += '<h2>' + linkHTMLToOpenWindow(versionControlUrl, test) + '</h2>';
1217 } else
1218 testNameHtml += '<h2>' + test + '</h2>';
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001219 }
Torne (Richard Coles)521d96e2013-06-19 11:58:24 +01001220
1221 return testNameHtml + htmlForIndividualTestOnAllBuildersWithResultsLinks(test);
1222}
1223
1224function setTestsParameter(input)
1225{
1226 g_history.setQueryParameter('tests', input.value);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001227}
1228
1229function htmlForNavBar()
1230{
1231 var extraHTML = '';
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001232 var html = ui.html.testTypeSwitcher(false, extraHTML, isCrossBuilderView());
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001233 html += '<div class=forms><form id=result-form ' +
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001234 'onsubmit="g_history.setQueryParameter(\'result\', result.value);' +
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001235 'return false;">Show all tests with result: ' +
1236 '<input name=result placeholder="e.g. CRASH" id=result-input>' +
Torne (Richard Coles)521d96e2013-06-19 11:58:24 +01001237 '</form><span>Show tests on all platforms: </span>' +
1238 // Use a textarea to avoid the 32k limit on the length of inputs.
1239 '<textarea name=tests ' +
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001240 'placeholder="Comma or space-separated list of tests or partial ' +
1241 'paths to show test results across all builders, e.g., ' +
Torne (Richard Coles)521d96e2013-06-19 11:58:24 +01001242 'foo/bar.html,foo/baz,domstorage" id=tests-input onchange="setTestsParameter(this)" ' +
1243 'onkeydown="if (event.keyCode == 13) { setTestsParameter(this); return false; }"></textarea>' +
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001244 '<span class=link onclick="showLegend()">Show legend [type ?]</span></div>';
1245 return html;
1246}
1247
1248function checkBoxToToggleState(key, text)
1249{
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001250 var stateEnabled = g_history.dashboardSpecificState[key];
1251 return '<label><input type=checkbox ' + (stateEnabled ? 'checked ' : '') + 'onclick="g_history.setQueryParameter(\'' + key + '\', ' + !stateEnabled + ')">' + text + '</label> ';
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001252}
1253
1254function linkHTMLToToggleState(key, linkText)
1255{
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001256 var stateEnabled = g_history.dashboardSpecificState[key];
1257 return '<span class=link onclick="g_history.setQueryParameter(\'' + key + '\', ' + !stateEnabled + ')">' + (stateEnabled ? 'Hide' : 'Show') + ' ' + linkText + '</span>';
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001258}
1259
1260function headerForTestTableHtml()
1261{
1262 return '<h2 style="display:inline-block">Failing tests</h2>' +
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +01001263 checkBoxToToggleState('showFlaky', 'Show flaky') +
1264 checkBoxToToggleState('showNonFlaky', 'Show non-flaky') +
1265 checkBoxToToggleState('showSkip', 'Show Skip') +
1266 checkBoxToToggleState('showWontFix', 'Show WontFix');
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001267}
1268
1269function generatePageForBuilder(builderName)
1270{
1271 processTestRunsForBuilder(builderName);
1272
Torne (Richard Coles)5267f702013-06-11 10:57:24 +01001273 var filteredResults = g_perBuilderFailures[builderName].filter(shouldShowTest);
1274 sortTests(filteredResults, g_history.dashboardSpecificState.sortColumn, g_history.dashboardSpecificState.sortOrder);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001275
1276 var testsHTML = '';
Torne (Richard Coles)5267f702013-06-11 10:57:24 +01001277 if (filteredResults.length) {
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001278 var tableRowsHTML = '';
Torne (Richard Coles)5267f702013-06-11 10:57:24 +01001279 for (var i = 0; i < filteredResults.length; i++)
1280 tableRowsHTML += htmlForSingleTestRow(filteredResults[i])
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001281 testsHTML = htmlForTestTable(tableRowsHTML);
1282 } else {
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001283 if (g_history.isLayoutTestResults())
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +01001284 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 +00001285 else
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +01001286 testsHTML += '<div>No tests have failed!</div>';
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001287 }
1288
1289 var html = htmlForNavBar();
1290
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001291 if (g_history.isLayoutTestResults())
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +01001292 html += headerForTestTableHtml();
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001293
1294 html += '<br>' + testsHTML;
1295 appendHTML(html);
1296
1297 var ths = document.getElementsByTagName('th');
1298 for (var i = 0; i < ths.length; i++) {
1299 ths[i].addEventListener('click', changeSort, false);
1300 ths[i].className = "sortable";
1301 }
1302
1303 hideLoadingUI();
1304}
1305
1306var VALID_KEYS_FOR_CROSS_BUILDER_VIEW = {
1307 tests: 1,
1308 result: 1,
1309 showChrome: 1,
1310 showExpectations: 1,
1311 showLargeExpectations: 1,
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001312 resultsHeight: 1,
1313 revision: 1
1314};
1315
1316function isInvalidKeyForCrossBuilderView(key)
1317{
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001318 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 +00001319}
1320
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001321function hideLegend()
1322{
1323 var legend = $('legend');
1324 if (legend)
1325 legend.parentNode.removeChild(legend);
1326}
1327
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001328function showLegend()
1329{
1330 var legend = $('legend');
1331 if (!legend) {
1332 legend = document.createElement('div');
1333 legend.id = 'legend';
1334 document.body.appendChild(legend);
1335 }
1336
1337 var html = '<div id=legend-toggle onclick="hideLegend()">Hide ' +
1338 'legend [type esc]</div><div id=legend-contents>';
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +01001339
1340 // Just grab the first failureMap. Technically, different builders can have different maps if they
1341 // haven't all cycled after the map was changed, but meh.
Torne (Richard Coles)5267f702013-06-11 10:57:24 +01001342 var failureMap = g_resultsByBuilder[Object.keys(g_resultsByBuilder)[0]][results.FAILURE_MAP];
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +01001343 for (var expectation in failureMap) {
1344 var failureString = failureMap[expectation];
1345 html += '<div class=' + classNameForFailureString(failureString) + '>' + failureString + '</div>';
1346 }
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001347
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001348 if (g_history.isLayoutTestResults()) {
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001349 html += '</div><br style="clear:both">' +
Torne (Richard Coles)5267f702013-06-11 10:57:24 +01001350 '</div>';
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001351
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +01001352 html += '<div>RELEASE TIMEOUTS:</div>' +
1353 htmlForSlowTimes(RELEASE_TIMEOUT) +
1354 '<div>DEBUG TIMEOUTS:</div>' +
1355 htmlForSlowTimes(DEBUG_TIMEOUT);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001356 }
1357
1358 legend.innerHTML = html;
1359}
1360
1361function htmlForSlowTimes(minTime)
1362{
Torne (Richard Coles)93ac45c2013-05-29 14:40:20 +01001363 return '<ul><li>' + minTime + ' seconds</li><li>' +
1364 SLOW_MULTIPLIER * minTime + ' seconds if marked Slow in TestExpectations</li></ul>';
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001365}
1366
1367function postHeightChangedMessage()
1368{
1369 if (window == parent)
1370 return;
1371
1372 var root = document.documentElement;
1373 var height = root.offsetHeight;
1374 if (root.offsetWidth < root.scrollWidth) {
1375 // We have a horizontal scrollbar. Include it in the height.
1376 var dummyNode = document.createElement('div');
1377 dummyNode.style.overflow = 'scroll';
1378 document.body.appendChild(dummyNode);
1379 var scrollbarWidth = dummyNode.offsetHeight - dummyNode.clientHeight;
1380 document.body.removeChild(dummyNode);
1381 height += scrollbarWidth;
1382 }
1383 parent.postMessage({command: 'heightChanged', height: height}, '*')
1384}
1385
1386if (window != parent)
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001387 window.addEventListener('blur', ui.popup.hide);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001388
1389document.addEventListener('keydown', function(e) {
1390 if (e.keyIdentifier == 'U+003F' || e.keyIdentifier == 'U+00BF') {
1391 // WebKit MAC retursn 3F. WebKit WIN returns BF. This is a bug!
1392 // ? key
1393 showLegend();
1394 } else if (e.keyIdentifier == 'U+001B') {
1395 // escape key
1396 hideLegend();
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001397 ui.popup.hide();
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001398 }
1399}, false);
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001400
1401window.addEventListener('load', function() {
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +01001402 resourceLoader = new loader.Loader();
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001403 resourceLoader.load();
1404}, false);