Skip to content

Commit 6444d5d

Browse files
committed
Fixed flaky timeout-threshold tests racing real setTimeout
no ref - {{#get}} and {{#recommendations}} timeout tests asserted that the helper's setTimeout(threshold=1ms) fired before the API stub's setTimeout(5ms), which is fundamentally a Node scheduler race - under CI load, both timers can land in the same macrotask and the ordering becomes unreliable, e.g. recommendations.test.js:173 in run 25404408183 and get.test.js:562 in run 25399938361 - install sinon fake timers in each test, kick off the helper, then tickAsync(2) — that fires the 1ms threshold timer but not the 5ms API timer, so the timeout branch wins deterministically - limited toFake to [setTimeout, clearTimeout] so Date and other timers aren't affected (the get notify test still uses real time)
1 parent 161b51b commit 6444d5d

2 files changed

Lines changed: 42 additions & 18 deletions

File tree

ghost/core/test/unit/frontend/helpers/get.test.js

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -551,23 +551,32 @@ describe('{{#get}} helper', function () {
551551
});
552552

553553
it('should log an error and return safely if it hits the timeout threshold', async function () {
554-
configUtils.set('optimization:getHelper:timeout:threshold', 1);
555-
556-
const result = await get.call(
557-
{},
558-
'posts',
559-
{hash: {}, data: locals, fn: fn, inverse: inverse}
560-
);
561-
562-
assert(result.toString().includes('data-aborted-get-helper'));
563-
// A log message will be output
564-
sinon.assert.calledOnce(logging.error);
565-
// The get helper gets called with an empty array of results
566-
sinon.assert.calledOnce(fn);
567-
const args = fn.firstCall.args[0];
568-
assert(args && typeof args === 'object');
569-
assert('posts' in args);
570-
assert.deepEqual(args.posts, []);
554+
const clock = sinon.useFakeTimers({toFake: ['setTimeout', 'clearTimeout']});
555+
try {
556+
configUtils.set('optimization:getHelper:timeout:threshold', 1);
557+
558+
const resultPromise = get.call(
559+
{},
560+
'posts',
561+
{hash: {}, data: locals, fn: fn, inverse: inverse}
562+
);
563+
// Advance past the 1ms threshold but not the stub's 5ms timer,
564+
// so the helper's timeout branch wins deterministically.
565+
await clock.tickAsync(2);
566+
const result = await resultPromise;
567+
568+
assert(result.toString().includes('data-aborted-get-helper'));
569+
// A log message will be output
570+
sinon.assert.calledOnce(logging.error);
571+
// The get helper gets called with an empty array of results
572+
sinon.assert.calledOnce(fn);
573+
const args = fn.firstCall.args[0];
574+
assert(args && typeof args === 'object');
575+
assert('posts' in args);
576+
assert.deepEqual(args.posts, []);
577+
} finally {
578+
clock.restore();
579+
}
571580
});
572581
});
573582

ghost/core/test/unit/frontend/helpers/recommendations.test.js

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,8 @@ describe('{{#recommendations}} helper', function () {
145145
});
146146

147147
describe('when timeout is exceeded', function () {
148+
let clock;
149+
148150
before(function () {
149151
sinon.stub(api, 'recommendationsPublic').get(() => {
150152
return {
@@ -158,16 +160,29 @@ describe('{{#recommendations}} helper', function () {
158160
};
159161
});
160162
});
163+
164+
beforeEach(function () {
165+
clock = sinon.useFakeTimers({toFake: ['setTimeout', 'clearTimeout']});
166+
});
167+
168+
afterEach(function () {
169+
clock.restore();
170+
});
171+
161172
after(async function () {
162173
await configUtils.restore();
163174
});
164175

165176
it('should log an error and return safely if it hits the timeout threshold', async function () {
166177
configUtils.set('optimization:getHelper:timeout:threshold', 1);
167178

168-
const response = await recommendations.call(
179+
const responsePromise = recommendations.call(
169180
'recommendations'
170181
);
182+
// Advance past the 1ms threshold but not the stub's 5ms timer,
183+
// so the helper's timeout branch wins deterministically.
184+
await clock.tickAsync(2);
185+
const response = await responsePromise;
171186

172187
// An error message is logged
173188
sinon.assert.calledOnce(logging.error);

0 commit comments

Comments
 (0)