diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 68fe787010..d317a4eb41 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -446,7 +446,6 @@ jobs: tests/boundary/filesystem/asarInventoryFreshness.test.ts tests/composition/javascript-applications/javascriptArtifactReconstruction.test.ts tests/conformance/providers/ghidra/windowsCapabilities.test.ts - tests/conformance/providers/ghidra/ghidraWindowsFixture.test.ts src/ghidra/GhidraInstallation.test.ts tests/boundary/providers/ghidra/ghidraTransport.test.ts tests/boundary/providers/ghidra/ghidraTargetSnapshot.test.ts diff --git a/docs/testing.md b/docs/testing.md index feb2bdf548..be803f7d60 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -6,10 +6,27 @@ from real captured inputs. Keep focused module tests for distinct failure or semantic cases that these workflows cannot reliably reproduce. A test's path or suite name does not establish its behavioral depth. +Avoid tests that mirror getters, enum mappings, fixture helpers, or prescribed +call sequences. Prefer one representative workflow over a Cartesian matrix +when every row crosses the same boundary. Keep distinct command handlers, +producer representations, failure reasons, and lifecycle states covered. +Consolidate their shared setup without hiding the inputs or expected evidence. + Tests are grouped into Vitest projects so ownership, allowed dependencies and -runtime cost are visible from their paths. Before pruning a test, identify the -replacement scenario and its assertions; passing success journeys do not -replace malformed input, cancellation, permission or cleanup coverage. +runtime cost are visible from their paths. Before pruning a distinct boundary +regression, identify the replacement scenario and its assertions; passing +success journeys do not replace malformed input, cancellation, permission or +cleanup coverage. Trivial helper checks do not need a replacement. If only +tests consume a production module, trace CLI/MCP and verifier imports, +including imports of compiled files, and remove abandoned scaffolding with its +tests when it has no runtime consumer. + +Measure slow files before removing capacity regressions. Optimize repeated +process startup, fixture construction, and production algorithms when they +dominate runtime. Keep at least one input beyond each formerly failing size or +depth; smaller fixtures do not establish that truncation or stack exhaustion +has been fixed. Record the toolchain, coverage mode, and cache conditions when +comparing durations. ## Behavioral depths diff --git a/tests/composition/hopperConfig.test.ts b/tests/composition/hopperConfig.test.ts deleted file mode 100644 index 82c21d5787..0000000000 --- a/tests/composition/hopperConfig.test.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { describe, expect, it } from "vitest"; - -import { - defaultHopperLauncherPath, - parseConfig, -} from "../../src/config/parseConfig.js"; - -describe("Hopper launcher defaults", () => { - it("prefers an executable system Hopper on Linux", () => { - const executable = (path: string) => path === "/opt/hopper/bin/Hopper"; - expect(defaultHopperLauncherPath("linux", "/home/test", executable)).toBe( - "/opt/hopper/bin/Hopper", - ); - }); - - it("falls back to the legacy user-local Hopper on Linux", () => { - const executable = (path: string) => - path === "/home/test/.local/share/rea/hopper/bin/Hopper"; - expect(defaultHopperLauncherPath("linux", "/home/test", executable)).toBe( - "/home/test/.local/share/rea/hopper/bin/Hopper", - ); - }); - - it("keeps the system path as the diagnostic fallback when neither exists", () => { - expect(defaultHopperLauncherPath("linux", "/home/test", () => false)).toBe( - "/opt/hopper/bin/Hopper", - ); - }); - - it("keeps explicit HOPPER_LAUNCHER_PATH authoritative", () => { - const parsed = parseConfig({ - HOPPER_LAUNCHER_PATH: "/custom/Hopper", - }); - expect(parsed.ok).toBe(true); - if (parsed.ok) - expect(parsed.value.hopperLauncherPath).toBe("/custom/Hopper"); - }); -}); diff --git a/tests/conformance/providers/conformanceFixtures.test.ts b/tests/conformance/providers/conformanceFixtures.test.ts deleted file mode 100644 index 4bb5ca6ee7..0000000000 --- a/tests/conformance/providers/conformanceFixtures.test.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { describe, expect, it } from "vitest"; - -import { sourceDigest } from "../../../scripts/lib/conformance-fixtures.mjs"; - -describe("source-built conformance fixtures", () => { - it("hashes source manifests independently of input ordering", () => { - const left = [ - { path: "b.c", content: "b" }, - { path: "a.c", content: "a" }, - ]; - const right = [...left].reverse(); - - expect(sourceDigest(left)).toBe(sourceDigest(right)); - expect(sourceDigest(left)).toMatch(/^[0-9a-f]{64}$/u); - }); -}); diff --git a/tests/conformance/providers/ghidra/ghidraWindowsFixture.test.ts b/tests/conformance/providers/ghidra/ghidraWindowsFixture.test.ts deleted file mode 100644 index 8d8573025d..0000000000 --- a/tests/conformance/providers/ghidra/ghidraWindowsFixture.test.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { execFile } from "node:child_process"; -import { createHash } from "node:crypto"; -import { readFile } from "node:fs/promises"; -import { dirname, resolve } from "node:path"; -import { promisify } from "node:util"; -import { fileURLToPath } from "node:url"; - -import { describe, expect, it } from "vitest"; - -import { parseExecutableHeader } from "../../../../src/domain/binaryTarget.js"; - -const exec = promisify(execFile); -const root = resolve(dirname(fileURLToPath(import.meta.url)), "../../../.."); -const generator = resolve(root, "scripts", "create-ghidra-windows-fixture.mjs"); -const fixture = resolve(root, "build", "fixtures", "rea-ghidra-windows.exe"); -const expectedSha256 = - "732ec6083ae3b63bcbda33f2d0fd7e1a6539babade07cbdae4fc20d3394faf49"; - -describe("controlled Windows Ghidra fixture", () => { - it("generates one deterministic native x86-64 PE application", async () => { - await exec(process.execPath, [generator]); - const bytes = await readFile(fixture); - - expect(createHash("sha256").update(bytes).digest("hex")).toBe( - expectedSha256, - ); - expect(parseExecutableHeader(bytes, "x64")).toMatchObject({ - ok: true, - value: { - format: "pe", - architecture: "x86_64", - executableRole: "application", - managed: false, - }, - }); - }); -}); diff --git a/tests/conformance/providers/managed/managedConformanceFixture.test.ts b/tests/conformance/providers/managed/managedConformanceFixture.test.ts deleted file mode 100644 index 4899a3decf..0000000000 --- a/tests/conformance/providers/managed/managedConformanceFixture.test.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { rm, writeFile } from "node:fs/promises"; -import { join } from "node:path"; - -import { afterEach, describe, expect, it } from "vitest"; - -import { createTestTempDirectory } from "../../../fixtures/temporaryDirectory.js"; - -import { buildManagedPeFixture } from "../../../../scripts/fixtures/managed/pe.mjs"; -import { parseBinaryTarget } from "../../../../src/application/BinaryTargetResolver.js"; - -const workspaces: string[] = []; - -afterEach(async () => { - await Promise.all( - workspaces.splice(0).map((path) => rm(path, { recursive: true })), - ); -}); - -describe("managed conformance fixture", () => { - it("emits PE32+ headers when the COFF machine is x86-64", async () => { - const workspace = await createTestTempDirectory("rea-managed-fixture-"); - workspaces.push(workspace); - const path = join(workspace, "fixture.exe"); - await writeFile(path, buildManagedPeFixture({ machine: 0x8664 })); - - const parsed = await parseBinaryTarget(path); - - expect(parsed).toMatchObject({ - ok: true, - value: { - format: "pe", - architecture: "x86_64", - managed: true, - }, - }); - }); -}); diff --git a/tests/evaluation/package/packageSetupSupport.test.ts b/tests/evaluation/package/packageSetupSupport.test.ts deleted file mode 100644 index 2d803ef589..0000000000 --- a/tests/evaluation/package/packageSetupSupport.test.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { describe, expect, it } from "vitest"; - -import { packageSetupSupport } from "../../../scripts/verify-package-discovery.mjs"; - -describe("packaged setup support classification", () => { - it.each([ - ["linux", true, true, true, true], - ["linux", true, false, true, false], - ["linux", false, true, false, false], - ["linux", false, false, false, false], - ["darwin", true, true, true, true], - ["darwin", true, false, true, false], - ["darwin", false, true, false, false], - ["darwin", false, false, false, false], - ["win32", true, true, true, false], - ["win32", true, false, true, false], - ["win32", false, true, false, false], - ["win32", false, false, false, false], - ] as const)( - "%s with node=%s and host=%s: agent setup=%s, Hopper setup=%s", - (platform, node, host, supportedSetupHost, hopperSetupSupported) => { - expect( - packageSetupSupport( - [ - { name: "host", ok: host }, - { name: "node", ok: node }, - ], - platform, - ), - ).toEqual({ supportedSetupHost, hopperSetupSupported }); - }, - ); - - it.each([ - { checks: undefined }, - { checks: [] }, - { checks: [{ name: "host", ok: true }] }, - ])("requires an affirmative Node check: $checks", ({ checks }) => { - expect(packageSetupSupport(checks, "linux")).toEqual({ - supportedSetupHost: false, - hopperSetupSupported: false, - }); - }); - - it("requires an affirmative host check only for Hopper setup", () => { - expect( - packageSetupSupport( - [ - { name: "node", ok: true }, - { name: "hopper", ok: true }, - ], - "linux", - ), - ).toEqual({ supportedSetupHost: true, hopperSetupSupported: false }); - }); -}); diff --git a/tests/process-global/vitestConfiguration.test.ts b/tests/process-global/vitestConfiguration.test.ts deleted file mode 100644 index 33dee06ff0..0000000000 --- a/tests/process-global/vitestConfiguration.test.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { execFile } from "node:child_process"; -import { readdir } from "node:fs/promises"; -import { join, relative, resolve } from "node:path"; -import { promisify } from "node:util"; - -import { describe, expect, it } from "vitest"; - -const execute = promisify(execFile); -describe("Vitest project configuration", () => { - it("classifies every deterministic test in exactly one project", async () => { - const { stdout } = await execute( - process.execPath, - [ - resolve("node_modules/vitest/vitest.mjs"), - "list", - "--filesOnly", - "--staticParse", - ], - { cwd: process.cwd(), maxBuffer: 4 * 1_024 * 1_024 }, - ); - const classified = parseProjects(stdout); - const repositoryTests = [ - ...(await testFiles("src")), - ...(await testFiles("tests")), - ].sort(); - - expect( - [...classified.values()] - .filter((owners) => owners.length > 1) - .map((owners) => owners.join(", ")), - ).toEqual([]); - expect([...classified.keys()].sort()).toEqual(repositoryTests); - }, 20_000); -}); - -const parseProjects = (output: string): Map => { - const classified = new Map(); - for (const line of output.split("\n")) { - const trimmed = line.trim(); - const separator = trimmed.indexOf("] "); - if (!trimmed.startsWith("[") || separator < 2) continue; - const project = trimmed.slice(1, separator); - const path = trimmed.slice(separator + 2); - if (path.length === 0) continue; - const owners = classified.get(path) ?? []; - owners.push(project); - classified.set(path, owners); - } - return classified; -}; - -const testFiles = async (root: string): Promise => { - const files: string[] = []; - for (const entry of await readdir(root, { withFileTypes: true })) { - const path = join(root, entry.name); - if (entry.isDirectory()) files.push(...(await testFiles(path))); - else if (entry.isFile() && entry.name.endsWith(".test.ts")) - files.push(relative(process.cwd(), path)); - } - return files; -};