blob: eff10c270101fba347f5b0b478657fa0d12a5e95 [file] [log] [blame]
Chris Craik93216d02015-03-05 13:58:42 -08001<!DOCTYPE html>
2<!--
3Copyright (c) 2014 The Chromium Authors. All rights reserved.
4Use of this source code is governed by a BSD-style license that can be
5found in the LICENSE file.
6-->
7<link rel="import" href="/base/event_target.html">
Chris Craikf516a622015-04-01 17:52:39 -07008<link rel="import" href="/base/xhr.html">
Chris Craik93216d02015-03-05 13:58:42 -08009<link rel="import" href="/base/iteration_helpers.html">
10<link rel="import" href="/base/unittest/test_suite.html">
11<script>
12'use strict';
13
14tv.exportTo('tv.b.unittest', function() {
Chris Craik93216d02015-03-05 13:58:42 -080015 function TestLink(linkPath, title) {
16 this.linkPath = linkPath;
17 this.title = title;
18 }
19
20 function SuiteLoader(opt_suiteNamesToLoad) {
21 tv.b.EventTarget.call(this);
22
23 this.testSuiteGUIDs_ = {};
24 this.testSuites = [];
25 this.testLinks = [];
26
27 if (opt_suiteNamesToLoad) {
Chris Craik44c28202015-05-12 17:25:16 -070028 var tmp = opt_suiteNamesToLoad.map(function(n) {
29 var parts = n.split('.');
30 return parts.join('/') + '.html';
31 });
Chris Craik93216d02015-03-05 13:58:42 -080032 this.allSuitesLoadedPromise = this.beginLoadingModules_(
Chris Craik44c28202015-05-12 17:25:16 -070033 tmp);
Chris Craik93216d02015-03-05 13:58:42 -080034 } else {
35 var p;
Chris Craikf516a622015-04-01 17:52:39 -070036 p = tv.b.getAsync('/tv/json/tests');
Chris Craik93216d02015-03-05 13:58:42 -080037 p = p.then(
38 function(data) {
39 var testMetadata = JSON.parse(data);
Chris Craik44c28202015-05-12 17:25:16 -070040 if (testMetadata.hasOwnProperty('test_module_names')) {
41 tv.showPanic(
42 'out of date webserver',
43 '>_< your devserver needs a restart kthxbai');
44 }
45 var testRelpaths = testMetadata.test_relpaths;
46 return this.beginLoadingModules_(testRelpaths, testMetadata);
Chris Craik93216d02015-03-05 13:58:42 -080047 }.bind(this));
48 this.allSuitesLoadedPromise = p;
49 }
50 }
51
Chris Craik44c28202015-05-12 17:25:16 -070052 function loadModule(relpath) {
Chris Craik93216d02015-03-05 13:58:42 -080053 return new Promise(function(resolve, reject) {
Chris Craik44c28202015-05-12 17:25:16 -070054 var href = '/' + relpath;
55 var moduleName = relpath.split('/').slice(-1)[0];
Chris Craik93216d02015-03-05 13:58:42 -080056
57 var importEl = document.createElement('link');
58 importEl.moduleName = moduleName;
59 importEl.setAttribute('rel', 'import');
60 importEl.setAttribute('href', href);
61 tv.doc.head.appendChild(importEl);
62
63 importEl.addEventListener('load', function() {
64 resolve(importEl);
65 });
66 importEl.addEventListener('error', function(e) {
67 reject('Error loading &#60;link rel="import" href="' + href + '"');
68 });
69 });
70 }
71
72 function loadModules(moduleNames) {
73 var promises = moduleNames.map(function(moduleName) {
74 return loadModule(moduleName);
75 });
76
77 }
78
79 SuiteLoader.prototype = {
80 __proto__: tv.b.EventTarget.prototype,
81
Chris Craik44c28202015-05-12 17:25:16 -070082 beginLoadingModules_: function(testRelpaths, opt_testMetadata) {
Chris Craik93216d02015-03-05 13:58:42 -080083 if (opt_testMetadata) {
84 var testMetadata = opt_testMetadata;
85 for (var i = 0; i < testMetadata.test_links.length; i++) {
86 var tl = testMetadata.test_links[i];
87 this.testLinks.push(new TestLink(tl['path'],
88 tl['title']));
89 }
90 }
91
92 // Hooks!
93 this.bindGlobalHooks_();
94
95 // Load the modules.
96 var modulePromises = [];
Chris Craik44c28202015-05-12 17:25:16 -070097 for (var i = 0; i < testRelpaths.length; i++) {
98 var p = loadModule(testRelpaths[i]);
Chris Craik93216d02015-03-05 13:58:42 -080099 p = p.then(
100 function(importEl) {
101 this.didImportElementGetLoaded_(importEl);
102 }.bind(this));
103 p.x = 7;
104 modulePromises.push(p);
105 }
106
107 var allModulesLoadedPromise = new Promise(function(resolve, reject) {
108 var remaining = modulePromises.length;
109 var resolved = false;
110 function oneMoreLoaded() {
111 if (resolved)
112 return;
113 remaining--;
114 if (remaining > 0)
115 return;
116 resolved = true;
117 resolve();
118 }
119
120 function oneRejected(e) {
121 if (resolved)
122 return;
123 resolved = true;
124 reject(e);
125 }
126
127 modulePromises.forEach(function(modulePromise) {
128 modulePromise.then(oneMoreLoaded, oneRejected);
129 });
130 });
131
132 // Script errors errors abort load;
133 var scriptErrorPromise = new Promise(function(xresolve, xreject) {
134 this.scriptErrorPromiseResolver_ = {
135 resolve: xresolve,
136 reject: xreject
137 };
138 }.bind(this));
139 var donePromise = Promise.race([
140 allModulesLoadedPromise,
141 scriptErrorPromise
142 ]);
143
144 // Cleanup.
145 return donePromise.then(
146 function() {
147 this.scriptErrorPromiseResolver_ = undefined;
148 this.unbindGlobalHooks_();
149 }.bind(this),
150 function(e) {
151 this.scriptErrorPromiseResolver_ = undefined;
152 this.unbindGlobalHooks_();
153 throw e;
154 }.bind(this));
155 },
156
157 bindGlobalHooks_: function() {
158 this.oldWindowOnError_ = window.onerror;
159 window.onerror = function(errorMsg, url, lineNumber) {
160 this.scriptErrorPromiseResolver_.reject(
161 new Error(errorMsg + '\n' + url + ':' + lineNumber));
162 if (this.oldWindowOnError_)
163 return this.oldWindowOnError_(errorMsg, url, lineNumber);
164 return false;
165 }.bind(this);
166 },
167
168 unbindGlobalHooks_: function() {
169 window.onerror = this.oldWindowOnError_;
170 this.oldWindowOnError_ = undefined;
171 },
172
173 didImportElementGetLoaded_: function(importEl) {
174 // The global tv.testSute function stashes test suites
175 // onto the _tv array.
176 var importDoc = importEl.import;
177 var suites = allTestSuitesByModuleURL[importDoc.URL];
178 suites.forEach(function(testSuite) {
179 if (this.testSuiteGUIDs_[testSuite.guid])
180 return;
181 this.testSuiteGUIDs_[testSuite.guid] = true;
182 this.testSuites.push(testSuite);
183
184 var e = new Event('suite-loaded');
185 e.testSuite = testSuite;
186 this.dispatchEvent(e);
187 }, this);
188 },
189
190 getAllTests: function() {
191 var tests = [];
192 this.testSuites.forEach(function(suite) {
193 tests.push.apply(tests, suite.tests);
194 });
195 return tests;
196 },
197
198 findTestWithFullyQualifiedName: function(fullyQualifiedName) {
199 for (var i = 0; i < this.testSuites.length; i++) {
200 var suite = this.testSuites[i];
201 for (var j = 0; j < suite.tests.length; j++) {
202 var test = suite.tests[j];
203 if (test.fullyQualifiedName == fullyQualifiedName)
204 return test;
205 }
206 }
207 throw new Error('Test ' + fullyQualifiedName +
208 'not found amongst ' + this.testSuites.length);
209 }
210 };
211
212 var allTestSuitesByModuleURL = [];
213
214 function _guessModuleNameFromURL(url) {
215 var m = /.+?:\/\/.+?(\/.+)/.exec(url);
216 if (!m)
217 throw new Error('Guessing module name failed');
218 var path = m[1];
219 if (path[0] != '/')
220 throw new Error('malformed path');
221 if (path.substring(path.length - 5) != '.html')
222 throw new Error('Cannot define testSuites outside html imports');
223 var parts = path.substring(1, path.length - 5).split('/');
224 return parts.join('.');
225 }
226
227 function testSuite(suiteConstructor) {
228 var linkDoc = document.currentScript.ownerDocument;
229 var url = linkDoc.URL;
230 var name = _guessModuleNameFromURL(url);
231 if (!document.currentScript)
232 throw new Error('Cannot call testSuite except during load.');
233
234 var testSuite = new tv.b.unittest.TestSuite(
235 name, suiteConstructor);
236
237
238 if (allTestSuitesByModuleURL[url] === undefined)
239 allTestSuitesByModuleURL[url] = [];
240 allTestSuitesByModuleURL[url].push(testSuite);
241 }
242
243 return {
244 SuiteLoader: SuiteLoader,
245 testSuite: testSuite
246 };
247});
248</script>