2 * Copyright (C) 2011 Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
26 var results = results || {};
30 var kResultsName = 'failing_results.json';
33 var TIMEOUT = 'TIMEOUT';
37 var IMAGE_TEXT = 'IMAGE+TEXT';
39 var MISSING = 'MISSING';
41 var kFailingResults = [TEXT, IMAGE_TEXT, AUDIO];
43 var kExpectedImageSuffix = '-expected.png';
44 var kActualImageSuffix = '-actual.png';
45 var kImageDiffSuffix = '-diff.png';
46 var kExpectedAudioSuffix = '-expected.wav';
47 var kActualAudioSuffix = '-actual.wav';
48 var kExpectedTextSuffix = '-expected.txt';
49 var kActualTextSuffix = '-actual.txt';
50 var kDiffTextSuffix = '-diff.txt';
51 var kCrashLogSuffix = '-crash-log.txt';
53 var kPNGExtension = 'png';
54 var kTXTExtension = 'txt';
55 var kWAVExtension = 'wav';
57 var kPreferredSuffixOrder = [
67 // FIXME: Add support for the rest of the result types.
71 results.kActualKind = 'actual';
72 results.kExpectedKind = 'expected';
73 results.kDiffKind = 'diff';
74 results.kUnknownKind = 'unknown';
77 results.kImageType = 'image'
78 results.kAudioType = 'audio'
79 results.kTextType = 'text'
80 // FIXME: There are more types of tests.
82 function possibleSuffixListFor(failureTypeList)
86 function pushImageSuffixes()
88 suffixList.push(kExpectedImageSuffix);
89 suffixList.push(kActualImageSuffix);
90 suffixList.push(kImageDiffSuffix);
93 function pushAudioSuffixes()
95 suffixList.push(kExpectedAudioSuffix);
96 suffixList.push(kActualAudioSuffix);
99 function pushTextSuffixes()
101 suffixList.push(kActualTextSuffix);
102 suffixList.push(kExpectedTextSuffix);
103 suffixList.push(kDiffTextSuffix);
105 // '-pretty-diff.html',
108 $.each(failureTypeList, function(index, failureType) {
109 switch(failureType) {
124 suffixList.push(kCrashLogSuffix);
131 // FIXME: Add support for the rest of the result types.
133 // '-expected-mismatch.html',
134 // ... and possibly more.
139 return base.uniquifyArray(suffixList);
142 results.failureTypeToExtensionList = function(failureType)
144 switch(failureType) {
146 return [kPNGExtension];
148 return [kWAVExtension];
150 return [kTXTExtension];
153 return [kTXTExtension, kPNGExtension];
155 // FIXME: Add support for the rest of the result types.
157 // '-expected-mismatch.html',
158 // ... and possibly more.
163 results.failureTypeList = function(failureBlob)
165 return failureBlob.split(' ');
168 function resultsDirectoryURL(builderName)
170 if (config.useLocalResults)
171 return '/localresult?path=';
172 return config.layoutTestResultsURL + '/' + config.resultsDirectoryNameFromBuilderName(builderName) + '/results/layout-test-results/';
175 function resultsPrefixListingURL(builderName, marker)
177 var url = config.layoutTestResultsURL + '/?prefix=' + config.resultsDirectoryNameFromBuilderName(builderName) + '/&delimiter=/';
179 return url + '&marker=' + marker;
183 function resultsDirectoryURLForBuildNumber(builderName, buildNumber)
185 return config.layoutTestResultsURL + '/' + config.resultsDirectoryNameFromBuilderName(builderName) + '/' + buildNumber + '/' ;
188 function resultsSummaryURL(builderName)
190 return resultsDirectoryURL(builderName) + kResultsName;
193 function resultsSummaryURLForBuildNumber(builderName, buildNumber)
195 return resultsDirectoryURLForBuildNumber(builderName, buildNumber) + kResultsName;
198 var g_resultsCache = new base.AsynchronousCache(function (key, callback) {
199 net.jsonp(key, callback);
202 results.ResultAnalyzer = base.extends(Object, {
203 init: function(resultNode)
205 this._isUnexpected = resultNode.is_unexpected;
206 this._actual = resultNode ? results.failureTypeList(resultNode.actual) : [];
207 this._expected = resultNode ? this._addImpliedExpectations(results.failureTypeList(resultNode.expected)) : [];
209 _addImpliedExpectations: function(resultsList)
211 if (resultsList.indexOf('FAIL') == -1)
213 return resultsList.concat(kFailingResults);
215 _hasPass: function(results)
217 return results.indexOf(PASS) != -1;
219 unexpectedResults: function()
221 return this._actual.filter(function(result) {
222 return this._expected.indexOf(result) == -1;
225 succeeded: function()
227 return this._hasPass(this._actual);
231 return this._actual.length > 1;
235 return this._expected.indexOf('WONTFIX') != -1;
237 hasUnexpectedFailures: function()
239 return this._isUnexpected;
243 function isExpectedFailure(resultNode)
245 var analyzer = new results.ResultAnalyzer(resultNode);
246 return !analyzer.hasUnexpectedFailures() && !analyzer.succeeded() && !analyzer.flaky() && !analyzer.wontfix();
249 function isUnexpectedFailure(resultNode)
251 var analyzer = new results.ResultAnalyzer(resultNode);
252 return analyzer.hasUnexpectedFailures() && !analyzer.succeeded() && !analyzer.flaky() && !analyzer.wontfix();
255 function isResultNode(node)
257 return !!node.actual;
260 results.expectedFailures = function(resultsTree)
262 return base.filterTree(resultsTree.tests, isResultNode, isExpectedFailure);
265 results.unexpectedFailures = function(resultsTree)
267 return base.filterTree(resultsTree.tests, isResultNode, isUnexpectedFailure);
270 function resultsByTest(resultsByBuilder, filter)
272 var resultsByTest = {};
274 $.each(resultsByBuilder, function(builderName, resultsTree) {
275 $.each(filter(resultsTree), function(testName, resultNode) {
276 resultsByTest[testName] = resultsByTest[testName] || {};
277 resultsByTest[testName][builderName] = resultNode;
281 return resultsByTest;
284 results.expectedFailuresByTest = function(resultsByBuilder)
286 return resultsByTest(resultsByBuilder, results.expectedFailures);
289 results.unexpectedFailuresByTest = function(resultsByBuilder)
291 return resultsByTest(resultsByBuilder, results.unexpectedFailures);
294 results.failureInfoForTestAndBuilder = function(resultsByTest, testName, builderName)
296 var failureInfoForTest = {
297 'testName': testName,
298 'builderName': builderName,
299 'failureTypeList': results.failureTypeList(resultsByTest[testName][builderName].actual),
302 return failureInfoForTest;
305 results.collectUnexpectedResults = function(dictionaryOfResultNodes)
307 var collectedResults = [];
308 $.each(dictionaryOfResultNodes, function(key, resultNode) {
309 var analyzer = new results.ResultAnalyzer(resultNode);
310 collectedResults = collectedResults.concat(analyzer.unexpectedResults());
312 return base.uniquifyArray(collectedResults);
315 // Callback data is [{ buildNumber:, url: }]
316 function historicalResultsLocations(builderName, callback)
318 var historicalResultsData = [];
320 function parseListingDocument(prefixListingDocument) {
321 $(prefixListingDocument).find("Prefix").each(function() {
322 var buildString = this.textContent.replace(config.resultsDirectoryNameFromBuilderName(builderName) + '/', '');
323 if (buildString.match(/\d+\//)) {
324 var buildNumber = parseInt(buildString);
326 'buildNumber': buildNumber,
327 'url': resultsSummaryURLForBuildNumber(builderName, buildNumber)
329 historicalResultsData.unshift(resultsData);
332 var nextMarker = $(prefixListingDocument).find('NextMarker').get();
333 if (nextMarker.length) {
334 var nextListingURL = resultsPrefixListingURL(builderName, nextMarker[0].textContent);
335 net.xml(nextListingURL, parseListingDocument);
337 callback(historicalResultsData);
341 builders.mostRecentBuildForBuilder(builderName, function (mostRecentBuildNumber) {
342 var marker = config.resultsDirectoryNameFromBuilderName(builderName) + "/" + (mostRecentBuildNumber - 100) + "/";
343 var listingURL = resultsPrefixListingURL(builderName, marker);
344 net.xml(listingURL, parseListingDocument);
348 function walkHistory(builderName, testName, callback)
350 var indexOfNextKeyToFetch = 0;
353 function continueWalk()
355 if (indexOfNextKeyToFetch >= keyList.length) {
356 processResultNode(0, null);
360 var resultsURL = keyList[indexOfNextKeyToFetch].url;
361 ++indexOfNextKeyToFetch;
362 g_resultsCache.get(resultsURL, function(resultsTree) {
363 if ($.isEmptyObject(resultsTree)) {
367 var resultNode = results.resultNodeForTest(resultsTree, testName);
368 var revision = parseInt(resultsTree['blink_revision'])
371 processResultNode(revision, resultNode);
375 function processResultNode(revision, resultNode)
377 var shouldContinue = callback(revision, resultNode);
383 historicalResultsLocations(builderName, function(resultsLocations) {
384 keyList = resultsLocations;
389 results.regressionRangeForFailure = function(builderName, testName, callback)
391 var oldestFailingRevision = 0;
392 var newestPassingRevision = 0;
394 walkHistory(builderName, testName, function(revision, resultNode) {
396 callback(oldestFailingRevision, newestPassingRevision);
400 newestPassingRevision = revision;
401 callback(oldestFailingRevision, newestPassingRevision);
404 if (isUnexpectedFailure(resultNode)) {
405 oldestFailingRevision = revision;
408 if (!oldestFailingRevision)
409 return true; // We need to keep looking for a failing revision.
410 newestPassingRevision = revision;
411 callback(oldestFailingRevision, newestPassingRevision);
416 function mergeRegressionRanges(regressionRanges)
418 var mergedRange = {};
420 mergedRange.oldestFailingRevision = 0;
421 mergedRange.newestPassingRevision = 0;
423 $.each(regressionRanges, function(builderName, range) {
424 if (!range.oldestFailingRevision && !range.newestPassingRevision)
427 if (!mergedRange.oldestFailingRevision)
428 mergedRange.oldestFailingRevision = range.oldestFailingRevision;
429 if (!mergedRange.newestPassingRevision)
430 mergedRange.newestPassingRevision = range.newestPassingRevision;
432 if (range.oldestFailingRevision && range.oldestFailingRevision < mergedRange.oldestFailingRevision)
433 mergedRange.oldestFailingRevision = range.oldestFailingRevision;
434 if (range.newestPassingRevision > mergedRange.newestPassingRevision)
435 mergedRange.newestPassingRevision = range.newestPassingRevision;
441 results.unifyRegressionRanges = function(builderNameList, testName, callback)
443 var regressionRanges = {};
445 var tracker = new base.RequestTracker(builderNameList.length, function() {
446 var mergedRange = mergeRegressionRanges(regressionRanges);
447 callback(mergedRange.oldestFailingRevision, mergedRange.newestPassingRevision);
450 $.each(builderNameList, function(index, builderName) {
451 results.regressionRangeForFailure(builderName, testName, function(oldestFailingRevision, newestPassingRevision) {
453 range.oldestFailingRevision = oldestFailingRevision;
454 range.newestPassingRevision = newestPassingRevision;
455 regressionRanges[builderName] = range;
456 tracker.requestComplete();
461 results.resultNodeForTest = function(resultsTree, testName)
463 var testNamePath = testName.split('/');
464 var currentNode = resultsTree['tests'];
465 $.each(testNamePath, function(index, segmentName) {
468 currentNode = (segmentName in currentNode) ? currentNode[segmentName] : null;
473 results.resultKind = function(url)
475 if (/-actual\.[a-z]+$/.test(url))
476 return results.kActualKind;
477 else if (/-expected\.[a-z]+$/.test(url))
478 return results.kExpectedKind;
479 else if (/diff\.[a-z]+$/.test(url))
480 return results.kDiffKind;
481 return results.kUnknownKind;
484 results.resultType = function(url)
486 if (/\.png$/.test(url))
487 return results.kImageType;
488 if (/\.wav$/.test(url))
489 return results.kAudioType;
490 return results.kTextType;
493 function sortResultURLsBySuffix(urls)
496 $.each(kPreferredSuffixOrder, function(i, suffix) {
497 $.each(urls, function(j, url) {
498 if (!base.endsWith(url, suffix))
500 sortedURLs.push(url);
503 if (sortedURLs.length != urls.length)
504 throw "sortResultURLsBySuffix failed to return the same number of URLs."
508 results.fetchResultsURLs = function(failureInfo, callback)
510 var testNameStem = base.trimExtension(failureInfo.testName);
511 var urlStem = resultsDirectoryURL(failureInfo.builderName);
513 var suffixList = possibleSuffixListFor(failureInfo.failureTypeList);
515 var tracker = new base.RequestTracker(suffixList.length, function() {
516 callback(sortResultURLsBySuffix(resultURLs));
518 $.each(suffixList, function(index, suffix) {
519 var url = urlStem + testNameStem + suffix;
521 success: function() {
522 resultURLs.push(url);
523 tracker.requestComplete();
526 tracker.requestComplete();
532 results.fetchResultsByBuilder = function(builderNameList, callback)
534 var resultsByBuilder = {};
535 var tracker = new base.RequestTracker(builderNameList.length, function() {
536 callback(resultsByBuilder);
538 $.each(builderNameList, function(index, builderName) {
539 var resultsURL = resultsSummaryURL(builderName);
540 net.jsonp(resultsURL, function(resultsTree) {
541 resultsByBuilder[builderName] = resultsTree;
542 tracker.requestComplete();