Public release release-5792a1a4e1cb
Source commit: 5792a1a4e1cb24ff71b1535d4b582ef980880752 Public tree identity: sha256:eea1f740446827ef10e4892923c13f13077c9e03438f2bf53d7f90536fb4cf95
This commit is contained in:
commit
a6b866e279
210 changed files with 78563 additions and 0 deletions
441
scripts/dap-smoke.js
Normal file
441
scripts/dap-smoke.js
Normal file
|
|
@ -0,0 +1,441 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
const assert = require("assert");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const { DapClient } = require("./dap-client");
|
||||
|
||||
(async () => {
|
||||
const repo = path.resolve(__dirname, "..");
|
||||
const project = path.join(repo, "examples/launch-build-demo");
|
||||
const sourcePath = fs.realpathSync(path.join(project, "src/build.rs"));
|
||||
const sourceLines = fs.readFileSync(sourcePath, "utf8").split(/\r?\n/);
|
||||
const buildMainLine =
|
||||
sourceLines.findIndex((line) => line.includes("pub async fn build_main()")) + 1;
|
||||
assert(buildMainLine > 0, "flagship source must contain build_main");
|
||||
|
||||
const asynchronousClient = new DapClient();
|
||||
try {
|
||||
const initialize = asynchronousClient.send("initialize", {
|
||||
adapterID: "clusterflux",
|
||||
linesStartAt1: true,
|
||||
columnsStartAt1: true,
|
||||
});
|
||||
await asynchronousClient.response(initialize, "initialize");
|
||||
const launch = asynchronousClient.send("launch", {
|
||||
entry: "long-join",
|
||||
project,
|
||||
runtimeBackend: "local-services",
|
||||
});
|
||||
await asynchronousClient.response(launch, "launch");
|
||||
await asynchronousClient.waitFor(
|
||||
(message) => message.type === "event" && message.event === "initialized"
|
||||
);
|
||||
|
||||
const configuredAt = Date.now();
|
||||
const configurationDone = asynchronousClient.send("configurationDone");
|
||||
await asynchronousClient.response(configurationDone, "configurationDone");
|
||||
assert(
|
||||
Date.now() - configuredAt < 2_000,
|
||||
"configurationDone must not wait for bundle build or runtime completion"
|
||||
);
|
||||
|
||||
const threadDeadline = Date.now() + 120_000;
|
||||
let runningThreads = [];
|
||||
while (Date.now() < threadDeadline) {
|
||||
const threadsRequest = asynchronousClient.send("threads");
|
||||
runningThreads = (
|
||||
await asynchronousClient.response(threadsRequest, "threads")
|
||||
).body.threads;
|
||||
if (runningThreads.length > 0) break;
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
}
|
||||
assert(runningThreads.length > 0, "asynchronous launch never reported a live thread");
|
||||
|
||||
const pauseAt = Date.now();
|
||||
const pause = asynchronousClient.send("pause", {
|
||||
threadId: runningThreads[0].id,
|
||||
});
|
||||
await asynchronousClient.response(pause, "pause");
|
||||
assert(Date.now() - pauseAt < 2_000, "pause response was blocked by runtime work");
|
||||
const paused = await asynchronousClient.waitFor(
|
||||
(message) =>
|
||||
message.type === "event" &&
|
||||
message.event === "stopped" &&
|
||||
message.body.reason === "pause"
|
||||
);
|
||||
|
||||
const continueAt = Date.now();
|
||||
const continued = asynchronousClient.send("continue", {
|
||||
threadId: paused.body.threadId,
|
||||
});
|
||||
await asynchronousClient.response(continued, "continue");
|
||||
assert(
|
||||
Date.now() - continueAt < 2_000,
|
||||
"continue response was blocked by runtime observation"
|
||||
);
|
||||
|
||||
const disconnectAt = Date.now();
|
||||
await asynchronousClient.close();
|
||||
assert(
|
||||
Date.now() - disconnectAt < 2_000,
|
||||
"disconnect response was blocked by runtime observation"
|
||||
);
|
||||
} catch (error) {
|
||||
if (asynchronousClient.child.exitCode === null) {
|
||||
asynchronousClient.child.kill("SIGKILL");
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
const client = new DapClient();
|
||||
try {
|
||||
const initialize = client.send("initialize", {
|
||||
adapterID: "clusterflux",
|
||||
linesStartAt1: true,
|
||||
columnsStartAt1: true,
|
||||
});
|
||||
await client.response(initialize, "initialize");
|
||||
|
||||
const launch = client.send("launch", {
|
||||
entry: "build",
|
||||
project,
|
||||
runtimeBackend: "local-services",
|
||||
});
|
||||
await client.response(launch, "launch");
|
||||
await client.waitFor(
|
||||
(message) => message.type === "event" && message.event === "initialized"
|
||||
);
|
||||
|
||||
const breakpoints = client.send("setBreakpoints", {
|
||||
source: { path: sourcePath },
|
||||
breakpoints: [{ line: buildMainLine }],
|
||||
});
|
||||
const breakpointResponse = await client.response(
|
||||
breakpoints,
|
||||
"setBreakpoints"
|
||||
);
|
||||
assert.strictEqual(breakpointResponse.body.breakpoints.length, 1);
|
||||
assert.strictEqual(breakpointResponse.body.breakpoints[0].verified, true);
|
||||
|
||||
const configurationDone = client.send("configurationDone");
|
||||
await client.response(configurationDone, "configurationDone");
|
||||
const stopped = await client.waitFor(
|
||||
(message) =>
|
||||
message.type === "event" &&
|
||||
message.event === "stopped" &&
|
||||
message.body.reason === "breakpoint"
|
||||
);
|
||||
assert.strictEqual(stopped.body.allThreadsStopped, true);
|
||||
assert.match(stopped.body.description, /confirmed by every active participant/i);
|
||||
|
||||
const threadsRequest = client.send("threads");
|
||||
const threads = (await client.response(threadsRequest, "threads")).body
|
||||
.threads;
|
||||
const mainThread = threads.find((thread) =>
|
||||
thread.name.includes("build coordinator main")
|
||||
);
|
||||
assert(mainThread, "the running Wasm entrypoint must be the DAP coordinator-main thread");
|
||||
assert.strictEqual(stopped.body.threadId, mainThread.id);
|
||||
|
||||
const stackRequest = client.send("stackTrace", {
|
||||
threadId: mainThread.id,
|
||||
startFrame: 0,
|
||||
levels: 1,
|
||||
});
|
||||
const stack = (await client.response(stackRequest, "stackTrace")).body
|
||||
.stackFrames;
|
||||
assert.strictEqual(stack.length, 1);
|
||||
assert.strictEqual(stack[0].line, buildMainLine);
|
||||
assert.strictEqual(stack[0].source.path, sourcePath);
|
||||
assert.match(stack[0].name, /build_main::wasm/);
|
||||
assert.doesNotMatch(stack[0].name, /podman|cmd\.exe|powershell|pid|native child/i);
|
||||
|
||||
const sourceRequest = client.send("source", { source: stack[0].source });
|
||||
const source = (await client.response(sourceRequest, "source")).body;
|
||||
assert.match(source.content, /build_main/);
|
||||
assert.match(source.mimeType, /rust/);
|
||||
|
||||
const scopesRequest = client.send("scopes", { frameId: stack[0].id });
|
||||
const scopes = (await client.response(scopesRequest, "scopes")).body.scopes;
|
||||
const localsScope = scopes.find((scope) => scope.name === "Source Locals");
|
||||
const wasmScope = scopes.find((scope) => scope.name === "Wasm Frame Locals");
|
||||
const argsScope = scopes.find(
|
||||
(scope) => scope.name === "Task Args and Handles"
|
||||
);
|
||||
const runtimeScope = scopes.find(
|
||||
(scope) => scope.name === "Clusterflux Runtime"
|
||||
);
|
||||
assert(localsScope && wasmScope && argsScope && runtimeScope);
|
||||
|
||||
const localsRequest = client.send("variables", {
|
||||
variablesReference: localsScope.variablesReference,
|
||||
});
|
||||
const locals = (await client.response(localsRequest, "variables")).body
|
||||
.variables;
|
||||
assert(
|
||||
locals.some(
|
||||
(variable) =>
|
||||
variable.name === "unavailable-local-diagnostic" &&
|
||||
String(variable.value).includes("cannot be inspected")
|
||||
)
|
||||
);
|
||||
|
||||
const wasmRequest = client.send("variables", {
|
||||
variablesReference: wasmScope.variablesReference,
|
||||
});
|
||||
const wasmLocals = (await client.response(wasmRequest, "variables")).body
|
||||
.variables;
|
||||
assert.deepStrictEqual(
|
||||
wasmLocals.map((variable) => variable.name),
|
||||
["wasm-local-diagnostic"]
|
||||
);
|
||||
assert.match(wasmLocals[0].value, /did not report inspectable Wasm frame locals/);
|
||||
|
||||
const argsRequest = client.send("variables", {
|
||||
variablesReference: argsScope.variablesReference,
|
||||
});
|
||||
const args = (await client.response(argsRequest, "variables")).body.variables;
|
||||
assert.deepStrictEqual(
|
||||
args.map((variable) => variable.name),
|
||||
["runtime-boundary-diagnostic"]
|
||||
);
|
||||
assert.match(args[0].value, /reported no task arguments or handles/);
|
||||
|
||||
const runtimeRequest = client.send("variables", {
|
||||
variablesReference: runtimeScope.variablesReference,
|
||||
});
|
||||
const runtime = (await client.response(runtimeRequest, "variables")).body
|
||||
.variables;
|
||||
const value = (name) => runtime.find((variable) => variable.name === name)?.value;
|
||||
assert.strictEqual(value("runtime_backend"), "LocalServices");
|
||||
assert.strictEqual(value("state"), "Frozen");
|
||||
assert.strictEqual(value("debug_epoch"), 1);
|
||||
assert.strictEqual(value("coordinator_task_events"), 0);
|
||||
assert.match(
|
||||
String(value("command_status")),
|
||||
/frozen through local services at executing Wasm probe/
|
||||
);
|
||||
|
||||
const step = client.send("next", { threadId: mainThread.id });
|
||||
const stepFailure = await client.failure(step, "next");
|
||||
assert.match(stepFailure.message, /source stepping is not yet available/i);
|
||||
assert.match(stepFailure.message, /synthetic step/i);
|
||||
|
||||
const restart = client.send("restartFrame", { frameId: stack[0].id });
|
||||
const restartFailure = await client.failure(restart, "restartFrame");
|
||||
assert.match(restartFailure.message, /checkpoint boundary|still active/i);
|
||||
|
||||
const incompatibleRestart = client.send("restartFrame", {
|
||||
frameId: stack[0].id,
|
||||
sourceCompatibility: "incompatible",
|
||||
});
|
||||
const incompatibleFailure = await client.failure(
|
||||
incompatibleRestart,
|
||||
"restartFrame"
|
||||
);
|
||||
assert.match(incompatibleFailure.message, /incompatible source edit/i);
|
||||
assert.match(incompatibleFailure.message, /whole virtual-process restart/i);
|
||||
|
||||
await client.close();
|
||||
} catch (error) {
|
||||
if (client.child.exitCode === null) client.child.kill("SIGKILL");
|
||||
throw error;
|
||||
}
|
||||
|
||||
const failMainLine =
|
||||
sourceLines.findIndex((line) => line.includes("pub async fn fail_main()")) + 1;
|
||||
assert(failMainLine > 0, "flagship source must contain fail_main");
|
||||
const taskTrapLine =
|
||||
sourceLines.findIndex((line) => line.includes("fn task_trap(")) + 1;
|
||||
assert(taskTrapLine > 0, "flagship source must contain task_trap");
|
||||
const restartClient = new DapClient();
|
||||
try {
|
||||
const initialize = restartClient.send("initialize", {
|
||||
adapterID: "clusterflux",
|
||||
linesStartAt1: true,
|
||||
columnsStartAt1: true,
|
||||
});
|
||||
await restartClient.response(initialize, "initialize");
|
||||
const launch = restartClient.send("launch", {
|
||||
entry: "fail",
|
||||
project,
|
||||
runtimeBackend: "local-services",
|
||||
});
|
||||
await restartClient.response(launch, "launch");
|
||||
await restartClient.waitFor(
|
||||
(message) => message.type === "event" && message.event === "initialized"
|
||||
);
|
||||
const breakpoints = restartClient.send("setBreakpoints", {
|
||||
source: { path: sourcePath },
|
||||
breakpoints: [{ line: failMainLine }, { line: taskTrapLine }],
|
||||
});
|
||||
const breakpointResponse = await restartClient.response(
|
||||
breakpoints,
|
||||
"setBreakpoints"
|
||||
);
|
||||
assert.deepStrictEqual(
|
||||
breakpointResponse.body.breakpoints.map((breakpoint) => breakpoint.verified),
|
||||
[true, true]
|
||||
);
|
||||
const configurationDone = restartClient.send("configurationDone");
|
||||
await restartClient.response(configurationDone, "configurationDone");
|
||||
const initialStop = await restartClient.waitFor(
|
||||
(message) =>
|
||||
message.type === "event" &&
|
||||
message.event === "stopped" &&
|
||||
message.body.reason === "breakpoint"
|
||||
);
|
||||
assert.strictEqual(initialStop.body.allThreadsStopped, true);
|
||||
const threadsRequest = restartClient.send("threads");
|
||||
const threads = (
|
||||
await restartClient.response(threadsRequest, "threads")
|
||||
).body.threads;
|
||||
const failThread = threads.find(
|
||||
(thread) => thread.id === initialStop.body.threadId
|
||||
);
|
||||
assert(failThread, "failed entrypoint must remain a virtual task thread");
|
||||
const stackRequest = restartClient.send("stackTrace", {
|
||||
threadId: failThread.id,
|
||||
startFrame: 0,
|
||||
levels: 1,
|
||||
});
|
||||
const failedStack = (
|
||||
await restartClient.response(stackRequest, "stackTrace")
|
||||
).body.stackFrames;
|
||||
assert.strictEqual(failedStack[0].line, failMainLine);
|
||||
|
||||
const continueRequest = restartClient.send("continue", {
|
||||
threadId: failThread.id,
|
||||
});
|
||||
await restartClient.response(continueRequest, "continue");
|
||||
const childStop = await restartClient.waitFor(
|
||||
(message) =>
|
||||
message.seq > initialStop.seq &&
|
||||
message.type === "event" &&
|
||||
message.event === "stopped" &&
|
||||
message.body.reason === "breakpoint",
|
||||
70000
|
||||
);
|
||||
assert.strictEqual(childStop.body.allThreadsStopped, true);
|
||||
|
||||
const childThreadsRequest = restartClient.send("threads");
|
||||
const childThreads = (
|
||||
await restartClient.response(childThreadsRequest, "threads")
|
||||
).body.threads;
|
||||
const childThread = childThreads.find(
|
||||
(thread) => thread.id === childStop.body.threadId
|
||||
);
|
||||
assert(childThread, "executing child task must become a DAP virtual thread");
|
||||
assert.notStrictEqual(childThread.id, failThread.id);
|
||||
assert.match(childThread.name, /task trap/i);
|
||||
|
||||
const childStackRequest = restartClient.send("stackTrace", {
|
||||
threadId: childThread.id,
|
||||
startFrame: 0,
|
||||
levels: 1,
|
||||
});
|
||||
const childStack = (
|
||||
await restartClient.response(childStackRequest, "stackTrace")
|
||||
).body.stackFrames;
|
||||
assert.strictEqual(childStack[0].line, taskTrapLine);
|
||||
assert.match(childStack[0].name, /task_trap::wasm/);
|
||||
|
||||
const childScopesRequest = restartClient.send("scopes", {
|
||||
frameId: childStack[0].id,
|
||||
});
|
||||
const childScopes = (
|
||||
await restartClient.response(childScopesRequest, "scopes")
|
||||
).body.scopes;
|
||||
const childArgsScope = childScopes.find(
|
||||
(scope) => scope.name === "Task Args and Handles"
|
||||
);
|
||||
assert(childArgsScope, "child task argument scope must be present");
|
||||
const childArgsRequest = restartClient.send("variables", {
|
||||
variablesReference: childArgsScope.variablesReference,
|
||||
});
|
||||
const childArgs = (
|
||||
await restartClient.response(childArgsRequest, "variables")
|
||||
).body.variables;
|
||||
assert(
|
||||
childArgs.some(
|
||||
(variable) =>
|
||||
variable.name === "arg_0" &&
|
||||
/SmallJson\(Number\(0\)\)/.test(String(variable.value))
|
||||
),
|
||||
"child task argument must come from the frozen node participant"
|
||||
);
|
||||
|
||||
const parentScopesRequest = restartClient.send("scopes", {
|
||||
frameId: failedStack[0].id,
|
||||
});
|
||||
const parentScopes = (
|
||||
await restartClient.response(parentScopesRequest, "scopes")
|
||||
).body.scopes;
|
||||
const parentArgsScope = parentScopes.find(
|
||||
(scope) => scope.name === "Task Args and Handles"
|
||||
);
|
||||
const parentArgsRequest = restartClient.send("variables", {
|
||||
variablesReference: parentArgsScope.variablesReference,
|
||||
});
|
||||
const parentArgs = (
|
||||
await restartClient.response(parentArgsRequest, "variables")
|
||||
).body.variables;
|
||||
assert(
|
||||
parentArgs.some(
|
||||
(variable) =>
|
||||
/^task_handle_\d+$/.test(variable.name) &&
|
||||
/definition=task_trap instance=ti:.*:child:\d+ state=active/.test(
|
||||
variable.value
|
||||
) &&
|
||||
variable.type === "runtime-handle"
|
||||
),
|
||||
"parent task handle must come from its live Wasm host registry"
|
||||
);
|
||||
|
||||
const continueChildRequest = restartClient.send("continue", {
|
||||
threadId: childThread.id,
|
||||
});
|
||||
await restartClient.response(continueChildRequest, "continue");
|
||||
await restartClient.waitFor(
|
||||
(message) =>
|
||||
message.seq > childStop.seq &&
|
||||
message.type === "event" &&
|
||||
message.event === "terminated"
|
||||
);
|
||||
|
||||
const restartRequest = restartClient.send("restartFrame", {
|
||||
frameId: failedStack[0].id,
|
||||
});
|
||||
const restartResponse = await restartClient.response(
|
||||
restartRequest,
|
||||
"restartFrame"
|
||||
);
|
||||
const restartedStop = await restartClient.waitFor(
|
||||
(message) =>
|
||||
message.seq > restartResponse.seq &&
|
||||
message.type === "event" &&
|
||||
message.event === "stopped" &&
|
||||
message.body.reason === "breakpoint"
|
||||
);
|
||||
assert.strictEqual(restartedStop.body.allThreadsStopped, true);
|
||||
const restartedStackRequest = restartClient.send("stackTrace", {
|
||||
threadId: restartedStop.body.threadId,
|
||||
startFrame: 0,
|
||||
levels: 1,
|
||||
});
|
||||
const restartedStack = (
|
||||
await restartClient.response(restartedStackRequest, "stackTrace")
|
||||
).body.stackFrames;
|
||||
assert.strictEqual(restartedStack[0].line, failMainLine);
|
||||
await restartClient.close();
|
||||
} catch (error) {
|
||||
if (restartClient.child.exitCode === null) restartClient.child.kill("SIGKILL");
|
||||
throw error;
|
||||
}
|
||||
|
||||
console.log("DAP smoke passed");
|
||||
})().catch((error) => {
|
||||
console.error(error.stack || error.message);
|
||||
process.exit(1);
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue