Skip to content

Commit 9c6b40f

Browse files
authored
perf(core): lazy load runtime diff formatting and fake timers (#1079)
1 parent c2478e0 commit 9c6b40f

5 files changed

Lines changed: 90 additions & 63 deletions

File tree

packages/core/src/runtime/api/fakeTimers.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,26 @@
55
* LICENSE file in the root directory of https://github.com/facebook/jest.
66
*/
77

8-
import {
9-
type FakeTimerInstallOpts,
10-
type FakeTimerWithContext,
11-
type InstalledClock,
12-
withGlobal,
8+
import type {
9+
FakeTimerInstallOpts,
10+
FakeTimerWithContext,
11+
InstalledClock,
1312
} from '@sinonjs/fake-timers';
1413

1514
export type { FakeTimerInstallOpts };
1615

1716
const RealDate = Date;
1817

18+
const loadFakeTimersModule = () => {
19+
// TODO: Switch back to createRequire(import.meta.url) once Rspack supports
20+
// preserving that pattern without breaking bundling/runtime resolution.
21+
// Preserve the public sync timer API while avoiding module init work
22+
// on worker startup when fake timers are never used.
23+
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
24+
const loaded = require('@sinonjs/fake-timers');
25+
return { withGlobal: loaded.withGlobal };
26+
};
27+
1928
export class FakeTimers {
2029
private _clock!: InstalledClock;
2130
private readonly _config: FakeTimerInstallOpts;
@@ -31,7 +40,7 @@ export class FakeTimers {
3140
}) {
3241
this._config = config;
3342
this._fakingTime = false;
34-
this._fakeTimers = withGlobal(global);
43+
this._fakeTimers = loadFakeTimersModule().withGlobal(global);
3544
}
3645

3746
clearAllTimers(): void {

packages/core/src/runtime/runner/runner.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ export class TestRunner {
117117
status: 'fail' as const,
118118
parentNames: test.parentNames,
119119
name: test.name,
120-
errors: formatTestError(error, test),
120+
errors: await formatTestError(error, test),
121121
testPath,
122122
project,
123123
};
@@ -187,7 +187,7 @@ export class TestRunner {
187187
status: 'fail' as const,
188188
parentNames: test.parentNames,
189189
name: test.name,
190-
errors: formatTestError(error, test),
190+
errors: await formatTestError(error, test),
191191
testPath,
192192
};
193193
}
@@ -207,7 +207,7 @@ export class TestRunner {
207207
} catch (error) {
208208
result.status = 'fail';
209209
result.errors ??= [];
210-
result.errors.push(...formatTestError(error));
210+
result.errors.push(...(await formatTestError(error)));
211211
}
212212

213213
if (result.status === 'fail') {
@@ -216,7 +216,7 @@ export class TestRunner {
216216
await fn(test.context);
217217
} catch (error) {
218218
result.errors ??= [];
219-
result.errors.push(...formatTestError(error));
219+
result.errors.push(...(await formatTestError(error)));
220220
}
221221
}
222222
// should not be updated for snapshots that have not been run when the test run fails
@@ -339,7 +339,7 @@ export class TestRunner {
339339
} catch (error) {
340340
hasBeforeAllError = true;
341341

342-
result.errors?.push(...formatTestError(error));
342+
result.errors?.push(...(await formatTestError(error)));
343343
}
344344
}
345345

@@ -370,7 +370,7 @@ export class TestRunner {
370370
}
371371
} catch (error) {
372372
// AfterAll failed does not affect test case results
373-
result.errors?.push(...formatTestError(error));
373+
result.errors?.push(...(await formatTestError(error)));
374374
}
375375
}
376376
result.duration = RealDate.now() - start;

packages/core/src/runtime/util.ts

Lines changed: 64 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
1-
import { diff } from 'jest-diff';
2-
import {
3-
format as prettyFormat,
4-
plugins as prettyFormatPlugins,
5-
} from 'pretty-format';
61
import type { FormattedError, Test } from '../types';
72

