From 294ba13c39bdbad603572105382f6da1101f8814 Mon Sep 17 00:00:00 2001 From: Nathan Flurry Date: Wed, 8 Jul 2026 13:22:36 -0700 Subject: [PATCH] fix(tar): tighten listing parity and add e2e --- docs-internal/registry-parity-worklist.md | 14 ++ software/tar/bin/tar | Bin 190938 -> 191725 bytes software/tar/native/crates/tar/src/lib.rs | 9 +- software/tar/test/tar.test.ts | 165 ++++++++++++++++++++++ 4 files changed, 186 insertions(+), 2 deletions(-) create mode 100644 software/tar/test/tar.test.ts diff --git a/docs-internal/registry-parity-worklist.md b/docs-internal/registry-parity-worklist.md index 1f417116f9..db09e8f2d6 100644 --- a/docs-internal/registry-parity-worklist.md +++ b/docs-internal/registry-parity-worklist.md @@ -586,6 +586,20 @@ 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-17-55-0700-item12-sed-biome-check.log`. Rev: `wvpklkqv`. + - **tar — DONE.** Added package-local VM e2e coverage for the staged `tar` + command and tightened the tar wrapper for Linux-like directory listing and + missing-input error context. The suite proves archive creation/listing, + extraction into `-C` directories, gzip auto-detection by extension, + `--strip-components`, and missing create-input errors through the packaged + WASM command. Proof: + `2026-07-08T13-21-48-0700-item12-tar-toolchain-cmd-build-clean-vendor.log`; + `2026-07-08T13-22-51-0700-item12-tar-package-build-after-wrapper-fix.log`; + `2026-07-08T13-23-02-0700-item12-tar-package-e2e-final.log`; + `2026-07-08T13-23-02-0700-item12-tar-check-types-final.log`; + `2026-07-08T13-23-02-0700-item12-tar-cargo-fmt-check-final.log`. + Biome is not applicable for this package test path; it reported the file is + ignored by config in + `2026-07-08T13-23-12-0700-item12-tar-biome-check.log`. Rev: `rszmulmk`. - **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/tar/bin/tar b/software/tar/bin/tar index 5d813b99d97d8785ad8e5b6bd8a93cb387fc4223..770b86e30694a2f5b1e2a8688aebbe0c3ff33f94 100644 GIT binary patch delta 33875 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 } increment_entry_count(entry_count)?; - let meta = fs::symlink_metadata(&disk_path)?; + let meta = fs::symlink_metadata(&disk_path).map_err(|e| { + io::Error::new(e.kind(), format!("metadata {}: {}", disk_path.display(), e)) + })?; if meta.is_dir() { append_dir( @@ -548,11 +550,12 @@ fn do_list(archive_file: Option<&str>, gzip: bool, verbose: bool) -> io::Result< let entry = entry_result?; let path = entry.path()?; + let entry_type = entry.header().entry_type(); if verbose { let h = entry.header(); let size = h.size().unwrap_or(0); let mode = h.mode().unwrap_or(0o644); - let type_ch = match h.entry_type() { + let type_ch = match entry_type { tar::EntryType::Directory => 'd', tar::EntryType::Symlink => 'l', _ => '-', @@ -565,6 +568,8 @@ fn do_list(archive_file: Option<&str>, gzip: bool, verbose: bool) -> io::Result< size, path.display() )?; + } else if entry_type == tar::EntryType::Directory { + writeln!(out, "{}/", path.display())?; } else { writeln!(out, "{}", path.display())?; } diff --git a/software/tar/test/tar.test.ts b/software/tar/test/tar.test.ts new file mode 100644 index 0000000000..adebae14c4 --- /dev/null +++ b/software/tar/test/tar.test.ts @@ -0,0 +1,165 @@ +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 TAR_COMMAND_DIR = fileURLToPath(new URL("../bin", import.meta.url)); +const hasTarPackageBinary = existsSync(join(TAR_COMMAND_DIR, "tar")); + +let tempRoot: string | undefined; + +function hostPath(path: string): string { + if (!tempRoot) throw new Error("fixture root not initialized"); + return join(tempRoot, path.replace(/^\/+/, "")); +} + +async function writeFixture(path: string, contents: string): Promise { + const target = hostPath(path); + await mkdir(dirname(target), { recursive: true }); + await writeFile(target, contents); +} + +async function createTestVFS(): Promise { + tempRoot = await mkdtemp(join(tmpdir(), "agentos-tar-")); + await writeFixture("/project/src/docs/readme.txt", "hello from docs\n"); + await writeFixture("/project/src/data/values.csv", "name,score\nAda,7\nLinus,3\n"); + await mkdir(hostPath("/project/out"), { recursive: true }); + await mkdir(hostPath("/project/strip"), { recursive: true }); + await mkdir(hostPath("/project/gzip-out"), { recursive: true }); + return new NodeFileSystem({ root: tempRoot }); +} + +function lines(stdout: string): string[] { + return stdout.split("\n").filter((line) => line.length > 0); +} + +const textDecoder = new TextDecoder(); + +describeIf(hasTarPackageBinary, "tar 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: [TAR_COMMAND_DIR] })); + } + + async function runTar(args: string[]) { + if (!kernel) throw new Error("kernel not mounted"); + let stdout = ""; + let stderr = ""; + const proc = kernel.spawn("tar", 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 }; + } + + async function createArchive(path = "/project/archive.tar") { + const result = await runTar(["-cf", path, "-C", "/project", "src"]); + expect(result.exitCode, result.stderr || result.stdout).toBe(0); + } + + async function readGuestText(path: string): Promise { + if (!kernel) throw new Error("kernel not mounted"); + return textDecoder.decode(await kernel.readFile(path)); + } + + it("creates and lists file-backed archives", async () => { + await mountFixture(); + await createArchive(); + + const result = await runTar(["-tf", "/project/archive.tar"]); + expect(result.exitCode, result.stderr || result.stdout).toBe(0); + expect(lines(result.stdout)).toEqual( + expect.arrayContaining([ + "src/", + "src/docs/", + "src/docs/readme.txt", + "src/data/", + "src/data/values.csv", + ]), + ); + }); + + it("extracts archives into target directories", async () => { + await mountFixture(); + await createArchive(); + + const result = await runTar(["-xf", "/project/archive.tar", "-C", "/project/out"]); + expect(result.exitCode, result.stderr || result.stdout).toBe(0); + await expect(readGuestText("/project/out/src/docs/readme.txt")).resolves.toBe( + "hello from docs\n", + ); + await expect(readGuestText("/project/out/src/data/values.csv")).resolves.toBe( + "name,score\nAda,7\nLinus,3\n", + ); + }); + + it("auto-detects gzip archives by extension", async () => { + await mountFixture(); + + const create = await runTar(["-czf", "/project/archive.tgz", "-C", "/project", "src"]); + expect(create.exitCode, create.stderr || create.stdout).toBe(0); + + const list = await runTar(["-tf", "/project/archive.tgz"]); + expect(list.exitCode, list.stderr || list.stdout).toBe(0); + expect(lines(list.stdout)).toContain("src/docs/readme.txt"); + + const extract = await runTar(["-xf", "/project/archive.tgz", "-C", "/project/gzip-out"]); + expect(extract.exitCode, extract.stderr || extract.stdout).toBe(0); + await expect(readGuestText("/project/gzip-out/src/docs/readme.txt")).resolves.toBe( + "hello from docs\n", + ); + }); + + it("strips path components on extraction", async () => { + await mountFixture(); + await createArchive(); + + const result = await runTar([ + "-xf", + "/project/archive.tar", + "-C", + "/project/strip", + "--strip-components=1", + ]); + expect(result.exitCode, result.stderr || result.stdout).toBe(0); + await expect(readGuestText("/project/strip/docs/readme.txt")).resolves.toBe( + "hello from docs\n", + ); + await expect(readGuestText("/project/strip/src/docs/readme.txt")).rejects.toThrow(); + }); + + it("fails when a create input is missing", async () => { + await mountFixture(); + + const result = await runTar(["-cf", "/project/missing.tar", "-C", "/project", "nope"]); + expect(result.exitCode).not.toBe(0); + expect(result.stderr).toContain("nope"); + }); +});