Skip to content

Commit 47a2634

Browse files
committedNov 28, 2024·
Add Tests
1 parent b8b3aa2 commit 47a2634

File tree

1 file changed

+111
-1
lines changed

1 file changed

+111
-1
lines changed
 

‎test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs

+111-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the MIT License.
33

44
using System;
5+
using System.Diagnostics;
56
using System.IO;
67
using System.Linq;
78
using System.Runtime.InteropServices;
@@ -11,13 +12,22 @@
1112
using Microsoft.Extensions.Logging;
1213
using Microsoft.Extensions.Logging.Debug;
1314
using OmniSharp.Extensions.DebugAdapter.Client;
15+
using DapStackFrame = OmniSharp.Extensions.DebugAdapter.Protocol.Models.StackFrame;
16+
using OmniSharp.Extensions.DebugAdapter.Protocol.Events;
1417
using OmniSharp.Extensions.DebugAdapter.Protocol.Models;
1518
using OmniSharp.Extensions.DebugAdapter.Protocol.Requests;
19+
using OmniSharp.Extensions.JsonRpc.Server;
1620
using Xunit;
1721
using Xunit.Abstractions;
1822

1923
namespace PowerShellEditorServices.Test.E2E
2024
{
25+
public class XunitOutputTraceListener(ITestOutputHelper output) : TraceListener
26+
{
27+
public override void Write(string message) => output.WriteLine(message);
28+
public override void WriteLine(string message) => output.WriteLine(message);
29+
}
30+
2131
[Trait("Category", "DAP")]
2232
public class DebugAdapterProtocolMessageTests : IAsyncLifetime, IDisposable
2333
{
@@ -28,15 +38,28 @@ public class DebugAdapterProtocolMessageTests : IAsyncLifetime, IDisposable
2838
private DebugAdapterClient PsesDebugAdapterClient;
2939
private PsesStdioProcess _psesProcess;
3040

41+
/// <summary>
42+
/// Completes when the debug adapter is started.
43+
/// </summary>
3144
public TaskCompletionSource<object> Started { get; } = new TaskCompletionSource<object>();
32-
45+
/// <summary>
46+
/// Completes when the first breakpoint is reached.
47+
/// </summary>
48+
public TaskCompletionSource<StoppedEvent> Stopped { get; } = new TaskCompletionSource<StoppedEvent>();
49+
50+
/// <summary>
51+
/// Constructor. The ITestOutputHelper is injected by xUnit and used to write diagnostic logs.
52+
/// </summary>
53+
/// <param name="output"></param>
3354
public DebugAdapterProtocolMessageTests(ITestOutputHelper output) => _output = output;
3455

3556
public async Task InitializeAsync()
3657
{
3758
LoggerFactory debugLoggerFactory = new();
3859
debugLoggerFactory.AddProvider(new DebugLoggerProvider());
3960

61+
// NOTE: To see debug logger output, add this line to your test
62+
4063
_psesProcess = new PsesStdioProcess(debugLoggerFactory, true);
4164
await _psesProcess.Start();
4265

@@ -65,6 +88,13 @@ public async Task InitializeAsync()
6588
Started.SetResult(true);
6689
return Task.CompletedTask;
6790
})
91+
// We use this to create a task we can await to test debugging after a breakpoint has been received.
92+
.OnNotification<StoppedEvent>(null, (stoppedEvent, _) =>
93+
{
94+
Console.WriteLine("StoppedEvent received");
95+
Stopped.SetResult(stoppedEvent);
96+
return Task.CompletedTask;
97+
})
6898
// The OnInitialized delegate gets run when we first receive the _Initialize_ response:
6999
// https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Initialize
70100
.OnInitialized((_, _, _, _) =>
@@ -263,6 +293,86 @@ public async Task CanSetBreakpointsAsync()
263293
(i) => Assert.Equal("after breakpoint", i));
264294
}
265295

