From 54d51cc3d90d168152101fb35fb0f28852915d1d Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 26 Oct 2026 14:28:16 +0000 Subject: [PATCH] test(cli): cut per-spawn cost in five migrate integration suites Four suites spawned the source entry through the tsx binary; spawn it with this process's node, as the family's other spawners do (same entry, same src/, same pinned NODE_ENV). object-scope read its fixture database through a child node per read; read it in-process with better-sqlite3. Co-authored-by: Claude Claude-Session: https://claude.ai/code/session_01XYjtBuXuGmMz385DUttmzN --- .../migrate/object-scope.integration.test.ts | 50 +++++++++---------- .../plan.boot-parity.integration.test.ts | 12 +++-- ...red-default-datasource.integration.test.ts | 12 +++-- ...ullable-tenant-columns.integration.test.ts | 12 +++-- ...plan.telemetry-sibling.integration.test.ts | 12 +++-- 5 files changed, 61 insertions(+), 37 deletions(-) diff --git a/packages/cli/src/commands/migrate/object-scope.integration.test.ts b/packages/cli/src/commands/migrate/object-scope.integration.test.ts index 6bf613f5bb3..95bad80de1b 100644 --- a/packages/cli/src/commands/migrate/object-scope.integration.test.ts +++ b/packages/cli/src/commands/migrate/object-scope.integration.test.ts @@ -47,6 +47,7 @@ import { copyFileSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'nod import { tmpdir } from 'node:os'; import { dirname, join, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; +import Database from 'better-sqlite3'; const HERE = dirname(fileURLToPath(import.meta.url)); const CLI_ROOT = resolve(HERE, '..', '..', '..'); @@ -125,26 +126,14 @@ process.exit(0); `; /** The repro's off-shape value, written past the write path: a location keyed the retired way. */ -const OFF_SHAPE_CHILD = ` -const { SqlDriver } = await import('@objectstack/driver-sql'); -const raw = new SqlDriver({ client: 'better-sqlite3', connection: { filename: process.env.FIXTURE_DB }, useNullAsDefault: true }); -await raw.knex('os21644_site').where({ id: 's1' }).update({ geo: JSON.stringify({ latitude: 1, longitude: 2 }) }); -await raw.disconnect(); -process.exit(0); -`; - -/** The flag rows and the app rows, read on a connection of our own. */ -const READ_STATE_CHILD = ` -const { SqlDriver } = await import('@objectstack/driver-sql'); -const raw = new SqlDriver({ client: 'better-sqlite3', connection: { filename: process.env.FIXTURE_DB }, useNullAsDefault: true }); -const k = raw.knex; -const flags = await k('sys_migration').select('id', 'last_run_at', 'verified_at', 'blocking').orderBy('id'); -const site = await k('os21644_site').select('id', 'geo').orderBy('id'); -const product = await k('os21644_product').select('id', 'image').orderBy('id'); -await raw.disconnect(); -process.stdout.write(JSON.stringify({ flags, site, product })); -process.exit(0); -`; +function writeOffShape(db: string): void { + const conn = new Database(db); + try { + conn.prepare('UPDATE os21644_site SET geo = ? WHERE id = ?').run(JSON.stringify({ latitude: 1, longitude: 2 }), 's1'); + } finally { + conn.close(); + } +} interface Run { code: number | null; @@ -186,10 +175,22 @@ function freshDb(): string { return db; } +/** + * The flag rows and the app rows, read on a connection of our own, in this + * process: a child `node` per read cost ~0.5 s each, sixteen times a file. + */ function readState(db: string): State { - const out = childNode(READ_STATE_CHILD, { FIXTURE_DB: db }); - if (out.status !== 0) throw new Error(`could not read the fixture database (status ${out.status})\n${out.stderr}`); - return JSON.parse(out.stdout) as State; + // Opened as the child it replaces opened it (read-write, knex's default). + const conn = new Database(db); + try { + return { + flags: conn.prepare('SELECT id, last_run_at, verified_at, blocking FROM sys_migration ORDER BY id').all() as FlagRow[], + site: conn.prepare('SELECT id, geo FROM os21644_site ORDER BY id').all() as State['site'], + product: conn.prepare('SELECT id, image FROM os21644_product ORDER BY id').all() as State['product'], + }; + } finally { + conn.close(); + } } function flagRow(state: State, id: string): FlagRow | undefined { @@ -296,8 +297,7 @@ beforeAll(async () => { // The measured repro: one off-shape value stored, then a misspelled --object --apply. const reproDb = freshDb(); - const offShape = childNode(OFF_SHAPE_CHILD, { FIXTURE_DB: reproDb }); - if (offShape.status !== 0) throw new Error(`the off-shape value was not written\n${offShape.stderr}`); + writeOffShape(reproDb); const reproRightDb = join(dir, 'data', 'repro-right.db'); copyFileSync(reproDb, reproRightDb); reproFullDry = await runCommand('value-shapes', ['--json'], reproDb); diff --git a/packages/cli/src/commands/migrate/plan.boot-parity.integration.test.ts b/packages/cli/src/commands/migrate/plan.boot-parity.integration.test.ts index 71195d64238..86939034c4c 100644 --- a/packages/cli/src/commands/migrate/plan.boot-parity.integration.test.ts +++ b/packages/cli/src/commands/migrate/plan.boot-parity.integration.test.ts @@ -57,8 +57,14 @@ import Database from 'better-sqlite3'; const HERE = dirname(fileURLToPath(import.meta.url)); const CLI_ROOT = resolve(HERE, '..', '..', '..'); +/** + * The source entry, spawned by this process's own `node`, as the family's other spawners + * (`data-commands.absent-database`, `object-scope`, `unmapped-columns`) spawn it. Not through + * the `tsx` binary: the entry, its `src/` and its pinned `NODE_ENV=development` are the same + * either way, and the wrapper only adds a second process and its own transform, ~3.4 s of + * every spawn (#23181 slice 12: `tsx bin/run-dev.js --version` 8.28-8.44 s, `node` 4.93-4.99 s). + */ const RUN_DEV = resolve(CLI_ROOT, 'bin', 'run-dev.js'); -const TSX = resolve(CLI_ROOT, '..', '..', 'node_modules', '.bin', 'tsx'); const CASE_TIMEOUT_MS = 300_000; const NOT_AN_OBJECT = ['sys_packages']; @@ -98,10 +104,10 @@ interface Reading { function bootThenPlan(dir: string): Reading { const dbFile = join(dir, 'boot.db'); const env = childEnv(dir, dbFile); - const boot = spawnSync(TSX, [RUN_DEV, 'serve', '--no-server', '--no-ui', '--no-console', '--port', '0'], { + const boot = spawnSync(process.execPath, [RUN_DEV, 'serve', '--no-server', '--no-ui', '--no-console', '--port', '0'], { cwd: dir, env: { ...env, OS_MIGRATE_AND_EXIT: '1' }, encoding: 'utf8', timeout: 240_000, }); - const plan = spawnSync(TSX, [RUN_DEV, 'migrate', 'plan', '--json'], { + const plan = spawnSync(process.execPath, [RUN_DEV, 'migrate', 'plan', '--json'], { cwd: dir, env, encoding: 'utf8', timeout: 240_000, }); let payload: any; diff --git a/packages/cli/src/commands/migrate/plan.declared-default-datasource.integration.test.ts b/packages/cli/src/commands/migrate/plan.declared-default-datasource.integration.test.ts index 6dabed3f68d..502ff46adbd 100644 --- a/packages/cli/src/commands/migrate/plan.declared-default-datasource.integration.test.ts +++ b/packages/cli/src/commands/migrate/plan.declared-default-datasource.integration.test.ts @@ -55,8 +55,14 @@ import Database from 'better-sqlite3'; const HERE = dirname(fileURLToPath(import.meta.url)); const CLI_ROOT = resolve(HERE, '..', '..', '..'); +/** + * The source entry, spawned by this process's own `node`, as the family's other spawners + * (`data-commands.absent-database`, `object-scope`, `unmapped-columns`) spawn it. Not through + * the `tsx` binary: the entry, its `src/` and its pinned `NODE_ENV=development` are the same + * either way, and the wrapper only adds a second process and its own transform, ~3.4 s of + * every spawn (#23181 slice 12: `tsx bin/run-dev.js --version` 8.28-8.44 s, `node` 4.93-4.99 s). + */ const RUN_DEV = resolve(CLI_ROOT, 'bin', 'run-dev.js'); -const TSX = resolve(CLI_ROOT, '..', '..', 'node_modules', '.bin', 'tsx'); const CASE_TIMEOUT_MS = 600_000; const SPAWN_TIMEOUT_MS = 240_000; @@ -134,7 +140,7 @@ async function freePort(): Promise { */ function devBootOnce(): string { const r = spawnSync( - TSX, + process.execPath, [RUN_DEV, 'dev', '--port', String(port), '--compile', '--no-watch', '--no-restart', '--no-seed-admin'], { cwd: dir, env: env({ OS_MIGRATE_AND_EXIT: '1' }), encoding: 'utf8', timeout: SPAWN_TIMEOUT_MS }, ); @@ -145,7 +151,7 @@ function devBootOnce(): string { /** One read-only `os migrate` door's `--json` document. */ function migrateJson(args: string[]): any { - const r = spawnSync(TSX, [RUN_DEV, 'migrate', ...args, '--json'], { + const r = spawnSync(process.execPath, [RUN_DEV, 'migrate', ...args, '--json'], { cwd: dir, env: env(), encoding: 'utf8', timeout: SPAWN_TIMEOUT_MS, }); expect(r.status, `os migrate ${args.join(' ')} failed:\n${`${r.stdout}\n${r.stderr}`.slice(-1500)}`).toBe(0); diff --git a/packages/cli/src/commands/migrate/plan.nullable-tenant-columns.integration.test.ts b/packages/cli/src/commands/migrate/plan.nullable-tenant-columns.integration.test.ts index f8ad44276b8..306c6cd073d 100644 --- a/packages/cli/src/commands/migrate/plan.nullable-tenant-columns.integration.test.ts +++ b/packages/cli/src/commands/migrate/plan.nullable-tenant-columns.integration.test.ts @@ -32,8 +32,14 @@ import Database from 'better-sqlite3'; const HERE = dirname(fileURLToPath(import.meta.url)); const CLI_ROOT = resolve(HERE, '..', '..', '..'); +/** + * The source entry, spawned by this process's own `node`, as the family's other spawners + * (`data-commands.absent-database`, `object-scope`, `unmapped-columns`) spawn it. Not through + * the `tsx` binary: the entry, its `src/` and its pinned `NODE_ENV=development` are the same + * either way, and the wrapper only adds a second process and its own transform, ~3.4 s of + * every spawn (#23181 slice 12: `tsx bin/run-dev.js --version` 8.28-8.44 s, `node` 4.93-4.99 s). + */ const RUN_DEV = resolve(CLI_ROOT, 'bin', 'run-dev.js'); -const TSX = resolve(CLI_ROOT, '..', '..', 'node_modules', '.bin', 'tsx'); const CASE_TIMEOUT_MS = 300_000; const APP_OBJECT = 'os25134_deal'; @@ -93,14 +99,14 @@ function childEnv(f: Fixture, extra: NodeJS.ProcessEnv = {}): NodeJS.ProcessEnv /** `os serve`'s own provision-and-exit: a fresh store, the full composition schema-synced into it. */ function serveBoot(f: Fixture): void { - const boot = spawnSync(TSX, [RUN_DEV, 'serve', '--no-server', '--no-ui', '--no-console', '--port', '0'], { + const boot = spawnSync(process.execPath, [RUN_DEV, 'serve', '--no-server', '--no-ui', '--no-console', '--port', '0'], { cwd: f.dir, env: childEnv(f, { OS_MIGRATE_AND_EXIT: '1' }), encoding: 'utf8', timeout: 240_000, }); expect(boot.status, `the serving boot did not provision the database:\n${(boot.stderr ?? '').slice(-6000)}`).toBe(0); } function plan(f: Fixture, json: boolean): { exit: number | null; payload: any; text: string } { - const run = spawnSync(TSX, [RUN_DEV, 'migrate', 'plan', ...(json ? ['--json'] : [])], { + const run = spawnSync(process.execPath, [RUN_DEV, 'migrate', 'plan', ...(json ? ['--json'] : [])], { cwd: f.dir, env: childEnv(f), encoding: 'utf8', timeout: 240_000, }); let payload: any; diff --git a/packages/cli/src/commands/migrate/plan.telemetry-sibling.integration.test.ts b/packages/cli/src/commands/migrate/plan.telemetry-sibling.integration.test.ts index 68aea502606..9a7379bb210 100644 --- a/packages/cli/src/commands/migrate/plan.telemetry-sibling.integration.test.ts +++ b/packages/cli/src/commands/migrate/plan.telemetry-sibling.integration.test.ts @@ -46,8 +46,14 @@ import Database from 'better-sqlite3'; const HERE = dirname(fileURLToPath(import.meta.url)); const CLI_ROOT = resolve(HERE, '..', '..', '..'); +/** + * The source entry, spawned by this process's own `node`, as the family's other spawners + * (`data-commands.absent-database`, `object-scope`, `unmapped-columns`) spawn it. Not through + * the `tsx` binary: the entry, its `src/` and its pinned `NODE_ENV=development` are the same + * either way, and the wrapper only adds a second process and its own transform, ~3.4 s of + * every spawn (#23181 slice 12: `tsx bin/run-dev.js --version` 8.28-8.44 s, `node` 4.93-4.99 s). + */ const RUN_DEV = resolve(CLI_ROOT, 'bin', 'run-dev.js'); -const TSX = resolve(CLI_ROOT, '..', '..', 'node_modules', '.bin', 'tsx'); const CASE_TIMEOUT_MS = 300_000; /** The one table outside the object set (`PackageServicePlugin.start()`'s raw DDL; see the boot-parity pin). */ @@ -116,7 +122,7 @@ const BOOT_STREAM_TAIL = 8_000; * there at `error`), and an assertion that failed without it left the CI log unable to confirm or refute one. */ function serveBoot(f: Fixture, extra: NodeJS.ProcessEnv = {}): string { - const boot = spawnSync(TSX, [RUN_DEV, 'serve', '--no-server', '--no-ui', '--no-console', '--port', '0'], { + const boot = spawnSync(process.execPath, [RUN_DEV, 'serve', '--no-server', '--no-ui', '--no-console', '--port', '0'], { cwd: f.dir, env: childEnv(f, { ...extra, OS_MIGRATE_AND_EXIT: '1' }), encoding: 'utf8', timeout: 240_000, }); const output = @@ -130,7 +136,7 @@ function serveBoot(f: Fixture, extra: NodeJS.ProcessEnv = {}): string { const afterBoot = (bootOutput: string, what: string): string => `${what}\n${bootOutput}`; function command(f: Fixture, argv: string[], extra: NodeJS.ProcessEnv = {}): { exit: number | null; payload: any } { - const run = spawnSync(TSX, [RUN_DEV, 'migrate', ...argv], { cwd: f.dir, env: childEnv(f, extra), encoding: 'utf8', timeout: 240_000 }); + const run = spawnSync(process.execPath, [RUN_DEV, 'migrate', ...argv], { cwd: f.dir, env: childEnv(f, extra), encoding: 'utf8', timeout: 240_000 }); let payload: any; try { payload = JSON.parse(run.stdout); } catch { payload = { unparsed: `${run.stdout}\n${run.stderr}`.slice(-800) }; } return { exit: run.status, payload };