diff --git a/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs b/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs
index a3c68eadb..23c32655e 100644
--- a/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs
+++ b/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT License.
using System;
+using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
@@ -11,13 +12,22 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Debug;
using OmniSharp.Extensions.DebugAdapter.Client;
+using DapStackFrame = OmniSharp.Extensions.DebugAdapter.Protocol.Models.StackFrame;
+using OmniSharp.Extensions.DebugAdapter.Protocol.Events;
using OmniSharp.Extensions.DebugAdapter.Protocol.Models;
using OmniSharp.Extensions.DebugAdapter.Protocol.Requests;
+using OmniSharp.Extensions.JsonRpc.Server;
using Xunit;
using Xunit.Abstractions;
namespace PowerShellEditorServices.Test.E2E
{
+ public class XunitOutputTraceListener(ITestOutputHelper output) : TraceListener
+ {
+ public override void Write(string message) => output.WriteLine(message);
+ public override void WriteLine(string message) => output.WriteLine(message);
+ }
+
[Trait("Category", "DAP")]
public class DebugAdapterProtocolMessageTests : IAsyncLifetime, IDisposable
{
@@ -28,8 +38,19 @@ public class DebugAdapterProtocolMessageTests : IAsyncLifetime, IDisposable
private DebugAdapterClient PsesDebugAdapterClient;
private PsesStdioProcess _psesProcess;
+ ///
+ /// Completes when the debug adapter is started.
+ ///
public TaskCompletionSource Started { get; } = new TaskCompletionSource();
-
+ ///
+ /// Completes when the first breakpoint is reached.
+ ///
+ public TaskCompletionSource Stopped { get; } = new TaskCompletionSource();
+
+ ///
+ /// Constructor. The ITestOutputHelper is injected by xUnit and used to write diagnostic logs.
+ ///
+ ///
public DebugAdapterProtocolMessageTests(ITestOutputHelper output) => _output = output;
public async Task InitializeAsync()
@@ -37,6 +58,8 @@ public async Task InitializeAsync()
LoggerFactory debugLoggerFactory = new();
debugLoggerFactory.AddProvider(new DebugLoggerProvider());
+ // NOTE: To see debug logger output, add this line to your test
+
_psesProcess = new PsesStdioProcess(debugLoggerFactory, true);
await _psesProcess.Start();
@@ -65,6 +88,13 @@ public async Task InitializeAsync()
Started.SetResult(true);
return Task.CompletedTask;
})
+ // We use this to create a task we can await to test debugging after a breakpoint has been received.
+ .OnNotification(null, (stoppedEvent, _) =>
+ {
+ Console.WriteLine("StoppedEvent received");
+ Stopped.SetResult(stoppedEvent);
+ return Task.CompletedTask;
+ })
// The OnInitialized delegate gets run when we first receive the _Initialize_ response:
// https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Initialize
.OnInitialized((_, _, _, _) =>
@@ -263,6 +293,86 @@ public async Task CanSetBreakpointsAsync()
(i) => Assert.Equal("after breakpoint", i));
}
+ [SkippableFact]
+ public async Task FailsIfStacktraceRequestedWhenNotPaused()
+ {
+ Skip.If(PsesStdioProcess.RunningInConstrainedLanguageMode,
+ "Breakpoints can't be set in Constrained Language Mode.");
+ string filePath = NewTestFile(GenerateScriptFromLoggingStatements(
+ "labelTestBreakpoint"
+ ));
+ // Set a breakpoint
+ await PsesDebugAdapterClient.SetBreakpoints(
+ new SetBreakpointsArguments
+ {
+ Source = new Source { Name = Path.GetFileName(filePath), Path = filePath },
+ Breakpoints = new SourceBreakpoint[] { new SourceBreakpoint { Line = 1 } },
+ SourceModified = false,
+ }
+ );
+
+ // Signal to start the script
+ await PsesDebugAdapterClient.RequestConfigurationDone(new ConfigurationDoneArguments());
+ await PsesDebugAdapterClient.LaunchScript(filePath, Started);
+
+
+ // Get the stacktrace for the breakpoint
+ await Assert.ThrowsAsync(() => PsesDebugAdapterClient.RequestStackTrace(
+ new StackTraceArguments { }
+ ));
+ }
+
+ [SkippableFact]
+ public async Task SendsInitialLabelBreakpointForPerformanceReasons(ITestOutputHelper output)
+ {
+ Skip.If(PsesStdioProcess.RunningInConstrainedLanguageMode,
+ "Breakpoints can't be set in Constrained Language Mode.");
+ string filePath = NewTestFile(GenerateScriptFromLoggingStatements(
+ "before breakpoint",
+ "at breakpoint",
+ "after breakpoint"
+ ));
+
+ // Enables DAP messages to be written to the test output
+ Trace.Listeners.Add(new XunitOutputTraceListener(_output));
+
+ //TODO: This is technically wrong per the spec, configDone should be completed BEFORE launching, but this is how the vscode client does it today and we really need to fix that.
+ await PsesDebugAdapterClient.LaunchScript(filePath, Started);
+
+ // {"command":"setBreakpoints","arguments":{"source":{"name":"dfsdfg.ps1","path":"/Users/tyleonha/Code/PowerShell/Misc/foo/dfsdfg.ps1"},"lines":[2],"breakpoints":[{"line":2}],"sourceModified":false},"type":"request","seq":3}
+ SetBreakpointsResponse setBreakpointsResponse = await PsesDebugAdapterClient.SetBreakpoints(new SetBreakpointsArguments
+ {
+ Source = new Source { Name = Path.GetFileName(filePath), Path = filePath },
+ Breakpoints = new SourceBreakpoint[] { new SourceBreakpoint { Line = 2 } },
+ SourceModified = false,
+ });
+
+ Breakpoint breakpoint = setBreakpointsResponse.Breakpoints.First();
+ Assert.True(breakpoint.Verified);
+ Assert.Equal(filePath, breakpoint.Source.Path, ignoreCase: s_isWindows);
+ Assert.Equal(2, breakpoint.Line);
+
+ ConfigurationDoneResponse configDoneResponse = await PsesDebugAdapterClient.RequestConfigurationDone(new ConfigurationDoneArguments());
+
+ // FIXME: I think there is a race condition here. If you remove this, the following line Stack Trace fails because the breakpoint hasn't been hit yet. I think the whole getLog process just works long enough for ConfigurationDone to complete and for the breakpoint to be hit.
+
+ // I've tried to do this properly by waiting for a StoppedEvent, but that doesn't seem to work, I'm probably just not wiring it up right in the handler.
+ Assert.NotNull(configDoneResponse);
+ Assert.Collection(await GetLog(),
+ (i) => Assert.Equal("before breakpoint", i));
+ File.Delete(s_testOutputPath);
+
+ // Get the stacktrace for the breakpoint
+ StackTraceResponse stackTraceResponse = await PsesDebugAdapterClient.RequestStackTrace(
+ new StackTraceArguments { ThreadId = 1 }
+ );
+ DapStackFrame firstFrame = stackTraceResponse.StackFrames.First();
+ Assert.Equal(
+ firstFrame.PresentationHint,
+ StackFramePresentationHint.Label
+ );
+ }
+
// This is a regression test for a bug where user code causes a new synchronization context
// to be created, breaking the extension. It's most evident when debugging PowerShell
// scripts that use System.Windows.Forms. It required fixing both Editor Services and