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