Files
zeekayandHanzo Dev 51a68b9311 Rebrand shuru as Hanzo VM
Crates keep their history: vm (lib, was shuru-vm), hanzo-vm (CLI package
and binary, was shuru-cli), vm-guest, vm-proto, vm-proxy, vm-sdk,
vm-store, vm-darwin, vm-linux; all at 0.1.0, Apache-2.0, hanzoai/vm.
Config is vm.json, data lives in ~/.hanzo/vm (HANZO_VM_HOME), env vars
are HANZO_VM_*, the guest runs as /usr/bin/vm-guest, and assets download
from hanzoai/vm releases. The TypeScript SDK is @hanzo/vm. NOTICE
credits shuru as the origin. prepare-rootfs.sh builds the image natively
on arm64 Linux with mke2fs -d and re-execs in Docker only on macOS.
Includes rustfmt over the tree and fixes for clippy 1.98 lints.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-01 11:49:10 -07:00

155 lines
5.2 KiB
TypeScript

/**
* Integration tests for filesystem operations against the real hanzo-vm VM.
*
* Prerequisites:
* 1. cargo build -p vm-guest --target aarch64-unknown-linux-musl --release
* 2. ./scripts/prepare-rootfs.sh
* 3. cargo build -p hanzo-vm
* 4. codesign --entitlements vm.entitlements --force -s - target/debug/hanzo-vm
*
* Run: bun test test/integration/filesystem.test.ts
*/
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
import { existsSync } from "node:fs";
import { resolve } from "node:path";
import { Sandbox } from "../../src/sandbox";
const REPO_ROOT = resolve(import.meta.dir, "../../../..");
const HANZO_VM_BIN = resolve(REPO_ROOT, "target/debug/hanzo-vm");
const canRun = existsSync(HANZO_VM_BIN);
describe.if(canRun)("filesystem operations", () => {
let sb: Sandbox;
beforeAll(async () => {
sb = await Sandbox.start({ bin: HANZO_VM_BIN });
}, 60_000);
afterAll(async () => {
await sb?.stop();
}, 15_000);
test("mkdir creates directory", async () => {
await sb.mkdir("/tmp/fs-test-dir");
const s = await sb.stat("/tmp/fs-test-dir");
expect(s.isDir).toBe(true);
}, 30_000);
test("mkdir recursive creates nested directories", async () => {
await sb.mkdir("/tmp/fs-test-nested/a/b/c");
const s = await sb.stat("/tmp/fs-test-nested/a/b/c");
expect(s.isDir).toBe(true);
}, 30_000);
test("writeFile + readDir lists files", async () => {
await sb.mkdir("/tmp/fs-test-readdir");
await sb.writeFile("/tmp/fs-test-readdir/hello.txt", "hello");
await sb.writeFile("/tmp/fs-test-readdir/world.txt", "world");
await sb.mkdir("/tmp/fs-test-readdir/subdir");
const entries = await sb.readDir("/tmp/fs-test-readdir");
const names = entries.map((e) => e.name).sort();
expect(names).toEqual(["hello.txt", "subdir", "world.txt"]);
const dir = entries.find((e) => e.name === "subdir");
expect(dir?.type).toBe("dir");
const file = entries.find((e) => e.name === "hello.txt");
expect(file?.type).toBe("file");
expect(file?.size).toBe(5);
}, 30_000);
test("stat returns file metadata", async () => {
await sb.writeFile("/tmp/fs-test-stat.txt", "test content");
const s = await sb.stat("/tmp/fs-test-stat.txt");
expect(s.size).toBe(12);
expect(s.isFile).toBe(true);
expect(s.isDir).toBe(false);
expect(s.isSymlink).toBe(false);
expect(s.mtime).toBeGreaterThan(0);
expect(s.mode).toBeGreaterThan(0);
}, 30_000);
test("stat throws for nonexistent path", async () => {
await expect(
sb.stat("/tmp/fs-test-does-not-exist"),
).rejects.toThrow();
}, 30_000);
test("copy duplicates a file", async () => {
await sb.writeFile("/tmp/fs-test-copy-src.txt", "copy me");
await sb.copy("/tmp/fs-test-copy-src.txt", "/tmp/fs-test-copy-dst.txt");
const data = await sb.readFile("/tmp/fs-test-copy-dst.txt");
expect(new TextDecoder().decode(data)).toBe("copy me");
}, 30_000);
test("copy recursive duplicates a directory", async () => {
await sb.mkdir("/tmp/fs-test-copydir/sub");
await sb.writeFile("/tmp/fs-test-copydir/a.txt", "aaa");
await sb.writeFile("/tmp/fs-test-copydir/sub/b.txt", "bbb");
await sb.copy("/tmp/fs-test-copydir", "/tmp/fs-test-copydir2", {
recursive: true,
});
const a = await sb.readFile("/tmp/fs-test-copydir2/a.txt");
expect(new TextDecoder().decode(a)).toBe("aaa");
const b = await sb.readFile("/tmp/fs-test-copydir2/sub/b.txt");
expect(new TextDecoder().decode(b)).toBe("bbb");
}, 30_000);
test("rename moves a file", async () => {
await sb.writeFile("/tmp/fs-test-rename-old.txt", "move me");
await sb.rename(
"/tmp/fs-test-rename-old.txt",
"/tmp/fs-test-rename-new.txt",
);
const data = await sb.readFile("/tmp/fs-test-rename-new.txt");
expect(new TextDecoder().decode(data)).toBe("move me");
// Original should no longer exist
await expect(sb.stat("/tmp/fs-test-rename-old.txt")).rejects.toThrow();
}, 30_000);
test("chmod changes file permissions", async () => {
await sb.writeFile("/tmp/fs-test-chmod.txt", "chmod me");
await sb.chmod("/tmp/fs-test-chmod.txt", 0o755);
const s = await sb.stat("/tmp/fs-test-chmod.txt");
// Check the permission bits (lower 12 bits of mode)
expect(s.mode & 0o777).toBe(0o755);
}, 30_000);
test("remove deletes a file", async () => {
await sb.writeFile("/tmp/fs-test-rm.txt", "delete me");
await sb.remove("/tmp/fs-test-rm.txt");
await expect(sb.stat("/tmp/fs-test-rm.txt")).rejects.toThrow();
}, 30_000);
test("remove recursive deletes a directory tree", async () => {
await sb.mkdir("/tmp/fs-test-rmdir/nested");
await sb.writeFile("/tmp/fs-test-rmdir/nested/file.txt", "data");
await sb.remove("/tmp/fs-test-rmdir", { recursive: true });
await expect(sb.stat("/tmp/fs-test-rmdir")).rejects.toThrow();
}, 30_000);
test("exists returns true for existing path", async () => {
await sb.writeFile("/tmp/fs-test-exists.txt", "hi");
expect(await sb.exists("/tmp/fs-test-exists.txt")).toBe(true);
}, 30_000);
test("exists returns false for missing path", async () => {
expect(await sb.exists("/tmp/fs-test-nope-nope")).toBe(false);
}, 30_000);
});
describe.if(!canRun)("filesystem operations (skipped)", () => {
test("hanzo-vm binary not found", () => {
console.log(
"Build with: cargo build -p hanzo-vm && codesign --entitlements vm.entitlements --force -s - target/debug/hanzo-vm",
);
});
});