Skip to content
File

Blob: src/workerd/api/node/tests/worker_threads-nodejs-test.js

javascript396 lines
1// Copyright (c) 2025 Cloudflare, Inc.
2// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3// https://opensource.org/licenses/Apache-2.0
4import { strictEqual, throws, ok, deepStrictEqual, rejects } from 'node:assert';
5import * as worker_threads from 'node:worker_threads';
6import {
7 BroadcastChannel,
8 MessageChannel,
9 MessagePort,
10 Worker,
11 SHARE_ENV,
12 getEnvironmentData,
13 isMainThread,
14 isMarkedAsUntransferable,
15 markAsUntransferable,
16 markAsUncloneable,
17 moveMessagePortToContext,
18 parentPort,
19 receiveMessageOnPort,
20 resourceLimits,
21 setEnvironmentData,
22 postMessageToThread,
23 threadId,
24 workerData,
25 isInternalThread,
26} from 'node:worker_threads';
27 
28export const testExports = {
29 async test() {
30 strictEqual(typeof BroadcastChannel, 'function');
31 strictEqual(typeof MessageChannel, 'function');
32 strictEqual(typeof MessagePort, 'function');
33 strictEqual(typeof Worker, 'function');
34 strictEqual(typeof getEnvironmentData, 'function');
35 strictEqual(typeof setEnvironmentData, 'function');
36 strictEqual(typeof isMarkedAsUntransferable, 'function');
37 strictEqual(typeof markAsUntransferable, 'function');
38 strictEqual(typeof markAsUncloneable, 'function');
39 strictEqual(typeof moveMessagePortToContext, 'function');
40 strictEqual(typeof receiveMessageOnPort, 'function');
41 strictEqual(typeof postMessageToThread, 'function');
42 
43 strictEqual(typeof isMainThread, 'boolean');
44 strictEqual(typeof SHARE_ENV, 'symbol');
45 strictEqual(typeof resourceLimits, 'object');
46 strictEqual(typeof threadId, 'number');
47 strictEqual(typeof isInternalThread, 'boolean');
48 
49 strictEqual(worker_threads.default.BroadcastChannel, BroadcastChannel);
50 strictEqual(worker_threads.default.MessageChannel, MessageChannel);
51 strictEqual(worker_threads.default.MessagePort, MessagePort);
52 strictEqual(worker_threads.default.Worker, Worker);
53 strictEqual(worker_threads.default.getEnvironmentData, getEnvironmentData);
54 strictEqual(worker_threads.default.setEnvironmentData, setEnvironmentData);
55 strictEqual(worker_threads.default.isMainThread, isMainThread);
56 strictEqual(
57 worker_threads.default.isMarkedAsUntransferable,
58 isMarkedAsUntransferable
59 );
60 strictEqual(
61 worker_threads.default.markAsUntransferable,
62 markAsUntransferable
63 );
64 strictEqual(worker_threads.default.markAsUncloneable, markAsUncloneable);
65 strictEqual(
66 worker_threads.default.moveMessagePortToContext,
67 moveMessagePortToContext
68 );
69 strictEqual(worker_threads.default.parentPort, parentPort);
70 strictEqual(
71 worker_threads.default.receiveMessageOnPort,
72 receiveMessageOnPort
73 );
74 strictEqual(worker_threads.default.resourceLimits, resourceLimits);
75 strictEqual(worker_threads.default.SHARE_ENV, SHARE_ENV);
76 strictEqual(
77 worker_threads.default.postMessageToThread,
78 postMessageToThread
79 );
80 strictEqual(worker_threads.default.threadId, threadId);
81 strictEqual(worker_threads.default.workerData, workerData);
82 strictEqual(worker_threads.default.isInternalThread, isInternalThread);
83 },
84};
85 
86export const testBroadcastChannelConstructor = {
87 async test() {
88 throws(
89 () => {
90 new BroadcastChannel();
91 },
92 {
93 code: 'ERR_METHOD_NOT_IMPLEMENTED',
94 }
95 );
96 },
97};
98 
99export const testWorkerConstructor = {
100 async test() {
101 throws(
102 () => {
103 new Worker();
104 },
105 {
106 code: 'ERR_METHOD_NOT_IMPLEMENTED',
107 }
108 );
109 },
110};
111 
112export const testWorkerProperties = {
113 async test() {
114 const proto = Worker.prototype;
115 
116 strictEqual(typeof proto.postMessage, 'function');
117 strictEqual(typeof proto.postMessageToThread, 'function');
118 strictEqual(typeof proto.ref, 'function');
119 strictEqual(typeof proto.unref, 'function');
120 strictEqual(typeof proto.terminate, 'function');
121 strictEqual(typeof proto.getHeapSnapshot, 'function');
122 strictEqual(typeof proto.getHeapStatistics, 'function');
123 
124 ok(Symbol.asyncDispose in proto);
125 strictEqual(typeof proto[Symbol.asyncDispose], 'function');
126 },
127};
128 
129export const testMessageChannelAndPort = {
130 async test() {
131 // Only check reference equality if the global MessageChannel is exposed.
132 // The node:worker_threads module now imports from cloudflare-internal:messagechannel
133 // so it works independently of the expose_global_message_channel compat flag.
134 if (typeof globalThis.MessageChannel !== 'undefined') {
135 strictEqual(MessageChannel, globalThis.MessageChannel);
136 strictEqual(MessagePort, globalThis.MessagePort);
137 }
138 
139 const channel = new MessageChannel();
140 ok(channel instanceof MessageChannel);
141 ok(channel.port1 instanceof MessagePort);
142 ok(channel.port2 instanceof MessagePort);
143 },
144};
145 
146export const testEnvironmentData = {
147 async test() {
148 const key1 = 'test_key_1';
149 const key2 = 'test_key_2';
150 const value1 = 'test_value_1';
151 const value2 = { test: 'object' };
152 
153 strictEqual(getEnvironmentData(key1), undefined);
154 strictEqual(getEnvironmentData(key2), undefined);
155 
156 setEnvironmentData(key1, value1);
157 strictEqual(getEnvironmentData(key1), value1);
158 
159 setEnvironmentData(key2, value2);
160 deepStrictEqual(getEnvironmentData(key2), value2);
161 
162 setEnvironmentData(key1, null);
163 strictEqual(getEnvironmentData(key1), null);
164 },
165};
166 
167export const testIsMainThread = {
168 async test() {
169 strictEqual(isMainThread, true);
170 strictEqual(typeof isMainThread, 'boolean');
171 },
172};
173 
174export const testIsMarkedAsUntransferable = {
175 async test() {
176 strictEqual(isMarkedAsUntransferable({}), false);
177 strictEqual(isMarkedAsUntransferable(null), false);
178 strictEqual(isMarkedAsUntransferable(undefined), false);
179 strictEqual(isMarkedAsUntransferable(123), false);
180 strictEqual(isMarkedAsUntransferable('string'), false);
181 strictEqual(isMarkedAsUntransferable(new ArrayBuffer(8)), false);
182 },
183};
184 
185export const testMarkAsUntransferable = {
186 async test() {
187 const obj = {};
188 const buffer = new ArrayBuffer(8);
189 
190 markAsUntransferable(obj);
191 markAsUntransferable(buffer);
192 markAsUntransferable(null);
193 markAsUntransferable(undefined);
194 
195 ok(true);
196 },
197};
198 
199export const testMarkAsUncloneable = {
200 async test() {
201 const obj = {};
202 const buffer = new ArrayBuffer(8);
203 
204 markAsUncloneable(obj);
205 markAsUncloneable(buffer);
206 markAsUncloneable(null);
207 markAsUncloneable(undefined);
208 
209 ok(true);
210 },
211};
212 
213export const testParentPort = {
214 async test() {
215 strictEqual(parentPort, null);
216 },
217};
218 
219export const testReceiveMessageOnPort = {
220 async test() {
221 const channel = new MessageChannel();
222 
223 strictEqual(receiveMessageOnPort(channel.port1), undefined);
224 strictEqual(receiveMessageOnPort(channel.port2), undefined);
225 },
226};
227 
228export const testSHARE_ENV = {
229 async test() {
230 strictEqual(typeof SHARE_ENV, 'symbol');
231 strictEqual(SHARE_ENV, Symbol.for('nodejs.worker_threads.SHARE_ENV'));
232 },
233};
234 
235export const testResourceLimits = {
236 async test() {
237 strictEqual(typeof resourceLimits, 'object');
238 ok(resourceLimits !== null);
239 deepStrictEqual(resourceLimits, {});
240 },
241};
242 
243export const testThreadId = {
244 async test() {
245 strictEqual(typeof threadId, 'number');
246 strictEqual(threadId, 0);
247 },
248};
249 
250export const testWorkerData = {
251 async test() {
252 strictEqual(workerData, null);
253 },
254};
255 
256export const testPostMessageToThread = {
257 async test() {
258 throws(
259 () => {
260 postMessageToThread(1, 'message');
261 },
262 {
263 code: 'ERR_METHOD_NOT_IMPLEMENTED',
264 }
265 );
266 
267 throws(
268 () => {
269 postMessageToThread(1, 'message', 1000);
270 },
271 {
272 code: 'ERR_METHOD_NOT_IMPLEMENTED',
273 }
274 );
275 
276 throws(
277 () => {
278 postMessageToThread(0, {});
279 },
280 {
281 code: 'ERR_METHOD_NOT_IMPLEMENTED',
282 }
283 );
284 
285 throws(
286 () => {
287 postMessageToThread(123, null);
288 },
289 {
290 code: 'ERR_METHOD_NOT_IMPLEMENTED',
291 }
292 );
293 },
294};
295 
296export const testIsInternalThread = {
297 async test() {
298 strictEqual(typeof isInternalThread, 'boolean');
299 strictEqual(isInternalThread, false);
300 },
301};
302 
303export const testDefaultExport = {
304 async test() {
305 const defaultExport = worker_threads.default;
306 
307 ok(defaultExport);
308 strictEqual(typeof defaultExport, 'object');
309 
310 ok('BroadcastChannel' in defaultExport);
311 ok('MessageChannel' in defaultExport);
312 ok('MessagePort' in defaultExport);
313 ok('Worker' in defaultExport);
314 ok('SHARE_ENV' in defaultExport);
315 ok('getEnvironmentData' in defaultExport);
316 ok('isMainThread' in defaultExport);
317 ok('isMarkedAsUntransferable' in defaultExport);
318 ok('markAsUntransferable' in defaultExport);
319 ok('markAsUncloneable' in defaultExport);
320 ok('moveMessagePortToContext' in defaultExport);
321 ok('parentPort' in defaultExport);
322 ok('receiveMessageOnPort' in defaultExport);
323 ok('resourceLimits' in defaultExport);
324 ok('setEnvironmentData' in defaultExport);
325 ok('postMessageToThread' in defaultExport);
326 ok('threadId' in defaultExport);
327 ok('workerData' in defaultExport);
328 ok('isInternalThread' in defaultExport);
329 
330 const expectedKeys = [
331 'BroadcastChannel',
332 'MessageChannel',
333 'MessagePort',
334 'Worker',
335 'SHARE_ENV',
336 'getEnvironmentData',
337 'isMainThread',
338 'isMarkedAsUntransferable',
339 'markAsUntransferable',
340 'markAsUncloneable',
341 'moveMessagePortToContext',
342 'parentPort',
343 'receiveMessageOnPort',
344 'resourceLimits',
345 'setEnvironmentData',
346 'postMessageToThread',
347 'threadId',
348 'workerData',
349 'isInternalThread',
350 ];
351 deepStrictEqual(Object.keys(defaultExport).sort(), expectedKeys.sort());
352 },
353};
354 
355export const testWorkerTerminate = {
356 async test() {
357 const proto = Worker.prototype;
358 await rejects(
359 async () => {
360 await proto.terminate.call({});
361 },
362 {
363 code: 'ERR_METHOD_NOT_IMPLEMENTED',
364 }
365 );
366 },
367};
368 
369export const testWorkerGetHeapSnapshot = {
370 async test() {
371 const proto = Worker.prototype;
372 await rejects(
373 async () => {
374 await proto.getHeapSnapshot.call({});
375 },
376 {
377 code: 'ERR_METHOD_NOT_IMPLEMENTED',
378 }
379 );
380 },
381};
382 
383export const testWorkerGetHeapStatistics = {
384 async test() {
385 const proto = Worker.prototype;
386 await rejects(
387 async () => {
388 await proto.getHeapStatistics.call({});
389 },
390 {
391 code: 'ERR_METHOD_NOT_IMPLEMENTED',
392 }
393 );
394 },
395};