Skip to content

Commit 5ea2138

Browse files
Refactor e2e tests (#1398)
* refactor e2e tests * is windows for ignore case * use CLM check * misc feedback
1 parent 14ccbab commit 5ea2138

File tree

6 files changed

+323
-162
lines changed

6 files changed

+323
-162
lines changed

test/PowerShellEditorServices.Test.E2E/DAPTestsFixures.cs

Lines changed: 0 additions & 86 deletions
This file was deleted.
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
//
2+
// Copyright (c) Microsoft. All rights reserved.
3+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
4+
//
5+
6+
using System;
7+
using System.IO;
8+
using System.Reflection;
9+
using System.Threading.Tasks;
10+
using Microsoft.PowerShell.EditorServices.Handlers;
11+
using Xunit;
12+
using OmniSharp.Extensions.DebugAdapter.Client;
13+
using OmniSharp.Extensions.DebugAdapter.Protocol.Requests;
14+
using System.Threading;
15+
using System.Text;
16+
using System.Linq;
17+
using Xunit.Abstractions;
18+
using Microsoft.Extensions.Logging;
19+
using OmniSharp.Extensions.DebugAdapter.Protocol.Models;
20+
21+
namespace PowerShellEditorServices.Test.E2E
22+
{
23+
public static class DebugAdapterClientExtensions
24+
{
25+
public static async Task LaunchScript(this DebugAdapterClient debugAdapterClient, string filePath, TaskCompletionSource<object> started)
26+
{
27+
LaunchResponse launchResponse = await debugAdapterClient.RequestLaunch(new PsesLaunchRequestArguments
28+
{
29+
NoDebug = false,
30+
Script = filePath,
31+
Cwd = "",
32+
CreateTemporaryIntegratedConsole = false,
33+
}).ConfigureAwait(false);
34+
35+
if (launchResponse == null)
36+
{
37+
throw new Exception("Launch response was null.");
38+
}
39+
40+
// This will check to see if we received the Initialized event from the server.
41+
await Task.Run(
42+
async () => await started.Task.ConfigureAwait(false),
43+
new CancellationTokenSource(2000).Token).ConfigureAwait(false);
44+
}
45+
}
46+
}

test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs

Lines changed: 176 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,28 +5,100 @@
55

66
using System;
77
using System.IO;
8+
using System.Linq;
89
using System.Reflection;
10+
using System.Runtime.InteropServices;
11+
using System.Text;
12+
using System.Threading;
913
using System.Threading.Tasks;
10-
using Microsoft.PowerShell.EditorServices.Handlers;
11-
using Xunit;
14+
using Microsoft.Extensions.Logging;
1215
using OmniSharp.Extensions.DebugAdapter.Client;
16+
using OmniSharp.Extensions.DebugAdapter.Protocol.Models;
1317
using OmniSharp.Extensions.DebugAdapter.Protocol.Requests;
14-
using System.Threading;
18+
using Xunit;
19+
using Xunit.Abstractions;
1520

1621
namespace PowerShellEditorServices.Test.E2E
1722
{
18-
public class DebugAdapterProtocolMessageTests : IClassFixture<DAPTestsFixture>
23+
public class DebugAdapterProtocolMessageTests : IAsyncLifetime
1924
{
25+
private const string TestOutputFileName = "__dapTestOutputFile.txt";
26+
private readonly static bool s_isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
2027
private readonly static string s_binDir =
2128
Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
29+
private readonly static string s_testOutputPath = Path.Combine(s_binDir, TestOutputFileName);
2230

23-
private readonly DebugAdapterClient PsesDebugAdapterClient;
24-
private readonly DAPTestsFixture _dapTestsFixture;
31+
private readonly ITestOutputHelper _output;
32+
private DebugAdapterClient PsesDebugAdapterClient;
33+
private PsesStdioProcess _psesProcess;
2534

26-
public DebugAdapterProtocolMessageTests(DAPTestsFixture data)
35+
public TaskCompletionSource<object> Started { get; } = new TaskCompletionSource<object>();
36+
37+
public DebugAdapterProtocolMessageTests(ITestOutputHelper output)
2738
{
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+
}
30102
}
31103

32104
private string NewTestFile(string script, bool isPester = false)
@@ -38,6 +110,32 @@ private string NewTestFile(string script, bool isPester = false)
38110
return filePath;
39111
}
40112

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")]
41139
[Fact]
42140
public void CanInitializeWithCorrectServerSettings()
43141
{
@@ -49,34 +147,88 @@ public void CanInitializeWithCorrectServerSettings()
49147
Assert.True(PsesDebugAdapterClient.ServerSettings.SupportsSetVariable);
50148
}
51149

150+
[Trait("Category", "DAP")]
52151
[Fact]
53152
public async Task CanLaunchScriptWithNoBreakpointsAsync()
54153
{
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
57186
{
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,
62201
}).ConfigureAwait(false);
63202

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);
70207

71208
ConfigurationDoneResponse configDoneResponse = await PsesDebugAdapterClient.RequestConfigurationDone(new ConfigurationDoneArguments()).ConfigureAwait(false);
72209
Assert.NotNull(configDoneResponse);
73210

74211
// At this point the script should be running so lets give it time
75212
await Task.Delay(2000).ConfigureAwait(false);
76213

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));
80232
}
81233
}
82234
}

0 commit comments

Comments
 (0)