Make sure the queue runner goes async for async specs

- Even if `done` is called synchronously.

See #1327 #1334 jasmine/gulp-jasmine-browser#48

This is a backport of 578f63b9bd
to 2.6.x.
This commit is contained in:
Steve Gravrock
2017-06-06 15:38:36 -07:00
parent 5d1d19f494
commit a3bc74776a
4 changed files with 106 additions and 60 deletions

View File

@@ -3884,6 +3884,10 @@ getJasmineRequireObj().QueueRunner = function(j$) {
var clearTimeout = function () { var clearTimeout = function () {
Function.prototype.apply.apply(self.timeout.clearTimeout, [j$.getGlobal(), [timeoutId]]); Function.prototype.apply.apply(self.timeout.clearTimeout, [j$.getGlobal(), [timeoutId]]);
}, },
completedSynchronously = true,
setTimeout = function(delayedFn, delay) {
return Function.prototype.apply.apply(self.timeout.setTimeout, [j$.getGlobal(), [delayedFn, delay]]);
},
handleError = function(error) { handleError = function(error) {
onException(error); onException(error);
next(); next();
@@ -3891,7 +3895,13 @@ getJasmineRequireObj().QueueRunner = function(j$) {
next = once(function () { next = once(function () {
clearTimeout(timeoutId); clearTimeout(timeoutId);
self.globalErrors.popListener(handleError); self.globalErrors.popListener(handleError);
self.run(queueableFns, iterativeIndex + 1); if (completedSynchronously) {
setTimeout(function() {
self.run(queueableFns, iterativeIndex + 1);
});
} else {
self.run(queueableFns, iterativeIndex + 1);
}
}), }),
timeoutId; timeoutId;
@@ -3903,15 +3913,16 @@ getJasmineRequireObj().QueueRunner = function(j$) {
self.globalErrors.pushListener(handleError); self.globalErrors.pushListener(handleError);
if (queueableFn.timeout) { if (queueableFn.timeout) {
timeoutId = Function.prototype.apply.apply(self.timeout.setTimeout, [j$.getGlobal(), [function() { timeoutId = setTimeout(function() {
var error = new Error('Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.'); var error = new Error('Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.');
onException(error); onException(error);
next(); next();
}, queueableFn.timeout()]]); }, queueableFn.timeout());
} }
try { try {
queueableFn.fn.call(self.userContext, next); queueableFn.fn.call(self.userContext, next);
completedSynchronously = false;
} catch (e) { } catch (e) {
handleException(e, queueableFn); handleException(e, queueableFn);
next(); next();

View File

@@ -170,6 +170,7 @@ describe("QueueRunner", function() {
queueRunner.execute(); queueRunner.execute();
jasmine.clock().tick();
expect(onComplete).toHaveBeenCalled(); expect(onComplete).toHaveBeenCalled();
expect(onException).toHaveBeenCalled(); expect(onException).toHaveBeenCalled();
@@ -189,6 +190,7 @@ describe("QueueRunner", function() {
queueRunner.execute(); queueRunner.execute();
jasmine.clock().tick(1);
expect(onComplete).toHaveBeenCalled(); expect(onComplete).toHaveBeenCalled();
jasmine.clock().tick(jasmineUnderTest.DEFAULT_TIMEOUT_INTERVAL); jasmine.clock().tick(jasmineUnderTest.DEFAULT_TIMEOUT_INTERVAL);
@@ -197,12 +199,13 @@ describe("QueueRunner", function() {
it("only moves to the next spec the first time you call done", function() { it("only moves to the next spec the first time you call done", function() {
var queueableFn = { fn: function(done) {done(); done();} }, var queueableFn = { fn: function(done) {done(); done();} },
nextQueueableFn = { fn: jasmine.createSpy('nextFn') }; nextQueueableFn = { fn: jasmine.createSpy('nextFn') },
queueRunner = new jasmineUnderTest.QueueRunner({ queueRunner = new jasmineUnderTest.QueueRunner({
queueableFns: [queueableFn, nextQueueableFn] queueableFns: [queueableFn, nextQueueableFn]
}); });
queueRunner.execute(); queueRunner.execute();
jasmine.clock().tick(1);
expect(nextQueueableFn.fn.calls.count()).toEqual(1); expect(nextQueueableFn.fn.calls.count()).toEqual(1);
}); });
@@ -211,10 +214,10 @@ describe("QueueRunner", function() {
setTimeout(done, 1); setTimeout(done, 1);
throw new Error('error!'); throw new Error('error!');
} }, } },
nextQueueableFn = { fn: jasmine.createSpy('nextFn') }; nextQueueableFn = { fn: jasmine.createSpy('nextFn') },
queueRunner = new jasmineUnderTest.QueueRunner({ queueRunner = new jasmineUnderTest.QueueRunner({
queueableFns: [queueableFn, nextQueueableFn] queueableFns: [queueableFn, nextQueueableFn]
}); });
queueRunner.execute(); queueRunner.execute();
jasmine.clock().tick(1); jasmine.clock().tick(1);
@@ -271,6 +274,33 @@ describe("QueueRunner", function() {
expect(onException).toHaveBeenCalledWith(errorWithMessage(/^foo$/)); expect(onException).toHaveBeenCalledWith(errorWithMessage(/^foo$/));
expect(nextQueueableFn.fn).toHaveBeenCalled(); expect(nextQueueableFn.fn).toHaveBeenCalled();
}); });
it("handles exceptions thrown while waiting for the stack to clear", function() {
var queueableFn = { fn: function(done) { done() } },
global = {},
errorListeners = [],
globalErrors = {
pushListener: function(f) { errorListeners.push(f); },
popListener: function() { errorListeners.pop(); }
},
clearStack = jasmine.createSpy('clearStack'),
onException = jasmine.createSpy('onException'),
queueRunner = new jasmineUnderTest.QueueRunner({
queueableFns: [queueableFn],
globalErrors: globalErrors,
clearStack: clearStack,
onException: onException
}),
error = new Error('nope');
queueRunner.execute();
jasmine.clock().tick();
expect(clearStack).toHaveBeenCalled();
expect(errorListeners.length).toEqual(1);
errorListeners[0](error);
clearStack.calls.argsFor(0)[0]();
expect(onException).toHaveBeenCalledWith(error);
});
}); });
it("calls exception handlers when an exception is thrown in a fn", function() { it("calls exception handlers when an exception is thrown in a fn", function() {
@@ -306,40 +336,20 @@ describe("QueueRunner", function() {
it("continues running the functions even after an exception is thrown in an async spec", function() { it("continues running the functions even after an exception is thrown in an async spec", function() {
var queueableFn = { fn: function(done) { throw new Error("error"); } }, var queueableFn = { fn: function(done) { throw new Error("error"); } },
nextQueueableFn = { fn: jasmine.createSpy("nextFunction") }, nextQueueableFn = { fn: jasmine.createSpy("nextFunction") },
timeout = { setTimeout: jasmine.createSpy("setTimeout"),
clearTimeout: jasmine.createSpy("setTimeout")
},
queueRunner = new jasmineUnderTest.QueueRunner({ queueRunner = new jasmineUnderTest.QueueRunner({
queueableFns: [queueableFn, nextQueueableFn] queueableFns: [queueableFn, nextQueueableFn],
timeout: timeout
}); });
queueRunner.execute(); queueRunner.execute();
timeout.setTimeout.calls.argsFor(0)[0]();
expect(nextQueueableFn.fn).toHaveBeenCalled(); expect(nextQueueableFn.fn).toHaveBeenCalled();
}); });
it("handles exceptions thrown while waiting for the stack to clear", function() {
var queueableFn = { fn: function(done) { done() } },
global = {},
errorListeners = [],
globalErrors = {
pushListener: function(f) { errorListeners.push(f); },
popListener: function() { errorListeners.pop(); }
},
clearStack = jasmine.createSpy('clearStack'),
onException = jasmine.createSpy('onException'),
queueRunner = new jasmineUnderTest.QueueRunner({
queueableFns: [queueableFn],
globalErrors: globalErrors,
clearStack: clearStack,
onException: onException
}),
error = new Error('nope');
queueRunner.execute();
expect(clearStack).toHaveBeenCalled();
expect(errorListeners.length).toEqual(1);
errorListeners[0](error);
clearStack.calls.argsFor(0)[0]();
expect(onException).toHaveBeenCalledWith(error);
});
it("calls a provided complete callback when done", function() { it("calls a provided complete callback when done", function() {
var queueableFn = { fn: jasmine.createSpy('fn') }, var queueableFn = { fn: jasmine.createSpy('fn') },
completeCallback = jasmine.createSpy('completeCallback'), completeCallback = jasmine.createSpy('completeCallback'),
@@ -353,23 +363,34 @@ describe("QueueRunner", function() {
expect(completeCallback).toHaveBeenCalled(); expect(completeCallback).toHaveBeenCalled();
}); });
it("calls a provided stack clearing function when done", function() { describe("clearing the stack", function() {
var asyncFn = { fn: function(done) { done() } }, beforeEach(function() {
afterFn = { fn: jasmine.createSpy('afterFn') }, jasmine.clock().install();
completeCallback = jasmine.createSpy('completeCallback'), });
clearStack = jasmine.createSpy('clearStack'),
queueRunner = new jasmineUnderTest.QueueRunner({
queueableFns: [asyncFn, afterFn],
clearStack: clearStack,
onComplete: completeCallback
});
clearStack.and.callFake(function(fn) { fn(); }); afterEach(function() {
jasmine.clock().uninstall();
});
queueRunner.execute(); it("calls a provided stack clearing function when done", function() {
expect(afterFn.fn).toHaveBeenCalled(); var asyncFn = { fn: function(done) { done() } },
expect(clearStack).toHaveBeenCalled(); afterFn = { fn: jasmine.createSpy('afterFn') },
clearStack.calls.argsFor(0)[0](); completeCallback = jasmine.createSpy('completeCallback'),
expect(completeCallback).toHaveBeenCalled(); clearStack = jasmine.createSpy('clearStack'),
queueRunner = new jasmineUnderTest.QueueRunner({
queueableFns: [asyncFn, afterFn],
clearStack: clearStack,
onComplete: completeCallback
});
clearStack.and.callFake(function(fn) { fn(); });
queueRunner.execute();
jasmine.clock().tick();
expect(afterFn.fn).toHaveBeenCalled();
expect(clearStack).toHaveBeenCalled();
clearStack.calls.argsFor(0)[0]();
expect(completeCallback).toHaveBeenCalled();
});
}); });
}); });

View File

@@ -456,7 +456,7 @@ describe("Env integration", function() {
env.execute(); env.execute();
}); });
it("copes with late async failures", function(done) { it("copes with async failures after done has been called", function(done) {
var global = { var global = {
setTimeout: function(fn, delay) { setTimeout(fn, delay) }, setTimeout: function(fn, delay) { setTimeout(fn, delay) },
clearTimeout: function(fn, delay) { clearTimeout(fn, delay) }, clearTimeout: function(fn, delay) { clearTimeout(fn, delay) },
@@ -466,6 +466,7 @@ describe("Env integration", function() {
reporter = jasmine.createSpyObj('fakeReporter', [ "specDone", "jasmineDone", "suiteDone" ]); reporter = jasmine.createSpyObj('fakeReporter', [ "specDone", "jasmineDone", "suiteDone" ]);
reporter.jasmineDone.and.callFake(function() { reporter.jasmineDone.and.callFake(function() {
expect(reporter.specDone).not.toHaveFailedExpecationsForRunnable('A suite fails', ['fail thrown']);
expect(reporter.suiteDone).toHaveFailedExpecationsForRunnable('A suite', ['fail thrown']); expect(reporter.suiteDone).toHaveFailedExpecationsForRunnable('A suite', ['fail thrown']);
done(); done();
}); });
@@ -474,9 +475,11 @@ describe("Env integration", function() {
env.fdescribe('A suite', function() { env.fdescribe('A suite', function() {
env.it('fails', function(specDone) { env.it('fails', function(specDone) {
specDone();
setTimeout(function() { setTimeout(function() {
global.onerror('fail'); specDone();
setTimeout(function() {
global.onerror('fail');
});
}); });
}); });
}); });

View File

@@ -65,6 +65,10 @@ getJasmineRequireObj().QueueRunner = function(j$) {
var clearTimeout = function () { var clearTimeout = function () {
Function.prototype.apply.apply(self.timeout.clearTimeout, [j$.getGlobal(), [timeoutId]]); Function.prototype.apply.apply(self.timeout.clearTimeout, [j$.getGlobal(), [timeoutId]]);
}, },
completedSynchronously = true,
setTimeout = function(delayedFn, delay) {
return Function.prototype.apply.apply(self.timeout.setTimeout, [j$.getGlobal(), [delayedFn, delay]]);
},
handleError = function(error) { handleError = function(error) {
onException(error); onException(error);
next(); next();
@@ -72,7 +76,13 @@ getJasmineRequireObj().QueueRunner = function(j$) {
next = once(function () { next = once(function () {
clearTimeout(timeoutId); clearTimeout(timeoutId);
self.globalErrors.popListener(handleError); self.globalErrors.popListener(handleError);
self.run(queueableFns, iterativeIndex + 1); if (completedSynchronously) {
setTimeout(function() {
self.run(queueableFns, iterativeIndex + 1);
});
} else {
self.run(queueableFns, iterativeIndex + 1);
}
}), }),
timeoutId; timeoutId;
@@ -84,15 +94,16 @@ getJasmineRequireObj().QueueRunner = function(j$) {
self.globalErrors.pushListener(handleError); self.globalErrors.pushListener(handleError);
if (queueableFn.timeout) { if (queueableFn.timeout) {
timeoutId = Function.prototype.apply.apply(self.timeout.setTimeout, [j$.getGlobal(), [function() { timeoutId = setTimeout(function() {
var error = new Error('Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.'); var error = new Error('Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.');
onException(error); onException(error);
next(); next();
}, queueableFn.timeout()]]); }, queueableFn.timeout());
} }
try { try {
queueableFn.fn.call(self.userContext, next); queueableFn.fn.call(self.userContext, next);
completedSynchronously = false;
} catch (e) { } catch (e) {
handleException(e, queueableFn); handleException(e, queueableFn);
next(); next();