From ce10ee252bd63cc2173b3ba78f0b1138cdbcc574 Mon Sep 17 00:00:00 2001 From: Neil Date: Sun, 18 Oct 2026 00:56:55 -0700 Subject: [PATCH] test: tighten the round-2 parity, serve and watcher tests Parity test: one string-skip helper, no infinite loop on an unterminated block comment, no internal shorthand. Serve test: orcadServeArgs takes a bindHost instead of the test splicing argv, typed readiness, and stderr back in the daemon failure. Watcher test: a sentinel at both watched levels. --- .../filesystem-watcher-ignore-real.test.ts | 4 +- .../orcad-serve-parity.integration.test.ts | 38 +++++++++--- .../runtime-composition-parity.test.ts | 62 ++++++++----------- src/shared/orcad-serve-argv.ts | 9 ++- 4 files changed, 64 insertions(+), 49 deletions(-) diff --git a/src/main/ipc/filesystem-watcher-ignore-real.test.ts b/src/main/ipc/filesystem-watcher-ignore-real.test.ts index 9656576108f9..aaaeec7a9a67 100644 --- a/src/main/ipc/filesystem-watcher-ignore-real.test.ts +++ b/src/main/ipc/filesystem-watcher-ignore-real.test.ts @@ -73,9 +73,9 @@ describe('filesystem watcher native ignores', () => { sourceFiles.push(join(root, 'source\nnewline.ts'), join(nestedSource, 'source\nnewline.ts')) } await writeUntilSeen(sourceFiles) - // Why: events arrive in order, so once a file written after the storm is seen, any + // Why: events arrive in order, so once a file written after the storm is seen at both levels, any // unignored generated event is already recorded. Replaces a fixed debounce sleep. - await writeUntilSeen([join(nestedSource, 'sentinel.ts')]) + await writeUntilSeen([join(root, 'sentinel.ts'), join(nestedSource, 'sentinel.ts')]) expect(errors).toEqual([]) const generatedPaths = new Set(generatedFiles) diff --git a/src/main/orcad/orcad-serve-parity.integration.test.ts b/src/main/orcad/orcad-serve-parity.integration.test.ts index 34285f83c35c..f4a6c4494967 100644 --- a/src/main/orcad/orcad-serve-parity.integration.test.ts +++ b/src/main/orcad/orcad-serve-parity.integration.test.ts @@ -203,11 +203,14 @@ describe.skipIf(skip)('orca serve on orcad', () => { const appRoot = profile() mkdirSync(join(appRoot, 'out', 'web'), { recursive: true }) writeFileSync(join(appRoot, 'out', 'web', WEB_CLIENT_INDEX_FILENAME), 'orca web') - // The argv `orca serve` builds, but on loopback: a test must not listen on every interface. - const args = orcadServeArgs({ json: true, port: '0' }, findBundledWebClientRoot(appRoot)) - args.splice(args.indexOf('0.0.0.0'), 1, '127.0.0.1') + // The argv `orca serve` builds, but on loopback. + const args = orcadServeArgs( + { json: true, port: '0' }, + findBundledWebClientRoot(appRoot), + '127.0.0.1' + ) try { - const readiness = await serveOnce(userData, args) + const { readiness } = await serveOnce(userData, args) const webClientUrl: unknown = readiness.pairing?.webClientUrl expect(webClientUrl).toEqual(expect.stringMatching(/^http:\/\/127\.0\.0\.1:\d+\//u)) const index = await fetch(new URL(`/${WEB_CLIENT_INDEX_FILENAME}`, String(webClientUrl))) @@ -262,14 +265,30 @@ const running = new Map>() /** Starts `orca serve --json` on orcad and returns the terminal daemon pid its readiness names. */ async function serveDaemonPid(userData: string): Promise { - const readiness = await serveOnce(userData, ['--bind', '127.0.0.1', '--port', '0', '--json']) + const { readiness, stderr } = await serveOnce(userData, [ + '--bind', + '127.0.0.1', + '--port', + '0', + '--json' + ]) const daemon = readiness.health?.terminalDaemon - expect(daemon?.state).toBe('live') + if (daemon?.state !== 'live') { + throw new Error(`terminal daemon not live (${String(daemon?.state)}): ${stderr}`) + } return daemon.pid } -/** Starts orcad with `args` and returns its parsed readiness line. */ -async function serveOnce(userData: string, args: string[]) { +type ServeReadiness = { + pairing?: { webClientUrl?: unknown } + health?: { terminalDaemon?: { state?: string; pid: number } } +} + +/** Starts orcad with `args` and returns its parsed readiness line and stderr so far. */ +async function serveOnce( + userData: string, + args: string[] +): Promise<{ readiness: ServeReadiness; stderr: string }> { const child = spawnProcess({ program: runtime!, args: [join(slotDir, 'orcad.js'), ...args], @@ -291,7 +310,8 @@ async function serveOnce(userData: string, args: string[]) { } }) }) - return JSON.parse(line) + const readiness: ServeReadiness = JSON.parse(line) + return { readiness, stderr } } /** SIGTERM is serve's graceful stop on POSIX; it leaves the daemon running by design. */ diff --git a/src/main/runtime/construction/runtime-composition-parity.test.ts b/src/main/runtime/construction/runtime-composition-parity.test.ts index 8bf0fd5f8374..ac04222c6899 100644 --- a/src/main/runtime/construction/runtime-composition-parity.test.ts +++ b/src/main/runtime/construction/runtime-composition-parity.test.ts @@ -7,7 +7,7 @@ import { SKILL_INSTALL_CAPABILITY } from '../../../shared/skill-install-capabili /** * Electron (`main-process-*`) and orcad (`orcad-entry.ts`) each compose the same runtime, and every - * dependency is optional, so one root can silently skip a service the other wires (C2: #30120 never + * dependency is optional, so one root can silently skip a service the other wires (#30120 never * gave orcad its web client root). This records what each root's source wires and fails on any * difference that is not declared below with a reason. */ @@ -18,7 +18,7 @@ type DeclaredDifference = { wiring: string missingOn: Root reason: string - /** Capabilities the missing host still advertises; its methods must refuse accurately (D3). */ + /** Capabilities the missing host still advertises; its methods must refuse accurately. */ advertisedUnserved?: readonly string[] } @@ -148,29 +148,37 @@ const SPREAD_SOURCES: Record = { const repoRoot = join(__dirname, '../../../..') const read = (file: string): string => stripComments(readFileSync(join(repoRoot, file), 'utf8')) +const QUOTES = `'"\`` + +/** The index of the quote that closes the string opening at `open` (or the end of the source). */ +function stringEnd(source: string, open: number): number { + for (let i = open + 1; i < source.length; i++) { + if (source[i] === '\\') { + i++ + } else if (source[i] === source[open]) { + return i + } + } + return source.length +} + function stripComments(source: string): string { let out = '' - let quote: string | null = null for (let i = 0; i < source.length; i++) { const char = source[i] - if (quote) { - out += char - if (char === '\\') { - out += source[++i] ?? '' - } else if (char === quote) { - quote = null - } + if (QUOTES.includes(char)) { + const end = stringEnd(source, i) + out += source.slice(i, end + 1) + i = end } else if (char === '/' && source[i + 1] === '/') { while (i < source.length && source[i] !== '\n') { i++ } out += '\n' } else if (char === '/' && source[i + 1] === '*') { - i = source.indexOf('*/', i + 2) + 1 + const end = source.indexOf('*/', i + 2) + i = end === -1 ? source.length : end + 1 } else { - if (char === "'" || char === '"' || char === '`') { - quote = char - } out += char } } @@ -180,19 +188,10 @@ function stripComments(source: string): string { /** The inner text of the brace group that opens at `open` (strings are opaque). */ function braceGroup(source: string, open: number): string { let depth = 0 - let quote: string | null = null for (let i = open; i < source.length; i++) { const char = source[i] - if (quote) { - if (char === '\\') { - i++ - } else if (char === quote) { - quote = null - } - continue - } - if (char === "'" || char === '"' || char === '`') { - quote = char + if (QUOTES.includes(char)) { + i = stringEnd(source, i) } else if (char === '{') { depth++ } else if (char === '}' && --depth === 0) { @@ -218,20 +217,11 @@ function objectLiteralAfter(source: string, anchor: string): string { function topLevelEntries(inner: string): string[] { const entries: string[] = [] let depth = 0 - let quote: string | null = null let start = 0 for (let i = 0; i < inner.length; i++) { const char = inner[i] - if (quote) { - if (char === '\\') { - i++ - } else if (char === quote) { - quote = null - } - continue - } - if (char === "'" || char === '"' || char === '`') { - quote = char + if (QUOTES.includes(char)) { + i = stringEnd(inner, i) } else if ('({['.includes(char)) { depth++ } else if (')}]'.includes(char)) { diff --git a/src/shared/orcad-serve-argv.ts b/src/shared/orcad-serve-argv.ts index 5abf293d1175..4f8fab14aeb9 100644 --- a/src/shared/orcad-serve-argv.ts +++ b/src/shared/orcad-serve-argv.ts @@ -31,10 +31,15 @@ export function serveOptionArgs(args: ServeOrcaAppArgs, prefix: '--' | '--serve- ] } -export function orcadServeArgs(args: ServeOrcaAppArgs, webClientRoot?: string): string[] { +/** `bindHost` exists for tests, which must not listen on every interface. */ +export function orcadServeArgs( + args: ServeOrcaAppArgs, + webClientRoot?: string, + bindHost = '0.0.0.0' +): string[] { return [ '--bind', - '0.0.0.0', + bindHost, ...serveOptionArgs(args, '--'), ...(webClientRoot ? ['--web-client-root', webClientRoot] : []) ]