blob: c77946476dd8b1bd5c99c1a63611ba51c67c255c [file] [log] [blame]
<!DOCTYPE html>
<!--
Copyright (c) 2014 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
-->
<link rel="import" href="/base/event_target.html">
<link rel="import" href="/base/xhr.html">
<link rel="import" href="/base/iteration_helpers.html">
<link rel="import" href="/base/unittest/test_suite.html">
<script>
'use strict';
tr.exportTo('tr.b.unittest', function() {
function TestLink(linkPath, title) {
this.linkPath = linkPath;
this.title = title;
}
function SuiteLoader(opt_suiteNamesToLoad) {
tr.b.EventTarget.call(this);
this.testSuiteGUIDs_ = {};
this.testSuites = [];
this.testLinks = [];
if (opt_suiteNamesToLoad) {
var tmp = opt_suiteNamesToLoad.map(function(n) {
var parts = n.split('.');
return parts.join('/') + '.html';
});
this.allSuitesLoadedPromise = this.beginLoadingModules_(
tmp);
} else {
var p;
p = tr.b.getAsync('/tr/json/tests');
p = p.then(
function(data) {
var testMetadata = JSON.parse(data);
if (testMetadata.hasOwnProperty('test_module_names')) {
tr.showPanic(
'out of date webserver',
'>_< your devserver needs a restart kthxbai');
}
var testRelpaths = testMetadata.test_relpaths;
return this.beginLoadingModules_(testRelpaths, testMetadata);
}.bind(this));
this.allSuitesLoadedPromise = p;
}
}
function loadModule(relpath) {
return new Promise(function(resolve, reject) {
var href = '/' + relpath;
var moduleName = relpath.split('/').slice(-1)[0];
var importEl = document.createElement('link');
importEl.moduleName = moduleName;
importEl.setAttribute('rel', 'import');
importEl.setAttribute('href', href);
tr.doc.head.appendChild(importEl);
importEl.addEventListener('load', function() {
resolve(importEl);
});
importEl.addEventListener('error', function(e) {
reject('Error loading &#60;link rel="import" href="' + href + '"');
});
});
}
function loadModules(moduleNames) {
var promises = moduleNames.map(function(moduleName) {
return loadModule(moduleName);
});
}
SuiteLoader.prototype = {
__proto__: tr.b.EventTarget.prototype,
beginLoadingModules_: function(testRelpaths, opt_testMetadata) {
if (opt_testMetadata) {
var testMetadata = opt_testMetadata;
for (var i = 0; i < testMetadata.test_links.length; i++) {
var tl = testMetadata.test_links[i];
this.testLinks.push(new TestLink(tl['path'],
tl['title']));
}
}
// Hooks!
this.bindGlobalHooks_();
// Load the modules.
var modulePromises = [];
for (var i = 0; i < testRelpaths.length; i++) {
var p = loadModule(testRelpaths[i]);
p = p.then(
function(importEl) {
this.didImportElementGetLoaded_(importEl);
}.bind(this));
p.x = 7;
modulePromises.push(p);
}
var allModulesLoadedPromise = new Promise(function(resolve, reject) {
var remaining = modulePromises.length;
var resolved = false;
function oneMoreLoaded() {
if (resolved)
return;
remaining--;
if (remaining > 0)
return;
resolved = true;
resolve();
}
function oneRejected(e) {
if (resolved)
return;
resolved = true;
reject(e);
}
modulePromises.forEach(function(modulePromise) {
modulePromise.then(oneMoreLoaded, oneRejected);
});
});
// Script errors errors abort load;
var scriptErrorPromise = new Promise(function(xresolve, xreject) {
this.scriptErrorPromiseResolver_ = {
resolve: xresolve,
reject: xreject
};
}.bind(this));
var donePromise = Promise.race([
allModulesLoadedPromise,
scriptErrorPromise
]);
// Cleanup.
return donePromise.then(
function() {
this.scriptErrorPromiseResolver_ = undefined;
this.unbindGlobalHooks_();
}.bind(this),
function(e) {
this.scriptErrorPromiseResolver_ = undefined;
this.unbindGlobalHooks_();
throw e;
}.bind(this));
},
bindGlobalHooks_: function() {
this.oldWindowOnError_ = window.onerror;
window.onerror = function(errorMsg, url, lineNumber) {
this.scriptErrorPromiseResolver_.reject(
new Error(errorMsg + '\n' + url + ':' + lineNumber));
if (this.oldWindowOnError_)
return this.oldWindowOnError_(errorMsg, url, lineNumber);
return false;
}.bind(this);
},
unbindGlobalHooks_: function() {
window.onerror = this.oldWindowOnError_;
this.oldWindowOnError_ = undefined;
},
didImportElementGetLoaded_: function(importEl) {
// The global tr.testSute function stashes test suites
// onto the _tr array.
var importDoc = importEl.import;
var suites = allTestSuitesByModuleURL[importDoc.URL];
suites.forEach(function(testSuite) {
if (this.testSuiteGUIDs_[testSuite.guid])
return;
this.testSuiteGUIDs_[testSuite.guid] = true;
this.testSuites.push(testSuite);
var e = new Event('suite-loaded');
e.testSuite = testSuite;
this.dispatchEvent(e);
}, this);
},
getAllTests: function() {
var tests = [];
this.testSuites.forEach(function(suite) {
tests.push.apply(tests, suite.tests);
});
return tests;
},
findTestWithFullyQualifiedName: function(fullyQualifiedName) {
for (var i = 0; i < this.testSuites.length; i++) {
var suite = this.testSuites[i];
for (var j = 0; j < suite.tests.length; j++) {
var test = suite.tests[j];
if (test.fullyQualifiedName == fullyQualifiedName)
return test;
}
}
throw new Error('Test ' + fullyQualifiedName +
'not found amongst ' + this.testSuites.length);
}
};
var allTestSuitesByModuleURL = [];
function _guessModuleNameFromURL(url) {
var m = /.+?:\/\/.+?(\/.+)/.exec(url);
if (!m)
throw new Error('Guessing module name failed');
var path = m[1];
if (path[0] != '/')
throw new Error('malformed path');
if (path.substring(path.length - 5) != '.html')
throw new Error('Cannot define testSuites outside html imports');
var parts = path.substring(1, path.length - 5).split('/');
return parts.join('.');
}
function testSuite(suiteConstructor) {
var linkDoc = document.currentScript.ownerDocument;
var url = linkDoc.URL;
var name = _guessModuleNameFromURL(url);
if (!document.currentScript)
throw new Error('Cannot call testSuite except during load.');
var testSuite = new tr.b.unittest.TestSuite(
name, suiteConstructor);
if (allTestSuitesByModuleURL[url] === undefined)
allTestSuitesByModuleURL[url] = [];
allTestSuitesByModuleURL[url].push(testSuite);
}
return {
SuiteLoader: SuiteLoader,
testSuite: testSuite
};
});
</script>