jest-doctor 0.0.4 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist/createEnvMixin.d.ts +11 -0
  2. package/dist/createEnvMixin.js +19 -14
  3. package/dist/env/jsdom.d.ts +11 -0
  4. package/dist/env/node.d.ts +11 -0
  5. package/dist/patch/console.d.ts +0 -1
  6. package/dist/patch/console.js +3 -14
  7. package/dist/{utils → patch}/createAsyncHookCleaner.js +3 -1
  8. package/dist/{utils → patch}/createAsyncHookDetector.d.ts +1 -1
  9. package/dist/patch/createAsyncHookDetector.js +30 -0
  10. package/dist/patch/fakeTimers.d.ts +1 -1
  11. package/dist/patch/fakeTimers.js +21 -14
  12. package/dist/patch/hook.d.ts +2 -2
  13. package/dist/patch/hook.js +12 -11
  14. package/dist/patch/it.d.ts +2 -2
  15. package/dist/patch/it.js +16 -18
  16. package/dist/patch/processOutput.d.ts +3 -0
  17. package/dist/patch/processOutput.js +30 -0
  18. package/dist/patch/promiseConcurrency.js +51 -14
  19. package/dist/patch/timers.d.ts +1 -1
  20. package/dist/patch/timers.js +14 -10
  21. package/dist/reporter.js +16 -1
  22. package/dist/requireEnvironment.d.ts +11 -0
  23. package/dist/types.d.ts +50 -29
  24. package/dist/utils/analyzeCallback.js +3 -0
  25. package/dist/utils/getStack.d.ts +1 -1
  26. package/dist/utils/getStack.js +12 -2
  27. package/dist/utils/initLeakRecord.js +1 -0
  28. package/dist/utils/initOriginal.d.ts +8 -0
  29. package/dist/utils/initOriginal.js +2 -0
  30. package/dist/utils/isIgnored.d.ts +3 -0
  31. package/dist/utils/isIgnored.js +11 -0
  32. package/dist/utils/normalizeOptions.d.ts +1 -1
  33. package/dist/utils/normalizeOptions.js +94 -48
  34. package/dist/utils/reportLeaks.js +42 -48
  35. package/package.json +5 -4
  36. package/readme.md +30 -19
  37. package/dist/patch/promise.d.ts +0 -3
  38. package/dist/patch/promise.js +0 -34
  39. package/dist/patch/race.d.ts +0 -1
  40. package/dist/patch/race.js +0 -2
  41. package/dist/utils/createAsyncHookDetector.js +0 -23
  42. /package/dist/{utils → patch}/createAsyncHookCleaner.d.ts +0 -0
@@ -29,6 +29,14 @@ declare const createEnvMixin: <EnvironmentConstructor extends JestDoctorConstruc
29
29
  clearTimeout: typeof clearTimeout;
30
30
  setInterval: typeof setInterval;
31
31
  clearInterval: typeof clearInterval;
32
+ stdout: {
33
+ (buffer: Uint8Array | string, cb?: (err?: Error | null) => void): boolean;
34
+ (str: Uint8Array | string, encoding?: BufferEncoding, cb?: (err?: Error | null) => void): boolean;
35
+ };
36
+ stderr: {
37
+ (buffer: Uint8Array | string, cb?: (err?: Error | null) => void): boolean;
38
+ (str: Uint8Array | string, encoding?: BufferEncoding, cb?: (err?: Error | null) => void): boolean;
39
+ };
32
40
  console: {
33
41
  log: (message?: any, ...optionalParams: any[]) => void;
34
42
  info: (message?: any, ...optionalParams: any[]) => void;
@@ -53,9 +61,12 @@ declare const createEnvMixin: <EnvironmentConstructor extends JestDoctorConstruc
53
61
  fakeTimers: number;
54
62
  console: number;
55
63
  totalDelay: number;
64
+ processOutputs: number;
56
65
  };
57
66
  tearDownError?: Error;
58
67
  asyncIdToPromise: Map<number, Promise<unknown>>;
68
+ asyncRoot: number;
69
+ asyncIdToParentId: Map<number, number>;
59
70
  setup(): Promise<void>;
