Skip to content

Commit 3d89331

Browse files
joyeecheungaduh95
authored andcommitted
test_runner: use module.registerHooks in module mocks
Migrate away from module.register(). This no longer needs to deal with the worker synchronization. PR-URL: #60326 Reviewed-By: Marco Ippolito <[email protected]> Reviewed-By: Chemi Atlow <[email protected]> Reviewed-By: Yagiz Nizipli <[email protected]> Reviewed-By: Geoffrey Booth <[email protected]>
1 parent 063fbd8 commit 3d89331

File tree

5 files changed

+54
-190
lines changed

5 files changed

+54
-190
lines changed

lib/internal/test_runner/coverage.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ const {
3737
},
3838
} = require('internal/errors');
3939
const { matchGlobPattern } = require('internal/fs/glob');
40-
const { kMockSearchParam } = require('internal/test_runner/mock/mock');
40+
const { constants: { kMockSearchParam } } = require('internal/test_runner/mock/loader');
4141

4242
const kCoverageFileRegex = /^coverage-(\d+)-(\d{13})-(\d+)\.json$/;
4343
const kIgnoreRegex = /\/\* node:coverage ignore next (?<count>\d+ )?\*\//;

lib/internal/test_runner/mock/loader.js

Lines changed: 20 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,77 +1,24 @@
11
'use strict';
22
const {
3-
AtomicsNotify,
4-
AtomicsStore,
53
JSONStringify,
64
SafeMap,
75
} = primordials;
8-
const {
9-
kBadExportsMessage,
10-
kMockSearchParam,
11-
kMockSuccess,
12-
kMockExists,
13-
kMockUnknownMessage,
14-
} = require('internal/test_runner/mock/mock');
6+
7+
const kMockSearchParam = 'node-test-mock';
8+
const kBadExportsMessage = 'Cannot create mock because named exports ' +
9+
'cannot be applied to the provided default export.';
10+
1511
const { URL, URLParse } = require('internal/url');
1612
let debug = require('internal/util/debuglog').debuglog('test_runner', (fn) => {
1713
debug = fn;
1814
});
1915

20-
// TODO(cjihrig): The mocks need to be thread aware because the exports are
21-
// evaluated on the thread that creates the mock. Before marking this API as
22-
// stable, one of the following issues needs to be implemented:
23-
// https://github.com/nodejs/node/issues/49472
24-
// or https://github.com/nodejs/node/issues/52219
25-
2616
const mocks = new SafeMap();
2717

