diff --git a/spec/core/EnvSpec.js b/spec/core/EnvSpec.js index 9fb5b08e..2d815ecc 100644 --- a/spec/core/EnvSpec.js +++ b/spec/core/EnvSpec.js @@ -291,6 +291,55 @@ describe("Env integration", function() { env.execute(); }); + + describe("with a mock clock", function() { + beforeEach(function() { + jasmine.getEnv().clock.install(); + }); + + afterEach(function() { + jasmine.getEnv().clock.uninstall(); + }); + + it("should not hang on async specs that forget to call done()", function(done) { + var env = new j$.Env(), + reporter = jasmine.createSpyObj('fakeReporter', [ + "jasmineStarted", + "jasmineDone", + "suiteStarted", + "suiteDone", + "specStarted", + "specDone" + ]); + + env.addReporter(reporter); + + env.describe("tests", function() { + env.it("async spec that will hang", function(underTestCallback) { + env.expect(true).toBeTruthy(); + }); + + env.it("after async spec", function() { + env.expect(true).toBeTruthy(); + }); + }); + + env.execute(); + + reporter.jasmineDone.and.callFake(function() { + expect(reporter.jasmineStarted).toHaveBeenCalledWith({ + totalSpecsDefined: 2 + }); + + expect(reporter.specDone).toHaveBeenCalledWith(jasmine.objectContaining({status: 'passed'})); + expect(reporter.specDone).toHaveBeenCalledWith(jasmine.objectContaining({status: 'failed'})); + + done(); + }); + + jasmine.getEnv().clock.tick(60001); + }); + }); // TODO: something is wrong with this spec it("should report as expected", function(done) { diff --git a/spec/core/SpySpec.js b/spec/core/SpySpec.js index c42db911..a77d469c 100644 --- a/spec/core/SpySpec.js +++ b/spec/core/SpySpec.js @@ -15,6 +15,8 @@ describe('Spies', function () { }); it("warns the user that we indend to overwrite an existing property", function() { + TestClass.prototype.someFunction.and = "existing"; + expect(function() { j$.createSpy(TestClass.prototype, TestClass.prototype.someFunction); }).toThrowError("Jasmine spies would overwrite the 'and' and 'calls' properties on the object being spied upon"); @@ -23,8 +25,8 @@ describe('Spies', function () { it("adds a spyStrategy and callTracker to the spy", function() { var spy = j$.createSpy(TestClass.prototype, TestClass.prototype.someFunction); - expect(spy.and).toEqual(jasmine.any(j$.SpyStrategy); - expect(spy.calls).toEqual(jasmine.any(j$.CallTracker); + expect(spy.and).toEqual(jasmine.any(j$.SpyStrategy)); + expect(spy.calls).toEqual(jasmine.any(j$.CallTracker)); }); }); diff --git a/spec/support/dev_boot.js b/spec/support/dev_boot.js index 3a6572d8..cdb3a32e 100644 --- a/spec/support/dev_boot.js +++ b/spec/support/dev_boot.js @@ -6,6 +6,9 @@ var env = jasmine.getEnv(); + SUPER_TIMEOUT = setTimeout; + SUPER_CLEAR_TIMEOUT = clearTimeout; + var jasmineInterface = { describe: function(description, specDefinitions) { return env.describe(description, specDefinitions); diff --git a/src/core/Env.js b/src/core/Env.js index e21ea96f..c36231b0 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -1,13 +1,14 @@ getJasmineRequireObj().Env = function(j$) { function Env(options) { options = options || {}; - + var self = this; var global = options.global || j$.getGlobal(); var catchExceptions = true; var realSetTimeout = j$.getGlobal().setTimeout; + var realClearTimeout = j$.getGlobal().clearTimeout; this.clock = new j$.Clock(global, new j$.DelayedFunctionScheduler()); var spies = []; @@ -120,9 +121,13 @@ getJasmineRequireObj().Env = function(j$) { } } + var asyncSpecTimeout = 60000; + var queueRunnerFactory = function(options) { options.catchException = self.catchException; options.clearStack = options.clearStack || clearStack; + options.realTimer = { setTimeout: realSetTimeout, clearTimeout: realClearTimeout }; + options.asyncSpecTimeout = asyncSpecTimeout; new j$.QueueRunner(options).run(options.fns, 0); }; diff --git a/src/core/QueueRunner.js b/src/core/QueueRunner.js index ac615053..9739ef88 100644 --- a/src/core/QueueRunner.js +++ b/src/core/QueueRunner.js @@ -6,6 +6,11 @@ getJasmineRequireObj().QueueRunner = function() { this.clearStack = attrs.clearStack || function(fn) {fn();}; this.onException = attrs.onException || function() {}; this.catchException = attrs.catchException || function() { return true; }; + + this.timer = attrs.realTimer; + this.asyncSpecTimeout = attrs.asyncSpecTimeout || 60000; + + this.leaf = attrs.leaf || false; } QueueRunner.prototype.execute = function() { @@ -16,16 +21,23 @@ getJasmineRequireObj().QueueRunner = function() { var length = fns.length, self = this, iterativeIndex; + + var nextIteration = function(currentIteration) { + return function() { + self.run(fns, currentIteration + 1); + }; + }; for(iterativeIndex = recursiveIndex; iterativeIndex < length; iterativeIndex++) { var fn = fns[iterativeIndex]; + if (fn.length > 0) { - var attemptSuccessful = attempt(function() { - fn.call(self, function() { self.run(fns, iterativeIndex + 1); }); - }); + var attemptSuccessful = attempt(fn, nextIteration(iterativeIndex)); if(attemptSuccessful) { return; + } else { + // TODO cleanup the timeout ? } } else { attempt(function() { fn.call(self); }); @@ -38,9 +50,24 @@ getJasmineRequireObj().QueueRunner = function() { this.clearStack(this.onComplete); } - function attempt(fn) { + function attempt(fn, done) { + var timeout; + try { - fn(); + if (self.leaf) { + timeout = self.timer.setTimeout(function() { + self.onException(new Error("timeout")); + done(); + }, self.asyncSpecTimeout); + } + + var next = function() { + if (self.leaf) { self.timer.clearTimeout(timeout); } + done(); + }; + + fn.call(self, next); + return true; } catch (e) { self.onException(e); diff --git a/src/core/Spec.js b/src/core/Spec.js index 6f051a68..e63f2c17 100644 --- a/src/core/Spec.js +++ b/src/core/Spec.js @@ -57,6 +57,7 @@ getJasmineRequireObj().Spec = function() { this.queueRunner({ fns: allFns, + leaf: true, onException: function(e) { if (Spec.isPendingSpecException(e)) { self.pend();