Skip to content

Commit d1b7343

Browse files
authored
Implement ICorDebug integration for Run with Debug (#86)
1 parent bb42665 commit d1b7343

19 files changed

Lines changed: 1086 additions & 89 deletions

.github/agents/basicAgent.agent.md

Lines changed: 62 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ description: Used for general purpose NodeDev development
1616
4) Document newly added content or concepts in this `.github/agents/basicAgent.agent.md` file or any related documentation file.
1717
5) When the user corrects major mistakes done during your development, document them in this file to ensure it is never done again.
1818
6) You must always install playwright BEFORE trying to run the tests. build the projects and install playwright. If you struggle (take multiple iterations to do it), document the steps you took in this file to make it easier next time.
19+
7) **ALWAYS read the E2E testing documentation (`docs/e2e-testing.md`) BEFORE making any changes to E2E tests.** This documentation contains critical information about test patterns, selector strategies, and troubleshooting.
20+
8) **When encountering E2E test issues (timeouts, element not found, etc.), ALWAYS use the Playwright MCP tools** to take screenshots and inspect the page state before assuming the test or functionality is broken. Use `playwright-browser_snapshot` and `playwright-browser_take_screenshot` to validate element visibility and page state.
1921

2022
## Programming style
2123

@@ -39,10 +41,11 @@ NodeDev is a visual programming environment built with Blazor and Blazor.Diagram
3941

4042
### UI Structure
4143
The main UI consists of:
42-
- **AppBar**: Top toolbar with project controls (New, Open, Save, Options)
44+
- **AppBar**: Top toolbar with project controls (New, Open, Save, Options, Run, Run with Debug)
4345
- **ProjectExplorer**: Left panel showing project structure (classes, methods, properties)
4446
- **GraphCanvas**: Central canvas where nodes are placed and connected
4547
- **ClassExplorer**: Shows details of the currently selected class
48+
- **DebuggerConsolePanel**: Bottom panel with tabs for Console Output and Debug Callbacks
4649

4750
### Graph System
4851
- Uses Blazor.Diagrams library for visual node editing
@@ -128,27 +131,82 @@ Detailed topic-specific documentation is maintained in the `docs/` folder:
128131

129132
## Debugging Infrastructure
130133

