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