Chris Craik | 93216d0 | 2015-03-05 13:58:42 -0800 | [diff] [blame] | 1 | <!DOCTYPE html> |
| 2 | <!-- |
| 3 | Copyright (c) 2014 The Chromium Authors. All rights reserved. |
| 4 | Use of this source code is governed by a BSD-style license that can be |
| 5 | found in the LICENSE file. |
| 6 | --> |
| 7 | <link rel="import" href="/base/event_target.html"> |
Chris Craik | f516a62 | 2015-04-01 17:52:39 -0700 | [diff] [blame] | 8 | <link rel="import" href="/base/xhr.html"> |
Chris Craik | 93216d0 | 2015-03-05 13:58:42 -0800 | [diff] [blame] | 9 | <link rel="import" href="/base/iteration_helpers.html"> |
| 10 | <link rel="import" href="/base/unittest/test_suite.html"> |
| 11 | <script> |
| 12 | 'use strict'; |
| 13 | |
| 14 | tv.exportTo('tv.b.unittest', function() { |
Chris Craik | 93216d0 | 2015-03-05 13:58:42 -0800 | [diff] [blame] | 15 | 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 Craik | 44c2820 | 2015-05-12 17:25:16 -0700 | [diff] [blame^] | 28 | var tmp = opt_suiteNamesToLoad.map(function(n) { |
| 29 | var parts = n.split('.'); |
| 30 | return parts.join('/') + '.html'; |
| 31 | }); |
Chris Craik | 93216d0 | 2015-03-05 13:58:42 -0800 | [diff] [blame] | 32 | this.allSuitesLoadedPromise = this.beginLoadingModules_( |
Chris Craik | 44c2820 | 2015-05-12 17:25:16 -0700 | [diff] [blame^] | 33 | tmp); |
Chris Craik | 93216d0 | 2015-03-05 13:58:42 -0800 | [diff] [blame] | 34 | } else { |
| 35 | var p; |
Chris Craik | f516a62 | 2015-04-01 17:52:39 -0700 | [diff] [blame] | 36 | p = tv.b.getAsync('/tv/json/tests'); |
Chris Craik | 93216d0 | 2015-03-05 13:58:42 -0800 | [diff] [blame] | 37 | p = p.then( |
| 38 | function(data) { |
| 39 | var testMetadata = JSON.parse(data); |
Chris Craik | 44c2820 | 2015-05-12 17:25:16 -0700 | [diff] [blame^] | 40 | 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 Craik | 93216d0 | 2015-03-05 13:58:42 -0800 | [diff] [blame] | 47 | }.bind(this)); |
| 48 | this.allSuitesLoadedPromise = p; |
| 49 | } |
| 50 | } |
| 51 | |
Chris Craik | 44c2820 | 2015-05-12 17:25:16 -0700 | [diff] [blame^] | 52 | function loadModule(relpath) { |
Chris Craik | 93216d0 | 2015-03-05 13:58:42 -0800 | [diff] [blame] | 53 | return new Promise(function(resolve, reject) { |
Chris Craik | 44c2820 | 2015-05-12 17:25:16 -0700 | [diff] [blame^] | 54 | var href = '/' + relpath; |
| 55 | var moduleName = relpath.split('/').slice(-1)[0]; |
Chris Craik | 93216d0 | 2015-03-05 13:58:42 -0800 | [diff] [blame] | 56 | |
| 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 <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 Craik | 44c2820 | 2015-05-12 17:25:16 -0700 | [diff] [blame^] | 82 | beginLoadingModules_: function(testRelpaths, opt_testMetadata) { |
Chris Craik | 93216d0 | 2015-03-05 13:58:42 -0800 | [diff] [blame] | 83 | 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 Craik | 44c2820 | 2015-05-12 17:25:16 -0700 | [diff] [blame^] | 97 | for (var i = 0; i < testRelpaths.length; i++) { |
| 98 | var p = loadModule(testRelpaths[i]); |
Chris Craik | 93216d0 | 2015-03-05 13:58:42 -0800 | [diff] [blame] | 99 | 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> |