Skip to content
File

Blob: src/workerd/server/tests/weakref/test.mjs

javascript148 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
4/*
5This is a node.js script which handlers workerd lifecycle and sends multiple requests.
6FinalizationRegistry callbacks run, if scheduled, across I/O boundaries, and wd-test,
7which only run tests within a single test() handler, are not enough to test this behaviour.
8This test now covers both FinalizationRegistry and WeakRef APIs.
9*/
10 
11import { env } from 'node:process';
12import { beforeEach, afterEach, test, after } from 'node:test';
13import assert from 'node:assert';
14import { WorkerdServerHarness } from '../server-harness.mjs';
15 
16// Global that is reset for each test.
17let workerd;
18 
19assert.notStrictEqual(
20 env.WORKERD_BINARY,
21 undefined,
22 'You must set the WORKERD_BINARY environment variable.'
23);
24assert.notStrictEqual(
25 env.WORKERD_CONFIG,
26 undefined,
27 'You must set the WORKERD_CONFIG environment variable.'
28);
29 
30// Start workerd.
31beforeEach(async () => {
32 console.log('[TEST] Starting workerd process...');
33 workerd = new WorkerdServerHarness({
34 workerdBinary: env.WORKERD_BINARY,
35 workerdConfig: env.WORKERD_CONFIG,
36 
37 // Hard-coded to match a socket name expected in the `workerdConfig` file.
38 listenPortNames: ['http'],
39 });
40 
41 await workerd.start();
42 
43 await workerd.getListenPort('http');
44 console.log('[TEST] Workerd process started successfully');
45});
46 
47// Stop workerd.
48afterEach(async () => {
49 if (workerd) {
50 console.log('[TEST] Stopping workerd process...');
51 try {
52 const [code, signal] = await workerd.stop();
53 console.log(`[TEST] Workerd stopped with code=${code}, signal=${signal}`);
54 assert(
55 code === 0 || signal === 'SIGTERM',
56 `code=${code}, signal=${signal}`
57 );
58 } catch (error) {
59 console.error('[TEST] Error stopping workerd:', error);
60 throw error;
61 } finally {
62 workerd = null;
63 }
64 }
65 console.log('[TEST] Cleanup complete');
66});
67 
68// FinalizationRegistry callbacks run across I/O boundaries
69test('JS FinalizationRegistry', async () => {
70 let httpPort = await workerd.getListenPort('http');
71 
72 // The first request doesn't do any I/O so we won't notice the effects
73 // of FinalizationRegistry cleanup callbacks as part of the response
74 const response = await fetch(`http://localhost:${httpPort}?test=fr`);
75 console.log('[TEST] First FR request completed');
76 assert.strictEqual(await response.text(), '0');
77 
78 // Subsequent requests do I/O so we will immediately see
79 // the effects of FinalizationRegistry cleanup callbacks
80 for (let i = 0; i < 2; ++i) {
81 console.log(`[TEST] FR request ${i + 2} starting...`);
82 const response = await fetch(`http://localhost:${httpPort}?test=fr`);
83 console.log(`[TEST] FR request ${i + 2} completed`);
84 assert.strictEqual(await response.text(), `${i + 2}`);
85 }
86});
87 
88// Test WeakRef behavior
89test('JS WeakRef', async () => {
90 let httpPort = await workerd.getListenPort('http');
91 
92 // Create a new object and a WeakRef to it
93 console.log('[TEST] Creating WeakRef object...');
94 let response = await fetch(
95 `http://localhost:${httpPort}?test=weakref&create`
96 );
97 let data = await response.json();
98 console.log('[TEST] WeakRef object created:', data);
99 
100 // Verify the object is created and accessible via the WeakRef
101 assert.strictEqual(data.created, true);
102 assert.strictEqual(data.value, "I'm alive!");
103 
104 // Check that the WeakRef is still valid
105 console.log('[TEST] Checking WeakRef validity...');
106 response = await fetch(`http://localhost:${httpPort}?test=weakref`);
107 data = await response.json();
108 console.log('[TEST] WeakRef validity check result:', data);
109 assert.strictEqual(data.isDereferenced, false);
110 assert.strictEqual(data.value, "I'm alive!");
111 
112 // Force garbage collection and check if the WeakRef is dereferenced
113 console.log('[TEST] Forcing garbage collection...');
114 response = await fetch(`http://localhost:${httpPort}?test=weakref&gc`);
115 data = await response.json();
116 console.log('[TEST] GC result:', data);
117 // Check if the reference is gone after GC
118 assert.strictEqual(data.isDereferenced, true);
119 assert.strictEqual(data.value, null);
120 
121 // Create a new object again to verify WeakRef can be reset
122 console.log('[TEST] Creating new WeakRef object after GC...');
123 response = await fetch(`http://localhost:${httpPort}?test=weakref&create`);
124 data = await response.json();
125 console.log('[TEST] New WeakRef object created:', data);
126 
127 // The new object should be accessible
128 assert.strictEqual(data.created, true);
129 assert.strictEqual(data.value, "I'm alive!");
130});
131 
132// Ensure clean exit
133after(async () => {
134 console.log('[TEST] All tests completed, ensuring clean exit...');
135 // Give a moment for any cleanup to complete
136 await new Promise((resolve) => setTimeout(resolve, 100));
137 
138 // Force exit to prevent hanging
139 console.log('[TEST] Exiting process...');
140 process.exit(0);
141});
142 
143// Handle uncaught errors
144process.on('uncaughtException', (error) => {
145 console.error('[TEST] Uncaught exception:', error);
146 process.exit(1);
147});