147 lines
5.3 KiB
TypeScript
147 lines
5.3 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { test } from "node:test";
|
|
import { ActorDebugAdapter } from "../src/debugger";
|
|
import { DebugVariable } from "../src/debugVariables";
|
|
import { DebugHarness } from "../test/debugHarness";
|
|
import { ImageClient } from "../src/client";
|
|
import { parseValue } from "../src/values";
|
|
import { startImage } from "./server";
|
|
|
|
test("debugger variables show committed data rather than invocation drafts and never execute Rune", async (t) => {
|
|
const image = await startImage();
|
|
const client = await ImageClient.connect(image.address, (error) =>
|
|
assert.equal(error, undefined),
|
|
);
|
|
t.after(async () => {
|
|
await client.close();
|
|
await image.stop();
|
|
});
|
|
const source =
|
|
'pub fn handle(ctx, state, sender, message) {\n state.items.push(message);\n nested(state)\n}\nfn nested(state) {\n if state.other.flag { state } else { panic("unexpected flag") }\n}';
|
|
const file = await client.writeSource("tests/variables", null, source);
|
|
const state = parseValue('{"items":[9223372036854775807],"other":{"flag":true}}');
|
|
const actor = await client.spawn(file.path, state);
|
|
await client.send(actor, parseValue('{"value":-9223372036854775808}'));
|
|
const h = new DebugHarness(
|
|
(send) =>
|
|
new ActorDebugAdapter(
|
|
client,
|
|
actor,
|
|
() => "runa-execution://1/loaded/test.rn",
|
|
() => {},
|
|
send,
|
|
(path) => `runa-image://${image.address}/source/${path}.rn`,
|
|
() => true,
|
|
),
|
|
);
|
|
t.after(() => h.adapter.dispose());
|
|
await h.attach();
|
|
const bps = await h.request<{ breakpoints: { verified: boolean }[] }>("setBreakpoints", {
|
|
source: { sourceReference: 1 },
|
|
breakpoints: [{ line: 6 }],
|
|
});
|
|
assert.equal(bps.breakpoints[0]?.verified, true);
|
|
await h.request("continue", { threadId: 1 });
|
|
await client.tick(1, 1000);
|
|
h.adapter.invalidate();
|
|
await h.wait(
|
|
(message) =>
|
|
message.event === "stopped" && (message.body as { reason: string }).reason === "breakpoint",
|
|
);
|
|
const before = await client.inspectActor(actor);
|
|
const stack = await h.request<{ stackFrames: { id: number }[] }>("stackTrace", { threadId: 1 });
|
|
assert.equal(stack.stackFrames.length, 2);
|
|
const scope = async (frame: number) => {
|
|
const reply = await h.request<{ scopes: { variablesReference: number }[] }>("scopes", {
|
|
frameId: frame,
|
|
});
|
|
return reply.scopes[0]!.variablesReference;
|
|
};
|
|
const reference = await scope(stack.stackFrames[0]!.id);
|
|
assert.equal(
|
|
await scope(stack.stackFrames[1]!.id),
|
|
reference,
|
|
"actor scope is shared, not fabricated per-frame locals",
|
|
);
|
|
const children = async (reference: number) =>
|
|
(
|
|
await h.request<{ variables: DebugVariable[] }>("variables", {
|
|
variablesReference: reference,
|
|
})
|
|
).variables;
|
|
const roots = await children(reference);
|
|
const stateFields = await children(
|
|
roots.find((field) => field.name === "committed_state")!.variablesReference,
|
|
);
|
|
const items = stateFields.find((field) => field.name === "items")!;
|
|
assert.equal(items.indexedVariables, 1, "the invocation's push has not committed");
|
|
assert.equal((await children(items.variablesReference))[0]?.value, "9223372036854775807");
|
|
const mailbox = await children(
|
|
roots.find((field) => field.name === "mailbox")!.variablesReference,
|
|
);
|
|
const reserved = await children(
|
|
mailbox.find((field) => field.name === "reserved")!.variablesReference,
|
|
);
|
|
const message = await children(reserved[0]!.variablesReference);
|
|
assert.equal(message[0]?.value, "-9223372036854775808");
|
|
const watch = await h.request<{ result: string; variablesReference: number }>("evaluate", {
|
|
expression: "actor/committed_state/items",
|
|
context: "watch",
|
|
});
|
|
assert.equal(watch.result, "List(1)");
|
|
assert.equal(watch.variablesReference, items.variablesReference);
|
|
assert.equal(
|
|
(
|
|
await h.request<{ result: string }>("evaluate", {
|
|
expression: "actor/mailbox/reserved/0/value",
|
|
context: "repl",
|
|
})
|
|
).result,
|
|
"-9223372036854775808",
|
|
);
|
|
await assert.rejects(
|
|
h.request("evaluate", {
|
|
expression: "actor/committed_state/items.push(1)",
|
|
context: "repl",
|
|
}),
|
|
/not found/,
|
|
);
|
|
assert.deepEqual(
|
|
await client.inspectActor(actor),
|
|
before,
|
|
"scope expansion must not mutate or advance the actor",
|
|
);
|
|
let completed = false;
|
|
for (let count = 0; count < 10; count++) {
|
|
const since = h.messages.length;
|
|
await h.request("next", { threadId: 1 });
|
|
await h.wait((message) => message.event === "stopped", since);
|
|
if (!(await client.inspectActor(actor)).execution) {
|
|
completed = true;
|
|
break;
|
|
}
|
|
}
|
|
assert.equal(completed, true);
|
|
assert.deepEqual(
|
|
(await client.inspectActor(actor)).state,
|
|
parseValue(
|
|
'{"items":[9223372036854775807,{"value":-9223372036854775808}],"other":{"flag":true}}',
|
|
),
|
|
);
|
|
await assert.rejects(h.request("variables", { variablesReference: reference }), /Stale/);
|
|
assert.equal(
|
|
(
|
|
await h.request<{ result: string }>("evaluate", {
|
|
expression: "actor/committed_state/items",
|
|
context: "watch",
|
|
})
|
|
).result,
|
|
"List(2)",
|
|
"the same watch resolves against the new committed stop snapshot",
|
|
);
|
|
await assert.rejects(
|
|
h.request("variables", { variablesReference: watch.variablesReference }),
|
|
/Stale/,
|
|
);
|
|
});
|