Add experimental safariYieldStrategy: "time" config option
This greatly improves speed, at least in jasmine-core's own tests.
This commit is contained in:
@@ -1323,6 +1323,7 @@ getJasmineRequireObj().Env = function(j$) {
|
|||||||
|
|
||||||
config.update(changes);
|
config.update(changes);
|
||||||
deprecator.verboseDeprecations(config.verboseDeprecations);
|
deprecator.verboseDeprecations(config.verboseDeprecations);
|
||||||
|
stackClearer.setSafariYieldStrategy(config.safariYieldStrategy);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -3475,7 +3476,22 @@ getJasmineRequireObj().Configuration = function(j$) {
|
|||||||
* @type number
|
* @type number
|
||||||
* @default 0
|
* @default 0
|
||||||
*/
|
*/
|
||||||
extraDescribeStackFrames: 0
|
extraDescribeStackFrames: 0,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The strategy to use in Safari and similar browsers to determine how often
|
||||||
|
* to yield control by calling setTimeout. If set to "count", the default,
|
||||||
|
* the frequency of setTimeout calls is based on the number of relevant
|
||||||
|
* function calls. If set to "time", the frequency of setTimeout calls is
|
||||||
|
* based on elapsed time. Using "time" may provide a significant performance
|
||||||
|
* improvement, but as of 6.0 it hasn't been tested with a wide variety of
|
||||||
|
* workloads and should be considered experimental.
|
||||||
|
* @name Configuration#safariYieldStrategy
|
||||||
|
* @since 6.0.0
|
||||||
|
* @type 'count' | 'time'
|
||||||
|
* @default 'count'
|
||||||
|
*/
|
||||||
|
safariYieldStrategy: 'count'
|
||||||
};
|
};
|
||||||
Object.freeze(defaultConfig);
|
Object.freeze(defaultConfig);
|
||||||
|
|
||||||
@@ -3536,6 +3552,18 @@ getJasmineRequireObj().Configuration = function(j$) {
|
|||||||
this.#values.extraDescribeStackFrames =
|
this.#values.extraDescribeStackFrames =
|
||||||
changes.extraDescribeStackFrames;
|
changes.extraDescribeStackFrames;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typeof changes.safariYieldStrategy !== 'undefined') {
|
||||||
|
const v = changes.safariYieldStrategy;
|
||||||
|
|
||||||
|
if (v === 'count' || v === 'time') {
|
||||||
|
this.#values.safariYieldStrategy = v;
|
||||||
|
} else {
|
||||||
|
throw new Error(
|
||||||
|
"Invalid safariYieldStrategy value. Valid values are 'count' and 'time'."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -10662,21 +10690,46 @@ getJasmineRequireObj().StackClearer = function(j$) {
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const maxInlineCallCount = 10;
|
const maxInlineCallCount = 10;
|
||||||
|
// 25ms gives a good balance of speed and UI responsiveness when running
|
||||||
|
// jasmine-core's own tests in Safari 18. The exact value isn't critical.
|
||||||
|
const safariYieldIntervalMs = 25;
|
||||||
|
|
||||||
function browserQueueMicrotaskImpl(global) {
|
function browserQueueMicrotaskImpl(global) {
|
||||||
const unclampedSetTimeout = getUnclampedSetTimeout(global);
|
const unclampedSetTimeout = getUnclampedSetTimeout(global);
|
||||||
const { queueMicrotask } = global;
|
const { queueMicrotask } = global;
|
||||||
let currentCallCount = 0;
|
let yieldStrategy = 'count';
|
||||||
|
let currentCallCount = 0; // for count strategy
|
||||||
|
let nextSetTimeoutTime; // for time strategy
|
||||||
|
|
||||||
return {
|
return {
|
||||||
clearStack(fn) {
|
clearStack(fn) {
|
||||||
currentCallCount++;
|
currentCallCount++;
|
||||||
|
let shouldSetTimeout;
|
||||||
|
|
||||||
if (currentCallCount < maxInlineCallCount) {
|
if (yieldStrategy === 'time') {
|
||||||
queueMicrotask(fn);
|
const now = new Date().getTime();
|
||||||
|
shouldSetTimeout = now >= nextSetTimeoutTime;
|
||||||
|
if (shouldSetTimeout) {
|
||||||
|
nextSetTimeoutTime = now + safariYieldIntervalMs;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
currentCallCount = 0;
|
shouldSetTimeout = currentCallCount >= maxInlineCallCount;
|
||||||
|
if (shouldSetTimeout) {
|
||||||
|
currentCallCount = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldSetTimeout) {
|
||||||
unclampedSetTimeout(fn);
|
unclampedSetTimeout(fn);
|
||||||
|
} else {
|
||||||
|
queueMicrotask(fn);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setSafariYieldStrategy(strategy) {
|
||||||
|
yieldStrategy = strategy;
|
||||||
|
|
||||||
|
if (yieldStrategy === 'time') {
|
||||||
|
nextSetTimeoutTime = new Date().getTime() + safariYieldIntervalMs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -10688,7 +10741,8 @@ getJasmineRequireObj().StackClearer = function(j$) {
|
|||||||
return {
|
return {
|
||||||
clearStack(fn) {
|
clearStack(fn) {
|
||||||
queueMicrotask(fn);
|
queueMicrotask(fn);
|
||||||
}
|
},
|
||||||
|
setSafariYieldStrategy() {}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -10708,7 +10762,8 @@ getJasmineRequireObj().StackClearer = function(j$) {
|
|||||||
currentCallCount = 0;
|
currentCallCount = 0;
|
||||||
setTimeout(fn);
|
setTimeout(fn);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
setSafariYieldStrategy() {}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ describe('Configuration', function() {
|
|||||||
'seed',
|
'seed',
|
||||||
'specFilter',
|
'specFilter',
|
||||||
'extraItStackFrames',
|
'extraItStackFrames',
|
||||||
'extraDescribeStackFrames'
|
'extraDescribeStackFrames',
|
||||||
|
'safariYieldStrategy'
|
||||||
];
|
];
|
||||||
Object.freeze(standardBooleanKeys);
|
Object.freeze(standardBooleanKeys);
|
||||||
Object.freeze(allKeys);
|
Object.freeze(allKeys);
|
||||||
@@ -36,6 +37,7 @@ describe('Configuration', function() {
|
|||||||
expect(subject.detectLateRejectionHandling).toEqual(false);
|
expect(subject.detectLateRejectionHandling).toEqual(false);
|
||||||
expect(subject.extraItStackFrames).toEqual(0);
|
expect(subject.extraItStackFrames).toEqual(0);
|
||||||
expect(subject.extraDescribeStackFrames).toEqual(0);
|
expect(subject.extraDescribeStackFrames).toEqual(0);
|
||||||
|
expect(subject.safariYieldStrategy).toEqual('count');
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('copy()', function() {
|
describe('copy()', function() {
|
||||||
@@ -137,5 +139,28 @@ describe('Configuration', function() {
|
|||||||
subject.update({ extraDescribeStackFrames: 100000 });
|
subject.update({ extraDescribeStackFrames: 100000 });
|
||||||
expect(subject.extraDescribeStackFrames).toEqual(100000);
|
expect(subject.extraDescribeStackFrames).toEqual(100000);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('sets safariYieldStrategy when valid', function() {
|
||||||
|
const subject = new privateUnderTest.Configuration();
|
||||||
|
|
||||||
|
subject.update({ safariYieldStrategy: undefined });
|
||||||
|
expect(subject.safariYieldStrategy).toEqual('count');
|
||||||
|
|
||||||
|
subject.update({ safariYieldStrategy: 'time' });
|
||||||
|
expect(subject.safariYieldStrategy).toEqual('time');
|
||||||
|
|
||||||
|
subject.update({ safariYieldStrategy: 'count' });
|
||||||
|
expect(subject.safariYieldStrategy).toEqual('count');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejcts invalid safariYieldStrategy values', function() {
|
||||||
|
const subject = new privateUnderTest.Configuration();
|
||||||
|
|
||||||
|
expect(function() {
|
||||||
|
subject.update({ safariYieldStrategy: 'thyme' });
|
||||||
|
}).toThrowError(
|
||||||
|
"Invalid safariYieldStrategy value. Valid values are 'count' and 'time'."
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -180,30 +180,82 @@ describe('StackClearer', function() {
|
|||||||
expect(called).toBe(true);
|
expect(called).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('uses setTimeout instead of queueMicrotask every 10 calls to make sure we release the CPU', function() {
|
function hasSetTimeoutBehavior(configure) {
|
||||||
const queueMicrotask = jasmine.createSpy('queueMicrotask');
|
it('uses setTimeout instead of queueMicrotask every 10 calls', function() {
|
||||||
const setTimeout = jasmine.createSpy('setTimeout');
|
const queueMicrotask = jasmine.createSpy('queueMicrotask');
|
||||||
const global = {
|
const setTimeout = jasmine.createSpy('setTimeout');
|
||||||
...makeGlobal(),
|
const global = {
|
||||||
queueMicrotask,
|
...makeGlobal(),
|
||||||
setTimeout
|
queueMicrotask,
|
||||||
};
|
setTimeout
|
||||||
const { clearStack } = privateUnderTest.getStackClearer(global);
|
};
|
||||||
|
const stackClearer = privateUnderTest.getStackClearer(global);
|
||||||
|
|
||||||
for (let i = 0; i < 9; i++) {
|
if (configure) {
|
||||||
clearStack(function() {});
|
configure(stackClearer);
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(queueMicrotask).toHaveBeenCalled();
|
for (let i = 0; i < 9; i++) {
|
||||||
expect(setTimeout).not.toHaveBeenCalled();
|
stackClearer.clearStack(function() {});
|
||||||
|
}
|
||||||
|
|
||||||
clearStack(function() {});
|
expect(queueMicrotask).toHaveBeenCalled();
|
||||||
expect(queueMicrotask).toHaveBeenCalledTimes(9);
|
expect(setTimeout).not.toHaveBeenCalled();
|
||||||
expect(setTimeout).toHaveBeenCalledTimes(1);
|
|
||||||
|
|
||||||
clearStack(function() {});
|
stackClearer.clearStack(function() {});
|
||||||
expect(queueMicrotask).toHaveBeenCalledTimes(10);
|
expect(queueMicrotask).toHaveBeenCalledTimes(9);
|
||||||
expect(setTimeout).toHaveBeenCalledTimes(1);
|
expect(setTimeout).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
stackClearer.clearStack(function() {});
|
||||||
|
expect(queueMicrotask).toHaveBeenCalledTimes(10);
|
||||||
|
expect(setTimeout).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
hasSetTimeoutBehavior();
|
||||||
|
|
||||||
|
describe('With yield strategy explicitly set to count', function() {
|
||||||
|
hasSetTimeoutBehavior(function(stackClearer) {
|
||||||
|
stackClearer.setSafariYieldStrategy('count');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('With yield strategy set to time', function() {
|
||||||
|
beforeEach(function() {
|
||||||
|
jasmine.clock().install();
|
||||||
|
jasmine.clock().mockDate();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function() {
|
||||||
|
jasmine.clock().uninstall();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses setTimeout instead of queueMicrotask every 25 milliseconds', function() {
|
||||||
|
const queueMicrotask = jasmine.createSpy('queueMicrotask');
|
||||||
|
const setTimeout = jasmine.createSpy('setTimeout');
|
||||||
|
const global = {
|
||||||
|
...makeGlobal(),
|
||||||
|
queueMicrotask,
|
||||||
|
setTimeout
|
||||||
|
};
|
||||||
|
const stackClearer = privateUnderTest.getStackClearer(global);
|
||||||
|
stackClearer.setSafariYieldStrategy('time');
|
||||||
|
|
||||||
|
// 10+ counts should not trigger a setTimeout if they happen fast enough
|
||||||
|
jasmine.clock().tick(24);
|
||||||
|
for (let i = 0; i < 11; i++) {
|
||||||
|
stackClearer.clearStack(function() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(queueMicrotask).toHaveBeenCalled();
|
||||||
|
expect(setTimeout).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
queueMicrotask.calls.reset();
|
||||||
|
jasmine.clock().tick(1);
|
||||||
|
stackClearer.clearStack(function() {});
|
||||||
|
expect(queueMicrotask).not.toHaveBeenCalled();
|
||||||
|
expect(setTimeout).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,8 @@ module.exports = {
|
|||||||
'helpers/resetEnv.js'
|
'helpers/resetEnv.js'
|
||||||
],
|
],
|
||||||
env: {
|
env: {
|
||||||
forbidDuplicateNames: true
|
forbidDuplicateNames: true,
|
||||||
|
safariYieldStrategy: 'time'
|
||||||
},
|
},
|
||||||
random: true,
|
random: true,
|
||||||
browser: {
|
browser: {
|
||||||
|
|||||||
@@ -151,7 +151,22 @@ getJasmineRequireObj().Configuration = function(j$) {
|
|||||||
* @type number
|
* @type number
|
||||||
* @default 0
|
* @default 0
|
||||||
*/
|
*/
|
||||||
extraDescribeStackFrames: 0
|
extraDescribeStackFrames: 0,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The strategy to use in Safari and similar browsers to determine how often
|
||||||
|
* to yield control by calling setTimeout. If set to "count", the default,
|
||||||
|
* the frequency of setTimeout calls is based on the number of relevant
|
||||||
|
* function calls. If set to "time", the frequency of setTimeout calls is
|
||||||
|
* based on elapsed time. Using "time" may provide a significant performance
|
||||||
|
* improvement, but as of 6.0 it hasn't been tested with a wide variety of
|
||||||
|
* workloads and should be considered experimental.
|
||||||
|
* @name Configuration#safariYieldStrategy
|
||||||
|
* @since 6.0.0
|
||||||
|
* @type 'count' | 'time'
|
||||||
|
* @default 'count'
|
||||||
|
*/
|
||||||
|
safariYieldStrategy: 'count'
|
||||||
};
|
};
|
||||||
Object.freeze(defaultConfig);
|
Object.freeze(defaultConfig);
|
||||||
|
|
||||||
@@ -212,6 +227,18 @@ getJasmineRequireObj().Configuration = function(j$) {
|
|||||||
this.#values.extraDescribeStackFrames =
|
this.#values.extraDescribeStackFrames =
|
||||||
changes.extraDescribeStackFrames;
|
changes.extraDescribeStackFrames;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typeof changes.safariYieldStrategy !== 'undefined') {
|
||||||
|
const v = changes.safariYieldStrategy;
|
||||||
|
|
||||||
|
if (v === 'count' || v === 'time') {
|
||||||
|
this.#values.safariYieldStrategy = v;
|
||||||
|
} else {
|
||||||
|
throw new Error(
|
||||||
|
"Invalid safariYieldStrategy value. Valid values are 'count' and 'time'."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -99,6 +99,7 @@ getJasmineRequireObj().Env = function(j$) {
|
|||||||
|
|
||||||
config.update(changes);
|
config.update(changes);
|
||||||
deprecator.verboseDeprecations(config.verboseDeprecations);
|
deprecator.verboseDeprecations(config.verboseDeprecations);
|
||||||
|
stackClearer.setSafariYieldStrategy(config.safariYieldStrategy);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -2,21 +2,46 @@ getJasmineRequireObj().StackClearer = function(j$) {
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const maxInlineCallCount = 10;
|
const maxInlineCallCount = 10;
|
||||||
|
// 25ms gives a good balance of speed and UI responsiveness when running
|
||||||
|
// jasmine-core's own tests in Safari 18. The exact value isn't critical.
|
||||||
|
const safariYieldIntervalMs = 25;
|
||||||
|
|
||||||
function browserQueueMicrotaskImpl(global) {
|
function browserQueueMicrotaskImpl(global) {
|
||||||
const unclampedSetTimeout = getUnclampedSetTimeout(global);
|
const unclampedSetTimeout = getUnclampedSetTimeout(global);
|
||||||
const { queueMicrotask } = global;
|
const { queueMicrotask } = global;
|
||||||
let currentCallCount = 0;
|
let yieldStrategy = 'count';
|
||||||
|
let currentCallCount = 0; // for count strategy
|
||||||
|
let nextSetTimeoutTime; // for time strategy
|
||||||
|
|
||||||
return {
|
return {
|
||||||
clearStack(fn) {
|
clearStack(fn) {
|
||||||
currentCallCount++;
|
currentCallCount++;
|
||||||
|
let shouldSetTimeout;
|
||||||
|
|
||||||
if (currentCallCount < maxInlineCallCount) {
|
if (yieldStrategy === 'time') {
|
||||||
queueMicrotask(fn);
|
const now = new Date().getTime();
|
||||||
|
shouldSetTimeout = now >= nextSetTimeoutTime;
|
||||||
|
if (shouldSetTimeout) {
|
||||||
|
nextSetTimeoutTime = now + safariYieldIntervalMs;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
currentCallCount = 0;
|
shouldSetTimeout = currentCallCount >= maxInlineCallCount;
|
||||||
|
if (shouldSetTimeout) {
|
||||||
|
currentCallCount = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldSetTimeout) {
|
||||||
unclampedSetTimeout(fn);
|
unclampedSetTimeout(fn);
|
||||||
|
} else {
|
||||||
|
queueMicrotask(fn);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setSafariYieldStrategy(strategy) {
|
||||||
|
yieldStrategy = strategy;
|
||||||
|
|
||||||
|
if (yieldStrategy === 'time') {
|
||||||
|
nextSetTimeoutTime = new Date().getTime() + safariYieldIntervalMs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -28,7 +53,8 @@ getJasmineRequireObj().StackClearer = function(j$) {
|
|||||||
return {
|
return {
|
||||||
clearStack(fn) {
|
clearStack(fn) {
|
||||||
queueMicrotask(fn);
|
queueMicrotask(fn);
|
||||||
}
|
},
|
||||||
|
setSafariYieldStrategy() {}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,7 +74,8 @@ getJasmineRequireObj().StackClearer = function(j$) {
|
|||||||
currentCallCount = 0;
|
currentCallCount = 0;
|
||||||
setTimeout(fn);
|
setTimeout(fn);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
setSafariYieldStrategy() {}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user