5
5
6
6
using System ;
7
7
using System . IO ;
8
+ using System . Linq ;
8
9
using System . Reflection ;
10
+ using System . Runtime . InteropServices ;
11
+ using System . Text ;
12
+ using System . Threading ;
9
13
using System . Threading . Tasks ;
10
- using Microsoft . PowerShell . EditorServices . Handlers ;
11
- using Xunit ;
14
+ using Microsoft . Extensions . Logging ;
12
15
using OmniSharp . Extensions . DebugAdapter . Client ;
16
+ using OmniSharp . Extensions . DebugAdapter . Protocol . Models ;
13
17
using OmniSharp . Extensions . DebugAdapter . Protocol . Requests ;
14
- using System . Threading ;
18
+ using Xunit ;
19
+ using Xunit . Abstractions ;
15
20
16
21
namespace PowerShellEditorServices . Test . E2E
17
22
{
18
- public class DebugAdapterProtocolMessageTests : IClassFixture < DAPTestsFixture >
23
+ public class DebugAdapterProtocolMessageTests : IAsyncLifetime
19
24
{
25
+ private const string TestOutputFileName = "__dapTestOutputFile.txt" ;
26
+ private readonly static bool s_isWindows = RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) ;
20
27
private readonly static string s_binDir =
21
28
Path . GetDirectoryName ( Assembly . GetExecutingAssembly ( ) . Location ) ;
29
+ private readonly static string s_testOutputPath = Path . Combine ( s_binDir , TestOutputFileName ) ;
22
30
23
- private readonly DebugAdapterClient PsesDebugAdapterClient ;
24
- private readonly DAPTestsFixture _dapTestsFixture ;
31
+ private readonly ITestOutputHelper _output ;
32
+ private DebugAdapterClient PsesDebugAdapterClient ;
33
+ private PsesStdioProcess _psesProcess ;
25
34
26
- public DebugAdapterProtocolMessageTests ( DAPTestsFixture data )
35
+ public TaskCompletionSource < object > Started { get ; } = new TaskCompletionSource < object > ( ) ;
36
+
37
+ public DebugAdapterProtocolMessageTests ( ITestOutputHelper output )
27
38
{
28
- _dapTestsFixture = data ;
29
- PsesDebugAdapterClient = data . PsesDebugAdapterClient ;
39
+ _output = output ;
40
+ }
41
+
42
+ public async Task InitializeAsync ( )
43
+ {
44
+ var factory = new LoggerFactory ( ) ;
45
+ _psesProcess = new PsesStdioProcess ( factory , true ) ;
46
+ await _psesProcess . Start ( ) ;
47
+
48
+ var initialized = new TaskCompletionSource < bool > ( ) ;
49
+ PsesDebugAdapterClient = DebugAdapterClient . Create ( options =>
50
+ {
51
+ options
52
+ . WithInput ( _psesProcess . OutputStream )
53
+ . WithOutput ( _psesProcess . InputStream )
54
+ // The OnStarted delegate gets run when we receive the _Initialized_ event from the server:
55
+ // https://microsoft.github.io/debug-adapter-protocol/specification#Events_Initialized
56
+ . OnStarted ( ( client , token ) => {
57
+ Started . SetResult ( true ) ;
58
+ return Task . CompletedTask ;
59
+ } )
60
+ // The OnInitialized delegate gets run when we first receive the _Initialize_ response:
61
+ // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Initialize
62
+ . OnInitialized ( ( client , request , response , token ) => {
63
+ initialized . SetResult ( true ) ;
64
+ return Task . CompletedTask ;
65
+ } ) ;
66
+ } ) ;
67
+
68
+ // PSES follows the following flow:
69
+ // Receive a Initialize request
70
+ // Run Initialize handler and send response back
71
+ // Receive a Launch/Attach request
72
+ // Run Launch/Attach handler and send response back
73
+ // PSES sends the initialized event at the end of the Launch/Attach handler
74
+
75
+ // The way that the Omnisharp client works is that this Initialize method doesn't return until
76
+ // after OnStarted is run... which only happens when Initialized is received from the server.
77
+ // so if we would await this task, it would deadlock.
78
+ // To get around this, we run the Initialize() without await but use a `TaskCompletionSource<bool>`
79
+ // that gets completed when we receive the response to Initialize
80
+ // This tells us that we are ready to send messages to PSES... but are not stuck waiting for
81
+ // Initialized.
82
+ PsesDebugAdapterClient . Initialize ( CancellationToken . None ) . ConfigureAwait ( false ) ;
83
+ await initialized . Task . ConfigureAwait ( false ) ;
84
+ }
85
+
86
+ public async Task DisposeAsync ( )
87
+ {
88
+ try
89
+ {
90
+ await PsesDebugAdapterClient . RequestDisconnect ( new DisconnectArguments
91
+ {
92
+ Restart = false ,
93
+ TerminateDebuggee = true
94
+ } ) . ConfigureAwait ( false ) ;
95
+ await _psesProcess . Stop ( ) . ConfigureAwait ( false ) ;
96
+ PsesDebugAdapterClient ? . Dispose ( ) ;
97
+ }
98
+ catch ( ObjectDisposedException )
99
+ {
100
+ // Language client has a disposal bug in it
101
+ }
30
102
}
31
103
32
104
private string NewTestFile ( string script , bool isPester = false )
@@ -38,6 +110,32 @@ private string NewTestFile(string script, bool isPester = false)
38
110
return filePath ;
39
111
}
40
112
113
+ private string GenerateScriptFromLoggingStatements ( params string [ ] logStatements )
114
+ {
115
+ if ( logStatements . Length == 0 )
116
+ {
117
+ throw new ArgumentNullException ( "Expected at least one argument." ) ;
118
+ }
119
+
120
+ // Have script create/overwrite file first with `>`.
121
+ StringBuilder builder = new StringBuilder ( ) . Append ( '\' ' ) . Append ( logStatements [ 0 ] ) . Append ( "' > '" ) . Append ( s_testOutputPath ) . AppendLine ( "'" ) ;
122
+ for ( int i = 1 ; i < logStatements . Length ; i ++ )
123
+ {
124
+ // Then append to that script with `>>`.
125
+ builder . Append ( '\' ' ) . Append ( logStatements [ i ] ) . Append ( "' >> '" ) . Append ( s_testOutputPath ) . AppendLine ( "'" ) ;
126
+ }
127
+
128
+ _output . WriteLine ( "Script is:" ) ;
129
+ _output . WriteLine ( builder . ToString ( ) ) ;
130
+ return builder . ToString ( ) ;
131
+ }
132
+
133
+ private string [ ] GetLog ( )
134
+ {
135
+ return File . ReadLines ( s_testOutputPath ) . ToArray ( ) ;
136
+ }
137
+
138
+ [ Trait ( "Category" , "DAP" ) ]
41
139
[ Fact ]
42
140
public void CanInitializeWithCorrectServerSettings ( )
43
141
{
@@ -49,34 +147,88 @@ public void CanInitializeWithCorrectServerSettings()
49
147
Assert . True ( PsesDebugAdapterClient . ServerSettings . SupportsSetVariable ) ;
50
148
}
51
149
150
+ [ Trait ( "Category" , "DAP" ) ]
52
151
[ Fact ]
53
152
public async Task CanLaunchScriptWithNoBreakpointsAsync ( )
54
153
{
55
- string filePath = NewTestFile ( "'works' > \" $PSScriptRoot/testFile.txt\" " ) ;
56
- LaunchResponse launchResponse = await PsesDebugAdapterClient . RequestLaunch ( new PsesLaunchRequestArguments
154
+ string filePath = NewTestFile ( GenerateScriptFromLoggingStatements ( "works" ) ) ;
155
+
156
+ await PsesDebugAdapterClient . LaunchScript ( filePath , Started ) . ConfigureAwait ( false ) ;
157
+
158
+ ConfigurationDoneResponse configDoneResponse = await PsesDebugAdapterClient . RequestConfigurationDone ( new ConfigurationDoneArguments ( ) ) . ConfigureAwait ( false ) ;
159
+ Assert . NotNull ( configDoneResponse ) ;
160
+
161
+ // At this point the script should be running so lets give it time
162
+ await Task . Delay ( 2000 ) . ConfigureAwait ( false ) ;
163
+
164
+ string [ ] log = GetLog ( ) ;
165
+ Assert . Equal ( "works" , log [ 0 ] ) ;
166
+ }
167
+
168
+ [ Trait ( "Category" , "DAP" ) ]
169
+ [ SkippableFact ]
170
+ public async Task CanSetBreakpointsAsync ( )
171
+ {
172
+ Skip . If (
173
+ PsesStdioProcess . RunningInConstainedLanguageMode ,
174
+ "You can't set breakpoints in ConstrainedLanguage mode." ) ;
175
+
176
+ string filePath = NewTestFile ( GenerateScriptFromLoggingStatements (
177
+ "before breakpoint" ,
178
+ "at breakpoint" ,
179
+ "after breakpoint"
180
+ ) ) ;
181
+
182
+ await PsesDebugAdapterClient . LaunchScript ( filePath , Started ) . ConfigureAwait ( false ) ;
183
+
184
+ // {"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}
185
+ SetBreakpointsResponse setBreakpointsResponse = await PsesDebugAdapterClient . RequestSetBreakpoints ( new SetBreakpointsArguments
57
186
{
58
- NoDebug = false ,
59
- Script = filePath ,
60
- Cwd = "" ,
61
- CreateTemporaryIntegratedConsole = false ,
187
+ Source = new Source
188
+ {
189
+ Name = Path . GetFileName ( filePath ) ,
190
+ Path = filePath
191
+ } ,
192
+ Lines = new long [ ] { 2 } ,
193
+ Breakpoints = new SourceBreakpoint [ ]
194
+ {
195
+ new SourceBreakpoint
196
+ {
197
+ Line = 2 ,
198
+ }
199
+ } ,
200
+ SourceModified = false ,
62
201
} ) . ConfigureAwait ( false ) ;
63
202
64
- Assert . NotNull ( launchResponse ) ;
65
-
66
- // This will check to see if we received the Initialized event from the server.
67
- await Task . Run (
68
- async ( ) => await _dapTestsFixture . Started . Task . ConfigureAwait ( false ) ,
69
- new CancellationTokenSource ( 2000 ) . Token ) . ConfigureAwait ( false ) ;
203
+ var breakpoint = setBreakpointsResponse . Breakpoints . First ( ) ;
204
+ Assert . True ( breakpoint . Verified ) ;
205
+ Assert . Equal ( filePath , breakpoint . Source . Path , ignoreCase : s_isWindows ) ;
206
+ Assert . Equal ( 2 , breakpoint . Line ) ;
70
207
71
208
ConfigurationDoneResponse configDoneResponse = await PsesDebugAdapterClient . RequestConfigurationDone ( new ConfigurationDoneArguments ( ) ) . ConfigureAwait ( false ) ;
72
209
Assert . NotNull ( configDoneResponse ) ;
73
210
74
211
// At this point the script should be running so lets give it time
75
212
await Task . Delay ( 2000 ) . ConfigureAwait ( false ) ;
76
213
77
- string testFile = Path . Join ( Path . GetDirectoryName ( filePath ) , "testFile.txt" ) ;
78
- string contents = await File . ReadAllTextAsync ( testFile ) . ConfigureAwait ( false ) ;
79
- Assert . Equal ( $ "works{ Environment . NewLine } ", contents ) ;
214
+ string [ ] log = GetLog ( ) ;
215
+ Assert . Single ( log , ( i ) => i == "before breakpoint" ) ;
216
+
217
+ ContinueResponse continueResponse = await PsesDebugAdapterClient . RequestContinue ( new ContinueArguments
218
+ {
219
+ ThreadId = 1 ,
220
+ } ) . ConfigureAwait ( true ) ;
221
+
222
+ Assert . NotNull ( continueResponse ) ;
223
+
224
+ // At this point the script should be running so lets give it time
225
+ await Task . Delay ( 2000 ) . ConfigureAwait ( false ) ;
226
+
227
+ log = GetLog ( ) ;
228
+ Assert . Collection ( log ,
229
+ ( i ) => Assert . Equal ( "before breakpoint" , i ) ,
230
+ ( i ) => Assert . Equal ( "at breakpoint" , i ) ,
231
+ ( i ) => Assert . Equal ( "after breakpoint" , i ) ) ;
80
232
}
81
233
}
82
234
}
0 commit comments