Compare commits

..

9 Commits

Author SHA1 Message Date
Gregg Van Hove
ca5b1de2eb bump version to 2.6.3 2017-06-07 13:41:22 -07:00
Steve Gravrock
a3bc74776a 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.
2017-06-06 15:49:37 -07:00
Gregg Van Hove
5d1d19f494 Bump version to 2.6.2 2017-05-17 17:24:16 -07:00
Steve Gravrock
4f49288f31 Built distribution
- Fixes #1327
- Fixes jasmine/gulp-jasmine-browser#48
2017-05-16 14:36:35 -07:00
Steve Gravrock
b9adc76dc7 Clear the stack if onmessage is called before the previous invocation finishes 2017-05-16 14:08:58 -07:00
Gregg Van Hove
5ac3e21abb Merge branch 'interleaved-suites' of https://github.com/sgravrock/jasmine into sgravrock-interleaved-suites
- Merges #1352 from @sgravrock
- Fixes #1344
- Fixes #1349
2017-05-10 13:49:05 -07:00
Steve Gravrock
b1e97cfb09 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>
2017-05-09 15:01:18 -07:00
Gregg Van Hove
8e5823c0d2 Merge branch 'sgravrock-global-errors-rethrow'
- Merges #1347 from @sgravrock
2017-05-08 14:15:34 -07:00
Steve Gravrock
10f1220e55 Don't mask errors that occur when no handlers are installed
It's possible for async code to cause an error when Jasmine
doesn't have any listeners registered internally. This causes
Jasmine to crash (Node) or log to the console (browser)
because of trying to call the nonexistent handler. This change
doesn't fix the overall problem but it does ensure that the
original error is logged rather than Jasmine's internal error.
2017-05-08 11:09:32 -07:00
13 changed files with 278 additions and 39 deletions

View File

