UNPKG

@qooxdoo/framework

Version:

The JS Framework for Coders

427 lines (367 loc) 12.8 kB
/* ************************************************************************ qooxdoo - the new era of web development http://qooxdoo.org Copyright: 2007-2008 1&1 Internet AG, Germany, http://www.1und1.de License: MIT: https://opensource.org/licenses/MIT See the LICENSE file in the project's top-level directory for details. Authors: * Fabian Jakobs (fjakobs) * Daniel Wagner (d_wagner) ************************************************************************ */ /** * The test result class runs the test functions and fires events depending on * the result of the test run. */ qx.Class.define("qx.dev.unit.TestResult", { extend : qx.core.Object, /* ***************************************************************************** EVENTS ***************************************************************************** */ events : { /** * Fired before the test is started * * Event data: The test {@link qx.dev.unit.TestFunction} */ startTest : "qx.event.type.Data", /** Fired after the test has finished * * Event data: The test {@link qx.dev.unit.TestFunction} */ endTest : "qx.event.type.Data", /** * Fired if the test raised an {@link qx.core.AssertionError} * * Event data: The test {@link qx.dev.unit.TestFunction} */ error : "qx.event.type.Data", /** * Fired if the test failed with a different exception * * Event data: The test {@link qx.dev.unit.TestFunction} */ failure : "qx.event.type.Data", /** * Fired if an asynchronous test sets a timeout * * Event data: The test {@link qx.dev.unit.TestFunction} */ wait : "qx.event.type.Data", /** * Fired if the test was skipped, e.g. because a requirement was not met. * * Event data: The test {@link qx.dev.unit.TestFunction} */ skip : "qx.event.type.Data", /** * Fired if a performance test returned results. * * Event data: The test {@link qx.dev.unit.TestFunction} */ endMeasurement : "qx.event.type.Data" }, /* ***************************************************************************** STATICS ***************************************************************************** */ statics : { /** * Run a test function using a given test result * * @param testResult {qx.dev.unit.TestResult} The test result to use to run the test * @param test {qx.dev.unit.TestSuite|qx.dev.unit.TestFunction} The test * @param testFunction {var} The test function */ run : function(testResult, test, testFunction) { testResult.run(test, testFunction); } }, /* ***************************************************************************** MEMBERS ***************************************************************************** */ members : { _timeout : null, /** * Run the test * * @param test {qx.dev.unit.TestSuite|qx.dev.unit.TestFunction} The test * @param testFunction {Function} The test function * @param self {Object?} The context in which to run the test function * @param resume {Boolean?} Resume a currently waiting test * * @return {var} The return value of the test function */ run : function(test, testFunction, self, resume) { if(!this._timeout) { this._timeout = {}; } var testClass = test.getTestClass(); if (!testClass.hasListener("assertionFailed")) { testClass.addListener("assertionFailed", function(ev) { var error = [{ exception : ev.getData(), test : test }]; this.fireDataEvent("failure", error); }, this); } if (resume && !this._timeout[test.getFullName()]) { this._timeout[test.getFullName()] = "failed"; var qxEx = new qx.type.BaseError("Error in asynchronous test", "resume() called before wait()"); this._createError("failure", [qxEx], test); this.fireDataEvent("endTest", test); return undefined; } this.fireDataEvent("startTest", test); if (qx.core.Environment.get("qx.debug.dispose")) { qx.dev.Debug.startDisposeProfiling(); } if (this._timeout[test.getFullName()]) { if (this._timeout[test.getFullName()] !== "failed") { this._timeout[test.getFullName()].stop(); this._timeout[test.getFullName()].dispose(); } delete this._timeout[test.getFullName()]; } else { try { test.setUp(); } catch(ex) { if (ex instanceof qx.dev.unit.AsyncWrapper) { if (this._timeout[test.getFullName()]) { // Do nothing if there's already a timeout for this test return; } if (ex.getDelay()) { var that = this; var defaultTimeoutFunction = function() { throw new qx.core.AssertionError( "Asynchronous Test Error in setUp", "Timeout reached before resume() was called." ); }; var timeoutFunc = (ex.getDeferredFunction() ? ex.getDeferredFunction() : defaultTimeoutFunction); var context = (ex.getContext() ? ex.getContext() : window); this._timeout[test.getFullName()] = qx.event.Timer.once(function() { this.run(test, timeoutFunc, context); }, that, ex.getDelay()); this.fireDataEvent("wait", test); } return undefined; } else { try { this.tearDown(test); } catch (except) { /* Any exceptions here are likely caused by setUp having failed previously, so we'll ignore them. */ } if (ex.classname == "qx.dev.unit.RequirementError") { this._createError("skip", [ex], test); this.fireDataEvent("endTest", test); } else { if (ex instanceof qx.type.BaseError && ex.message == qx.type.BaseError.DEFAULTMESSAGE) { ex.message = "setUp failed"; } else { ex.message = "setUp failed: " + ex.message; } this._createError("error", [ex], test); this.fireDataEvent("endTest", test); } return undefined; } } } var returnValue; try { returnValue = testFunction.call(self || window); } catch(ex) { var error = true; if (ex instanceof qx.dev.unit.AsyncWrapper) { if (this._timeout[test.getFullName()]) { // Do nothing if there's already a timeout for this test return; } if (ex.getDelay()) { var that = this; var defaultTimeoutFunction = function() { throw new qx.core.AssertionError( "Asynchronous Test Error", "Timeout reached before resume() was called." ); }; var timeoutFunc = (ex.getDeferredFunction() ? ex.getDeferredFunction() : defaultTimeoutFunction); var context = (ex.getContext() ? ex.getContext() : window); this._timeout[test.getFullName()] = qx.event.Timer.once(function() { this.run(test, timeoutFunc, context); }, that, ex.getDelay()); this.fireDataEvent("wait", test); } } else if (ex instanceof qx.dev.unit.MeasurementResult) { error = false; this._createError("endMeasurement", [ex], test); } else { try { this.tearDown(test); } catch(except) {} if (ex.classname == "qx.core.AssertionError") { this._createError("failure", [ex], test); this.fireDataEvent("endTest", test); } else if (ex.classname == "qx.dev.unit.RequirementError") { this._createError("skip", [ex], test); this.fireDataEvent("endTest", test); } else { this._createError("error", [ex], test); this.fireDataEvent("endTest", test); } } } if (!error) { try { this.tearDown(test); this.fireDataEvent("endTest", test); } catch(ex) { if (ex instanceof qx.type.BaseError && ex.message == qx.type.BaseError.DEFAULTMESSAGE) { ex.message = "tearDown failed"; } else { ex.message = "tearDown failed: " + ex.message; } this._createError("error", [ex], test); this.fireDataEvent("endTest", test); } } /* if (!this._timeout[test.getFullName()]) { this.__removeListeners(test.getTestClass()[test.getName()]); } */ return returnValue; }, /** * Fire an error event * * @param eventName {String} Name of the event * @param exceptions {Error[]} The exception(s), which caused the test to fail * @param test {qx.dev.unit.TestSuite|qx.dev.unit.TestFunction} The test */ _createError : function(eventName, exceptions, test) { var errors = []; for (var i=0,l=exceptions.length; i<l; i++) { // WebKit and Opera errors.push({ exception : exceptions[i], test : test }); } this.fireDataEvent(eventName, errors); }, /** * Wraps the AUT's qx.event.Registration.addListener function so that it * stores references to all added listeners in an array attached to the * current test function. This is done so that any listeners left over after * test execution can be removed to make sure they don't influence other * tests. * * @param testFunction {qx.dev.unit.TestFunction} The current test */ __wrapAddListener : function(testFunction) { testFunction._addedListeners = []; if (!qx.event.Registration.addListenerOriginal) { qx.event.Registration.addListenerOriginal = qx.event.Registration.addListener; qx.event.Registration.addListener = function(target, type, listener, self, capture) { var listenerId = qx.event.Registration.addListenerOriginal(target, type, listener, self, capture); var store = true; if ( (target.classname && target.classname.indexOf("testrunner.unit") == 0) || (self && self.classname && self.classname.indexOf("testrunner.unit") == 0) ) { store = false; } if (store) { testFunction._addedListeners.push([target, listenerId]); } return listenerId; }; } }, /** * Removes any listeners left over after a test's run. * * @param testFunction {qx.dev.unit.TestFunction} The current test */ __removeListeners : function(testFunction) { // remove listeners added during test execution if (testFunction._addedListeners) { var listeners = testFunction._addedListeners; for (var i=0,l=listeners.length; i<l; i++) { var target = listeners[i][0]; var id = listeners[i][1]; try { qx.event.Registration.removeListenerById(target, id); } catch(ex) {} } } }, /** * Calls the generic tearDown method on the test class, then the specific * tearDown for the test, if one is defined. * * @param test {Object} The test object (first argument of {@link #run}) */ tearDown : function(test) { test.tearDown(); var testClass = test.getTestClass(); var specificTearDown = "tearDown" + qx.lang.String.firstUp(test.getName()); if (testClass[specificTearDown]) { testClass[specificTearDown](); } testClass.doAutoDispose(); if (qx.core.Environment.get("qx.debug.dispose") && qx.dev.Debug.disposeProfilingActive) { var testName = test.getFullName(); var undisposed = qx.dev.Debug.stopDisposeProfiling(); for (var i=0; i<undisposed.length; i++) { var trace; if (undisposed[i].stackTrace) { trace = undisposed[i].stackTrace.join("\n"); } window.top.qx.log.Logger.warn("Undisposed object in " + testName + ": " + undisposed[i].object.classname + "[" + undisposed[i].object.toHashCode() + "]" + "\n" + trace); } } } }, destruct : function() { this._timeout = null; } });