test(sandbox): fix duplicate registration assertion

This commit is contained in:
Guillaume ARM 2026-06-14 17:30:31 +02:00
parent f2da9d7438
commit af1bc0e666
3 changed files with 21 additions and 13 deletions

View File

@ -3,9 +3,9 @@ import test from "node:test";
import { formatFailure, startCraftos, startSandbox, waitFor } from "./harness.js"; import { formatFailure, startCraftos, startSandbox, waitFor } from "./harness.js";
// Two real computers with the same traposId (id:label): newest-wins. After both // Two real computers with the same traposId (id:label): newest-wins. After both
// complete hello, exactly one registration exists; aborting the newest clears it // complete hello, exactly one registration exists; aborting the displaced first
// (the displaced first computer is in its 5s reconnect backoff), proving the newest // computer does not affect the live registration, and a gateway->computer ping is
// connection is the one that's registered. // still answered by the newest computer.
test("duplicate traposId is resolved newest-wins", async () => { test("duplicate traposId is resolved newest-wins", async () => {
const sandbox = await startSandbox(); const sandbox = await startSandbox();
const TRAPOS_ID = "9:dup"; const TRAPOS_ID = "9:dup";
@ -13,7 +13,7 @@ test("duplicate traposId is resolved newest-wins", async () => {
const first = startCraftos("gateway-client.lua", { const first = startCraftos("gateway-client.lua", {
computerId: 9, computerId: 9,
computerLabel: "dup", computerLabel: "dup",
shellArgs: [sandbox.baseUrl, "-", "idle"], shellArgs: [sandbox.baseUrl, "-", "idle", "60"],
}); });
const second = startCraftos("gateway-client.lua", { const second = startCraftos("gateway-client.lua", {
computerId: 9, computerId: 9,
@ -30,11 +30,10 @@ test("duplicate traposId is resolved newest-wins", async () => {
assert.equal(sandbox.registry.count(), 1, "exactly one registration after the duplicate"); assert.equal(sandbox.registry.count(), 1, "exactly one registration after the duplicate");
assert.ok(sandbox.registry.has("local", TRAPOS_ID)); assert.ok(sandbox.registry.has("local", TRAPOS_ID));
// Aborting the newest clears the entry; the displaced first is in reconnect first.abort();
// backoff (~5s) and has not re-registered yet. await first.done.catch(() => undefined);
second.abort(); const payload = await sandbox.registry.request("local", TRAPOS_ID, "ping", {}, 2_000);
await second.done.catch(() => undefined); assert.deepEqual(payload, { traposId: TRAPOS_ID }, "newest stayed registered after first exited");
await waitFor(() => sandbox.registry.count() === 0, 3_000, "newest was the live registration");
} catch (error) { } catch (error) {
throw new Error( throw new Error(
formatFailure( formatFailure(

View File

@ -139,7 +139,11 @@ export function startCraftos(
const result = await new Promise<{ status: number | null; signal: NodeJS.Signals | null }>((resolve) => { const result = await new Promise<{ status: number | null; signal: NodeJS.Signals | null }>((resolve) => {
child.once("close", (code, signal) => resolve({ status: code, signal })); child.once("close", (code, signal) => resolve({ status: code, signal }));
child.once("error", () => resolve({ status: null, signal: null })); child.once("error", (err: NodeJS.ErrnoException) => {
if (err.code !== "ABORT_ERR") {
resolve({ status: null, signal: null });
}
});
}); });
return { status: result.status, signal: result.signal, output: Buffer.concat(chunks).toString("utf8") }; return { status: result.status, signal: result.signal, output: Buffer.concat(chunks).toString("utf8") };

View File

@ -2,7 +2,7 @@
-- against a real gateway. Reuses startSession/request/onMessage so the test covers -- against a real gateway. Reuses startSession/request/onMessage so the test covers
-- production client code + CraftOS JSON serialization quirks. -- production client code + CraftOS JSON serialization quirks.
-- --
-- Args (via shell.run): url, secret, scenario. -- Args (via shell.run): url, secret, scenario, reconnectDelay.
-- url gateway origin, e.g. ws://127.0.0.1:PORT (daemon appends /gateway) -- url gateway origin, e.g. ws://127.0.0.1:PORT (daemon appends /gateway)
-- secret shared secret ('' => none) -- secret shared secret ('' => none)
-- scenario health | auth | idle (default: health) -- scenario health | auth | idle (default: health)
@ -15,18 +15,23 @@ local url = args[1];
local secret = args[2]; local secret = args[2];
if secret == '' or secret == '-' then secret = nil; end if secret == '' or secret == '-' then secret = nil; end
local scenario = args[3] or 'health'; local scenario = args[3] or 'health';
local reconnectDelay = tonumber(args[4] or '');
local createSandbox = require('/apis/libsandbox'); local createSandbox = require('/apis/libsandbox');
local createEventLoop = require('/apis/eventloop'); local createEventLoop = require('/apis/eventloop');
_G.bootEventLoop = createEventLoop(); _G.bootEventLoop = createEventLoop();
local sandbox = createSandbox(); local sandbox = createSandbox();
local session = sandbox.startSession({ local sessionOpts = {
eventloop = _G.bootEventLoop, eventloop = _G.bootEventLoop,
url = url, url = url,
secret = secret, secret = secret,
os = os, os = os,
}); };
if reconnectDelay then
sessionOpts.reconnectDelay = reconnectDelay;
end
local session = sandbox.startSession(sessionOpts);
-- Connect, probe the gateway, report opencodeOk. -- Connect, probe the gateway, report opencodeOk.
local function runHealth() local function runHealth()