@@ -854,6 +854,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);
}
@@ -1601,11 +1605,22 @@ getJasmineRequireObj().clearStack = function(j$) {
head = {},
tail = head;
var taskRunning = false;
channel.port1.onmessage = function() {
head = head.next;
var task = head.task;
delete head.task;
task();
if (taskRunning) {
global.setTimeout(task, 0);
} else {
try {
taskRunning = true;
task();
} finally {
taskRunning = false;
}
}
};
return function clearStack(fn) {
@@ -2168,7 +2183,12 @@ getJasmineRequireObj().GlobalErrors = function(j$) {
var onerror = function onerror() {
var handler = handlers[handlers.length - 1];
handler.apply(null, Array.prototype.slice.call(arguments, 0));
if (handler) {
handler.apply(null, Array.prototype.slice.call(arguments, 0));
} else {
throw arguments[0];
}
};
this.uninstall = function noop() {};
@@ -3823,6 +3843,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);
};
@@ -3842,7 +3867,10 @@ getJasmineRequireObj().QueueRunner = function(j$) {
}
}
this.clearStack(this.onComplete);
this.clearStack(function() {
self.globalErrors.popListener(self.handleFinalError);
self.onComplete();
});
function attemptSync(queueableFn) {
try {
@@ -3856,6 +3884,10 @@ getJasmineRequireObj().QueueRunner = function(j$) {
var clearTimeout = function () {
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) {
onException(error);
next();
@@ -3863,7 +3895,13 @@ getJasmineRequireObj().QueueRunner = function(j$) {
next = once(function () {
clearTimeout(timeoutId);
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;
@@ -3875,15 +3913,16 @@ getJasmineRequireObj().QueueRunner = function(j$) {
self.globalErrors.pushListener(handleError);
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.');
onException(error);
next();
}, queueableFn.timeout()]]);
}, queueableFn.timeout());
}
try {
queueableFn.fn.call(self.userContext, next);
completedSynchronously = false;
} catch (e) {
handleException(e, queueableFn);
next();
@@ -4937,5 +4976,5 @@ getJasmineRequireObj().TreeProcessor = function() {
};
getJasmineRequireObj().version = function() {
return '2.6.1';
return '2.6.3';
};

View File

@@ -4,6 +4,6 @@
#
module Jasmine
module Core
VERSION = "2.6.1"
VERSION = "2.6.3"
end
end

View File

@@ -1,7 +1,7 @@
{
"name": "jasmine-core",
"license": "MIT",
"version": "2.6.1",
"version": "2.6.3",
"repository": {
"type": "git",
"url": "https://github.com/jasmine/jasmine.git"

23
release_notes/2.6.2.md Normal file
View File

@@ -0,0 +1,23 @@
# Jasmine 2.6.2 Release Notes
## Summary
This is a patch release to fix some regressions and performance problems in the 2.6.0 release
## Changes
* Clear the stack if onmessage is called before the previous invocation finishes
- Fixes #1327
- Fixes jasmine/gulp-jasmine-browser#48
* Correctly route errors that occur while a QueueRunner is clearing stack
- Merges #1352 from @sgravrock
- Fixes #1344
- Fixes #1349
* Don't mask errors that occur when no handlers are installed
- Merges #1347 from @sgravrock
------
_Release Notes generated with _[Anchorman](http://github.com/infews/anchorman)_

17
release_notes/2.6.3.md Normal file
View File

@@ -0,0 +1,17 @@
# Jasmine 2.6.3 Release Notes
## Summary
This is a patch release to fix some regressions and performance problems in the 2.6.0 release
## Changes
* Make sure the queue runner goes async for async specs
- Fixes [#1327](https://github.com/jasmine/jasmine/issues/1327)
- Fixes [#1334](https://github.com/jasmine/jasmine/issues/1334)
- Fixes [jasmine/gulp-jasmine-browser#48](https://github.com/jasmine/gulp-jasmine-browser/issues/48)
------
_Release Notes generated with _[Anchorman](http://github.com/infews/anchorman)_

View File

@@ -37,6 +37,27 @@ describe("ClearStack", function() {
expect(called).toBe(true);
});
it("calls setTimeout when onmessage is called recursively", function() {
var fakeChannel = {
port1: {},
port2: { postMessage: function() { fakeChannel.port1.onmessage(); } }
},
setTimeout = jasmine.createSpy('setTimeout'),
global = {
MessageChannel: function() { return fakeChannel; },
setTimeout: setTimeout,
},
clearStack = jasmineUnderTest.getClearStack(global),
fn = jasmine.createSpy("second clearStack function");
clearStack(function() {
clearStack(fn);
});
expect(fn).not.toHaveBeenCalled();
expect(setTimeout).toHaveBeenCalledWith(fn, 0);
});
it("falls back to setTimeout", function() {
var setTimeout = jasmine.createSpy('setTimeout').and.callFake(function(fn) { fn() }),
global = { setTimeout: setTimeout },

View File

@@ -62,6 +62,22 @@ describe("GlobalErrors", function() {
expect(fakeGlobal.onerror).toBe(originalCallback);
});
it("rethrows the original error when there is no handler", function() {
var fakeGlobal = { },
errors = new jasmineUnderTest.GlobalErrors(fakeGlobal),
originalError = new Error('nope');
errors.install();
try {
fakeGlobal.onerror(originalError);
} catch (e) {
expect(e).toBe(originalError);
}
errors.uninstall();
});
it("works in node.js", function() {
var fakeGlobal = {
process: {

View File

@@ -170,6 +170,7 @@ describe("QueueRunner", function() {
queueRunner.execute();
jasmine.clock().tick();
expect(onComplete).toHaveBeenCalled();
expect(onException).toHaveBeenCalled();
@@ -189,6 +190,7 @@ describe("QueueRunner", function() {
queueRunner.execute();
jasmine.clock().tick(1);
expect(onComplete).toHaveBeenCalled();
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() {
var queueableFn = { fn: function(done) {done(); done();} },
nextQueueableFn = { fn: jasmine.createSpy('nextFn') };
queueRunner = new jasmineUnderTest.QueueRunner({
queueableFns: [queueableFn, nextQueueableFn]
});
nextQueueableFn = { fn: jasmine.createSpy('nextFn') },
queueRunner = new jasmineUnderTest.QueueRunner({
queueableFns: [queueableFn, nextQueueableFn]
});
queueRunner.execute();
jasmine.clock().tick(1);
expect(nextQueueableFn.fn.calls.count()).toEqual(1);
});
@@ -211,10 +214,10 @@ describe("QueueRunner", function() {
setTimeout(done, 1);
throw new Error('error!');
} },
nextQueueableFn = { fn: jasmine.createSpy('nextFn') };
queueRunner = new jasmineUnderTest.QueueRunner({
queueableFns: [queueableFn, nextQueueableFn]
});
nextQueueableFn = { fn: jasmine.createSpy('nextFn') },
queueRunner = new jasmineUnderTest.QueueRunner({
queueableFns: [queueableFn, nextQueueableFn]
});
queueRunner.execute();
jasmine.clock().tick(1);
@@ -252,7 +255,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();
@@ -271,6 +274,33 @@ describe("QueueRunner", function() {
expect(onException).toHaveBeenCalledWith(errorWithMessage(/^foo$/));
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() {
@@ -306,11 +336,17 @@ describe("QueueRunner", 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"); } },
nextQueueableFn = { fn: jasmine.createSpy("nextFunction") },
timeout = { setTimeout: jasmine.createSpy("setTimeout"),
clearTimeout: jasmine.createSpy("setTimeout")
},
queueRunner = new jasmineUnderTest.QueueRunner({
queueableFns: [queueableFn, nextQueueableFn]
queueableFns: [queueableFn, nextQueueableFn],
timeout: timeout
});
queueRunner.execute();
timeout.setTimeout.calls.argsFor(0)[0]();
expect(nextQueueableFn.fn).toHaveBeenCalled();
});
@@ -327,21 +363,34 @@ describe("QueueRunner", function() {
expect(completeCallback).toHaveBeenCalled();
});
it("calls a provided stack clearing function when done", function() {
var asyncFn = { fn: function(done) { done() } },
afterFn = { fn: jasmine.createSpy('afterFn') },
completeCallback = jasmine.createSpy('completeCallback'),
clearStack = jasmine.createSpy('clearStack'),
queueRunner = new jasmineUnderTest.QueueRunner({
queueableFns: [asyncFn, afterFn],
clearStack: clearStack,
onComplete: completeCallback
});
describe("clearing the stack", function() {
beforeEach(function() {
jasmine.clock().install();
});
clearStack.and.callFake(function(fn) { fn(); });
afterEach(function() {
jasmine.clock().uninstall();
});
queueRunner.execute();
expect(afterFn.fn).toHaveBeenCalled();
expect(clearStack).toHaveBeenCalledWith(completeCallback);
it("calls a provided stack clearing function when done", function() {
var asyncFn = { fn: function(done) { done() } },
afterFn = { fn: jasmine.createSpy('afterFn') },
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(); });
queueRunner.execute();
jasmine.clock().tick();
expect(afterFn.fn).toHaveBeenCalled();
expect(clearStack).toHaveBeenCalled();
clearStack.calls.argsFor(0)[0]();
expect(completeCallback).toHaveBeenCalled();
});
});
});

View File

@@ -456,6 +456,41 @@ describe("Env integration", function() {
env.execute();
});
it("copes with async failures after done has been called", 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.specDone).not.toHaveFailedExpecationsForRunnable('A suite fails', ['fail thrown']);
expect(reporter.suiteDone).toHaveFailedExpecationsForRunnable('A suite', ['fail thrown']);
done();
});
env.addReporter(reporter);
env.fdescribe('A suite', function() {
env.it('fails', function(specDone) {
setTimeout(function() {
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

@@ -4,11 +4,22 @@ getJasmineRequireObj().clearStack = function(j$) {
head = {},
tail = head;
var taskRunning = false;
channel.port1.onmessage = function() {
head = head.next;
var task = head.task;
delete head.task;
task();
if (taskRunning) {
global.setTimeout(task, 0);
} else {
try {
taskRunning = true;
task();
} finally {
taskRunning = false;
}
}
};
return function clearStack(fn) {

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

@@ -5,7 +5,12 @@ getJasmineRequireObj().GlobalErrors = function(j$) {
var onerror = function onerror() {
var handler = handlers[handlers.length - 1];
handler.apply(null, Array.prototype.slice.call(arguments, 0));
if (handler) {
handler.apply(null, Array.prototype.slice.call(arguments, 0));
} else {
throw arguments[0];
}
};
this.uninstall = function noop() {};

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 {
@@ -57,6 +65,10 @@ getJasmineRequireObj().QueueRunner = function(j$) {
var clearTimeout = function () {
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) {
onException(error);
next();
@@ -64,7 +76,13 @@ getJasmineRequireObj().QueueRunner = function(j$) {
next = once(function () {
clearTimeout(timeoutId);
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;
@@ -76,15 +94,16 @@ getJasmineRequireObj().QueueRunner = function(j$) {
self.globalErrors.pushListener(handleError);
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.');
onException(error);
next();
}, queueableFn.timeout()]]);
}, queueableFn.timeout());
}
try {
queueableFn.fn.call(self.userContext, next);
completedSynchronously = false;
} catch (e) {
handleException(e, queueableFn);
next();