Cleanup: Move flatten-trie to loader.
[WebKit-https.git] / Tools / TestResultServer / static-dashboards / loader.js
1 // Copyright (C) 2012 Google Inc. All rights reserved.
2 // Copyright (C) 2012 Zan Dobersek <zandobersek@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 var CHROMIUM_EXPECTATIONS_URL = 'http://svn.webkit.org/repository/webkit/trunk/LayoutTests/platform/chromium/TestExpectations';
36
37 function pathToBuilderResultsFile(builderName) {
38     return TEST_RESULTS_SERVER + 'testfile?builder=' + builderName +
39            '&master=' + builderMaster(builderName).name +
40            '&testtype=' + g_crossDashboardState.testType + '&name=';
41 }
42
43 loader.request = function(url, success, error, opt_isBinaryData)
44 {
45     var xhr = new XMLHttpRequest();
46     xhr.open('GET', url, true);
47     if (opt_isBinaryData)
48         xhr.overrideMimeType('text/plain; charset=x-user-defined');
49     xhr.onreadystatechange = function(e) {
50         if (xhr.readyState == 4) {
51             if (xhr.status == 200)
52                 success(xhr);
53             else
54                 error(xhr);
55         }
56     }
57     xhr.send();
58 }
59
60 loader.Loader = function(opt_onLoadingComplete)
61 {
62     this._loadingSteps = [
63         this._loadBuildersList,
64         this._loadResultsFiles,
65         this._loadExpectationsFiles,
66     ];
67
68     this._buildersThatFailedToLoad = [];
69     this._staleBuilders = [];
70     this._loadingComplete = false;
71     this._errors = new ui.Errors();
72     this._onLoadingComplete = opt_onLoadingComplete || function() {};
73 }
74
75 // TODO(aboxhall): figure out whether this is a performance bottleneck and
76 // change calling code to understand the trie structure instead if necessary.
77 loader.Loader._flattenTrie = function(trie, prefix)
78 {
79     var result = {};
80     for (var name in trie) {
81         var fullName = prefix ? prefix + "/" + name : name;
82         var data = trie[name];
83         if ("results" in data)
84             result[fullName] = data;
85         else {
86             var partialResult = loader.Loader._flattenTrie(data, fullName);
87             for (var key in partialResult) {
88                 result[key] = partialResult[key];
89             }
90         }
91     }
92     return result;
93 }
94
95 loader.Loader.prototype = {
96     load: function()
97     {
98         this._loadNext();
99     },
100     isLoadingComplete: function()
101     {
102         return this._loadingComplete;
103     },
104     showErrors: function() 
105     {
106         this._errors.show();
107     },
108     _loadNext: function()
109     {
110         var loadingStep = this._loadingSteps.shift();
111         if (!loadingStep) {
112             this._loadingComplete = true;
113             this._addErrors();
114             this._onLoadingComplete();
115             return;
116         }
117         loadingStep.apply(this);
118     },
119     _loadBuildersList: function()
120     {
121         loadBuildersList(currentBuilderGroupName(), g_crossDashboardState.testType);
122         this._loadNext();
123     },
124     _loadResultsFiles: function()
125     {
126         parseParameters();
127
128         for (var builderName in currentBuilders())
129             this._loadResultsFileForBuilder(builderName);
130     },
131     _loadResultsFileForBuilder: function(builderName)
132     {
133         var resultsFilename;
134         if (isTreeMap())
135             resultsFilename = 'times_ms.json';
136         else if (g_crossDashboardState.showAllRuns)
137             resultsFilename = 'results.json';
138         else
139             resultsFilename = 'results-small.json';
140
141         var resultsFileLocation = pathToBuilderResultsFile(builderName) + resultsFilename;
142         loader.request(resultsFileLocation,
143                 partial(function(loader, builderName, xhr) {
144                     loader._handleResultsFileLoaded(builderName, xhr.responseText);
145                 }, this, builderName),
146                 partial(function(loader, builderName, xhr) {
147                     loader._handleResultsFileLoadError(builderName);
148                 }, this, builderName));
149     },
150     _handleResultsFileLoaded: function(builderName, fileData)
151     {
152         if (isTreeMap())
153             this._processTimesJSONData(builderName, fileData);
154         else
155             this._processResultsJSONData(builderName, fileData);
156
157         // We need this work-around for webkit.org/b/50589.
158         if (!g_resultsByBuilder[builderName]) {
159             this._handleResultsFileLoadError(builderName);
160             return;
161         }
162
163         this._handleResourceLoad();
164     },
165     _processTimesJSONData: function(builderName, fileData)
166     {
167         // FIXME: We should probably include the builderName in the JSON
168         // rather than relying on only loading one JSON file per page.
169         g_resultsByBuilder[builderName] = JSON.parse(fileData);
170     },
171     _processResultsJSONData: function(builderName, fileData)
172     {
173         var builds = JSON.parse(fileData);
174
175         var json_version = builds['version'];
176         for (var builderName in builds) {
177             if (builderName == 'version')
178                 continue;
179
180             // If a test suite stops being run on a given builder, we don't want to show it.
181             // Assume any builder without a run in two weeks for a given test suite isn't
182             // running that suite anymore.
183             // FIXME: Grab which bots run which tests directly from the buildbot JSON instead.
184             var lastRunSeconds = builds[builderName].secondsSinceEpoch[0];
185             if ((Date.now() / 1000) - lastRunSeconds > ONE_WEEK_SECONDS)
186                 continue;
187
188             if ((Date.now() / 1000) - lastRunSeconds > ONE_DAY_SECONDS)
189                 this._staleBuilders.push(builderName);
190
191             if (json_version >= 4)
192                 builds[builderName][TESTS_KEY] = loader.Loader._flattenTrie(builds[builderName][TESTS_KEY]);
193             g_resultsByBuilder[builderName] = builds[builderName];
194         }
195     },
196     _handleResultsFileLoadError: function(builderName)
197     {
198         console.error('Failed to load results file for ' + builderName + '.');
199
200         // FIXME: loader shouldn't depend on state defined in dashboard_base.js.
201         this._buildersThatFailedToLoad.push(builderName);
202
203         // Remove this builder from builders, so we don't try to use the
204         // data that isn't there.
205         delete currentBuilders()[builderName];
206
207         // Proceed as if the resource had loaded.
208         this._handleResourceLoad();
209     },
210     _handleResourceLoad: function()
211     {
212         if (this._haveResultsFilesLoaded())
213             this._loadNext();
214     },
215     _haveResultsFilesLoaded: function()
216     {
217         for (var builder in currentBuilders()) {
218             if (!g_resultsByBuilder[builder])
219                 return false;
220         }
221         return true;
222     },
223     _loadExpectationsFiles: function()
224     {
225         if (!isFlakinessDashboard() && !g_crossDashboardState.useTestData) {
226             this._loadNext();
227             return;
228         }
229
230         var expectationsFilesToRequest = {};
231         traversePlatformsTree(function(platform, platformName) {
232             if (platform.fallbackPlatforms)
233                 platform.fallbackPlatforms.forEach(function(fallbackPlatform) {
234                     var fallbackPlatformObject = platformObjectForName(fallbackPlatform);
235                     if (fallbackPlatformObject.expectationsDirectory && !(fallbackPlatform in expectationsFilesToRequest))
236                         expectationsFilesToRequest[fallbackPlatform] = EXPECTATIONS_URL_BASE_PATH + fallbackPlatformObject.expectationsDirectory + '/TestExpectations';
237                 });
238
239             if (platform.expectationsDirectory)
240                 expectationsFilesToRequest[platformName] = EXPECTATIONS_URL_BASE_PATH + platform.expectationsDirectory + '/TestExpectations';
241         });
242
243         for (platformWithExpectations in expectationsFilesToRequest)
244             loader.request(expectationsFilesToRequest[platformWithExpectations],
245                     partial(function(loader, platformName, xhr) {
246                         g_expectationsByPlatform[platformName] = getParsedExpectations(xhr.responseText);
247
248                         delete expectationsFilesToRequest[platformName];
249                         if (!Object.keys(expectationsFilesToRequest).length)
250                             loader._loadNext();
251                     }, this, platformWithExpectations),
252                     partial(function(platformName, xhr) {
253                         console.error('Could not load expectations file for ' + platformName);
254                     }, platformWithExpectations));
255     },
256     _addErrors: function()
257     {
258         if (this._buildersThatFailedToLoad.length)
259             this._errors.addError('ERROR: Failed to get data from ' + this._buildersThatFailedToLoad.toString() +'.');
260
261         if (this._staleBuilders.length)
262             this._errors.addError('ERROR: Data from ' + this._staleBuilders.toString() + ' is more than 1 day stale.');
263     }
264 }
265
266 })();