e7738de6d2
CI / Deep Native Runtime Cases (1/6) (push) Has been skipped
CI / Native Preflight (push) Failing after 1s
CI / Native Runtime Cases (1/2) (push) Failing after 0s
CI / Native Runtime Cases (2/2) (push) Failing after 1s
CI / Native Metadata Reports (push) Failing after 0s
CI / Native Direct Backend Artifacts (push) Failing after 0s
CI / Native Sanitizer Smoke (push) Failing after 1s
CI / Command Contract Snapshots (push) Failing after 1s
CI / Deep Conformance Suite (push) Has been skipped
CI / Graph Build Perf (push) Failing after 1s
CI / Deep Native Preflight (push) Has been skipped
CI / Deep Native Runtime Cases (2/6) (push) Has been skipped
CI / Deep Native Runtime Cases (3/6) (push) Has been skipped
CI / Conformance Suite (push) Failing after 1s
CI / Workspace Checks (push) Failing after 0s
CI / Deep Native Runtime Cases (5/6) (push) Has been skipped
CI / Deep Native Runtime Cases (6/6) (push) Has been skipped
CI / Deep Native Runtime Cases (4/6) (push) Has been skipped
CI / Deep Graph Build Perf (push) Has been skipped
283 lines
12 KiB
TypeScript
283 lines
12 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { execFile } from "node:child_process";
|
|
import { access, mkdtemp, mkdir, readdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
import { tmpdir } from "node:os";
|
|
import { join, relative, sep } from "node:path";
|
|
import { describe, it } from "node:test";
|
|
import { promisify } from "node:util";
|
|
|
|
const execFileAsync = promisify(execFile);
|
|
const root = process.cwd();
|
|
const zero = join(root, "bin", "zero");
|
|
const nativeZero = join(root, ".zero", "bin", "zero");
|
|
const supportedTargets = [
|
|
"darwin-arm64",
|
|
"darwin-x64",
|
|
"linux-musl-x64",
|
|
"linux-musl-arm64",
|
|
"linux-x64",
|
|
"linux-arm64",
|
|
"win32-x64.exe",
|
|
"win32-arm64.exe",
|
|
];
|
|
const artifactSummaryPattern = /\((?:\d+ B|\d+\.\d KiB|\d+\.\d MiB|\d+\.\d GiB), (?:\d+ ms|\d+\.\d s)\)/;
|
|
const runnableDirectTarget =
|
|
process.platform === "darwin" && process.arch === "arm64" ? "darwin-arm64" :
|
|
process.platform === "linux" && process.arch === "x64" ? "linux-musl-x64" :
|
|
null;
|
|
|
|
function runnableBuildArgs(input: string, out: string) {
|
|
if (runnableDirectTarget) return ["build", "--emit", "exe", "--target", runnableDirectTarget, input, "--out", out];
|
|
throw new Error("no runnable direct target for this host");
|
|
}
|
|
|
|
function runZero(args: string[], options: { cwd?: string; env?: NodeJS.ProcessEnv } = {}) {
|
|
return execFileAsync(zero, args, { cwd: options.cwd ?? root, env: options.env ?? process.env });
|
|
}
|
|
|
|
function runNativeZero(args: string[], options: { cwd?: string; env?: NodeJS.ProcessEnv } = {}) {
|
|
return execFileAsync(nativeZero, args, { cwd: options.cwd ?? root, env: options.env ?? process.env });
|
|
}
|
|
|
|
async function collectSkillMdFiles(dir: string): Promise<string[]> {
|
|
const ignoredDirs = new Set([".git", ".next", ".zero", "node_modules"]);
|
|
let entries;
|
|
try {
|
|
entries = await readdir(dir, { withFileTypes: true });
|
|
} catch {
|
|
return [];
|
|
}
|
|
|
|
const files: string[] = [];
|
|
for (const entry of entries) {
|
|
if (entry.isDirectory()) {
|
|
if (ignoredDirs.has(entry.name)) continue;
|
|
files.push(...await collectSkillMdFiles(join(dir, entry.name)));
|
|
} else if (entry.isFile() && entry.name === "SKILL.md") {
|
|
files.push(relative(root, join(dir, entry.name)).split(sep).join("/"));
|
|
}
|
|
}
|
|
return files.sort();
|
|
}
|
|
|
|
describe("native zero CLI", () => {
|
|
it("prints a terse plain version with the build hash", async () => {
|
|
const versionPattern = /^zero 0\.3\.4 \(build (?:[0-9a-f]{7,40}|unknown)\)\n$/;
|
|
assert.match((await runZero(["--version"])).stdout, versionPattern);
|
|
assert.match((await runNativeZero(["--version"])).stdout, versionPattern);
|
|
});
|
|
|
|
it("checks directly and rejects removed legacy build flags", async () => {
|
|
const check = await runZero(["check", "examples/hello.graph"]);
|
|
assert.match(check.stdout, /ok/);
|
|
|
|
const removedEmit = await runZero(["build", "--json", "--emit", "c", "examples/hello.graph"]).catch((error) => error);
|
|
assert.notEqual(removedEmit.code, 0);
|
|
assert.equal(JSON.parse(removedEmit.stdout).diagnostics[0].code, "BLD003");
|
|
|
|
const removedFlag = await runZero(["build", "--json", "--legacy-backend", "examples/hello.graph"]).catch((error) => error);
|
|
assert.notEqual(removedFlag.code, 0);
|
|
assert.equal(JSON.parse(removedFlag.stdout).diagnostics[0].code, "BLD003");
|
|
});
|
|
|
|
it("builds and runs an executable", async () => {
|
|
const cwd = await mkdtemp(join(tmpdir(), "zero-cli-"));
|
|
const exe = join(cwd, "add");
|
|
|
|
try {
|
|
const build = await runZero(runnableBuildArgs("examples/add.graph", exe));
|
|
assert.ok(build.stdout.includes(`${exe} (`));
|
|
assert.match(build.stdout, artifactSummaryPattern);
|
|
const run = await execFileAsync(exe, []);
|
|
assert.match(run.stdout, /math works/);
|
|
await assert.rejects(access(`${exe}.c`));
|
|
|
|
const runExe = join(cwd, "add-run");
|
|
const zeroRun = await runZero(["run", "--out", runExe, "examples/add.graph"]);
|
|
assert.match(zeroRun.stdout, /math works/);
|
|
assert.equal(zeroRun.stderr, "");
|
|
await assert.rejects(access(`${runExe}.c`));
|
|
|
|
const zeroRunWithArgs = await runZero(["run", "--out", join(cwd, "add-run-args"), "examples/add.graph", "--", "--emit", "c"]);
|
|
assert.match(zeroRunWithArgs.stdout, /math works/);
|
|
|
|
const exitCodeRun = await runZero(["run", "--out", join(cwd, "return42"), "examples/direct-exe-return.graph"]).catch((error) => error);
|
|
assert.equal(exitCodeRun.code, 42);
|
|
} finally {
|
|
await rm(cwd, { force: true, recursive: true });
|
|
}
|
|
});
|
|
|
|
it("checks and builds a manifest project", async () => {
|
|
const cwd = await mkdtemp(join(tmpdir(), "zero-project-"));
|
|
const project = join(cwd, "demo");
|
|
const src = join(project, "src");
|
|
const exe = join(cwd, "demo-exe");
|
|
|
|
try {
|
|
await mkdir(src, { recursive: true });
|
|
await writeFile(
|
|
join(project, "zero.json"),
|
|
`{
|
|
"package": { "name": "demo", "version": "0.1.0" },
|
|
"targets": { "cli": { "kind": "exe", "main": "src/main.0" } }
|
|
}
|
|
`,
|
|
);
|
|
await writeFile(
|
|
join(src, "main.0"),
|
|
`pub fn main(world: World) -> Void raises {
|
|
check world.out.write("hello from demo\\n")
|
|
}
|
|
`,
|
|
);
|
|
await runZero(["import", project]);
|
|
|
|
assert.match((await runZero(["check", project])).stdout, /ok/);
|
|
await runZero(runnableBuildArgs(project, exe));
|
|
assert.match((await execFileAsync(exe, [])).stdout, /hello from demo/);
|
|
} finally {
|
|
await rm(cwd, { force: true, recursive: true });
|
|
}
|
|
});
|
|
|
|
it("reports graph, size, objects, and targets", async () => {
|
|
const graph = JSON.parse((await runZero(["inspect", "--json", "examples/point.graph"])).stdout);
|
|
assert.equal(graph.shapes[0].name, "Point");
|
|
assert.equal(graph.functions.some((item: { name: string }) => item.name === "main"), true);
|
|
|
|
const size = JSON.parse((await runZero(["size", "--json", "examples/hello.graph"])).stdout);
|
|
assert.equal(size.schemaVersion, 1);
|
|
assert.equal(size.generatedCBytes, 0);
|
|
|
|
const objOut = join(tmpdir(), `zero-target-${Date.now()}.o`);
|
|
try {
|
|
const objBuild = await runZero(["build", "--emit", "obj", "--target", "linux-musl-arm64", "examples/direct-exe-return.graph", "--out", objOut]);
|
|
assert.ok(objBuild.stdout.includes(`${objOut} (`));
|
|
assert.match(objBuild.stdout, artifactSummaryPattern);
|
|
const bytes = await readFile(objOut);
|
|
assert.equal(bytes[0], 0x7f);
|
|
assert.equal(bytes[1], 0x45);
|
|
} finally {
|
|
await rm(objOut, { force: true });
|
|
}
|
|
|
|
const targets = JSON.parse((await runZero(["targets"])).stdout);
|
|
assert.deepEqual(
|
|
targets.targets.map((target: { name: string }) => target.name),
|
|
supportedTargets,
|
|
);
|
|
assert.equal(targets.targets.find((target: { name: string }) => target.name === "linux-musl-x64")?.compilerTarget, "x86_64-linux-musl");
|
|
assert.equal(targets.targets.find((target: { name: string }) => target.name === "win32-x64.exe")?.exeSuffix, ".exe");
|
|
});
|
|
|
|
it("lists and retrieves bundled skills", async () => {
|
|
assert.deepEqual(await collectSkillMdFiles(root), ["skills/zero/SKILL.md"]);
|
|
|
|
const list = JSON.parse((await runZero(["skills", "list", "--json"])).stdout);
|
|
assert.equal(list.success, true);
|
|
const skillNames = new Set(list.data.map((skill: { name: string }) => skill.name));
|
|
for (const name of [
|
|
"zero",
|
|
"agent",
|
|
"builds",
|
|
"diagnostics",
|
|
"language",
|
|
"packages",
|
|
"stdlib",
|
|
"testing",
|
|
]) {
|
|
assert.equal(skillNames.has(name), true);
|
|
}
|
|
|
|
const zeroSkill = JSON.parse((await runZero(["skills", "get", "zero", "--full", "--json"])).stdout);
|
|
assert.equal(zeroSkill.success, true);
|
|
assert.match(zeroSkill.data[0].content, /# Zero/);
|
|
assert.match(zeroSkill.data[0].content, /zero skills get zero --full/);
|
|
assert.equal(zeroSkill.data[0].files, undefined);
|
|
|
|
const languageSkill = JSON.parse((await runZero(["skills", "get", "language", "--json"])).stdout);
|
|
assert.equal(languageSkill.success, true);
|
|
assert.match(languageSkill.data[0].content, /# zerolang Language/);
|
|
assert.match(languageSkill.data[0].content, /pub fn main/);
|
|
|
|
const diagnosticSkill = JSON.parse((await runZero(["skills", "get", "diagnostics", "--json"])).stdout);
|
|
assert.equal(diagnosticSkill.success, true);
|
|
assert.match(diagnosticSkill.data[0].content, /fixSafety/);
|
|
|
|
const missing = await runZero(["skills", "get", "missing", "--json"]).catch((error) => error);
|
|
assert.notEqual(missing.code, 0);
|
|
assert.equal(JSON.parse(missing.stdout).success, false);
|
|
|
|
const removedPath = await runZero(["skills", "path", "zero", "--json"]).catch((error) => error);
|
|
assert.notEqual(removedPath.code, 0);
|
|
assert.match(JSON.parse(removedPath.stdout).error, /Unknown skills subcommand: path/);
|
|
|
|
const badSkillsFlag = await runZero(["skills", "-x"]).catch((error) => error);
|
|
assert.notEqual(badSkillsFlag.code, 0);
|
|
assert.match(badSkillsFlag.stderr, /Unknown skills flag: -x/);
|
|
|
|
const badListFlag = await runZero(["skills", "list", "--unknown", "--json"]).catch((error) => error);
|
|
assert.notEqual(badListFlag.code, 0);
|
|
assert.match(JSON.parse(badListFlag.stdout).error, /Unknown skills flag: --unknown/);
|
|
|
|
const badGetFlag = await runZero(["skills", "get", "language", "--unknown", "--json"]).catch((error) => error);
|
|
assert.notEqual(badGetFlag.code, 0);
|
|
assert.match(JSON.parse(badGetFlag.stdout).error, /Unknown skills flag: --unknown/);
|
|
|
|
const nativeList = JSON.parse((await runNativeZero(["skills", "list", "--json"])).stdout);
|
|
assert.equal(nativeList.success, true);
|
|
assert.equal(nativeList.data.some((skill: { name: string }) => skill.name === "language"), true);
|
|
|
|
const nativeLanguageSkill = JSON.parse((await runNativeZero(["skills", "get", "language", "--json"])).stdout);
|
|
assert.equal(nativeLanguageSkill.success, true);
|
|
assert.match(nativeLanguageSkill.data[0].content, /# zerolang Language/);
|
|
});
|
|
|
|
it("handles target-specific executable names", async () => {
|
|
const cwd = await mkdtemp(join(tmpdir(), "zero-target-"));
|
|
const out = join(cwd, "hello-windows");
|
|
|
|
try {
|
|
const result = await runZero(["build", "--emit", "exe", "--target", "win32-x64.exe", "examples/hello.graph", "--out", out], {
|
|
env: { ...process.env, ZERO_CC: "/usr/bin/false" },
|
|
});
|
|
assert.match(result.stdout, /hello-windows\.exe/);
|
|
const bytes = await readFile(`${out}.exe`);
|
|
assert.equal(bytes[0], 0x4d);
|
|
assert.equal(bytes[1], 0x5a);
|
|
await assert.rejects(access(`${out}.exe.c`));
|
|
} finally {
|
|
await rm(cwd, { force: true, recursive: true });
|
|
}
|
|
});
|
|
|
|
it("rejects unsupported target triples", async () => {
|
|
const result = await runZero(["check", "--target", "mips-unknown-none", "examples/hello.graph"]).catch((error) => error);
|
|
assert.notEqual(result.code, 0);
|
|
assert.match(result.stderr, /unknown target 'mips-unknown-none'/);
|
|
});
|
|
|
|
it("builds hosted Linux hello through the direct executable backend", async () => {
|
|
const cwd = await mkdtemp(join(tmpdir(), "zero-cross-"));
|
|
const out = join(cwd, "hello-linux-musl");
|
|
|
|
try {
|
|
const build = await runZero(["build", "--emit", "exe", "--target", "linux-musl-x64", "examples/hello.graph", "--out", out]);
|
|
assert.match(build.stdout, /hello-linux-musl/);
|
|
assert.ok((await readFile(out)).includes(Buffer.from("hello from zero\n")));
|
|
} finally {
|
|
await rm(cwd, { force: true, recursive: true });
|
|
}
|
|
});
|
|
|
|
it("emits native diagnostic codes", async () => {
|
|
const out = join(tmpdir(), `zero-backend-blocked-${Date.now()}`);
|
|
const result = await runZero(["build", "--json", "--target", "linux-musl-x64", "conformance/common/fail/unsupported-target-feature.graph", "--out", out]).catch((error) => error);
|
|
assert.notEqual(result.code, 0);
|
|
const diagnostic = JSON.parse(result.stdout).diagnostics[0];
|
|
assert.equal(diagnostic.code, "BLD004");
|
|
assert.match(diagnostic.help, /use zero check/);
|
|
});
|
|
});
|