blob: 510ecd5b7e839d478d7a41a446042caf5061b0c8 [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//////////////////////////////////////////////////////////////////////////////
32var ALL = 'ALL';
33var FORWARD = 'forward';
34var BACKWARD = 'backward';
35var GTEST_MODIFIERS = ['FLAKY', 'FAILS', 'MAYBE', 'DISABLED'];
36var TEST_URL_BASE_PATH_TRAC = 'http://trac.webkit.org/browser/trunk/LayoutTests/';
37var TEST_URL_BASE_PATH = "http://svn.webkit.org/repository/webkit/trunk/LayoutTests/";
38var EXPECTATIONS_URL_BASE_PATH = TEST_URL_BASE_PATH + "platform/";
39var TEST_RESULTS_BASE_PATH = 'http://build.chromium.org/f/chromium/layout_test_results/';
40var GPU_RESULTS_BASE_PATH = 'http://chromium-browser-gpu-tests.commondatastorage.googleapis.com/runs/'
41
42var PLATFORMS = {
43 'CHROMIUM': {
44 expectationsDirectory: 'chromium',
45 subPlatforms: {
46 'LION': { fallbackPlatforms: ['CHROMIUM'] },
47 'SNOWLEOPARD': { fallbackPlatforms: ['CHROMIUM'] },
48 'XP': { fallbackPlatforms: ['CHROMIUM'] },
49 'VISTA': { fallbackPlatforms: ['CHROMIUM'] },
50 'WIN7': { fallbackPlatforms: ['CHROMIUM'] },
51 'LUCID': { fallbackPlatforms: ['CHROMIUM'] },
52 'ANDROID': { fallbackPlatforms: ['CHROMIUM'], expectationsDirectory: 'chromium-android' }
53 },
54 platformModifierUnions: {
55 'MAC': ['CHROMIUM_LION', 'CHROMIUM_SNOWLEOPARD'],
56 'WIN': ['CHROMIUM_XP', 'CHROMIUM_VISTA', 'CHROMIUM_WIN7'],
57 'LINUX': ['CHROMIUM_LUCID']
58 }
59 },
60 'APPLE': {
61 subPlatforms: {
62 'MAC': {
63 expectationsDirectory: 'mac',
64 subPlatforms: {
65 'LION': {
66 expectationsDirectory: 'mac-lion',
67 subPlatforms: {
68 'WK1': { fallbackPlatforms: ['APPLE_MAC_LION', 'APPLE_MAC'] },
69 'WK2': { fallbackPlatforms: ['APPLE_MAC_LION', 'APPLE_MAC', 'WK2'] }
70 }
71 },
72 'SNOWLEOPARD': {
73 expectationsDirectory: 'mac-snowleopard',
74 subPlatforms: {
75 'WK1': { fallbackPlatforms: ['APPLE_MAC_SNOWLEOPARD', 'APPLE_MAC'] },
76 'WK2': { fallbackPlatforms: ['APPLE_MAC_SNOWLEOPARD', 'APPLE_MAC', 'WK2'] }
77 }
78 }
79 }
80 },
81 'WIN': {
82 expectationsDirectory: 'win',
83 subPlatforms: {
84 'XP': { fallbackPlatforms: ['APPLE_WIN'] },
85 'WIN7': { fallbackPlatforms: ['APPLE_WIN'] }
86 }
87 }
88 }
89 },
90 'GTK': {
91 expectationsDirectory: 'gtk',
92 subPlatforms: {
93 'LINUX': {
94 subPlatforms: {
95 'WK1': { fallbackPlatforms: ['GTK'] },
96 'WK2': { fallbackPlatforms: ['GTK', 'WK2'], expectationsDirectory: 'gtk-wk2' }
97 }
98 }
99 }
100 },
101 'QT': {
102 expectationsDirectory: 'qt',
103 subPlatforms: {
104 'LINUX': { fallbackPlatforms: ['QT'] }
105 }
106 },
107 'EFL': {
108 expectationsDirectory: 'efl',
109 subPlatforms: {
110 'LINUX': {
111 subPlatforms: {
112 'WK1': { fallbackPlatforms: ['EFL'], expectationsDirectory: 'efl-wk1' },
113 'WK2': { fallbackPlatforms: ['EFL', 'WK2'], expectationsDirectory: 'efl-wk2' }
114 }
115 }
116 }
117 },
118 'WK2': {
119 basePlatform: true,
120 expectationsDirectory: 'wk2'
121 }
122};
123
124var BUILD_TYPES = {'DEBUG': 'DBG', 'RELEASE': 'RELEASE'};
125var MIN_SECONDS_FOR_SLOW_TEST = 4;
126var MIN_SECONDS_FOR_SLOW_TEST_DEBUG = 2 * MIN_SECONDS_FOR_SLOW_TEST;
127var FAIL_RESULTS = ['IMAGE', 'IMAGE+TEXT', 'TEXT', 'MISSING'];
128var CHUNK_SIZE = 25;
129var MAX_RESULTS = 1500;
130
131// FIXME: Figure out how to make this not be hard-coded.
132var VIRTUAL_SUITES = {
133 'platform/chromium/virtual/gpu/fast/canvas': 'fast/canvas',
134 'platform/chromium/virtual/gpu/canvas/philip': 'canvas/philip'
135};
136
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000137var resourceLoader;
138
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000139//////////////////////////////////////////////////////////////////////////////
140// Methods and objects from dashboard_base.js to override.
141//////////////////////////////////////////////////////////////////////////////
142function generatePage()
143{
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000144 if (g_history.crossDashboardState.useTestData)
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000145 return;
146
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000147 document.body.innerHTML = '<div id="loading-ui">LOADING...</div>';
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000148 resourceLoader.showErrors();
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000149
150 // tests expands to all tests that match the CSV list.
151 // result expands to all tests that ever have the given result
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000152 if (g_history.dashboardSpecificState.tests || g_history.dashboardSpecificState.result)
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000153 generatePageForIndividualTests(individualTests());
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000154 else if (g_history.dashboardSpecificState.expectationsUpdate)
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000155 generatePageForExpectationsUpdate();
156 else
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000157 generatePageForBuilder(g_history.dashboardSpecificState.builder || currentBuilderGroup().defaultBuilder());
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000158
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000159 for (var builder in currentBuilders())
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000160 processTestResultsForBuilderAsync(builder);
161
162 postHeightChangedMessage();
163}
164
165function handleValidHashParameter(key, value)
166{
167 switch(key) {
168 case 'tests':
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000169 history.validateParameter(g_history.dashboardSpecificState, key, value,
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000170 function() {
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000171 return string.isValidName(value);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000172 });
173 return true;
174
175 case 'result':
176 value = value.toUpperCase();
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000177 history.validateParameter(g_history.dashboardSpecificState, key, value,
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000178 function() {
179 for (var result in LAYOUT_TEST_EXPECTATIONS_MAP_) {
180 if (value == LAYOUT_TEST_EXPECTATIONS_MAP_[result])
181 return true;
182 }
183 return false;
184 });
185 return true;
186
187 case 'builder':
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000188 history.validateParameter(g_history.dashboardSpecificState, key, value,
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000189 function() {
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000190 return value in currentBuilders();
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000191 });
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000192
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000193 return true;
194
195 case 'sortColumn':
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000196 history.validateParameter(g_history.dashboardSpecificState, key, value,
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000197 function() {
198 // Get all possible headers since the actual used set of headers
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000199 // depends on the values in g_history.dashboardSpecificState, which are currently being set.
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000200 var headers = tableHeaders(true);
201 for (var i = 0; i < headers.length; i++) {
202 if (value == sortColumnFromTableHeader(headers[i]))
203 return true;
204 }
205 return value == 'test' || value == 'builder';
206 });
207 return true;
208
209 case 'sortOrder':
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000210 history.validateParameter(g_history.dashboardSpecificState, key, value,
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000211 function() {
212 return value == FORWARD || value == BACKWARD;
213 });
214 return true;
215
216 case 'resultsHeight':
217 case 'updateIndex':
218 case 'revision':
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000219 history.validateParameter(g_history.dashboardSpecificState, key, Number(value),
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000220 function() {
221 return value.match(/^\d+$/);
222 });
223 return true;
224
225 case 'showChrome':
226 case 'showCorrectExpectations':
227 case 'showWrongExpectations':
228 case 'showExpectations':
229 case 'showFlaky':
230 case 'showLargeExpectations':
231 case 'legacyExpectationsSemantics':
232 case 'showSkipped':
233 case 'showSlow':
234 case 'showUnexpectedPasses':
235 case 'showWontFixSkip':
236 case 'expectationsUpdate':
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000237 g_history.dashboardSpecificState[key] = value == 'true';
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000238 return true;
239
240 default:
241 return false;
242 }
243}
244
245g_defaultDashboardSpecificStateValues = {
246 sortOrder: BACKWARD,
247 sortColumn: 'flakiness',
248 showExpectations: false,
249 showFlaky: true,
250 showLargeExpectations: false,
251 legacyExpectationsSemantics: true,
252 showChrome: true,
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000253 showCorrectExpectations: !g_history.isLayoutTestResults(),
254 showWrongExpectations: !g_history.isLayoutTestResults(),
255 showWontFixSkip: !g_history.isLayoutTestResults(),
256 showSlow: !g_history.isLayoutTestResults(),
257 showSkipped: !g_history.isLayoutTestResults(),
258 showUnexpectedPasses: !g_history.isLayoutTestResults(),
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000259 expectationsUpdate: false,
260 updateIndex: 0,
261 resultsHeight: 300,
262 revision: null,
263 tests: '',
264 result: '',
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000265 builder: null
266};
267
268DB_SPECIFIC_INVALIDATING_PARAMETERS = {
269 'tests' : 'builder',
270 'testType': 'builder',
271 'group': 'builder'
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000272};
273
274//////////////////////////////////////////////////////////////////////////////
275// GLOBALS
276//////////////////////////////////////////////////////////////////////////////
277
278var g_perBuilderPlatformAndBuildType = {};
279var g_perBuilderFailures = {};
280// Map of builder to arrays of tests that are listed in the expectations file
281// but have for that builder.
282var g_perBuilderWithExpectationsButNoFailures = {};
283// Map of builder to arrays of paths that are skipped. This shows the raw
284// path used in TestExpectations rather than the test path since we
285// don't actually have any data here for skipped tests.
286var g_perBuilderSkippedPaths = {};
287// Maps test path to an array of {builder, testResults} objects.
288var g_testToResultsMap = {};
289// Tests that the user wants to update expectations for.
290var g_confirmedTests = {};
291
292function traversePlatformsTree(callback)
293{
294 function traverse(platformObject, parentPlatform) {
295 Object.keys(platformObject).forEach(function(platformName) {
296 var platform = platformObject[platformName];
297 platformName = parentPlatform ? parentPlatform + platformName : platformName;
298
299 if (platform.subPlatforms)
300 traverse(platform.subPlatforms, platformName + '_');
301 else if (!platform.basePlatform)
302 callback(platform, platformName);
303 });
304 }
305 traverse(PLATFORMS, null);
306}
307
308function createResultsObjectForTest(test, builder)
309{
310 return {
311 test: test,
312 builder: builder,
313 // HTML for display of the results in the flakiness column
314 html: '',
315 flips: 0,
316 slowestTime: 0,
317 slowestNonTimeoutCrashTime: 0,
318 meetsExpectations: true,
319 isWontFixSkip: false,
320 isFlaky: false,
321 // Sorted string of missing expectations
322 missing: '',
323 // String of extra expectations (i.e. expectations that never occur).
324 extra: '',
325 modifiers: '',
326 bugs: '',
327 expectations : '',
328 rawResults: '',
329 // List of all the results the test actually has.
330 actualResults: []
331 };
332}
333
334function matchingElement(stringToMatch, elementsMap)
335{
336 for (var element in elementsMap) {
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000337 if (string.contains(stringToMatch, elementsMap[element]))
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000338 return element;
339 }
340}
341
342function determineWKPlatform(builderName, basePlatform)
343{
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000344 var isWK2Builder = string.contains(builderName, 'WK2') || string.contains(builderName, 'WEBKIT2');
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000345 return basePlatform + (isWK2Builder ? '_WK2' : '_WK1');
346}
347
348function nonChromiumPlatform(builderNameUpperCase)
349{
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000350 if (string.contains(builderNameUpperCase, 'WINDOWS 7'))
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000351 return 'APPLE_WIN_WIN7';
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000352 if (string.contains(builderNameUpperCase, 'WINDOWS XP'))
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000353 return 'APPLE_WIN_XP';
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000354 if (string.contains(builderNameUpperCase, 'QT LINUX'))
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000355 return 'QT_LINUX';
356
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000357 if (string.contains(builderNameUpperCase, 'LION'))
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000358 return determineWKPlatform(builderNameUpperCase, 'APPLE_MAC_LION');
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000359 if (string.contains(builderNameUpperCase, 'SNOWLEOPARD'))
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000360 return determineWKPlatform(builderNameUpperCase, 'APPLE_MAC_SNOWLEOPARD');
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000361 if (string.contains(builderNameUpperCase, 'GTK LINUX'))
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000362 return determineWKPlatform(builderNameUpperCase, 'GTK_LINUX');
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000363 if (string.contains(builderNameUpperCase, 'EFL'))
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000364 return determineWKPlatform(builderNameUpperCase, 'EFL_LINUX');
365}
366
367function chromiumPlatform(builderNameUpperCase)
368{
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000369 if (string.contains(builderNameUpperCase, 'MAC')) {
370 if (string.contains(builderNameUpperCase, '10.7'))
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000371 return 'CHROMIUM_LION';
372 // The webkit.org 'Chromium Mac Release (Tests)' bot runs SnowLeopard.
373 return 'CHROMIUM_SNOWLEOPARD';
374 }
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000375 if (string.contains(builderNameUpperCase, 'WIN7'))
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000376 return 'CHROMIUM_WIN7';
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000377 if (string.contains(builderNameUpperCase, 'VISTA'))
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000378 return 'CHROMIUM_VISTA';
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000379 if (string.contains(builderNameUpperCase, 'WIN') || string.contains(builderNameUpperCase, 'XP'))
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000380 return 'CHROMIUM_XP';
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000381 if (string.contains(builderNameUpperCase, 'LINUX'))
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000382 return 'CHROMIUM_LUCID';
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000383 if (string.contains(builderNameUpperCase, 'ANDROID'))
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000384 return 'CHROMIUM_ANDROID';
385 // The interactive bot is XP, but doesn't have an OS in it's name.
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000386 if (string.contains(builderNameUpperCase, 'INTERACTIVE'))
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000387 return 'CHROMIUM_XP';
388}
389
390
391function platformAndBuildType(builderName)
392{
393 if (!g_perBuilderPlatformAndBuildType[builderName]) {
394 var builderNameUpperCase = builderName.toUpperCase();
395
396 var platform = '';
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000397 if (g_history.isLayoutTestResults() && currentBuilderGroupName() == '@ToT - webkit.org' && !string.contains(builderNameUpperCase, 'CHROMIUM'))
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000398 platform = nonChromiumPlatform(builderNameUpperCase);
399 else
400 platform = chromiumPlatform(builderNameUpperCase);
401
402 if (!platform)
403 console.error('Could not resolve platform for builder: ' + builderName);
404
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000405 var buildType = string.contains(builderNameUpperCase, 'DBG') || string.contains(builderNameUpperCase, 'DEBUG') ? 'DEBUG' : 'RELEASE';
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000406 g_perBuilderPlatformAndBuildType[builderName] = {platform: platform, buildType: buildType};
407 }
408 return g_perBuilderPlatformAndBuildType[builderName];
409}
410
411function isDebug(builderName)
412{
413 return platformAndBuildType(builderName).buildType == 'DEBUG';
414}
415
416// Returns the expectation string for the given single character result.
417// This string should match the expectations that are put into
418// test_expectations.py.
419//
420// For example, if we start explicitly listing IMAGE result failures,
421// this function should start returning 'IMAGE'.
422function expectationsFileStringForResult(result)
423{
424 // For the purposes of comparing against the expecations of a test,
425 // consider simplified diff failures as just text failures since
426 // the test_expectations file doesn't treat them specially.
427 if (result == 'S')
428 return 'TEXT';
429
430 if (result == 'N')
431 return '';
432
433 return expectationsMap()[result];
434}
435
436var TestTrie = function(builders, resultsByBuilder)
437{
438 this._trie = {};
439
440 for (var builder in builders) {
441 var testsForBuilder = resultsByBuilder[builder].tests;
442 for (var test in testsForBuilder)
443 this._addTest(test.split('/'), this._trie);
444 }
445}
446
447TestTrie.prototype.forEach = function(callback, startingTriePath)
448{
449 var testsTrie = this._trie;
450 if (startingTriePath) {
451 var splitPath = startingTriePath.split('/');
452 while (splitPath.length && testsTrie)
453 testsTrie = testsTrie[splitPath.shift()];
454 }
455
456 if (!testsTrie)
457 return;
458
459 function traverse(trie, triePath) {
460 if (trie == true)
461 callback(triePath);
462 else {
463 for (var member in trie)
464 traverse(trie[member], triePath ? triePath + '/' + member : member);
465 }
466 }
467 traverse(testsTrie, startingTriePath);
468}
469
470TestTrie.prototype._addTest = function(test, trie)
471{
472 var rootComponent = test.shift();
473 if (!test.length) {
474 if (!trie[rootComponent])
475 trie[rootComponent] = true;
476 return;
477 }
478
479 if (!trie[rootComponent] || trie[rootComponent] == true)
480 trie[rootComponent] = {};
481 this._addTest(test, trie[rootComponent]);
482}
483
484// Map of all tests to true values. This is just so we can have the list of
485// all tests across all the builders.
486var g_allTestsTrie;
487
488function getAllTestsTrie()
489{
490 if (!g_allTestsTrie)
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000491 g_allTestsTrie = new TestTrie(currentBuilders(), g_resultsByBuilder);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000492
493 return g_allTestsTrie;
494}
495
496// Returns an array of tests to be displayed in the individual tests view.
497// Note that a directory can be listed as a test, so we expand that into all
498// tests in the directory.
499function individualTests()
500{
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000501 if (g_history.dashboardSpecificState.result)
502 return allTestsWithResult(g_history.dashboardSpecificState.result);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000503
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000504 if (!g_history.dashboardSpecificState.tests)
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000505 return [];
506
507 return individualTestsForSubstringList();
508}
509
510function substringList()
511{
512 // Convert windows slashes to unix slashes.
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000513 var tests = g_history.dashboardSpecificState.tests.replace(/\\/g, '/');
514 var separator = string.contains(tests, ' ') ? ' ' : ',';
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000515 var testList = tests.split(separator);
516
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000517 if (g_history.isLayoutTestResults())
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000518 return testList;
519
520 var testListWithoutModifiers = [];
521 testList.forEach(function(path) {
522 GTEST_MODIFIERS.forEach(function(modifier) {
523 path = path.replace('.' + modifier + '_', '.');
524 });
525 testListWithoutModifiers.push(path);
526 });
527 return testListWithoutModifiers;
528}
529
530function individualTestsForSubstringList()
531{
532 var testList = substringList();
533
534 // Put the tests into an object first and then move them into an array
535 // as a way of deduping.
536 var testsMap = {};
537 for (var i = 0; i < testList.length; i++) {
538 var path = testList[i];
539
540 // Ignore whitespace entries as they'd match every test.
541 if (path.match(/^\s*$/))
542 continue;
543
544 var hasAnyMatches = false;
545 getAllTestsTrie().forEach(function(triePath) {
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000546 if (string.caseInsensitiveContains(triePath, path)) {
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000547 testsMap[triePath] = 1;
548 hasAnyMatches = true;
549 }
550 });
551
552 // If a path doesn't match any tests, then assume it's a full path
553 // to a test that passes on all builders.
554 if (!hasAnyMatches)
555 testsMap[path] = 1;
556 }
557
558 var testsArray = [];
559 for (var test in testsMap)
560 testsArray.push(test);
561 return testsArray;
562}
563
564// Returns whether this test's slowest time is above the cutoff for
565// being a slow test.
566function isSlowTest(resultsForTest)
567{
568 var maxTime = isDebug(resultsForTest.builder) ? MIN_SECONDS_FOR_SLOW_TEST_DEBUG : MIN_SECONDS_FOR_SLOW_TEST;
569 return resultsForTest.slowestNonTimeoutCrashTime > maxTime;
570}
571
572// Returns whether this test's slowest time is *well* below the cutoff for
573// being a slow test.
574function isFastTest(resultsForTest)
575{
576 var maxTime = isDebug(resultsForTest.builder) ? MIN_SECONDS_FOR_SLOW_TEST_DEBUG : MIN_SECONDS_FOR_SLOW_TEST;
577 return resultsForTest.slowestNonTimeoutCrashTime < maxTime / 2;
578}
579
580function allTestsWithResult(result)
581{
582 processTestRunsForAllBuilders();
583 var retVal = [];
584
585 getAllTestsTrie().forEach(function(triePath) {
586 for (var i = 0; i < g_testToResultsMap[triePath].length; i++) {
587 if (g_testToResultsMap[triePath][i].actualResults.indexOf(result) != -1) {
588 retVal.push(triePath);
589 break;
590 }
591 }
592 });
593
594 return retVal;
595}
596
597
598// Adds all the tests for the given builder to the testMapToPopulate.
599function addTestsForBuilder(builder, testMapToPopulate)
600{
601 var tests = g_resultsByBuilder[builder].tests;
602 for (var test in tests) {
603 testMapToPopulate[test] = true;
604 }
605}
606
607// Map of all tests to true values by platform and build type.
608// e.g. g_allTestsByPlatformAndBuildType['XP']['DEBUG'] will have the union
609// of all tests run on the xp-debug builders.
610var g_allTestsByPlatformAndBuildType = {};
611traversePlatformsTree(function(platform, platformName) {
612 g_allTestsByPlatformAndBuildType[platformName] = {};
613});
614
615// Map of all tests to true values by platform and build type.
616// e.g. g_allTestsByPlatformAndBuildType['WIN']['DEBUG'] will have the union
617// of all tests run on the win-debug builders.
618function allTestsWithSamePlatformAndBuildType(platform, buildType)
619{
620 if (!g_allTestsByPlatformAndBuildType[platform][buildType]) {
621 var tests = {};
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000622 for (var thisBuilder in currentBuilders()) {
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000623 var thisBuilderBuildInfo = platformAndBuildType(thisBuilder);
624 if (thisBuilderBuildInfo.buildType == buildType && thisBuilderBuildInfo.platform == platform) {
625 addTestsForBuilder(thisBuilder, tests);
626 }
627 }
628 g_allTestsByPlatformAndBuildType[platform][buildType] = tests;
629 }
630
631 return g_allTestsByPlatformAndBuildType[platform][buildType];
632}
633
634function getExpectations(test, platform, buildType)
635{
636 var testObject = g_allExpectations[test];
637 if (!testObject)
638 return null;
639
640 var platformObject = testObject[platform];
641 if (!platformObject)
642 return null;
643
644 return platformObject[buildType];
645}
646
647function filterBugs(modifiers)
648{
649 var bugs = modifiers.match(/\b(Bug|webkit.org|crbug.com|code.google.com)\S*/g);
650 if (!bugs)
651 return {bugs: '', modifiers: modifiers};
652 for (var j = 0; j < bugs.length; j++)
653 modifiers = modifiers.replace(bugs[j], '');
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000654 return {bugs: bugs.join(' '), modifiers: string.collapseWhitespace(string.trimString(modifiers))};
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000655}
656
657function populateExpectationsData(resultsObject)
658{
659 var buildInfo = platformAndBuildType(resultsObject.builder);
660 var expectations = getExpectations(resultsObject.test, buildInfo.platform, buildInfo.buildType);
661 if (!expectations)
662 return;
663
664 resultsObject.expectations = expectations.expectations;
665 var filteredModifiers = filterBugs(expectations.modifiers);
666 resultsObject.modifiers = filteredModifiers.modifiers;
667 resultsObject.bugs = filteredModifiers.bugs;
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000668 resultsObject.isWontFixSkip = string.contains(expectations.modifiers, 'WONTFIX') || string.contains(expectations.modifiers, 'SKIP');
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000669}
670
671function platformObjectForName(platformName)
672{
673 var platformsList = platformName.split("_");
674 var platformObject = PLATFORMS[platformsList.shift()];
675 platformsList.forEach(function(platformName) {
676 platformObject = platformObject.subPlatforms[platformName];
677 });
678 return platformObject;
679}
680
681// Data structure to hold the processed expectations.
682// g_allExpectations[testPath][platform][buildType] gets the object that has
683// expectations and modifiers properties for this platform/buildType.
684//
685// platform and buildType both go through fallback sets of keys from most
686// specific key to least specific. For example, on Windows XP, we first
687// check the platform WIN-XP, if there's no such object, we check WIN,
688// then finally we check ALL. For build types, we check the current
689// buildType, then ALL.
690var g_allExpectations;
691
692function getParsedExpectations(data)
693{
694 var expectations = [];
695 var lines = data.split('\n');
696 lines.forEach(function(line) {
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000697 line = string.trimString(line);
698 if (!line || string.startsWith(line, '#'))
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000699 return;
700
701 // This code mimics _tokenize_line_using_new_format() in
702 // Tools/Scripts/webkitpy/layout_tests/models/test_expectations.py
703 //
704 // FIXME: consider doing more error checking here.
705 //
706 // FIXME: Clean this all up once we've fully cut over to the new syntax.
707 var tokens = line.split(/\s+/)
708 var parsed_bugs = [];
709 var parsed_modifiers = [];
710 var parsed_path;
711 var parsed_expectations = [];
712 var state = 'start';
713
714 // This clones _configuration_tokens_list in test_expectations.py.
715 // FIXME: unify with the platforms constants at the top of the file.
716 var configuration_tokens = {
717 'Release': 'RELEASE',
718 'Debug': 'DEBUG',
719 'Mac': 'MAC',
720 'Win': 'WIN',
721 'Linux': 'LINUX',
722 'SnowLeopard': 'SNOWLEOPARD',
723 'Lion': 'LION',
724 'MountainLion': 'MOUNTAINLION',
725 'Win7': 'WIN7',
726 'XP': 'XP',
727 'Vista': 'VISTA',
728 'Android': 'ANDROID',
729 };
730
731 var expectation_tokens = {
732 'Crash': 'CRASH',
733 'Failure': 'FAIL',
734 'ImageOnlyFailure': 'IMAGE',
735 'Missing': 'MISSING',
736 'Pass': 'PASS',
737 'Rebaseline': 'REBASELINE',
738 'Skip': 'SKIP',
739 'Slow': 'SLOW',
740 'Timeout': 'TIMEOUT',
741 'WontFix': 'WONTFIX',
742 };
743
744
745 tokens.forEach(function(token) {
746 if (token.indexOf('Bug') != -1 ||
747 token.indexOf('webkit.org') != -1 ||
748 token.indexOf('crbug.com') != -1 ||
749 token.indexOf('code.google.com') != -1) {
750 parsed_bugs.push(token);
751 } else if (token == '[') {
752 if (state == 'start') {
753 state = 'configuration';
754 } else if (state == 'name_found') {
755 state = 'expectations';
756 }
757 } else if (token == ']') {
758 if (state == 'configuration') {
759 state = 'name';
760 } else if (state == 'expectations') {
761 state = 'done';
762 }
763 } else if (state == 'configuration') {
764 parsed_modifiers.push(configuration_tokens[token]);
765 } else if (state == 'expectations') {
766 if (token == 'Rebaseline' || token == 'Skip' || token == 'Slow' || token == 'WontFix') {
767 parsed_modifiers.push(token.toUpperCase());
768 } else {
769 parsed_expectations.push(expectation_tokens[token]);
770 }
771 } else if (token == '#') {
772 state = 'done';
773 } else if (state == 'name' || state == 'start') {
774 parsed_path = token;
775 state = 'name_found';
776 }
777 });
778
779 if (!parsed_expectations.length) {
780 if (parsed_modifiers.indexOf('Slow') == -1) {
781 parsed_modifiers.push('Skip');
782 parsed_expectations = ['Pass'];
783 }
784 }
785
786 // FIXME: Should we include line number and comment lines here?
787 expectations.push({
788 modifiers: parsed_bugs.concat(parsed_modifiers).join(' '),
789 path: parsed_path,
790 expectations: parsed_expectations.join(' '),
791 });
792 });
793 return expectations;
794}
795
796
797function addTestToAllExpectationsForPlatform(test, platformName, expectations, modifiers)
798{
799 if (!g_allExpectations[test])
800 g_allExpectations[test] = {};
801
802 if (!g_allExpectations[test][platformName])
803 g_allExpectations[test][platformName] = {};
804
805 var allBuildTypes = [];
806 modifiers.split(' ').forEach(function(modifier) {
807 if (modifier in BUILD_TYPES) {
808 allBuildTypes.push(modifier);
809 return;
810 }
811 });
812 if (!allBuildTypes.length)
813 allBuildTypes = Object.keys(BUILD_TYPES);
814
815 allBuildTypes.forEach(function(buildType) {
816 g_allExpectations[test][platformName][buildType] = {modifiers: modifiers, expectations: expectations};
817 });
818}
819
820function processExpectationsForPlatform(platformObject, platformName, expectationsArray)
821{
822 if (!g_allExpectations)
823 g_allExpectations = {};
824
825 if (!expectationsArray)
826 return;
827
828 // Sort the array to hit more specific paths last. More specific
829 // paths (e.g. foo/bar/baz.html) override entries for less-specific ones (e.g. foo/bar).
830 expectationsArray.sort(alphanumericCompare('path'));
831
832 for (var i = 0; i < expectationsArray.length; i++) {
833 var path = expectationsArray[i].path;
834 var modifiers = expectationsArray[i].modifiers;
835 var expectations = expectationsArray[i].expectations;
836
837 var shouldProcessExpectation = false;
838 var hasPlatformModifierUnions = false;
839 if (platformObject.fallbackPlatforms) {
840 platformObject.fallbackPlatforms.forEach(function(fallbackPlatform) {
841 if (shouldProcessExpectation)
842 return;
843
844 var fallbackPlatformObject = platformObjectForName(fallbackPlatform);
845 if (!fallbackPlatformObject.platformModifierUnions)
846 return;
847
848 modifiers.split(' ').forEach(function(modifier) {
849 if (modifier in fallbackPlatformObject.platformModifierUnions) {
850 hasPlatformModifierUnions = true;
851 if (fallbackPlatformObject.platformModifierUnions[modifier].indexOf(platformName) != -1)
852 shouldProcessExpectation = true;
853 }
854 });
855 });
856 }
857
858 if (!hasPlatformModifierUnions)
859 shouldProcessExpectation = true;
860
861 if (!shouldProcessExpectation)
862 continue;
863
864 getAllTestsTrie().forEach(function(triePath) {
865 addTestToAllExpectationsForPlatform(triePath, platformName, expectations, modifiers);
866 }, path);
867 }
868}
869
870function processExpectations()
871{
872 // FIXME: An expectations-by-platform object should be passed into this function rather than checking
873 // for a global object. That way this function can be better tested and meaningful errors can
874 // be reported when expectations for a given platform are not found in that object.
875 if (!g_expectationsByPlatform)
876 return;
877
878 traversePlatformsTree(function(platform, platformName) {
879 if (platform.fallbackPlatforms) {
880 platform.fallbackPlatforms.forEach(function(fallbackPlatform) {
881 if (fallbackPlatform in g_expectationsByPlatform)
882 processExpectationsForPlatform(platform, platformName, g_expectationsByPlatform[fallbackPlatform]);
883 });
884 }
885
886 if (platformName in g_expectationsByPlatform)
887 processExpectationsForPlatform(platform, platformName, g_expectationsByPlatform[platformName]);
888 });
889
890 g_expectationsByPlatform = undefined;
891}
892
893function processMissingTestsWithExpectations(builder, platform, buildType)
894{
895 var noFailures = [];
896 var skipped = [];
897
898 var allTestsForPlatformAndBuildType = allTestsWithSamePlatformAndBuildType(platform, buildType);
899 for (var test in g_allExpectations) {
900 var expectations = getExpectations(test, platform, buildType);
901
902 if (!expectations)
903 continue;
904
905 // Test has expectations, but no result in the builders results.
906 // This means it's either SKIP or passes on all builds.
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000907 if (!allTestsForPlatformAndBuildType[test] && !string.contains(expectations.modifiers, 'WONTFIX')) {
908 if (string.contains(expectations.modifiers, 'SKIP'))
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000909 skipped.push(test);
910 else if (!expectations.expectations.match(/^\s*PASS\s*$/)) {
911 // Don't show tests expected to always pass. This is used in ways like
912 // the following:
913 // foo/bar = FAIL
914 // foo/bar/baz.html = PASS
915 noFailures.push({test: test, expectations: expectations.expectations, modifiers: expectations.modifiers});
916 }
917 }
918 }
919
920 g_perBuilderSkippedPaths[builder] = skipped.sort();
921 g_perBuilderWithExpectationsButNoFailures[builder] = noFailures.sort();
922}
923
924function processTestResultsForBuilderAsync(builder)
925{
926 setTimeout(function() { processTestRunsForBuilder(builder); }, 0);
927}
928
929function processTestRunsForAllBuilders()
930{
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000931 for (var builder in currentBuilders())
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000932 processTestRunsForBuilder(builder);
933}
934
935function processTestRunsForBuilder(builderName)
936{
937 if (g_perBuilderFailures[builderName])
938 return;
939
940 if (!g_resultsByBuilder[builderName]) {
941 console.error('No tests found for ' + builderName);
942 g_perBuilderFailures[builderName] = [];
943 return;
944 }
945
946 processExpectations();
Torne (Richard Coles)926b0012013-03-28 15:32:48 +0000947
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +0000948 var buildInfo = platformAndBuildType(builderName);
949 var platform = buildInfo.platform;
950 var buildType = buildInfo.buildType;
951 processMissingTestsWithExpectations(builderName, platform, buildType);
952
953 var failures = [];
954 var allTestsForThisBuilder = g_resultsByBuilder[builderName].tests;
955
956 for (var test in allTestsForThisBuilder) {
957 var resultsForTest = createResultsObjectForTest(test, builderName);
958 populateExpectationsData(resultsForTest);
959
960 var rawTest = g_resultsByBuilder[builderName].tests[test];
961 resultsForTest.rawTimes = rawTest.times;
962 var rawResults = rawTest.results;
963 resultsForTest.rawResults = rawResults;
964
965 // FIXME: Switch to resultsByBuild
966 var times = resultsForTest.rawTimes;
967 var numTimesSeen = 0;
968 var numResultsSeen = 0;
969 var resultsIndex = 0;
970 var currentResult;
971 for (var i = 0; i < times.length; i++) {
972 numTimesSeen += times[i][RLE.LENGTH];
973
974 while (rawResults[resultsIndex] && numTimesSeen > (numResultsSeen + rawResults[resultsIndex][RLE.LENGTH])) {
975 numResultsSeen += rawResults[resultsIndex][RLE.LENGTH];
976 resultsIndex++;
977 }
978
979 if (rawResults && rawResults[resultsIndex])
980 currentResult = rawResults[resultsIndex][RLE.VALUE];
981
982 var time = times[i][RLE.VALUE]
983
984 // Ignore times for crashing/timeout runs for the sake of seeing if
985 // a test should be marked slow.
986 if (currentResult != 'C' && currentResult != 'T')
987 resultsForTest.slowestNonTimeoutCrashTime = Math.max(resultsForTest.slowestNonTimeoutCrashTime, time);
988 resultsForTest.slowestTime = Math.max(resultsForTest.slowestTime, time);
989 }
990
991 processMissingAndExtraExpectations(resultsForTest);
992 failures.push(resultsForTest);
993
994 if (!g_testToResultsMap[test])
995 g_testToResultsMap[test] = [];
996 g_testToResultsMap[test].push(resultsForTest);
997 }
998
999 g_perBuilderFailures[builderName] = failures;
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001000}
1001
1002function processMissingAndExtraExpectations(resultsForTest)
1003{
1004 // Heuristic for determining whether expectations apply to a given test:
1005 // -If a test result happens < MIN_RUNS_FOR_FLAKE, then consider it a flaky
1006 // result and include it in the list of expected results.
1007 // -Otherwise, grab the first contiguous set of runs with the same result
1008 // for >= MIN_RUNS_FOR_FLAKE and ignore all following runs >=
1009 // MIN_RUNS_FOR_FLAKE.
1010 // This lets us rule out common cases of a test changing expectations for
1011 // a few runs, then being fixed or otherwise modified in a non-flaky way.
1012 var rawResults = resultsForTest.rawResults;
1013
1014 // If the first result is no-data that means the test is skipped or is
1015 // being run on a different builder (e.g. moved from one shard to another).
1016 // Ignore these results since we have no real data about what's going on.
1017 if (rawResults[0][RLE.VALUE] == 'N')
1018 return;
1019
1020 // Only consider flake if it doesn't happen twice in a row.
1021 var MIN_RUNS_FOR_FLAKE = 2;
1022 var resultsMap = {}
1023 var numResultsSeen = 0;
1024 var haveSeenNonFlakeResult = false;
1025 var numRealResults = 0;
1026
1027 var seenResults = {};
1028 for (var i = 0; i < rawResults.length; i++) {
1029 var numResults = rawResults[i][RLE.LENGTH];
1030 numResultsSeen += numResults;
1031
1032 var result = rawResults[i][RLE.VALUE];
1033
1034 var hasMinRuns = numResults >= MIN_RUNS_FOR_FLAKE;
1035 if (haveSeenNonFlakeResult && hasMinRuns)
1036 continue;
1037 else if (hasMinRuns)
1038 haveSeenNonFlakeResult = true;
1039 else if (!seenResults[result]) {
1040 // Only consider a short-lived result if we've seen it more than once.
1041 // Otherwise, we include lots of false-positives due to tests that fail
1042 // for a couple runs and then start passing.
1043 seenResults[result] = true;
1044 continue;
1045 }
1046
1047 var expectation = expectationsFileStringForResult(result);
1048 resultsMap[expectation] = true;
1049 numRealResults++;
1050 }
1051
1052 resultsForTest.flips = i - 1;
1053 resultsForTest.isFlaky = numRealResults > 1;
1054
1055 var missingExpectations = [];
1056 var extraExpectations = [];
1057
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001058 if (g_history.isLayoutTestResults()) {
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001059 var expectationsArray = resultsForTest.expectations ? resultsForTest.expectations.split(' ') : [];
1060 extraExpectations = expectationsArray.filter(
1061 function(element) {
1062 // FIXME: Once all the FAIL lines are removed from
1063 // TestExpectations, delete all the legacyExpectationsSemantics
1064 // code.
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001065 if (g_history.dashboardSpecificState.legacyExpectationsSemantics) {
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001066 if (element == 'FAIL') {
1067 for (var i = 0; i < FAIL_RESULTS.length; i++) {
1068 if (resultsMap[FAIL_RESULTS[i]])
1069 return false;
1070 }
1071 return true;
1072 }
1073 }
1074
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001075 return element && !resultsMap[element] && !string.contains(element, 'BUG');
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001076 });
1077
1078 for (var result in resultsMap) {
1079 resultsForTest.actualResults.push(result);
1080 var hasExpectation = false;
1081 for (var i = 0; i < expectationsArray.length; i++) {
1082 var expectation = expectationsArray[i];
1083 // FIXME: Once all the FAIL lines are removed from
1084 // TestExpectations, delete all the legacyExpectationsSemantics
1085 // code.
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001086 if (g_history.dashboardSpecificState.legacyExpectationsSemantics) {
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001087 if (expectation == 'FAIL') {
1088 for (var j = 0; j < FAIL_RESULTS.length; j++) {
1089 if (result == FAIL_RESULTS[j]) {
1090 hasExpectation = true;
1091 break;
1092 }
1093 }
1094 }
1095 }
1096
1097 if (result == expectation)
1098 hasExpectation = true;
1099
1100 if (hasExpectation)
1101 break;
1102 }
1103 // If we have no expectations for a test and it only passes, then don't
1104 // list PASS as a missing expectation. We only want to list PASS if it
1105 // flaky passes, so there would be other expectations.
1106 if (!hasExpectation && !(!expectationsArray.length && result == 'PASS' && numRealResults == 1))
1107 missingExpectations.push(result);
1108 }
1109
1110 // Only highlight tests that take > 2 seconds as needing to be marked as
1111 // slow. There are too many tests that take ~2 seconds every couple
1112 // hundred runs. It's not worth the manual maintenance effort.
1113 // Also, if a test times out, then it should not be marked as slow.
1114 var minTimeForNeedsSlow = isDebug(resultsForTest.builder) ? 2 : 1;
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001115 if (isSlowTest(resultsForTest) && !resultsMap['TIMEOUT'] && (!resultsForTest.modifiers || !string.contains(resultsForTest.modifiers, 'SLOW')))
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001116 missingExpectations.push('SLOW');
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001117 else if (isFastTest(resultsForTest) && resultsForTest.modifiers && string.contains(resultsForTest.modifiers, 'SLOW'))
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001118 extraExpectations.push('SLOW');
1119
1120 // If there are no missing results or modifiers besides build
1121 // type, platform, or bug and the expectations are all extra
1122 // that is, extraExpectations - expectations = PASS,
1123 // include PASS as extra, since that means this line in
1124 // test_expectations can be deleted..
1125 if (!missingExpectations.length && !(resultsForTest.modifiers && realModifiers(resultsForTest.modifiers))) {
1126 var extraPlusPass = extraExpectations.concat(['PASS']);
1127 if (extraPlusPass.sort().toString() == expectationsArray.slice(0).sort().toString())
1128 extraExpectations.push('PASS');
1129 }
1130
1131 }
1132
1133 resultsForTest.meetsExpectations = !missingExpectations.length && !extraExpectations.length;
1134 resultsForTest.missing = missingExpectations.sort().join(' ');
1135 resultsForTest.extra = extraExpectations.sort().join(' ');
1136}
1137
1138
1139var BUG_URL_PREFIX = '<a href="http://';
1140var BUG_URL_POSTFIX = '/$1">crbug.com/$1</a> ';
1141var WEBKIT_BUG_URL_POSTFIX = '/$1">webkit.org/b/$1</a> ';
1142var INTERNAL_BUG_REPLACE_VALUE = BUG_URL_PREFIX + 'b' + BUG_URL_POSTFIX;
1143var EXTERNAL_BUG_REPLACE_VALUE = BUG_URL_PREFIX + 'crbug.com' + BUG_URL_POSTFIX;
1144var WEBKIT_BUG_REPLACE_VALUE = BUG_URL_PREFIX + 'webkit.org/b' + WEBKIT_BUG_URL_POSTFIX;
1145
1146function htmlForBugs(bugs)
1147{
1148 bugs = bugs.replace(/crbug.com\/(\d+)(\ |$)/g, EXTERNAL_BUG_REPLACE_VALUE);
1149 bugs = bugs.replace(/webkit.org\/b\/(\d+)(\ |$)/g, WEBKIT_BUG_REPLACE_VALUE);
1150 return bugs;
1151}
1152
1153function linkHTMLToOpenWindow(url, text)
1154{
1155 return '<a href="' + url + '" target="_blank">' + text + '</a>';
1156}
1157
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001158// FIXME: replaced with ui.html.chromiumRevisionLink/ui.html.webKitRevisionLink
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001159function createBlameListHTML(revisions, index, urlBase, separator, repo)
1160{
1161 var thisRevision = revisions[index];
1162 if (!thisRevision)
1163 return '';
1164
1165 var previousRevision = revisions[index + 1];
1166 if (previousRevision && previousRevision != thisRevision) {
1167 previousRevision++;
1168 return linkHTMLToOpenWindow(urlBase + thisRevision + separator + previousRevision,
1169 repo + ' blamelist r' + previousRevision + ':r' + thisRevision);
1170 } else
1171 return 'At ' + repo + ' revision: ' + thisRevision;
1172}
1173
1174// Returns whether the result for index'th result for testName on builder was
1175// a failure.
1176function isFailure(builder, testName, index)
1177{
1178 var currentIndex = 0;
1179 var rawResults = g_resultsByBuilder[builder].tests[testName].results;
1180 for (var i = 0; i < rawResults.length; i++) {
1181 currentIndex += rawResults[i][RLE.LENGTH];
1182 if (currentIndex > index)
1183 return isFailingResult(rawResults[i][RLE.VALUE]);
1184 }
1185 console.error('Index exceeds number of results: ' + index);
1186}
1187
1188// Returns an array of indexes for all builds where this test failed.
1189function indexesForFailures(builder, testName)
1190{
1191 var rawResults = g_resultsByBuilder[builder].tests[testName].results;
1192 var buildNumbers = g_resultsByBuilder[builder].buildNumbers;
1193 var index = 0;
1194 var failures = [];
1195 for (var i = 0; i < rawResults.length; i++) {
1196 var numResults = rawResults[i][RLE.LENGTH];
1197 if (isFailingResult(rawResults[i][RLE.VALUE])) {
1198 for (var j = 0; j < numResults; j++)
1199 failures.push(index + j);
1200 }
1201 index += numResults;
1202 }
1203 return failures;
1204}
1205
1206// Returns the path to the failure log for this non-webkit test.
1207function pathToFailureLog(testName)
1208{
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001209 return '/steps/' + g_history.crossDashboardState.testType + '/logs/' + testName.split('.')[1]
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001210}
1211
1212function showPopupForBuild(e, builder, index, opt_testName)
1213{
1214 var html = '';
1215
1216 var time = g_resultsByBuilder[builder].secondsSinceEpoch[index];
1217 if (time) {
1218 var date = new Date(time * 1000);
1219 html += date.toLocaleDateString() + ' ' + date.toLocaleTimeString();
1220 }
1221
1222 var buildNumber = g_resultsByBuilder[builder].buildNumbers[index];
1223 var master = builderMaster(builder);
1224 var buildBasePath = master.logPath(builder, buildNumber);
1225
1226 html += '<ul><li>' + linkHTMLToOpenWindow(buildBasePath, 'Build log') +
1227 '</li><li>' +
1228 createBlameListHTML(g_resultsByBuilder[builder].webkitRevision, index,
1229 'http://trac.webkit.org/log/?verbose=on&rev=', '&stop_rev=',
1230 'WebKit') +
1231 '</li>';
1232
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001233 if (master.name == WEBKIT_BUILDER_MASTER) {
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001234 var revision = g_resultsByBuilder[builder].webkitRevision[index];
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001235 html += '<li><span class=link onclick="g_history.setQueryParameter(\'revision\',' +
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001236 revision + ')">Show results for WebKit r' + revision +
1237 '</span></li>';
1238 } else {
1239 html += '<li>' +
1240 createBlameListHTML(g_resultsByBuilder[builder].chromeRevision, index,
1241 'http://build.chromium.org/f/chromium/perf/dashboard/ui/changelog.html?url=/trunk/src&mode=html&range=', ':', 'Chrome') +
1242 '</li>';
1243
1244 var chromeRevision = g_resultsByBuilder[builder].chromeRevision[index];
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001245 if (chromeRevision && g_history.isLayoutTestResults()) {
1246 html += '<li><a href="' + TEST_RESULTS_BASE_PATH + currentBuilders()[builder] +
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001247 '/' + chromeRevision + '/layout-test-results.zip">layout-test-results.zip</a></li>';
1248 }
1249 }
1250
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001251 if (!g_history.isLayoutTestResults() && opt_testName && isFailure(builder, opt_testName, index))
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001252 html += '<li>' + linkHTMLToOpenWindow(buildBasePath + pathToFailureLog(opt_testName), 'Failure log') + '</li>';
1253
1254 html += '</ul>';
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001255 ui.popup.show(e.target, html);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001256}
1257
1258function htmlForTestResults(test)
1259{
1260 var html = '';
1261 var results = test.rawResults.concat();
1262 var times = test.rawTimes.concat();
1263 var builder = test.builder;
1264 var master = builderMaster(builder);
1265 var buildNumbers = g_resultsByBuilder[builder].buildNumbers;
1266
1267 var indexToReplaceCurrentResult = -1;
1268 var indexToReplaceCurrentTime = -1;
1269 var currentResultArray, currentTimeArray, currentResult, innerHTML, resultString;
1270 for (var i = 0; i < buildNumbers.length; i++) {
1271 if (i > indexToReplaceCurrentResult) {
1272 currentResultArray = results.shift();
1273 if (currentResultArray) {
1274 currentResult = currentResultArray[RLE.VALUE];
1275 // Treat simplified diff failures as just text failures.
1276 if (currentResult == 'S')
1277 currentResult = 'F';
1278 indexToReplaceCurrentResult += currentResultArray[RLE.LENGTH];
1279 } else {
1280 currentResult = 'N';
1281 indexToReplaceCurrentResult += buildNumbers.length;
1282 }
1283 resultString = expectationsFileStringForResult(currentResult);
1284 }
1285
1286 if (i > indexToReplaceCurrentTime) {
1287 currentTimeArray = times.shift();
1288 var currentTime = 0;
1289 if (currentResultArray) {
1290 currentTime = currentTimeArray[RLE.VALUE];
1291 indexToReplaceCurrentTime += currentTimeArray[RLE.LENGTH];
1292 } else
1293 indexToReplaceCurrentTime += buildNumbers.length;
1294
1295 innerHTML = currentTime || '&nbsp;';
1296 }
1297
1298 var extraClassNames = '';
1299 var webkitRevision = g_resultsByBuilder[builder].webkitRevision;
1300 var isWebkitMerge = webkitRevision[i + 1] && webkitRevision[i] != webkitRevision[i + 1];
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001301 if (isWebkitMerge && master.name != WEBKIT_BUILDER_MASTER)
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001302 extraClassNames += ' merge';
1303
1304 html += '<td title="' + (resultString || 'NO DATA') + '. Click for more info." class="results ' + currentResult +
1305 extraClassNames + '" onclick=\'showPopupForBuild(event, "' + builder + '",' + i + ',"' + test.test + '")\'>' + innerHTML;
1306 }
1307 return html;
1308}
1309
1310function htmlForTestsWithExpectationsButNoFailures(builder)
1311{
1312 var tests = g_perBuilderWithExpectationsButNoFailures[builder];
1313 var skippedPaths = g_perBuilderSkippedPaths[builder];
1314 var showUnexpectedPassesLink = linkHTMLToToggleState('showUnexpectedPasses', 'tests that have not failed in last ' + g_resultsByBuilder[builder].buildNumbers.length + ' runs');
1315 var showSkippedLink = linkHTMLToToggleState('showSkipped', 'skipped tests in TestExpectations');
1316
1317
1318 var html = '';
1319 if (tests.length || skippedPaths.length) {
1320 var buildInfo = platformAndBuildType(builder);
1321 html += '<h2 style="display:inline-block">Expectations for ' + buildInfo.platform + '-' + buildInfo.buildType + '</h2> ';
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001322 if (!g_history.dashboardSpecificState.showUnexpectedPasses && tests.length)
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001323 html += showUnexpectedPassesLink;
1324 html += ' ';
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001325 if (!g_history.dashboardSpecificState.showSkipped && skippedPaths.length)
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001326 html += showSkippedLink;
1327 }
1328
1329 var open = '<div onclick="selectContents(this)">';
1330
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001331 if (g_history.dashboardSpecificState.showUnexpectedPasses && tests.length) {
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001332 html += '<div id="passing-tests">' + showUnexpectedPassesLink;
1333 for (var i = 0; i < tests.length; i++)
1334 html += open + tests[i].test + '</div>';
1335 html += '</div>';
1336 }
1337
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001338 if (g_history.dashboardSpecificState.showSkipped && skippedPaths.length)
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001339 html += '<div id="skipped-tests">' + showSkippedLink + open + skippedPaths.join('</div>' + open) + '</div></div>';
1340 return html + '<br>';
1341}
1342
1343// Returns whether we should exclude test results from the test table.
1344function shouldHideTest(testResult)
1345{
1346 if (testResult.isWontFixSkip)
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001347 return !g_history.dashboardSpecificState.showWontFixSkip;
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001348
1349 if (testResult.isFlaky)
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001350 return !g_history.dashboardSpecificState.showFlaky;
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001351
1352 if (isSlowTest(testResult))
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001353 return !g_history.dashboardSpecificState.showSlow;
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001354
1355 if (testResult.meetsExpectations)
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001356 return !g_history.dashboardSpecificState.showCorrectExpectations;
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001357
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001358 return !g_history.dashboardSpecificState.showWrongExpectations;
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001359}
1360
1361// Sets the browser's selection to the element's contents.
1362function selectContents(element)
1363{
1364 window.getSelection().selectAllChildren(element);
1365}
1366
1367function createBugHTML(test)
1368{
1369 var symptom = test.isFlaky ? 'flaky' : 'failing';
1370 var title = encodeURIComponent('Layout Test ' + test.test + ' is ' + symptom);
1371 var description = encodeURIComponent('The following layout test is ' + symptom + ' on ' +
1372 '[insert platform]\n\n' + test.test + '\n\nProbable cause:\n\n' +
1373 '[insert probable cause]');
1374
1375 var component = encodeURIComponent('Tools / Tests');
1376 url = 'https://bugs.webkit.org/enter_bug.cgi?assigned_to=webkit-unassigned%40lists.webkit.org&product=WebKit&form_name=enter_bug&component=' + component + '&short_desc=' + title + '&comment=' + description;
1377 return '<a href="' + url + '" class="file-bug">FILE BUG</a>';
1378}
1379
1380function isCrossBuilderView()
1381{
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001382 return g_history.dashboardSpecificState.tests || g_history.dashboardSpecificState.result || g_history.dashboardSpecificState.expectationsUpdate;
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001383}
1384
1385function tableHeaders(opt_getAll)
1386{
1387 var headers = [];
1388 if (isCrossBuilderView() || opt_getAll)
1389 headers.push('builder');
1390
1391 if (!isCrossBuilderView() || opt_getAll)
1392 headers.push('test');
1393
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001394 if (g_history.isLayoutTestResults() || opt_getAll)
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001395 headers.push('bugs', 'modifiers', 'expectations');
1396
1397 headers.push('slowest run', 'flakiness (numbers are runtimes in seconds)');
1398 return headers;
1399}
1400
1401function htmlForSingleTestRow(test)
1402{
1403 if (!isCrossBuilderView() && shouldHideTest(test)) {
1404 // The innerHTML call is considerably faster if we exclude the rows for
1405 // items we're not showing than if we hide them using display:none.
1406 // For the crossBuilderView, we want to show all rows the user is
1407 // explicitly listing tests to view.
1408 return '';
1409 }
1410
1411 var headers = tableHeaders();
1412 var html = '';
1413 for (var i = 0; i < headers.length; i++) {
1414 var header = headers[i];
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001415 if (string.startsWith(header, 'test') || string.startsWith(header, 'builder')) {
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001416 // If isCrossBuilderView() is true, we're just viewing a single test
1417 // with results for many builders, so the first column is builder names
1418 // instead of test paths.
1419 var testCellClassName = 'test-link' + (isCrossBuilderView() ? ' builder-name' : '');
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001420 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 +00001421
1422 html += '<tr><td class="' + testCellClassName + '">' + testCellHTML;
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001423 } else if (string.startsWith(header, 'bugs'))
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001424 html += '<td class=options-container>' + (test.bugs ? htmlForBugs(test.bugs) : createBugHTML(test));
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001425 else if (string.startsWith(header, 'modifiers'))
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001426 html += '<td class=options-container>' + test.modifiers;
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001427 else if (string.startsWith(header, 'expectations'))
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001428 html += '<td class=options-container>' + test.expectations;
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001429 else if (string.startsWith(header, 'slowest'))
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001430 html += '<td>' + (test.slowestTime ? test.slowestTime + 's' : '');
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001431 else if (string.startsWith(header, 'flakiness'))
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001432 html += htmlForTestResults(test);
1433 }
1434 return html;
1435}
1436
1437function sortColumnFromTableHeader(headerText)
1438{
1439 return headerText.split(' ', 1)[0];
1440}
1441
1442function htmlForTableColumnHeader(headerName, opt_fillColSpan)
1443{
1444 // Use the first word of the header title as the sortkey
1445 var thisSortValue = sortColumnFromTableHeader(headerName);
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001446 var arrowHTML = thisSortValue == g_history.dashboardSpecificState.sortColumn ?
1447 '<span class=' + g_history.dashboardSpecificState.sortOrder + '>' + (g_history.dashboardSpecificState.sortOrder == FORWARD ? '&uarr;' : '&darr;' ) + '</span>' : '';
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001448 return '<th sortValue=' + thisSortValue +
1449 // Extend last th through all the rest of the columns.
1450 (opt_fillColSpan ? ' colspan=10000' : '') +
1451 // Extra span here is so flex boxing actually centers.
1452 // There's probably a better way to do this with CSS only though.
1453 '><div class=table-header-content><span></span>' + arrowHTML +
1454 '<span class=header-text>' + headerName + '</span>' + arrowHTML + '</div></th>';
1455}
1456
1457function htmlForTestTable(rowsHTML, opt_excludeHeaders)
1458{
1459 var html = '<table class=test-table>';
1460 if (!opt_excludeHeaders) {
1461 html += '<thead><tr>';
1462 var headers = tableHeaders();
1463 for (var i = 0; i < headers.length; i++)
1464 html += htmlForTableColumnHeader(headers[i], i == headers.length - 1);
1465 html += '</tr></thead>';
1466 }
1467 return html + '<tbody>' + rowsHTML + '</tbody></table>';
1468}
1469
1470function appendHTML(html)
1471{
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001472 // InnerHTML to a div that's not in the document. This is
1473 // ~300ms faster in Safari 4 and Chrome 4 on mac.
1474 var div = document.createElement('div');
1475 div.innerHTML = html;
1476 document.body.appendChild(div);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001477 postHeightChangedMessage();
1478}
1479
1480function alphanumericCompare(column, reverse)
1481{
1482 return reversibleCompareFunction(function(a, b) {
1483 // Put null entries at the bottom
1484 var a = a[column] ? String(a[column]) : 'z';
1485 var b = b[column] ? String(b[column]) : 'z';
1486
1487 if (a < b)
1488 return -1;
1489 else if (a == b)
1490 return 0;
1491 else
1492 return 1;
1493 }, reverse);
1494}
1495
1496function numericSort(column, reverse)
1497{
1498 return reversibleCompareFunction(function(a, b) {
1499 a = parseFloat(a[column]);
1500 b = parseFloat(b[column]);
1501 return a - b;
1502 }, reverse);
1503}
1504
1505function reversibleCompareFunction(compare, reverse)
1506{
1507 return function(a, b) {
1508 return compare(reverse ? b : a, reverse ? a : b);
1509 };
1510}
1511
1512function changeSort(e)
1513{
1514 var target = e.currentTarget;
1515 e.preventDefault();
1516
1517 var sortValue = target.getAttribute('sortValue');
1518 while (target && target.tagName != 'TABLE')
1519 target = target.parentNode;
1520
1521 var sort = 'sortColumn';
1522 var orderKey = 'sortOrder';
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001523 if (sortValue == g_history.dashboardSpecificState[sort] && g_history.dashboardSpecificState[orderKey] == FORWARD)
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001524 order = BACKWARD;
1525 else
1526 order = FORWARD;
1527
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001528 g_history.setQueryParameter(sort, sortValue, orderKey, order);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001529}
1530
1531function sortTests(tests, column, order)
1532{
1533 var resultsProperty, sortFunctionGetter;
1534 if (column == 'flakiness') {
1535 sortFunctionGetter = numericSort;
1536 resultsProperty = 'flips';
1537 } else if (column == 'slowest') {
1538 sortFunctionGetter = numericSort;
1539 resultsProperty = 'slowestTime';
1540 } else {
1541 sortFunctionGetter = alphanumericCompare;
1542 resultsProperty = column;
1543 }
1544
1545 tests.sort(sortFunctionGetter(resultsProperty, order == BACKWARD));
1546}
1547
1548// Sorts a space separated expectations string in alphanumeric order.
1549// @param {string} str The expectations string.
1550// @return {string} The sorted string.
1551function sortExpectationsString(str)
1552{
1553 return str.split(' ').sort().join(' ');
1554}
1555
1556function addUpdate(testsNeedingUpdate, test, builderName, missing, extra)
1557{
1558 if (!testsNeedingUpdate[test])
1559 testsNeedingUpdate[test] = {};
1560
1561 var buildInfo = platformAndBuildType(builderName);
1562 var builder = buildInfo.platform + ' ' + buildInfo.buildType;
1563 if (!testsNeedingUpdate[test][builder])
1564 testsNeedingUpdate[test][builder] = {};
1565
1566 if (missing)
1567 testsNeedingUpdate[test][builder].missing = sortExpectationsString(missing);
1568
1569 if (extra)
1570 testsNeedingUpdate[test][builder].extra = sortExpectationsString(extra);
1571}
1572
1573
1574// From a string of modifiers, returns a string of modifiers that
1575// are for real result changes, like SLOW, and excludes modifiers
1576// that specificy things like platform, build_type, bug.
1577// @param {string} modifierString String containing all modifiers.
1578// @return {string} String containing only modifiers that effect the results.
1579function realModifiers(modifierString)
1580{
1581 var modifiers = modifierString.split(' ');;
1582 return modifiers.filter(function(modifier) {
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001583 if (modifier in BUILD_TYPES || string.startsWith(modifier, 'BUG'))
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001584 return false;
1585
1586 var matchesPlatformOrUnion = false;
1587 traversePlatformsTree(function(platform, platformName) {
1588 if (matchesPlatformOrUnion)
1589 return;
1590
1591 if (platform.fallbackPlatforms) {
1592 platform.fallbackPlatforms.forEach(function(fallbackPlatform) {
1593 if (matchesPlatformOrUnion)
1594 return;
1595
1596 var fallbackPlatformObject = platformObjectForName(fallbackPlatform);
1597 if (!fallbackPlatformObject.platformModifierUnions)
1598 return;
1599
1600 matchesPlatformOrUnion = modifier in fallbackPlatformObject.subPlatforms || modifier in fallbackPlatformObject.platformModifierUnions;
1601 });
1602 }
1603 });
1604
1605 return !matchesPlatformOrUnion;
1606 }).join(' ');
1607}
1608
1609function generatePageForExpectationsUpdate()
1610{
1611 // Always show all runs when auto-updating expectations.
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001612 if (!g_history.crossDashboardState.showAllRuns)
1613 g_history.setQueryParameter('showAllRuns', true);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001614
1615 processTestRunsForAllBuilders();
1616 var testsNeedingUpdate = {};
1617 for (var test in g_testToResultsMap) {
1618 var results = g_testToResultsMap[test];
1619 for (var i = 0; i < results.length; i++) {
1620 var thisResult = results[i];
1621
1622 if (!thisResult.missing && !thisResult.extra)
1623 continue;
1624
1625 var allPassesOrNoDatas = thisResult.rawResults.filter(function (x) { return x[1] != "P" && x[1] != "N"; }).length == 0;
1626
1627 if (allPassesOrNoDatas)
1628 continue;
1629
1630 addUpdate(testsNeedingUpdate, test, thisResult.builder, thisResult.missing, thisResult.extra);
1631 }
1632 }
1633
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001634 for (var builder in currentBuilders()) {
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001635 var tests = g_perBuilderWithExpectationsButNoFailures[builder]
1636 for (var i = 0; i < tests.length; i++) {
1637 // Anything extra in this case is what is listed in expectations
1638 // plus modifiers other than bug, platform, build type.
1639 var modifiers = realModifiers(tests[i].modifiers);
1640 var extras = tests[i].expectations;
1641 extras += modifiers ? ' ' + modifiers : '';
1642 addUpdate(testsNeedingUpdate, tests[i].test, builder, null, extras);
1643 }
1644 }
1645
1646 // Get the keys in alphabetical order, so it is easy to process groups
1647 // of tests.
1648 var keys = Object.keys(testsNeedingUpdate).sort();
1649 showUpdateInfoForTest(testsNeedingUpdate, keys);
1650}
1651
1652// Show the test results and the json for differing expectations, and
1653// allow the user to include or exclude this update.
1654//
1655// @param {Object} testsNeedingUpdate Tests that need updating.
1656// @param {Array.<string>} keys Keys into the testNeedingUpdate object.
1657function showUpdateInfoForTest(testsNeedingUpdate, keys)
1658{
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001659 var test = keys[g_history.dashboardSpecificState.updateIndex];
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001660 document.body.innerHTML = '';
1661
1662 // FIXME: Make this DOM creation less verbose.
1663 var index = document.createElement('div');
1664 index.style.cssFloat = 'right';
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001665 index.textContent = (g_history.dashboardSpecificState.updateIndex + 1) + ' of ' + keys.length + ' tests';
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001666 document.body.appendChild(index);
1667
1668 var buttonRegion = document.createElement('div');
1669 var includeBtn = document.createElement('input');
1670 includeBtn.type = 'button';
1671 includeBtn.value = 'include selected';
1672 includeBtn.addEventListener('click', partial(handleUpdate, testsNeedingUpdate, keys), false);
1673 buttonRegion.appendChild(includeBtn);
1674
1675 var previousBtn = document.createElement('input');
1676 previousBtn.type = 'button';
1677 previousBtn.value = 'previous';
1678 previousBtn.addEventListener('click',
1679 function() {
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001680 setUpdateIndex(g_history.dashboardSpecificState.updateIndex - 1, testsNeedingUpdate, keys);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001681 },
1682 false);
1683 buttonRegion.appendChild(previousBtn);
1684
1685 var nextBtn = document.createElement('input');
1686 nextBtn.type = 'button';
1687 nextBtn.value = 'next';
1688 nextBtn.addEventListener('click', partial(nextUpdate, testsNeedingUpdate, keys), false);
1689 buttonRegion.appendChild(nextBtn);
1690
1691 var doneBtn = document.createElement('input');
1692 doneBtn.type = 'button';
1693 doneBtn.value = 'done';
1694 doneBtn.addEventListener('click', finishUpdate, false);
1695 buttonRegion.appendChild(doneBtn);
1696
1697 document.body.appendChild(buttonRegion);
1698
1699 var updates = testsNeedingUpdate[test];
1700 var checkboxes = document.createElement('div');
1701 for (var builder in updates) {
1702 // Create a checkbox for each builder.
1703 var checkboxRegion = document.createElement('div');
1704 var checkbox = document.createElement('input');
1705 checkbox.type = 'checkbox';
1706 checkbox.id = builder;
1707 checkbox.checked = true;
1708 checkboxRegion.appendChild(checkbox);
1709 checkboxRegion.appendChild(document.createTextNode(builder + ' : ' + JSON.stringify(updates[builder])));
1710 checkboxes.appendChild(checkboxRegion);
1711 }
1712 document.body.appendChild(checkboxes);
1713
1714 var div = document.createElement('div');
1715 div.innerHTML = htmlForIndividualTestOnAllBuildersWithResultsLinks(test);
1716 document.body.appendChild(div);
1717 appendExpectations();
1718}
1719
1720
1721// When the user has finished selecting expectations to update, provide them
1722// with json to copy over.
1723function finishUpdate()
1724{
1725 document.body.innerHTML = 'The next step is to copy the output below ' +
1726 'into a local file and save it. Then, run<br><code>python ' +
1727 'src/webkit/tools/layout_tests/webkitpy/layout_tests/update_expectat' +
1728 'ions_from_dashboard.py path/to/local/file</code><br>in order to ' +
1729 'update the expectations file.<br><textarea id="results" '+
1730 'style="width:600px;height:600px;"> ' +
1731 JSON.stringify(g_confirmedTests) + '</textarea>';
1732 results.focus();
1733 document.execCommand('SelectAll');
1734}
1735
1736// Handle user click on "include selected" button.
1737// Includes the tests that are selected and exclude the rest.
1738// @param {Object} testsNeedingUpdate Tests that need updating.
1739// @param {Array.<string>} keys Keys into the testNeedingUpdate object.
1740function handleUpdate(testsNeedingUpdate, keys)
1741{
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001742 var test = keys[g_history.dashboardSpecificState.updateIndex];
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001743 var updates = testsNeedingUpdate[test];
1744 for (var builder in updates) {
1745 // Add included tests, and delete excluded tests if
1746 // they were previously included.
1747 if ($(builder).checked) {
1748 if (!g_confirmedTests[test])
1749 g_confirmedTests[test] = {};
1750 g_confirmedTests[test][builder] = testsNeedingUpdate[test][builder];
1751 } else if (g_confirmedTests[test] && g_confirmedTests[test][builder]) {
1752 delete g_confirmedTests[test][builder];
1753 if (!Object.keys(g_confirmedTests[test]).length)
1754 delete g_confirmedTests[test];
1755 }
1756 }
1757 nextUpdate(testsNeedingUpdate, keys);
1758}
1759
1760
1761// Move to the next item to update.
1762// @param {Object} testsNeedingUpdate Tests that need updating.
1763// @param {Array.<string>} keys Keys into the testNeedingUpdate object.
1764function nextUpdate(testsNeedingUpdate, keys)
1765{
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001766 setUpdateIndex(g_history.dashboardSpecificState.updateIndex + 1, testsNeedingUpdate, keys);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001767}
1768
1769
1770// Advance the index we are updating at. If we walk over the end
1771// or beginning, just loop.
1772// @param {string} newIndex The index into the keys to move to.
1773// @param {Object} testsNeedingUpdate Tests that need updating.
1774// @param {Array.<string>} keys Keys into the testNeedingUpdate object.
1775function setUpdateIndex(newIndex, testsNeedingUpdate, keys)
1776{
1777 if (newIndex == -1)
1778 newIndex = keys.length - 1;
1779 else if (newIndex == keys.length)
1780 newIndex = 0;
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001781 g_history.setQueryParameter("updateIndex", newIndex);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001782 showUpdateInfoForTest(testsNeedingUpdate, keys);
1783}
1784
1785function htmlForIndividualTestOnAllBuilders(test)
1786{
1787 processTestRunsForAllBuilders();
1788
1789 var testResults = g_testToResultsMap[test];
1790 if (!testResults)
1791 return '<div class="not-found">Test not found. Either it does not exist, is skipped or passes on all platforms.</div>';
1792
1793 var html = '';
1794 var shownBuilders = [];
1795 for (var j = 0; j < testResults.length; j++) {
1796 shownBuilders.push(testResults[j].builder);
1797 html += htmlForSingleTestRow(testResults[j]);
1798 }
1799
1800 var skippedBuilders = []
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001801 for (builder in currentBuilders()) {
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001802 if (shownBuilders.indexOf(builder) == -1)
1803 skippedBuilders.push(builder);
1804 }
1805
1806 var skippedBuildersHtml = '';
1807 if (skippedBuilders.length) {
1808 skippedBuildersHtml = '<div>The following builders either don\'t run this test (e.g. it\'s skipped) or all runs passed:</div>' +
1809 '<div class=skipped-builder-list><div class=skipped-builder>' + skippedBuilders.join('</div><div class=skipped-builder>') + '</div></div>';
1810 }
1811
1812 return htmlForTestTable(html) + skippedBuildersHtml;
1813}
1814
1815function htmlForIndividualTestOnAllBuildersWithResultsLinks(test)
1816{
1817 processTestRunsForAllBuilders();
1818
1819 var testResults = g_testToResultsMap[test];
1820 var html = '';
1821 html += htmlForIndividualTestOnAllBuilders(test);
1822
1823 html += '<div class=expectations test=' + test + '><div>' +
1824 linkHTMLToToggleState('showExpectations', 'results')
1825
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001826 if (g_history.isLayoutTestResults() || g_history.isGPUTestResults()) {
1827 if (g_history.isLayoutTestResults())
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001828 html += ' | ' + linkHTMLToToggleState('showLargeExpectations', 'large thumbnails');
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001829 if (testResults && currentBuilderGroup().master().name == WEBKIT_BUILDER_MASTER) {
1830 var revision = g_history.dashboardSpecificState.revision || '';
1831 html += '<form onsubmit="g_history.setQueryParameter(\'revision\', revision.value);' +
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001832 'return false;">Show results for WebKit revision: ' +
1833 '<input name=revision placeholder="e.g. 65540" value="' + revision +
1834 '" id=revision-input></form>';
1835 } else
1836 html += ' | <b>Only shows actual results/diffs from the most recent *failure* on each bot.</b>';
1837 } else {
1838 html += ' | <span>Results height:<input ' +
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001839 'onchange="g_history.setQueryParameter(\'resultsHeight\',this.value)" value="' +
1840 g_history.dashboardSpecificState.resultsHeight + '" style="width:2.5em">px</span>';
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001841 }
1842 html += '</div></div>';
1843 return html;
1844}
1845
1846function getExpectationsContainer(expectationsContainers, parentContainer, expectationsType)
1847{
1848 if (!expectationsContainers[expectationsType]) {
1849 var container = document.createElement('div');
1850 container.className = 'expectations-container';
1851 parentContainer.appendChild(container);
1852 expectationsContainers[expectationsType] = container;
1853 }
1854 return expectationsContainers[expectationsType];
1855}
1856
1857function ensureTrailingSlash(path)
1858{
1859 if (path.match(/\/$/))
1860 return path;
1861 return path + '/';
1862}
1863
1864function maybeAddPngChecksum(expectationDiv, pngUrl)
1865{
1866 // pngUrl gets served from the browser cache since we just loaded it in an
1867 // <img> tag.
1868 loader.request(pngUrl,
1869 function(xhr) {
1870 // Convert the first 2k of the response to a byte string.
1871 var bytes = xhr.responseText.substring(0, 2048);
1872 for (var position = 0; position < bytes.length; ++position)
1873 bytes[position] = bytes[position] & 0xff;
1874
1875 // Look for the comment.
1876 var commentKey = 'tEXtchecksum\x00';
1877 var checksumPosition = bytes.indexOf(commentKey);
1878 if (checksumPosition == -1)
1879 return;
1880
1881 var checksum = bytes.substring(checksumPosition + commentKey.length, checksumPosition + commentKey.length + 32);
1882 var checksumContainer = document.createElement('span');
1883 checksumContainer.innerText = 'Embedded checksum: ' + checksum;
1884 checksumContainer.setAttribute('class', 'pngchecksum');
1885 expectationDiv.parentNode.appendChild(checksumContainer);
1886 },
1887 function(xhr) {},
1888 true);
1889}
1890
1891// Adds a specific expectation. If it's an image, it's only added on the
1892// image's onload handler. If it's a text file, then a script tag is appended
1893// as a hack to see if the file 404s (necessary since it's cross-domain).
1894// Once all the expectations for a specific type have loaded or errored
1895// (e.g. all the text results), then we go through and identify which platform
1896// uses which expectation.
1897//
1898// @param {Object} expectationsContainers Map from expectations type to
1899// container DIV.
1900// @param {Element} parentContainer Container element for
1901// expectationsContainer divs.
1902// @param {string} platform Platform string. Empty string for non-platform
1903// specific expectations.
1904// @param {string} path Relative path to the expectation.
1905// @param {string} base Base path for the expectation URL.
1906// @param {string} opt_builder Builder whose actual results this expectation
1907// points to.
1908// @param {string} opt_suite "virtual suite" that the test belongs to, if any.
1909function addExpectationItem(expectationsContainers, parentContainer, platform, path, base, opt_builder, opt_suite)
1910{
1911 var parts = path.split('.')
1912 var fileExtension = parts[parts.length - 1];
1913 if (fileExtension == 'html')
1914 fileExtension = 'txt';
1915
1916 var container = getExpectationsContainer(expectationsContainers, parentContainer, fileExtension);
1917 var isImage = path.match(/\.png$/);
1918
1919 // FIXME: Stop using script tags once all the places we pull from support CORS.
1920 var platformPart = platform ? ensureTrailingSlash(platform) : '';
1921 var suitePart = opt_suite ? ensureTrailingSlash(opt_suite) : '';
1922
1923 var childContainer = document.createElement('span');
1924 childContainer.className = 'unloaded';
1925
1926 var appendExpectationsItem = function(item) {
1927 childContainer.appendChild(expectationsTitle(platformPart + suitePart, path, opt_builder));
1928 childContainer.className = 'expectations-item';
1929 item.className = 'expectation ' + fileExtension;
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001930 if (g_history.dashboardSpecificState.showLargeExpectations)
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001931 item.className += ' large';
1932 childContainer.appendChild(item);
1933 handleFinishedLoadingExpectations(container);
1934 };
1935
1936 var url = base + platformPart + path;
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001937 if (isImage || !string.startsWith(base, 'http://svn.webkit.org')) {
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001938 var dummyNode = document.createElement(isImage ? 'img' : 'script');
1939 dummyNode.src = url;
1940 dummyNode.onload = function() {
1941 var item;
1942 if (isImage) {
1943 item = dummyNode;
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00001944 if (string.startsWith(base, 'http://svn.webkit.org'))
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00001945 maybeAddPngChecksum(item, url);
1946 } else {
1947 item = document.createElement('iframe');
1948 item.src = url;
1949 }
1950 appendExpectationsItem(item);
1951 }
1952 dummyNode.onerror = function() {
1953 childContainer.parentNode.removeChild(childContainer);
1954 handleFinishedLoadingExpectations(container);
1955 }
1956
1957 // Append script elements now so that they load. Images load without being
1958 // appended to the DOM.
1959 if (!isImage)
1960 childContainer.appendChild(dummyNode);
1961 } else {
1962 loader.request(url,
1963 function(xhr) {
1964 var item = document.createElement('pre');
1965 item.innerText = xhr.responseText;
1966 appendExpectationsItem(item);
1967 },
1968 function(xhr) {/* Do nothing on errors since they're expected */});
1969 }
1970
1971 container.appendChild(childContainer);
1972}
1973
1974
1975// Identifies which expectations are used on which platform once all the
1976// expectations of a given type have loaded (e.g. the container for png
1977// expectations for this test had no child elements with the class
1978// "unloaded").
1979//
1980// @param {string} container Element containing the expectations for a given
1981// test and a given type (e.g. png).
1982function handleFinishedLoadingExpectations(container)
1983{
1984 if (container.getElementsByClassName('unloaded').length)
1985 return;
1986
1987 var titles = container.getElementsByClassName('expectations-title');
1988 for (var platform in g_fallbacksMap) {
1989 var fallbacks = g_fallbacksMap[platform];
1990 var winner = null;
1991 var winningIndex = -1;
1992 for (var i = 0; i < titles.length; i++) {
1993 var title = titles[i];
1994
1995 if (!winner && title.platform == "") {
1996 winner = title;
1997 continue;
1998 }
1999
2000 var rawPlatform = title.platform && title.platform.replace('platform/', '');
2001 for (var j = 0; j < fallbacks.length; j++) {
2002 if ((winningIndex == -1 || winningIndex > j) && rawPlatform == fallbacks[j]) {
2003 winningIndex = j;
2004 winner = title;
2005 break;
2006 }
2007 }
2008 }
2009 if (winner)
2010 winner.getElementsByClassName('platforms')[0].innerHTML += '<div class=used-platform>' + platform + '</div>';
2011 else {
2012 console.log('No expectations identified for this test. This means ' +
2013 'there is a logic bug in the dashboard for which expectations a ' +
2014 'platform uses or trac.webkit.org/src.chromium.org is giving 5XXs.');
2015 }
2016 }
2017
2018 consolidateUsedPlatforms(container);
2019}
2020
2021// Consolidate platforms when all sub-platforms for a given platform are represented.
2022// e.g., if all of the WIN- platforms are there, replace them with just WIN.
2023function consolidateUsedPlatforms(container)
2024{
2025 var allPlatforms = Object.keys(g_fallbacksMap);
2026
2027 var platformElements = container.getElementsByClassName('platforms');
2028 for (var i = 0, platformsLength = platformElements.length; i < platformsLength; i++) {
2029 var usedPlatforms = platformElements[i].getElementsByClassName('used-platform');
2030 if (!usedPlatforms.length)
2031 continue;
2032
2033 var platforms = {};
2034 platforms['MAC'] = {};
2035 platforms['WIN'] = {};
2036 platforms['LINUX'] = {};
2037 allPlatforms.forEach(function(platform) {
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00002038 if (string.startsWith(platform, 'MAC'))
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00002039 platforms['MAC'][platform] = 1;
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00002040 else if (string.startsWith(platform, 'WIN'))
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00002041 platforms['WIN'][platform] = 1;
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00002042 else if (string.startsWith(platform, 'LINUX'))
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00002043 platforms['LINUX'][platform] = 1;
2044 });
2045
2046 for (var j = 0, usedPlatformsLength = usedPlatforms.length; j < usedPlatformsLength; j++) {
2047 for (var platform in platforms)
2048 delete platforms[platform][usedPlatforms[j].textContent];
2049 }
2050
2051 for (var platform in platforms) {
2052 if (!Object.keys(platforms[platform]).length) {
2053 var nodesToRemove = [];
2054 for (var j = 0, usedPlatformsLength = usedPlatforms.length; j < usedPlatformsLength; j++) {
2055 var usedPlatform = usedPlatforms[j];
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00002056 if (string.startsWith(usedPlatform.textContent, platform))
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00002057 nodesToRemove.push(usedPlatform);
2058 }
2059
2060 nodesToRemove.forEach(function(element) { element.parentNode.removeChild(element); });
2061 platformElements[i].insertAdjacentHTML('afterBegin', '<div class=used-platform>' + platform + '</div>');
2062 }
2063 }
2064 }
2065}
2066
2067function addExpectations(expectationsContainers, container, base,
2068 platform, text, png, reftest_html_file, reftest_mismatch_html_file, suite)
2069{
2070 var builder = '';
2071 addExpectationItem(expectationsContainers, container, platform, text, base, builder, suite);
2072 addExpectationItem(expectationsContainers, container, platform, png, base, builder, suite);
2073 addExpectationItem(expectationsContainers, container, platform, reftest_html_file, base, builder, suite);
2074 addExpectationItem(expectationsContainers, container, platform, reftest_mismatch_html_file, base, builder, suite);
2075}
2076
2077function expectationsTitle(platform, path, builder)
2078{
2079 var header = document.createElement('h3');
2080 header.className = 'expectations-title';
2081
2082 var innerHTML;
2083 if (builder) {
2084 var resultsType;
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00002085 if (string.endsWith(path, '-crash-log.txt'))
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00002086 resultsType = 'STACKTRACE';
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00002087 else if (string.endsWith(path, '-actual.txt') || string.endsWith(path, '-actual.png'))
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00002088 resultsType = 'ACTUAL RESULTS';
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00002089 else if (string.endsWith(path, '-wdiff.html'))
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00002090 resultsType = 'WDIFF';
2091 else
2092 resultsType = 'DIFF';
2093
2094 innerHTML = resultsType + ': ' + builder;
2095 } else if (platform === "") {
2096 var parts = path.split('/');
2097 innerHTML = parts[parts.length - 1];
2098 } else
2099 innerHTML = platform || path;
2100
2101 header.innerHTML = '<div class=title>' + innerHTML +
2102 '</div><div style="float:left">&nbsp;</div>' +
2103 '<div class=platforms style="float:right"></div>';
2104 header.platform = platform;
2105 return header;
2106}
2107
2108function loadExpectations(expectationsContainer)
2109{
2110 var test = expectationsContainer.getAttribute('test');
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00002111 if (g_history.isLayoutTestResults())
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00002112 loadExpectationsLayoutTests(test, expectationsContainer);
2113 else {
2114 var results = g_testToResultsMap[test];
2115 for (var i = 0; i < results.length; i++)
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00002116 if (g_history.isGPUTestResults())
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00002117 loadGPUResultsForBuilder(results[i].builder, test, expectationsContainer);
2118 else
2119 loadNonWebKitResultsForBuilder(results[i].builder, test, expectationsContainer);
2120 }
2121}
2122
2123function gpuResultsPath(chromeRevision, builder)
2124{
2125 return chromeRevision + '_' + builder.replace(/[^A-Za-z0-9]+/g, '_');
2126}
2127
2128function loadGPUResultsForBuilder(builder, test, expectationsContainer)
2129{
2130 var container = document.createElement('div');
2131 container.className = 'expectations-container';
2132 container.innerHTML = '<div><b>' + builder + '</b></div>';
2133 expectationsContainer.appendChild(container);
2134
2135 var failureIndex = indexesForFailures(builder, test)[0];
2136
2137 var buildNumber = g_resultsByBuilder[builder].buildNumbers[failureIndex];
2138 var pathToLog = builderMaster(builder).logPath(builder, buildNumber) + pathToFailureLog(test);
2139
2140 var chromeRevision = g_resultsByBuilder[builder].chromeRevision[failureIndex];
2141 var resultsUrl = GPU_RESULTS_BASE_PATH + gpuResultsPath(chromeRevision, builder);
2142 var filename = test.split(/\./)[1] + '.png';
2143
2144 appendNonWebKitResults(container, pathToLog, 'non-webkit-results');
2145 appendNonWebKitResults(container, resultsUrl + '/gen/' + filename, 'gpu-test-results', 'Generated');
2146 appendNonWebKitResults(container, resultsUrl + '/ref/' + filename, 'gpu-test-results', 'Reference');
2147 appendNonWebKitResults(container, resultsUrl + '/diff/' + filename, 'gpu-test-results', 'Diff');
2148}
2149
2150function loadNonWebKitResultsForBuilder(builder, test, expectationsContainer)
2151{
2152 var failureIndexes = indexesForFailures(builder, test);
2153 var container = document.createElement('div');
2154 container.innerHTML = '<div><b>' + builder + '</b></div>';
2155 expectationsContainer.appendChild(container);
2156 for (var i = 0; i < failureIndexes.length; i++) {
2157 // FIXME: This doesn't seem to work anymore. Did the paths change?
2158 // Once that's resolved, see if we need to try each GTEST_MODIFIERS prefix as well.
2159 var buildNumber = g_resultsByBuilder[builder].buildNumbers[failureIndexes[i]];
2160 var pathToLog = builderMaster(builder).logPath(builder, buildNumber) + pathToFailureLog(test);
2161 appendNonWebKitResults(container, pathToLog, 'non-webkit-results');
2162 }
2163}
2164
2165function appendNonWebKitResults(container, url, itemClassName, opt_title)
2166{
2167 // Use a script tag to detect whether the URL 404s.
2168 // Need to use a script tag since the URL is cross-domain.
2169 var dummyNode = document.createElement('script');
2170 dummyNode.src = url;
2171
2172 dummyNode.onload = function() {
2173 var item = document.createElement('iframe');
2174 item.src = dummyNode.src;
2175 item.className = itemClassName;
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00002176 item.style.height = g_history.dashboardSpecificState.resultsHeight + 'px';
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00002177
2178 if (opt_title) {
2179 var childContainer = document.createElement('div');
2180 childContainer.style.display = 'inline-block';
2181 var title = document.createElement('div');
2182 title.textContent = opt_title;
2183 childContainer.appendChild(title);
2184 childContainer.appendChild(item);
2185 container.replaceChild(childContainer, dummyNode);
2186 } else
2187 container.replaceChild(item, dummyNode);
2188 }
2189 dummyNode.onerror = function() {
2190 container.removeChild(dummyNode);
2191 }
2192
2193 container.appendChild(dummyNode);
2194}
2195
2196function buildInfoForRevision(builder, revision)
2197{
2198 var revisions = g_resultsByBuilder[builder].webkitRevision;
2199 var revisionStart = 0, revisionEnd = 0, buildNumber = 0;
2200 for (var i = 0; i < revisions.length; i++) {
2201 if (revision > revisions[i]) {
2202 revisionStart = revisions[i - 1];
2203 revisionEnd = revisions[i];
2204 buildNumber = g_resultsByBuilder[builder].buildNumbers[i - 1];
2205 break;
2206 }
2207 }
2208
2209 if (revisionEnd)
2210 revisionEnd++;
2211 else
2212 revisionEnd = '';
2213
2214 return {revisionStart: revisionStart, revisionEnd: revisionEnd, buildNumber: buildNumber};
2215}
2216
2217function lookupVirtualTestSuite(test) {
2218 for (var suite in VIRTUAL_SUITES) {
2219 if (test.indexOf(suite) != -1)
2220 return suite;
2221 }
2222 return '';
2223}
2224
2225function baseTest(test, suite) {
2226 base = VIRTUAL_SUITES[suite];
2227 return base ? test.replace(suite, base) : test;
2228}
2229
2230function loadBaselinesForTest(expectationsContainers, expectationsContainer, test) {
2231 var testWithoutSuffix = test.substring(0, test.lastIndexOf('.'));
2232 var text = testWithoutSuffix + "-expected.txt";
2233 var png = testWithoutSuffix + "-expected.png";
2234 var reftest_html_file = testWithoutSuffix + "-expected.html";
2235 var reftest_mismatch_html_file = testWithoutSuffix + "-expected-mismatch.html";
2236 var suite = lookupVirtualTestSuite(test);
2237
2238 if (!suite)
2239 addExpectationItem(expectationsContainers, expectationsContainer, null, test, TEST_URL_BASE_PATH);
2240
2241 addExpectations(expectationsContainers, expectationsContainer,
2242 TEST_URL_BASE_PATH, '', text, png, reftest_html_file, reftest_mismatch_html_file, suite);
2243
2244 var fallbacks = allFallbacks();
2245 for (var i = 0; i < fallbacks.length; i++) {
2246 var fallback = 'platform/' + fallbacks[i];
2247 addExpectations(expectationsContainers, expectationsContainer, TEST_URL_BASE_PATH, fallback, text, png,
2248 reftest_html_file, reftest_mismatch_html_file, suite);
2249 }
2250
2251 if (suite)
2252 loadBaselinesForTest(expectationsContainers, expectationsContainer, baseTest(test, suite));
2253}
2254
2255function loadExpectationsLayoutTests(test, expectationsContainer)
2256{
2257 // Map from file extension to container div for expectations of that type.
2258 var expectationsContainers = {};
2259
2260 var revisionContainer = document.createElement('div');
2261 revisionContainer.textContent = "Showing results for: "
2262 expectationsContainer.appendChild(revisionContainer);
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00002263 for (var builder in currentBuilders()) {
2264 if (builderMaster(builder).name == WEBKIT_BUILDER_MASTER) {
2265 var latestRevision = g_history.dashboardSpecificState.revision || g_resultsByBuilder[builder].webkitRevision[0];
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00002266 var buildInfo = buildInfoForRevision(builder, latestRevision);
2267 var revisionInfo = document.createElement('div');
2268 revisionInfo.style.cssText = 'background:lightgray;margin:0 3px;padding:0 2px;display:inline-block;';
2269 revisionInfo.innerHTML = builder + ' r' + buildInfo.revisionEnd +
2270 ':r' + buildInfo.revisionStart + ', build ' + buildInfo.buildNumber;
2271 revisionContainer.appendChild(revisionInfo);
2272 }
2273 }
2274
2275 loadBaselinesForTest(expectationsContainers, expectationsContainer, test);
2276
2277 var testWithoutSuffix = test.substring(0, test.lastIndexOf('.'));
2278 var actualResultSuffixes = ['-actual.txt', '-actual.png', '-crash-log.txt', '-diff.txt', '-wdiff.html', '-diff.png'];
2279
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00002280 for (var builder in currentBuilders()) {
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00002281 var actualResultsBase;
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00002282 if (builderMaster(builder).name == WEBKIT_BUILDER_MASTER) {
2283 var latestRevision = g_history.dashboardSpecificState.revision || g_resultsByBuilder[builder].webkitRevision[0];
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00002284 var buildInfo = buildInfoForRevision(builder, latestRevision);
2285 actualResultsBase = 'http://build.webkit.org/results/' + builder +
2286 '/r' + buildInfo.revisionStart + ' (' + buildInfo.buildNumber + ')/';
2287 } else
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00002288 actualResultsBase = TEST_RESULTS_BASE_PATH + currentBuilders()[builder] + '/results/layout-test-results/';
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00002289
2290 for (var i = 0; i < actualResultSuffixes.length; i++) {
2291 addExpectationItem(expectationsContainers, expectationsContainer, null,
2292 testWithoutSuffix + actualResultSuffixes[i], actualResultsBase, builder);
2293 }
2294 }
2295
2296 // Add a clearing element so floated elements don't bleed out of their
2297 // containing block.
2298 var br = document.createElement('br');
2299 br.style.clear = 'both';
2300 expectationsContainer.appendChild(br);
2301}
2302
2303var g_allFallbacks;
2304
2305// Returns the reverse sorted, deduped list of all platform fallback
2306// directories.
2307function allFallbacks()
2308{
2309 if (!g_allFallbacks) {
2310 var holder = {};
2311 for (var platform in g_fallbacksMap) {
2312 var fallbacks = g_fallbacksMap[platform];
2313 for (var i = 0; i < fallbacks.length; i++)
2314 holder[fallbacks[i]] = 1;
2315 }
2316
2317 g_allFallbacks = [];
2318 for (var fallback in holder)
2319 g_allFallbacks.push(fallback);
2320
2321 g_allFallbacks.sort(function(a, b) {
2322 if (a == b)
2323 return 0;
2324 return a < b;
2325 });
2326 }
2327 return g_allFallbacks;
2328}
2329
2330function appendExpectations()
2331{
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00002332 var expectations = g_history.dashboardSpecificState.showExpectations ? document.getElementsByClassName('expectations') : [];
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00002333 // Loading expectations is *very* slow. Use a large timeout to avoid
2334 // totally hanging the renderer.
2335 performChunkedAction(expectations, function(chunk) {
2336 for (var i = 0, len = chunk.length; i < len; i++)
2337 loadExpectations(chunk[i]);
2338 postHeightChangedMessage();
2339
2340 }, hideLoadingUI, 10000);
2341}
2342
2343function hideLoadingUI()
2344{
2345 var loadingDiv = $('loading-ui');
2346 if (loadingDiv)
2347 loadingDiv.style.display = 'none';
2348 postHeightChangedMessage();
2349}
2350
2351function generatePageForIndividualTests(tests)
2352{
2353 console.log('Number of tests: ' + tests.length);
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00002354 if (g_history.dashboardSpecificState.showChrome)
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00002355 appendHTML(htmlForNavBar());
2356 performChunkedAction(tests, function(chunk) {
2357 appendHTML(htmlForIndividualTests(chunk));
2358 }, appendExpectations, 500);
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00002359 if (g_history.dashboardSpecificState.showChrome)
2360 $('tests-input').value = g_history.dashboardSpecificState.tests;
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00002361}
2362
2363function performChunkedAction(tests, handleChunk, onComplete, timeout, opt_index) {
2364 var index = opt_index || 0;
2365 setTimeout(function() {
2366 var chunk = Array.prototype.slice.call(tests, index * CHUNK_SIZE, (index + 1) * CHUNK_SIZE);
2367 if (chunk.length) {
2368 handleChunk(chunk);
2369 performChunkedAction(tests, handleChunk, onComplete, timeout, ++index);
2370 } else
2371 onComplete();
2372 // No need for a timeout on the first chunked action.
2373 }, index ? timeout : 0);
2374}
2375
2376function htmlForIndividualTests(tests)
2377{
2378 var testsHTML = [];
2379 for (var i = 0; i < tests.length; i++) {
2380 var test = tests[i];
2381 var testNameHtml = '';
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00002382 if (g_history.dashboardSpecificState.showChrome || tests.length > 1) {
2383 if (g_history.isLayoutTestResults()) {
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00002384 var suite = lookupVirtualTestSuite(test);
2385 var base = suite ? baseTest(test, suite) : test;
2386 var tracURL = TEST_URL_BASE_PATH_TRAC + base;
2387 testNameHtml += '<h2>' + linkHTMLToOpenWindow(tracURL, test) + '</h2>';
2388 } else
2389 testNameHtml += '<h2>' + test + '</h2>';
2390 }
2391
2392 testsHTML.push(testNameHtml + htmlForIndividualTestOnAllBuildersWithResultsLinks(test));
2393 }
2394 return testsHTML.join('<hr>');
2395}
2396
2397function htmlForNavBar()
2398{
2399 var extraHTML = '';
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00002400 var html = ui.html.testTypeSwitcher(false, extraHTML, isCrossBuilderView());
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00002401 html += '<div class=forms><form id=result-form ' +
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00002402 'onsubmit="g_history.setQueryParameter(\'result\', result.value);' +
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00002403 'return false;">Show all tests with result: ' +
2404 '<input name=result placeholder="e.g. CRASH" id=result-input>' +
2405 '</form><form id=tests-form ' +
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00002406 'onsubmit="g_history.setQueryParameter(\'tests\', tests.value);' +
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00002407 'return false;"><span>Show tests on all platforms: </span>' +
2408 '<input name=tests ' +
2409 'placeholder="Comma or space-separated list of tests or partial ' +
2410 'paths to show test results across all builders, e.g., ' +
2411 'foo/bar.html,foo/baz,domstorage" id=tests-input></form>' +
2412 '<span class=link onclick="showLegend()">Show legend [type ?]</span></div>';
2413 return html;
2414}
2415
2416function checkBoxToToggleState(key, text)
2417{
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00002418 var stateEnabled = g_history.dashboardSpecificState[key];
2419 return '<label><input type=checkbox ' + (stateEnabled ? 'checked ' : '') + 'onclick="g_history.setQueryParameter(\'' + key + '\', ' + !stateEnabled + ')">' + text + '</label> ';
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00002420}
2421
2422function linkHTMLToToggleState(key, linkText)
2423{
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00002424 var stateEnabled = g_history.dashboardSpecificState[key];
2425 return '<span class=link onclick="g_history.setQueryParameter(\'' + key + '\', ' + !stateEnabled + ')">' + (stateEnabled ? 'Hide' : 'Show') + ' ' + linkText + '</span>';
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00002426}
2427
2428function headerForTestTableHtml()
2429{
2430 return '<h2 style="display:inline-block">Failing tests</h2>' +
2431 checkBoxToToggleState('showWontFixSkip', 'WONTFIX/SKIP') +
2432 checkBoxToToggleState('showCorrectExpectations', 'tests with correct expectations') +
2433 checkBoxToToggleState('showWrongExpectations', 'tests with wrong expectations') +
2434 checkBoxToToggleState('showFlaky', 'flaky') +
2435 checkBoxToToggleState('showSlow', 'slow');
2436}
2437
2438function generatePageForBuilder(builderName)
2439{
2440 processTestRunsForBuilder(builderName);
2441
2442 var results = g_perBuilderFailures[builderName];
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00002443 sortTests(results, g_history.dashboardSpecificState.sortColumn, g_history.dashboardSpecificState.sortOrder);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00002444
2445 var testsHTML = '';
2446 if (results.length) {
2447 var tableRowsHTML = '';
2448 for (var i = 0; i < results.length; i++)
2449 tableRowsHTML += htmlForSingleTestRow(results[i])
2450 testsHTML = htmlForTestTable(tableRowsHTML);
2451 } else {
2452 testsHTML = '<div>No tests found. ';
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00002453 if (g_history.isLayoutTestResults())
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00002454 testsHTML += 'Try showing tests with correct expectations.</div>';
2455 else
2456 testsHTML += 'This means no tests have failed!</div>';
2457 }
2458
2459 var html = htmlForNavBar();
2460
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00002461 if (g_history.isLayoutTestResults())
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00002462 html += htmlForTestsWithExpectationsButNoFailures(builderName) + headerForTestTableHtml();
2463
2464 html += '<br>' + testsHTML;
2465 appendHTML(html);
2466
2467 var ths = document.getElementsByTagName('th');
2468 for (var i = 0; i < ths.length; i++) {
2469 ths[i].addEventListener('click', changeSort, false);
2470 ths[i].className = "sortable";
2471 }
2472
2473 hideLoadingUI();
2474}
2475
2476var VALID_KEYS_FOR_CROSS_BUILDER_VIEW = {
2477 tests: 1,
2478 result: 1,
2479 showChrome: 1,
2480 showExpectations: 1,
2481 showLargeExpectations: 1,
2482 legacyExpectationsSemantics: 1,
2483 resultsHeight: 1,
2484 revision: 1
2485};
2486
2487function isInvalidKeyForCrossBuilderView(key)
2488{
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00002489 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 +00002490}
2491
2492// Sets the page state to regenerate the page.
2493// @param {Object} params New or modified query parameters as key: value.
2494function handleQueryParameterChange(params)
2495{
2496 for (key in params) {
2497 if (key == 'tests') {
2498 // Entering cross-builder view, only keep valid keys for that view.
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00002499 for (var currentKey in g_history.dashboardSpecificState) {
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00002500 if (isInvalidKeyForCrossBuilderView(currentKey)) {
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00002501 delete g_history.dashboardSpecificState[currentKey];
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00002502 }
2503 }
2504 } else if (isInvalidKeyForCrossBuilderView(key)) {
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00002505 delete g_history.dashboardSpecificState.tests;
2506 delete g_history.dashboardSpecificState.result;
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00002507 }
2508 }
2509
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00002510 return true;
2511}
2512
2513function hideLegend()
2514{
2515 var legend = $('legend');
2516 if (legend)
2517 legend.parentNode.removeChild(legend);
2518}
2519
2520var g_fallbacksMap = {};
2521g_fallbacksMap['WIN-XP'] = ['chromium-win-xp', 'chromium-win', 'chromium'];
2522g_fallbacksMap['WIN-7'] = ['chromium-win', 'chromium'];
2523g_fallbacksMap['MAC-SNOWLEOPARD'] = ['chromium-mac-snowleopard', 'chromium-mac', 'chromium'];
2524g_fallbacksMap['MAC-LION'] = ['chromium-mac', 'chromium'];
2525g_fallbacksMap['LINUX-32'] = ['chromium-linux-x86', 'chromium-linux', 'chromium-win', 'chromium'];
2526g_fallbacksMap['LINUX-64'] = ['chromium-linux', 'chromium-win', 'chromium'];
2527
2528function htmlForFallbackHelp(fallbacks)
2529{
2530 return '<ol class=fallback-list><li>' + fallbacks.join('</li><li>') + '</li></ol>';
2531}
2532
2533function showLegend()
2534{
2535 var legend = $('legend');
2536 if (!legend) {
2537 legend = document.createElement('div');
2538 legend.id = 'legend';
2539 document.body.appendChild(legend);
2540 }
2541
2542 var html = '<div id=legend-toggle onclick="hideLegend()">Hide ' +
2543 'legend [type esc]</div><div id=legend-contents>';
2544 for (var expectation in expectationsMap())
2545 html += '<div class=' + expectation + '>' + expectationsMap()[expectation] + '</div>';
2546
2547 html += '<div class=merge>WEBKIT MERGE</div>';
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00002548 if (g_history.isLayoutTestResults()) {
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00002549 html += '</div><br style="clear:both">' +
2550 '</div><h3>Test expectatons fallback order.</h3>';
2551
2552 for (var platform in g_fallbacksMap)
2553 html += '<div class=fallback-header>' + platform + '</div>' + htmlForFallbackHelp(g_fallbacksMap[platform]);
2554
2555 html += '<div>TIMES:</div>' +
2556 htmlForSlowTimes(MIN_SECONDS_FOR_SLOW_TEST) +
2557 '<div>DEBUG TIMES:</div>' +
2558 htmlForSlowTimes(MIN_SECONDS_FOR_SLOW_TEST_DEBUG);
2559 }
2560
2561 legend.innerHTML = html;
2562}
2563
2564function htmlForSlowTimes(minTime)
2565{
2566 return '<ul><li>&lt;1 second == !SLOW</li><li>&gt;1 second && &lt;' +
2567 minTime + ' seconds == SLOW || !SLOW is fine</li><li>&gt;' +
2568 minTime + ' seconds == SLOW</li></ul>';
2569}
2570
2571function postHeightChangedMessage()
2572{
2573 if (window == parent)
2574 return;
2575
2576 var root = document.documentElement;
2577 var height = root.offsetHeight;
2578 if (root.offsetWidth < root.scrollWidth) {
2579 // We have a horizontal scrollbar. Include it in the height.
2580 var dummyNode = document.createElement('div');
2581 dummyNode.style.overflow = 'scroll';
2582 document.body.appendChild(dummyNode);
2583 var scrollbarWidth = dummyNode.offsetHeight - dummyNode.clientHeight;
2584 document.body.removeChild(dummyNode);
2585 height += scrollbarWidth;
2586 }
2587 parent.postMessage({command: 'heightChanged', height: height}, '*')
2588}
2589
2590if (window != parent)
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00002591 window.addEventListener('blur', ui.popup.hide);
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00002592
2593document.addEventListener('keydown', function(e) {
2594 if (e.keyIdentifier == 'U+003F' || e.keyIdentifier == 'U+00BF') {
2595 // WebKit MAC retursn 3F. WebKit WIN returns BF. This is a bug!
2596 // ? key
2597 showLegend();
2598 } else if (e.keyIdentifier == 'U+001B') {
2599 // escape key
2600 hideLegend();
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00002601 ui.popup.hide();
Torne (Richard Coles)5c87bf82012-11-14 11:46:17 +00002602 }
2603}, false);
Torne (Richard Coles)926b0012013-03-28 15:32:48 +00002604
2605window.addEventListener('load', function() {
2606 resourceLoader = new loader.Loader(intializeHistory);
2607 resourceLoader.load();
2608}, false);