1<!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 8<link rel="import" href="/tracing/base/event.html"> 9<link rel="import" href="/tracing/base/event_target.html"> 10<link rel="import" href="/tracing/base/iteration_helpers.html"> 11<link rel="import" href="/tracing/base/unittest/test_suite.html"> 12<link rel="import" href="/tracing/base/xhr.html"> 13 14<script> 15'use strict'; 16 17tr.exportTo('tr.b.unittest', function() { 18 function HTMLImportsModuleLoader() { 19 } 20 HTMLImportsModuleLoader.prototype = { 21 loadModule: function(testRelpath, moduleName) { 22 return new Promise(function(resolve, reject) { 23 var importEl = document.createElement('link'); 24 importEl.moduleName = moduleName; 25 importEl.setAttribute('rel', 'import'); 26 importEl.setAttribute('href', testRelpath); 27 28 importEl.addEventListener('load', function() { 29 resolve({testRelpath: testRelpath, 30 moduleName: moduleName}); 31 }); 32 importEl.addEventListener('error', function(e) { 33 reject('Error loading <link rel="import" href="' + 34 testRelpath + '"'); 35 }); 36 37 tr.doc.head.appendChild(importEl); 38 }); 39 }, 40 41 getCurrentlyExecutingModuleName: function() { 42 if (!document.currentScript) 43 throw new Error('Cannot call testSuite except during load.'); 44 var linkDoc = document.currentScript.ownerDocument; 45 var url = linkDoc.URL; 46 var name = this.guessModuleNameFromURL_(url); 47 return name; 48 }, 49 50 guessModuleNameFromURL_: function(url) { 51 var m = /.+?:\/\/.+?(\/.+)/.exec(url); 52 if (!m) 53 throw new Error('Guessing module name failed'); 54 var path = m[1]; 55 if (path[0] != '/') 56 throw new Error('malformed path'); 57 if (path.substring(path.length - 5) != '.html') 58 throw new Error('Cannot define testSuites outside html imports'); 59 var parts = path.substring(1, path.length - 5).split('/'); 60 return parts.join('.'); 61 } 62 }; 63 64 function HeadlessModuleLoader() { 65 this.currentlyExecutingModuleInfo_ = undefined; 66 } 67 HeadlessModuleLoader.prototype = { 68 loadModule: function(testRelpath, moduleName) { 69 return Promise.resolve().then(function() { 70 var moduleInfo = { 71 testRelpath: testRelpath, 72 moduleName: moduleName 73 }; 74 if (this.currentlyExecutingModuleInfo_ !== undefined) 75 throw new Error('WAT'); 76 this.currentlyExecutingModuleInfo_ = moduleInfo; 77 78 try { 79 loadHTML(testRelpath); 80 } catch (e) { 81 e.message = 'While loading ' + moduleName + ', ' + e.message; 82 e.stack = 'While loading ' + moduleName + ', ' + e.stack; 83 throw e; 84 } finally { 85 this.currentlyExecutingModuleInfo_ = undefined; 86 } 87 88 return moduleInfo; 89 }.bind(this)); 90 }, 91 92 getCurrentlyExecutingModuleName: function() { 93 if (this.currentlyExecutingModuleInfo_ === undefined) 94 throw new Error('No currently loading module'); 95 return this.currentlyExecutingModuleInfo_.moduleName; 96 } 97 }; 98 99 100 function SuiteLoader(suiteRelpathsToLoad) { 101 tr.b.EventTarget.call(this); 102 103 this.currentModuleLoader_ = undefined; 104 this.testSuites = []; 105 106 if (tr.isHeadless) { 107 this.currentModuleLoader_ = new HeadlessModuleLoader(); 108 } else { 109 this.currentModuleLoader_ = new HTMLImportsModuleLoader(); 110 } 111 112 this.allSuitesLoadedPromise = this.beginLoadingModules_( 113 suiteRelpathsToLoad); 114 } 115 116 SuiteLoader.prototype = { 117 __proto__: tr.b.EventTarget.prototype, 118 119 beginLoadingModules_: function(testRelpaths) { 120 // Hooks! 121 this.bindGlobalHooks_(); 122 123 // Load the modules. 124 var modulePromises = []; 125 for (var i = 0; i < testRelpaths.length; i++) { 126 var testRelpath = testRelpaths[i]; 127 var moduleName = testRelpath.split('/').slice(-1)[0]; 128 129 var p = this.currentModuleLoader_.loadModule(testRelpath, moduleName); 130 modulePromises.push(p); 131 } 132 133 var allModulesLoadedPromise = new Promise(function(resolve, reject) { 134 var remaining = modulePromises.length; 135 var resolved = false; 136 function oneMoreLoaded() { 137 if (resolved) 138 return; 139 remaining--; 140 if (remaining > 0) 141 return; 142 resolved = true; 143 resolve(); 144 } 145 146 function oneRejected(e) { 147 if (resolved) 148 return; 149 resolved = true; 150 reject(e); 151 } 152 153 modulePromises.forEach(function(modulePromise) { 154 modulePromise.then(oneMoreLoaded, oneRejected); 155 }); 156 }); 157 158 // Script errors errors abort load; 159 var scriptErrorPromise = new Promise(function(xresolve, xreject) { 160 this.scriptErrorPromiseResolver_ = { 161 resolve: xresolve, 162 reject: xreject 163 }; 164 }.bind(this)); 165 var donePromise = Promise.race([ 166 allModulesLoadedPromise, 167 scriptErrorPromise 168 ]); 169 170 // Cleanup. 171 return donePromise.then( 172 function() { 173 this.scriptErrorPromiseResolver_ = undefined; 174 this.unbindGlobalHooks_(); 175 }.bind(this), 176 function(e) { 177 this.scriptErrorPromiseResolver_ = undefined; 178 this.unbindGlobalHooks_(); 179 throw e; 180 }.bind(this)); 181 }, 182 183 bindGlobalHooks_: function() { 184 if (global._currentSuiteLoader !== undefined) 185 throw new Error('A suite loader exists already'); 186 global._currentSuiteLoader = this; 187 188 this.oldGlobalOnError_ = global.onerror; 189 global.onerror = function(errorMsg, url, lineNumber) { 190 this.scriptErrorPromiseResolver_.reject( 191 new Error(errorMsg + '\n' + url + ':' + lineNumber)); 192 if (this.oldGlobalOnError_) 193 return this.oldGlobalOnError_(errorMsg, url, lineNumber); 194 return false; 195 }.bind(this); 196 }, 197 198 unbindGlobalHooks_: function() { 199 global._currentSuiteLoader = undefined; 200 201 global.onerror = this.oldGlobalOnError_; 202 this.oldGlobalOnError_ = undefined; 203 }, 204 205 constructAndRegisterTestSuite: function(suiteConstructor) { 206 var name = this.currentModuleLoader_.getCurrentlyExecutingModuleName(); 207 208 var testSuite = new tr.b.unittest.TestSuite( 209 name, suiteConstructor); 210 211 this.testSuites.push(testSuite); 212 213 var e = new tr.b.Event('suite-loaded'); 214 e.testSuite = testSuite; 215 this.dispatchEvent(e); 216 }, 217 218 getAllTests: function() { 219 var tests = []; 220 this.testSuites.forEach(function(suite) { 221 tests.push.apply(tests, suite.tests); 222 }); 223 return tests; 224 }, 225 226 findTestWithFullyQualifiedName: function(fullyQualifiedName) { 227 for (var i = 0; i < this.testSuites.length; i++) { 228 var suite = this.testSuites[i]; 229 for (var j = 0; j < suite.tests.length; j++) { 230 var test = suite.tests[j]; 231 if (test.fullyQualifiedName == fullyQualifiedName) 232 return test; 233 } 234 } 235 throw new Error('Test ' + fullyQualifiedName + 236 'not found amongst ' + this.testSuites.length); 237 } 238 }; 239 240 return { 241 SuiteLoader: SuiteLoader 242 }; 243}); 244</script> 245