test(inspector): enforce execution-plane boundaries
This commit is contained in:
parent
28cc3e930b
commit
6822ad3afc
1 changed files with 112 additions and 0 deletions
112
packages/experimental/inspector/tests/layout.host.spec.ts
Normal file
112
packages/experimental/inspector/tests/layout.host.spec.ts
Normal file
|
|
@ -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<string[]> {
|
||||
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<string[]> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<string[]> {
|
||||
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] ?? '')
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue