2
2
// Licensed under the MIT License.
3
3
4
4
using System ;
5
+ using System . Diagnostics ;
5
6
using System . IO ;
6
7
using System . Linq ;
7
8
using System . Runtime . InteropServices ;
8
9
using System . Text ;
9
10
using System . Threading ;
10
11
using System . Threading . Tasks ;
11
- using Microsoft . Extensions . Logging ;
12
- using Microsoft . Extensions . Logging . Debug ;
13
12
using OmniSharp . Extensions . DebugAdapter . Client ;
13
+ using DapStackFrame = OmniSharp . Extensions . DebugAdapter . Protocol . Models . StackFrame ;
14
+ using OmniSharp . Extensions . DebugAdapter . Protocol . Events ;
14
15
using OmniSharp . Extensions . DebugAdapter . Protocol . Models ;
15
16
using OmniSharp . Extensions . DebugAdapter . Protocol . Requests ;
17
+ using OmniSharp . Extensions . JsonRpc . Server ;
16
18
using Xunit ;
17
19
using Xunit . Abstractions ;
20
+ using Microsoft . Extensions . Logging . Abstractions ;
18
21
19
22
namespace PowerShellEditorServices . Test . E2E
20
23
{
24
+ public class XunitOutputTraceListener ( ITestOutputHelper output ) : TraceListener
25
+ {
26
+ public override void Write ( string message ) => output . WriteLine ( message ) ;
27
+ public override void WriteLine ( string message ) => output . WriteLine ( message ) ;
28
+ }
29
+
21
30
[ Trait ( "Category" , "DAP" ) ]
22
31
public class DebugAdapterProtocolMessageTests : IAsyncLifetime , IDisposable
23
32
{
@@ -28,16 +37,26 @@ public class DebugAdapterProtocolMessageTests : IAsyncLifetime, IDisposable
28
37
private DebugAdapterClient PsesDebugAdapterClient ;
29
38
private PsesStdioProcess _psesProcess ;
30
39
40
+ /// <summary>
41
+ /// Completes when the debug adapter is started.
42
+ /// </summary>
31
43
public TaskCompletionSource < object > Started { get ; } = new TaskCompletionSource < object > ( ) ;
32
-
44
+ /// <summary>
45
+ /// Completes when the first breakpoint is reached.
46
+ /// </summary>
47
+ public TaskCompletionSource < StoppedEvent > Stopped { get ; } = new TaskCompletionSource < StoppedEvent > ( ) ;
48
+
49
+ /// <summary>
50
+ /// Constructor. The ITestOutputHelper is injected by xUnit and used to write diagnostic logs.
51
+ /// </summary>
52
+ /// <param name="output"></param>
33
53
public DebugAdapterProtocolMessageTests ( ITestOutputHelper output ) => _output = output ;
34
54
35
55
public async Task InitializeAsync ( )
36
56
{
37
- LoggerFactory debugLoggerFactory = new ( ) ;
38
- debugLoggerFactory . AddProvider ( new DebugLoggerProvider ( ) ) ;
57
+ // NOTE: To see debug logger output, add this line to your test
39
58
40
- _psesProcess = new PsesStdioProcess ( debugLoggerFactory , true ) ;
59
+ _psesProcess = new PsesStdioProcess ( new NullLoggerFactory ( ) , true ) ;
41
60
await _psesProcess . Start ( ) ;
42
61
43
62
TaskCompletionSource < bool > initialized = new ( ) ;
@@ -53,18 +72,20 @@ public async Task InitializeAsync()
53
72
options
54
73
. WithInput ( _psesProcess . OutputStream )
55
74
. WithOutput ( _psesProcess . InputStream )
56
- . ConfigureLogging ( builder =>
57
- builder
58
- . AddDebug ( )
59
- . SetMinimumLevel ( LogLevel . Trace )
60
- )
61
75
// The OnStarted delegate gets run when we receive the _Initialized_ event from the server:
62
76
// https://microsoft.github.io/debug-adapter-protocol/specification#Events_Initialized
63
77
. OnStarted ( ( _ , _ ) =>
64
78
{
65
79
Started . SetResult ( true ) ;
66
80
return Task . CompletedTask ;
67
81
} )
82
+ // We use this to create a task we can await to test debugging after a breakpoint has been received.
83
+ . OnNotification < StoppedEvent > ( null , ( stoppedEvent , _ ) =>
84
+ {
85
+ Console . WriteLine ( "StoppedEvent received" ) ;
86
+ Stopped . SetResult ( stoppedEvent ) ;
87
+ return Task . CompletedTask ;
88
+ } )
68
89
// The OnInitialized delegate gets run when we first receive the _Initialize_ response:
69
90
// https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Initialize
70
91
. OnInitialized ( ( _ , _ , _ , _ ) =>
@@ -263,6 +284,83 @@ public async Task CanSetBreakpointsAsync()
263
284
( i ) => Assert . Equal ( "after breakpoint" , i ) ) ;
264
285
}
265
286
287
+ [ SkippableFact ]
288
+ public async Task FailsIfStacktraceRequestedWhenNotPaused ( )
289
+ {
290
+ Skip . If ( PsesStdioProcess . RunningInConstrainedLanguageMode ,
291
+ "Breakpoints can't be set in Constrained Language Mode." ) ;
292
+ string filePath = NewTestFile ( GenerateScriptFromLoggingStatements (
293
+ "labelTestBreakpoint"
294
+ ) ) ;
295
+ // Set a breakpoint
296
+ await PsesDebugAdapterClient . SetBreakpoints (
297
+ new SetBreakpointsArguments
298
+ {
299
+ Source = new Source { Name = Path . GetFileName ( filePath ) , Path = filePath } ,
300
+ Breakpoints = new SourceBreakpoint [ ] { new SourceBreakpoint { Line = 1 } } ,
301
+ SourceModified = false ,
302
+ }
303
+ ) ;
304
+
305
+ // Signal to start the script
306
+ await PsesDebugAdapterClient . RequestConfigurationDone ( new ConfigurationDoneArguments ( ) ) ;
307
+ await PsesDebugAdapterClient . LaunchScript ( filePath , Started ) ;
308
+
309
+
310
+ // Get the stacktrace for the breakpoint
311
+ await Assert . ThrowsAsync < JsonRpcException > ( ( ) => PsesDebugAdapterClient . RequestStackTrace (
312
+ new StackTraceArguments { }
313
+ ) ) ;
314
+ }
315
+
316
+ [ SkippableFact ]
317
+ public async Task SendsInitialLabelBreakpointForPerformanceReasons ( )
318
+ {
319
+ Skip . If ( PsesStdioProcess . RunningInConstrainedLanguageMode ,
320
+ "Breakpoints can't be set in Constrained Language Mode." ) ;
321
+ string filePath = NewTestFile ( GenerateScriptFromLoggingStatements (
322
+ "before breakpoint" ,
323
+ "at breakpoint" ,
324
+ "after breakpoint"
325
+ ) ) ;
326
+
327
+ //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.
328
+ await PsesDebugAdapterClient . LaunchScript ( filePath , Started ) ;
329
+
330
+ // {"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}
331
+ SetBreakpointsResponse setBreakpointsResponse = await PsesDebugAdapterClient . SetBreakpoints ( new SetBreakpointsArguments
332
+ {
333
+ Source = new Source { Name = Path . GetFileName ( filePath ) , Path = filePath } ,
334
+ Breakpoints = new SourceBreakpoint [ ] { new SourceBreakpoint { Line = 2 } } ,
335
+ SourceModified = false ,
336
+ } ) ;
337
+
338
+ Breakpoint breakpoint = setBreakpointsResponse . Breakpoints . First ( ) ;
339
+ Assert . True ( breakpoint . Verified ) ;
340
+ Assert . Equal ( filePath , breakpoint . Source . Path , ignoreCase : s_isWindows ) ;
341
+ Assert . Equal ( 2 , breakpoint . Line ) ;
342
+
343
+ ConfigurationDoneResponse configDoneResponse = await PsesDebugAdapterClient . RequestConfigurationDone ( new ConfigurationDoneArguments ( ) ) ;
344
+
345
+ // 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.
346
+
347
+ // 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.
348
+ Assert . NotNull ( configDoneResponse ) ;
349
+ Assert . Collection ( await GetLog ( ) ,
350
+ ( i ) => Assert . Equal ( "before breakpoint" , i ) ) ;
351
+ File . Delete ( s_testOutputPath ) ;
352
+
353
+ // Get the stacktrace for the breakpoint
354
+ StackTraceResponse stackTraceResponse = await PsesDebugAdapterClient . RequestStackTrace (
355
+ new StackTraceArguments { ThreadId = 1 }
356
+ ) ;
357
+ DapStackFrame firstFrame = stackTraceResponse . StackFrames . First ( ) ;
358
+ Assert . Equal (
359
+ firstFrame . PresentationHint ,
360
+ StackFramePresentationHint . Label
361
+ ) ;
362
+ }
363
+
266
364
// This is a regression test for a bug where user code causes a new synchronization context
267
365
// to be created, breaking the extension. It's most evident when debugging PowerShell
268
366
// scripts that use System.Windows.Forms. It required fixing both Editor Services and
0 commit comments