From 6822ad3afc2efbc2b5d1f55e1bf6ef31e35b8f1c Mon Sep 17 00:00:00 2001 From: imccyu <276526105+imccyu@users.noreply.github.com> Date: Thu, 27 Aug 2026 01:27:28 +0800 Subject: [PATCH] test(inspector): enforce execution-plane boundaries --- .../inspector/tests/layout.host.spec.ts | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 packages/experimental/inspector/tests/layout.host.spec.ts diff --git a/packages/experimental/inspector/tests/layout.host.spec.ts b/packages/experimental/inspector/tests/layout.host.spec.ts new file mode 100644 index 0000000000..39b8cf7bf4 --- /dev/null +++ b/packages/experimental/inspector/tests/layout.host.spec.ts @@ -0,0 +1,112 @@ +/** Host-side source layout invariants. */ + +import { readdir, readFile } from 'node:fs/promises' +import { dirname, relative, resolve, sep } from 'node:path' +import { fileURLToPath } from 'node:url' +import { describe, expect, it } from 'vitest' + +const sourceRoot = fileURLToPath(new URL('../src/', import.meta.url)) +const packageRoot = fileURLToPath(new URL('../', import.meta.url)) +const testsRoot = fileURLToPath(new URL('./', import.meta.url)) + +describe('Inspector execution layout', () => { + it('keeps Client and Host implementation paths mirrored', async () => { + expect(await sourceFiles('client')).toEqual(await sourceFiles('host')) + }) + + it('keeps Worker Client and Host backend paths mirrored', async () => { + expect(await sourceFiles('worker/realms/client')).toEqual(await sourceFiles('worker/realms/host')) + }) + + it('keeps shared modules independent of execution-specific directories', async () => { + await expectNoImports('shared', ['client', 'host', 'worker']) + }) + + it('keeps Client and Host modules isolated from each other and the Worker implementation', async () => { + await expectNoImports('client', ['host', 'worker']) + await expectNoImports('host', ['client', 'worker']) + }) + + it('keeps compiler files and specs on their declared execution face', async () => { + const hostFiles = await compilerFiles('tsconfig.host.json') + const clientFiles = await compilerFiles('tsconfig.client.json') + expect(hostFiles.some(file => file.startsWith('src/client/'))).toBe(false) + expect(clientFiles.some(file => file.startsWith('src/host/') || file.startsWith('src/worker/'))).toBe(false) + + const testFiles = (await walk(testsRoot)).filter(file => file.endsWith('.ts')) + const specs = testFiles.filter(file => file.endsWith('.spec.ts')) + expect(specs.every(file => file.endsWith('.host.spec.ts') || file.endsWith('.client.spec.ts'))).toBe(true) + await expectTestImports(testFiles.filter(file => + file.endsWith('.host.ts') || file.endsWith('.host.spec.ts')), ['client']) + await expectTestImports(testFiles.filter(file => + file.endsWith('.client.ts') || file.endsWith('.client.spec.ts')), ['host', 'worker']) + }) + + it('keeps Worker repositories and realm backends independent of the Chrome adapter', async () => { + await expectNoImports('worker/inspection', ['worker/cdp']) + await expectNoImports('worker/realms', ['worker/cdp']) + }) +}) + +async function sourceFiles(directory: string): Promise { + const root = resolve(sourceRoot, directory) + return (await walk(root)) + .filter(file => file.endsWith('.ts')) + .map(file => relative(root, file).split(sep).join('/')) + .sort() +} + +async function compilerFiles(config: string): Promise { + const parsed = JSON.parse(await readFile(resolve(packageRoot, config), 'utf8')) as { files?: unknown } + if (!Array.isArray(parsed.files) || !parsed.files.every(file => typeof file === 'string')) { + throw new Error(`${config} must declare a string files array`) + } + return parsed.files +} + +async function expectTestImports(files: readonly string[], forbidden: readonly string[]): Promise { + for (const file of files) { + const source = await readFile(file, 'utf8') + for (const specifier of relativeSpecifiers(source)) { + const target = resolve(dirname(file), specifier) + for (const directory of forbidden) { + const forbiddenRoot = resolve(sourceRoot, directory) + expect( + target === forbiddenRoot || target.startsWith(`${forbiddenRoot}${sep}`), + `${relative(testsRoot, file)} imports ${specifier}`, + ).toBe(false) + } + } + } +} + +async function expectNoImports(owner: string, forbidden: readonly string[]): Promise { + const root = resolve(sourceRoot, owner) + for (const file of await walk(root)) { + if (!file.endsWith('.ts')) continue + const source = await readFile(file, 'utf8') + for (const specifier of relativeSpecifiers(source)) { + const target = resolve(dirname(file), specifier) + for (const directory of forbidden) { + const forbiddenRoot = resolve(sourceRoot, directory) + expect( + target === forbiddenRoot || target.startsWith(`${forbiddenRoot}${sep}`), + `${relative(sourceRoot, file)} imports ${specifier}`, + ).toBe(false) + } + } + } +} + +async function walk(directory: string): Promise { + const entries = await readdir(directory, { withFileTypes: true }) + const files = await Promise.all(entries.map(async (entry) => { + const value = resolve(directory, entry.name) + return entry.isDirectory() ? await walk(value) : [value] + })) + return files.flat() +} + +function relativeSpecifiers(source: string): string[] { + return [...source.matchAll(/(?:from\s+|import\s*\()['"](\.[^'"]+)['"]/gu)].map(match => match[1] ?? '') +}