@harperfast/integration-testing 0.5.0 → 0.5.2

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.
@@ -0,0 +1,50 @@
1
+ /**
2
+ * This method attempts to validate all loopback addresses in the pool by trying to
3
+ * bind to each one. It returns an object containing arrays of successfully bound
4
+ * loopback addresses and those that failed along with their errors.
5
+ *
6
+ * It will check all loopback addresses from 127.0.0.2 to 127.0.0.33 (by default).
7
+ *
8
+ * Use the HARPER_INTEGRATION_TEST_LOOPBACK_POOL_COUNT environment variable to
9
+ * adjust the number of loopback addresses to validate (up to 254).
10
+ */
11
+ export declare function validateLoopbackAddressPool(): Promise<{
12
+ successful: string[];
13
+ failed: {
14
+ loopbackAddress: string;
15
+ error: Error;
16
+ }[];
17
+ }>;
18
+ /**
19
+ * Retrieves the next available loopback address from the pool using a file-based
20
+ * locking mechanism to safely allocate addresses across concurrent test processes.
21
+ *
22
+ * **How it works:**
23
+ * 1. Acquires a file-based lock to prevent race conditions with other processes
24
+ * 2. Reads the pool state (a JSON array of process IDs, with null for available slots)
25
+ * 3. Finds the first available (null) slot and assigns the current process PID to it
26
+ * 4. Writes the updated pool back to disk and releases the lock
27
+ * 5. Validates that the allocated address can actually be bound to
28
+ * 6. Returns the loopback address (e.g., "127.0.0.2")
29
+ *
30
+ * If no addresses are available, waits and retries until one becomes available.
31
+ *
32
+ * **Pool file location:** `${tmpdir()}/harper-integration-test-loopback-pool.json`
33
+ * **Lock file location:** `${tmpdir()}/harper-integration-test-loopback-pool.lock`
34
+ *
35
+ * @returns A promise that resolves with an allocated loopback address
36
+ * @throws {LoopbackAddressValidationError} If the allocated address cannot be bound to
37
+ */
38
+ export declare function getNextAvailableLoopbackAddress(): Promise<string>;
39
+ /**
40
+ * Releases a loopback address back to the pool, making it available for other processes.
41
+ *
42
+ * @param address The loopback address to release (e.g., "127.0.0.2")
43
+ * @throws InvalidLoopbackAddressError if the address format is invalid
44
+ */
45
+ export declare function releaseLoopbackAddress(address: string): Promise<void>;
46
+ /**
47
+ * Releases all loopback addresses assigned to the current process.
48
+ * Useful for cleanup during graceful shutdown.
49
+ */
50
+ export declare function releaseAllLoopbackAddressesForCurrentProcess(): Promise<void>;
@@ -0,0 +1,421 @@
1
+ import { setTimeout as sleep } from 'node:timers/promises';
2
+ import { join } from 'node:path';
3
+ import { tmpdir } from 'node:os';
4
+ import { open, readFile, stat, unlink, writeFile } from 'node:fs/promises';
5
+ import { createServer } from 'node:net';
6
+ // Configuration constants
7
+ // The pool starts at 127.0.0.2 by default (rather than 127.0.0.1) to avoid conflicting
8
+ // with other servers commonly bound to localhost. Override via the
9
+ // HARPER_INTEGRATION_TEST_LOOPBACK_POOL_START environment variable.
10
+ const HARPER_LOOPBACK_POOL_START = process.env.HARPER_INTEGRATION_TEST_LOOPBACK_POOL_START
11
+ ? parseInt(process.env.HARPER_INTEGRATION_TEST_LOOPBACK_POOL_START, 10)
12
+ : 2;
13
+ if (HARPER_LOOPBACK_POOL_START < 1 || HARPER_LOOPBACK_POOL_START > 255) {
14
+ throw new Error('HARPER_INTEGRATION_TEST_LOOPBACK_POOL_START must be between 1 and 255');
15
+ }
16
+ const HARPER_LOOPBACK_POOL_COUNT = process.env.HARPER_INTEGRATION_TEST_LOOPBACK_POOL_COUNT
17
+ ? parseInt(process.env.HARPER_INTEGRATION_TEST_LOOPBACK_POOL_COUNT, 10)
18
+ : 32;
19
+ const HARPER_LOOPBACK_POOL_MAX = 256 - HARPER_LOOPBACK_POOL_START;
20
+ if (HARPER_LOOPBACK_POOL_COUNT < 1 || HARPER_LOOPBACK_POOL_COUNT > HARPER_LOOPBACK_POOL_MAX) {
21
+ throw new Error(`HARPER_INTEGRATION_TEST_LOOPBACK_POOL_COUNT must be between 1 and ${HARPER_LOOPBACK_POOL_MAX}`);
22
+ }
23
+ const HARPER_LOOPBACK_POOL_PATH = join(tmpdir(), 'harper-integration-test-loopback-pool.json');
24
+ const HARPER_LOOPBACK_POOL_LOCK_PATH = join(tmpdir(), 'harper-integration-test-loopback-pool.lock');
25
+ // Constants for timeouts and retries
26
+ const LOCK_STALE_TIMEOUT_MS = 10000;
27
+ const RETRY_DELAY_MS = 1000;
28
+ // Port used as a conflict canary when allocating an address. This MUST be a port that
29
+ // Harper binds WITHOUT SO_REUSEPORT (the operations API — `OPERATIONS_API_PORT` in
30
+ // harperLifecycle.ts — does exactly that, main-thread-only). Because that port is
31
+ // exclusive, a stale/overlapping Harper node still holding the address makes a plain
32
+ // (non-reusePort) bind here fail with EADDRINUSE — which is precisely the signal we
33
+ // need. Without this check, SO_REUSEPORT on the shared HTTP/replication ports would let
34
+ // a freshly-assigned node silently co-bind an address another node still owns, corrupting
35
+ // both suites. Override via HARPER_INTEGRATION_TEST_CONFLICT_PROBE_PORT if the operations
36
+ // port ever changes. (Hardcoded rather than imported from harperLifecycle to avoid a
37
+ // circular import between these two modules.)
38
+ const CONFLICT_PROBE_PORT = (() => {
39
+ const parsed = parseInt(process.env.HARPER_INTEGRATION_TEST_CONFLICT_PROBE_PORT || '', 10);
40
+ // Fall back to the default if unset/non-numeric/out-of-range, so an invalid override
41
+ // can't turn into a `server.listen(NaN)` crash during allocation.
42
+ return Number.isNaN(parsed) || parsed < 0 || parsed > 65535 ? 9925 : parsed;
43
+ })();
44
+ // Custom error classes
45
+ class LoopbackAddressValidationError extends Error {
46
+ constructor(address, cause) {
47
+ super(`Failed to validate loopback address ${address}. This likely means your system does not have the required loopback addresses configured/enabled. Refer to the Harper Integration Test documentation (integrationTests/README.md) for more information.`);
48
+ this.name = 'LoopbackAddressValidationError';
49
+ if (cause) {
50
+ this.cause = cause;
51
+ }
52
+ }
53
+ }
54
+ class InvalidLoopbackAddressError extends Error {
55
+ constructor(address) {
56
+ super(`Invalid loopback address format: ${address}. Expected format: 127.0.0.X where X is between ${HARPER_LOOPBACK_POOL_START} and ${HARPER_LOOPBACK_POOL_START + HARPER_LOOPBACK_POOL_COUNT - 1}`);
57
+ this.name = 'InvalidLoopbackAddressError';
58
+ }
59
+ }
60
+ /**
61
+ * Acquires a file-based lock by creating the lock file. This enables safe concurrent
62
+ * access to the loopback pool across multiple test processes.
63
+ *
64
+ * Uses the 'wx' file flag which atomically fails if the file already exists, providing
65
+ * a simple but effective cross-process mutex. Handles stale locks by removing lock files
66
+ * older than LOCK_STALE_TIMEOUT_MS (10 seconds).
67
+ *
68
+ * @returns A promise that resolves when the lock is acquired
69
+ */
70
+ async function acquireLock() {
71
+ while (true) {
72
+ try {
73
+ // The 'wx' flag causes the open to fail if the file already exists
74
+ const lockFileHandle = await open(HARPER_LOOPBACK_POOL_LOCK_PATH, 'wx');
75
+ // We have the lock - close the handle as we don't intend to write to it
76
+ await lockFileHandle.close();
77
+ return;
78
+ }
79
+ catch (error) {
80
+ // If the lock file already exists, it's either stale or we wait for it to be released
81
+ if (error.code === 'EEXIST') {
82
+ try {
83
+ const lockFileStat = await stat(HARPER_LOOPBACK_POOL_LOCK_PATH);
84
+ // If the lock file is older than the timeout, consider it stale and remove it
85
+ if (Date.now() - lockFileStat.mtimeMs > LOCK_STALE_TIMEOUT_MS) {
86
+ await unlink(HARPER_LOOPBACK_POOL_LOCK_PATH);
87
+ }
88
+ }
89
+ catch {
90
+ // Lock file may have been removed by another process, continue
91
+ }
92
+ await sleep(RETRY_DELAY_MS);
93
+ continue;
94
+ }
95
+ // Rethrow other errors
96
+ throw error;
97
+ }
98
+ }
99
+ }
100
+ /**
101
+ * Releases the file-based lock by deleting the lock file.
102
+ */
103
+ async function releaseLock() {
104
+ try {
105
+ await unlink(HARPER_LOOPBACK_POOL_LOCK_PATH);
106
+ }
107
+ catch {
108
+ // Ignore errors if lock file is already gone
109
+ }
110
+ }
111
+ /**
112
+ * Executes a callback function while holding the lock. Automatically acquires
113
+ * and releases the lock, ensuring the lock is always released even if the callback
114
+ * throws an error.
115
+ *
116
+ * @param callback The async function to execute while holding the lock
117
+ * @returns The result of the callback function
118
+ */
119
+ async function withLock(callback) {
120
+ await acquireLock();
121
+ try {
122
+ return await callback();
123
+ }
124
+ finally {
125
+ await releaseLock();
126
+ }
127
+ }
128
+ /**
129
+ * Reads the loopback pool from the pool file. The pool is a JSON array where each
130
+ * index represents a loopback address (127.0.0.2, 127.0.0.3, etc.) and the value
131
+ * is either null (available) or a process PID (in use).
132
+ *
133
+ * If the file doesn't exist, creates and returns a new empty pool with all addresses
134
+ * marked as available (null).
135
+ *
136
+ * @returns The loopback pool array
137
+ */
138
+ async function readPoolFile() {
139
+ try {
140
+ const content = await readFile(HARPER_LOOPBACK_POOL_PATH, 'utf-8');
141
+ return JSON.parse(content);
142
+ }
143
+ catch (error) {
144
+ if (error.code === 'ENOENT') {
145
+ // If the pool file doesn't exist yet, create it with null entries
146
+ return Array(HARPER_LOOPBACK_POOL_COUNT).fill(null);
147
+ }
148
+ throw error;
149
+ }
150
+ }
151
+ /**
152
+ * Writes the loopback pool to the pool file as JSON.
153
+ *
154
+ * @param pool The loopback pool array to persist
155
+ */
156
+ async function writePoolFile(pool) {
157
+ await writeFile(HARPER_LOOPBACK_POOL_PATH, JSON.stringify(pool));
158
+ }
159
+ /**
160
+ * Validates the format of a loopback address and extracts the pool index.
161
+ *
162
+ * Expects addresses in the format "127.0.0.X" where X is between the pool start
163
+ * (127.0.0.2) and the pool start + pool count - 1. The returned index is 0-based
164
+ * (e.g., "127.0.0.2" returns index 0).
165
+ *
166
+ * @param address The loopback address to parse (e.g., "127.0.0.2")
167
+ * @returns The 0-based pool index for this address
168
+ * @throws {InvalidLoopbackAddressError} If the address format is invalid or out of range
169
+ */
170
+ function parseLoopbackAddress(address) {
171
+ const parts = address.split('.');
172
+ if (parts.length !== 4 || parts[0] !== '127' || parts[1] !== '0' || parts[2] !== '0') {
173
+ throw new InvalidLoopbackAddressError(address);
174
+ }
175
+ const index = parseInt(parts[3], 10) - HARPER_LOOPBACK_POOL_START;
176
+ if (isNaN(index) || index < 0 || index >= HARPER_LOOPBACK_POOL_COUNT) {
177
+ throw new InvalidLoopbackAddressError(address);
178
+ }
179
+ return index;
180
+ }
181
+ /**
182
+ * Validates that a given loopback address can be bound to by creating a temporary
183
+ * TCP server on that address. This ensures the loopback address is actually configured
184
+ * and available on the system before allocating it to a test process.
185
+ *
186
+ * The server is bound to port 0 (random port) just to verify the address exists,
187
+ * then immediately closed.
188
+ *
189
+ * @param loopbackAddress The loopback IP address to validate (e.g., "127.0.0.2")
190
+ * @returns A promise that resolves with the address if valid
191
+ * @throws An error with the loopbackAddress property if binding fails
192
+ */
193
+ function validateLoopbackAddress(loopbackAddress) {
194
+ return new Promise((resolve, reject) => {
195
+ const server = createServer();
196
+ server.once('error', (error) => {
197
+ const enhancedError = error;
198
+ enhancedError.loopbackAddress = loopbackAddress;
199
+ reject(enhancedError);
200
+ });
201
+ server.listen(0, loopbackAddress, () => {
202
+ server.close(() => {
203
+ resolve(loopbackAddress);
204
+ });
205
+ });
206
+ });
207
+ }
208
+ /**
209
+ * Conflict canary: detects whether a Harper node is already bound to `loopbackAddress`
210
+ * by attempting an exclusive (non-SO_REUSEPORT) bind on the operations-API port. Node's
211
+ * `net` server does not set SO_REUSEPORT, so if a stale/overlapping Harper node still
212
+ * owns the address's operations port, this bind fails with EADDRINUSE.
213
+ *
214
+ * Returns `true` if the address appears in use (EADDRINUSE), `false` if it is free.
215
+ * Re-throws any other bind error (e.g. EADDRNOTAVAIL) — that's a real configuration
216
+ * problem the caller should surface, not a transient conflict to skip.
217
+ *
218
+ * @param loopbackAddress The loopback IP address to probe (e.g., "127.0.0.2")
219
+ */
220
+ function isLoopbackAddressInUse(loopbackAddress) {
221
+ return new Promise((resolve, reject) => {
222
+ const server = createServer();
223
+ const onError = (error) => {
224
+ if (error.code === 'EADDRINUSE') {
225
+ resolve(true);
226
+ return;
227
+ }
228
+ const enhancedError = error;
229
+ enhancedError.loopbackAddress = loopbackAddress;
230
+ reject(enhancedError);
231
+ };
232
+ server.once('error', onError);
233
+ server.listen(CONFLICT_PROBE_PORT, loopbackAddress, () => {
234
+ // Bind succeeded — the address is free. Drop the error listener so a stray
235
+ // post-bind socket error during close() can't flip the resolved verdict.
236
+ server.off('error', onError);
237
+ server.close(() => {
238
+ resolve(false);
239
+ });
240
+ });
241
+ });
242
+ }
243
+ /**
244
+ * This method attempts to validate all loopback addresses in the pool by trying to
245
+ * bind to each one. It returns an object containing arrays of successfully bound
246
+ * loopback addresses and those that failed along with their errors.
247
+ *
248
+ * It will check all loopback addresses from 127.0.0.2 to 127.0.0.33 (by default).
249
+ *
250
+ * Use the HARPER_INTEGRATION_TEST_LOOPBACK_POOL_COUNT environment variable to
251
+ * adjust the number of loopback addresses to validate (up to 254).
252
+ */
253
+ export async function validateLoopbackAddressPool() {
254
+ return Promise.allSettled(Array.from({ length: HARPER_LOOPBACK_POOL_COUNT }, (_, i) => validateLoopbackAddress(`127.0.0.${i + HARPER_LOOPBACK_POOL_START}`))).then((results) => results.reduce((acc, result) => {
255
+ if (result.status === 'fulfilled') {
256
+ acc.successful.push(result.value);
257
+ }
258
+ else {
259
+ const error = result.reason;
260
+ acc.failed.push({ loopbackAddress: error.loopbackAddress, error });
261
+ }
262
+ return acc;
263
+ }, { successful: [], failed: [] }));
264
+ }
265
+ /**
266
+ * Retrieves the next available loopback address from the pool using a file-based
267
+ * locking mechanism to safely allocate addresses across concurrent test processes.
268
+ *
269
+ * **How it works:**
270
+ * 1. Acquires a file-based lock to prevent race conditions with other processes
271
+ * 2. Reads the pool state (a JSON array of process IDs, with null for available slots)
272
+ * 3. Finds the first available (null) slot and assigns the current process PID to it
273
+ * 4. Writes the updated pool back to disk and releases the lock
274
+ * 5. Validates that the allocated address can actually be bound to
275
+ * 6. Returns the loopback address (e.g., "127.0.0.2")
276
+ *
277
+ * If no addresses are available, waits and retries until one becomes available.
278
+ *
279
+ * **Pool file location:** `${tmpdir()}/harper-integration-test-loopback-pool.json`
280
+ * **Lock file location:** `${tmpdir()}/harper-integration-test-loopback-pool.lock`
281
+ *
282
+ * @returns A promise that resolves with an allocated loopback address
283
+ * @throws {LoopbackAddressValidationError} If the allocated address cannot be bound to
284
+ */
285
+ export async function getNextAvailableLoopbackAddress() {
286
+ // Each index maps to a different loopback address (index 0 -> 127.0.0.2, index 1 -> 127.0.0.3, etc.)
287
+ // So if the first test process number is 42, it would be assigned to index 0 associated with address 127.0.0.2
288
+ // [42, null, null, ...];
289
+ // Then the next process (call is 43) gets the next available, so index 1 -> 127.0.0.3
290
+ // [42, 43, null, ...];
291
+ // And so on...
292
+ // As processes exit and release their loopback addresses, those addresses become available for new processes to use
293
+ // [42, null, 44, ...];
294
+ // Next process (45) gets index 1 again ->
295
+ // [42, 45, 44, ...];
296
+ // This continues until all loopback addresses are used, at which point new processes will wait until an address becomes available
297
+ // Since multiple processes may be trying to get a loopback address at the same time, we need to implement a simple file-based locking mechanism to prevent race conditions
298
+ // Indices found in-use by the conflict canary during THIS call. Tracked locally (not by
299
+ // parking them in the shared pool) so we never deadlock ourselves: if we parked every
300
+ // poisoned slot under our own live PID, the pool could fill with our PID, the free-slot search
301
+ // would find nothing, removeDeadProcessesFromPool couldn't reclaim them (we're alive), and we'd
302
+ // spin forever. Instead we release poisoned slots back to the pool and just avoid re-trying
303
+ // them within this attempt; the set is cleared before each wait so they can be re-probed once
304
+ // the lingering node has had time to exit.
305
+ const triedIndices = new Set();
306
+ while (true) {
307
+ const assignedIndex = await withLock(async () => {
308
+ // Read the pool file
309
+ const loopbackPool = await readPoolFile();
310
+ // Find the first available index we haven't already found in-use this attempt
311
+ let index = null;
312
+ for (let i = 0; i < loopbackPool.length; i++) {
313
+ if (loopbackPool[i] === null && !triedIndices.has(i)) {
314
+ index = i;
315
+ break;
316
+ }
317
+ }
318
+ if (index === null) {
319
+ // Nothing available - remove any dead processes from the pool and wait for one to become available
320
+ removeDeadProcessesFromPool(loopbackPool);
321
+ }
322
+ else {
323
+ // Assign the process PID to that index to mark it as used
324
+ loopbackPool[index] = process.pid;
325
+ }
326
+ // Write the updated pool back to the file
327
+ await writePoolFile(loopbackPool);
328
+ return index;
329
+ });
330
+ // If we got an index, validate and return the address
331
+ if (assignedIndex !== null) {
332
+ const loopbackAddress = `127.0.0.${assignedIndex + HARPER_LOOPBACK_POOL_START}`;
333
+ try {
334
+ await validateLoopbackAddress(loopbackAddress);
335
+ }
336
+ catch (error) {
337
+ // Validation failed - throw a proper error instead of breaking
338
+ throw new LoopbackAddressValidationError(loopbackAddress, error);
339
+ }
340
+ // Conflict canary: a stale or still-running Harper node from an overlapping
341
+ // suite may already hold this address (e.g. the pool slot was freed while its
342
+ // node lingered). SO_REUSEPORT on Harper's shared HTTP/replication ports would
343
+ // otherwise let our node silently co-bind it, splitting connections between two
344
+ // nodes and corrupting both suites. Detect that here via the exclusive
345
+ // operations port and skip the poisoned address.
346
+ let inUse;
347
+ try {
348
+ inUse = await isLoopbackAddressInUse(loopbackAddress);
349
+ }
350
+ catch (error) {
351
+ throw new LoopbackAddressValidationError(loopbackAddress, error);
352
+ }
353
+ if (!inUse) {
354
+ return loopbackAddress;
355
+ }
356
+ // Release the slot back to the pool (so it isn't leaked under our PID) and remember
357
+ // not to re-try it this attempt; loop to claim the next available one.
358
+ await releaseLoopbackAddress(loopbackAddress);
359
+ triedIndices.add(assignedIndex);
360
+ console.warn(`[loopback-pool] ${loopbackAddress} is still in use by another Harper node (operations port ${CONFLICT_PROBE_PORT} bound); skipping to avoid an SO_REUSEPORT co-bind.`);
361
+ continue;
362
+ }
363
+ // No available addresses; clear the per-attempt skip set so poisoned addresses can be
364
+ // re-probed (their lingering node may have exited during the wait), then wait and retry.
365
+ triedIndices.clear();
366
+ await sleep(RETRY_DELAY_MS);
367
+ }
368
+ }
369
+ /**
370
+ * Removes any dead processes from the loopback pool.
371
+ * @param loopbackPool
372
+ */
373
+ function removeDeadProcessesFromPool(loopbackPool) {
374
+ loopbackPool.forEach((pid, index) => {
375
+ if (pid === null)
376
+ return;
377
+ try {
378
+ process.kill(pid, 0);
379
+ }
380
+ catch {
381
+ loopbackPool[index] = null;
382
+ }
383
+ });
384
+ }
385
+ /**
386
+ * Releases a loopback address back to the pool, making it available for other processes.
387
+ *
388
+ * @param address The loopback address to release (e.g., "127.0.0.2")
389
+ * @throws InvalidLoopbackAddressError if the address format is invalid
390
+ */
391
+ export async function releaseLoopbackAddress(address) {
392
+ // Validate and parse the address
393
+ const index = parseLoopbackAddress(address);
394
+ await withLock(async () => {
395
+ // Read the pool file
396
+ const loopbackPool = await readPoolFile();
397
+ // Release the address by setting it to null
398
+ loopbackPool[index] = null;
399
+ // Write the updated pool back to the file
400
+ await writePoolFile(loopbackPool);
401
+ });
402
+ }
403
+ /**
404
+ * Releases all loopback addresses assigned to the current process.
405
+ * Useful for cleanup during graceful shutdown.
406
+ */
407
+ export async function releaseAllLoopbackAddressesForCurrentProcess() {
408
+ await withLock(async () => {
409
+ // Read the pool file
410
+ const loopbackPool = await readPoolFile();
411
+ // Find and release all addresses assigned to this process
412
+ for (let i = 0; i < loopbackPool.length; i++) {
413
+ if (loopbackPool[i] === process.pid) {
414
+ loopbackPool[i] = null;
415
+ }
416
+ }
417
+ // Write the updated pool back to the file
418
+ await writePoolFile(loopbackPool);
419
+ });
420
+ }
421
+ //# sourceMappingURL=loopbackAddressPool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loopbackAddressPool.js","sourceRoot":"","sources":["../src/loopbackAddressPool.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Checks whether a TCP port can be bound on a given host (i.e. the port is free).
3
+ *
4
+ * Attempts to bind a throwaway server to `host:port`; resolves `true` if the bind
5
+ * succeeds (the server is closed immediately) and `false` if it fails (e.g. the port
6
+ * is still held by another process or socket).
7
+ *
8
+ * @param host The host/address to bind on (e.g. "127.0.0.2")
9
+ * @param port The port to test
10
+ * @returns A promise resolving to `true` if the port is free, `false` otherwise
11
+ */
12
+ export declare function isPortFree(host: string, port: number): Promise<boolean>;
13
+ /**
14
+ * Polls until every `host:port` in `ports` is free, or `timeoutMs` elapses.
15
+ *
16
+ * Unlike binding to an ephemeral port (which only proves the *address* is usable), this
17
+ * verifies the specific fixed ports a Harper instance binds are actually released, which
18
+ * is what the next test suite needs before it can reuse the address.
19
+ *
20
+ * @param host The host/address the ports are bound on (e.g. "127.0.0.2")
21
+ * @param ports The fixed ports to wait on
22
+ * @param timeoutMs Maximum time to wait for all ports to become free
23
+ * @param pollIntervalMs Delay between polls (default 100ms)
24
+ * @returns `true` if all ports became free within the timeout, `false` if it gave up
25
+ */
26
+ export declare function waitForPortsFree(host: string, ports: number[], timeoutMs: number, pollIntervalMs?: number): Promise<boolean>;
@@ -0,0 +1,47 @@
1
+ import { createServer } from 'node:net';
2
+ import { setTimeout as sleep } from 'node:timers/promises';
3
+ /**
4
+ * Checks whether a TCP port can be bound on a given host (i.e. the port is free).
5
+ *
6
+ * Attempts to bind a throwaway server to `host:port`; resolves `true` if the bind
7
+ * succeeds (the server is closed immediately) and `false` if it fails (e.g. the port
8
+ * is still held by another process or socket).
9
+ *
10
+ * @param host The host/address to bind on (e.g. "127.0.0.2")
11
+ * @param port The port to test
12
+ * @returns A promise resolving to `true` if the port is free, `false` otherwise
13
+ */
14
+ export function isPortFree(host, port) {
15
+ return new Promise((resolve) => {
16
+ const server = createServer();
17
+ server.once('error', () => resolve(false));
18
+ server.listen(port, host, () => {
19
+ server.close(() => resolve(true));
20
+ });
21
+ });
22
+ }
23
+ /**
24
+ * Polls until every `host:port` in `ports` is free, or `timeoutMs` elapses.
25
+ *
26
+ * Unlike binding to an ephemeral port (which only proves the *address* is usable), this
27
+ * verifies the specific fixed ports a Harper instance binds are actually released, which
28
+ * is what the next test suite needs before it can reuse the address.
29
+ *
30
+ * @param host The host/address the ports are bound on (e.g. "127.0.0.2")
31
+ * @param ports The fixed ports to wait on
32
+ * @param timeoutMs Maximum time to wait for all ports to become free
33
+ * @param pollIntervalMs Delay between polls (default 100ms)
34
+ * @returns `true` if all ports became free within the timeout, `false` if it gave up
35
+ */
36
+ export async function waitForPortsFree(host, ports, timeoutMs, pollIntervalMs = 100) {
37
+ const deadline = Date.now() + timeoutMs;
38
+ while (true) {
39
+ const results = await Promise.all(ports.map((port) => isPortFree(host, port)));
40
+ if (results.every(Boolean))
41
+ return true;
42
+ if (Date.now() >= deadline)
43
+ return false;
44
+ await sleep(pollIntervalMs);
45
+ }
46
+ }
47
+ //# sourceMappingURL=portUtils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"portUtils.js","sourceRoot":"","sources":["../src/portUtils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAE,UAAU,IAAI,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAE3D;;;;;;;;;;GAUG;AACH,MAAM,UAAU,UAAU,CAAC,IAAY,EAAE,IAAY;IACpD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC9B,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC;QAC9B,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;QAC3C,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE;YAC9B,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QACnC,CAAC,CAAC,CAAC;IACJ,CAAC,CAAC,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACrC,IAAY,EACZ,KAAe,EACf,SAAiB,EACjB,cAAc,GAAG,GAAG;IAEpB,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;IACxC,OAAO,IAAI,EAAE,CAAC;QACb,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;QAC/E,IAAI,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC;YAAE,OAAO,IAAI,CAAC;QACxC,IAAI,IAAI,CAAC,GAAG,EAAE,IAAI,QAAQ;YAAE,OAAO,KAAK,CAAC;QACzC,MAAM,KAAK,CAAC,cAAc,CAAC,CAAC;IAC7B,CAAC;AACF,CAAC"}
package/dist/run.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};