296+
[SkippableFact]
297+
public async Task FailsIfStacktraceRequestedWhenNotPaused()
298+
{
299+
Skip.If(PsesStdioProcess.RunningInConstrainedLanguageMode,
300+
"Breakpoints can't be set in Constrained Language Mode.");
301+
string filePath = NewTestFile(GenerateScriptFromLoggingStatements(
302+
"labelTestBreakpoint"
303+
));
304+
// Set a breakpoint
305+
await PsesDebugAdapterClient.SetBreakpoints(
306+
new SetBreakpointsArguments
307+
{
308+
Source = new Source { Name = Path.GetFileName(filePath), Path = filePath },
309+
Breakpoints = new SourceBreakpoint[] { new SourceBreakpoint { Line = 1 } },
310+
SourceModified = false,
311+
}
312+
);
313+
314+
// Signal to start the script
315+
await PsesDebugAdapterClient.RequestConfigurationDone(new ConfigurationDoneArguments());
316+
await PsesDebugAdapterClient.LaunchScript(filePath, Started);
317+
318+
319+
// Get the stacktrace for the breakpoint
320+
await Assert.ThrowsAsync<JsonRpcException>(() => PsesDebugAdapterClient.RequestStackTrace(
321+
new StackTraceArguments { }
322+
));
323+
}
324+
325+
[SkippableFact]
326+
public async Task SendsInitialLabelBreakpointForPerformanceReasons(ITestOutputHelper output)
327+
{
328+
Skip.If(PsesStdioProcess.RunningInConstrainedLanguageMode,
329+
"Breakpoints can't be set in Constrained Language Mode.");
330+
string filePath = NewTestFile(GenerateScriptFromLoggingStatements(
331+
"before breakpoint",
332+
"at breakpoint",
333+
"after breakpoint"
334+
));
335+
336+
// Enables DAP messages to be written to the test output
337+
Trace.Listeners.Add(new XunitOutputTraceListener(_output));
338+
339+
//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.
340+
await PsesDebugAdapterClient.LaunchScript(filePath, Started);
341+
342+
// {"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}
343+
SetBreakpointsResponse setBreakpointsResponse = await PsesDebugAdapterClient.SetBreakpoints(new SetBreakpointsArguments
344+
{
345+
Source = new Source { Name = Path.GetFileName(filePath), Path = filePath },
346+
Breakpoints = new SourceBreakpoint[] { new SourceBreakpoint { Line = 2 } },
347+
SourceModified = false,
348+
});
349+
350+
Breakpoint breakpoint = setBreakpointsResponse.Breakpoints.First();
351+
Assert.True(breakpoint.Verified);
352+
Assert.Equal(filePath, breakpoint.Source.Path, ignoreCase: s_isWindows);
353+
Assert.Equal(2, breakpoint.Line);
354+
355+
ConfigurationDoneResponse configDoneResponse = await PsesDebugAdapterClient.RequestConfigurationDone(new ConfigurationDoneArguments());
356+
357+
// 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.
358+
359+
// 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.
360+
Assert.NotNull(configDoneResponse);
361+
Assert.Collection(await GetLog(),
362+
(i) => Assert.Equal("before breakpoint", i));
363+
File.Delete(s_testOutputPath);
364+
365+
// Get the stacktrace for the breakpoint
366+
StackTraceResponse stackTraceResponse = await PsesDebugAdapterClient.RequestStackTrace(
367+
new StackTraceArguments { ThreadId = 1 }
368+
);
369+
DapStackFrame firstFrame = stackTraceResponse.StackFrames.First();
370+
Assert.Equal(
371+
firstFrame.PresentationHint,
372+
StackFramePresentationHint.Label
373+
);
374+
}
375+
266376
// This is a regression test for a bug where user code causes a new synchronization context
267377
// to be created, breaking the extension. It's most evident when debugging PowerShell
268378
// scripts that use System.Windows.Forms. It required fixing both Editor Services and

0 commit comments

Comments
 (0)