clearStack optimizations
* Avoid setTimeout in Node, because we don't need the overhead there. * Still call setTimeout in browsers to prevent the tab from being killed. * Use queueMicrotask in Safari, because it's dramatically faster than MessageChannel there. * Continue to use MessageChannel in other supported browsers becuase it's somewhat faster than queueMicrotask there. * Don't use setImmediate any more because there's a faster alternative in all supported envs. In jasmine-core's own test suite, this yields a roughly 50-70% speedup in Node, ~20% in Edge, and 75-90%(!) in Safari.
This commit is contained in:
@@ -9,160 +9,216 @@ describe('ClearStack', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('uses setImmediate when available', function() {
|
||||
const setImmediate = jasmine
|
||||
.createSpy('setImmediate')
|
||||
.and.callFake(function(fn) {
|
||||
fn();
|
||||
}),
|
||||
global = { setImmediate: setImmediate },
|
||||
clearStack = jasmineUnderTest.getClearStack(global);
|
||||
let called = false;
|
||||
describe('in Safari', function() {
|
||||
usesQueueMicrotaskWithSetTimeout(function() {
|
||||
return {
|
||||
navigator: {
|
||||
userAgent:
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/605.0.8 (KHTML, like Gecko) Version/15.1 Safari/605.0.8'
|
||||
},
|
||||
// queueMicrotask should be used even though MessageChannel is present
|
||||
MessageChannel: fakeMessageChannel
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
clearStack(function() {
|
||||
called = true;
|
||||
describe('in browsers other than Safari', function() {
|
||||
usesMessageChannel(function() {
|
||||
return {
|
||||
navigator: {
|
||||
// Chrome's user agent string contains "Safari" so it's a good test case
|
||||
userAgent:
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36'
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
expect(called).toBe(true);
|
||||
expect(setImmediate).toHaveBeenCalled();
|
||||
describe('when MessageChannel is unavailable', function() {
|
||||
usesQueueMicrotaskWithSetTimeout(function() {
|
||||
return {
|
||||
navigator: {
|
||||
userAgent: 'CERN-LineMode/2.15 libwww/2.17b3',
|
||||
MessageChannel: undefined
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('uses setTimeout instead of setImmediate every 10 calls to make sure we release the CPU', function() {
|
||||
const 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() {
|
||||
const fakeChannel = {
|
||||
port1: {},
|
||||
port2: {
|
||||
postMessage: function() {
|
||||
fakeChannel.port1.onmessage();
|
||||
describe('in Node', function() {
|
||||
usesQueueMicrotaskWithoutSetTimeout(function() {
|
||||
return {
|
||||
process: {
|
||||
versions: {
|
||||
node: '3.1415927'
|
||||
}
|
||||
}
|
||||
},
|
||||
global = {
|
||||
MessageChannel: function() {
|
||||
return fakeChannel;
|
||||
}
|
||||
},
|
||||
clearStack = jasmineUnderTest.getClearStack(global);
|
||||
let called = false;
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
clearStack(function() {
|
||||
called = true;
|
||||
function usesMessageChannel(makeGlobal) {
|
||||
it('uses MessageChannel', function() {
|
||||
const global = {
|
||||
...makeGlobal(),
|
||||
MessageChannel: fakeMessageChannel
|
||||
};
|
||||
const clearStack = jasmineUnderTest.getClearStack(global);
|
||||
let called = false;
|
||||
|
||||
clearStack(function() {
|
||||
called = true;
|
||||
});
|
||||
|
||||
expect(called).toBe(true);
|
||||
});
|
||||
|
||||
expect(called).toBe(true);
|
||||
});
|
||||
|
||||
it('uses setTimeout instead of MessageChannel every 10 calls to make sure we release the CPU', function() {
|
||||
const fakeChannel = {
|
||||
port1: {},
|
||||
port2: {
|
||||
postMessage: jasmine
|
||||
.createSpy('postMessage')
|
||||
.and.callFake(function() {
|
||||
fakeChannel.port1.onmessage();
|
||||
})
|
||||
}
|
||||
},
|
||||
setTimeout = jasmine.createSpy('setTimeout'),
|
||||
global = {
|
||||
it('uses setTimeout instead of MessageChannel every 10 calls to make sure we release the CPU', function() {
|
||||
const fakeChannel = fakeMessageChannel();
|
||||
spyOn(fakeChannel.port2, 'postMessage');
|
||||
const setTimeout = jasmine.createSpy('setTimeout');
|
||||
const global = {
|
||||
...makeGlobal(),
|
||||
setTimeout,
|
||||
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() {
|
||||
const 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');
|
||||
};
|
||||
const clearStack = jasmineUnderTest.getClearStack(global);
|
||||
|
||||
clearStack(function() {
|
||||
clearStack(fn);
|
||||
for (let i = 0; i < 9; i++) {
|
||||
clearStack(function() {});
|
||||
}
|
||||
|
||||
expect(fakeChannel.port2.postMessage).toHaveBeenCalled();
|
||||
expect(setTimeout).not.toHaveBeenCalled();
|
||||
|
||||
clearStack(function() {});
|
||||
expect(fakeChannel.port2.postMessage).toHaveBeenCalledTimes(9);
|
||||
expect(setTimeout).toHaveBeenCalledTimes(1);
|
||||
|
||||
clearStack(function() {});
|
||||
expect(fakeChannel.port2.postMessage).toHaveBeenCalledTimes(10);
|
||||
expect(setTimeout).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
expect(fn).not.toHaveBeenCalled();
|
||||
expect(setTimeout).toHaveBeenCalledWith(fn, 0);
|
||||
});
|
||||
it('calls setTimeout when onmessage is called recursively', function() {
|
||||
const setTimeout = jasmine.createSpy('setTimeout');
|
||||
const global = {
|
||||
...makeGlobal(),
|
||||
setTimeout,
|
||||
MessageChannel: fakeMessageChannel
|
||||
};
|
||||
const clearStack = jasmineUnderTest.getClearStack(global);
|
||||
const fn = jasmine.createSpy('second clearStack function');
|
||||
|
||||
it('falls back to setTimeout', function() {
|
||||
const setTimeout = jasmine
|
||||
.createSpy('setTimeout')
|
||||
.and.callFake(function(fn) {
|
||||
clearStack(function() {
|
||||
clearStack(fn);
|
||||
});
|
||||
|
||||
expect(fn).not.toHaveBeenCalled();
|
||||
expect(setTimeout).toHaveBeenCalledWith(fn, 0);
|
||||
});
|
||||
}
|
||||
|
||||
function usesQueueMicrotaskWithSetTimeout(makeGlobal) {
|
||||
it('uses queueMicrotask', function() {
|
||||
const global = {
|
||||
...makeGlobal(),
|
||||
queueMicrotask: function(fn) {
|
||||
fn();
|
||||
}),
|
||||
global = { setTimeout: setTimeout },
|
||||
clearStack = jasmineUnderTest.getClearStack(global);
|
||||
let called = false;
|
||||
}
|
||||
};
|
||||
const clearStack = jasmineUnderTest.getClearStack(global);
|
||||
let called = false;
|
||||
|
||||
clearStack(function() {
|
||||
called = true;
|
||||
clearStack(function() {
|
||||
called = true;
|
||||
});
|
||||
|
||||
expect(called).toBe(true);
|
||||
});
|
||||
|
||||
expect(called).toBe(true);
|
||||
expect(setTimeout).toHaveBeenCalledWith(jasmine.any(Function), 0);
|
||||
});
|
||||
it('uses setTimeout instead of queueMicrotask every 10 calls to make sure we release the CPU', function() {
|
||||
const queueMicrotask = jasmine.createSpy('queueMicrotask');
|
||||
const setTimeout = jasmine.createSpy('setTimeout');
|
||||
const global = {
|
||||
...makeGlobal(),
|
||||
queueMicrotask,
|
||||
setTimeout
|
||||
};
|
||||
const clearStack = jasmineUnderTest.getClearStack(global);
|
||||
|
||||
for (let i = 0; i < 9; i++) {
|
||||
clearStack(function() {});
|
||||
}
|
||||
|
||||
expect(queueMicrotask).toHaveBeenCalled();
|
||||
expect(setTimeout).not.toHaveBeenCalled();
|
||||
|
||||
clearStack(function() {});
|
||||
expect(queueMicrotask).toHaveBeenCalledTimes(9);
|
||||
expect(setTimeout).toHaveBeenCalledTimes(1);
|
||||
|
||||
clearStack(function() {});
|
||||
expect(queueMicrotask).toHaveBeenCalledTimes(10);
|
||||
expect(setTimeout).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
}
|
||||
|
||||
function usesQueueMicrotaskWithoutSetTimeout(makeGlobal) {
|
||||
it('uses queueMicrotask', function() {
|
||||
const global = {
|
||||
...makeGlobal(),
|
||||
queueMicrotask: function(fn) {
|
||||
fn();
|
||||
}
|
||||
};
|
||||
const clearStack = jasmineUnderTest.getClearStack(global);
|
||||
let called = false;
|
||||
|
||||
clearStack(function() {
|
||||
called = true;
|
||||
});
|
||||
|
||||
expect(called).toBe(true);
|
||||
});
|
||||
|
||||
it('does not use setTimeout', function() {
|
||||
const queueMicrotask = jasmine.createSpy('queueMicrotask');
|
||||
const setTimeout = jasmine.createSpy('setTimeout');
|
||||
const global = {
|
||||
...makeGlobal(),
|
||||
queueMicrotask,
|
||||
setTimeout
|
||||
};
|
||||
const clearStack = jasmineUnderTest.getClearStack(global);
|
||||
|
||||
clearStack(function() {});
|
||||
clearStack(function() {});
|
||||
clearStack(function() {});
|
||||
clearStack(function() {});
|
||||
clearStack(function() {});
|
||||
clearStack(function() {});
|
||||
clearStack(function() {});
|
||||
clearStack(function() {});
|
||||
clearStack(function() {});
|
||||
clearStack(function() {});
|
||||
|
||||
expect(queueMicrotask).toHaveBeenCalledTimes(10);
|
||||
expect(setTimeout).not.toHaveBeenCalled();
|
||||
});
|
||||
}
|
||||
|
||||
function fakeMessageChannel() {
|
||||
const channel = {
|
||||
port1: {},
|
||||
port2: {
|
||||
postMessage: function() {
|
||||
channel.port1.onmessage();
|
||||
}
|
||||
}
|
||||
};
|
||||
return channel;
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user