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<link rel="import" href="/tracing/base/raf.html"> 8<link rel="import" href="/tracing/base/timing.html"> 9<link rel="import" href="/tracing/base/event_target.html"> 10<script> 11'use strict'; 12 13tr.exportTo('tr.b.unittest', function() { 14 var realTvOnAnimationFrameError; 15 var realGlobalOnError; 16 var realGlobalHistoryPushState; 17 18 var NUM_TESTS_PER_RIC = 16; 19 20 function installGlobalTestHooks(runner) { 21 realTvOnAnimationFrameError = tr.b.onAnimationFrameError; 22 tr.b.onAnimationFrameError = function(error) { 23 runner.results.addErrorForCurrentTest(error); 24 }; 25 26 if (tr.isExported('global.onerror')) { 27 realGlobalOnError = global.onerror; 28 global.onerror = function(errorMsg, url, lineNumber) { 29 runner.results.addErrorForCurrentTest( 30 errorMsg + ' at ' + url + ':' + lineNumber); 31 if (realGlobalOnError) 32 return realGlobalOnError(errorMsg, url, lineNumber); 33 return false; 34 }; 35 } 36 37 if (tr.isExported('global.history')) { 38 realGlobalHistoryPushState = global.history.pushState; 39 global.history.pushState = function() { 40 }; 41 } 42 43 tr.b.unittest.addHTMLOutputForCurrentTest = function(element) { 44 runner.results.addHTMLOutputForCurrentTest(element); 45 }; 46 47 if (tr.isExported('global.sessionStorage')) { 48 global.sessionStorage.clear(); 49 } 50 51 var e = new tr.b.Event('tr-unittest-will-run'); 52 TestRunner.dispatchEvent(e); 53 } 54 55 function uninstallGlobalTestHooks() { 56 if (tr.isExported('global.onerror')) { 57 global.onerror = realGlobalOnError; 58 realGlobalOnError = undefined; 59 } 60 61 tr.b.onAnimationFrameError = realTvOnAnimationFrameError; 62 realTvOnAnimationFrameError = undefined; 63 64 if (tr.isExported('global.history')) { 65 global.history.pushState = realGlobalHistoryPushState; 66 realGlobalHistoryPushState = undefined; 67 } 68 69 tr.b.unittest.addHTMLOutputForCurrentTest = undefined; 70 } 71 72 73 function TestRunner(results, testCases) { 74 this.results_ = results; 75 this.testCases_ = testCases; 76 this.pendingTestCases_ = []; 77 78 this.runOneTestCaseScheduled_ = false; 79 this.numRunsSinceLastRIC_ = 0; 80 81 this.runCompletedPromise = undefined; 82 this.runCompletedResolver_ = undefined; 83 84 this.currentTestCase_ = undefined; 85 } 86 87 TestRunner.prototype = { 88 __proto__: Object.prototype, 89 90 beginRunning: function() { 91 if (this.pendingTestCases_.length) 92 throw new Error('Tests still running!'); 93 94 this.runCompletedPromise = new Promise(function(resolve, reject) { 95 this.runCompletedResolver_ = { 96 resolve: resolve, 97 reject: reject 98 }; 99 }.bind(this)); 100 101 this.pendingTestCases_ = this.testCases_.slice(0); 102 103 this.results_.willRunTests(this.pendingTestCases_); 104 105 this.scheduleRunOneTestCase_(); 106 107 return this.runCompletedPromise; 108 }, 109 110 beginToStopRunning: function() { 111 if (!this.runCompletedResolver_) 112 throw new Error('Still running'); 113 this.pendingTestCases_ = []; 114 return this.runCompletedPromise; 115 }, 116 117 get testCases() { 118 return this.testCases_; 119 }, 120 121 get results() { 122 return this.results_; 123 }, 124 125 scheduleRunOneTestCase_: function() { 126 if (this.runOneTestCaseScheduled_) 127 return; 128 this.runOneTestCaseScheduled_ = true; 129 130 this.numRunsSinceLastRIC_++; 131 if (this.numRunsSinceLastRIC_ === NUM_TESTS_PER_RIC) { 132 this.numRunsSinceLastRIC_ = 0; 133 tr.b.requestIdleCallback(this.runOneTestCase_, this); 134 } else { 135 Promise.resolve().then(this.runOneTestCase_.bind(this)); 136 } 137 }, 138 139 runOneTestCase_: function() { 140 this.runOneTestCaseScheduled_ = false; 141 142 if (this.pendingTestCases_.length == 0) { 143 this.didFinishRunningAllTests_(); 144 return; 145 } 146 147 this.currentTestCase_ = this.pendingTestCases_.splice(0, 1)[0]; 148 this.currentMark_ = tr.b.Timing.mark( 149 'TestRunner', 'RunTest', {testName: this.currentTestCase_.name}); 150 this.results_.willRunTest(this.currentTestCase_); 151 152 if (this.isCurrentTestFlaky_()) { 153 this.results_.setCurrentTestFlaky(); 154 this.results_.didCurrentTestEnd(); 155 this.currentMark_.end(); 156 this.currentTestCase_ = undefined; 157 this.scheduleRunOneTestCase_(); 158 return; 159 } 160 161 if (!this.setUpCurrentTestCase_()) { 162 this.results_.didCurrentTestEnd(); 163 this.currentMark_.end(); 164 this.currentTestCase_ = undefined; 165 this.scheduleRunOneTestCase_(); 166 return; 167 } 168 169 this.runCurrentTestCase_().then( 170 function pass(result) { 171 try { 172 this.tearDownCurrentTestCase_(true); 173 if (result) 174 this.results_.setReturnValueFromCurrentTest(result); 175 this.results_.didCurrentTestEnd(); 176 this.currentMark_.end(); 177 this.currentTestCase_ = undefined; 178 this.scheduleRunOneTestCase_(); 179 } catch (e) { 180 this.hadInternalError_(e); 181 throw e; 182 } 183 }.bind(this), 184 function fail(error) { 185 try { 186 this.results_.addErrorForCurrentTest(error); 187 this.tearDownCurrentTestCase_(false); 188 this.results_.didCurrentTestEnd(); 189 this.currentMark_.end(); 190 this.currentTestCase_ = undefined; 191 this.scheduleRunOneTestCase_(); 192 } catch (e) { 193 this.hadInternalError_(e); 194 throw e; 195 } 196 }.bind(this)); 197 }, 198 199 isCurrentTestFlaky_: function() { 200 return !!this.currentTestCase_.options['flaky']; 201 }, 202 203 setUpCurrentTestCase_: function() { 204 // Try setting it up. Return true if succeeded. 205 installGlobalTestHooks(this); 206 try { 207 this.currentTestCase_.setUp(); 208 } catch (error) { 209 this.results_.addErrorForCurrentTest(error); 210 return false; 211 } 212 return true; 213 }, 214 215 runCurrentTestCase_: function() { 216 return new Promise(function(resolve, reject) { 217 try { 218 var maybePromise = this.currentTestCase_.run(); 219 } catch (error) { 220 reject(error); 221 return; 222 } 223 224 if (maybePromise !== undefined && maybePromise.then) { 225 maybePromise.then( 226 function(result) { 227 resolve(result); 228 }, 229 function(error) { 230 reject(error); 231 }); 232 } else { 233 resolve(maybePromise); 234 } 235 }.bind(this)); 236 }, 237 238 hadInternalError_: function(outerE) { 239 try { 240 this.results.didRunTests(); 241 } catch (e) { 242 console.log('Had another error during hadInternalError_!'); 243 console.log(e.stack); 244 } 245 246 this.runCompletedResolver_.reject(outerE); 247 this.runCompletedResolver_ = undefined; 248 }, 249 250 tearDownCurrentTestCase_: function() { 251 try { 252 this.currentTestCase_.tearDown(); 253 } catch (error) { 254 this.results_.addErrorForCurrentTest(error); 255 } 256 257 uninstallGlobalTestHooks(); 258 }, 259 260 didFinishRunningAllTests_: function() { 261 this.results.didRunTests(); 262 this.runCompletedResolver_.resolve(); 263 this.runCompletedResolver_ = undefined; 264 } 265 }; 266 267 tr.b.EventTarget.decorate(TestRunner); 268 269 return { 270 TestRunner: TestRunner 271 }; 272}); 273</script> 274