134+
### Hard Debugging (ICorDebug)
135+
NodeDev now supports "Hard Debugging" via the ICorDebug API (.NET's unmanaged debugging interface). This provides low-level debugging capabilities including:
136+
- Process attachment and management
137+
- Debug event callbacks (process creation, module loading, thread creation, etc.)
138+
- Future support for breakpoints and step-through execution
139+
140+
**Running with Debug:**
141+
The UI provides two run modes:
142+
1. **Run** - Normal execution without debugger attachment
143+
2. **Run with Debug** - Executes with ICorDebug debugger attached
144+
145+
**Important**: "Run with Debug" requires successful debugger attachment. If attachment fails for any reason (DbgShim not found, CLR enumeration fails, etc.), the operation will fail with an error dialog showing the specific issue. There is no fallback to normal execution.
146+
147+
**Debug State Management:**
148+
- `Project.IsHardDebugging` - Boolean property indicating active debug session
149+
- `Project.DebuggedProcessId` - Process ID of debugged process (null when not debugging)
150+
- `Project.HardDebugStateChanged` - Observable stream for debug state changes (true when attached, false when detached)
151+
- `Project.DebugCallbacks` - Observable stream of `DebugCallbackEventArgs` for all debug events
152+
153+
**UI Visual Feedback:**
154+
- "Run with Debug" button changes color (warning) and shows PID when debugging
155+
- Button is disabled during active debug session
156+
- DebuggerConsolePanel shows two tabs:
157+
- "Console Output" - Standard output from the program
158+
- "Debug Callbacks" - Real-time debug events with timestamps
159+
160+
**Implementation Pattern:**
161+
```csharp
162+
// Running with debug in Project.cs
163+
try
164+
{
165+
var result = project.RunWithDebug(BuildOptions.Debug);
166+
}
167+
catch (InvalidOperationException ex)
168+
{
169+
// Handle debug attachment failure
170+
// Show error dialog to user with ex.Message
171+
Console.WriteLine($"Debug failed: {ex.Message}");
172+
}
173+
174+
// Subscribing to debug callbacks
175+
project.DebugCallbacks.Subscribe(callback => {
176+
Console.WriteLine($"{callback.CallbackType}: {callback.Description}");
177+
});
178+
179+
// Subscribing to debug state changes
180+
project.HardDebugStateChanged.Subscribe(isDebugging => {
181+
if (isDebugging)
182+
Console.WriteLine("Debugging started");
183+
else
184+
Console.WriteLine("Debugging stopped");
185+
});
186+
```
187+
131188
### Debugger Module (NodeDev.Core.Debugger)
132189
The debugging infrastructure is located in `src/NodeDev.Core/Debugger/` and provides ICorDebug API access via the ClrDebug NuGet package:
133190

134191
- **DbgShimResolver**: Cross-platform resolution for the dbgshim library from NuGet packages or system paths
135192
- **DebugSessionEngine**: Main debugging engine with process launch, attach, and callback handling
136-
- **ManagedDebuggerCallbacks**: Implementation of ICorDebugManagedCallback interfaces
193+
- **ManagedDebuggerCallbacks**: Implementation of ICorDebugManagedCallback interfaces via ClrDebug
137194
- **DebugEngineException**: Custom exception type for debugging errors
138195

139196
**Dependencies:**
140197
- `ClrDebug` (v0.3.4): C# wrappers for the unmanaged ICorDebug API
141198
- `Microsoft.Diagnostics.DbgShim` (v9.0.652701): Native dbgshim library for all platforms
142199

143200
### ScriptRunner
144-
NodeDev includes a separate console application called **ScriptRunner** that serves as the target process for debugging. This architecture is being developed to support "Hard Debugging" via the ICorDebug API (.NET's unmanaged debugging interface).
201+
NodeDev includes a separate console application called **ScriptRunner** that serves as the target process for debugging. This architecture supports "Hard Debugging" via the ICorDebug API.
145202

146203
**Architecture:**
147204
- **Host Process**: The Visual IDE (NodeDev.Blazor.Server or NodeDev.Blazor.MAUI)
148205
- **Target Process**: ScriptRunner - a separate console application that executes the user's compiled code
149206

150207
**ScriptRunner Features:**
151208
- Accepts a DLL path as command-line argument
209+
- Optional `--wait-for-debugger` flag to pause execution until debugger attaches
152210
- Loads assemblies using `Assembly.LoadFrom()`
153211
- Finds and invokes entry points:
154212
- Static `Program.Main` method (in any namespace)
@@ -160,12 +218,5 @@ NodeDev includes a separate console application called **ScriptRunner** that ser
160218
- ScriptRunner is automatically built with NodeDev.Core
161219
- MSBuild targets copy ScriptRunner to the output directory of dependent projects
162220
- The `Project.Run()` method automatically locates and launches ScriptRunner
221+
- The `Project.RunWithDebug()` method launches ScriptRunner and attaches debugger
163222
- The `Project.GetScriptRunnerPath()` method returns the ScriptRunner location for debugging infrastructure
164-
165-
**Future: ICorDebug Integration**
166-
This infrastructure prepares NodeDev for implementing advanced debugging features:
167-
- Breakpoints in visual graphs
168-
- Step-through execution
169-
- Variable inspection at runtime
170-
- Exception handling and catching
171-
- Live debugging across process boundaries

.github/workflows/dotnet.yml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313
build:
1414
name: Build the entire solution
1515
uses: ./.github/workflows/workflow-build.yml
16-
16+
1717
tests:
1818
name: Run Unit Tests
1919
needs: build
@@ -24,3 +24,12 @@ jobs:
2424
needs: build
2525
uses: ./.github/workflows/workflow-e2e-tests.yml
2626

27+
tests-windows:
28+
name: Run Unit Tests (Windows)
29+
needs: build
30+
uses: ./.github/workflows/workflow-tests-windows.yml
31+
32+
e2e-tests-windows:
33+
name: Run End To End Tests (Windows)
34+
needs: build
35+
uses: ./.github/workflows/workflow-e2e-tests-windows.yml

.github/workflows/workflow-build.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,13 @@ jobs:
1717
with:
1818
dotnet-version: 10.0.x
1919

20-
- name: Restore .NET dependencies
20+
- name: Install workloads
2121
working-directory: ./src
22-
run: dotnet restore
22+
run: dotnet workload restore
2323

2424
- name: Build
2525
working-directory: ./src
26-
run: dotnet build --no-restore
26+
run: dotnet build -c Release
2727

2828
- name: Upload Build Artifact
2929
uses: actions/upload-artifact@v4

.github/workflows/workflow-e2e-tests-windows.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,19 @@ jobs:
2020

2121
- name: Build Necessary for Playwright
2222
working-directory: ./src/NodeDev.EndToEndTests
23-
run: dotnet build
23+
run: dotnet build -c Release
2424

2525
- name: Allow run
2626
run: chmod -R +x ./src/NodeDev.Blazor.Server/bin
2727

2828
- name: Ensure browsers are installed
29-
run: pwsh ./src/NodeDev.EndToEndTests/bin/Debug/net10.0/playwright.ps1 install --with-deps
29+
run: pwsh ./src/NodeDev.EndToEndTests/bin/Release/net10.0/playwright.ps1 install --with-deps
3030

3131
- name: Test
3232
env:
3333
HEADLESS: true
3434
working-directory: ./src/NodeDev.EndToEndTests
35-
run: dotnet test --no-build --verbosity normal
35+
run: dotnet test -c Release --no-build --verbosity normal
3636

3737
- name: Upload std Artifact
3838
if: failure()

.github/workflows/workflow-e2e-tests.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,19 @@ jobs:
2020

2121
- name: Build Necessary for Playwright
2222
working-directory: ./src/NodeDev.EndToEndTests
23-
run: dotnet build
23+
run: dotnet build -c Release
2424

2525
- name: Allow run
2626
run: chmod -R +x ./src/NodeDev.Blazor.Server/bin
2727

2828
- name: Ensure browsers are installed
29-
run: pwsh ./src/NodeDev.EndToEndTests/bin/Debug/net10.0/playwright.ps1 install --with-deps
29+
run: pwsh ./src/NodeDev.EndToEndTests/bin/Release/net10.0/playwright.ps1 install --with-deps
3030

3131
- name: Test
3232
env:
3333
HEADLESS: true
3434
working-directory: ./src/NodeDev.EndToEndTests
35-
run: dotnet test --no-build --verbosity normal
35+
run: dotnet test -c Release --no-build --verbosity normal
3636

3737
- name: Upload std Artifact
3838
if: failure()

.github/workflows/workflow-tests-windows.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,4 @@ jobs:
2424

2525
- name: Test
2626
working-directory: ./src/NodeDev.Tests
27-
run: dotnet test --no-build --verbosity normal
27+
run: dotnet test -c Release --no-build --verbosity normal

.github/workflows/workflow-tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,4 @@ jobs:
2424

2525
- name: Test
2626
working-directory: ./src/NodeDev.Tests
27-
run: dotnet test --no-build --verbosity normal
27+
run: dotnet test -c Release --no-build --verbosity normal

docs/e2e-testing.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,29 @@ Components are marked with `data-test-id` attributes for reliable selection:
226226
- `graph-node`: Individual nodes (with `data-test-node-name` for the node name)
227227
- Graph ports are located by CSS class and port name
228228

229+
### MudBlazor Component Selectors
230+
231+
**IMPORTANT**: MudBlazor components like `MudTabPanel` do NOT forward custom attributes like `data-test-id` to the rendered HTML. For these components, use CSS classes instead:
232+
233+
```razor
234+
<!-- WRONG - data-test-id won't work on MudTabPanel -->
235+
<MudTabPanel Text="Console Output" data-test-id="consoleOutputTab">
236+
237+
<!-- CORRECT - use Class attribute instead -->
238+
<MudTabPanel Text="Console Output" Class="consoleOutputTab">
239+
```
240+
241+
In tests, select by CSS class:
242+
```csharp
243+
// Use CSS class selector for MudBlazor components that don't forward data-test-id
244+
var consoleOutputTab = Page.Locator(".consoleOutputTab");
245+
```
246+
247+
**Always verify your selectors work** by using Playwright tools to inspect the page:
248+
1. Use `playwright-browser_snapshot` to see the accessibility tree
249+
2. Use `playwright-browser_take_screenshot` to visually inspect the page
250+
3. If a selector doesn't find elements, the attribute may not be rendered - check the actual HTML
251+
229252
## Running Tests
230253

231254
### Locally
@@ -244,9 +267,26 @@ Tests run automatically in GitHub Actions with headless mode enabled.
244267
3. **Validate with screenshots**: Capture screenshots during critical operations
245268
4. **Test incrementally**: Start with simple movements before complex scenarios
246269
5. **Account for grid snapping**: Node positions may snap to grid, use tolerance in assertions
270+
6. **ALWAYS read this documentation BEFORE modifying E2E tests**
271+
7. **NEVER skip, disable, or remove tests** - fix the underlying issue instead
247272

248273
## Troubleshooting
249274

275+
### ⚠️ IMPORTANT: Always Use Playwright Tools First
276+
277+
**When encountering any E2E test issues (timeout, element not found, assertion failures), ALWAYS use the Playwright MCP tools to diagnose before assuming the test or functionality is broken:**
278+
279+
1. **`playwright-browser_snapshot`** - Get accessibility tree of current page state
280+
2. **`playwright-browser_take_screenshot`** - Capture visual screenshot to see actual UI state
281+
3. **`playwright-browser_navigate`** - Manually navigate to test the UI
282+
4. **`playwright-browser_click`** - Test interactions manually
283+
284+
These tools help you:
285+
- Verify elements exist and are visible
286+
- See the actual HTML/CSS classes rendered (important for MudBlazor components)
287+
- Understand timing issues by inspecting state at specific moments
288+
- Validate selectors before assuming they're correct
289+
250290
### Nodes Don't Move
251291
- Verify the node name matches exactly (case-sensitive)
252292
- Check if node is visible on canvas before dragging

src/NodeDev.Blazor/Components/DebuggerConsolePanel.razor

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,24 @@
33
<MudPaper Class="w100 pa-1 overflow-y-scroll d-flex flex-column relative" Style="height: 200px" data-test-id="consolePanel">
44
<MudIconButton Icon="@MudBlazor.Icons.Material.Filled.ArrowDropDown" Class="absolute" Style="right: 0px; top: 0px" Size="Size.Small" OnClick="@(() => IsShowing = false)"></MudIconButton>
55

6-
@foreach (var line in Lines.Reverse())
7-
{
8-
<span class="w100" data-test-id="consoleLine">@line</span>
9-
}
6+
<MudTabs Elevation="0" Rounded="false" Class="w100 h100 consoleTabs">
7+
<MudTabPanel Text="Console Output" Class="consoleOutputTab">
8+
<div class="w100 h100 overflow-y-scroll d-flex flex-column-reverse">
9+
@foreach (var line in Lines.Reverse())
10+
{
11+
<span class="w100 consoleLine">@line</span>
12+
}
13+
</div>
14+
</MudTabPanel>
15+
<MudTabPanel Text="Debug Callbacks" Class="debugCallbacksTab">
16+
<div class="w100 h100 overflow-y-scroll d-flex flex-column-reverse">
17+
@foreach (var callback in DebugCallbacks.Reverse())
18+
{
19+
<span class="w100 debugCallbackLine">@callback</span>
20+
}
21+
</div>
22+
</MudTabPanel>
23+
</MudTabs>
1024
</MudPaper>
1125

1226
}

src/NodeDev.Blazor/Components/DebuggerConsolePanel.razor.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using Microsoft.AspNetCore.Components;
22
using NodeDev.Core;
3+
using NodeDev.Core.Debugger;
34
using System.Reactive.Subjects;
45

56

@@ -12,10 +13,12 @@ public partial class DebuggerConsolePanel : ComponentBase, IDisposable
1213

1314
private IDisposable? GraphExecutionChangedDisposable;
1415
private IDisposable? ConsoleOutputDisposable;
16+
private IDisposable? DebugCallbackDisposable;
1517

1618
private TextWriter? PreviousTextWriter;
1719

1820
private readonly ReverseQueue Lines = new(10_000);
21+
private readonly ReverseQueue DebugCallbacks = new(10_000);
1922
private string LastLine = ">";
2023

2124
private bool IsShowing = false;
@@ -31,11 +34,13 @@ protected override void OnInitialized()
3134

3235
GraphExecutionChangedDisposable = Project.GraphExecutionChanged.Subscribe(OnGraphExecutionChanged);
3336
ConsoleOutputDisposable = Project.ConsoleOutput.Subscribe(OnConsoleOutput);
37+
DebugCallbackDisposable = Project.DebugCallbacks.Subscribe(OnDebugCallback);
3438
}
3539

