Home | History | Annotate | Download | only in unittest
      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 
      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 
     17 tr.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 &#60;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