#!/usr/bin/env node const cp = require("child_process"); const assert = require("assert"); const fs = require("fs"); const os = require("os"); const path = require("path"); class DapClient { constructor() { this.child = cp.spawn( "cargo", ["run", "-q", "-p", "disasmer-dap", "--bin", "disasmer-debug-dap"], { cwd: process.cwd() } ); this.seq = 1; this.buffer = Buffer.alloc(0); this.messages = []; this.waiters = []; this.stderr = ""; this.child.stdout.on("data", (chunk) => { this.buffer = Buffer.concat([this.buffer, chunk]); this.parse(); }); this.child.stderr.on("data", (chunk) => { this.stderr += chunk.toString(); }); this.child.on("exit", () => this.flushWaiters()); } send(command, args = {}) { const seq = this.seq++; const message = { seq, type: "request", command, arguments: args }; const payload = Buffer.from(JSON.stringify(message)); this.child.stdin.write(`Content-Length: ${payload.length}\r\n\r\n`); this.child.stdin.write(payload); return seq; } async response(seq, command) { const message = await this.waitFor( (item) => item.type === "response" && item.request_seq === seq && item.command === command ); if (!message.success) { throw new Error(`DAP ${command} failed: ${message.message || JSON.stringify(message)}`); } return message; } async failure(seq, command) { const message = await this.waitFor( (item) => item.type === "response" && item.request_seq === seq && item.command === command ); if (message.success) { throw new Error(`DAP ${command} unexpectedly succeeded`); } return message; } waitFor(predicate, timeoutMs = 120000) { const existing = this.messages.find(predicate); if (existing) return Promise.resolve(existing); return new Promise((resolve, reject) => { const timer = setTimeout(() => { this.child.kill("SIGKILL"); reject(new Error(`timed out waiting for DAP message\n${this.stderr}`)); }, timeoutMs); this.waiters.push({ predicate, resolve, timer }); }); } parse() { while (true) { const headerEnd = this.buffer.indexOf("\r\n\r\n"); if (headerEnd < 0) return; const header = this.buffer.slice(0, headerEnd).toString(); const match = header.match(/Content-Length: (\d+)/i); if (!match) throw new Error(`bad DAP header: ${header}`); const length = Number(match[1]); const start = headerEnd + 4; const end = start + length; if (this.buffer.length < end) return; const payload = this.buffer.slice(start, end).toString(); this.buffer = this.buffer.slice(end); this.messages.push(JSON.parse(payload)); this.flushWaiters(); } } flushWaiters() { for (const waiter of [...this.waiters]) { const message = this.messages.find(waiter.predicate); if (!message) continue; clearTimeout(waiter.timer); this.waiters.splice(this.waiters.indexOf(waiter), 1); waiter.resolve(message); } } async close() { const seq = this.send("disconnect"); await this.response(seq, "disconnect"); this.child.stdin.end(); } } function createFailingProject() { const root = fs.mkdtempSync(path.join(os.tmpdir(), "disasmer-dap-failing-")); fs.mkdirSync(path.join(root, "src"), { recursive: true }); fs.writeFileSync( path.join(root, "Cargo.toml"), `[package] name = "dap-failing-demo" version = "0.1.0" edition = "2021" ` ); fs.writeFileSync( path.join(root, "src/lib.rs"), `#[cfg(test)] mod tests { #[test] fn fails_for_restart_smoke() { assert_eq!(1, 2); } } ` ); return root; } function waitForJsonLine(child, description) { return new Promise((resolve, reject) => { let buffer = ""; let stderr = ""; 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(new Error(`${description} did not emit JSON: ${buffer}\n${error.stack || error.message}`)); } }); child.stderr.on("data", (chunk) => { stderr += chunk.toString(); }); child.once("exit", (code, signal) => { reject(new Error(`${description} exited before JSON line with code ${code} signal ${signal}\n${stderr}`)); }); }); } async function startCoordinator() { const child = cp.spawn( "cargo", [ "run", "-q", "-p", "disasmer-coordinator", "--bin", "disasmer-coordinator", "--", "--listen", "127.0.0.1:0" ], { cwd: process.cwd() } ); const ready = await waitForJsonLine(child, "coordinator"); return { child, listen: ready.listen }; } async function startExplicitWorker(listen) { const child = cp.spawn( "cargo", [ "run", "-q", "-p", "disasmer-node", "--bin", "disasmer-node", "--", "--coordinator", listen, "--tenant", "tenant", "--project-id", "project", "--node", "dap-live-worker", "--worker", "--assignment-poll-ms", "50", "--emit-ready" ], { cwd: process.cwd() } ); const ready = await waitForJsonLine(child, "explicit worker"); assert.strictEqual(ready.node_status, "ready"); assert.strictEqual(ready.mode, "worker"); assert.strictEqual(ready.node, "dap-live-worker"); return child; } function killChild(child) { if (child && child.exitCode === null) { child.kill("SIGTERM"); } } async function launchToBreakpoint({ runtimeBackend = "simulated", breakpointLine, breakpointLines, project = path.join(process.cwd(), "examples/launch-build-demo"), operatorEndpoint, tenant = "tenant", projectId = "project", actorUser = "dap" }) { const client = new DapClient(); const initialize = client.send("initialize", { adapterID: "disasmer", linesStartAt1: true, columnsStartAt1: true }); await client.response(initialize, "initialize"); const launchArgs = { entry: "build", project, runtimeBackend, tenant, projectId, actorUser }; if (operatorEndpoint) { launchArgs.operatorEndpoint = operatorEndpoint; } const launch = client.send("launch", launchArgs); await client.response(launch, "launch"); await client.waitFor((message) => message.type === "event" && message.event === "initialized"); const breakpoints = client.send("setBreakpoints", { source: { path: path.join(project, "src/build.rs") }, breakpoints: (breakpointLines || [breakpointLine]).map((line) => ({ line })) }); const breakpointResponse = await client.response(breakpoints, "setBreakpoints"); assert.deepStrictEqual( breakpointResponse.body.breakpoints.map((breakpoint) => breakpoint.verified), (breakpointLines || [breakpointLine]).map(() => true) ); const exceptions = client.send("setExceptionBreakpoints", { filters: [] }); await client.response(exceptions, "setExceptionBreakpoints"); const configurationDone = client.send("configurationDone"); await client.response(configurationDone, "configurationDone"); const stopped = await client.waitFor( (message) => message.type === "event" && message.event === "stopped" ); assert.strictEqual(stopped.body.allThreadsStopped, true); assert.strictEqual(stopped.body.reason, "breakpoint"); return { client, stopped }; } (async () => { const launchProject = path.join(process.cwd(), "examples/launch-build-demo"); const launchSource = fs.realpathSync(path.join(launchProject, "src/build.rs")); const dapSource = fs.readFileSync( path.join(process.cwd(), "crates/disasmer-dap/src/main.rs"), "utf8" ); const liveStart = dapSource.indexOf("fn run_live_services_runtime"); const liveEnd = dapSource.indexOf("fn run_with_coordinator", liveStart); const liveRuntimeSource = dapSource.slice(liveStart, liveEnd); assert.doesNotMatch(liveRuntimeSource, /run_node_against_coordinator|Command::new|project_binary/); const { client, stopped } = await launchToBreakpoint({ runtimeBackend: "local-services", project: launchProject, breakpointLine: 22 }); assert.strictEqual(stopped.body.threadId, 2); const threadsRequest = client.send("threads"); const threads = (await client.response(threadsRequest, "threads")).body.threads; assert.deepStrictEqual( threads.map((thread) => thread.id), [1, 2, 3, 4] ); assert(threads.some((thread) => thread.name.includes("compile linux"))); assert(threads.some((thread) => thread.name.includes("compile windows"))); assert(threads.some((thread) => thread.name.includes("package artifacts"))); const stackRequest = client.send("stackTrace", { threadId: 2, startFrame: 0, levels: 1 }); const stack = (await client.response(stackRequest, "stackTrace")).body.stackFrames; assert.strictEqual(stack.length, 1); assert.match(stack[0].name, /compile linux::run/); assert.strictEqual(stack[0].line, 22); assert.strictEqual(stack[0].source.path, launchSource); assert.strictEqual(stack[0].source.sourceReference || 0, 0); 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, /compile_linux/); assert.match(source.mimeType, /rust/); const nextRequest = client.send("next", { threadId: 2 }); await client.response(nextRequest, "next"); const stepped = await client.waitFor( (message) => message.type === "event" && message.event === "stopped" && message.body.reason === "step" ); assert.strictEqual(stepped.body.threadId, 2); assert.strictEqual(stepped.body.allThreadsStopped, true); const steppedStackRequest = client.send("stackTrace", { threadId: 2, startFrame: 0, levels: 1 }); const steppedStack = (await client.response(steppedStackRequest, "stackTrace")).body.stackFrames; assert.strictEqual(steppedStack[0].line, 23); const steppedSourceRequest = client.send("source", { source: steppedStack[0].source }); const steppedSource = (await client.response(steppedSourceRequest, "source")).body; assert.match(steppedSource.content, /compile_linux/); const scopesRequest = client.send("scopes", { frameId: steppedStack[0].id }); const scopes = (await client.response(scopesRequest, "scopes")).body.scopes; const localsRef = scopes.find((scope) => scope.name === "Source Locals").variablesReference; const argsRef = scopes.find((scope) => scope.name === "Task Args and Handles").variablesReference; const runtimeRef = scopes.find((scope) => scope.name === "Disasmer Runtime").variablesReference; const outputRef = scopes.find((scope) => scope.name === "Recent Output").variablesReference; const localsRequest = client.send("variables", { variablesReference: localsRef }); 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 argsRequest = client.send("variables", { variablesReference: argsRef }); const args = (await client.response(argsRequest, "variables")).body.variables; const target = args.find((variable) => variable.name === "target"); const artifact = args.find((variable) => variable.name === "artifact"); const sourceSnapshot = args.find((variable) => variable.name === "source_snapshot"); const returnValue = args.find((variable) => variable.name === "return_value"); const blob = args.find((variable) => variable.name === "blob"); const vfsMounts = args.find((variable) => variable.name === "vfs_mounts"); assert(target); assert(target.variablesReference > 0); assert(artifact); assert.strictEqual(artifact.value, 'Artifact { id = "/vfs/artifacts/dap-output.txt" }'); assert(sourceSnapshot); assert.match(sourceSnapshot.value, /^SourceSnapshot \{ digest = "source:\/\/local-checkout\/[a-f0-9]{12}" \}$/); assert(returnValue); assert.match(returnValue.value, /Artifact/); assert(blob); assert.match(blob.value, /^Blob \{ digest = "sha256:[a-f0-9]{64}" \}$/); assert(vfsMounts); assert(vfsMounts.variablesReference > 0); const targetRequest = client.send("variables", { variablesReference: target.variablesReference }); const targetFields = (await client.response(targetRequest, "variables")).body.variables; assert(targetFields.some((variable) => variable.name === "environment" && variable.value === "linux")); assert( targetFields.some( (variable) => variable.name === "required_capability" && variable.value === "Command" ) ); const vfsRequest = client.send("variables", { variablesReference: vfsMounts.variablesReference }); const vfs = (await client.response(vfsRequest, "variables")).body.variables; assert(vfs.some((variable) => variable.name === "/vfs/artifacts")); assert(vfs.some((variable) => variable.name === "/vfs/sources")); assert(vfs.some((variable) => variable.name === "/vfs/blobs")); const runtimeRequest = client.send("variables", { variablesReference: runtimeRef }); const runtime = (await client.response(runtimeRequest, "variables")).body.variables; const processId = runtime.find((variable) => variable.name === "virtual_process_id"); const commandSpec = runtime.find((variable) => variable.name === "command_spec"); assert(processId); assert.match(processId.value, /^vp-[a-f0-9]{12}$/); assert(runtime.some((variable) => variable.name === "debug_epoch" && variable.value === 2)); assert(runtime.some((variable) => variable.name === "runtime_backend" && variable.value === "LocalServices")); assert(runtime.some((variable) => variable.name === "coordinator_task_events" && variable.value === 1)); assert(runtime.some((variable) => variable.name === "command_status" && String(variable.value).includes("completed through local services"))); assert(commandSpec); assert(commandSpec.variablesReference > 0); assert(runtime.some((variable) => variable.name === "stdout_tail")); assert(runtime.some((variable) => variable.name === "stderr_tail")); const commandRequest = client.send("variables", { variablesReference: commandSpec.variablesReference }); const commandVariables = (await client.response(commandRequest, "variables")).body.variables; assert(commandVariables.some((variable) => variable.name === "program" && variable.value === "cargo")); assert( commandVariables.some( (variable) => variable.name === "required_capability" && variable.value === "Command" ) ); const outputRequest = client.send("variables", { variablesReference: outputRef }); const output = (await client.response(outputRequest, "variables")).body.variables; assert(output.some((variable) => variable.name === "stdout_tail")); assert(output.some((variable) => variable.name === "stderr_tail")); assert(output.some((variable) => String(variable.value).includes("all-stop"))); assert(output.some((variable) => String(variable.value).includes("attached node completed task"))); assert(output.some((variable) => String(variable.value).includes("coordinator recorded 1 task event"))); const continueRequest = client.send("continue", { threadId: 2 }); await client.response(continueRequest, "continue"); await client.waitFor((message) => message.type === "event" && message.event === "continued"); const pauseRequest = client.send("pause", { threadId: 2 }); await client.response(pauseRequest, "pause"); const paused = await client.waitFor( (message) => message.type === "event" && message.event === "stopped" && message.body.reason === "pause" ); assert.strictEqual(paused.body.allThreadsStopped, true); const restartRequest = client.send("restartFrame", { frameId: stack[0].id }); await client.response(restartRequest, "restartFrame"); await client.waitFor( (message) => message.type === "event" && message.event === "output" && String(message.body.output).includes("Restarted selected task") ); const incompatibleRestartRequest = client.send("restartFrame", { frameId: stack[0].id, sourceCompatibility: "incompatible" }); const incompatibleRestart = await client.failure(incompatibleRestartRequest, "restartFrame"); assert.match(incompatibleRestart.message, /incompatible source edit/i); assert.match(incompatibleRestart.message, /whole virtual-process restart/i); const continueAfterRestartRequest = client.send("continue", { threadId: 2 }); await client.response(continueAfterRestartRequest, "continue"); await client.waitFor((message) => message.type === "event" && message.event === "continued"); const freezeFailureRequest = client.send("pause", { threadId: 2, simulateFreezeFailure: true }); const freezeFailure = await client.failure(freezeFailureRequest, "pause"); assert.match(freezeFailure.message, /all-stop failed/i); assert.match(freezeFailure.message, /could not freeze/i); await client.close(); const attachClient = new DapClient(); try { const attachInitialize = attachClient.send("initialize", { adapterID: "disasmer", linesStartAt1: true, columnsStartAt1: true }); await attachClient.response(attachInitialize, "initialize"); const attachRequest = attachClient.send("attach", { entry: "build", project: launchProject, runtimeBackend: "live-services", operatorEndpoint: "127.0.0.1:1" }); await attachClient.response(attachRequest, "attach"); await attachClient.waitFor( (message) => message.type === "event" && message.event === "initialized" ); const attachBreakpoints = attachClient.send("setBreakpoints", { source: { path: path.join(launchProject, "src/build.rs") }, breakpoints: [{ line: 12 }] }); await attachClient.response(attachBreakpoints, "setBreakpoints"); const attachDone = attachClient.send("configurationDone"); await attachClient.response(attachDone, "configurationDone"); const attachStopped = await attachClient.waitFor( (message) => message.type === "event" && message.event === "stopped" ); assert.strictEqual(attachStopped.body.allThreadsStopped, true); const attachStackRequest = attachClient.send("stackTrace", { threadId: 1, startFrame: 0, levels: 1 }); const attachStack = (await attachClient.response(attachStackRequest, "stackTrace")).body .stackFrames; const attachScopesRequest = attachClient.send("scopes", { frameId: attachStack[0].id }); const attachScopes = (await attachClient.response(attachScopesRequest, "scopes")).body.scopes; const attachRuntimeRef = attachScopes.find((scope) => scope.name === "Disasmer Runtime") .variablesReference; const attachRuntimeRequest = attachClient.send("variables", { variablesReference: attachRuntimeRef }); const attachRuntime = (await attachClient.response(attachRuntimeRequest, "variables")).body .variables; assert( attachRuntime.some( (variable) => variable.name === "command_status" && String(variable.value).includes("attached to existing virtual process") ) ); } finally { await attachClient.close().catch(() => {}); } let sourceLocalsSession; try { sourceLocalsSession = await launchToBreakpoint({ runtimeBackend: "simulated", project: launchProject, breakpointLine: 60 }); assert.strictEqual(sourceLocalsSession.stopped.body.threadId, 1); const localsStackRequest = sourceLocalsSession.client.send("stackTrace", { threadId: 1, startFrame: 0, levels: 1 }); const localsStack = (await sourceLocalsSession.client.response(localsStackRequest, "stackTrace")) .body.stackFrames; assert.strictEqual(localsStack[0].line, 60); const localsScopesRequest = sourceLocalsSession.client.send("scopes", { frameId: localsStack[0].id }); const localsScopes = (await sourceLocalsSession.client.response(localsScopesRequest, "scopes")) .body.scopes; const sourceLocalsRef = localsScopes.find((scope) => scope.name === "Source Locals") .variablesReference; const sourceLocalsRequest = sourceLocalsSession.client.send("variables", { variablesReference: sourceLocalsRef }); const sourceLocals = ( await sourceLocalsSession.client.response(sourceLocalsRequest, "variables") ).body.variables; assert( sourceLocals.some( (variable) => variable.name === "linux" && String(variable.value).includes("TaskHandle") && String(variable.value).includes("compile-linux") && String(variable.value).includes("virtual_thread_id = 2") ) ); assert( sourceLocals.some((variable) => variable.name === "linux_thread" && variable.value === "2") ); assert( sourceLocals.some( (variable) => variable.name === "linux_artifact" && String(variable.value).includes("Artifact") ) ); } finally { if (sourceLocalsSession) { await sourceLocalsSession.client.close().catch(() => {}); } } let wasmLocalsSession; try { wasmLocalsSession = await launchToBreakpoint({ runtimeBackend: "simulated", project: launchProject, breakpointLine: 31 }); assert.strictEqual(wasmLocalsSession.stopped.body.threadId, 1); const wasmStackRequest = wasmLocalsSession.client.send("stackTrace", { threadId: 1, startFrame: 0, levels: 1 }); const wasmStack = (await wasmLocalsSession.client.response(wasmStackRequest, "stackTrace")) .body.stackFrames; assert.strictEqual(wasmStack[0].line, 31); const wasmScopesRequest = wasmLocalsSession.client.send("scopes", { frameId: wasmStack[0].id }); const wasmScopes = (await wasmLocalsSession.client.response(wasmScopesRequest, "scopes")).body .scopes; const wasmLocalsRef = wasmScopes.find((scope) => scope.name === "Wasm Frame Locals") .variablesReference; const wasmLocalsRequest = wasmLocalsSession.client.send("variables", { variablesReference: wasmLocalsRef }); const wasmLocals = (await wasmLocalsSession.client.response(wasmLocalsRequest, "variables")) .body.variables; assert( wasmLocals.some( (variable) => variable.name === "wasm_local_0" && String(variable.value).includes("41") && variable.type === "wasm-frame-local" ), "DAP variables must expose Wasmtime frame-local values from the product node runtime" ); } finally { if (wasmLocalsSession) { await wasmLocalsSession.client.close().catch(() => {}); } } let liveCoordinator; let liveWorker; let liveSession; try { liveCoordinator = await startCoordinator(); liveSession = await launchToBreakpoint({ runtimeBackend: "live-services", operatorEndpoint: liveCoordinator.listen, project: launchProject, breakpointLines: [12, 22] }); assert.strictEqual(liveSession.stopped.body.threadId, 1); const liveStackRequest = liveSession.client.send("stackTrace", { threadId: 1, startFrame: 0, levels: 1 }); const liveStack = (await liveSession.client.response(liveStackRequest, "stackTrace")).body .stackFrames; assert.strictEqual(liveStack[0].line, 12); assert.strictEqual(liveStack[0].source.path, launchSource); const liveScopesRequest = liveSession.client.send("scopes", { frameId: liveStack[0].id }); const liveScopes = (await liveSession.client.response(liveScopesRequest, "scopes")).body.scopes; const liveRuntimeRef = liveScopes.find((scope) => scope.name === "Disasmer Runtime") .variablesReference; const liveOutputRef = liveScopes.find((scope) => scope.name === "Recent Output") .variablesReference; const liveRuntimeRequest = liveSession.client.send("variables", { variablesReference: liveRuntimeRef }); const liveRuntime = (await liveSession.client.response(liveRuntimeRequest, "variables")).body .variables; assert( liveRuntime.some( (variable) => variable.name === "runtime_backend" && variable.value === "LiveServices" ) ); assert( liveRuntime.some( (variable) => variable.name === "coordinator_task_events" && variable.value === 0 ) ); assert( liveRuntime.some( (variable) => variable.name === "command_status" && String(variable.value).includes("coordinator-side virtual process started") ) ); const liveOutputRequest = liveSession.client.send("variables", { variablesReference: liveOutputRef }); const liveOutput = (await liveSession.client.response(liveOutputRequest, "variables")).body .variables; assert( liveOutput.some((variable) => String(variable.value).includes("Command-capability virtual task") ) ); liveWorker = await startExplicitWorker(liveCoordinator.listen); const liveContinueRequest = liveSession.client.send("continue", { threadId: 1 }); await liveSession.client.response(liveContinueRequest, "continue"); await liveSession.client.waitFor( (message) => message.type === "event" && message.event === "continued" ); const liveTaskStopped = await liveSession.client.waitFor( (message) => message.type === "event" && message.event === "stopped" && message.body.reason === "breakpoint" && message.body.threadId === 2 ); assert.strictEqual(liveTaskStopped.body.threadId, 2); const liveTaskStackRequest = liveSession.client.send("stackTrace", { threadId: 2, startFrame: 0, levels: 1 }); const liveTaskStack = (await liveSession.client.response(liveTaskStackRequest, "stackTrace")) .body.stackFrames; assert.strictEqual(liveTaskStack[0].line, 22); const liveTaskScopesRequest = liveSession.client.send("scopes", { frameId: liveTaskStack[0].id }); const liveTaskScopes = (await liveSession.client.response(liveTaskScopesRequest, "scopes")) .body.scopes; const liveTaskRuntimeRef = liveTaskScopes.find((scope) => scope.name === "Disasmer Runtime") .variablesReference; const liveTaskOutputRef = liveTaskScopes.find((scope) => scope.name === "Recent Output") .variablesReference; const liveTaskRuntimeRequest = liveSession.client.send("variables", { variablesReference: liveTaskRuntimeRef }); const liveTaskRuntime = (await liveSession.client.response(liveTaskRuntimeRequest, "variables")) .body.variables; assert( liveTaskRuntime.some( (variable) => variable.name === "coordinator_task_events" && variable.value === 1 ) ); assert( liveTaskRuntime.some( (variable) => variable.name === "command_status" && String(variable.value).includes("completed through live services") ) ); assert(liveTaskRuntime.some((variable) => variable.name === "stdout_tail")); assert(liveTaskRuntime.some((variable) => variable.name === "stderr_tail")); const liveTaskOutputRequest = liveSession.client.send("variables", { variablesReference: liveTaskOutputRef }); const liveTaskOutput = (await liveSession.client.response(liveTaskOutputRequest, "variables")) .body.variables; assert(liveTaskOutput.some((variable) => variable.name === "stdout_tail")); assert(liveTaskOutput.some((variable) => variable.name === "stderr_tail")); assert( liveTaskOutput.some((variable) => String(variable.value).includes("attached node completed task") ) ); } finally { if (liveSession) { await liveSession.client.close().catch(() => {}); } killChild(liveWorker); killChild(liveCoordinator && liveCoordinator.child); } const failingProject = createFailingProject(); let failedSession; try { failedSession = await launchToBreakpoint({ runtimeBackend: "local-services", breakpointLine: 42, project: failingProject }); assert.strictEqual(failedSession.stopped.body.threadId, 2); const failedStackRequest = failedSession.client.send("stackTrace", { threadId: 2, startFrame: 0, levels: 1 }); const failedStack = (await failedSession.client.response(failedStackRequest, "stackTrace")).body .stackFrames; assert.strictEqual(failedStack.length, 1); assert.match(failedStack[0].name, /compile linux::run/); const failedScopesRequest = failedSession.client.send("scopes", { frameId: failedStack[0].id }); const failedScopes = (await failedSession.client.response(failedScopesRequest, "scopes")).body .scopes; const failedRuntimeRef = failedScopes.find((scope) => scope.name === "Disasmer Runtime") .variablesReference; const failedOutputRef = failedScopes.find((scope) => scope.name === "Recent Output") .variablesReference; const failedRuntimeRequest = failedSession.client.send("variables", { variablesReference: failedRuntimeRef }); const failedRuntime = (await failedSession.client.response(failedRuntimeRequest, "variables")) .body.variables; assert( failedRuntime.some( (variable) => variable.name === "command_status" && String(variable.value).includes("failed through local services") ) ); assert( failedRuntime.some( (variable) => variable.name === "state" && String(variable.value).includes("Failed") ) ); const failedOutputRequest = failedSession.client.send("variables", { variablesReference: failedOutputRef }); const failedOutput = (await failedSession.client.response(failedOutputRequest, "variables")).body .variables; assert( failedOutput.some((variable) => String(variable.value).includes("attached node failed task")) ); const failedRestartRequest = failedSession.client.send("restartFrame", { frameId: failedStack[0].id, sourceEdit: { compatibility: "compatible" } }); await failedSession.client.response(failedRestartRequest, "restartFrame"); await failedSession.client.waitFor( (message) => message.type === "event" && message.event === "output" && String(message.body.output).includes("Restarted failed task") ); const failedRuntimeAfterRestartRequest = failedSession.client.send("variables", { variablesReference: failedRuntimeRef }); const failedRuntimeAfterRestart = ( await failedSession.client.response(failedRuntimeAfterRestartRequest, "variables") ).body.variables; assert( failedRuntimeAfterRestart.some( (variable) => variable.name === "command_status" && String(variable.value).includes("failed task restarted") ) ); const failedOutputAfterRestartRequest = failedSession.client.send("variables", { variablesReference: failedOutputRef }); const failedOutputAfterRestart = ( await failedSession.client.response(failedOutputAfterRestartRequest, "variables") ).body.variables; assert( failedOutputAfterRestart.some((variable) => String(variable.value).includes("task restarted from VFS checkpoint") ) ); } finally { if (failedSession) { await failedSession.client.close().catch(() => {}); } fs.rmSync(failingProject, { recursive: true, force: true }); } const mainSession = await launchToBreakpoint({ runtimeBackend: "local-services", project: launchProject, breakpointLine: 42 }); assert.strictEqual(mainSession.stopped.body.threadId, 1); const mainStackRequest = mainSession.client.send("stackTrace", { threadId: 1, startFrame: 0, levels: 1 }); const mainStack = (await mainSession.client.response(mainStackRequest, "stackTrace")) .body.stackFrames; assert.strictEqual(mainStack.length, 1); assert.match(mainStack[0].name, /build virtual process::run/); assert.strictEqual(mainStack[0].line, 42); assert.strictEqual(mainStack[0].source.path, launchSource); assert.strictEqual(mainStack[0].source.sourceReference || 0, 0); const mainSourceRequest = mainSession.client.send("source", { source: mainStack[0].source }); const mainSource = (await mainSession.client.response(mainSourceRequest, "source")).body; assert.match(mainSource.content, /build/); const mainScopesRequest = mainSession.client.send("scopes", { frameId: mainStack[0].id }); const mainScopes = (await mainSession.client.response(mainScopesRequest, "scopes")).body.scopes; const mainRuntimeRef = mainScopes.find((scope) => scope.name === "Disasmer Runtime") .variablesReference; const mainRuntimeRequest = mainSession.client.send("variables", { variablesReference: mainRuntimeRef }); const mainRuntime = (await mainSession.client.response(mainRuntimeRequest, "variables")).body .variables; assert( mainRuntime.some( (variable) => variable.name === "runtime_backend" && variable.value === "LocalServices" ) ); assert( mainRuntime.some( (variable) => variable.name === "coordinator_task_events" && variable.value === 1 ) ); await mainSession.client.close(); const multiMainSession = await launchToBreakpoint({ runtimeBackend: "local-services", project: launchProject, breakpointLines: [42, 43] }); assert.strictEqual(multiMainSession.stopped.body.threadId, 1); const firstMainStackRequest = multiMainSession.client.send("stackTrace", { threadId: 1, startFrame: 0, levels: 1 }); const firstMainStack = (await multiMainSession.client.response(firstMainStackRequest, "stackTrace")) .body.stackFrames; assert.strictEqual(firstMainStack[0].line, 42); const continueMainRequest = multiMainSession.client.send("continue", { threadId: 1 }); await multiMainSession.client.response(continueMainRequest, "continue"); await multiMainSession.client.waitFor( (message) => message.type === "event" && message.event === "continued" ); const secondMainStop = await multiMainSession.client.waitFor( (message) => message.type === "event" && message.event === "stopped" && message.body.reason === "breakpoint" ); assert.strictEqual(secondMainStop.body.threadId, 1); const secondMainStackRequest = multiMainSession.client.send("stackTrace", { threadId: 1, startFrame: 0, levels: 1 }); const secondMainStack = ( await multiMainSession.client.response(secondMainStackRequest, "stackTrace") ).body.stackFrames; assert.strictEqual(secondMainStack[0].line, 43); await multiMainSession.client.close(); console.log("DAP smoke passed"); })().catch((err) => { console.error(err.stack || err.message); process.exit(1); });