@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,594 @@
1
+ import { spawn, ChildProcess } from 'node:child_process';
2
+ import { createWriteStream, existsSync } from 'node:fs';
3
+ import { join, basename, dirname } from 'node:path';
4
+ import { tmpdir } from 'node:os';
5
+ import { mkdtemp, mkdir, rm, cp } from 'node:fs/promises';
6
+ import {} from 'node:test';
7
+ import { getNextAvailableLoopbackAddress, releaseLoopbackAddress } from "./loopbackAddressPool.js";
8
+ import { waitForPortsFree } from "./portUtils.js";
9
+ import { ok, equal } from 'node:assert';
10
+ import { createRequire } from 'node:module';
11
+ /**
12
+ * Creates a plain object satisfying HarperTestContext, for use outside node:test
13
+ * (e.g. as a Playwright worker fixture).
14
+ *
15
+ * @param name Optional name for log directory naming (e.g. Playwright worker index).
16
+ */
17
+ export function createHarperContext(name) {
18
+ return { name };
19
+ }
20
+ // Constants
21
+ const HTTP_PORT = 9926;
22
+ const HTTPS_PORT = 9927;
23
+ const MQTT_PORT = 1883;
24
+ const MQTTS_PORT = 8883;
25
+ export const OPERATIONS_API_PORT = 9925;
26
+ export const DEFAULT_ADMIN_USERNAME = 'admin';
27
+ export const DEFAULT_ADMIN_PASSWORD = 'Abc1234!';
28
+ /** Every fixed port a Harper test instance may bind, all on its assigned loopback address. */
29
+ const ALL_HARPER_PORTS = [OPERATIONS_API_PORT, HTTP_PORT, HTTPS_PORT, MQTT_PORT, MQTTS_PORT];
30
+ /** Whether we appear to be running in CI (most CI providers set CI=true). */
31
+ const IS_CI = !!process.env.CI;
32
+ /**
33
+ * Maximum time to wait between chunks of Harper startup output before treating the process
34
+ * as hung. The startup watchdog resets this window on every chunk of output, so the limit is
35
+ * time-since-last-progress rather than total boot time: a slow-but-healthy boot that keeps
36
+ * logging never trips it — only true silence does.
37
+ *
38
+ * Override with `HARPER_INTEGRATION_TEST_STARTUP_TIMEOUT_MS`. Default 60s.
39
+ */
40
+ export const DEFAULT_STARTUP_TIMEOUT_MS = parseInt(process.env.HARPER_INTEGRATION_TEST_STARTUP_TIMEOUT_MS || '', 10) || 60000;
41
+ /**
42
+ * Absolute ceiling on total startup time, regardless of ongoing output — a generous backstop
43
+ * so a process that chatters forever without ever reporting ready still fails. Higher under CI,
44
+ * where shared/contended runners boot more slowly.
45
+ *
46
+ * Override with `HARPER_INTEGRATION_TEST_STARTUP_MAX_MS`. Default 120s (300s under CI).
47
+ */
48
+ export const DEFAULT_STARTUP_MAX_MS = parseInt(process.env.HARPER_INTEGRATION_TEST_STARTUP_MAX_MS || '', 10) || (IS_CI ? 300000 : 120000);
49
+ /**
50
+ * Grace period after SIGTERM before escalating to SIGKILL during teardown, giving Harper time
51
+ * to shut down cleanly (flush RocksDB, release ports, reap worker children).
52
+ *
53
+ * Override with `HARPER_INTEGRATION_TEST_TEARDOWN_GRACE_MS`. Default 5s.
54
+ */
55
+ export const DEFAULT_TEARDOWN_GRACE_MS = parseInt(process.env.HARPER_INTEGRATION_TEST_TEARDOWN_GRACE_MS || '', 10) || 5000;
56
+ /**
57
+ * Time teardown's safety assertion waits for Harper's fixed ports to be free before recycling the
58
+ * loopback address. Killing Harper's process tree (and waiting for exit) should free them
59
+ * immediately, so this normally returns on the first check; it only matters if a child process
60
+ * escaped the kill. If the ports are still in use at the deadline, the address is recycled anyway
61
+ * (no worse than not waiting) and a warning is logged.
62
+ *
63
+ * Override with `HARPER_INTEGRATION_TEST_PORT_RELEASE_TIMEOUT_MS`. Default 5s.
64
+ */
65
+ export const DEFAULT_PORT_RELEASE_TIMEOUT_MS = parseInt(process.env.HARPER_INTEGRATION_TEST_PORT_RELEASE_TIMEOUT_MS || '', 10) || 5000;
66
+ /** Short backstop wait for the process 'exit' event after sending SIGKILL during teardown. */
67
+ const SIGKILL_EXIT_WAIT_MS = 1000;
68
+ /**
69
+ * The runtime to use for running Harper during tests.
70
+ * Set via the HARPER_RUNTIME environment variable ('node' or 'bun').
71
+ * Defaults to 'node'.
72
+ */
73
+ export const HARPER_RUNTIME = process.env.HARPER_RUNTIME || 'node';
74
+ /**
75
+ * Marker emitted on stdout by startHarper() so the test runner (run.ts) can map
76
+ * a Harper instance's log directory to the currently executing test file via
77
+ * the node:test `test:stdout` event.
78
+ */
79
+ export const LOG_DIR_MARKER_PREFIX = '[Harper] Logs for this instance will be stored in:';
80
+ /**
81
+ * Gets the path to the Harper CLI script.
82
+ *
83
+ * Resolution order:
84
+ * 1. `harperBinPath` argument
85
+ * 2. `HARPER_INTEGRATION_TEST_INSTALL_SCRIPT` environment variable
86
+ * 3. Auto-resolved from 'harper' package in node_modules
87
+ * 4. Auto-resolved from current directory or ancestors ('dist/bin/harper.js')
88
+ *
89
+ * @returns The absolute path to the Harper CLI entry script
90
+ * @throws {AssertionError} If the script cannot be found
91
+ */
92
+ function getHarperScript(harperBinPath) {
93
+ // 1. Explicit option
94
+ if (harperBinPath) {
95
+ ok(existsSync(harperBinPath), `Harper script not found at provided harperBinPath: ${harperBinPath}`);
96
+ return harperBinPath;
97
+ }
98
+ // 2. Environment variable
99
+ const envPath = process.env.HARPER_INTEGRATION_TEST_INSTALL_SCRIPT;
100
+ if (envPath) {
101
+ ok(existsSync(envPath), `Harper script not found at HARPER_INTEGRATION_TEST_INSTALL_SCRIPT path: ${envPath}`);
102
+ return envPath;
103
+ }
104
+ // 3. Auto-resolve from node_modules
105
+ try {
106
+ const require = createRequire(import.meta.url);
107
+ const resolved = require.resolve('harper/dist/bin/harper.js');
108
+ if (existsSync(resolved)) {
109
+ return resolved;
110
+ }
111
+ }
112
+ catch {
113
+ // harper package not found in node_modules
114
+ }
115
+ // 4. Auto-resolve from current directory or ancestors
116
+ let currentDir = process.cwd();
117
+ while (true) {
118
+ const potentialPath = join(currentDir, 'dist/bin/harper.js');
119
+ if (existsSync(potentialPath)) {
120
+ return potentialPath;
121
+ }
122
+ const parentDir = dirname(currentDir);
123
+ if (parentDir === currentDir) {
124
+ break;
125
+ }
126
+ currentDir = parentDir;
127
+ }
128
+ throw new Error(`Harper CLI script not found. Provide the path via:\n` +
129
+ ` - harperBinPath option: startHarper(ctx, { harperBinPath: '/path/to/dist/bin/harper.js' })\n` +
130
+ ` - HARPER_INTEGRATION_TEST_INSTALL_SCRIPT environment variable\n` +
131
+ ` - Install 'harper' as a dependency in your project\n` +
132
+ ` - Run tests within a directory containing 'dist/bin/harper.js'`);
133
+ }
134
+ /**
135
+ * Strips ANSI escape sequences (colors, bold, underline, cursor movement, etc.) from a string.
136
+ */
137
+ // eslint-disable-next-line no-control-regex
138
+ const ANSI_REGEX = /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nq-uy=><~]/g;
139
+ function stripAnsi(str) {
140
+ return str.replace(ANSI_REGEX, '');
141
+ }
142
+ /**
143
+ * Sanitizes a string for use as a filesystem directory name.
144
+ */
145
+ function sanitizeForFilesystem(name) {
146
+ return name
147
+ .replace(/[^a-zA-Z0-9_-]/g, '_')
148
+ .replace(/_+/g, '_')
149
+ .substring(0, 100);
150
+ }
151
+ /**
152
+ * Error thrown when a Harper process fails to start or times out.
153
+ * Includes captured stdout and stderr for diagnostics.
154
+ */
155
+ export class HarperStartupError extends Error {
156
+ stdout;
157
+ stderr;
158
+ constructor(message, stdout, stderr) {
159
+ let fullMessage = message;
160
+ if (stdout) {
161
+ fullMessage += `\n\nstdout:\n${stdout}`;
162
+ }
163
+ if (stderr) {
164
+ fullMessage += `\n\nstderr:\n${stderr}`;
165
+ }
166
+ super(fullMessage);
167
+ this.name = 'HarperStartupError';
168
+ this.stdout = stdout;
169
+ this.stderr = stderr;
170
+ }
171
+ }
172
+ /**
173
+ * Runs a Harper CLI command and captures output.
174
+ *
175
+ * When `logDir` is provided, stdout and stderr are also written to files
176
+ * (`stdout.log` and `stderr.log`) in that directory.
177
+ *
178
+ * @throws {HarperStartupError} If the command times out or exits with a non-zero status code
179
+ *
180
+ * Exported for unit testing; not part of the public API (not re-exported from `index.ts`).
181
+ */
182
+ export function runHarperCommand({ args, env, completionMessage, logDir, harperBinPath, timeoutMs, maxMs, }) {
183
+ const harperScript = getHarperScript(harperBinPath);
184
+ const runtime = HARPER_RUNTIME;
185
+ const runtimeArgs = runtime === 'bun'
186
+ ? [harperScript, ...args]
187
+ : ['--trace-warnings', '--force-node-api-uncaught-exceptions-policy=true', harperScript, ...args];
188
+ const proc = spawn(runtime, runtimeArgs, {
189
+ env: { ...process.env, ...env },
190
+ // On POSIX, run Harper as its own process-group leader so teardown can signal the whole
191
+ // group (parent + any worker children), not just the direct child. Windows has no process
192
+ // groups; killHarper uses `taskkill /T` there instead. stdio stays piped (not detached),
193
+ // and we never unref, so output capture and lifetime management are unchanged.
194
+ detached: process.platform !== 'win32',
195
+ });
196
+ // Reap this process's tree if the runner exits/is interrupted before teardown (it's detached,
197
+ // so it would otherwise survive signals sent to the runner's group).
198
+ trackHarperProcess(proc);
199
+ let stdoutStream;
200
+ let stderrStream;
201
+ if (logDir) {
202
+ stdoutStream = createWriteStream(join(logDir, 'stdout.log'));
203
+ stderrStream = createWriteStream(join(logDir, 'stderr.log'));
204
+ }
205
+ const idleTimeoutMs = timeoutMs ?? DEFAULT_STARTUP_TIMEOUT_MS;
206
+ const maxTimeoutMs = maxMs ?? DEFAULT_STARTUP_MAX_MS;
207
+ return new Promise((resolve, reject) => {
208
+ let stdout = '';
209
+ let stderr = '';
210
+ let settled = false;
211
+ let idleTimer;
212
+ let maxTimer;
213
+ const clearTimers = () => {
214
+ clearTimeout(idleTimer);
215
+ clearTimeout(maxTimer);
216
+ };
217
+ const failStartup = (message) => {
218
+ if (settled)
219
+ return;
220
+ settled = true;
221
+ clearTimers();
222
+ reject(new HarperStartupError(message, stdout, stderr));
223
+ // Harper is spawned detached (its own process group), so `proc.kill()` would only hit the
224
+ // direct child and orphan any worker children still holding ports. Reap the whole tree.
225
+ signalHarperTree(proc, 'SIGKILL');
226
+ };
227
+ const succeed = () => {
228
+ if (settled)
229
+ return;
230
+ settled = true;
231
+ clearTimers();
232
+ resolve({ process: proc, stdout, stderr });
233
+ };
234
+ // Reset on every chunk of output so the limit is time-since-last-progress, not total boot
235
+ // time: a slow-but-healthy boot that keeps logging never trips it, only true silence does.
236
+ const resetIdleTimer = () => {
237
+ if (settled)
238
+ return;
239
+ clearTimeout(idleTimer);
240
+ idleTimer = setTimeout(() => failStartup(`Harper produced no startup output for ${idleTimeoutMs}ms before reporting ready (likely hung)`), idleTimeoutMs);
241
+ };
242
+ // Absolute backstop, regardless of ongoing output.
243
+ maxTimer = setTimeout(() => failStartup(`Harper did not report ready within the maximum startup time of ${maxTimeoutMs}ms`), maxTimeoutMs);
244
+ resetIdleTimer();
245
+ proc.stdout?.on('data', (data) => {
246
+ const dataString = stripAnsi(data.toString());
247
+ stdoutStream?.write(dataString);
248
+ // Once ready, keep streaming logs to disk but stop the watchdog and capture: the
249
+ // returned startupOutput is a snapshot taken at readiness, and the server may run
250
+ // (and log) for the rest of the suite.
251
+ if (settled)
252
+ return;
253
+ resetIdleTimer();
254
+ stdout += dataString;
255
+ // Match against the accumulated output, not just this chunk, so a marker split across
256
+ // two stream chunks is still detected.
257
+ if (completionMessage && stdout.includes(completionMessage)) {
258
+ succeed();
259
+ }
260
+ });
261
+ proc.stderr?.on('data', (data) => {
262
+ const dataString = stripAnsi(data.toString());
263
+ stderrStream?.write(dataString);
264
+ if (settled)
265
+ return;
266
+ resetIdleTimer();
267
+ stderr += dataString;
268
+ });
269
+ proc.on('error', (error) => {
270
+ if (settled)
271
+ return;
272
+ settled = true;
273
+ clearTimers();
274
+ // 'exit' won't fire for a failed spawn, so close the log streams here to avoid leaking FDs.
275
+ stdoutStream?.end();
276
+ stderrStream?.end();
277
+ reject(error);
278
+ });
279
+ proc.on('exit', (statusCode, signal) => {
280
+ clearTimers();
281
+ if (!settled) {
282
+ settled = true;
283
+ if (statusCode === 0) {
284
+ resolve({ process: proc, stdout, stderr });
285
+ }
286
+ else {
287
+ const errorMessage = `Harper process failed with exit code/signal ${statusCode ?? signal}`;
288
+ stderrStream?.write(errorMessage);
289
+ reject(new HarperStartupError(errorMessage, stdout, stderr));
290
+ }
291
+ }
292
+ stdoutStream?.end();
293
+ stderrStream?.end();
294
+ });
295
+ });
296
+ }
297
+ /**
298
+ * Sets up a Harper instance with a component pre-installed from a local directory.
299
+ *
300
+ * Copies `fixturePath` into `{dataRootDir}/components/{name}` before Harper starts,
301
+ * so the component is available on the first request without a post-startup deploy.
302
+ * Use this when tests need a known route available at startup (e.g. mTLS cert tests).
303
+ *
304
+ * @param ctx - The test context to populate with Harper instance details
305
+ * @param fixturePath - Absolute path to the component directory to pre-install
306
+ * @param options - Optional configuration for the setup process
307
+ */
308
+ export async function setupHarperWithFixture(ctx, fixturePath, options) {
309
+ const dataRootDirPrefix = join(process.env.HARPER_INTEGRATION_TEST_INSTALL_PARENT_DIR || tmpdir(), 'harper-integration-test-');
310
+ const dataRootDir = await mkdtemp(dataRootDirPrefix);
311
+ await cp(fixturePath, join(dataRootDir, 'components', basename(fixturePath)), { recursive: true, dereference: true });
312
+ ctx.harper = { dataRootDir };
313
+ return startHarper(ctx, options);
314
+ }
315
+ /**
316
+ * Sets up and starts a Harper instance for testing.
317
+ *
318
+ * @param ctx - The test context to populate with Harper instance details
319
+ * @param options - Optional configuration for the setup process
320
+ * @returns The context with the `harper` property populated
321
+ *
322
+ * @example
323
+ * ```ts
324
+ * suite('My tests', (ctx: ContextWithHarper) => {
325
+ * before(async () => {
326
+ * await startHarper(ctx);
327
+ * });
328
+ *
329
+ * after(async () => {
330
+ * await teardownHarper(ctx);
331
+ * });
332
+ *
333
+ * test('can connect', async () => {
334
+ * const response = await fetch(ctx.harper.httpURL);
335
+ * // ...
336
+ * });
337
+ * });
338
+ * ```
339
+ */
340
+ export async function startHarper(ctx, options) {
341
+ const dataRootDirPrefix = join(process.env.HARPER_INTEGRATION_TEST_INSTALL_PARENT_DIR || tmpdir(), `harper-integration-test-`);
342
+ const dataRootDir = ctx.harper?.dataRootDir ?? (await mkdtemp(dataRootDirPrefix));
343
+ const loopbackAddress = ctx.harper?.hostname ?? (await getNextAvailableLoopbackAddress());
344
+ // Set up per-suite log directory when HARPER_INTEGRATION_TEST_LOG_DIR is configured
345
+ const logDirEnv = process.env.HARPER_INTEGRATION_TEST_LOG_DIR;
346
+ let logDir;
347
+ if (logDirEnv) {
348
+ const suiteName = sanitizeForFilesystem(ctx.name || 'unknown');
349
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
350
+ logDir = join(logDirEnv, `${suiteName}-${sanitizeForFilesystem(loopbackAddress)}-${timestamp}`);
351
+ await mkdir(logDir, { recursive: true });
352
+ // Output for the test runner (e.g. run.ts) to map this log dir to the current test file
353
+ process.stdout.write(`${LOG_DIR_MARKER_PREFIX} ${logDir}\n`);
354
+ }
355
+ // Point Harper's log directory to the suite log dir so hdb.log is preserved for upload
356
+ const config = { ...options?.config };
357
+ if (logDir) {
358
+ config.logging = { ...config.logging, root: logDir };
359
+ }
360
+ const args = [
361
+ `--ROOTPATH=${dataRootDir}`,
362
+ `--AUTHENTICATION_AUTHORIZELOCAL=true`,
363
+ `--HDB_ADMIN_USERNAME=${DEFAULT_ADMIN_USERNAME}`,
364
+ `--HDB_ADMIN_PASSWORD=${DEFAULT_ADMIN_PASSWORD}`,
365
+ '--THREADS_COUNT=1',
366
+ '--THREADS_DEBUG=false',
367
+ `--NODE_HOSTNAME=${loopbackAddress}`,
368
+ `--HTTP_PORT=${loopbackAddress}:${HTTP_PORT}`,
369
+ `--OPERATIONSAPI_NETWORK_PORT=${loopbackAddress}:${OPERATIONS_API_PORT}`,
370
+ `--MQTT_NETWORK_PORT=${loopbackAddress}:${MQTT_PORT}`,
371
+ `--MQTT_NETWORK_SECUREPORT=${loopbackAddress}:${MQTTS_PORT}`,
372
+ '--LOGGING_LEVEL=debug',
373
+ '--LOGGING_STDSTREAMS=false',
374
+ ];
375
+ // Bind secure port if HTTPS is needed (mTLS or other TLS config present)
376
+ if (options?.config?.http?.mtls !== undefined || options?.config?.tls !== undefined) {
377
+ args.push(`--HTTP_SECUREPORT=${loopbackAddress}:${HTTPS_PORT}`);
378
+ }
379
+ // HARPER_SET_CONFIG must be passed as an environment variable, not a CLI arg,
380
+ // because applyRuntimeEnvVarConfig reads from process.env.HARPER_SET_CONFIG
381
+ const harperEnv = {
382
+ HARPER_SET_CONFIG: JSON.stringify(config),
383
+ ...options?.env,
384
+ };
385
+ const result = await runHarperCommand({
386
+ args,
387
+ env: harperEnv,
388
+ completionMessage: 'successfully started',
389
+ logDir,
390
+ harperBinPath: options?.harperBinPath,
391
+ timeoutMs: options?.startupTimeoutMs,
392
+ maxMs: options?.startupMaxMs,
393
+ });
394
+ ctx.harper = {
395
+ dataRootDir,
396
+ admin: {
397
+ username: DEFAULT_ADMIN_USERNAME,
398
+ password: DEFAULT_ADMIN_PASSWORD,
399
+ },
400
+ httpURL: `http://${loopbackAddress}:${HTTP_PORT}`,
401
+ operationsAPIURL: `http://${loopbackAddress}:${OPERATIONS_API_PORT}`,
402
+ hostname: loopbackAddress,
403
+ process: result.process,
404
+ logDir,
405
+ startupOutput: { stdout: result.stdout, stderr: result.stderr },
406
+ };
407
+ return ctx;
408
+ }
409
+ /**
410
+ * Signals Harper's entire process tree, not just the direct child.
411
+ *
412
+ * Harper runs its listeners in worker threads (in-process), but components can also spawn
413
+ * child processes, so we target the whole tree to be safe:
414
+ * - POSIX: Harper is spawned as its own process-group leader (`detached`), so a negative PID
415
+ * signals the group (parent + descendants).
416
+ * - Windows: there are no process groups, so we shell out to `taskkill /T` (tree). `/F` (force)
417
+ * is required to actually terminate console processes, so it is used for the SIGKILL step.
418
+ *
419
+ * Best-effort: errors (e.g. the process already exited) are swallowed; teardown's port assertion
420
+ * and the wait-for-exit are the safety nets.
421
+ */
422
+ function signalHarperTree(proc, signal) {
423
+ const pid = proc.pid;
424
+ if (pid === undefined)
425
+ return;
426
+ if (process.platform === 'win32') {
427
+ const args = ['/pid', String(pid), '/T'];
428
+ if (signal === 'SIGKILL')
429
+ args.push('/F');
430
+ try {
431
+ spawn('taskkill', args, { stdio: 'ignore' }).on('error', () => { });
432
+ }
433
+ catch {
434
+ try {
435
+ proc.kill(signal);
436
+ }
437
+ catch {
438
+ // process already gone
439
+ }
440
+ }
441
+ return;
442
+ }
443
+ try {
444
+ // Negative PID => signal the whole process group (proc is the group leader via `detached`).
445
+ process.kill(-pid, signal);
446
+ }
447
+ catch {
448
+ // Group already gone, or (defensively) not a group leader — fall back to the direct child.
449
+ try {
450
+ proc.kill(signal);
451
+ }
452
+ catch {
453
+ // process already gone
454
+ }
455
+ }
456
+ }
457
+ /**
458
+ * Tracks live Harper processes so that if the test runner exits or is interrupted before teardown
459
+ * runs (Ctrl+C, or a CI job killing the runner), their process trees are reaped instead of left
460
+ * orphaned holding the fixed ports. This matters specifically because Harper is spawned `detached`
461
+ * (its own process group), so it would otherwise survive signals delivered to the runner's group.
462
+ *
463
+ * Best-effort: on POSIX the group SIGKILL is delivered synchronously (works even from the 'exit'
464
+ * handler); on Windows the `taskkill` shell-out may not complete before the runner exits.
465
+ */
466
+ const liveHarperProcesses = new Set();
467
+ let runnerCleanupRegistered = false;
468
+ function trackHarperProcess(proc) {
469
+ liveHarperProcesses.add(proc);
470
+ proc.once('exit', () => liveHarperProcesses.delete(proc));
471
+ if (runnerCleanupRegistered)
472
+ return;
473
+ runnerCleanupRegistered = true;
474
+ const reapAll = () => {
475
+ for (const child of liveHarperProcesses)
476
+ signalHarperTree(child, 'SIGKILL');
477
+ };
478
+ process.once('exit', reapAll);
479
+ // SIGINT/SIGTERM don't fire 'exit'; reap, then re-raise so the runner still terminates normally.
480
+ for (const signal of ['SIGINT', 'SIGTERM']) {
481
+ process.once(signal, () => {
482
+ reapAll();
483
+ process.kill(process.pid, signal);
484
+ });
485
+ }
486
+ }
487
+ /**
488
+ * Kill harper process (can be used for teardown, or killing it before a restart).
489
+ *
490
+ * Sends SIGTERM to Harper's whole process tree first and gives it a grace period to shut down
491
+ * cleanly (flush RocksDB, release ports, reap worker children) before escalating to SIGKILL.
492
+ * After SIGKILL it waits briefly for the process to actually exit, so callers can rely on it
493
+ * being gone — and, since a dead process releases its listening sockets, on its ports being free.
494
+ *
495
+ * @param ctx
496
+ * @param options.graceMs Time to wait after SIGTERM before sending SIGKILL. Defaults to
497
+ * {@link DEFAULT_TEARDOWN_GRACE_MS}.
498
+ */
499
+ export async function killHarper(ctx, options) {
500
+ const proc = ctx.harper?.process;
501
+ if (!proc)
502
+ return;
503
+ // Already exited — nothing to do.
504
+ if (proc.exitCode !== null || proc.signalCode !== null)
505
+ return;
506
+ const graceMs = options?.graceMs ?? DEFAULT_TEARDOWN_GRACE_MS;
507
+ await new Promise((resolve) => {
508
+ let done = false;
509
+ let sigkillTimer;
510
+ let backstopTimer;
511
+ const finish = () => {
512
+ if (done)
513
+ return;
514
+ done = true;
515
+ proc.off('exit', finish);
516
+ clearTimeout(sigkillTimer);
517
+ clearTimeout(backstopTimer);
518
+ resolve();
519
+ };
520
+ proc.once('exit', finish);
521
+ // Ask the whole Harper tree to shut down cleanly first.
522
+ signalHarperTree(proc, 'SIGTERM');
523
+ // If it hasn't exited within the grace period, force-kill the tree and wait briefly for the
524
+ // 'exit' event before resolving (with a backstop in case it never fires).
525
+ sigkillTimer = setTimeout(() => {
526
+ signalHarperTree(proc, 'SIGKILL');
527
+ backstopTimer = setTimeout(finish, SIGKILL_EXIT_WAIT_MS);
528
+ }, graceMs);
529
+ });
530
+ }
531
+ /**
532
+ * Tears down a Harper instance and cleans up all resources.
533
+ *
534
+ * This function stops the Harper instance, releases the loopback address,
535
+ * and removes the installation directory.
536
+ * @param ctx - The test context with Harper instance details
537
+ *
538
+ * @example
539
+ * ```ts
540
+ * suite('My tests', (ctx: ContextWithHarper) => {
541
+ * before(async () => {
542
+ * await startHarper(ctx);
543
+ * });
544
+ *
545
+ * after(async () => {
546
+ * await teardownHarper(ctx);
547
+ * });
548
+ * });
549
+ * ```
550
+ */
551
+ export async function teardownHarper(ctx) {
552
+ if (!ctx.harper)
553
+ return;
554
+ await killHarper(ctx);
555
+ // Safety assertion: killHarper waits for Harper's process tree to exit, which releases its
556
+ // listening sockets, so the fixed ports should already be free here. We still verify before
557
+ // recycling the address (the pool only guarantees the *address* is bindable, not that these
558
+ // specific ports are free) and warn if anything is somehow still holding them — that warning
559
+ // is a signal that a Harper child process escaped the tree kill, not normal operation. The
560
+ // address is recycled regardless.
561
+ if (ctx.harper.hostname) {
562
+ const portsFreed = await waitForPortsFree(ctx.harper.hostname, ALL_HARPER_PORTS, DEFAULT_PORT_RELEASE_TIMEOUT_MS);
563
+ if (portsFreed) {
564
+ await releaseLoopbackAddress(ctx.harper.hostname);
565
+ }
566
+ else {
567
+ // A Harper child escaped the tree kill and still holds this address's ports. Do NOT
568
+ // recycle the address — under SO_REUSEPORT a later suite could silently co-bind it
569
+ // (the exact failure the loopback pool exists to prevent). Leave the slot parked under
570
+ // this process's PID; it is reclaimed when this (per-file) process exits and its PID
571
+ // goes dead. The conflict canary in getNextAvailableLoopbackAddress is the backstop if
572
+ // the address is somehow handed out before then.
573
+ console.warn(`Harper ports on ${ctx.harper.hostname} still in use after teardown (${DEFAULT_PORT_RELEASE_TIMEOUT_MS}ms); NOT recycling the address (a Harper child outlived the kill). The slot will be reclaimed when this process exits.`);
574
+ }
575
+ }
576
+ // a few retries are typically necessary, might take a sec for a process to finish, especially since rocksdb may be flushing
577
+ try {
578
+ await rm(ctx.harper.dataRootDir, { recursive: true, force: true, maxRetries: 10 });
579
+ }
580
+ catch (error) {
581
+ console.error('Error removing directory', error);
582
+ }
583
+ }
584
+ export async function sendOperation(context, operation) {
585
+ const response = await fetch(context.operationsAPIURL, {
586
+ method: 'POST',
587
+ headers: { 'Content-Type': 'application/json' },
588
+ body: JSON.stringify(operation),
589
+ });
590
+ const responseData = await response.json();
591
+ equal(response.status, 200, JSON.stringify(responseData));
592
+ return responseData;
593
+ }
594
+ //# sourceMappingURL=harperLifecycle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"harperLifecycle.js","sourceRoot":"","sources":["../src/harperLifecycle.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export { startHarper, setupHarperWithFixture, killHarper, teardownHarper, sendOperation, createHarperContext, HarperStartupError, OPERATIONS_API_PORT, DEFAULT_ADMIN_USERNAME, DEFAULT_ADMIN_PASSWORD, DEFAULT_STARTUP_TIMEOUT_MS, HARPER_RUNTIME, type StartHarperOptions, type HarperContext, type HarperTestContext, type StartedHarperTestContext, type ContextWithHarper, } from './harperLifecycle.ts';
2
+ export { validateLoopbackAddressPool, getNextAvailableLoopbackAddress, releaseLoopbackAddress, releaseAllLoopbackAddressesForCurrentProcess, } from './loopbackAddressPool.ts';
3
+ export { targz } from './targz.ts';
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export { startHarper, setupHarperWithFixture, killHarper, teardownHarper, sendOperation, createHarperContext, HarperStartupError, OPERATIONS_API_PORT, DEFAULT_ADMIN_USERNAME, DEFAULT_ADMIN_PASSWORD, DEFAULT_STARTUP_TIMEOUT_MS, HARPER_RUNTIME, } from "./harperLifecycle.js";
2
+ export { validateLoopbackAddressPool, getNextAvailableLoopbackAddress, releaseLoopbackAddress, releaseAllLoopbackAddressesForCurrentProcess, } from "./loopbackAddressPool.js";
3
+ export { targz } from "./targz.js";
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,WAAW,EACX,sBAAsB,EACtB,UAAU,EACV,cAAc,EACd,aAAa,EACb,mBAAmB,EACnB,kBAAkB,EAClB,mBAAmB,EACnB,sBAAsB,EACtB,sBAAsB,EACtB,0BAA0B,EAC1B,cAAc,GAMd,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EACN,2BAA2B,EAC3B,+BAA+B,EAC/B,sBAAsB,EACtB,4CAA4C,GAC5C,MAAM,0BAA0B,CAAC;AAElC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC"}