28-
async function initialize(data) {
29-
data?.port.on('message', ({ type, payload }) => {
30-
debug('mock loader received message type "%s" with payload %o', type, payload);
31-
32-
if (type === 'node:test:register') {
33-
const { baseURL } = payload;
34-
const mock = mocks.get(baseURL);
35-
36-
if (mock?.active) {
37-
debug('already mocking "%s"', baseURL);
38-
sendAck(payload.ack, kMockExists);
39-
return;
40-
}
41-
42-
const localVersion = mock?.localVersion ?? 0;
43-
44-
debug('new mock version %d for "%s"', localVersion, baseURL);
45-
mocks.set(baseURL, {
46-
__proto__: null,
47-
active: true,
48-
cache: payload.cache,
49-
exportNames: payload.exportNames,
50-
format: payload.format,
51-
hasDefaultExport: payload.hasDefaultExport,
52-
localVersion,
53-
url: baseURL,
54-
});
55-
sendAck(payload.ack);
56-
} else if (type === 'node:test:unregister') {
57-
const mock = mocks.get(payload.baseURL);
58-
59-
if (mock !== undefined) {
60-
mock.active = false;
61-
mock.localVersion++;
62-
}
63-
64-
sendAck(payload.ack);
65-
} else {
66-
sendAck(payload.ack, kMockUnknownMessage);
67-
}
68-
});
69-
}
70-
71-
async function resolve(specifier, context, nextResolve) {
18+
function resolve(specifier, context, nextResolve) {
7219
debug('resolve hook entry, specifier = "%s", context = %o', specifier, context);
7320

74-
const nextResolveResult = await nextResolve(specifier, context);
21+
const nextResolveResult = nextResolve(specifier, context);
7522
const mockSpecifier = nextResolveResult.url;
7623

7724
const mock = mocks.get(mockSpecifier);
@@ -95,7 +42,7 @@ async function resolve(specifier, context, nextResolve) {
9542
return { __proto__: null, url: href, format: nextResolveResult.format };
9643
}
9744

98-
async function load(url, context, nextLoad) {
45+
function load(url, context, nextLoad) {
9946
debug('load hook entry, url = "%s", context = %o', url, context);
10047
const parsedURL = URLParse(url);
10148
if (parsedURL) {
@@ -105,7 +52,7 @@ async function load(url, context, nextLoad) {
10552
const baseURL = parsedURL ? parsedURL.href : url;
10653
const mock = mocks.get(baseURL);
10754

108-
const original = await nextLoad(url, context);
55+
const original = nextLoad(url, context);
10956
debug('load hook, mock = %o', mock);
11057
if (mock?.active !== true) {
11158
return original;
@@ -130,14 +77,14 @@ async function load(url, context, nextLoad) {
13077
__proto__: null,
13178
format,
13279
shortCircuit: true,
133-
source: await createSourceFromMock(mock, format),
80+
source: createSourceFromMock(mock, format),
13481
};
13582

13683
debug('load hook finished, result = %o', result);
13784
return result;
13885
}
13986

140-
async function createSourceFromMock(mock, format) {
87+
function createSourceFromMock(mock, format) {
14188
// Create mock implementation from provided exports.
14289
const { exportNames, hasDefaultExport, url } = mock;
14390
const useESM = format === 'module' || format === 'module-typescript';
@@ -196,9 +143,12 @@ if (module.exports === null || typeof module.exports !== 'object') {
196143
return source;
197144
}
198145

199-
function sendAck(buf, status = kMockSuccess) {
200-
AtomicsStore(buf, 0, status);
201-
AtomicsNotify(buf, 0);
202-
}
203-
204-
module.exports = { initialize, load, resolve };
146+
module.exports = {
147+
hooks: { __proto__: null, load, resolve },
148+
mocks,
149+
constants: {
150+
__proto__: null,
151+
kBadExportsMessage,
152+
kMockSearchParam,
153+
},
154+
};

lib/internal/test_runner/mock/mock.js

Lines changed: 33 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,9 @@
22
const {
33
ArrayPrototypePush,
44
ArrayPrototypeSlice,
5-
AtomicsStore,
6-
AtomicsWait,
75
Error,
86
FunctionPrototypeBind,
97
FunctionPrototypeCall,
10-
Int32Array,
118
ObjectDefineProperty,
129
ObjectGetOwnPropertyDescriptor,
1310
ObjectGetPrototypeOf,
@@ -19,9 +16,6 @@ const {
1916
SafeMap,
2017
StringPrototypeSlice,
2118
StringPrototypeStartsWith,
22-
globalThis: {
23-
SharedArrayBuffer,
24-
},
2519
} = primordials;
2620
const {
2721
codes: {
@@ -54,19 +48,10 @@ const {
5448
validateOneOf,
5549
} = require('internal/validators');
5650
const { MockTimers } = require('internal/test_runner/mock/mock_timers');
57-
const { strictEqual, notStrictEqual } = require('assert');
5851
const { Module } = require('internal/modules/cjs/loader');
59-
const { MessageChannel } = require('worker_threads');
6052
const { _load, _nodeModulePaths, _resolveFilename, isBuiltin } = Module;
6153
function kDefaultFunction() {}
6254
const enableModuleMocking = getOptionValue('--experimental-test-module-mocks');
63-
const kMockSearchParam = 'node-test-mock';
64-
const kMockSuccess = 1;
65-
const kMockExists = 2;
66-
const kMockUnknownMessage = 3;
67-
const kWaitTimeout = 5_000;
68-
const kBadExportsMessage = 'Cannot create mock because named exports ' +
69-
'cannot be applied to the provided default export.';
7055
const kSupportedFormats = [
7156
'builtin',
7257
'commonjs-typescript',
@@ -76,6 +61,11 @@ const kSupportedFormats = [
7661
'module',
7762
];
7863
let sharedModuleState;
64+
const {
65+
hooks: mockHooks,
66+
mocks,
67+
constants: { kBadExportsMessage, kMockSearchParam },
68+
} = require('internal/test_runner/mock/loader');
7969

8070
class MockFunctionContext {
8171
#calls;
@@ -201,8 +191,8 @@ class MockModuleContext {
201191
hasDefaultExport,
202192
namedExports,
203193
sharedState,
194+
specifier,
204195
}) {
205-
const ack = new Int32Array(new SharedArrayBuffer(4));
206196
const config = {
207197
__proto__: null,
208198
cache,
@@ -218,28 +208,36 @@ class MockModuleContext {
218208
this.#sharedState = sharedState;
219209
this.#restore = {
220210
__proto__: null,
221-
ack,
222211
baseURL,
223212
cached: fullPath in Module._cache,
224213
format,
225214
fullPath,
226215
value: Module._cache[fullPath],
227216
};
228217

229-
sharedState.loaderPort.postMessage({
230-
__proto__: null,
231-
type: 'node:test:register',
232-
payload: {
218+
const mock = mocks.get(baseURL);
219+
220+
if (mock?.active) {
221+
debug('already mocking "%s"', baseURL);
222+
throw new ERR_INVALID_STATE(
223+
`Cannot mock '${specifier}'. The module is already mocked.`,
224+
);
225+
} else {
226+
const localVersion = mock?.localVersion ?? 0;
227+
228+
debug('new mock version %d for "%s"', localVersion, baseURL);
229+
mocks.set(baseURL, {
233230
__proto__: null,
234-
ack,
235-
baseURL,
231+
url: baseURL,
236232
cache,
237233
exportNames: ObjectKeys(namedExports),
238234
hasDefaultExport,
239235
format,
240-
},
241-
});
242-
waitForAck(ack);
236+
localVersion,
237+
active: true,
238+
});
239+
}
240+
243241
delete Module._cache[fullPath];
244242
sharedState.mockExports.set(baseURL, {
245243
__proto__: null,
@@ -261,17 +259,12 @@ class MockModuleContext {
261259
Module._cache[this.#restore.fullPath] = this.#restore.value;
262260
}
263261

264-
AtomicsStore(this.#restore.ack, 0, 0);
265-
this.#sharedState.loaderPort.postMessage({
266-
__proto__: null,
267-
type: 'node:test:unregister',
268-
payload: {
269-
__proto__: null,
270-
ack: this.#restore.ack,
271-
baseURL: this.#restore.baseURL,
272-
},
273-
});
274-
waitForAck(this.#restore.ack);
262+
const mock = mocks.get(this.#restore.baseURL);
263+
264+
if (mock !== undefined) {
265+
mock.active = false;
266+
mock.localVersion++;
267+
}
275268

276269
this.#sharedState.mockMap.delete(this.#restore.baseURL);
277270
this.#sharedState.mockMap.delete(this.#restore.fullPath);
@@ -654,7 +647,7 @@ class MockTracker {
654647
const hasFileProtocol = StringPrototypeStartsWith(filename, 'file://');
655648
const caller = hasFileProtocol ? filename : pathToFileURL(filename).href;
656649
const { format, url } = sharedState.moduleLoader.resolveSync(
657-
mockSpecifier, caller, null,
650+
mockSpecifier, caller, kEmptyObject,
658651
);
659652
debug('module mock, url = "%s", format = "%s", caller = "%s"', url, format, caller);
660653
if (format) { // Format is not yet known for ambiguous files when detection is enabled.
@@ -828,20 +821,13 @@ function setupSharedModuleState() {
828821
if (sharedModuleState === undefined) {
829822
const { mock } = require('test');
830823
const mockExports = new SafeMap();
831-
const { port1, port2 } = new MessageChannel();
824+
const { registerHooks } = require('internal/modules/customization_hooks');
832825
const moduleLoader = esmLoader.getOrInitializeCascadedLoader();
833826

834-
moduleLoader.register(
835-
'internal/test_runner/mock/loader',
836-
'node:',
837-
{ __proto__: null, port: port2 },
838-
[port2],
839-
true,
840-
);
827+
registerHooks(mockHooks);
841828

842829
sharedModuleState = {
843830
__proto__: null,
844-
loaderPort: port1,
845831
mockExports,
846832
mockMap: new SafeMap(),
847833
moduleLoader,
@@ -941,13 +927,6 @@ function findMethodOnPrototypeChain(instance, methodName) {
941927
return descriptor;
942928
}
943929

944-
function waitForAck(buf) {
945-
const result = AtomicsWait(buf, 0, 0, kWaitTimeout);
946-
947-
notStrictEqual(result, 'timed-out', 'test mocking synchronization failed');
948-
strictEqual(buf[0], kMockSuccess);
949-
}
950-
951930
function ensureNodeScheme(specifier) {
952931
if (!StringPrototypeStartsWith(specifier, 'node:')) {
953932
return `node:${specifier}`;
@@ -962,10 +941,5 @@ if (!enableModuleMocking) {
962941

963942
module.exports = {
964943
ensureNodeScheme,
965-
kBadExportsMessage,
966-
kMockSearchParam,
967-
kMockSuccess,
968-
kMockExists,
969-
kMockUnknownMessage,
970944
MockTracker,
971945
};

test/parallel/test-permission-dc-worker-threads.js

Lines changed: 0 additions & 19 deletions
This file was deleted.

0 commit comments

Comments
 (0)