60
71
  handleEvent(event: unknown, state: unknown): Promise<void>;
61
72
  handleTestEvent: JestEnvironment["handleTestEvent"];
@@ -12,8 +12,8 @@ const fakeTimers_1 = __importDefault(require("./patch/fakeTimers"));
12
12
  const consts_1 = require("./consts");
13
13
  const timers_1 = __importDefault(require("./patch/timers"));
14
14
  const it_1 = __importDefault(require("./patch/it"));
15
- const createAsyncHookDetector_1 = __importDefault(require("./utils/createAsyncHookDetector"));
16
- const createAsyncHookCleaner_1 = __importDefault(require("./utils/createAsyncHookCleaner"));
15
+ const createAsyncHookDetector_1 = __importDefault(require("./patch/createAsyncHookDetector"));
16
+ const createAsyncHookCleaner_1 = __importDefault(require("./patch/createAsyncHookCleaner"));
17
17
  const reportLeaks_1 = __importDefault(require("./utils/reportLeaks"));
18
18
  const cleanupAfterTest_1 = __importDefault(require("./utils/cleanupAfterTest"));
19
19
  const initLeakRecord_1 = __importDefault(require("./utils/initLeakRecord"));
@@ -21,8 +21,8 @@ const hook_1 = __importDefault(require("./patch/hook"));
21
21
  const getAllAfterEach_1 = __importDefault(require("./utils/getAllAfterEach"));
22
22
  const normalizeOptions_1 = __importDefault(require("./utils/normalizeOptions"));
23
23
  const getReporterTmpDir_1 = __importDefault(require("./utils/getReporterTmpDir"));
24
- const promise_1 = __importDefault(require("./patch/promise"));
25
24
  const promiseConcurrency_1 = __importDefault(require("./patch/promiseConcurrency"));
