Re-enable index-basics-workers test to see if still times
[chromium-blink-merge.git] / tools / cc-frame-viewer / src / base / unittest.js
blob7d134b65acac4f15344d20144503034f80b639d9
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 'use strict';
7 /**
8 * @fileoverview A test harness loosely based on Python unittest, but that
9 * installs global assert methods during the test for backward compatibility
10 * with Closure tests.
12 base.requireStylesheet('base.unittest');
13 base.exportTo('base.unittest', function() {
15 var NOCATCH_MODE = false;
17 // Uncomment the line below to make unit test failures throw exceptions.
18 NOCATCH_MODE = true;
20 function createTestCaseDiv(testName, opt_href, opt_alwaysShowErrorLink) {
21 var el = document.createElement('test-case');
23 var titleBlockEl = document.createElement('title');
24 titleBlockEl.style.display = 'inline';
25 el.appendChild(titleBlockEl);
27 var titleEl = document.createElement('span');
28 titleEl.style.marginRight = '20px';
29 titleBlockEl.appendChild(titleEl);
31 var errorLink = document.createElement('a');
32 errorLink.textContent = 'Run individually...';
33 if (opt_href)
34 errorLink.href = opt_href;
35 else
36 errorLink.href = '#' + testName;
37 errorLink.style.display = 'none';
38 titleBlockEl.appendChild(errorLink);
40 el.__defineSetter__('status', function(status) {
41 titleEl.textContent = testName + ': ' + status;
42 updateClassListGivenStatus(titleEl, status);
43 if (status == 'FAILED' || opt_alwaysShowErrorLink)
44 errorLink.style.display = '';
45 else
46 errorLink.style.display = 'none';
47 });
49 el.addError = function(test, e) {
50 var errorEl = createErrorDiv(test, e);
51 el.appendChild(errorEl);
52 return errorEl;
55 el.addHTMLOutput = function(opt_title, opt_element) {
56 var outputEl = createOutputDiv(opt_title, opt_element);
57 el.appendChild(outputEl);
58 return outputEl.contents;
61 el.status = 'READY';
62 return el;
65 function createErrorDiv(test, e) {
66 var el = document.createElement('test-case-error');
67 el.className = 'unittest-error';
69 var stackEl = document.createElement('test-case-stack');
70 if (typeof e == 'string') {
71 stackEl.textContent = e;
72 } else if (e.stack) {
73 var i = document.location.pathname.lastIndexOf('/');
74 var path = document.location.origin +
75 document.location.pathname.substring(0, i);
76 var pathEscaped = path.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
77 var cleanStack = e.stack.replace(new RegExp(pathEscaped, 'g'), '.');
78 stackEl.textContent = cleanStack;
79 } else {
80 stackEl.textContent = e;
82 el.appendChild(stackEl);
83 return el;
86 function createOutputDiv(opt_title, opt_element) {
87 var el = document.createElement('test-case-output');
88 if (opt_title) {
89 var titleEl = document.createElement('div');
90 titleEl.textContent = opt_title;
91 el.appendChild(titleEl);
93 var contentEl = opt_element || document.createElement('div');
94 el.appendChild(contentEl);
96 el.__defineGetter__('contents', function() {
97 return contentEl;
98 });
99 return el;
102 function statusToClassName(status) {
103 if (status == 'PASSED')
104 return 'unittest-green';
105 else if (status == 'RUNNING' || status == 'READY')
106 return 'unittest-yellow';
107 else
108 return 'unittest-red';
111 function updateClassListGivenStatus(el, status) {
112 var newClass = statusToClassName(status);
113 if (newClass != 'unittest-green')
114 el.classList.remove('unittest-green');
115 if (newClass != 'unittest-yellow')
116 el.classList.remove('unittest-yellow');
117 if (newClass != 'unittest-red')
118 el.classList.remove('unittest-red');
120 el.classList.add(newClass);
123 function HTMLTestRunner(opt_title, opt_curHash) {
124 // This constructs a HTMLDivElement and then adds our own runner methods to
125 // it. This is usually done via ui.js' define system, but we dont want our
126 // test runner to be dependent on the UI lib. :)
127 var outputEl = document.createElement('unittest-test-runner');
128 outputEl.__proto__ = HTMLTestRunner.prototype;
129 this.decorate.call(outputEl, opt_title, opt_curHash);
130 return outputEl;
133 HTMLTestRunner.prototype = {
134 __proto__: HTMLDivElement.prototype,
136 decorate: function(opt_title, opt_curHash) {
137 this.running = false;
139 this.currentTest_ = undefined;
140 this.results = undefined;
141 if (opt_curHash) {
142 var trimmedHash = opt_curHash.substring(1);
143 this.filterFunc_ = function(testName) {
144 return testName.indexOf(trimmedHash) == 0;
146 } else
147 this.filterFunc_ = function(testName) { return true; };
149 this.statusEl_ = document.createElement('title');
150 this.appendChild(this.statusEl_);
152 this.resultsEl_ = document.createElement('div');
153 this.appendChild(this.resultsEl_);
155 this.title_ = opt_title || document.title;
157 this.updateStatus();
160 computeResultStats: function() {
161 var numTestsRun = 0;
162 var numTestsPassed = 0;
163 var numTestsWithErrors = 0;
164 if (this.results) {
165 for (var i = 0; i < this.results.length; i++) {
166 numTestsRun++;
167 if (this.results[i].errors.length)
168 numTestsWithErrors++;
169 else
170 numTestsPassed++;
173 return {
174 numTestsRun: numTestsRun,
175 numTestsPassed: numTestsPassed,
176 numTestsWithErrors: numTestsWithErrors
180 updateStatus: function() {
181 var stats = this.computeResultStats();
182 var status;
183 if (!this.results) {
184 status = 'READY';
185 } else if (this.running) {
186 status = 'RUNNING';
187 } else {
188 if (stats.numTestsRun && stats.numTestsWithErrors == 0)
189 status = 'PASSED';
190 else
191 status = 'FAILED';
194 updateClassListGivenStatus(this.statusEl_, status);
195 this.statusEl_.textContent = this.title_ + ' [' + status + ']';
198 get done() {
199 return this.results && this.running == false;
202 run: function(tests) {
203 this.results = [];
204 this.running = true;
205 this.updateStatus();
206 for (var i = 0; i < tests.length; i++) {
207 if (!this.filterFunc_(tests[i].testName))
208 continue;
209 tests[i].run(this);
210 this.updateStatus();
212 this.running = false;
213 this.updateStatus();
216 willRunTest: function(test) {
217 this.currentTest_ = test;
218 this.currentResults_ = {testName: test.testName,
219 errors: []};
220 this.results.push(this.currentResults_);
222 this.currentTestCaseEl_ = createTestCaseDiv(test.testName);
223 this.currentTestCaseEl_.status = 'RUNNING';
224 this.resultsEl_.appendChild(this.currentTestCaseEl_);
228 * Adds some html content to the currently running test
229 * @param {String} opt_title The title for the output.
230 * @param {HTMLElement} opt_element The element to add. If not added, then.
231 * @return {HTMLElement} The element added, or if !opt_element, the element
232 * created.
234 addHTMLOutput: function(opt_title, opt_element) {
235 return this.currentTestCaseEl_.addHTMLOutput(opt_title, opt_element);
238 addError: function(e) {
239 this.currentResults_.errors.push(e);
240 return this.currentTestCaseEl_.addError(this.currentTest_, e);
243 didRunTest: function(test) {
244 if (!this.currentResults_.errors.length)
245 this.currentTestCaseEl_.status = 'PASSED';
246 else
247 this.currentTestCaseEl_.status = 'FAILED';
249 this.currentResults_ = undefined;
250 this.currentTest_ = undefined;
254 function TestError(opt_message) {
255 var that = new Error(opt_message);
256 Error.captureStackTrace(that, TestError);
257 that.__proto__ = TestError.prototype;
258 return that;
261 TestError.prototype = {
262 __proto__: Error.prototype
266 * @constructor TestCase
268 function TestCase(testMethod, opt_testMethodName) {
269 if (!testMethod)
270 throw new Error('testMethod must be provided');
271 if (testMethod.name == '' && !opt_testMethodName)
272 throw new Error('testMethod must have a name, ' +
273 'or opt_testMethodName must be provided.');
275 this.testMethod_ = testMethod;
276 this.testMethodName_ = opt_testMethodName || testMethod.name;
277 this.results_ = undefined;
280 function forAllAssertAndEnsureMethodsIn_(prototype, fn) {
281 for (var fieldName in prototype) {
282 if (fieldName.indexOf('assert') != 0 &&
283 fieldName.indexOf('ensure') != 0)
284 continue;
285 var fieldValue = prototype[fieldName];
286 if (typeof fieldValue != 'function')
287 continue;
288 fn(fieldName, fieldValue);
292 TestCase.prototype = {
293 __proto__: Object.prototype,
295 get testName() {
296 return this.testMethodName_;
299 bindGlobals_: function() {
300 forAllAssertAndEnsureMethodsIn_(TestCase.prototype,
301 function(fieldName, fieldValue) {
302 global[fieldName] = fieldValue.bind(this);
306 unbindGlobals_: function() {
307 forAllAssertAndEnsureMethodsIn_(TestCase.prototype,
308 function(fieldName, fieldValue) {
309 delete global[fieldName];
314 * Adds some html content to the currently running test
315 * @param {String} opt_title The title for the output.
316 * @param {HTMLElement} opt_element The element to add. If not added, then.
317 * @return {HTMLElement} The element added, or if !opt_element, the element
318 * created.
320 addHTMLOutput: function(opt_title, opt_element) {
321 return this.results_.addHTMLOutput(opt_title, opt_element);
324 assertTrue: function(a, opt_message) {
325 if (a)
326 return;
327 var message = opt_message || 'Expected true, got ' + a;
328 throw new TestError(message);
331 assertFalse: function(a, opt_message) {
332 if (!a)
333 return;
334 var message = opt_message || 'Expected false, got ' + a;
335 throw new TestError(message);
338 assertUndefined: function(a, opt_message) {
339 if (a === undefined)
340 return;
341 var message = opt_message || 'Expected undefined, got ' + a;
342 throw new TestError(message);
345 assertNotUndefined: function(a, opt_message) {
346 if (a !== undefined)
347 return;
348 var message = opt_message || 'Expected not undefined, got ' + a;
349 throw new TestError(message);
352 assertNull: function(a, opt_message) {
353 if (a === null)
354 return;
355 var message = opt_message || 'Expected null, got ' + a;
356 throw new TestError(message);
359 assertNotNull: function(a, opt_message) {
360 if (a !== null)
361 return;
362 var message = opt_message || 'Expected non-null, got ' + a;
363 throw new TestError(message);
366 assertEquals: function(a, b, opt_message) {
367 if (a == b)
368 return;
369 var message = opt_message || 'Expected ' + a + ', got ' + b;
370 throw new TestError(message);
373 assertNotEquals: function(a, b, opt_message) {
374 if (a != b)
375 return;
376 var message = opt_message || 'Expected something not equal to ' + b;
377 throw new TestError(message);
380 assertArrayEquals: function(a, b, opt_message) {
381 if (a.length == b.length) {
382 var ok = true;
383 for (var i = 0; i < a.length; i++) {
384 ok &= a[i] === b[i];
386 if (ok)
387 return;
390 var message = opt_message || 'Expected array ' + a + ', got array ' + b;
391 throw new TestError(message);
394 assertArrayShallowEquals: function(a, b, opt_message) {
395 if (a.length == b.length) {
396 var ok = true;
397 for (var i = 0; i < a.length; i++) {
398 ok &= a[i] === b[i];
400 if (ok)
401 return;
404 var message = opt_message || 'Expected array ' + b + ', got array ' + a;
405 throw new TestError(message);
408 assertAlmostEquals: function(a, b, opt_message) {
409 if (Math.abs(a - b) < 0.00001)
410 return;
411 var message = opt_message || 'Expected almost ' + a + ', got ' + b;
412 throw new TestError(message);
415 assertThrows: function(fn, opt_message) {
416 try {
417 fn();
418 } catch (e) {
419 return;
421 var message = opt_message || 'Expected throw from ' + fn;
422 throw new TestError(message);
425 setUp: function() {
428 run: function(results) {
429 this.bindGlobals_();
430 try {
431 this.results_ = results;
432 results.willRunTest(this);
434 if (NOCATCH_MODE) {
435 this.setUp();
436 this.testMethod_();
437 this.tearDown();
438 } else {
439 // Set up.
440 try {
441 this.setUp();
442 } catch (e) {
443 results.addError(e);
444 return;
447 // Run.
448 try {
449 this.testMethod_();
450 } catch (e) {
451 results.addError(e);
454 // Tear down.
455 try {
456 this.tearDown();
457 } catch (e) {
458 if (typeof e == 'string')
459 e = new TestError(e);
460 results.addError(e);
463 } finally {
464 this.unbindGlobals_();
465 results.didRunTest(this);
466 this.results_ = undefined;
470 tearDown: function() {
476 * Returns an array of TestCase objects correpsonding to the tests
477 * found in the given object. This considers any functions beginning with test
478 * as a potential test.
480 * @param {object} opt_objectToEnumerate The object to enumerate, or global if
481 * not specified.
482 * @param {RegExp} opt_filter Return only tests that match this regexp.
484 function discoverTests(opt_objectToEnumerate, opt_filter) {
485 var objectToEnumerate = opt_objectToEnumerate || global;
487 var tests = [];
488 for (var testMethodName in objectToEnumerate) {
489 if (testMethodName.search(/^test.+/) != 0)
490 continue;
492 if (opt_filter && testMethodName.search(opt_filter) == -1)
493 continue;
495 var testMethod = objectToEnumerate[testMethodName];
496 if (typeof testMethod != 'function')
497 continue;
498 var testCase = new TestCase(testMethod, testMethodName);
499 tests.push(testCase);
501 tests.sort(function(a, b) {
502 return a.testName < b.testName;
504 return tests;
508 * Runs all unit tests.
510 function runAllTests(opt_objectToEnumerate) {
511 var runner;
512 function init() {
513 if (runner)
514 runner.parentElement.removeChild(runner);
515 runner = new HTMLTestRunner(document.title, document.location.hash);
516 // Stash the runner on global so that the global test runner
517 // can get to it.
518 global.G_testRunner = runner;
521 function append() {
522 document.body.appendChild(runner);
525 function run() {
526 var objectToEnumerate = opt_objectToEnumerate || global;
527 var tests = discoverTests(objectToEnumerate);
528 runner.run(tests);
531 global.addEventListener('hashchange', function() {
532 init();
533 append();
534 run();
537 init();
538 if (document.body)
539 append();
540 else
541 document.addEventListener('DOMContentLoaded', append);
542 global.addEventListener('load', run);
545 if (/_test.html$/.test(document.location.pathname))
546 runAllTests();
548 return {
549 HTMLTestRunner: HTMLTestRunner,
550 TestError: TestError,
551 TestCase: TestCase,
552 discoverTests: discoverTests,
553 runAllTests: runAllTests,
554 createErrorDiv_: createErrorDiv,
555 createTestCaseDiv_: createTestCaseDiv