1 // Copyright (C) 2012 Google Inc. All rights reserved. 2 // Copyright (C) 2012 Zan Dobersek <zandobersek (a] gmail.com> 3 // 4 // Redistribution and use in source and binary forms, with or without 5 // modification, are permitted provided that the following conditions are 6 // met: 7 // 8 // * Redistributions of source code must retain the above copyright 9 // notice, this list of conditions and the following disclaimer. 10 // * Redistributions in binary form must reproduce the above 11 // copyright notice, this list of conditions and the following disclaimer 12 // in the documentation and/or other materials provided with the 13 // distribution. 14 // * Neither the name of Google Inc. nor the names of its 15 // contributors may be used to endorse or promote products derived from 16 // this software without specific prior written permission. 17 // 18 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 30 var loader = loader || {}; 31 32 (function() { 33 34 var TEST_RESULTS_SERVER = 'http://test-results.appspot.com/'; 35 36 function pathToBuilderResultsFile(builderName) { 37 return TEST_RESULTS_SERVER + 'testfile?builder=' + builderName + 38 '&master=' + builders.master(builderName).name + 39 '&testtype=' + g_history.crossDashboardState.testType + '&name='; 40 } 41 42 loader.request = function(url, success, error, opt_isBinaryData) 43 { 44 var xhr = new XMLHttpRequest(); 45 xhr.open('GET', url, true); 46 if (opt_isBinaryData) 47 xhr.overrideMimeType('text/plain; charset=x-user-defined'); 48 xhr.onreadystatechange = function(e) { 49 if (xhr.readyState == 4) { 50 if (xhr.status == 200) 51 success(xhr); 52 else 53 error(xhr); 54 } 55 } 56 xhr.send(); 57 } 58 59 loader.Loader = function() 60 { 61 this._loadingSteps = [ 62 this._loadBuildersList, 63 this._loadResultsFiles, 64 ]; 65 66 this._buildersThatFailedToLoad = []; 67 this._staleBuilders = []; 68 this._errors = new ui.Errors(); 69 // TODO(jparent): Pass in the appropriate history obj per db. 70 this._history = g_history; 71 } 72 73 // TODO(aboxhall): figure out whether this is a performance bottleneck and 74 // change calling code to understand the trie structure instead if necessary. 75 loader.Loader._flattenTrie = function(trie, prefix) 76 { 77 var result = {}; 78 for (var name in trie) { 79 var fullName = prefix ? prefix + "/" + name : name; 80 var data = trie[name]; 81 if ("results" in data) 82 result[fullName] = data; 83 else { 84 var partialResult = loader.Loader._flattenTrie(data, fullName); 85 for (var key in partialResult) { 86 result[key] = partialResult[key]; 87 } 88 } 89 } 90 return result; 91 } 92 93 loader.Loader.prototype = { 94 load: function() 95 { 96 this._loadNext(); 97 }, 98 showErrors: function() 99 { 100 this._errors.show(); 101 }, 102 _loadNext: function() 103 { 104 var loadingStep = this._loadingSteps.shift(); 105 if (!loadingStep) { 106 this._addErrors(); 107 this._history.initialize(); 108 return; 109 } 110 loadingStep.apply(this); 111 }, 112 _loadBuildersList: function() 113 { 114 builders.loadBuildersList(currentBuilderGroupName(), this._history.crossDashboardState.testType); 115 this._loadNext(); 116 }, 117 _loadResultsFiles: function() 118 { 119 var builderNames = Object.keys(currentBuilders()); 120 if (builderNames.length) 121 builderNames.forEach(this._loadResultsFileForBuilder.bind(this)); 122 else 123 this._loadNext(); 124 125 }, 126 _loadResultsFileForBuilder: function(builderName) 127 { 128 var resultsFilename; 129 if (history.isTreeMap()) 130 resultsFilename = 'times_ms.json'; 131 else if (this._history.crossDashboardState.showAllRuns) 132 resultsFilename = 'results.json'; 133 else 134 resultsFilename = 'results-small.json'; 135 136 var resultsFileLocation = pathToBuilderResultsFile(builderName) + resultsFilename; 137 loader.request(resultsFileLocation, 138 partial(function(loader, builderName, xhr) { 139 loader._handleResultsFileLoaded(builderName, xhr.responseText); 140 }, this, builderName), 141 partial(function(loader, builderName, xhr) { 142 loader._handleResultsFileLoadError(builderName); 143 }, this, builderName)); 144 }, 145 _handleResultsFileLoaded: function(builderName, fileData) 146 { 147 if (history.isTreeMap()) 148 this._processTimesJSONData(builderName, fileData); 149 else 150 this._processResultsJSONData(builderName, fileData); 151 152 // We need this work-around for webkit.org/b/50589. 153 if (!g_resultsByBuilder[builderName]) { 154 this._handleResultsFileLoadError(builderName); 155 return; 156 } 157 158 this._handleResourceLoad(); 159 }, 160 _processTimesJSONData: function(builderName, fileData) 161 { 162 // FIXME: We should probably include the builderName in the JSON 163 // rather than relying on only loading one JSON file per page. 164 g_resultsByBuilder[builderName] = JSON.parse(fileData); 165 }, 166 _processResultsJSONData: function(builderName, fileData) 167 { 168 var builds = JSON.parse(fileData); 169 170 if (builderName == 'version' || builderName == 'failure_map') 171 return; 172 173 var ONE_DAY_SECONDS = 60 * 60 * 24; 174 var ONE_WEEK_SECONDS = ONE_DAY_SECONDS * 7; 175 176 // If a test suite stops being run on a given builder, we don't want to show it. 177 // Assume any builder without a run in two weeks for a given test suite isn't 178 // running that suite anymore. 179 // FIXME: Grab which bots run which tests directly from the buildbot JSON instead. 180 var lastRunSeconds = builds[builderName].secondsSinceEpoch[0]; 181 if ((Date.now() / 1000) - lastRunSeconds > ONE_WEEK_SECONDS) 182 return; 183 184 if ((Date.now() / 1000) - lastRunSeconds > ONE_DAY_SECONDS) 185 this._staleBuilders.push(builderName); 186 187 builds[builderName][results.TESTS] = loader.Loader._flattenTrie(builds[builderName][results.TESTS]); 188 g_resultsByBuilder[builderName] = builds[builderName]; 189 }, 190 _handleResultsFileLoadError: function(builderName) 191 { 192 console.error('Failed to load results file for ' + builderName + '.'); 193 194 // FIXME: loader shouldn't depend on state defined in dashboard_base.js. 195 this._buildersThatFailedToLoad.push(builderName); 196 197 // Remove this builder from builders, so we don't try to use the 198 // data that isn't there. 199 delete currentBuilders()[builderName]; 200 201 // Proceed as if the resource had loaded. 202 this._handleResourceLoad(); 203 }, 204 _handleResourceLoad: function() 205 { 206 if (this._haveResultsFilesLoaded()) 207 this._loadNext(); 208 }, 209 _haveResultsFilesLoaded: function() 210 { 211 for (var builderName in currentBuilders()) { 212 if (!g_resultsByBuilder[builderName] && this._buildersThatFailedToLoad.indexOf(builderName) < 0) 213 return false; 214 } 215 return true; 216 }, 217 _addErrors: function() 218 { 219 if (this._buildersThatFailedToLoad.length) 220 this._errors.addError('ERROR: Failed to get data from ' + this._buildersThatFailedToLoad.toString() +'.'); 221 222 if (this._staleBuilders.length) 223 this._errors.addError('ERROR: Data from ' + this._staleBuilders.toString() + ' is more than 1 day stale.'); 224 } 225 } 226 227 })(); 228