Correctly route errors that occur while a QueueRunner is clearing stack

Besides surfacing the error in the hopefully-correct place, this also
prevents the queue runners for sibling suites from interleaving, which
in turn prevents all kinds of internal state corruption.

Signed-off-by: Gregg Van Hove <gvanhove@pivotal.io>
This commit is contained in:
Steve Gravrock
2017-05-09 14:44:53 -07:00
committed by Gregg Van Hove
parent 2835ca3cce
commit b1e97cfb09
4 changed files with 75 additions and 3 deletions

View File

@@ -252,7 +252,7 @@ describe("QueueRunner", function() {
nextQueueableFn.fn.and.callFake(function() {
// should remove the same function that was added
expect(globalErrors.popListener).toHaveBeenCalledWith(globalErrors.pushListener.calls.argsFor(0)[0]);
expect(globalErrors.popListener).toHaveBeenCalledWith(globalErrors.pushListener.calls.argsFor(1)[0]);
});
queueRunner.execute();
@@ -314,6 +314,32 @@ describe("QueueRunner", function() {
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() {
var queueableFn = { fn: jasmine.createSpy('fn') },
completeCallback = jasmine.createSpy('completeCallback'),
@@ -342,6 +368,8 @@ describe("QueueRunner", function() {
queueRunner.execute();
expect(afterFn.fn).toHaveBeenCalled();
expect(clearStack).toHaveBeenCalledWith(completeCallback);
expect(clearStack).toHaveBeenCalled();
clearStack.calls.argsFor(0)[0]();
expect(completeCallback).toHaveBeenCalled();
});
});

View File

@@ -456,6 +456,38 @@ describe("Env integration", function() {
env.execute();
});
it("copes with late async failures", function(done) {
var global = {
setTimeout: function(fn, delay) { setTimeout(fn, delay) },
clearTimeout: function(fn, delay) { clearTimeout(fn, delay) },
};
spyOn(jasmineUnderTest, 'getGlobal').and.returnValue(global);
var env = new jasmineUnderTest.Env(),
reporter = jasmine.createSpyObj('fakeReporter', [ "specDone", "jasmineDone", "suiteDone" ]);
reporter.jasmineDone.and.callFake(function() {
expect(reporter.suiteDone).toHaveFailedExpecationsForRunnable('A suite', ['fail thrown']);
done();
});
env.addReporter(reporter);
env.fdescribe('A suite', function() {
env.it('fails', function(specDone) {
specDone();
setTimeout(function() {
global.onerror('fail');
});
});
});
env.describe('Ignored', function() {
env.it('is not run', function() {});
});
env.execute();
});
describe('suiteDone reporting', function(){
it("reports when an afterAll fails an expectation", function(done) {
var env = new jasmineUnderTest.Env(),

View File

@@ -238,6 +238,10 @@ getJasmineRequireObj().Env = function(j$) {
reporter.suiteStarted(suite.result);
},
nodeComplete: function(suite, result) {
if (suite !== currentSuite()) {
throw new Error('Tried to complete the wrong suite');
}
if (!suite.markedPending) {
clearResourcesForRunnable(suite.id);
}

View File

@@ -24,6 +24,11 @@ getJasmineRequireObj().QueueRunner = function(j$) {
}
QueueRunner.prototype.execute = function() {
var self = this;
this.handleFinalError = function(error) {
self.onException(error);
};
this.globalErrors.pushListener(this.handleFinalError);
this.run(this.queueableFns, 0);
};
@@ -43,7 +48,10 @@ getJasmineRequireObj().QueueRunner = function(j$) {
}
}
this.clearStack(this.onComplete);
this.clearStack(function() {
self.globalErrors.popListener(self.handleFinalError);
self.onComplete();
});
function attemptSync(queueableFn) {
try {