From 92cd2469619c12b66bfe43665f47e18821007967 Mon Sep 17 00:00:00 2001 From: Nathan Flurry Date: Wed, 8 Jul 2026 13:18:19 -0700 Subject: [PATCH] test(sed): add package e2e coverage --- docs-internal/registry-parity-worklist.md | 9 ++ software/sed/test/sed.test.ts | 142 ++++++++++++++++++++++ 2 files changed, 151 insertions(+) create mode 100644 software/sed/test/sed.test.ts diff --git a/docs-internal/registry-parity-worklist.md b/docs-internal/registry-parity-worklist.md index b1adf168d1..1f417116f9 100644 --- a/docs-internal/registry-parity-worklist.md +++ b/docs-internal/registry-parity-worklist.md @@ -577,6 +577,15 @@ real e2e tests that prove Linux-parity behavior — not smoke tests. Biome is not applicable for this package test path; it reported the file is ignored by config in `2026-07-08T13-16-16-0700-item12-gawk-biome-check.log`. Rev: `pzxkurol`. + - **sed — DONE.** Added package-local VM e2e coverage for the staged `sed` + command. The suite proves file-operand substitutions, addressed `-n` + printing, addressed deletion, multiple `-e` expressions, and missing + input-file errors through the packaged WASM command. Proof: + `2026-07-08T13-17-46-0700-item12-sed-package-e2e-initial.log`; + `2026-07-08T13-17-46-0700-item12-sed-check-types-initial.log`. + Biome is not applicable for this package test path; it reported the file is + ignored by config in + `2026-07-08T13-17-55-0700-item12-sed-biome-check.log`. Rev: `wvpklkqv`. - **jq — DONE.** Added package-local VM e2e coverage for the staged `jq` command and fixed the jaq-backed CLI wrapper to accept Linux-style file operands instead of only stdin. The suite now proves version output, diff --git a/software/sed/test/sed.test.ts b/software/sed/test/sed.test.ts new file mode 100644 index 0000000000..c6ccf420a2 --- /dev/null +++ b/software/sed/test/sed.test.ts @@ -0,0 +1,142 @@ +import { existsSync } from "node:fs"; +import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { + NodeFileSystem, + createKernel, + createWasmVmRuntime, + describeIf, +} from "@agentos/test-harness"; +import type { Kernel } from "@agentos/test-harness"; +import { afterEach, expect, it } from "vitest"; + +const SED_COMMAND_DIR = fileURLToPath(new URL("../bin", import.meta.url)); +const hasSedPackageBinary = existsSync(join(SED_COMMAND_DIR, "sed")); + +let tempRoot: string | undefined; + +async function writeFixture(path: string, contents: string): Promise { + if (!tempRoot) throw new Error("fixture root not initialized"); + const hostPath = join(tempRoot, path.replace(/^\/+/, "")); + await mkdir(dirname(hostPath), { recursive: true }); + await writeFile(hostPath, contents); +} + +async function createTestVFS(): Promise { + tempRoot = await mkdtemp(join(tmpdir(), "agentos-sed-")); + await writeFixture( + "/project/log.txt", + [ + "ERROR: disk full", + "INFO: retrying", + "ERROR: timeout", + "DEBUG: ignored", + ].join("\n") + "\n", + ); + await writeFixture( + "/project/records.txt", + ["alpha:build:4", "beta:test:6", "gamma:deploy:2"].join("\n") + "\n", + ); + return new NodeFileSystem({ root: tempRoot }); +} + +function lines(stdout: string): string[] { + return stdout.split("\n").filter((line) => line.length > 0); +} + +describeIf(hasSedPackageBinary, "sed command", { timeout: 10_000 }, () => { + let kernel: Kernel | undefined; + + afterEach(async () => { + await kernel?.dispose(); + kernel = undefined; + if (tempRoot) { + await rm(tempRoot, { recursive: true, force: true }); + tempRoot = undefined; + } + }); + + async function mountFixture(): Promise { + const vfs = await createTestVFS(); + kernel = createKernel({ filesystem: vfs }); + await kernel.mount(createWasmVmRuntime({ commandDirs: [SED_COMMAND_DIR] })); + } + + async function runSed(args: string[]) { + if (!kernel) throw new Error("kernel not mounted"); + let stdout = ""; + let stderr = ""; + const proc = kernel.spawn("sed", args, { + onStdout: (chunk) => { + stdout += Buffer.from(chunk).toString("utf8"); + }, + onStderr: (chunk) => { + stderr += Buffer.from(chunk).toString("utf8"); + }, + }); + const exitCode = await proc.wait(); + await new Promise((resolve) => setTimeout(resolve, 0)); + return { stdout, stderr, exitCode }; + } + + it("substitutes text in file operands", async () => { + await mountFixture(); + + const result = await runSed(["s/ERROR/WARN/", "/project/log.txt"]); + expect(result.exitCode, result.stderr || result.stdout).toBe(0); + expect(lines(result.stdout)).toEqual([ + "WARN: disk full", + "INFO: retrying", + "WARN: timeout", + "DEBUG: ignored", + ]); + }); + + it("prints addressed matches with -n", async () => { + await mountFixture(); + + const result = await runSed(["-n", "/ERROR/p", "/project/log.txt"]); + expect(result.exitCode, result.stderr || result.stdout).toBe(0); + expect(lines(result.stdout)).toEqual(["ERROR: disk full", "ERROR: timeout"]); + }); + + it("deletes addressed records", async () => { + await mountFixture(); + + const result = await runSed(["/DEBUG/d", "/project/log.txt"]); + expect(result.exitCode, result.stderr || result.stdout).toBe(0); + expect(lines(result.stdout)).toEqual([ + "ERROR: disk full", + "INFO: retrying", + "ERROR: timeout", + ]); + }); + + it("applies multiple expressions in order", async () => { + await mountFixture(); + + const result = await runSed([ + "-e", + "s/:/ /g", + "-e", + "s/^/row /", + "/project/records.txt", + ]); + expect(result.exitCode, result.stderr || result.stdout).toBe(0); + expect(lines(result.stdout)).toEqual([ + "row alpha build 4", + "row beta test 6", + "row gamma deploy 2", + ]); + }); + + it("fails when an input file is missing", async () => { + await mountFixture(); + + const result = await runSed(["s/a/b/", "/project/missing.txt"]); + expect(result.exitCode).not.toBe(0); + expect(result.stderr).toContain("/project/missing.txt"); + }); +});