3+
const loadDiffModules = async () => {
4+
const [jestDiff, prettyFormat] = await Promise.all([
5+
import('jest-diff'),
6+
import('pretty-format'),
7+
]);
8+
9+
return {
10+
diff: jestDiff.diff,
11+
prettyFormat: prettyFormat.format,
12+
prettyFormatPlugins: prettyFormat.plugins,
13+
};
14+
};
15+
816
const REAL_TIMERS: {
917
setTimeout?: typeof globalThis.setTimeout;
1018
clearTimeout?: typeof globalThis.clearTimeout;
@@ -20,49 +28,59 @@ export const getRealTimers = (): typeof REAL_TIMERS => {
2028
return REAL_TIMERS;
2129
};
2230

23-
export const formatTestError = (err: any, test?: Test): FormattedError[] => {
31+
export const formatTestError = async (
32+
err: any,
33+
test?: Test,
34+
): Promise<FormattedError[]> => {
2435
const errors = Array.isArray(err) ? err : [err];
2536

26-
return errors.map((rawError) => {
27-
const error =
28-
typeof rawError === 'string' ? { message: rawError } : rawError;
29-
const errObj: FormattedError = {
30-
fullStack: error.fullStack,
31-
// Some error attributes cannot be enumerated
32-
message: error.message,
33-
name: error.name,
34-
stack: error.stack,
35-
};
36-
37-
if (error instanceof TestRegisterError && test?.type === 'case') {
38-
errObj.message = `Can't nest describe or test inside a test. ${error.message} because it is nested within test '${test.name}'`;
39-
}
40-
41-
if (
42-
error.showDiff ||
43-
(error.showDiff === undefined &&
44-
error.expected !== undefined &&
45-
error.actual !== undefined)
46-
) {
47-
errObj.diff = diff(err.expected, err.actual, {
48-
expand: false,
49-
})!;
50-
errObj.expected =
51-
typeof error.expected === 'string'
52-
? error.expected
53-
: prettyFormat(error.expected, {
54-
plugins: Object.values(prettyFormatPlugins),
55-
});
56-
errObj.actual =
57-
typeof error.actual === 'string'
58-
? error.actual
59-
: prettyFormat(error.actual, {
60-
plugins: Object.values(prettyFormatPlugins),
61-
});
62-
}
63-
64-
return errObj;
65-
});
37+
return Promise.all(
38+
errors.map(async (rawError) => {
39+
const error =
40+
typeof rawError === 'string' ? { message: rawError } : rawError;
41+
const errObj: FormattedError = {
42+
fullStack: error.fullStack,
43+
// Some error attributes cannot be enumerated
44+
message: error.message,
45+
name: error.name,
46+
stack: error.stack,
47+
};
48+
49+
if (error instanceof TestRegisterError && test?.type === 'case') {
50+
errObj.message = `Can't nest describe or test inside a test. ${error.message} because it is nested within test '${test.name}'`;
51+
}
52+
53+
if (
54+
error.showDiff ||
55+
(error.showDiff === undefined &&
56+
error.expected !== undefined &&
57+
error.actual !== undefined)
58+
) {
59+
const expected = error.expected;
60+
const actual = error.actual;
61+
const { diff, prettyFormat, prettyFormatPlugins } =
62+
await loadDiffModules();
63+
64+
errObj.diff = diff(expected, actual, {
65+
expand: false,
66+
})!;
67+
errObj.expected =
68+
typeof expected === 'string'
69+
? expected
70+
: prettyFormat(expected, {
71+
plugins: Object.values(prettyFormatPlugins),
72+
});
73+
errObj.actual =
74+
typeof actual === 'string'
75+
? actual
76+
: prettyFormat(actual, {
77+
plugins: Object.values(prettyFormatPlugins),
78+
});
79+
}
80+
81+
return errObj;
82+
}),
83+
);
6684
};
6785
// cspell:ignore sdjifo
6886
const formatRegExp = /%[sdjifoOc%]/;

packages/core/src/runtime/worker/globalSetupWorker.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ const runGlobalSetup = async (data: {
127127
return {
128128
success: false,
129129
hasTeardown: false,
130-
errors: formatTestError(error),
130+
errors: await formatTestError(error),
131131
};
132132
}
133133
};

packages/core/src/runtime/worker/index.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -390,14 +390,14 @@ const runInPool = async (
390390
project,
391391
testPath,
392392
tests,
393-
errors: formatTestError(unhandledErrors),
393+
errors: await formatTestError(unhandledErrors),
394394
};
395395
} catch (err) {
396396
return {
397397
project,
398398
testPath,
399399
tests: [],
400-
errors: formatTestError(err),
400+
errors: await formatTestError(err),
401401
};
402402
} finally {
403403
await teardown();
@@ -480,7 +480,7 @@ const runInPool = async (
480480
if (unhandledErrors.length > 0) {
481481
results.status = 'fail';
482482
results.errors = (results.errors || []).concat(
483-
...formatTestError(unhandledErrors),
483+
...(await formatTestError(unhandledErrors)),
484484
);
485485
}
486486

@@ -509,7 +509,7 @@ const runInPool = async (
509509
status: 'fail',
510510
name: '',
511511
results: [],
512-
errors: formatTestError(err),
512+
errors: await formatTestError(err),
513513
};
514514
} finally {
515515
await teardown();

0 commit comments

Comments
 (0)