3640
public void Clear()
3741
{
3842
Lines.Clear();
43+
DebugCallbacks.Clear();
3944
LastLine = ">";
4045
}
4146

@@ -60,6 +65,14 @@ private void OnConsoleOutput(string text)
6065
AddText(text);
6166
}
6267

68+
private void OnDebugCallback(DebugCallbackEventArgs args)
69+
{
70+
var timestamp = DateTime.Now;
71+
var callbackInfo = new DebugCallbackInfo(timestamp, args.CallbackType, args.Description);
72+
DebugCallbacks.Enqueue(callbackInfo.ToString());
73+
RefreshRequiredSubject.OnNext(null);
74+
}
75+
6376
private void AddText(string text)
6477
{
6578
var newLineCharacterIndex = text.IndexOf('\r');
@@ -90,6 +103,7 @@ public void Dispose()
90103

91104
GraphExecutionChangedDisposable?.Dispose();
92105
ConsoleOutputDisposable?.Dispose();
106+
DebugCallbackDisposable?.Dispose();
93107
RefreshRequiredDisposable?.Dispose();
94108

95109
if (PreviousTextWriter != null)
@@ -99,6 +113,11 @@ public void Dispose()
99113
}
100114
}
101115

116+
private record DebugCallbackInfo(DateTime Timestamp, string Type, string Description)
117+
{
118+
public override string ToString() => $"[{Timestamp:HH:mm:ss.fff}] {Type}: {Description}";
119+
}
120+
102121
private class ControlWriter : TextWriter
103122
{
104123
private Action<string> AddText;

0 commit comments

Comments
 (0)