25
+ const processOutput_1 = __importDefault(require("./patch/processOutput"));
26
26
  const createEnvMixin = (Environment) => {
27
27
  // @ts-expect-error strange ts rule where constructor arguments should be any[] where again TypeScript complains
28
28
  return class Base extends Environment {
@@ -40,6 +40,8 @@ const createEnvMixin = (Environment) => {
40
40
  aggregatedReport;
41
41
  tearDownError;
42
42
  asyncIdToPromise = new Map();
43
+ asyncRoot = 0;
44
+ asyncIdToParentId = new Map();
43
45
  constructor(config, context) {
44
46
  super(config, context);
45
47
  this.testPath = context.testPath.replace(/\W/g, '_');
@@ -50,15 +52,15 @@ const createEnvMixin = (Environment) => {
50
52
  fakeTimers: 0,
51
53
  console: 0,
52
54
  totalDelay: 0,
55
+ processOutputs: 0,
53
56
  };
54
- const tmpDir = (0, getReporterTmpDir_1.default)(config.projectConfig.reporters);
57
+ const tmpDir = (0, getReporterTmpDir_1.default)(config.projectConfig.reporters || config.globalConfig.reporters);
55
58
  this.reporterTmpDir = tmpDir
56
59
  ? node_path_1.default.join(tmpDir, config.globalConfig.seed.toString(), this.testPath + 'json')
57
60
  : '';
58
61
  this.options = (0, normalizeOptions_1.default)(config.projectConfig.testEnvironmentOptions);
59
62
  (0, initLeakRecord_1.default)(this, consts_1.MAIN_THREAD);
60
- if (this.options.report.promises &&
61
- this.options.report.promises.patch === 'async_hooks') {
63
+ if (this.options.report.promises) {
62
64
  this.asyncHookCleaner = (0, createAsyncHookCleaner_1.default)(this);
63
65
  this.asyncHookDetector = (0, createAsyncHookDetector_1.default)(this);
64
66
  }
@@ -74,11 +76,11 @@ const createEnvMixin = (Environment) => {
74
76
  if (report.console) {
75
77
  (0, console_1.default)(this, report.console);
76
78
  }
79
+ if (report.processOutputs) {
80
+ (0, processOutput_1.default)(this, report.processOutputs);
81
+ }
77
82
  if (report.promises) {
78
83
  (0, promiseConcurrency_1.default)(this);
79
- if (report.promises.patch === 'promise') {
80
- (0, promise_1.default)(this);
81
- }
82
84
  }
83
85
  }
84
86
  // unfortunately @jest/types doesn't export the necessary types,
@@ -99,11 +101,11 @@ const createEnvMixin = (Environment) => {
99
101
  else if (circusEvent.name === 'setup') {
100
102
  this.asyncHookCleaner?.enable();
101
103
  this.asyncHookDetector?.enable();
102
- (0, it_1.default)(this);
103
- (0, hook_1.default)(this, 'beforeEach');
104
- (0, hook_1.default)(this, 'beforeAll');
105
- (0, hook_1.default)(this, 'afterEach');
106
- (0, hook_1.default)(this, 'afterAll');
104
+ (0, it_1.default)(this, circusEvent.runtimeGlobals);
105
+ (0, hook_1.default)(this, 'beforeEach', circusEvent.runtimeGlobals);
106
+ (0, hook_1.default)(this, 'beforeAll', circusEvent.runtimeGlobals);
107
+ (0, hook_1.default)(this, 'afterEach', circusEvent.runtimeGlobals);
108
+ (0, hook_1.default)(this, 'afterAll', circusEvent.runtimeGlobals);
107
109
  }
108
110
  await super.handleEvent?.(circusEvent, circusState);
109
111
  }
@@ -132,6 +134,8 @@ const createEnvMixin = (Environment) => {
132
134
  }
133
135
  finally {
134
136
  (0, cleanupAfterTest_1.default)(this, leakRecord, consts_1.MAIN_THREAD);
137
+ process.stdout.write = this.original.stdout;
138
+ process.stderr.write = this.original.stderr;
135
139
  this.asyncHookCleaner?.disable();
136
140
  // this is added here for safety reasons,
137
141
  // because jest could abort and dont hit teardown event
@@ -140,6 +144,7 @@ const createEnvMixin = (Environment) => {
140
144
  this.aggregatedReport.timers +
141
145
  this.aggregatedReport.fakeTimers +
142
146
  this.aggregatedReport.console +
147
+ this.aggregatedReport.processOutputs +
143
148
  this.aggregatedReport.totalDelay;
144
149
  if (this.reporterTmpDir && hasLeak) {
145
150
  // needs to be sync or will be terminated by jest when an error occurs
@@ -7,6 +7,14 @@ declare const _default: {
7
7
  clearTimeout: typeof clearTimeout;
8
8
  setInterval: typeof setInterval;
9
9
  clearInterval: typeof clearInterval;
10
+ stdout: {
11
+ (buffer: Uint8Array | string, cb?: (err?: Error | null) => void): boolean;
12
+ (str: Uint8Array | string, encoding?: BufferEncoding, cb?: (err?: Error | null) => void): boolean;
13
+ };
14
+ stderr: {
15
+ (buffer: Uint8Array | string, cb?: (err?: Error | null) => void): boolean;
16
+ (str: Uint8Array | string, encoding?: BufferEncoding, cb?: (err?: Error | null) => void): boolean;
17
+ };
10
18
  console: {
11
19
  log: (message?: any, ...optionalParams: any[]) => void;
12
20
  info: (message?: any, ...optionalParams: any[]) => void;
@@ -31,9 +39,12 @@ declare const _default: {
31
39
  fakeTimers: number;
32
40
  console: number;
33
41
  totalDelay: number;
42
+ processOutputs: number;
34
43
  };
35
44
  tearDownError?: Error;
36
45
  asyncIdToPromise: Map<number, Promise<unknown>>;
46
+ asyncRoot: number;
47
+ asyncIdToParentId: Map<number, number>;
37
48
  setup(): Promise<void>;
38
49
  handleEvent(event: unknown, state: unknown): Promise<void>;
39
50
  handleTestEvent: import("@jest/environment").JestEnvironment["handleTestEvent"];
@@ -7,6 +7,14 @@ declare const _default: {
7
7
  clearTimeout: typeof clearTimeout;
8
8
  setInterval: typeof setInterval;
9
9
  clearInterval: typeof clearInterval;
10
+ stdout: {
11
+ (buffer: Uint8Array | string, cb?: (err?: Error | null) => void): boolean;
12
+ (str: Uint8Array | string, encoding?: BufferEncoding, cb?: (err?: Error | null) => void): boolean;
13
+ };
14
+ stderr: {
15
+ (buffer: Uint8Array | string, cb?: (err?: Error | null) => void): boolean;
16
+ (str: Uint8Array | string, encoding?: BufferEncoding, cb?: (err?: Error | null) => void): boolean;
17
+ };
10
18
  console: {
11
19
  log: (message?: any, ...optionalParams: any[]) => void;
12
20
  info: (message?: any, ...optionalParams: any[]) => void;
@@ -31,9 +39,12 @@ declare const _default: {
31
39
  fakeTimers: number;
32
40
  console: number;
33
41
  totalDelay: number;
42
+ processOutputs: number;
34
43
  };
35
44
  tearDownError?: Error;
36
45
  asyncIdToPromise: Map<number, Promise<unknown>>;
46
+ asyncRoot: number;
47
+ asyncIdToParentId: Map<number, number>;
37
48
  setup(): Promise<void>;
38
49
  handleEvent(event: unknown, state: unknown): Promise<void>;
39
50
  handleTestEvent: import("@jest/environment").JestEnvironment["handleTestEvent"];
@@ -1,4 +1,3 @@
1
1
  import type { JestDoctorEnvironment, ConsoleOptions } from '../types';
2
- export declare const isIgnored: (message: string, ignorePatterns: ConsoleOptions["ignore"]) => boolean;
3
2
  declare const patchConsole: (that: JestDoctorEnvironment, consoleOptions: ConsoleOptions) => void;
4
3
  export default patchConsole;
@@ -3,30 +3,19 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.isIgnored = void 0;
7
6
  const node_util_1 = require("node:util");
8
7
  const getStack_1 = __importDefault(require("../utils/getStack"));
9
- const isIgnored = (message, ignorePatterns) => {
10
- return ignorePatterns.some((pattern) => {
11
- if (typeof pattern === 'string') {
12
- return message.includes(pattern);
13
- }
14
- return pattern.test(message);
15
- });
16
- };
17
- exports.isIgnored = isIgnored;
8
+ const isIgnored_1 = __importDefault(require("../utils/isIgnored"));
18
9
  const patchConsole = (that, consoleOptions) => {
19
10
  const env = that.global;
20
11
  for (const consoleMethod of consoleOptions.methods) {
21
12
  const originalMethod = env.console[consoleMethod].bind(env.console);
22
13
  env.console[consoleMethod] = (...args) => {
23
14
  const message = (0, node_util_1.format)(...args);
24
- if (!(0, exports.isIgnored)(message, consoleOptions.ignore)) {
15
+ if (!(0, isIgnored_1.default)(message, consoleOptions.ignore)) {
25
16
  that.leakRecords.get(that.currentTestName)?.console.push({
26
17
  method: consoleMethod,
27
- stack: (0, getStack_1.default)(env.console[consoleMethod], 'Console.' + consoleMethod),
28
- testName: that.currentTestName,
29
- message,
18
+ stack: (0, getStack_1.default)(env.console[consoleMethod]),
30
19
  });
31
20
  }
32
21
  return originalMethod(...args);
@@ -4,9 +4,11 @@ const node_async_hooks_1 = require("node:async_hooks");
4
4
  const createAsyncHookCleaner = (that) => {
5
5
  return (0, node_async_hooks_1.createHook)({
6
6
  promiseResolve(asyncId) {
7
+ that.asyncIdToParentId.delete(asyncId);
7
8
  const owner = that.promiseOwner.get(asyncId);
8
- if (!owner)
9
+ if (!owner) {
9
10
  return;
11
+ }
10
12
  const promise = that.asyncIdToPromise.get(asyncId);
11
13
  if (promise) {
12
14
  that.leakRecords.get(owner)?.promises.delete(promise);
@@ -1,3 +1,3 @@
1
- import type { JestDoctorEnvironment } from '../types';
1
+ import { JestDoctorEnvironment } from '../types';
2
2
  declare const createAsyncHookDetector: (that: JestDoctorEnvironment) => import("async_hooks").AsyncHook;
3
3
  export default createAsyncHookDetector;
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const node_async_hooks_1 = require("node:async_hooks");
7
+ const getStack_1 = __importDefault(require("../utils/getStack"));
8
+ const isIgnored_1 = __importDefault(require("../utils/isIgnored"));
9
+ const createAsyncHookDetector = (that) => {
10
+ const init = (asyncId, type, parentAsyncId, resource) => {
11
+ if (type !== 'PROMISE') {
12
+ return;
13
+ }
14
+ that.asyncIdToParentId.set(asyncId, parentAsyncId);
15
+ const stack = (0, getStack_1.default)(init);
16
+ if (!(0, isIgnored_1.default)(stack, that.options.report.promises.ignore)) {
17
+ const owner = that.currentTestName;
18
+ that.promiseOwner.set(asyncId, owner);
19
+ const promise = resource;
20
+ that.asyncIdToPromise.set(asyncId, promise);
21
+ that.leakRecords.get(owner)?.promises.set(promise, {
22
+ stack,
23
+ asyncId,
24
+ parentAsyncId,
25
+ });
26
+ }
27
+ };
28
+ return (0, node_async_hooks_1.createHook)({ init });
29
+ };
30
+ exports.default = createAsyncHookDetector;
@@ -1,3 +1,3 @@
1
- import type { JestDoctorEnvironment } from '../types';
1
+ import { JestDoctorEnvironment } from '../types';
2
2
  declare const patchFakeTimers: (that: JestDoctorEnvironment) => void;
3
3
  export default patchFakeTimers;
@@ -3,8 +3,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- const node_console_1 = __importDefault(require("node:console"));
7
6
  const getStack_1 = __importDefault(require("../utils/getStack"));
7
+ const isIgnored_1 = __importDefault(require("../utils/isIgnored"));
8
+ const chalk_1 = __importDefault(require("chalk"));
8
9
  const patchFakeTimers = (that) => {
9
10
  const modernFakeTimers = that.fakeTimersModern;
10
11
  const fakeTimers = modernFakeTimers?._fakeTimers;
@@ -27,22 +28,28 @@ const patchFakeTimers = (that) => {
27
28
  fakeTimeout?.delete(timerId);
28
29
  callback();
29
30
  }, delay);
30
- fakeTimeout?.set(timerId, {
31
- type: 'fakeTimeout',
32
- delay: delay || 0,
33
- stack: (0, getStack_1.default)(that.global.setTimeout, 'setTimeout'),
34
- testName: that.currentTestName,
35
- });
31
+ const stack = (0, getStack_1.default)(that.global.setTimeout);
32
+ if (!(0, isIgnored_1.default)(stack, that.options.report.fakeTimers.ignore)) {
33
+ fakeTimeout?.set(timerId, {
34
+ type: 'fakeTimeout',
35
+ delay: delay || 0,
36
+ stack,
37
+ });
38
+ }
36
39
  return timerId;
37
40
  };
38
41
  clock.setInterval = function (callback, delay) {
39
42
  const intervalId = originalFakeSetInterval(callback, delay);
40
- that.leakRecords.get(that.currentTestName)?.fakeTimers.set(intervalId, {
41
- type: 'fakeInterval',
42
- delay: delay || 0,
43
- stack: (0, getStack_1.default)(that.global.setInterval, 'setInterval'),
44
- testName: that.currentTestName,
45
- });
43
+ const stack = (0, getStack_1.default)(that.global.setInterval);
44
+ if (!(0, isIgnored_1.default)(stack, that.options.report.fakeTimers.ignore)) {
45
+ that.leakRecords
46
+ .get(that.currentTestName)
47
+ ?.fakeTimers.set(intervalId, {
48
+ type: 'fakeInterval',
49
+ delay: delay || 0,
50
+ stack,
51
+ });
52
+ }
46
53
  return intervalId;
47
54
  };
48
55
  clock.clearTimeout = (timerId) => {
@@ -59,7 +66,7 @@ const patchFakeTimers = (that) => {
59
66
  };
60
67
  }
61
68
  else {
62
- node_console_1.default.warn('Fake timers could not be mocked!');
69
+ that.original.stderr(chalk_1.default.yellow('\nFake timers could not be mocked!'));
63
70
  }
64
71
  };
65
72
  exports.default = patchFakeTimers;
@@ -1,3 +1,3 @@
1
- import type { JestDoctorEnvironment } from '../types';
2
- declare const patchHook: (that: JestDoctorEnvironment, hookName: string) => void;
1
+ import { JestDoctorEnvironment, RuntimeGlobals } from '../types';
2
+ declare const patchHook: (that: JestDoctorEnvironment, hookName: string, runtimeGlobals: RuntimeGlobals) => void;
3
3
  export default patchHook;
@@ -4,18 +4,19 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const analyzeCallback_1 = __importDefault(require("../utils/analyzeCallback"));
7
- const patchHook = (that, hookName) => {
7
+ const patchHook = (that, hookName, runtimeGlobals) => {
8
8
  const originalHook = that.global[hookName];
9
- that.global[hookName] = function (callback, timeout) {
10
- const hookMock = function () {
11
- if (hookName === 'afterEach') {
12
- that.currentAfterEachCount -= 1;
13
- }
14
- return (0, analyzeCallback_1.default)(that, callback, this);
9
+ that.global[hookName] = runtimeGlobals[hookName] =
10
+ function (callback, timeout) {
11
+ const hookMock = function () {
12
+ if (hookName === 'afterEach') {
13
+ that.currentAfterEachCount -= 1;
14
+ }
15
+ return (0, analyzeCallback_1.default)(that, callback, this);
16
+ };
17
+ // unfortunately, jest types the return type as any
18
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return
19
+ return originalHook(hookMock, timeout);
15
20
  };
16
- // unfortunately, jest types the return type as any
17
- // eslint-disable-next-line @typescript-eslint/no-unsafe-return
18
- return originalHook(hookMock, timeout);
19
- };
20
21
  };
21
22
  exports.default = patchHook;
@@ -1,3 +1,3 @@
1
- import type { JestDoctorEnvironment } from '../types';
2
- declare const patchIt: (that: JestDoctorEnvironment) => void;
1
+ import { JestDoctorEnvironment, RuntimeGlobals } from '../types';
2
+ declare const patchIt: (that: JestDoctorEnvironment, runtimeGlobals: RuntimeGlobals) => void;
3
3
  export default patchIt;
package/dist/patch/it.js CHANGED
@@ -4,25 +4,23 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const analyzeCallback_1 = __importDefault(require("../utils/analyzeCallback"));
7
- const node_console_1 = __importDefault(require("node:console"));
8
- const patchIt = (that) => {
9
- const originalIt = that.global.it;
10
- if (originalIt) {
11
- const originalOnly = that.global.it.only;
12
- const createItPatch = (originalFn) => (testName, testFunction, timeout) => {
13
- const testHandler = function () {
14
- return (0, analyzeCallback_1.default)(that, testFunction, this);
15
- };
16
- return originalFn(testName, testHandler, timeout);
7
+ const patchIt = (that, runtimeGlobals) => {
8
+ const originalIt = runtimeGlobals.it;
9
+ const originalOnly = originalIt.only;
10
+ const createItPatch = (originalFn) => (testName, testFunction, timeout) => {
11
+ const testHandler = function () {
12
+ return (0, analyzeCallback_1.default)(that, testFunction, this);
17
13
  };
18
- const itPatch = createItPatch(originalIt);
19
- Object.assign(itPatch, originalIt);
20
- that.global.it = that.global.test = itPatch;
21
- that.global.it.concurrent = that.global.it;
22
- that.global.it.only = createItPatch(originalOnly);
23
- }
24
- else {
25
- node_console_1.default.warn('injectGlobal it is set to false, this will impact on leak detection!');
14
+ return originalFn(testName, testHandler, timeout);
15
+ };
16
+ const itPatch = createItPatch(originalIt);
17
+ const test = Object.assign(itPatch, originalIt, {
18
+ concurrent: itPatch,
19
+ only: createItPatch(originalOnly),
20
+ });
21
+ if (that.global.test) {
22
+ that.global.it = that.global.test = test;
26
23
  }
24
+ runtimeGlobals.it = runtimeGlobals.test = test;
27
25
  };
28
26
  exports.default = patchIt;
@@ -0,0 +1,3 @@
1
+ import { type OutputOptions, JestDoctorEnvironment } from '../types';
2
+ declare const patchProcessOutput: (that: JestDoctorEnvironment, outputOptions: OutputOptions) => void;
3
+ export default patchProcessOutput;
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const getStack_1 = __importDefault(require("../utils/getStack"));
7
+ const isIgnored_1 = __importDefault(require("../utils/isIgnored"));
8
+ const patchProcessOutput = (that, outputOptions) => {
9
+ const createOutputPatch = (method) => {
10
+ const patch = (...args) => {
11
+ const stack = (0, getStack_1.default)(patch);
12
+ if (!stack.includes('node_modules/@jest/console')) {
13
+ const owner = that.currentTestName;
14
+ const message = args[0].toString();
15
+ if (!(0, isIgnored_1.default)(message, outputOptions.ignore)) {
16
+ that.leakRecords.get(owner)?.processOutputs.push({
17
+ method,
18
+ stack,
19
+ });
20
+ }
21
+ }
22
+ return that.original[method](...args);
23
+ };
24
+ return patch;
25
+ };
26
+ outputOptions.methods.forEach((method) => {
27
+ process[method].write = createOutputPatch(method);
28
+ });
29
+ };
30
+ exports.default = patchProcessOutput;
@@ -1,26 +1,63 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ const node_async_hooks_1 = require("node:async_hooks");
3
4
  const patchPromiseConcurrency = (that) => {
4
5
  const env = that.global;
6
+ const getRootIds = () => {
7
+ let triggerId = (0, node_async_hooks_1.executionAsyncId)();
8
+ const rootIds = [triggerId];
9
+ while (triggerId !== that.asyncRoot) {
10
+ triggerId = that.asyncIdToParentId.get(triggerId);
11
+ rootIds.push(triggerId);
12
+ }
13
+ return rootIds;
14
+ };
15
+ const cleanPromise = (promises, promise, asyncId) => {
16
+ that.asyncIdToPromise.delete(asyncId);
17
+ that.promiseOwner.delete(asyncId);
18
+ that.asyncIdToParentId.delete(asyncId);
19
+ promises.delete(promise);
20
+ };
21
+ const removeChildPromises = (promises, concurrentPromises, rootIds) => {
22
+ if (promises) {
23
+ concurrentPromises.forEach((concurrentPromise) => {
24
+ const leak = promises.get(concurrentPromise);
25
+ if (leak) {
26
+ let asyncId = leak.asyncId;
27
+ // Promise.race, Promise.any and Promise.all will create a new Promise for every entry that needs to be deleted
28
+ promises.forEach((childLeak, key) => {
29
+ if (childLeak.parentAsyncId === asyncId) {
30
+ cleanPromise(promises, key, childLeak.asyncId);
31
+ }
32
+ });
33
+ let promiseToDelete = concurrentPromise;
34
+ while (!rootIds.includes(asyncId)) {
35
+ const parentId = that.asyncIdToParentId.get(asyncId);
36
+ cleanPromise(promises, promiseToDelete, asyncId);
37
+ asyncId = parentId;
38
+ promiseToDelete = that.asyncIdToPromise.get(asyncId);
39
+ }
40
+ }
41
+ });
42
+ }
43
+ };
5
44
  const concurrencyFactor = (fn) => (concurrentPromises) => {
6
45
  const promises = that.leakRecords.get(that.currentTestName)?.promises;
7
- // remove all related promises is a concurrent promise ends
8
- const removePromises = () => {
9
- if (promises) {
10
- concurrentPromises.forEach((racePromise) => {
11
- promises.delete(racePromise);
12
- });
13
- }
14
- };
15
- return fn(concurrentPromises).then((returnValues) => {
16
- removePromises();
17
- return returnValues;
18
- }, (error) => {
19
- removePromises();
20
- throw error;
46
+ const rootIds = getRootIds();
47
+ return fn(concurrentPromises).finally(() => {
48
+ removeChildPromises(promises, concurrentPromises, rootIds);
21
49
  });
22
50
  };
23
51
  env.Promise.race = concurrencyFactor(env.Promise.race.bind(env.Promise));
24
52
  env.Promise.any = concurrencyFactor(env.Promise.any.bind(env.Promise));
53
+ const originalPromiseAll = env.Promise.all.bind(env.Promise);
54
+ env.Promise.all = (concurrentPromises) => {
55
+ const promises = that.leakRecords.get(that.currentTestName)?.promises;
56
+ const rootIds = getRootIds();
57
+ return originalPromiseAll(concurrentPromises).catch((error) => {
58
+ removeChildPromises(promises, concurrentPromises, rootIds);
59
+ throw error;
60
+ });
61
+ };
25
62
  };
26
63
  exports.default = patchPromiseConcurrency;
@@ -1,3 +1,3 @@
1
- import type { JestDoctorEnvironment } from '../types';
1
+ import { JestDoctorEnvironment } from '../types';
2
2
  declare const timers: (that: JestDoctorEnvironment) => void;
3
3
  export default timers;
@@ -5,13 +5,17 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const getStack_1 = __importDefault(require("../utils/getStack"));
7
7
  const consts_1 = require("../consts");
8
+ const isIgnored_1 = __importDefault(require("../utils/isIgnored"));
8
9
  const timers = (that) => {
9
10
  const env = that.global;
11
+ const report = that.options.report;
10
12
  env.setTimeout = Object.assign(function (callback, delay) {
11
13
  const owner = that.currentTestName;
12
14
  const leakRecord = that.leakRecords.get(owner);
15
+ const stack = (0, getStack_1.default)(env.setTimeout);
16
+ const isAllowed = report.timers && !(0, isIgnored_1.default)(stack, report.timers.ignore);
13
17
  const timerId = that.original.setTimeout(() => {
14
- if (leakRecord) {
18
+ if (leakRecord && isAllowed) {
15
19
  if (owner !== consts_1.MAIN_THREAD && delay) {
16
20
  leakRecord.totalDelay += delay;
17
21
  }
@@ -22,27 +26,27 @@ const timers = (that) => {
22
26
  leakRecord?.timers.set(timerId, {
23
27
  type: 'timeout',
24
28
  delay: delay || 0,
25
- stack: (0, getStack_1.default)(env.setTimeout, 'fake setTimeout'),
26
- testName: owner,
29
+ stack,
30
+ isAllowed,
27
31
  });
28
32
  return timerId;
29
33
  }, that.original.setTimeout);
30
34
  env.setInterval = Object.assign(function (callback, delay) {
31
35
  const owner = that.currentTestName;
36
+ const leakRecord = that.leakRecords.get(owner);
37
+ const stack = (0, getStack_1.default)(env.setInterval);
38
+ const isAllowed = report.timers && !(0, isIgnored_1.default)(stack, report.timers.ignore);
32
39
  const intervalId = that.original.setInterval(() => {
33
- if (owner !== consts_1.MAIN_THREAD && delay) {
34
- const leakRecord = that.leakRecords.get(owner);
35
- if (leakRecord) {
36
- leakRecord.totalDelay += delay;
37
- }
40
+ if (isAllowed && owner !== consts_1.MAIN_THREAD && delay && leakRecord) {
41
+ leakRecord.totalDelay += delay;
38
42
  }
39
43
  callback();
40
44
  }, delay);
41
45
  that.leakRecords.get(owner)?.timers.set(intervalId, {
42
46
  type: 'interval',
43
47
  delay: delay || 0,
44
- stack: (0, getStack_1.default)(env.setInterval, 'fake setInterval'),
45
- testName: that.currentTestName,
48
+ isAllowed,
49
+ stack,
46
50
  });
47
51
  return intervalId;
48
52
  }, that.original.setInterval);