Compare commits

...

6 Commits

Author SHA1 Message Date
Gregg Van Hove
02c18a3596 Bump version to 2.6.4 2017-06-16 13:41:59 -07:00
Steve Gravrock
0c6397d802 Don't setTimeout() every time the stack is cleared via MessageChannel() 2017-06-16 13:37:07 -07:00
Gregg Van Hove
eb4671452e Don't use window 2017-06-15 14:33:23 -07:00
Gregg Van Hove
b38decf050 Break into a setTimeout every once in a while
- Allows the CPU to run other things that used the real `setTimeout`

- Fixes #1327
- See #1334
- Fixes jasmine/gulp-jasmine-browser#48
2017-06-15 14:21:33 -07:00
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
10 changed files with 276 additions and 86 deletions

View File

@@ -1600,7 +1600,9 @@ getJasmineRequireObj().CallTracker = function(j$) {
};
getJasmineRequireObj().clearStack = function(j$) {
function messageChannelImpl(global) {
var maxInlineCallCount = 10;
function messageChannelImpl(global, setTimeout) {
var channel = new global.MessageChannel(),
head = {},
tail = head;
@@ -1623,25 +1625,44 @@ getJasmineRequireObj().clearStack = function(j$) {
}
};
var currentCallCount = 0;
return function clearStack(fn) {
tail = tail.next = { task: fn };
channel.port2.postMessage(0);
currentCallCount++;
if (currentCallCount < maxInlineCallCount) {
tail = tail.next = { task: fn };
channel.port2.postMessage(0);
} else {
currentCallCount = 0;
setTimeout(fn);
}
};
}
function getClearStack(global) {
var currentCallCount = 0;
var realSetTimeout = global.setTimeout;
var setTimeoutImpl = function clearStack(fn) {
Function.prototype.apply.apply(realSetTimeout, [global, [fn, 0]]);
};
if (j$.isFunction_(global.setImmediate)) {
var realSetImmediate = global.setImmediate;
return function(fn) {
realSetImmediate(fn);
currentCallCount++;
if (currentCallCount < maxInlineCallCount) {
realSetImmediate(fn);
} else {
currentCallCount = 0;
setTimeoutImpl(fn);
}
};
} else if (!j$.util.isUndefined(global.MessageChannel)) {
return messageChannelImpl(global);
return messageChannelImpl(global, setTimeoutImpl);
} else {
var realSetTimeout = global.setTimeout;
return function clearStack(fn) {
Function.prototype.apply.apply(realSetTimeout, [global, [fn, 0]]);
};
return setTimeoutImpl;
}
}
@@ -3884,6 +3905,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();
@@ -3891,7 +3916,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;
@@ -3903,15 +3934,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();
@@ -4965,5 +4997,5 @@ getJasmineRequireObj().TreeProcessor = function() {
};
getJasmineRequireObj().version = function() {
return '2.6.2';
return '2.6.4';
};

View File

@@ -4,6 +4,6 @@
#
module Jasmine
module Core
VERSION = "2.6.2"
VERSION = "2.6.4"
end
end

View File

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

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)_

17
release_notes/2.6.4.md Normal file
View File

