From 4079b2a9329ad033cd64520c19083c3f9e2f2370 Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Thu, 3 Dec 2020 16:30:35 -0800 Subject: [PATCH 1/4] refactor e2e tests --- .../DAPTestsFixures.cs | 86 -------- .../DebugAdapterClientExtensions.cs | 46 +++++ .../DebugAdapterProtocolMessageTests.cs | 193 +++++++++++++++--- .../LSPTestsFixures.cs | 26 ++- .../LanguageServerProtocolMessageTests.cs | 107 ++++++---- .../PsesStdioProcess.cs} | 72 ++++--- 6 files changed, 343 insertions(+), 187 deletions(-) delete mode 100644 test/PowerShellEditorServices.Test.E2E/DAPTestsFixures.cs create mode 100644 test/PowerShellEditorServices.Test.E2E/DebugAdapterClientExtensions.cs rename test/PowerShellEditorServices.Test.E2E/{TestsFixture.cs => Processes/PsesStdioProcess.cs} (67%) diff --git a/test/PowerShellEditorServices.Test.E2E/DAPTestsFixures.cs b/test/PowerShellEditorServices.Test.E2E/DAPTestsFixures.cs deleted file mode 100644 index a56cf6ff0..000000000 --- a/test/PowerShellEditorServices.Test.E2E/DAPTestsFixures.cs +++ /dev/null @@ -1,86 +0,0 @@ -// -// 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.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; -using OmniSharp.Extensions.DebugAdapter.Client; -using OmniSharp.Extensions.DebugAdapter.Protocol.Requests; -using OmniSharp.Extensions.DebugAdapter.Protocol.Serialization; - -namespace PowerShellEditorServices.Test.E2E -{ - public class DAPTestsFixture : TestsFixture - { - public override bool IsDebugAdapterTests => true; - - public DebugAdapterClient PsesDebugAdapterClient { get; private set; } - - public TaskCompletionSource Started { get; } = new TaskCompletionSource(); - - public async override Task CustomInitializeAsync( - ILoggerFactory factory, - Stream inputStream, - Stream outputStream) - { - var initialized = new TaskCompletionSource(); - PsesDebugAdapterClient = DebugAdapterClient.Create(options => - { - options - .WithInput(inputStream) - .WithOutput(outputStream) - // 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` - // 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 override 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 - } - } - } -} diff --git a/test/PowerShellEditorServices.Test.E2E/DebugAdapterClientExtensions.cs b/test/PowerShellEditorServices.Test.E2E/DebugAdapterClientExtensions.cs new file mode 100644 index 000000000..bc3ed5381 --- /dev/null +++ b/test/PowerShellEditorServices.Test.E2E/DebugAdapterClientExtensions.cs @@ -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 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); + } + } +} diff --git a/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs b/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs index 7bd21f6b4..1fe6a461c 100644 --- a/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs +++ b/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs @@ -5,28 +5,99 @@ using System; using System.IO; +using System.Linq; using System.Reflection; +using System.Text; +using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Handlers; -using Xunit; 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 + public class DebugAdapterProtocolMessageTests : IAsyncLifetime { + private const string s_testOutputFileName = "__dapTestOutputFile.txt"; private readonly static string s_binDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + private readonly static string s_testOutputPath = Path.Combine(s_binDir, s_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 Started { get; } = new TaskCompletionSource(); + + 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(); + 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` + // 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) @@ -38,6 +109,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() { @@ -49,24 +146,59 @@ 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")] + [Fact] + public async Task CanSetBreakpointsAsync() + { + 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); + Assert.Equal(2, breakpoint.Line); ConfigurationDoneResponse configDoneResponse = await PsesDebugAdapterClient.RequestConfigurationDone(new ConfigurationDoneArguments()).ConfigureAwait(false); Assert.NotNull(configDoneResponse); @@ -74,9 +206,24 @@ await Task.Run( // 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)); } } } diff --git a/test/PowerShellEditorServices.Test.E2E/LSPTestsFixures.cs b/test/PowerShellEditorServices.Test.E2E/LSPTestsFixures.cs index 168c8d261..4a5d9aea3 100644 --- a/test/PowerShellEditorServices.Test.E2E/LSPTestsFixures.cs +++ b/test/PowerShellEditorServices.Test.E2E/LSPTestsFixures.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Reflection; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; @@ -19,24 +20,31 @@ using OmniSharp.Extensions.LanguageServer.Protocol.Models; using OmniSharp.Extensions.LanguageServer.Protocol.Window; using OmniSharp.Extensions.LanguageServer.Protocol.Workspace; +using Xunit; using Xunit.Abstractions; namespace PowerShellEditorServices.Test.E2E { - public class LSPTestsFixture : TestsFixture + public class LSPTestsFixture : IAsyncLifetime { - public override bool IsDebugAdapterTests => false; + protected readonly static string s_binDir = + Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + + private const bool IsDebugAdapterTests = false; public ILanguageClient PsesLanguageClient { get; private set; } public List Diagnostics { get; set; } public ITestOutputHelper Output { get; set; } - public async override Task CustomInitializeAsync( - ILoggerFactory factory, - Stream inputStream, - Stream outputStream) + protected PsesStdioProcess _psesProcess; + + public async Task InitializeAsync() { + var factory = new LoggerFactory(); + _psesProcess = new PsesStdioProcess(factory, IsDebugAdapterTests); + await _psesProcess.Start(); + Diagnostics = new List(); DirectoryInfo testdir = Directory.CreateDirectory(Path.Combine(s_binDir, Path.GetRandomFileName())); @@ -44,8 +52,8 @@ public async override Task CustomInitializeAsync( PsesLanguageClient = LanguageClient.PreInit(options => { options - .WithInput(inputStream) - .WithOutput(outputStream) + .WithInput(_psesProcess.OutputStream) + .WithOutput(_psesProcess.InputStream) .WithRootUri(DocumentUri.FromFileSystemPath(testdir.FullName)) .OnPublishDiagnostics(diagnosticParams => Diagnostics.AddRange(diagnosticParams.Diagnostics.Where(d => d != null))) .OnLogMessage(logMessageParams => Output?.WriteLine($"{logMessageParams.Type.ToString()}: {logMessageParams.Message}")); @@ -78,7 +86,7 @@ public async override Task CustomInitializeAsync( }); } - public override async Task DisposeAsync() + public async Task DisposeAsync() { try { diff --git a/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs b/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs index 5a15aac66..91c7fb70d 100644 --- a/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs +++ b/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs @@ -44,7 +44,7 @@ public LanguageServerProtocolMessageTests(ITestOutputHelper output, LSPTestsFixt Diagnostics = data.Diagnostics; Diagnostics.Clear(); - PwshExe = TestsFixture.PwshExe; + PwshExe = PsesStdioProcess.PwshExe; } public void Dispose() @@ -91,8 +91,9 @@ private async Task WaitForDiagnostics() } } + [Trait("Category", "LSP")] [Fact] - public async Task CanSendPowerShellGetVersionRequest() + public async Task CanSendPowerShellGetVersionRequestAsync() { PowerShellVersion details = await PsesLanguageClient @@ -109,8 +110,9 @@ PowerShellVersion details } } + [Trait("Category", "LSP")] [Fact] - public async Task CanSendWorkspaceSymbolRequest() + public async Task CanSendWorkspaceSymbolRequestAsync() { NewTestFile(@" @@ -132,11 +134,12 @@ function CanSendWorkspaceSymbolRequest { Assert.Equal("CanSendWorkspaceSymbolRequest { }", symbol.Name); } + [Trait("Category", "LSP")] [SkippableFact] - public async Task CanReceiveDiagnosticsFromFileOpen() + public async Task CanReceiveDiagnosticsFromFileOpenAsync() { Skip.If( - TestsFixture.RunningInConstainedLanguageMode && TestsFixture.IsWindowsPowerShell, + PsesStdioProcess.RunningInConstainedLanguageMode && PsesStdioProcess.IsWindowsPowerShell, "Windows PowerShell doesn't trust PSScriptAnalyzer by default so it won't load."); NewTestFile("$a = 4"); @@ -146,8 +149,9 @@ public async Task CanReceiveDiagnosticsFromFileOpen() Assert.Equal("PSUseDeclaredVarsMoreThanAssignments", diagnostic.Code); } + [Trait("Category", "LSP")] [Fact] - public async Task WontReceiveDiagnosticsFromFileOpenThatIsNotPowerShell() + public async Task WontReceiveDiagnosticsFromFileOpenThatIsNotPowerShellAsync() { NewTestFile("$a = 4", languageId: "plaintext"); await Task.Delay(2000); @@ -155,11 +159,12 @@ public async Task WontReceiveDiagnosticsFromFileOpenThatIsNotPowerShell() Assert.Empty(Diagnostics); } + [Trait("Category", "LSP")] [SkippableFact] - public async Task CanReceiveDiagnosticsFromFileChanged() + public async Task CanReceiveDiagnosticsFromFileChangedAsync() { Skip.If( - TestsFixture.RunningInConstainedLanguageMode && TestsFixture.IsWindowsPowerShell, + PsesStdioProcess.RunningInConstainedLanguageMode && PsesStdioProcess.IsWindowsPowerShell, "Windows PowerShell doesn't trust PSScriptAnalyzer by default so it won't load."); string filePath = NewTestFile("$a = 4"); @@ -207,11 +212,12 @@ public async Task CanReceiveDiagnosticsFromFileChanged() Assert.Equal("PSUseDeclaredVarsMoreThanAssignments", diagnostic.Code); } + [Trait("Category", "LSP")] [SkippableFact] - public async Task CanReceiveDiagnosticsFromConfigurationChange() + public async Task CanReceiveDiagnosticsFromConfigurationChangeAsync() { Skip.If( - TestsFixture.RunningInConstainedLanguageMode && TestsFixture.IsWindowsPowerShell, + PsesStdioProcess.RunningInConstainedLanguageMode && PsesStdioProcess.IsWindowsPowerShell, "Windows PowerShell doesn't trust PSScriptAnalyzer by default so it won't load."); NewTestFile("gci | % { $_ }"); @@ -256,8 +262,9 @@ public async Task CanReceiveDiagnosticsFromConfigurationChange() } } + [Trait("Category", "LSP")] [Fact] - public async Task CanSendFoldingRangeRequest() + public async Task CanSendFoldingRangeRequestAsync() { string scriptPath = NewTestFile(@"gci | % { $_ @@ -297,11 +304,12 @@ await PsesLanguageClient }); } + [Trait("Category", "LSP")] [SkippableFact] - public async Task CanSendFormattingRequest() + public async Task CanSendFormattingRequestAsync() { Skip.If( - TestsFixture.RunningInConstainedLanguageMode && TestsFixture.IsWindowsPowerShell, + PsesStdioProcess.RunningInConstainedLanguageMode && PsesStdioProcess.IsWindowsPowerShell, "Windows PowerShell doesn't trust PSScriptAnalyzer by default so it won't load."); string scriptPath = NewTestFile(@" @@ -334,11 +342,12 @@ public async Task CanSendFormattingRequest() Assert.Contains("\t", textEdit.NewText); } + [Trait("Category", "LSP")] [SkippableFact] - public async Task CanSendRangeFormattingRequest() + public async Task CanSendRangeFormattingRequestAsync() { Skip.If( - TestsFixture.RunningInConstainedLanguageMode && TestsFixture.IsWindowsPowerShell, + PsesStdioProcess.RunningInConstainedLanguageMode && PsesStdioProcess.IsWindowsPowerShell, "Windows PowerShell doesn't trust PSScriptAnalyzer by default so it won't load."); string scriptPath = NewTestFile(@" @@ -384,8 +393,9 @@ public async Task CanSendRangeFormattingRequest() Assert.Contains("\t", textEdit.NewText); } + [Trait("Category", "LSP")] [Fact] - public async Task CanSendDocumentSymbolRequest() + public async Task CanSendDocumentSymbolRequestAsync() { string scriptPath = NewTestFile(@" function CanSendDocumentSymbolRequest { @@ -419,8 +429,9 @@ await PsesLanguageClient }); } + [Trait("Category", "LSP")] [Fact] - public async Task CanSendReferencesRequest() + public async Task CanSendReferencesRequestAsync() { string scriptPath = NewTestFile(@" function CanSendReferencesRequest { @@ -471,8 +482,9 @@ function CanSendReferencesRequest { }); } + [Trait("Category", "LSP")] [Fact] - public async Task CanSendDocumentHighlightRequest() + public async Task CanSendDocumentHighlightRequestAsync() { string scriptPath = NewTestFile(@" Write-Host 'Hello!' @@ -518,8 +530,9 @@ await PsesLanguageClient }); } + [Trait("Category", "LSP")] [Fact] - public async Task CanSendPowerShellGetPSHostProcessesRequest() + public async Task CanSendPowerShellGetPSHostProcessesRequestAsync() { var process = new Process(); process.StartInfo.FileName = PwshExe; @@ -559,8 +572,9 @@ await PsesLanguageClient Assert.NotEmpty(pSHostProcessResponses); } + [Trait("Category", "LSP")] [Fact] - public async Task CanSendPowerShellGetRunspaceRequest() + public async Task CanSendPowerShellGetRunspaceRequestAsync() { var process = new Process(); process.StartInfo.FileName = PwshExe; @@ -602,8 +616,9 @@ await PsesLanguageClient Assert.NotEmpty(runspaceResponses); } + [Trait("Category", "LSP")] [Fact] - public async Task CanSendPesterLegacyCodeLensRequest() + public async Task CanSendPesterLegacyCodeLensRequestAsync() { // Make sure LegacyCodeLens is enabled because we'll need it in this test. PsesLanguageClient.Workspace.DidChangeConfiguration( @@ -667,8 +682,9 @@ public async Task CanSendPesterLegacyCodeLensRequest() }); } + [Trait("Category", "LSP")] [Fact] - public async Task CanSendPesterCodeLensRequest() + public async Task CanSendPesterCodeLensRequestAsync() { // Make sure Pester legacy CodeLens is disabled because we'll need it in this test. PsesLanguageClient.Workspace.DidChangeConfiguration( @@ -776,8 +792,9 @@ public async Task CanSendPesterCodeLensRequest() }); } + [Trait("Category", "LSP")] [Fact] - public async Task CanSendReferencesCodeLensRequest() + public async Task CanSendReferencesCodeLensRequestAsync() { string filePath = NewTestFile(@" function CanSendReferencesCodeLensRequest { @@ -814,11 +831,12 @@ function CanSendReferencesCodeLensRequest { Assert.Equal("1 reference", codeLensResolveResult.Command.Title); } + [Trait("Category", "LSP")] [SkippableFact] - public async Task CanSendCodeActionRequest() + public async Task CanSendCodeActionRequestAsync() { Skip.If( - TestsFixture.RunningInConstainedLanguageMode && TestsFixture.IsWindowsPowerShell, + PsesStdioProcess.RunningInConstainedLanguageMode && PsesStdioProcess.IsWindowsPowerShell, "Windows PowerShell doesn't trust PSScriptAnalyzer by default so it won't load."); string filePath = NewTestFile("gci"); @@ -871,8 +889,9 @@ await PsesLanguageClient }); } + [Trait("Category", "LSP")] [Fact] - public async Task CanSendCompletionAndCompletionResolveRequest() + public async Task CanSendCompletionAndCompletionResolveRequestAsync() { string filePath = NewTestFile("Write-H"); @@ -896,8 +915,9 @@ public async Task CanSendCompletionAndCompletionResolveRequest() Assert.Contains("Writes customized output to a host", updatedCompletionItem.Documentation.String); } + [Trait("Category", "LSP")] [Fact] - public async Task CanSendCompletionResolveWithModulePrefixRequest() + public async Task CanSendCompletionResolveWithModulePrefixRequestAsync() { await PsesLanguageClient .SendRequest( @@ -930,8 +950,9 @@ await PsesLanguageClient Assert.Contains("Extracts files from a specified archive", updatedCompletionItem.Documentation.String); } + [Trait("Category", "LSP")] [Fact] - public async Task CanSendHoverRequest() + public async Task CanSendHoverRequestAsync() { string filePath = NewTestFile("Write-Host"); @@ -958,8 +979,9 @@ public async Task CanSendHoverRequest() }); } + [Trait("Category", "LSP")] [Fact] - public async Task CanSendSignatureHelpRequest() + public async Task CanSendSignatureHelpRequestAsync() { string filePath = NewTestFile("Get-Date "); @@ -983,8 +1005,9 @@ public async Task CanSendSignatureHelpRequest() Assert.Contains("Get-Date", signatureHelp.Signatures.First().Label); } + [Trait("Category", "LSP")] [Fact] - public async Task CanSendDefinitionRequest() + public async Task CanSendDefinitionRequestAsync() { string scriptPath = NewTestFile(@" function CanSendDefinitionRequest { @@ -1021,10 +1044,11 @@ await PsesLanguageClient Assert.Equal(33, locationOrLocationLink.Location.Range.End.Character); } + [Trait("Category", "LSP")] [SkippableFact] - public async Task CanSendGetProjectTemplatesRequest() + public async Task CanSendGetProjectTemplatesRequestAsync() { - Skip.If(TestsFixture.RunningInConstainedLanguageMode, "Plaster doesn't work in ConstrainedLanguage mode."); + Skip.If(PsesStdioProcess.RunningInConstainedLanguageMode, "Plaster doesn't work in ConstrainedLanguage mode."); GetProjectTemplatesResponse getProjectTemplatesResponse = await PsesLanguageClient @@ -1047,11 +1071,12 @@ await PsesLanguageClient }); } + [Trait("Category", "LSP")] [SkippableFact] - public async Task CanSendGetCommentHelpRequest() + public async Task CanSendGetCommentHelpRequestAsync() { Skip.If( - TestsFixture.RunningInConstainedLanguageMode && TestsFixture.IsWindowsPowerShell, + PsesStdioProcess.RunningInConstainedLanguageMode && PsesStdioProcess.IsWindowsPowerShell, "Windows PowerShell doesn't trust PSScriptAnalyzer by default so it won't load."); string scriptPath = NewTestFile(@" @@ -1087,8 +1112,9 @@ await PsesLanguageClient Assert.Contains("myParam", commentHelpRequestResult.Content[7]); } + [Trait("Category", "LSP")] [Fact] - public async Task CanSendEvaluateRequest() + public async Task CanSendEvaluateRequestAsync() { EvaluateResponseBody evaluateResponseBody = await PsesLanguageClient @@ -1105,8 +1131,9 @@ await PsesLanguageClient Assert.Equal(0, evaluateResponseBody.VariablesReference); } + [Trait("Category", "LSP")] [Fact] - public async Task CanSendGetCommandRequest() + public async Task CanSendGetCommandRequestAsync() { List pSCommandMessages = await PsesLanguageClient @@ -1118,11 +1145,12 @@ await PsesLanguageClient Assert.True(pSCommandMessages.Count > 20); } + [Trait("Category", "LSP")] [SkippableFact] - public async Task CanSendExpandAliasRequest() + public async Task CanSendExpandAliasRequestAsync() { Skip.If( - TestsFixture.RunningInConstainedLanguageMode, + PsesStdioProcess.RunningInConstainedLanguageMode, "This feature currently doesn't support ConstrainedLanguage Mode."); ExpandAliasResult expandAliasResult = @@ -1138,8 +1166,9 @@ await PsesLanguageClient Assert.Equal("Get-ChildItem", expandAliasResult.Text); } + [Trait("Category", "LSP")] [Fact] - public async Task CanSendSemanticTokenRequest() + public async Task CanSendSemanticTokenRequestAsync() { string scriptContent = "function"; string scriptPath = NewTestFile(scriptContent); diff --git a/test/PowerShellEditorServices.Test.E2E/TestsFixture.cs b/test/PowerShellEditorServices.Test.E2E/Processes/PsesStdioProcess.cs similarity index 67% rename from test/PowerShellEditorServices.Test.E2E/TestsFixture.cs rename to test/PowerShellEditorServices.Test.E2E/Processes/PsesStdioProcess.cs index 4fee3ef36..7df2502bf 100644 --- a/test/PowerShellEditorServices.Test.E2E/TestsFixture.cs +++ b/test/PowerShellEditorServices.Test.E2E/Processes/PsesStdioProcess.cs @@ -1,19 +1,22 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; -using System.Threading.Tasks; using Microsoft.Extensions.Logging; -using Xunit; namespace PowerShellEditorServices.Test.E2E { - public abstract class TestsFixture : IAsyncLifetime + /// + /// A is responsible for launching or attaching to a language server, providing access to its input and output streams, and tracking its lifetime. + /// + public class PsesStdioProcess : StdioServerProcess { protected readonly static string s_binDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + #region private static or constants members + private readonly static string s_bundledModulePath = new FileInfo(Path.Combine( s_binDir, "..", "..", "..", "..", "..", @@ -32,30 +35,46 @@ public abstract class TestsFixture : IAsyncLifetime const string s_hostName = "TestHost"; const string s_hostProfileId = "TestHost"; const string s_hostVersion = "1.0.0"; - readonly static string[] s_additionalModules = { "PowerShellEditorServices.VSCode" }; + private readonly static string[] s_additionalModules = { "PowerShellEditorServices.VSCode" }; - protected StdioServerProcess _psesProcess; + #endregion - public static string PwshExe { get; } = Environment.GetEnvironmentVariable("PWSH_EXE_NAME") ?? "pwsh"; + #region public static properties + public static string PwshExe { get; } = Environment.GetEnvironmentVariable("PWSH_EXE_NAME") ?? "pwsh"; public static bool IsWindowsPowerShell { get; } = PwshExe.Contains("powershell"); public static bool RunningInConstainedLanguageMode { get; } = Environment.GetEnvironmentVariable("__PSLockdownPolicy", EnvironmentVariableTarget.Machine) != null; - public virtual bool IsDebugAdapterTests { get; set; } + #endregion - public async Task InitializeAsync() + #region ctor + + public PsesStdioProcess(ILoggerFactory loggerFactory, bool isDebugAdapter) : base(loggerFactory, GeneratePsesStartInfo(isDebugAdapter)) { - var factory = new LoggerFactory(); + } + + #endregion + #region helper private methods + + private static ProcessStartInfo GeneratePsesStartInfo(bool isDebugAdapter) + { ProcessStartInfo processStartInfo = new ProcessStartInfo { FileName = PwshExe }; - processStartInfo.ArgumentList.Add("-NoLogo"); - processStartInfo.ArgumentList.Add("-NoProfile"); - processStartInfo.ArgumentList.Add("-EncodedCommand"); + foreach (var arg in GeneratePsesArguments(isDebugAdapter)) + { + processStartInfo.ArgumentList.Add(arg); + } + + return processStartInfo; + } + + private static string[] GeneratePsesArguments(bool isDebugAdapter) + { List args = new List { "&", @@ -72,7 +91,7 @@ public async Task InitializeAsync() "-Stdio" }; - if (IsDebugAdapterTests) + if (isDebugAdapter) { args.Add("-DebugServiceOnly"); } @@ -80,27 +99,20 @@ public async Task InitializeAsync() string base64Str = Convert.ToBase64String( System.Text.Encoding.Unicode.GetBytes(string.Join(' ', args))); - processStartInfo.ArgumentList.Add(base64Str); - - _psesProcess = new StdioServerProcess(factory, processStartInfo); - await _psesProcess.Start(); - - await CustomInitializeAsync(factory, _psesProcess.OutputStream, _psesProcess.InputStream).ConfigureAwait(false); - } - - public virtual async Task DisposeAsync() - { - await _psesProcess.Stop(); + return new string[] + { + "-NoLogo", + "-NoProfile", + "-EncodedCommand", + base64Str + }; } - public abstract Task CustomInitializeAsync( - ILoggerFactory factory, - Stream inputStream, - Stream outputStream); - private static string SingleQuoteEscape(string str) { return $"'{str.Replace("'", "''")}'"; } + + #endregion } } From 4080da89876fd15d40849c4f0803b2b23daee23b Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Thu, 3 Dec 2020 16:46:10 -0800 Subject: [PATCH 2/4] is windows for ignore case --- .../DebugAdapterProtocolMessageTests.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs b/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs index 1fe6a461c..f94fc817e 100644 --- a/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs +++ b/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs @@ -7,11 +7,11 @@ 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.Extensions.Logging; -using Microsoft.PowerShell.EditorServices.Handlers; using OmniSharp.Extensions.DebugAdapter.Client; using OmniSharp.Extensions.DebugAdapter.Protocol.Models; using OmniSharp.Extensions.DebugAdapter.Protocol.Requests; @@ -23,6 +23,7 @@ namespace PowerShellEditorServices.Test.E2E public class DebugAdapterProtocolMessageTests : IAsyncLifetime { private const string s_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, s_testOutputFileName); @@ -197,7 +198,7 @@ public async Task CanSetBreakpointsAsync() var breakpoint = setBreakpointsResponse.Breakpoints.First(); Assert.True(breakpoint.Verified); - Assert.Equal(filePath, breakpoint.Source.Path); + Assert.Equal(filePath, breakpoint.Source.Path, ignoreCase: s_isWindows); Assert.Equal(2, breakpoint.Line); ConfigurationDoneResponse configDoneResponse = await PsesDebugAdapterClient.RequestConfigurationDone(new ConfigurationDoneArguments()).ConfigureAwait(false); From e60c14b833a41eb96795c09f54b83af7524639a2 Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Thu, 3 Dec 2020 17:46:16 -0800 Subject: [PATCH 3/4] use CLM check --- .../DebugAdapterProtocolMessageTests.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs b/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs index f94fc817e..3f1dc95f0 100644 --- a/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs +++ b/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs @@ -166,9 +166,13 @@ public async Task CanLaunchScriptWithNoBreakpointsAsync() } [Trait("Category", "DAP")] - [Fact] + [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", From 9ebff1760a62edf3b375d928586c98f17511d814 Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Mon, 7 Dec 2020 16:06:46 -0800 Subject: [PATCH 4/4] misc feedback --- .../DebugAdapterClientExtensions.cs | 2 +- .../DebugAdapterProtocolMessageTests.cs | 4 ++-- .../Processes/PsesStdioProcess.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/PowerShellEditorServices.Test.E2E/DebugAdapterClientExtensions.cs b/test/PowerShellEditorServices.Test.E2E/DebugAdapterClientExtensions.cs index bc3ed5381..e3fc8c0cd 100644 --- a/test/PowerShellEditorServices.Test.E2E/DebugAdapterClientExtensions.cs +++ b/test/PowerShellEditorServices.Test.E2E/DebugAdapterClientExtensions.cs @@ -32,7 +32,7 @@ public static async Task LaunchScript(this DebugAdapterClient debugAdapterClient CreateTemporaryIntegratedConsole = false, }).ConfigureAwait(false); - if(launchResponse == null) + if (launchResponse == null) { throw new Exception("Launch response was null."); } diff --git a/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs b/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs index 3f1dc95f0..a70f570f5 100644 --- a/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs +++ b/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs @@ -22,11 +22,11 @@ namespace PowerShellEditorServices.Test.E2E { public class DebugAdapterProtocolMessageTests : IAsyncLifetime { - private const string s_testOutputFileName = "__dapTestOutputFile.txt"; + 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, s_testOutputFileName); + private readonly static string s_testOutputPath = Path.Combine(s_binDir, TestOutputFileName); private readonly ITestOutputHelper _output; private DebugAdapterClient PsesDebugAdapterClient; diff --git a/test/PowerShellEditorServices.Test.E2E/Processes/PsesStdioProcess.cs b/test/PowerShellEditorServices.Test.E2E/Processes/PsesStdioProcess.cs index 7df2502bf..616d2b20f 100644 --- a/test/PowerShellEditorServices.Test.E2E/Processes/PsesStdioProcess.cs +++ b/test/PowerShellEditorServices.Test.E2E/Processes/PsesStdioProcess.cs @@ -65,7 +65,7 @@ private static ProcessStartInfo GeneratePsesStartInfo(bool isDebugAdapter) FileName = PwshExe }; - foreach (var arg in GeneratePsesArguments(isDebugAdapter)) + foreach (string arg in GeneratePsesArguments(isDebugAdapter)) { processStartInfo.ArgumentList.Add(arg); }