Home | History | Annotate | Download | only in scripts
      1 /*
      2  * Copyright (C) 2011 Google Inc. All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions
      6  * are met:
      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.
     12  *
     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.
     24  */
     25 
     26 var controllers = controllers || {};
     27 
     28 (function(){
     29 
     30 var kCheckoutUnavailableMessage = 'Failed! Garden-o-matic needs a local server to modify your working copy. Please run "webkit-patch garden-o-matic" start the local server.';
     31 
     32 // FIXME: Where should this function go?
     33 function rebaselineWithStatusUpdates(failureInfoList, resultsByTest)
     34 {
     35     var statusView = new ui.StatusArea('Rebaseline');
     36     var id = statusView.newId();
     37 
     38     var failuresToRebaseline = [];
     39     var testNamesLogged = [];
     40     failureInfoList.forEach(function(failureInfo) {
     41         if (isAnyReftest(failureInfo.testName, resultsByTest)) {
     42             if (testNamesLogged.indexOf(failureInfo.testName) == -1) {
     43                 statusView.addMessage(id, failureInfo.testName + ' is a ref test, skipping');
     44                 testNamesLogged.push(failureInfo.testName);
     45             }
     46         } else {
     47             failuresToRebaseline.push(failureInfo);
     48             if (testNamesLogged.indexOf(failureInfo.testName) == -1) {
     49                 statusView.addMessage(id, 'Rebaselining ' + failureInfo.testName + '...');
     50                 testNamesLogged.push(failureInfo.testName);
     51             }
     52         }
     53     });
     54 
     55     if (failuresToRebaseline.length) {
     56         checkout.rebaseline(failuresToRebaseline, function() {
     57             statusView.addFinalMessage(id, 'Rebaseline done! Please land with "webkit-patch land-cowhand".');
     58         }, function(failureInfo) {
     59             statusView.addMessage(id, failureInfo.testName + ' on ' + ui.displayNameForBuilder(failureInfo.builderName));
     60         }, function() {
     61             statusView.addFinalMessage(id, kCheckoutUnavailableMessage);
     62         }, function(failureInfo) {
     63             statusView.addMessage(id, 'Skipping rebaseline for ' + failureInfo.testName + ' on ' + ui.displayNameForBuilder(failureInfo.builderName) + ' because we only rebaseline from release bots.');
     64         });
     65     } else {
     66         statusView.addFinalMessage(id, 'No non-reftests left to rebaseline!')
     67     }
     68 }
     69 
     70 // FIXME: This is duplicated from ui/results.js :(.
     71 function isAnyReftest(testName, resultsByTest)
     72 {
     73     return Object.keys(resultsByTest[testName]).map(function(builder) {
     74         return resultsByTest[testName][builder];
     75     }).some(function(resultNode) {
     76         return resultNode.reftest_type && resultNode.reftest_type.length;
     77     });
     78 }
     79 
     80 // FIXME: Where should this function go?
     81 function updateExpectationsWithStatusUpdates(failureInfoList)
     82 {
     83     var statusView = new ui.StatusArea('Expectations Update');
     84     var id = statusView.newId();
     85 
     86     var testNames = base.uniquifyArray(failureInfoList.map(function(failureInfo) { return failureInfo.testName; }));
     87     var testName = testNames.length == 1 ? testNames[0] : testNames.length + ' tests';
     88     statusView.addMessage(id, 'Updating expectations of ' + testName + '...');
     89 
     90     checkout.updateExpectations(failureInfoList, function() {
     91         statusView.addFinalMessage(id, 'Expectations update done! Please land with "webkit-patch land-cowhand".');
     92     }, function() {
     93         statusView.addFinalMessage(id, kCheckoutUnavailableMessage);
     94     });
     95 }
     96 
     97 controllers.ResultsDetails = base.extends(Object, {
     98     init: function(view, resultsByTest)
     99     {
    100         this._view = view;
    101         this._resultsByTest = resultsByTest;
    102         this._view.setResultsByTest(resultsByTest);
    103 
    104         this._view.firstResult();
    105 
    106         $(this._view).bind('next', this.onNext.bind(this));
    107         $(this._view).bind('previous', this.onPrevious.bind(this));
    108         $(this._view).bind('rebaseline', this.onRebaseline.bind(this));
    109         $(this._view).bind('expectfailure', this.onUpdateExpectations.bind(this));
    110     },
    111     onNext: function()
    112     {
    113         this._view.nextResult();
    114     },
    115     onPrevious: function()
    116     {
    117         this._view.previousResult();
    118     },
    119     _failureInfoList: function()
    120     {
    121         var testName = this._view.currentTestName();
    122         return Object.keys(this._resultsByTest[testName]).map(function(builderName) {
    123             return results.failureInfoForTestAndBuilder(this._resultsByTest, testName, builderName);
    124         }.bind(this));
    125     },
    126     onRebaseline: function()
    127     {
    128         rebaselineWithStatusUpdates(this._failureInfoList(), this._resultsByTest);
    129         this._view.nextTest();
    130     },
    131     onUpdateExpectations: function()
    132     {
    133         updateExpectationsWithStatusUpdates(this._failureInfoList());
    134     }
    135 });
    136 
    137 controllers.ExpectedFailures = base.extends(Object, {
    138     init: function(model, view, delegate)
    139     {
    140         this._model = model;
    141         this._view = view;
    142         this._delegate = delegate;
    143     },
    144     update: function()
    145     {
    146         var expectedFailures = results.expectedFailuresByTest(this._model.resultsByBuilder);
    147         var failingTestsList = Object.keys(expectedFailures);
    148 
    149         $(this._view).empty();
    150         base.forEachDirectory(failingTestsList, function(label, testsFailingInDirectory) {
    151             var listItem = new ui.failures.ListItem(label, testsFailingInDirectory);
    152             this._view.appendChild(listItem);
    153             $(listItem).bind('examine', function() {
    154                 this.onExamine(testsFailingInDirectory);
    155             }.bind(this));
    156         }.bind(this));
    157     },
    158     onExamine: function(failingTestsList)
    159     {
    160         var resultsView = new ui.results.View({
    161             fetchResultsURLs: results.fetchResultsURLs
    162         });
    163         var failuresByTest = base.filterDictionary(
    164             results.expectedFailuresByTest(this._model.resultsByBuilder),
    165             function(key) {
    166                 return failingTestsList.indexOf(key) != -1;
    167             });
    168         var controller = new controllers.ResultsDetails(resultsView, failuresByTest);
    169         this._delegate.showResults(resultsView);
    170     }
    171 });
    172 
    173 var FailureStreamController = base.extends(Object, {
    174     _resultsFilter: null,
    175     _keyFor: function(failureAnalysis) { throw "Not implemented!"; },
    176     _createFailureView: function(failureAnalysis) { throw "Not implemented!"; },
    177 
    178     init: function(model, view, delegate)
    179     {
    180         this._model = model;
    181         this._view = view;
    182         this._delegate = delegate;
    183         this._testFailures = new base.UpdateTracker();
    184     },
    185     update: function(failureAnalysis)
    186     {
    187         var key = this._keyFor(failureAnalysis);
    188         var failure = this._testFailures.get(key);
    189         if (!failure) {
    190             failure = this._createFailureView(failureAnalysis);
    191             this._view.add(failure);
    192             $(failure).bind('examine', function() {
    193                 this.onExamine(failure);
    194             }.bind(this));
    195             $(failure).bind('rebaseline', function() {
    196                 this.onRebaseline(failure);
    197             }.bind(this));
    198             $(failure).bind('expectfailure', function() {
    199                 this.onUpdateExpectations(failure);
    200             }.bind(this));
    201         }
    202         failure.addFailureAnalysis(failureAnalysis);
    203         this._testFailures.update(key, failure);
    204         return failure;
    205     },
    206     purge: function() {
    207         this._testFailures.purge(function(failure) {
    208             failure.dismiss();
    209         });
    210         this._testFailures.forEach(function(failure) {
    211             failure.purge();
    212         });
    213     },
    214     onExamine: function(failures)
    215     {
    216         var resultsView = new ui.results.View({
    217             fetchResultsURLs: results.fetchResultsURLs
    218         });
    219 
    220         var testNameList = failures.testNameList();
    221         var failuresByTest = base.filterDictionary(
    222             this._resultsFilter(this._model.resultsByBuilder),
    223             function(key) {
    224                 return testNameList.indexOf(key) != -1;
    225             });
    226 
    227         var controller = new controllers.ResultsDetails(resultsView, failuresByTest);
    228         this._delegate.showResults(resultsView);
    229     },
    230     _toFailureInfoList: function(failures)
    231     {
    232         return base.flattenArray(failures.testNameList().map(model.unexpectedFailureInfoForTestName));
    233     },
    234     onRebaseline: function(failures)
    235     {
    236         var testNameList = failures.testNameList();
    237         var failuresByTest = base.filterDictionary(
    238             this._resultsFilter(this._model.resultsByBuilder),
    239             function(key) {
    240                 return testNameList.indexOf(key) != -1;
    241             });
    242 
    243         rebaselineWithStatusUpdates(this._toFailureInfoList(failures), failuresByTest);
    244     },
    245     onUpdateExpectations: function(failures)
    246     {
    247         updateExpectationsWithStatusUpdates(this._toFailureInfoList(failures));
    248     }
    249 });
    250 
    251 controllers.UnexpectedFailures = base.extends(FailureStreamController, {
    252     _resultsFilter: results.unexpectedFailuresByTest,
    253 
    254     _impliedFirstFailingRevision: function(failureAnalysis)
    255     {
    256         return failureAnalysis.newestPassingRevision + 1;
    257     },
    258     _keyFor: function(failureAnalysis)
    259     {
    260         return failureAnalysis.newestPassingRevision + "+" + failureAnalysis.oldestFailingRevision;
    261     },
    262     _createFailureView: function(failureAnalysis)
    263     {
    264         var failure = new ui.notifications.FailingTestsSummary();
    265         model.commitDataListForRevisionRange(this._impliedFirstFailingRevision(failureAnalysis), failureAnalysis.oldestFailingRevision).forEach(function(commitData) {
    266             var suspiciousCommit = failure.addCommitData(commitData);
    267             $(suspiciousCommit).bind('rollout', function() {
    268                 this.onRollout(commitData.revision, failure.testNameList());
    269             }.bind(this));
    270             $(failure).bind('blame', function() {
    271                 this.onBlame(failure, commitData);
    272             }.bind(this));
    273         }, this);
    274 
    275         return failure;
    276     },
    277     update: function(failureAnalysis)
    278     {
    279         var failure = FailureStreamController.prototype.update.call(this, failureAnalysis);
    280         failure.updateBuilderResults(model.buildersInFlightForRevision(this._impliedFirstFailingRevision(failureAnalysis)));
    281     },
    282     length: function()
    283     {
    284         return this._testFailures.length();
    285     },
    286     onBlame: function(failure, commitData)
    287     {
    288         failure.pinToCommitData(commitData);
    289         $('.action', failure).each(function() {
    290             // FIXME: This isn't the right way of finding and disabling this action.
    291             if (this.textContent == 'Blame')
    292                 this.disabled = true;
    293         });
    294     },
    295     onRollout: function(revision, testNameList)
    296     {
    297         checkout.rollout(revision, ui.rolloutReasonForTestNameList(testNameList), $.noop, function() {
    298             // FIXME: We should have a better error UI.
    299             alert(kCheckoutUnavailableMessage);
    300         });
    301     }
    302 });
    303 
    304 controllers.Failures = base.extends(FailureStreamController, {
    305     _resultsFilter: results.expectedFailuresByTest,
    306 
    307     _keyFor: function(failureAnalysis)
    308     {
    309         return base.dirName(failureAnalysis.testName);
    310     },
    311     _createFailureView: function(failureAnalysis)
    312     {
    313         return new ui.notifications.FailingTests();
    314     },
    315 });
    316 
    317 controllers.FailingBuilders = base.extends(Object, {
    318     init: function(view, message)
    319     {
    320         this._view = view;
    321         this._message = message;
    322         this._notification = null;
    323     },
    324     hasFailures: function()
    325     {
    326         return !!this._notification;
    327     },
    328     update: function(failuresList)
    329     {
    330         if (Object.keys(failuresList).length == 0) {
    331             if (this._notification) {
    332                 this._notification.dismiss();
    333                 this._notification = null;
    334             }
    335             return;
    336         }
    337         if (!this._notification) {
    338             this._notification = new ui.notifications.BuildersFailing(this._message);
    339             this._view.add(this._notification);
    340         }
    341         // FIXME: We should provide regression ranges for the failing builders.
    342         // This doesn't seem to happen often enough to worry too much about that, however.
    343         this._notification.setFailingBuilders(failuresList);
    344     }
    345 });
    346 
    347 })();
    348