@@ -0,0 +1,17 @@
# Jasmine 2.6.4 Release Notes
## Summary
This is a patch release to fix some regressions and performance problems in the 2.6.0 release
## Changes
* Break into a `setTimeout` every once in a while allowing the CPU to run other things that used the real `setTimeout`
- Fixes [#1327](https://github.com/jasmine/jasmine/issues/1327)
- See [#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

@@ -21,6 +21,34 @@ describe("ClearStack", function() {
expect(setImmediate).toHaveBeenCalled();
});
it("uses setTimeout instead of setImmediate every 10 calls to make sure we release the CPU", function() {
var setImmediate = jasmine.createSpy('setImmediate'),
setTimeout = jasmine.createSpy('setTimeout'),
global = { setImmediate: setImmediate, setTimeout: setTimeout },
clearStack = jasmineUnderTest.getClearStack(global);
clearStack(function() { });
clearStack(function() { });
clearStack(function() { });
clearStack(function() { });
clearStack(function() { });
clearStack(function() { });
clearStack(function() { });
clearStack(function() { });
clearStack(function() { });
expect(setImmediate).toHaveBeenCalled();
expect(setTimeout).not.toHaveBeenCalled();
clearStack(function() { });
expect(setImmediate.calls.count()).toEqual(9);
expect(setTimeout.calls.count()).toEqual(1);
clearStack(function() { });
expect(setImmediate.calls.count()).toEqual(10);
expect(setTimeout.calls.count()).toEqual(1);
});
it("uses MessageChannels when available", function() {
var fakeChannel = {
port1: {},
@@ -37,6 +65,41 @@ describe("ClearStack", function() {
expect(called).toBe(true);
});
it("uses setTimeout instead of MessageChannel every 10 calls to make sure we release the CPU", function() {
var fakeChannel = {
port1: {},
port2: {
postMessage: jasmine.createSpy('postMessage').and.callFake(function() {
fakeChannel.port1.onmessage();
})
}
},
setTimeout = jasmine.createSpy('setTimeout'),
global = { MessageChannel: function() { return fakeChannel; }, setTimeout: setTimeout },
clearStack = jasmineUnderTest.getClearStack(global);
clearStack(function() { });
clearStack(function() { });
clearStack(function() { });
clearStack(function() { });
clearStack(function() { });
clearStack(function() { });
clearStack(function() { });
clearStack(function() { });
clearStack(function() { });
expect(fakeChannel.port2.postMessage).toHaveBeenCalled();
expect(setTimeout).not.toHaveBeenCalled();
clearStack(function() { });
expect(fakeChannel.port2.postMessage.calls.count()).toEqual(9);
expect(setTimeout.calls.count()).toEqual(1);
clearStack(function() { });
expect(fakeChannel.port2.postMessage.calls.count()).toEqual(10);
expect(setTimeout.calls.count()).toEqual(1);
});
it("calls setTimeout when onmessage is called recursively", function() {
var fakeChannel = {
port1: {},

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);
@@ -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,40 +336,20 @@ 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();
});
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'),
@@ -353,23 +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).toHaveBeenCalled();
clearStack.calls.argsFor(0)[0]();
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
});
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();
});
it("copes with late async failures", function(done) {
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) },
@@ -466,6 +466,7 @@ describe("Env integration", function() {
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();
});
@@ -474,9 +475,11 @@ describe("Env integration", function() {
env.fdescribe('A suite', function() {
env.it('fails', function(specDone) {
specDone();
setTimeout(function() {
global.onerror('fail');
specDone();
setTimeout(function() {
global.onerror('fail');
});
});
});
});
@@ -949,16 +952,15 @@ describe("Env integration", function() {
});
describe("with a mock clock", function() {
var originalTimeout;
beforeEach(function() {
originalTimeout = jasmineUnderTest.DEFAULT_TIMEOUT_INTERVAL;
this.originalTimeout = jasmineUnderTest.DEFAULT_TIMEOUT_INTERVAL;
this.realSetTimeout = setTimeout;
jasmine.clock().install();
});
afterEach(function() {
jasmine.clock().uninstall();
jasmineUnderTest.DEFAULT_TIMEOUT_INTERVAL = originalTimeout;
jasmineUnderTest.DEFAULT_TIMEOUT_INTERVAL = this.originalTimeout;
});
it("should wait a specified interval before failing specs haven't called done yet", function(done) {
@@ -1077,7 +1079,8 @@ describe("Env integration", function() {
it('should wait a custom interval before reporting async functions that fail to call done', function(done) {
var env = new jasmineUnderTest.Env(),
reporter = jasmine.createSpyObj('fakeReport', ['jasmineDone', 'suiteDone', 'specDone']);
reporter = jasmine.createSpyObj('fakeReport', ['jasmineDone', 'suiteDone', 'specDone']),
realSetTimeout = this.realSetTimeout;
reporter.jasmineDone.and.callFake(function() {
expect(reporter.specDone).toHaveFailedExpecationsForRunnable('suite beforeAll times out', [
@@ -1107,6 +1110,11 @@ describe("Env integration", function() {
jasmineUnderTest.DEFAULT_TIMEOUT_INTERVAL = 10000;
env.describe('suite', function() {
env.afterAll(function() {
realSetTimeout(function() {
jasmine.clock().tick(10);
}, 100);
});
env.describe('beforeAll', function() {
env.beforeAll(function(innerDone) {
jasmine.clock().tick(5001);

View File

@@ -1,5 +1,7 @@
getJasmineRequireObj().clearStack = function(j$) {
function messageChannelImpl(global) {
var maxInlineCallCount = 10;
function messageChannelImpl(global, setTimeout) {
var channel = new global.MessageChannel(),
head = {},
tail = head;
@@ -22,25 +24,44 @@ getJasmineRequireObj().clearStack = function(j$) {
}
};
var currentCallCount = 0;
return function clearStack(fn) {
tail = tail.next = { task: fn };
channel.port2.postMessage(0);
currentCallCount++;
if (currentCallCount < maxInlineCallCount) {
tail = tail.next = { task: fn };
channel.port2.postMessage(0);
} else {
currentCallCount = 0;
setTimeout(fn);
}
};
}
function getClearStack(global) {
var currentCallCount = 0;
var realSetTimeout = global.setTimeout;
var setTimeoutImpl = function clearStack(fn) {
Function.prototype.apply.apply(realSetTimeout, [global, [fn, 0]]);
};
if (j$.isFunction_(global.setImmediate)) {
var realSetImmediate = global.setImmediate;
return function(fn) {
realSetImmediate(fn);
currentCallCount++;
if (currentCallCount < maxInlineCallCount) {
realSetImmediate(fn);
} else {
currentCallCount = 0;
setTimeoutImpl(fn);
}
};
} else if (!j$.util.isUndefined(global.MessageChannel)) {
return messageChannelImpl(global);
return messageChannelImpl(global, setTimeoutImpl);
} else {
var realSetTimeout = global.setTimeout;
return function clearStack(fn) {
Function.prototype.apply.apply(realSetTimeout, [global, [fn, 0]]);
};
return setTimeoutImpl;
}
}

View File

@@ -65,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();
@@ -72,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;
@@ -84,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();