Skip to content

Refactor e2e tests #1398

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Dec 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 0 additions & 86 deletions test/PowerShellEditorServices.Test.E2E/DAPTestsFixures.cs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//

using System;
using System.IO;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.PowerShell.EditorServices.Handlers;
using Xunit;
using OmniSharp.Extensions.DebugAdapter.Client;
using OmniSharp.Extensions.DebugAdapter.Protocol.Requests;
using System.Threading;
using System.Text;
using System.Linq;
using Xunit.Abstractions;
using Microsoft.Extensions.Logging;
using OmniSharp.Extensions.DebugAdapter.Protocol.Models;

namespace PowerShellEditorServices.Test.E2E
{
public static class DebugAdapterClientExtensions
{
public static async Task LaunchScript(this DebugAdapterClient debugAdapterClient, string filePath, TaskCompletionSource<object> started)
{
LaunchResponse launchResponse = await debugAdapterClient.RequestLaunch(new PsesLaunchRequestArguments
{
NoDebug = false,
Script = filePath,
Cwd = "",
CreateTemporaryIntegratedConsole = false,
}).ConfigureAwait(false);

if (launchResponse == null)
{
throw new Exception("Launch response was null.");
}

// This will check to see if we received the Initialized event from the server.
await Task.Run(
async () => await started.Task.ConfigureAwait(false),
new CancellationTokenSource(2000).Token).ConfigureAwait(false);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,100 @@

using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.PowerShell.EditorServices.Handlers;
using Xunit;
using Microsoft.Extensions.Logging;
using OmniSharp.Extensions.DebugAdapter.Client;
using OmniSharp.Extensions.DebugAdapter.Protocol.Models;
using OmniSharp.Extensions.DebugAdapter.Protocol.Requests;
using System.Threading;
using Xunit;
using Xunit.Abstractions;

namespace PowerShellEditorServices.Test.E2E
{
public class DebugAdapterProtocolMessageTests : IClassFixture<DAPTestsFixture>
public class DebugAdapterProtocolMessageTests : IAsyncLifetime
{
private const string TestOutputFileName = "__dapTestOutputFile.txt";
private readonly static bool s_isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
private readonly static string s_binDir =
Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
private readonly static string s_testOutputPath = Path.Combine(s_binDir, TestOutputFileName);

private readonly DebugAdapterClient PsesDebugAdapterClient;
private readonly DAPTestsFixture _dapTestsFixture;
private readonly ITestOutputHelper _output;
private DebugAdapterClient PsesDebugAdapterClient;
private PsesStdioProcess _psesProcess;

public DebugAdapterProtocolMessageTests(DAPTestsFixture data)
public TaskCompletionSource<object> Started { get; } = new TaskCompletionSource<object>();

public DebugAdapterProtocolMessageTests(ITestOutputHelper output)
{
_dapTestsFixture = data;
PsesDebugAdapterClient = data.PsesDebugAdapterClient;
_output = output;
}

public async Task InitializeAsync()
{
var factory = new LoggerFactory();
_psesProcess = new PsesStdioProcess(factory, true);
await _psesProcess.Start();

var initialized = new TaskCompletionSource<bool>();
PsesDebugAdapterClient = DebugAdapterClient.Create(options =>
{
options
.WithInput(_psesProcess.OutputStream)
.WithOutput(_psesProcess.InputStream)
// The OnStarted delegate gets run when we receive the _Initialized_ event from the server:
// https://microsoft.github.io/debug-adapter-protocol/specification#Events_Initialized
.OnStarted((client, token) => {
Started.SetResult(true);
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((client, request, response, token) => {
initialized.SetResult(true);
return Task.CompletedTask;
});
});

// PSES follows the following flow:
// Receive a Initialize request
// Run Initialize handler and send response back
// Receive a Launch/Attach request
// Run Launch/Attach handler and send response back
// PSES sends the initialized event at the end of the Launch/Attach handler

// The way that the Omnisharp client works is that this Initialize method doesn't return until
// after OnStarted is run... which only happens when Initialized is received from the server.
// so if we would await this task, it would deadlock.
// To get around this, we run the Initialize() without await but use a `TaskCompletionSource<bool>`
// that gets completed when we receive the response to Initialize
// This tells us that we are ready to send messages to PSES... but are not stuck waiting for
// Initialized.
PsesDebugAdapterClient.Initialize(CancellationToken.None).ConfigureAwait(false);
await initialized.Task.ConfigureAwait(false);
}

public async Task DisposeAsync()
{
try
{
await PsesDebugAdapterClient.RequestDisconnect(new DisconnectArguments
{
Restart = false,
TerminateDebuggee = true
}).ConfigureAwait(false);
await _psesProcess.Stop().ConfigureAwait(false);
PsesDebugAdapterClient?.Dispose();
}
catch (ObjectDisposedException)
{
// Language client has a disposal bug in it
}
}

private string NewTestFile(string script, bool isPester = false)
Expand All @@ -38,6 +110,32 @@ private string NewTestFile(string script, bool isPester = false)
return filePath;
}

private string GenerateScriptFromLoggingStatements(params string[] logStatements)
{
if (logStatements.Length == 0)
{
throw new ArgumentNullException("Expected at least one argument.");
}

// Have script create/overwrite file first with `>`.
StringBuilder builder = new StringBuilder().Append('\'').Append(logStatements[0]).Append("' > '").Append(s_testOutputPath).AppendLine("'");
for (int i = 1; i < logStatements.Length; i++)
{
// Then append to that script with `>>`.
builder.Append('\'').Append(logStatements[i]).Append("' >> '").Append(s_testOutputPath).AppendLine("'");
}

_output.WriteLine("Script is:");
_output.WriteLine(builder.ToString());
return builder.ToString();
}

private string[] GetLog()
{
return File.ReadLines(s_testOutputPath).ToArray();
}

[Trait("Category", "DAP")]
[Fact]
public void CanInitializeWithCorrectServerSettings()
{
Expand All @@ -49,34 +147,88 @@ public void CanInitializeWithCorrectServerSettings()
Assert.True(PsesDebugAdapterClient.ServerSettings.SupportsSetVariable);
}

[Trait("Category", "DAP")]
[Fact]
public async Task CanLaunchScriptWithNoBreakpointsAsync()
{
string filePath = NewTestFile("'works' > \"$PSScriptRoot/testFile.txt\"");
LaunchResponse launchResponse = await PsesDebugAdapterClient.RequestLaunch(new PsesLaunchRequestArguments
string filePath = NewTestFile(GenerateScriptFromLoggingStatements("works"));

await PsesDebugAdapterClient.LaunchScript(filePath, Started).ConfigureAwait(false);

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

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

string[] log = GetLog();
Assert.Equal("works", log[0]);
}

[Trait("Category", "DAP")]
[SkippableFact]
public async Task CanSetBreakpointsAsync()
{
Skip.If(
PsesStdioProcess.RunningInConstainedLanguageMode,
"You can't set breakpoints in ConstrainedLanguage mode.");

string filePath = NewTestFile(GenerateScriptFromLoggingStatements(
"before breakpoint",
"at breakpoint",
"after breakpoint"
));

await PsesDebugAdapterClient.LaunchScript(filePath, Started).ConfigureAwait(false);

// {"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.RequestSetBreakpoints(new SetBreakpointsArguments
{
NoDebug = false,
Script = filePath,
Cwd = "",
CreateTemporaryIntegratedConsole = false,
Source = new Source
{
Name = Path.GetFileName(filePath),
Path = filePath
},
Lines = new long[] { 2 },
Breakpoints = new SourceBreakpoint[]
{
new SourceBreakpoint
{
Line = 2,
}
},
SourceModified = false,
}).ConfigureAwait(false);

Assert.NotNull(launchResponse);

// This will check to see if we received the Initialized event from the server.
await Task.Run(
async () => await _dapTestsFixture.Started.Task.ConfigureAwait(false),
new CancellationTokenSource(2000).Token).ConfigureAwait(false);
var 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()).ConfigureAwait(false);
Assert.NotNull(configDoneResponse);

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

string testFile = Path.Join(Path.GetDirectoryName(filePath), "testFile.txt");
string contents = await File.ReadAllTextAsync(testFile).ConfigureAwait(false);
Assert.Equal($"works{Environment.NewLine}", contents);
string[] log = GetLog();
Assert.Single(log, (i) => i == "before breakpoint");

ContinueResponse continueResponse = await PsesDebugAdapterClient.RequestContinue(new ContinueArguments
{
ThreadId = 1,
}).ConfigureAwait(true);

Assert.NotNull(continueResponse);

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

log = GetLog();
Assert.Collection(log,
(i) => Assert.Equal("before breakpoint", i),
(i) => Assert.Equal("at breakpoint", i),
(i) => Assert.Equal("after breakpoint", i));
}
}
}
Loading