// Copyright (c) 2013 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. 'use strict'; base.requireStylesheet('base.unittest'); base.require('base.settings'); base.require('base.unittest.test_error'); base.require('base.unittest.assertions'); base.exportTo('base.unittest', function() { var TestResults = { FAILED: 0, PASSED: 1, PENDING: 2 }; var showCondensed_ = false; function showCondensed(val) { showCondensed_ = val; } function logWarningMessage(message) { var messagesEl = document.querySelector('#messages'); messagesEl.setAttribute('hasMessages', true); var li = document.createElement('li'); li.innerText = message; var list = document.querySelector('#message-list'); list.appendChild(li); } function TestRunner(suitePaths, tests) { this.suitePaths_ = suitePaths || []; this.suites_ = []; this.suiteNames_ = {}; this.tests_ = tests || []; this.moduleCount_ = 0; this.stats_ = { tests: 0, failures: 0, exceptions: [], duration: 0.0 }; } TestRunner.prototype = { __proto__: Object.prototype, loadSuites: function() { this.loadSuiteFiles(); }, run: function() { this.clear_(document.querySelector('#test-results')); this.clear_(document.querySelector('#exception-list')); this.clear_(document.querySelector('#message-list')); this.updateStats_(); this.runSuites_(); }, addSuite: function(suite) { if (this.suiteNames_[suite.name] === true) logWarningMessage('Duplicate test suite name detected: ' + suite.name); this.suites_.push(suite); this.suiteNames_[suite.name] = true; // This assumes one test suite per file. if (this.suites_.length === this.suitePaths_.length) this.run(); }, loadSuiteFiles: function() { var modules = []; this.suitePaths_.forEach(function(path) { var moduleName = path.slice(5, path.length - 3); moduleName = moduleName.replace(/\//g, '.'); modules.push(moduleName); }); base.require(modules); }, clear_: function(el) { while (el.firstChild) el.removeChild(el.firstChild); }, runSuites_: function(opt_idx) { var idx = opt_idx || 0; var suiteCount = this.suites_.length; if (idx >= suiteCount) { var harness = document.querySelector('#test-results'); harness.appendChild(document.createElement('br')); harness.appendChild(document.createTextNode('Test Run Complete')); return; } var suite = this.suites_[idx]; suite.showLongResults = (suiteCount === 1); suite.displayInfo(); suite.runTests(this.tests_); this.stats_.duration += suite.duration; this.stats_.tests += suite.testCount; this.stats_.failures += suite.failureCount; this.updateStats_(); // Give the view time to update. window.setTimeout(function() { this.runSuites_(idx + 1); }.bind(this), 1); }, onAnimationFrameError: function(e, opt_stack) { if (e.message) console.error(e.message, e.stack); else console.error(e); var exception = {e: e, stack: opt_stack}; this.stats_.exceptions.push(exception); this.appendException(exception); this.updateStats_(); }, updateStats_: function() { var statEl = document.querySelector('#stats'); statEl.innerHTML = this.suites_.length + ' suites, ' + '<span class="passed">' + this.stats_.tests + '</span> tests, ' + '<span class="failed">' + this.stats_.failures + '</span> failures, ' + '<span class="exception">' + this.stats_.exceptions.length + '</span> exceptions,' + ' in ' + this.stats_.duration + 'ms.'; }, appendException: function(exc) { var exceptionsEl = document.querySelector('#exceptions'); exceptionsEl.setAttribute('hasExceptions', this.stats_.exceptions.length); var excEl = document.createElement('li'); excEl.innerHTML = exc.e + '<pre>' + exc.stack + '</pre>'; var exceptionsEl = document.querySelector('#exception-list'); exceptionsEl.appendChild(excEl); } }; function TestSuite(name, suite) { this.name_ = name; this.tests_ = []; this.testNames_ = {}; this.failures_ = []; this.results_ = TestResults.PENDING; this.showLongResults = false; this.duration_ = 0.0; this.resultsEl_ = undefined; global.setup = function(fn) { this.setupFn_ = fn; }.bind(this); global.teardown = function(fn) { this.teardownFn_ = fn; }.bind(this); global.test = function(name, test) { if (this.testNames_[name] === true) logWarningMessage('Duplicate test name detected: ' + name); this.tests_.push(new Test(name, test)); this.testNames_[name] = true; }.bind(this); suite.call(); global.setup = undefined; global.teardown = undefined; global.test = undefined; } TestSuite.prototype = { __proto__: Object.prototype, get name() { return this.name_; }, get results() { return this.results_; }, get testCount() { return this.tests_.length; }, get failureCount() { return this.failures.length; }, get failures() { return this.failures_; }, get duration() { return this.duration_; }, displayInfo: function() { this.resultsEl_ = document.createElement('div'); this.resultsEl_.className = 'test-result'; var resultsPanel = document.querySelector('#test-results'); resultsPanel.appendChild(this.resultsEl_); if (this.showLongResults) { this.resultsEl_.innerText = this.name; } else { var link = '/src/tests.html?suite='; link += this.name.replace(/\./g, '/'); var suiteInfo = document.createElement('a'); suiteInfo.href = link; suiteInfo.innerText = this.name; this.resultsEl_.appendChild(suiteInfo); } var statusEl = document.createElement('span'); statusEl.classList.add('results'); statusEl.classList.add('pending'); statusEl.innerText = 'pending'; this.resultsEl_.appendChild(statusEl); }, runTests: function(testsToRun) { this.testsToRun_ = testsToRun; var start = new Date().getTime(); this.results_ = TestResults.PENDING; this.tests_.forEach(function(test) { if (this.testsToRun_.length !== 0 && this.testsToRun_.indexOf(test.name) === -1) return; // Clear settings storage before each test. global.sessionStorage.clear(); base.Settings.setAlternativeStorageInstance(global.sessionStorage); base.onAnimationFrameError = testRunner.onAnimationFrameError.bind(testRunner); if (this.setupFn_ !== undefined) this.setupFn_.bind(test).call(); var testWorkAreaEl_ = document.createElement('div'); this.resultsEl_.appendChild(testWorkAreaEl_); test.run(testWorkAreaEl_); this.resultsEl_.removeChild(testWorkAreaEl_); if (this.teardownFn_ !== undefined) this.teardownFn_.bind(test).call(); if (test.result === TestResults.FAILED) { this.failures_.push({ error: test.failure, test: test.name }); this.results_ = TestResults.FAILED; } }, this); if (this.results_ === TestResults.PENDING) this.results_ = TestResults.PASSED; this.duration_ = new Date().getTime() - start; this.outputResults(); }, outputResults: function() { if ((this.results === TestResults.PASSED) && showCondensed_ && !this.showLongResults) { var parent = this.resultsEl_.parentNode; parent.removeChild(this.resultsEl_); this.resultsEl_ = undefined; parent.appendChild(document.createTextNode('.')); return; } var status = this.resultsEl_.querySelector('.results'); status.classList.remove('pending'); if (this.results === TestResults.PASSED) { status.innerText = 'passed'; status.classList.add('passed'); } else { status.innerText = 'FAILED'; status.classList.add('failed'); } status.innerText += ' (' + this.duration_ + 'ms)'; var child = this.showLongResults ? this.outputLongResults() : this.outputShortResults(); if (child !== undefined) this.resultsEl_.appendChild(child); }, outputShortResults: function() { if (this.results === TestResults.PASSED) return undefined; var parent = document.createElement('div'); var failureList = this.failures; for (var i = 0; i < failureList.length; ++i) { var fail = failureList[i]; var preEl = document.createElement('pre'); preEl.className = 'failure'; preEl.innerText = 'Test: ' + fail.test + '\n' + fail.error.stack; parent.appendChild(preEl); } return parent; }, outputLongResults: function() { var parent = document.createElement('div'); this.tests_.forEach(function(test) { if (this.testsToRun_.length !== 0 && this.testsToRun_.indexOf(test.name) === -1) return; var testEl = document.createElement('div'); testEl.className = 'individual-result'; var link = '/src/tests.html?suite='; link += this.name.replace(/\./g, '/'); link += '&test=' + test.name.replace(/\./g, '/'); var suiteInfo = document.createElement('a'); suiteInfo.href = link; suiteInfo.innerText = test.name; testEl.appendChild(suiteInfo); parent.appendChild(testEl); var resultEl = document.createElement('span'); resultEl.classList.add('results'); testEl.appendChild(resultEl); if (test.result === TestResults.PASSED) { resultEl.classList.add('passed'); resultEl.innerText = 'passed'; } else { resultEl.classList.add('failed'); resultEl.innerText = 'FAILED'; var preEl = document.createElement('pre'); preEl.className = 'failure'; preEl.innerText = test.failure.stack; testEl.appendChild(preEl); } if (test.hasAppendedContent) testEl.appendChild(test.appendedContent); }.bind(this)); return parent; }, toString: function() { return this.name_; } }; function Test(name, test) { this.name_ = name; this.test_ = test; this.result_ = TestResults.FAILED; this.failure_ = undefined; this.appendedContent_ = undefined; } Test.prototype = { __proto__: Object.prototype, run: function(workArea) { this.testWorkArea_ = workArea; try { this.test_.bind(this).call(); this.result_ = TestResults.PASSED; } catch (e) { console.error(e, e.stack); this.failure_ = e; } }, get failure() { return this.failure_; }, get name() { return this.name_; }, get result() { return this.result_; }, get hasAppendedContent() { return (this.appendedContent_ !== undefined); }, get appendedContent() { return this.appendedContent_; }, addHTMLOutput: function(element) { this.testWorkArea_.appendChild(element); this.appendedContent_ = element; }, toString: function() { return this.name_; } }; var testRunner; function testSuite(name, suite) { testRunner.addSuite(new TestSuite(name, suite)); } function Suites(suitePaths, tests) { testRunner = new TestRunner(suitePaths, tests); testRunner.loadSuites(); } function runSuites() { testRunner.run(); } return { showCondensed: showCondensed, testSuite: testSuite, runSuites: runSuites, Suites: Suites }; });