Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions docs-internal/registry-parity-worklist.md
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Binary file modified software/tar/bin/tar
Binary file not shown.
9 changes: 7 additions & 2 deletions software/tar/native/crates/tar/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,9 @@ fn append_path<W: Write>(
}
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(
Expand Down Expand Up @@ -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',
_ => '-',
Expand All @@ -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())?;
}
Expand Down
165 changes: 165 additions & 0 deletions software/tar/test/tar.test.ts
Original file line number Diff line number Diff line change
@@ -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<void> {
const target = hostPath(path);
await mkdir(dirname(target), { recursive: true });
await writeFile(target, contents);
}

async function createTestVFS(): Promise<NodeFileSystem> {
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<void> {
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<void>((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<string> {
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");
});
});