agent-dbg 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.bin/ndbg +0 -0
- package/.claude/settings.local.json +21 -0
- package/.claude/skills/ndbg-debugger/ndbg-debugger/SKILL.md +116 -0
- package/.claude/skills/ndbg-debugger/ndbg-debugger/references/commands.md +173 -0
- package/CLAUDE.md +43 -0
- package/PROGRESS.md +261 -0
- package/README.md +67 -0
- package/biome.json +41 -0
- package/ndbg-spec.md +958 -0
- package/package.json +30 -0
- package/src/cdp/client.ts +198 -0
- package/src/cdp/types.ts +16 -0
- package/src/cli/parser.ts +287 -0
- package/src/cli/registry.ts +7 -0
- package/src/cli/types.ts +24 -0
- package/src/commands/attach.ts +47 -0
- package/src/commands/blackbox-ls.ts +38 -0
- package/src/commands/blackbox-rm.ts +57 -0
- package/src/commands/blackbox.ts +48 -0
- package/src/commands/break-ls.ts +57 -0
- package/src/commands/break-rm.ts +40 -0
- package/src/commands/break-toggle.ts +42 -0
- package/src/commands/break.ts +145 -0
- package/src/commands/breakable.ts +69 -0
- package/src/commands/catch.ts +38 -0
- package/src/commands/console.ts +61 -0
- package/src/commands/continue.ts +46 -0
- package/src/commands/eval.ts +70 -0
- package/src/commands/exceptions.ts +61 -0
- package/src/commands/hotpatch.ts +67 -0
- package/src/commands/launch.ts +69 -0
- package/src/commands/logpoint.ts +78 -0
- package/src/commands/pause.ts +46 -0
- package/src/commands/props.ts +77 -0
- package/src/commands/restart-frame.ts +36 -0
- package/src/commands/run-to.ts +70 -0
- package/src/commands/scripts.ts +57 -0
- package/src/commands/search.ts +73 -0
- package/src/commands/sessions.ts +71 -0
- package/src/commands/set-return.ts +49 -0
- package/src/commands/set.ts +61 -0
- package/src/commands/source.ts +59 -0
- package/src/commands/sourcemap.ts +66 -0
- package/src/commands/stack.ts +64 -0
- package/src/commands/state.ts +124 -0
- package/src/commands/status.ts +57 -0
- package/src/commands/step.ts +50 -0
- package/src/commands/stop.ts +27 -0
- package/src/commands/vars.ts +71 -0
- package/src/daemon/client.ts +147 -0
- package/src/daemon/entry.ts +242 -0
- package/src/daemon/paths.ts +26 -0
- package/src/daemon/server.ts +185 -0
- package/src/daemon/session-blackbox.ts +41 -0
- package/src/daemon/session-breakpoints.ts +492 -0
- package/src/daemon/session-execution.ts +121 -0
- package/src/daemon/session-inspection.ts +701 -0
- package/src/daemon/session-mutation.ts +197 -0
- package/src/daemon/session-state.ts +258 -0
- package/src/daemon/session.ts +938 -0
- package/src/daemon/spawn.ts +53 -0
- package/src/formatter/errors.ts +15 -0
- package/src/formatter/source.ts +74 -0
- package/src/formatter/stack.ts +70 -0
- package/src/formatter/values.ts +269 -0
- package/src/formatter/variables.ts +20 -0
- package/src/main.ts +45 -0
- package/src/protocol/messages.ts +316 -0
- package/src/refs/ref-table.ts +120 -0
- package/src/refs/resolver.ts +24 -0
- package/src/sourcemap/resolver.ts +318 -0
- package/tests/fixtures/async-app.js +34 -0
- package/tests/fixtures/console-app.js +12 -0
- package/tests/fixtures/error-app.js +28 -0
- package/tests/fixtures/exception-app.js +6 -0
- package/tests/fixtures/inspect-app.js +10 -0
- package/tests/fixtures/mutation-app.js +9 -0
- package/tests/fixtures/simple-app.js +50 -0
- package/tests/fixtures/step-app.js +13 -0
- package/tests/fixtures/ts-app/src/app.ts +21 -0
- package/tests/fixtures/ts-app/tsconfig.json +14 -0
- package/tests/integration/blackbox.test.ts +135 -0
- package/tests/integration/break-extras.test.ts +241 -0
- package/tests/integration/breakpoint.test.ts +217 -0
- package/tests/integration/console.test.ts +275 -0
- package/tests/integration/execution.test.ts +247 -0
- package/tests/integration/inspection.test.ts +311 -0
- package/tests/integration/mutation.test.ts +178 -0
- package/tests/integration/session.test.ts +223 -0
- package/tests/integration/source.test.ts +209 -0
- package/tests/integration/sourcemap.test.ts +214 -0
- package/tests/integration/state.test.ts +208 -0
- package/tests/unit/cdp-client.test.ts +422 -0
- package/tests/unit/daemon.test.ts +286 -0
- package/tests/unit/formatter.test.ts +716 -0
- package/tests/unit/parser.test.ts +105 -0
- package/tests/unit/refs.test.ts +383 -0
- package/tests/unit/sourcemap.test.ts +236 -0
- package/tsconfig.json +32 -0
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { DebugSession } from "../../src/daemon/session.ts";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Polls until the session reaches the expected state, or times out.
|
|
6
|
+
*/
|
|
7
|
+
async function waitForState(
|
|
8
|
+
session: DebugSession,
|
|
9
|
+
state: "idle" | "running" | "paused",
|
|
10
|
+
timeoutMs = 5000,
|
|
11
|
+
): Promise<void> {
|
|
12
|
+
const deadline = Date.now() + timeoutMs;
|
|
13
|
+
while (session.sessionState !== state && Date.now() < deadline) {
|
|
14
|
+
await Bun.sleep(50);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
describe("Console capture", () => {
|
|
19
|
+
test("captures console.log, console.warn, and console.error messages", async () => {
|
|
20
|
+
const session = new DebugSession("test-console-capture");
|
|
21
|
+
try {
|
|
22
|
+
await session.launch(["node", "tests/fixtures/console-app.js"], { brk: true });
|
|
23
|
+
await waitForState(session, "paused");
|
|
24
|
+
|
|
25
|
+
// Continue to the debugger statement — this will execute the console calls
|
|
26
|
+
await session.continue();
|
|
27
|
+
await waitForState(session, "paused", 5000);
|
|
28
|
+
|
|
29
|
+
// Allow a small delay for console events to be processed
|
|
30
|
+
await Bun.sleep(100);
|
|
31
|
+
|
|
32
|
+
const messages = session.getConsoleMessages();
|
|
33
|
+
expect(messages.length).toBeGreaterThanOrEqual(3);
|
|
34
|
+
|
|
35
|
+
const levels = messages.map((m) => m.level);
|
|
36
|
+
expect(levels).toContain("log");
|
|
37
|
+
expect(levels).toContain("warning");
|
|
38
|
+
expect(levels).toContain("error");
|
|
39
|
+
|
|
40
|
+
// Check that text is captured
|
|
41
|
+
const logMsg = messages.find((m) => m.text.includes("hello from app"));
|
|
42
|
+
expect(logMsg).toBeDefined();
|
|
43
|
+
|
|
44
|
+
const warnMsg = messages.find((m) => m.text.includes("warning message"));
|
|
45
|
+
expect(warnMsg).toBeDefined();
|
|
46
|
+
|
|
47
|
+
const errMsg = messages.find((m) => m.text.includes("error message"));
|
|
48
|
+
expect(errMsg).toBeDefined();
|
|
49
|
+
} finally {
|
|
50
|
+
await session.stop();
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("captures console messages with objects", async () => {
|
|
55
|
+
const session = new DebugSession("test-console-objects");
|
|
56
|
+
try {
|
|
57
|
+
await session.launch(["node", "tests/fixtures/console-app.js"], { brk: true });
|
|
58
|
+
await waitForState(session, "paused");
|
|
59
|
+
|
|
60
|
+
await session.continue();
|
|
61
|
+
await waitForState(session, "paused", 5000);
|
|
62
|
+
await Bun.sleep(100);
|
|
63
|
+
|
|
64
|
+
const messages = session.getConsoleMessages();
|
|
65
|
+
const objectMsg = messages.find((m) => m.text.includes("object:"));
|
|
66
|
+
expect(objectMsg).toBeDefined();
|
|
67
|
+
// The object should contain something about key/value
|
|
68
|
+
expect(objectMsg?.text).toContain("key");
|
|
69
|
+
} finally {
|
|
70
|
+
await session.stop();
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("filters console messages by level", async () => {
|
|
75
|
+
const session = new DebugSession("test-console-filter");
|
|
76
|
+
try {
|
|
77
|
+
await session.launch(["node", "tests/fixtures/console-app.js"], { brk: true });
|
|
78
|
+
await waitForState(session, "paused");
|
|
79
|
+
|
|
80
|
+
await session.continue();
|
|
81
|
+
await waitForState(session, "paused", 5000);
|
|
82
|
+
await Bun.sleep(100);
|
|
83
|
+
|
|
84
|
+
const errorMessages = session.getConsoleMessages({ level: "error" });
|
|
85
|
+
expect(errorMessages.length).toBeGreaterThanOrEqual(1);
|
|
86
|
+
for (const msg of errorMessages) {
|
|
87
|
+
expect(msg.level).toBe("error");
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const warnMessages = session.getConsoleMessages({ level: "warning" });
|
|
91
|
+
expect(warnMessages.length).toBeGreaterThanOrEqual(1);
|
|
92
|
+
for (const msg of warnMessages) {
|
|
93
|
+
expect(msg.level).toBe("warning");
|
|
94
|
+
}
|
|
95
|
+
} finally {
|
|
96
|
+
await session.stop();
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("console --since returns only last N entries", async () => {
|
|
101
|
+
const session = new DebugSession("test-console-since");
|
|
102
|
+
try {
|
|
103
|
+
await session.launch(["node", "tests/fixtures/console-app.js"], { brk: true });
|
|
104
|
+
await waitForState(session, "paused");
|
|
105
|
+
|
|
106
|
+
await session.continue();
|
|
107
|
+
await waitForState(session, "paused", 5000);
|
|
108
|
+
await Bun.sleep(100);
|
|
109
|
+
|
|
110
|
+
const allMessages = session.getConsoleMessages();
|
|
111
|
+
expect(allMessages.length).toBeGreaterThanOrEqual(3);
|
|
112
|
+
|
|
113
|
+
const lastTwo = session.getConsoleMessages({ since: 2 });
|
|
114
|
+
expect(lastTwo.length).toBe(2);
|
|
115
|
+
|
|
116
|
+
// The last two messages should match the tail of all messages
|
|
117
|
+
expect(lastTwo[0]?.text).toBe(allMessages[allMessages.length - 2]?.text);
|
|
118
|
+
expect(lastTwo[1]?.text).toBe(allMessages[allMessages.length - 1]?.text);
|
|
119
|
+
} finally {
|
|
120
|
+
await session.stop();
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("console --clear clears the buffer after returning", async () => {
|
|
125
|
+
const session = new DebugSession("test-console-clear");
|
|
126
|
+
try {
|
|
127
|
+
await session.launch(["node", "tests/fixtures/console-app.js"], { brk: true });
|
|
128
|
+
await waitForState(session, "paused");
|
|
129
|
+
|
|
130
|
+
await session.continue();
|
|
131
|
+
await waitForState(session, "paused", 5000);
|
|
132
|
+
await Bun.sleep(100);
|
|
133
|
+
|
|
134
|
+
const messages = session.getConsoleMessages({ clear: true });
|
|
135
|
+
expect(messages.length).toBeGreaterThanOrEqual(3);
|
|
136
|
+
|
|
137
|
+
// After clearing, buffer should be empty
|
|
138
|
+
const afterClear = session.getConsoleMessages();
|
|
139
|
+
expect(afterClear.length).toBe(0);
|
|
140
|
+
} finally {
|
|
141
|
+
await session.stop();
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("clearConsole() empties the buffer", async () => {
|
|
146
|
+
const session = new DebugSession("test-console-clear-method");
|
|
147
|
+
try {
|
|
148
|
+
await session.launch(["node", "tests/fixtures/console-app.js"], { brk: true });
|
|
149
|
+
await waitForState(session, "paused");
|
|
150
|
+
|
|
151
|
+
await session.continue();
|
|
152
|
+
await waitForState(session, "paused", 5000);
|
|
153
|
+
await Bun.sleep(100);
|
|
154
|
+
|
|
155
|
+
expect(session.getConsoleMessages().length).toBeGreaterThan(0);
|
|
156
|
+
session.clearConsole();
|
|
157
|
+
expect(session.getConsoleMessages().length).toBe(0);
|
|
158
|
+
} finally {
|
|
159
|
+
await session.stop();
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test("stop() clears console and exception buffers", async () => {
|
|
164
|
+
const session = new DebugSession("test-console-stop-clears");
|
|
165
|
+
try {
|
|
166
|
+
await session.launch(["node", "tests/fixtures/console-app.js"], { brk: true });
|
|
167
|
+
await waitForState(session, "paused");
|
|
168
|
+
|
|
169
|
+
await session.continue();
|
|
170
|
+
await waitForState(session, "paused", 5000);
|
|
171
|
+
await Bun.sleep(100);
|
|
172
|
+
|
|
173
|
+
expect(session.getConsoleMessages().length).toBeGreaterThan(0);
|
|
174
|
+
} finally {
|
|
175
|
+
await session.stop();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// After stop, buffers should be cleared
|
|
179
|
+
expect(session.getConsoleMessages().length).toBe(0);
|
|
180
|
+
expect(session.getExceptions().length).toBe(0);
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
describe("Exception capture", () => {
|
|
185
|
+
test("captures uncaught exceptions", async () => {
|
|
186
|
+
const session = new DebugSession("test-exception-capture");
|
|
187
|
+
try {
|
|
188
|
+
await session.launch(["node", "tests/fixtures/exception-app.js"], { brk: true });
|
|
189
|
+
await waitForState(session, "paused");
|
|
190
|
+
|
|
191
|
+
// Continue — the setTimeout will throw after 50ms and the process will crash
|
|
192
|
+
await session.continue();
|
|
193
|
+
// Wait for the process to exit (state becomes idle)
|
|
194
|
+
await waitForState(session, "idle", 5000);
|
|
195
|
+
await Bun.sleep(100);
|
|
196
|
+
|
|
197
|
+
const exceptions = session.getExceptions();
|
|
198
|
+
expect(exceptions.length).toBeGreaterThanOrEqual(1);
|
|
199
|
+
|
|
200
|
+
const entry = exceptions[0];
|
|
201
|
+
expect(entry).toBeDefined();
|
|
202
|
+
expect(entry?.text).toContain("Uncaught");
|
|
203
|
+
expect(entry?.description).toContain("uncaught!");
|
|
204
|
+
} finally {
|
|
205
|
+
await session.stop();
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test("exception entries have timestamp", async () => {
|
|
210
|
+
const session = new DebugSession("test-exception-timestamp");
|
|
211
|
+
const before = Date.now();
|
|
212
|
+
try {
|
|
213
|
+
await session.launch(["node", "tests/fixtures/exception-app.js"], { brk: true });
|
|
214
|
+
await waitForState(session, "paused");
|
|
215
|
+
|
|
216
|
+
await session.continue();
|
|
217
|
+
await waitForState(session, "idle", 5000);
|
|
218
|
+
await Bun.sleep(100);
|
|
219
|
+
|
|
220
|
+
const exceptions = session.getExceptions();
|
|
221
|
+
expect(exceptions.length).toBeGreaterThanOrEqual(1);
|
|
222
|
+
|
|
223
|
+
const entry = exceptions[0];
|
|
224
|
+
expect(entry).toBeDefined();
|
|
225
|
+
expect(entry!.timestamp).toBeGreaterThanOrEqual(before);
|
|
226
|
+
expect(entry!.timestamp).toBeLessThanOrEqual(Date.now());
|
|
227
|
+
} finally {
|
|
228
|
+
await session.stop();
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
test("exceptions --since returns only last N entries", async () => {
|
|
233
|
+
const session = new DebugSession("test-exception-since");
|
|
234
|
+
try {
|
|
235
|
+
await session.launch(["node", "tests/fixtures/exception-app.js"], { brk: true });
|
|
236
|
+
await waitForState(session, "paused");
|
|
237
|
+
|
|
238
|
+
await session.continue();
|
|
239
|
+
await waitForState(session, "idle", 5000);
|
|
240
|
+
await Bun.sleep(100);
|
|
241
|
+
|
|
242
|
+
const allExceptions = session.getExceptions();
|
|
243
|
+
expect(allExceptions.length).toBeGreaterThanOrEqual(1);
|
|
244
|
+
|
|
245
|
+
const lastOne = session.getExceptions({ since: 1 });
|
|
246
|
+
expect(lastOne.length).toBe(1);
|
|
247
|
+
expect(lastOne[0]?.text).toBe(allExceptions[allExceptions.length - 1]?.text);
|
|
248
|
+
} finally {
|
|
249
|
+
await session.stop();
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
test("console messages have timestamps", async () => {
|
|
254
|
+
const session = new DebugSession("test-console-timestamps");
|
|
255
|
+
const before = Date.now();
|
|
256
|
+
try {
|
|
257
|
+
await session.launch(["node", "tests/fixtures/console-app.js"], { brk: true });
|
|
258
|
+
await waitForState(session, "paused");
|
|
259
|
+
|
|
260
|
+
await session.continue();
|
|
261
|
+
await waitForState(session, "paused", 5000);
|
|
262
|
+
await Bun.sleep(100);
|
|
263
|
+
|
|
264
|
+
const messages = session.getConsoleMessages();
|
|
265
|
+
expect(messages.length).toBeGreaterThan(0);
|
|
266
|
+
|
|
267
|
+
for (const msg of messages) {
|
|
268
|
+
expect(msg.timestamp).toBeGreaterThanOrEqual(before);
|
|
269
|
+
expect(msg.timestamp).toBeLessThanOrEqual(Date.now());
|
|
270
|
+
}
|
|
271
|
+
} finally {
|
|
272
|
+
await session.stop();
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
});
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { DebugSession } from "../../src/daemon/session.ts";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Polls until the session reaches the expected state, or times out.
|
|
6
|
+
*/
|
|
7
|
+
async function waitForState(
|
|
8
|
+
session: DebugSession,
|
|
9
|
+
state: "idle" | "running" | "paused",
|
|
10
|
+
timeoutMs = 5000,
|
|
11
|
+
): Promise<void> {
|
|
12
|
+
const deadline = Date.now() + timeoutMs;
|
|
13
|
+
while (session.sessionState !== state && Date.now() < deadline) {
|
|
14
|
+
await Bun.sleep(50);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
describe("Execution control", () => {
|
|
19
|
+
test("continue resumes and process finishes", async () => {
|
|
20
|
+
const session = new DebugSession("test-exec-continue");
|
|
21
|
+
try {
|
|
22
|
+
await session.launch(["node", "tests/fixtures/step-app.js"], { brk: true });
|
|
23
|
+
await waitForState(session, "paused");
|
|
24
|
+
expect(session.sessionState).toBe("paused");
|
|
25
|
+
|
|
26
|
+
// Continue — the script should run to completion
|
|
27
|
+
await session.continue();
|
|
28
|
+
|
|
29
|
+
// The process should have finished (idle) or be finishing
|
|
30
|
+
await waitForState(session, "idle", 5000);
|
|
31
|
+
expect(["idle", "running"]).toContain(session.sessionState);
|
|
32
|
+
} finally {
|
|
33
|
+
await session.stop();
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("continue resumes and hits next breakpoint", async () => {
|
|
38
|
+
const session = new DebugSession("test-exec-continue-bp");
|
|
39
|
+
try {
|
|
40
|
+
await session.launch(["node", "tests/fixtures/step-app.js"], { brk: true });
|
|
41
|
+
await waitForState(session, "paused");
|
|
42
|
+
expect(session.sessionState).toBe("paused");
|
|
43
|
+
|
|
44
|
+
// Set a breakpoint on line 12 (const d = a + b + c;), CDP 0-based: 11
|
|
45
|
+
const cdp = session.cdp;
|
|
46
|
+
expect(cdp).not.toBeNull();
|
|
47
|
+
await cdp!.send("Debugger.setBreakpointByUrl", {
|
|
48
|
+
lineNumber: 11,
|
|
49
|
+
urlRegex: "step-app\\.js",
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Continue — should hit the breakpoint
|
|
53
|
+
await session.continue();
|
|
54
|
+
expect(session.sessionState).toBe("paused");
|
|
55
|
+
|
|
56
|
+
const status = session.getStatus();
|
|
57
|
+
expect(status.state).toBe("paused");
|
|
58
|
+
} finally {
|
|
59
|
+
await session.stop();
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("step over advances one line", async () => {
|
|
64
|
+
const session = new DebugSession("test-exec-step-over");
|
|
65
|
+
try {
|
|
66
|
+
await session.launch(["node", "tests/fixtures/step-app.js"], {
|
|
67
|
+
brk: true,
|
|
68
|
+
});
|
|
69
|
+
await waitForState(session, "paused");
|
|
70
|
+
|
|
71
|
+
const statusBefore = session.getStatus();
|
|
72
|
+
const lineBefore = statusBefore.pauseInfo?.line;
|
|
73
|
+
expect(lineBefore).toBeDefined();
|
|
74
|
+
|
|
75
|
+
// Step over — should advance to the next line
|
|
76
|
+
await session.step("over");
|
|
77
|
+
|
|
78
|
+
expect(session.sessionState).toBe("paused");
|
|
79
|
+
const statusAfter = session.getStatus();
|
|
80
|
+
expect(statusAfter.pauseInfo).toBeDefined();
|
|
81
|
+
expect(statusAfter.pauseInfo?.line).toBeDefined();
|
|
82
|
+
|
|
83
|
+
// The line should have changed (advanced by at least one)
|
|
84
|
+
if (lineBefore !== undefined && statusAfter.pauseInfo?.line !== undefined) {
|
|
85
|
+
expect(statusAfter.pauseInfo.line).toBeGreaterThan(lineBefore);
|
|
86
|
+
}
|
|
87
|
+
} finally {
|
|
88
|
+
await session.stop();
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("step into enters a function", async () => {
|
|
93
|
+
const session = new DebugSession("test-exec-step-into");
|
|
94
|
+
try {
|
|
95
|
+
await session.launch(["node", "tests/fixtures/step-app.js"], { brk: true });
|
|
96
|
+
await waitForState(session, "paused");
|
|
97
|
+
|
|
98
|
+
// Step to the helper() call line: const c = helper(a);
|
|
99
|
+
// Initial pause is at the first executable statement.
|
|
100
|
+
// Step over until we reach the line with helper() call.
|
|
101
|
+
// step-app.js line 11 (1-based) / 10 (0-based): const c = helper(a);
|
|
102
|
+
let currentLine = session.getStatus().pauseInfo?.line ?? 0;
|
|
103
|
+
while (currentLine < 10 && session.sessionState === "paused") {
|
|
104
|
+
await session.step("over");
|
|
105
|
+
currentLine = session.getStatus().pauseInfo?.line ?? currentLine;
|
|
106
|
+
}
|
|
107
|
+
expect(currentLine).toBe(10); // 0-based line for const c = helper(a);
|
|
108
|
+
|
|
109
|
+
// Now step into the function call. V8 may pause at sub-expressions
|
|
110
|
+
// before entering the function, so keep stepping into until the line
|
|
111
|
+
// moves to inside the helper function (0-based lines 3-5).
|
|
112
|
+
await session.step("into");
|
|
113
|
+
expect(session.sessionState).toBe("paused");
|
|
114
|
+
|
|
115
|
+
let line = session.getStatus().pauseInfo?.line;
|
|
116
|
+
// If we're still on the same line, step into once more
|
|
117
|
+
if (line !== undefined && line >= 10) {
|
|
118
|
+
await session.step("into");
|
|
119
|
+
line = session.getStatus().pauseInfo?.line;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
expect(line).toBeDefined();
|
|
123
|
+
// Should now be inside helper function body (0-based lines 3, 4, or 5)
|
|
124
|
+
if (line !== undefined) {
|
|
125
|
+
expect(line).toBeLessThan(10);
|
|
126
|
+
}
|
|
127
|
+
} finally {
|
|
128
|
+
await session.stop();
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("step out exits current function", async () => {
|
|
133
|
+
const session = new DebugSession("test-exec-step-out");
|
|
134
|
+
try {
|
|
135
|
+
await session.launch(["node", "tests/fixtures/step-app.js"], { brk: true });
|
|
136
|
+
await waitForState(session, "paused");
|
|
137
|
+
|
|
138
|
+
// Step to the helper() call and step into it
|
|
139
|
+
let currentLine = session.getStatus().pauseInfo?.line ?? 0;
|
|
140
|
+
while (currentLine < 10 && session.sessionState === "paused") {
|
|
141
|
+
await session.step("over");
|
|
142
|
+
currentLine = session.getStatus().pauseInfo?.line ?? currentLine;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Step into the function
|
|
146
|
+
await session.step("into");
|
|
147
|
+
let line = session.getStatus().pauseInfo?.line;
|
|
148
|
+
if (line !== undefined && line >= 10) {
|
|
149
|
+
await session.step("into");
|
|
150
|
+
line = session.getStatus().pauseInfo?.line;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
expect(session.sessionState).toBe("paused");
|
|
154
|
+
const lineInside = session.getStatus().pauseInfo?.line;
|
|
155
|
+
expect(lineInside).toBeDefined();
|
|
156
|
+
expect(lineInside).toBeLessThan(10); // inside helper
|
|
157
|
+
|
|
158
|
+
// Step out of the function
|
|
159
|
+
await session.step("out");
|
|
160
|
+
|
|
161
|
+
expect(session.sessionState).toBe("paused");
|
|
162
|
+
const statusOutside = session.getStatus();
|
|
163
|
+
expect(statusOutside.pauseInfo).toBeDefined();
|
|
164
|
+
// After stepping out, we should be back in the main scope
|
|
165
|
+
if (statusOutside.pauseInfo?.line !== undefined) {
|
|
166
|
+
expect(statusOutside.pauseInfo.line).toBeGreaterThanOrEqual(10);
|
|
167
|
+
}
|
|
168
|
+
} finally {
|
|
169
|
+
await session.stop();
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test("pause interrupts running process", async () => {
|
|
174
|
+
const session = new DebugSession("test-exec-pause");
|
|
175
|
+
try {
|
|
176
|
+
await session.launch(["node", "-e", "setInterval(() => {}, 100)"], { brk: false });
|
|
177
|
+
// Process should be running
|
|
178
|
+
expect(session.sessionState).toBe("running");
|
|
179
|
+
|
|
180
|
+
// Pause the running process
|
|
181
|
+
await session.pause();
|
|
182
|
+
|
|
183
|
+
expect(session.sessionState).toBe("paused");
|
|
184
|
+
const status = session.getStatus();
|
|
185
|
+
expect(status.state).toBe("paused");
|
|
186
|
+
} finally {
|
|
187
|
+
await session.stop();
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test("continue throws when not paused", async () => {
|
|
192
|
+
const session = new DebugSession("test-exec-continue-err");
|
|
193
|
+
try {
|
|
194
|
+
await session.launch(["node", "-e", "setInterval(() => {}, 100)"], { brk: false });
|
|
195
|
+
expect(session.sessionState).toBe("running");
|
|
196
|
+
|
|
197
|
+
await expect(session.continue()).rejects.toThrow("not paused");
|
|
198
|
+
} finally {
|
|
199
|
+
await session.stop();
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test("step throws when not paused", async () => {
|
|
204
|
+
const session = new DebugSession("test-exec-step-err");
|
|
205
|
+
try {
|
|
206
|
+
await session.launch(["node", "-e", "setInterval(() => {}, 100)"], { brk: false });
|
|
207
|
+
expect(session.sessionState).toBe("running");
|
|
208
|
+
|
|
209
|
+
await expect(session.step("over")).rejects.toThrow("not paused");
|
|
210
|
+
} finally {
|
|
211
|
+
await session.stop();
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test("pause throws when not running", async () => {
|
|
216
|
+
const session = new DebugSession("test-exec-pause-err");
|
|
217
|
+
try {
|
|
218
|
+
await session.launch(["node", "-e", "setTimeout(() => {}, 30000)"], { brk: true });
|
|
219
|
+
await waitForState(session, "paused");
|
|
220
|
+
|
|
221
|
+
await expect(session.pause()).rejects.toThrow("not running");
|
|
222
|
+
} finally {
|
|
223
|
+
await session.stop();
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
test("run-to stops at the specified line", async () => {
|
|
228
|
+
const session = new DebugSession("test-exec-run-to");
|
|
229
|
+
try {
|
|
230
|
+
await session.launch(["node", "tests/fixtures/step-app.js"], { brk: true });
|
|
231
|
+
await waitForState(session, "paused");
|
|
232
|
+
|
|
233
|
+
// Run to line 12 (const d = a + b + c;), CDP 0-based: 11
|
|
234
|
+
await session.runTo("step-app.js", 12);
|
|
235
|
+
|
|
236
|
+
expect(session.sessionState).toBe("paused");
|
|
237
|
+
const status = session.getStatus();
|
|
238
|
+
expect(status.pauseInfo).toBeDefined();
|
|
239
|
+
// CDP line is 0-based, so line 12 = index 11
|
|
240
|
+
if (status.pauseInfo?.line !== undefined) {
|
|
241
|
+
expect(status.pauseInfo.line).toBe(11);
|
|
242
|
+
}
|
|
243
|
+
} finally {
|
|
244
|
+
await session.stop();
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
});
|