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