arcrun — AI workflow execution engine (clean history)
Self-hosted 開源:WASM 零件 + recipe + cypher-executor,跑在你自己的 Cloudflare。 此為重建的乾淨歷史起點(移除曾誤 commit 的 GCP SA 金鑰,舊歷史保留在 richblack/arcrun 與本地 backup 分支)。含: - acr init --self-hosted installer(建 KV/R2 + codeload 拉預編譯 wasm + wrangler deploy + seed recipe) - recipe push 把關(資料外流提醒 + 打通檢查) - 19 個正當零件預編譯 wasm(claude_api/km_writer/kbdb_upsert_block 排除:違反 DECISIONS §1) - CLI / cypher-executor / registry / 完整 SDD Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,215 @@
|
||||
/**
|
||||
* WASI shim 單元測試
|
||||
* Task 2.2 — Requirements: 3.1, 3.3
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { createWasiShim } from '../src/lib/wasi-shim';
|
||||
|
||||
// 建立一個最小的 fake WebAssembly.Memory(用 ArrayBuffer 模擬)
|
||||
function makeFakeMemory(size = 65536): WebAssembly.Memory {
|
||||
// 用真實的 WebAssembly.Memory(Vitest 環境支援)
|
||||
return new WebAssembly.Memory({ initial: 1 });
|
||||
}
|
||||
|
||||
/** 在 memory 中寫入 iovec 陣列,回傳 iovs 指標 */
|
||||
function writeIovecs(
|
||||
view: DataView,
|
||||
iovecs: Array<{ buf: number; buf_len: number }>,
|
||||
startPtr: number,
|
||||
): number {
|
||||
for (let i = 0; i < iovecs.length; i++) {
|
||||
view.setUint32(startPtr + i * 8, iovecs[i].buf, true);
|
||||
view.setUint32(startPtr + i * 8 + 4, iovecs[i].buf_len, true);
|
||||
}
|
||||
return startPtr;
|
||||
}
|
||||
|
||||
describe('createWasiShim', () => {
|
||||
describe('fd_read(stdin)', () => {
|
||||
it('一次讀取完整 stdin', () => {
|
||||
const input = '{"key":"value"}';
|
||||
const shim = createWasiShim(input);
|
||||
const mem = makeFakeMemory();
|
||||
shim.setMemory(mem);
|
||||
|
||||
const view = new DataView(mem.buffer);
|
||||
const inputBytes = new TextEncoder().encode(input);
|
||||
|
||||
// 配置 buffer 區域(offset 100)和 iovec(offset 0)
|
||||
const bufPtr = 100;
|
||||
const iovsPtr = 0;
|
||||
const nreadPtr = 50;
|
||||
|
||||
writeIovecs(view, [{ buf: bufPtr, buf_len: inputBytes.length }], iovsPtr);
|
||||
|
||||
const fd_read = (shim.imports.wasi_snapshot_preview1 as Record<string, Function>).fd_read;
|
||||
const result = fd_read(0, iovsPtr, 1, nreadPtr);
|
||||
|
||||
expect(result).toBe(0); // ESUCCESS
|
||||
const nread = view.getUint32(nreadPtr, true);
|
||||
expect(nread).toBe(inputBytes.length);
|
||||
|
||||
// 驗證讀取的內容
|
||||
const readBytes = new Uint8Array(mem.buffer, bufPtr, nread);
|
||||
expect(new TextDecoder().decode(readBytes)).toBe(input);
|
||||
});
|
||||
|
||||
it('分多次讀取 stdin', () => {
|
||||
const input = 'hello';
|
||||
const shim = createWasiShim(input);
|
||||
const mem = makeFakeMemory();
|
||||
shim.setMemory(mem);
|
||||
|
||||
const view = new DataView(mem.buffer);
|
||||
const fd_read = (shim.imports.wasi_snapshot_preview1 as Record<string, Function>).fd_read;
|
||||
|
||||
// 第一次讀 3 bytes
|
||||
writeIovecs(view, [{ buf: 200, buf_len: 3 }], 0);
|
||||
fd_read(0, 0, 1, 50);
|
||||
expect(view.getUint32(50, true)).toBe(3);
|
||||
expect(new TextDecoder().decode(new Uint8Array(mem.buffer, 200, 3))).toBe('hel');
|
||||
|
||||
// 第二次讀剩餘 2 bytes
|
||||
writeIovecs(view, [{ buf: 300, buf_len: 10 }], 0);
|
||||
fd_read(0, 0, 1, 50);
|
||||
expect(view.getUint32(50, true)).toBe(2);
|
||||
expect(new TextDecoder().decode(new Uint8Array(mem.buffer, 300, 2))).toBe('lo');
|
||||
|
||||
// 第三次讀:stdin 已耗盡,nread = 0
|
||||
writeIovecs(view, [{ buf: 400, buf_len: 10 }], 0);
|
||||
fd_read(0, 0, 1, 50);
|
||||
expect(view.getUint32(50, true)).toBe(0);
|
||||
});
|
||||
|
||||
it('非 stdin fd 回傳 ENOSYS', () => {
|
||||
const shim = createWasiShim('');
|
||||
const mem = makeFakeMemory();
|
||||
shim.setMemory(mem);
|
||||
const view = new DataView(mem.buffer);
|
||||
writeIovecs(view, [{ buf: 100, buf_len: 10 }], 0);
|
||||
|
||||
const fd_read = (shim.imports.wasi_snapshot_preview1 as Record<string, Function>).fd_read;
|
||||
expect(fd_read(1, 0, 1, 50)).toBe(76); // ENOSYS
|
||||
expect(fd_read(2, 0, 1, 50)).toBe(76);
|
||||
});
|
||||
});
|
||||
|
||||
describe('fd_write(stdout/stderr)', () => {
|
||||
it('寫入 stdout(fd=1)並可透過 getStdout 讀取', () => {
|
||||
const shim = createWasiShim('');
|
||||
const mem = makeFakeMemory();
|
||||
shim.setMemory(mem);
|
||||
|
||||
const view = new DataView(mem.buffer);
|
||||
const data = new TextEncoder().encode('{"valid":true}');
|
||||
const bufPtr = 100;
|
||||
new Uint8Array(mem.buffer).set(data, bufPtr);
|
||||
writeIovecs(view, [{ buf: bufPtr, buf_len: data.length }], 0);
|
||||
|
||||
const fd_write = (shim.imports.wasi_snapshot_preview1 as Record<string, Function>).fd_write;
|
||||
const result = fd_write(1, 0, 1, 50);
|
||||
|
||||
expect(result).toBe(0);
|
||||
expect(view.getUint32(50, true)).toBe(data.length);
|
||||
expect(shim.getStdout()).toBe('{"valid":true}');
|
||||
});
|
||||
|
||||
it('寫入 stderr(fd=2)並可透過 getStderr 讀取', () => {
|
||||
const shim = createWasiShim('');
|
||||
const mem = makeFakeMemory();
|
||||
shim.setMemory(mem);
|
||||
|
||||
const view = new DataView(mem.buffer);
|
||||
const data = new TextEncoder().encode('error message');
|
||||
const bufPtr = 100;
|
||||
new Uint8Array(mem.buffer).set(data, bufPtr);
|
||||
writeIovecs(view, [{ buf: bufPtr, buf_len: data.length }], 0);
|
||||
|
||||
const fd_write = (shim.imports.wasi_snapshot_preview1 as Record<string, Function>).fd_write;
|
||||
fd_write(2, 0, 1, 50);
|
||||
|
||||
expect(shim.getStderr()).toBe('error message');
|
||||
expect(shim.getStdout()).toBe(''); // stdout 不受影響
|
||||
});
|
||||
|
||||
it('多次寫入 stdout 會合併', () => {
|
||||
const shim = createWasiShim('');
|
||||
const mem = makeFakeMemory();
|
||||
shim.setMemory(mem);
|
||||
|
||||
const view = new DataView(mem.buffer);
|
||||
const fd_write = (shim.imports.wasi_snapshot_preview1 as Record<string, Function>).fd_write;
|
||||
|
||||
const write = (text: string, bufPtr: number) => {
|
||||
const data = new TextEncoder().encode(text);
|
||||
new Uint8Array(mem.buffer).set(data, bufPtr);
|
||||
writeIovecs(view, [{ buf: bufPtr, buf_len: data.length }], 0);
|
||||
fd_write(1, 0, 1, 50);
|
||||
};
|
||||
|
||||
write('{"valid":', 100);
|
||||
write('true}', 200);
|
||||
|
||||
expect(shim.getStdout()).toBe('{"valid":true}');
|
||||
});
|
||||
|
||||
it('非 stdout/stderr fd 回傳 ENOSYS', () => {
|
||||
const shim = createWasiShim('');
|
||||
const mem = makeFakeMemory();
|
||||
shim.setMemory(mem);
|
||||
const view = new DataView(mem.buffer);
|
||||
writeIovecs(view, [{ buf: 100, buf_len: 5 }], 0);
|
||||
|
||||
const fd_write = (shim.imports.wasi_snapshot_preview1 as Record<string, Function>).fd_write;
|
||||
expect(fd_write(0, 0, 1, 50)).toBe(76); // stdin 不能寫
|
||||
expect(fd_write(3, 0, 1, 50)).toBe(76); // 其他 fd
|
||||
});
|
||||
});
|
||||
|
||||
describe('proc_exit', () => {
|
||||
it('proc_exit(0) 拋出 Error(正常結束)', () => {
|
||||
const shim = createWasiShim('');
|
||||
const proc_exit = (shim.imports.wasi_snapshot_preview1 as Record<string, Function>).proc_exit;
|
||||
expect(() => proc_exit(0)).toThrow('wasm exit: 0');
|
||||
});
|
||||
|
||||
it('proc_exit(1) 拋出 Error(錯誤結束)', () => {
|
||||
const shim = createWasiShim('');
|
||||
const proc_exit = (shim.imports.wasi_snapshot_preview1 as Record<string, Function>).proc_exit;
|
||||
expect(() => proc_exit(1)).toThrow('wasm exit: 1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('其餘 syscall 回傳 ENOSYS', () => {
|
||||
it('fd_seek 回傳 ENOSYS', () => {
|
||||
const shim = createWasiShim('');
|
||||
const fd_seek = (shim.imports.wasi_snapshot_preview1 as Record<string, Function>).fd_seek;
|
||||
expect(fd_seek(1, 0, 0, 0)).toBe(76);
|
||||
});
|
||||
|
||||
it('sock_connect 相關 syscall 回傳 ENOSYS', () => {
|
||||
const shim = createWasiShim('');
|
||||
const imports = shim.imports.wasi_snapshot_preview1 as Record<string, Function>;
|
||||
expect(imports.sock_recv()).toBe(76);
|
||||
expect(imports.sock_send()).toBe(76);
|
||||
expect(imports.sock_shutdown()).toBe(76);
|
||||
});
|
||||
|
||||
it('path_open 回傳 ENOSYS', () => {
|
||||
const shim = createWasiShim('');
|
||||
const imports = shim.imports.wasi_snapshot_preview1 as Record<string, Function>;
|
||||
expect(imports.path_open()).toBe(76);
|
||||
expect(imports.path_create_directory()).toBe(76);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setMemory 未呼叫時', () => {
|
||||
it('fd_write 在 memory 未設定時拋出錯誤', () => {
|
||||
const shim = createWasiShim('');
|
||||
// 不呼叫 setMemory
|
||||
const fd_write = (shim.imports.wasi_snapshot_preview1 as Record<string, Function>).fd_write;
|
||||
expect(() => fd_write(1, 0, 1, 50)).toThrow('WASI memory not set');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user