From 775e2ff0a9c8336e015e13fa7fd15693abf9d560 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Daoust?= Date: Thu, 7 Nov 2013 14:38:30 +0100 Subject: [PATCH 1/2] Clears timeout timer even when async spec throws an exception When an async spec throws a (sync) exception for some reason, the exception was correctly caught and reported by Jasmine but the timeout timer continued to run in the background. For instance, running the (rather stupid) example below would report the exception immediately but would also make the process loop for 5s (and report the exception depending on the reported being used). describe('exception', function () { it('is caught but timer continues to run', function (done) { throw new Error('Oh no!'); }); }); This happened because the timout timer is set in Spec while the "try... catch" block is in the queue runner. The "callDone" function of "timeoutable" that resets the timer was thus not called. The commit simply introduces a "try... catch" block within the `timeoutable` function to ensure that "callDone" gets called even when an exception is thrown. --- src/core/Spec.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/core/Spec.js b/src/core/Spec.js index 65ba928b..8c51b946 100644 --- a/src/core/Spec.js +++ b/src/core/Spec.js @@ -62,7 +62,13 @@ getJasmineRequireObj().Spec = function(j$) { done(); }; - fn.call(this, callDone); //TODO: do we care about more than 1 arg? + try { + fn.call(this, callDone); //TODO: do we care about more than 1 arg? + } + catch (e) { + onException(e); + callDone(); + } }; } From 4a7b79ad0df41ce8da61318bde092c8d058b5975 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Daoust?= Date: Thu, 7 Nov 2013 16:08:41 +0100 Subject: [PATCH 2/2] Regression spec added for timeout timer in an async spec The spec ensures that the timeout timer is properly cleared out even when the async spec throws an exception. --- spec/core/SpecSpec.js | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/spec/core/SpecSpec.js b/spec/core/SpecSpec.js index d82aff05..54ea330f 100644 --- a/spec/core/SpecSpec.js +++ b/spec/core/SpecSpec.js @@ -218,6 +218,44 @@ describe("Spec", function() { expect(specNameSpy.calls.mostRecent().args[0].id).toEqual(spec.id); }); + it("resets the timeout timer when an async spec throws an exception", function(done) { + var handleException = jasmine.createSpy('exception handler'), + spec = new j$.Spec({ + fn: function(done) { throw new Error('test'); }, + catchExceptions: function() { return true; }, + expectationResultFactory: handleException, + timer: { + // Force timeout to 0 to postpone execution to next tick + // without using the default 5s interval + setTimeout: function (fn) { return setTimeout(fn, 0); }, + clearTimeout: clearTimeout + }, + queueRunner: function (attrs) { + // Fake the "run" method of a regular queue runner + // for an async spec. + try { + attrs.fns[0].call({}, function () {}); + } catch (e) { + handleException(e); + } + } + }); + + // Spec execution will create the timeout timer that would report + // a failure on next tick unless it gets properly cleared before + // the end of this tick. + spec.execute(); + + // Run the expect clause on next tick to detect the case when the + // timeout timer was not properly reset. The exception handler is + // called once when the error is thrown. It is called twice when + // the timeout timer is not properly reset. + setTimeout(function () { + expect(handleException.calls.count()).toEqual(1); + done(); + }, 0); + }); + describe("when a spec is marked pending during execution", function() { it("should mark the spec as pending", function() { var fakeQueueRunner = function(opts) {