#!/usr/bin/env node const assert = require("assert"); const cp = require("child_process"); const net = require("net"); const path = require("path"); const { coordinatorWireRequest } = require("./coordinator-wire"); const { configurePodmanTestEnvironment } = require("./podman-test-env"); const repo = path.resolve(__dirname, ".."); configurePodmanTestEnvironment(repo); if ( !process.env.CLUSTERFLUX_PODMAN_NIX_SHELL && cp.spawnSync("podman", ["--version"], { stdio: "ignore" }).status !== 0 && cp.spawnSync("nix", ["--version"], { stdio: "ignore" }).status === 0 ) { cp.execFileSync( "nix", ["shell", "nixpkgs#podman", "--command", "node", __filename], { cwd: repo, env: { ...process.env, CLUSTERFLUX_PODMAN_NIX_SHELL: "1", }, stdio: "inherit", } ); process.exit(0); } const project = path.join(repo, "tests/fixtures/runtime-conformance"); cp.execFileSync( "cargo", ["build", "-q", "-p", "clusterflux-node", "--bin", "clusterflux-node"], { cwd: repo, stdio: "inherit" } ); function waitForJsonLine(child) { return new Promise((resolve, reject) => { let buffer = ""; child.stdout.on("data", (chunk) => { buffer += chunk.toString(); const newline = buffer.indexOf("\n"); if (newline < 0) return; try { resolve(JSON.parse(buffer.slice(0, newline).trim())); } catch (error) { reject(error); } }); child.once("exit", (code) => { reject(new Error(`process exited before JSON line with code ${code}`)); }); }); } function send(addr, message) { return new Promise((resolve, reject) => { const socket = net.connect(addr.port, addr.host, () => { socket.write(`${JSON.stringify(coordinatorWireRequest(message))}\n`); }); let buffer = ""; socket.on("data", (chunk) => { buffer += chunk.toString(); const newline = buffer.indexOf("\n"); if (newline < 0) return; socket.end(); try { resolve(JSON.parse(buffer.slice(0, newline))); } catch (error) { reject(error); } }); socket.on("error", reject); }); } function runCli(args, env = {}) { return new Promise((resolve, reject) => { const child = cp.spawn( "cargo", ["run", "-q", "-p", "clusterflux-cli", "--bin", "clusterflux", "--", ...args], { cwd: repo, env: { ...process.env, ...env } } ); const cliPid = child.pid; let stdout = ""; let stderr = ""; child.stdout.on("data", (chunk) => { stdout += chunk.toString(); }); child.stderr.on("data", (chunk) => { stderr += chunk.toString(); }); child.on("exit", (code) => { if (code !== 0) { reject(new Error(`CLI run failed with code ${code}\n${stderr}`)); return; } try { resolve({ pid: cliPid, report: JSON.parse(stdout) }); } catch (error) { reject(new Error(`CLI output was not JSON: ${stdout}\n${error.stack || error.message}`)); } }); }); } (async () => { const coordinator = cp.spawn( "cargo", [ "run", "-q", "-p", "clusterflux-coordinator", "--bin", "clusterflux-coordinator", "--", "--listen", "127.0.0.1:0", "--allow-local-trusted-loopback" ], { cwd: repo } ); assert(Number.isInteger(coordinator.pid)); try { const ready = await waitForJsonLine(coordinator); const [host, portText] = ready.listen.split(":"); const addr = { host, port: Number(portText) }; assert.strictEqual((await send(addr, { type: "ping" })).type, "pong"); const { pid: cliPid, report } = await runCli([ "run", "--coordinator", `${addr.host}:${addr.port}`, "--project", project, "--json", ]); assert(Number.isInteger(cliPid)); assert.notStrictEqual(cliPid, coordinator.pid); assert.strictEqual(report.plan.entry, "build"); assert.deepStrictEqual(report.plan.session, "Anonymous"); assert.strictEqual(report.boundary.cli_process_started_node_process, true); assert.strictEqual(report.boundary.cli_process_started_coordinator_process, false); assert(Number.isInteger(report.boundary.spawned_node_process_id)); assert.notStrictEqual(report.boundary.spawned_node_process_id, cliPid); assert.notStrictEqual(report.boundary.spawned_node_process_id, coordinator.pid); assert.strictEqual(report.boundary.node_session_requests, 0); assert.strictEqual(report.node_report.node_status, "completed"); assert.strictEqual(report.node_report.execution_substrate, "wasm"); assert.strictEqual(report.node_report.task_spawn_host_import, true); assert.strictEqual( report.node_report.pre_node_process_status.processes.length, 1 ); assert.deepStrictEqual( report.node_report.pre_node_process_status.processes[0].connected_nodes, [] ); assert.strictEqual( report.node_report.pre_node_process_status.processes[0].main_state, "running" ); assert.strictEqual( report.node_report.pre_node_process_status.processes[0].main_wait_state, "waiting_for_node", "the coordinator must expose that the capless main is parked on placement before a node exists" ); assert.strictEqual( report.node_report.pre_node_process_status.processes[0].main_task_instance, report.node_report.run.task_instance ); assert.strictEqual(report.node_report.run.status, "main_launched"); assert.strictEqual(report.node_report.join.type, "task_joined"); const process = report.node_report.run.process; assert.strictEqual(process, "vp-current"); const events = await send(addr, { type: "list_task_events", tenant: "tenant", project: "project", actor_user: "user", process }); assert.strictEqual(events.type, "task_events"); assert(events.events.length >= 4); assert( events.events .filter((event) => event.executor === "node") .every((event) => event.node === "node-cli-local") ); assert( events.events.some( (event) => event.executor === "coordinator_main" && event.node === "coordinator-main" ) ); assert(events.events.every((event) => event.process === process)); assert.deepStrictEqual( new Set(events.events.map((event) => event.task_definition)), new Set([ report.node_report.run.task_definition, "prepare_source", "compile_linux", "package_release", ]) ); assert.strictEqual( new Set(events.events.map((event) => event.task)).size, events.events.length, "every live task event must retain its unique instance identity" ); assert( events.events.some( (event) => event.task === report.node_report.run.task_instance ) ); assert(events.events.some((event) => event.task.endsWith(":child:1"))); assert(events.events.some((event) => event.task.endsWith(":child:2"))); assert(events.events.some((event) => event.task.endsWith(":child:3"))); assert(events.events.some((event) => event.artifact_path)); } finally { coordinator.kill("SIGTERM"); } const { pid: autoCliPid, report: autoReport } = await runCli([ "run", "--local", "--project", project, "--json", ]); assert(Number.isInteger(autoCliPid)); assert.strictEqual(autoReport.plan.entry, "build"); assert.deepStrictEqual(autoReport.plan.coordinator, "LocalOnly"); assert.deepStrictEqual(autoReport.plan.session, "Anonymous"); assert.strictEqual(autoReport.boundary.cli_process_started_node_process, true); assert.strictEqual(autoReport.boundary.cli_process_started_coordinator_process, true); assert.match(autoReport.boundary.coordinator_address, /^127\.0\.0\.1:\d+$/); assert(Number.isInteger(autoReport.boundary.coordinator_process_id)); assert(Number.isInteger(autoReport.boundary.spawned_node_process_id)); assert.notStrictEqual(autoReport.boundary.coordinator_process_id, autoCliPid); assert.notStrictEqual(autoReport.boundary.spawned_node_process_id, autoCliPid); assert.notStrictEqual( autoReport.boundary.spawned_node_process_id, autoReport.boundary.coordinator_process_id ); assert.strictEqual(autoReport.boundary.node_session_requests, 0); assert.strictEqual(autoReport.node_report.node_status, "completed"); assert.strictEqual(autoReport.node_report.execution_substrate, "wasm"); assert.strictEqual(autoReport.node_report.task_spawn_host_import, true); assert.strictEqual(autoReport.node_report.run.status, "main_launched"); assert.strictEqual(autoReport.node_report.join.type, "task_joined"); console.log("CLI local run smoke passed"); })().catch((error) => { console.error(error.stack || error.message); process.exit(1); });