diff --git a/.vsts-ci/azure-pipelines-ci.yml b/.vsts-ci/azure-pipelines-ci.yml index 25706f2c0..dc2f2d461 100644 --- a/.vsts-ci/azure-pipelines-ci.yml +++ b/.vsts-ci/azure-pipelines-ci.yml @@ -8,14 +8,6 @@ variables: - name: DOTNET_CLI_TELEMETRY_OPTOUT value: 'true' -trigger: - branches: - include: - - master - -pr: -- master - jobs: - job: PS51_Win2016 displayName: PowerShell 5.1 - Windows Server 2016 diff --git a/integrated.sln b/integrated.sln deleted file mode 100644 index 0b0680083..000000000 --- a/integrated.sln +++ /dev/null @@ -1,153 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26124.0 -MinimumVisualStudioVersion = 15.0.26124.0 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{31BB87E9-A4A1-4266-B150-CEACE7C424C4}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerShellEditorServices", "src\PowerShellEditorServices\PowerShellEditorServices.csproj", "{00158E75-B6E0-43E4-98E9-BDAC8EEF4C09}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerShellEditorServices.Hosting", "src\PowerShellEditorServices.Hosting\PowerShellEditorServices.Hosting.csproj", "{C6D0523A-B537-4115-ADB3-218E902684DA}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerShellEditorServices.VSCode", "src\PowerShellEditorServices.VSCode\PowerShellEditorServices.VSCode.csproj", "{6A33B29C-74FE-43C2-9207-CE51DC2ABF37}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Management.Automation", "..\PowerShell\src\System.Management.Automation\System.Management.Automation.csproj", "{1C80C3F5-16DD-4385-8C2D-EE38DA2C5EA4}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PSReadLine", "..\PSReadLine\PSReadLine\PSReadLine.csproj", "{DB03A789-7930-4BB2-B01E-65C2A754FC42}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Engine", "..\PSScriptAnalyzer\Engine\Engine.csproj", "{04D0D7FD-DC38-4BE0-BE9A-54DBC978F201}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Rules", "..\PSScriptAnalyzer\Rules\Rules.csproj", "{6F224847-AC0E-4901-B5BA-7DE81D8B3FBC}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.PowerShell.Commands.Utility", "..\PowerShell\src\Microsoft.PowerShell.Commands.Utility\Microsoft.PowerShell.Commands.Utility.csproj", "{E64BB0B6-9D93-4B6D-9EFF-F669FD5BA842}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.PowerShell.ConsoleHost", "..\PowerShell\src\Microsoft.PowerShell.ConsoleHost\Microsoft.PowerShell.ConsoleHost.csproj", "{39FB7ECC-F839-474C-89CC-467E8A4ADB51}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {00158E75-B6E0-43E4-98E9-BDAC8EEF4C09}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {00158E75-B6E0-43E4-98E9-BDAC8EEF4C09}.Debug|Any CPU.Build.0 = Debug|Any CPU - {00158E75-B6E0-43E4-98E9-BDAC8EEF4C09}.Debug|x64.ActiveCfg = Debug|Any CPU - {00158E75-B6E0-43E4-98E9-BDAC8EEF4C09}.Debug|x64.Build.0 = Debug|Any CPU - {00158E75-B6E0-43E4-98E9-BDAC8EEF4C09}.Debug|x86.ActiveCfg = Debug|Any CPU - {00158E75-B6E0-43E4-98E9-BDAC8EEF4C09}.Debug|x86.Build.0 = Debug|Any CPU - {00158E75-B6E0-43E4-98E9-BDAC8EEF4C09}.Release|Any CPU.ActiveCfg = Release|Any CPU - {00158E75-B6E0-43E4-98E9-BDAC8EEF4C09}.Release|Any CPU.Build.0 = Release|Any CPU - {00158E75-B6E0-43E4-98E9-BDAC8EEF4C09}.Release|x64.ActiveCfg = Release|Any CPU - {00158E75-B6E0-43E4-98E9-BDAC8EEF4C09}.Release|x64.Build.0 = Release|Any CPU - {00158E75-B6E0-43E4-98E9-BDAC8EEF4C09}.Release|x86.ActiveCfg = Release|Any CPU - {00158E75-B6E0-43E4-98E9-BDAC8EEF4C09}.Release|x86.Build.0 = Release|Any CPU - {C6D0523A-B537-4115-ADB3-218E902684DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C6D0523A-B537-4115-ADB3-218E902684DA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C6D0523A-B537-4115-ADB3-218E902684DA}.Debug|x64.ActiveCfg = Debug|Any CPU - {C6D0523A-B537-4115-ADB3-218E902684DA}.Debug|x64.Build.0 = Debug|Any CPU - {C6D0523A-B537-4115-ADB3-218E902684DA}.Debug|x86.ActiveCfg = Debug|Any CPU - {C6D0523A-B537-4115-ADB3-218E902684DA}.Debug|x86.Build.0 = Debug|Any CPU - {C6D0523A-B537-4115-ADB3-218E902684DA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C6D0523A-B537-4115-ADB3-218E902684DA}.Release|Any CPU.Build.0 = Release|Any CPU - {C6D0523A-B537-4115-ADB3-218E902684DA}.Release|x64.ActiveCfg = Release|Any CPU - {C6D0523A-B537-4115-ADB3-218E902684DA}.Release|x64.Build.0 = Release|Any CPU - {C6D0523A-B537-4115-ADB3-218E902684DA}.Release|x86.ActiveCfg = Release|Any CPU - {C6D0523A-B537-4115-ADB3-218E902684DA}.Release|x86.Build.0 = Release|Any CPU - {6A33B29C-74FE-43C2-9207-CE51DC2ABF37}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6A33B29C-74FE-43C2-9207-CE51DC2ABF37}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6A33B29C-74FE-43C2-9207-CE51DC2ABF37}.Debug|x64.ActiveCfg = Debug|Any CPU - {6A33B29C-74FE-43C2-9207-CE51DC2ABF37}.Debug|x64.Build.0 = Debug|Any CPU - {6A33B29C-74FE-43C2-9207-CE51DC2ABF37}.Debug|x86.ActiveCfg = Debug|Any CPU - {6A33B29C-74FE-43C2-9207-CE51DC2ABF37}.Debug|x86.Build.0 = Debug|Any CPU - {6A33B29C-74FE-43C2-9207-CE51DC2ABF37}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6A33B29C-74FE-43C2-9207-CE51DC2ABF37}.Release|Any CPU.Build.0 = Release|Any CPU - {6A33B29C-74FE-43C2-9207-CE51DC2ABF37}.Release|x64.ActiveCfg = Release|Any CPU - {6A33B29C-74FE-43C2-9207-CE51DC2ABF37}.Release|x64.Build.0 = Release|Any CPU - {6A33B29C-74FE-43C2-9207-CE51DC2ABF37}.Release|x86.ActiveCfg = Release|Any CPU - {6A33B29C-74FE-43C2-9207-CE51DC2ABF37}.Release|x86.Build.0 = Release|Any CPU - {1C80C3F5-16DD-4385-8C2D-EE38DA2C5EA4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1C80C3F5-16DD-4385-8C2D-EE38DA2C5EA4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1C80C3F5-16DD-4385-8C2D-EE38DA2C5EA4}.Debug|x64.ActiveCfg = Debug|Any CPU - {1C80C3F5-16DD-4385-8C2D-EE38DA2C5EA4}.Debug|x64.Build.0 = Debug|Any CPU - {1C80C3F5-16DD-4385-8C2D-EE38DA2C5EA4}.Debug|x86.ActiveCfg = Debug|Any CPU - {1C80C3F5-16DD-4385-8C2D-EE38DA2C5EA4}.Debug|x86.Build.0 = Debug|Any CPU - {1C80C3F5-16DD-4385-8C2D-EE38DA2C5EA4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1C80C3F5-16DD-4385-8C2D-EE38DA2C5EA4}.Release|Any CPU.Build.0 = Release|Any CPU - {1C80C3F5-16DD-4385-8C2D-EE38DA2C5EA4}.Release|x64.ActiveCfg = Release|Any CPU - {1C80C3F5-16DD-4385-8C2D-EE38DA2C5EA4}.Release|x64.Build.0 = Release|Any CPU - {1C80C3F5-16DD-4385-8C2D-EE38DA2C5EA4}.Release|x86.ActiveCfg = Release|Any CPU - {1C80C3F5-16DD-4385-8C2D-EE38DA2C5EA4}.Release|x86.Build.0 = Release|Any CPU - {DB03A789-7930-4BB2-B01E-65C2A754FC42}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DB03A789-7930-4BB2-B01E-65C2A754FC42}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DB03A789-7930-4BB2-B01E-65C2A754FC42}.Debug|x64.ActiveCfg = Debug|Any CPU - {DB03A789-7930-4BB2-B01E-65C2A754FC42}.Debug|x64.Build.0 = Debug|Any CPU - {DB03A789-7930-4BB2-B01E-65C2A754FC42}.Debug|x86.ActiveCfg = Debug|Any CPU - {DB03A789-7930-4BB2-B01E-65C2A754FC42}.Debug|x86.Build.0 = Debug|Any CPU - {DB03A789-7930-4BB2-B01E-65C2A754FC42}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DB03A789-7930-4BB2-B01E-65C2A754FC42}.Release|Any CPU.Build.0 = Release|Any CPU - {DB03A789-7930-4BB2-B01E-65C2A754FC42}.Release|x64.ActiveCfg = Release|Any CPU - {DB03A789-7930-4BB2-B01E-65C2A754FC42}.Release|x64.Build.0 = Release|Any CPU - {DB03A789-7930-4BB2-B01E-65C2A754FC42}.Release|x86.ActiveCfg = Release|Any CPU - {DB03A789-7930-4BB2-B01E-65C2A754FC42}.Release|x86.Build.0 = Release|Any CPU - {04D0D7FD-DC38-4BE0-BE9A-54DBC978F201}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {04D0D7FD-DC38-4BE0-BE9A-54DBC978F201}.Debug|Any CPU.Build.0 = Debug|Any CPU - {04D0D7FD-DC38-4BE0-BE9A-54DBC978F201}.Debug|x64.ActiveCfg = Debug|Any CPU - {04D0D7FD-DC38-4BE0-BE9A-54DBC978F201}.Debug|x64.Build.0 = Debug|Any CPU - {04D0D7FD-DC38-4BE0-BE9A-54DBC978F201}.Debug|x86.ActiveCfg = Debug|Any CPU - {04D0D7FD-DC38-4BE0-BE9A-54DBC978F201}.Debug|x86.Build.0 = Debug|Any CPU - {04D0D7FD-DC38-4BE0-BE9A-54DBC978F201}.Release|Any CPU.ActiveCfg = Release|Any CPU - {04D0D7FD-DC38-4BE0-BE9A-54DBC978F201}.Release|Any CPU.Build.0 = Release|Any CPU - {04D0D7FD-DC38-4BE0-BE9A-54DBC978F201}.Release|x64.ActiveCfg = Release|Any CPU - {04D0D7FD-DC38-4BE0-BE9A-54DBC978F201}.Release|x64.Build.0 = Release|Any CPU - {04D0D7FD-DC38-4BE0-BE9A-54DBC978F201}.Release|x86.ActiveCfg = Release|Any CPU - {04D0D7FD-DC38-4BE0-BE9A-54DBC978F201}.Release|x86.Build.0 = Release|Any CPU - {6F224847-AC0E-4901-B5BA-7DE81D8B3FBC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6F224847-AC0E-4901-B5BA-7DE81D8B3FBC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6F224847-AC0E-4901-B5BA-7DE81D8B3FBC}.Debug|x64.ActiveCfg = Debug|Any CPU - {6F224847-AC0E-4901-B5BA-7DE81D8B3FBC}.Debug|x64.Build.0 = Debug|Any CPU - {6F224847-AC0E-4901-B5BA-7DE81D8B3FBC}.Debug|x86.ActiveCfg = Debug|Any CPU - {6F224847-AC0E-4901-B5BA-7DE81D8B3FBC}.Debug|x86.Build.0 = Debug|Any CPU - {6F224847-AC0E-4901-B5BA-7DE81D8B3FBC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6F224847-AC0E-4901-B5BA-7DE81D8B3FBC}.Release|Any CPU.Build.0 = Release|Any CPU - {6F224847-AC0E-4901-B5BA-7DE81D8B3FBC}.Release|x64.ActiveCfg = Release|Any CPU - {6F224847-AC0E-4901-B5BA-7DE81D8B3FBC}.Release|x64.Build.0 = Release|Any CPU - {6F224847-AC0E-4901-B5BA-7DE81D8B3FBC}.Release|x86.ActiveCfg = Release|Any CPU - {6F224847-AC0E-4901-B5BA-7DE81D8B3FBC}.Release|x86.Build.0 = Release|Any CPU - {E64BB0B6-9D93-4B6D-9EFF-F669FD5BA842}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E64BB0B6-9D93-4B6D-9EFF-F669FD5BA842}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E64BB0B6-9D93-4B6D-9EFF-F669FD5BA842}.Debug|x64.ActiveCfg = Debug|Any CPU - {E64BB0B6-9D93-4B6D-9EFF-F669FD5BA842}.Debug|x64.Build.0 = Debug|Any CPU - {E64BB0B6-9D93-4B6D-9EFF-F669FD5BA842}.Debug|x86.ActiveCfg = Debug|Any CPU - {E64BB0B6-9D93-4B6D-9EFF-F669FD5BA842}.Debug|x86.Build.0 = Debug|Any CPU - {E64BB0B6-9D93-4B6D-9EFF-F669FD5BA842}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E64BB0B6-9D93-4B6D-9EFF-F669FD5BA842}.Release|Any CPU.Build.0 = Release|Any CPU - {E64BB0B6-9D93-4B6D-9EFF-F669FD5BA842}.Release|x64.ActiveCfg = Release|Any CPU - {E64BB0B6-9D93-4B6D-9EFF-F669FD5BA842}.Release|x64.Build.0 = Release|Any CPU - {E64BB0B6-9D93-4B6D-9EFF-F669FD5BA842}.Release|x86.ActiveCfg = Release|Any CPU - {E64BB0B6-9D93-4B6D-9EFF-F669FD5BA842}.Release|x86.Build.0 = Release|Any CPU - {39FB7ECC-F839-474C-89CC-467E8A4ADB51}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {39FB7ECC-F839-474C-89CC-467E8A4ADB51}.Debug|Any CPU.Build.0 = Debug|Any CPU - {39FB7ECC-F839-474C-89CC-467E8A4ADB51}.Debug|x64.ActiveCfg = Debug|Any CPU - {39FB7ECC-F839-474C-89CC-467E8A4ADB51}.Debug|x64.Build.0 = Debug|Any CPU - {39FB7ECC-F839-474C-89CC-467E8A4ADB51}.Debug|x86.ActiveCfg = Debug|Any CPU - {39FB7ECC-F839-474C-89CC-467E8A4ADB51}.Debug|x86.Build.0 = Debug|Any CPU - {39FB7ECC-F839-474C-89CC-467E8A4ADB51}.Release|Any CPU.ActiveCfg = Release|Any CPU - {39FB7ECC-F839-474C-89CC-467E8A4ADB51}.Release|Any CPU.Build.0 = Release|Any CPU - {39FB7ECC-F839-474C-89CC-467E8A4ADB51}.Release|x64.ActiveCfg = Release|Any CPU - {39FB7ECC-F839-474C-89CC-467E8A4ADB51}.Release|x64.Build.0 = Release|Any CPU - {39FB7ECC-F839-474C-89CC-467E8A4ADB51}.Release|x86.ActiveCfg = Release|Any CPU - {39FB7ECC-F839-474C-89CC-467E8A4ADB51}.Release|x86.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {00158E75-B6E0-43E4-98E9-BDAC8EEF4C09} = {31BB87E9-A4A1-4266-B150-CEACE7C424C4} - {C6D0523A-B537-4115-ADB3-218E902684DA} = {31BB87E9-A4A1-4266-B150-CEACE7C424C4} - {6A33B29C-74FE-43C2-9207-CE51DC2ABF37} = {31BB87E9-A4A1-4266-B150-CEACE7C424C4} - EndGlobalSection -EndGlobal diff --git a/src/PowerShellEditorServices.Hosting/EditorServicesLoader.cs b/src/PowerShellEditorServices.Hosting/EditorServicesLoader.cs index 10b7281af..7b48b9c3d 100644 --- a/src/PowerShellEditorServices.Hosting/EditorServicesLoader.cs +++ b/src/PowerShellEditorServices.Hosting/EditorServicesLoader.cs @@ -120,6 +120,11 @@ public static EditorServicesLoader Create( { AppDomain.CurrentDomain.AssemblyLoad += (object sender, AssemblyLoadEventArgs args) => { + if (args.LoadedAssembly.IsDynamic) + { + return; + } + logger.Log( PsesLogLevel.Diagnostic, $"Loaded '{args.LoadedAssembly.GetName()}' from '{args.LoadedAssembly.Location}'"); diff --git a/src/PowerShellEditorServices/Server/PsesDebugServer.cs b/src/PowerShellEditorServices/Server/PsesDebugServer.cs index 0d45df793..0b1bf209b 100644 --- a/src/PowerShellEditorServices/Server/PsesDebugServer.cs +++ b/src/PowerShellEditorServices/Server/PsesDebugServer.cs @@ -3,6 +3,7 @@ using System; using System.IO; +using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -28,7 +29,9 @@ internal class PsesDebugServer : IDisposable private DebugAdapterServer _debugAdapterServer; - private PowerShellDebugContext _debugContext; + private PsesInternalHost _psesHost; + + private bool _startedPses; protected readonly ILoggerFactory _loggerFactory; @@ -61,8 +64,8 @@ public async Task StartAsync() { // We need to let the PowerShell Context Service know that we are in a debug session // so that it doesn't send the powerShell/startDebugger message. - _debugContext = ServiceProvider.GetService().DebugContext; - _debugContext.IsDebugServerActive = true; + _psesHost = ServiceProvider.GetService(); + _psesHost.DebugContext.IsDebugServerActive = true; options .WithInput(_inputStream) @@ -88,8 +91,11 @@ public async Task StartAsync() // The OnInitialize delegate gets run when we first receive the _Initialize_ request: // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Initialize .OnInitialize(async (server, request, cancellationToken) => { + // We need to make sure the host has been started + _startedPses = !(await _psesHost.TryStartAsync(new HostStartOptions(), CancellationToken.None).ConfigureAwait(false)); + // Ensure the debugger mode is set correctly - this is required for remote debugging to work - _debugContext.EnableDebugMode(); + _psesHost.DebugContext.EnableDebugMode(); var breakpointService = server.GetService(); // Clear any existing breakpoints before proceeding @@ -115,7 +121,7 @@ public void Dispose() // Note that the lifetime of the DebugContext is longer than the debug server; // It represents the debugger on the PowerShell process we're in, // while a new debug server is spun up for every debugging session - _debugContext.IsDebugServerActive = false; + _psesHost.DebugContext.IsDebugServerActive = false; _debugAdapterServer.Dispose(); _inputStream.Dispose(); _outputStream.Dispose(); @@ -126,6 +132,13 @@ public void Dispose() public async Task WaitForShutdown() { await _serverStopped.Task.ConfigureAwait(false); + + // If we started the host, we need to ensure any errors are marshalled back to us like this + if (_startedPses) + { + _psesHost.TriggerShutdown(); + await _psesHost.Shutdown.ConfigureAwait(false); + } } #region Events diff --git a/src/PowerShellEditorServices/Server/PsesLanguageServer.cs b/src/PowerShellEditorServices/Server/PsesLanguageServer.cs index 86eceac43..5b0522818 100644 --- a/src/PowerShellEditorServices/Server/PsesLanguageServer.cs +++ b/src/PowerShellEditorServices/Server/PsesLanguageServer.cs @@ -10,6 +10,7 @@ using Microsoft.PowerShell.EditorServices.Hosting; using Microsoft.PowerShell.EditorServices.Services; using Microsoft.PowerShell.EditorServices.Services.Extension; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; using Microsoft.PowerShell.EditorServices.Services.Template; using OmniSharp.Extensions.LanguageServer.Protocol.Server; using OmniSharp.Extensions.LanguageServer.Server; @@ -32,6 +33,8 @@ internal class PsesLanguageServer private readonly HostStartupInfo _hostDetails; private readonly TaskCompletionSource _serverStart; + private PsesInternalHost _psesHost; + /// /// Create a new language server instance. /// @@ -75,8 +78,12 @@ public async Task StartAsync() options .WithInput(_inputStream) .WithOutput(_outputStream) - .WithServices(serviceCollection => serviceCollection - .AddPsesLanguageServices(_hostDetails)) // NOTE: This adds a lot of services! + .WithServices(serviceCollection => + { + + // NOTE: This adds a lot of services! + serviceCollection.AddPsesLanguageServices(_hostDetails); + }) .ConfigureLogging(builder => builder .AddSerilog(Log.Logger) // TODO: Set dispose to true? .AddLanguageProtocolLogging() @@ -116,6 +123,8 @@ public async Task StartAsync() IServiceProvider serviceProvider = languageServer.Services; + _psesHost = serviceProvider.GetService(); + var workspaceService = serviceProvider.GetService(); // Grab the workspace path from the parameters @@ -150,6 +159,10 @@ public async Task WaitForShutdown() Log.Logger.Debug("Shutting down OmniSharp Language Server"); await _serverStart.Task.ConfigureAwait(false); await LanguageServer.WaitForExit.ConfigureAwait(false); + + // Doing this means we're able to route through any exceptions experienced on the pipeline thread + _psesHost.TriggerShutdown(); + await _psesHost.Shutdown.ConfigureAwait(false); } } } diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs index 71cc5d3c5..7750585c0 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs @@ -158,17 +158,16 @@ private static PSCommand BuildPSCommandFromArguments(string command, IReadOnlyLi // We are forced to use a hack here so that we can reuse PowerShell's parameter binding var sb = new StringBuilder() - .Append("& '") - .Append(command.Replace("'", "''")) - .Append("'"); + .Append("& ") + .Append(StringEscaping.SingleQuoteAndEscape(command)); foreach (string arg in arguments) { sb.Append(' '); - if (ArgumentNeedsEscaping(arg)) + if (StringEscaping.PowerShellArgumentNeedsEscaping(arg)) { - sb.Append('\'').Append(arg.Replace("'", "''")).Append('\''); + sb.Append(StringEscaping.SingleQuoteAndEscape(arg)); } else { @@ -178,25 +177,5 @@ private static PSCommand BuildPSCommandFromArguments(string command, IReadOnlyLi return new PSCommand().AddScript(sb.ToString()); } - - private static bool ArgumentNeedsEscaping(string argument) - { - foreach (char c in argument) - { - switch (c) - { - case '\'': - case '"': - case '|': - case '&': - case ';': - case ':': - case char w when char.IsWhiteSpace(w): - return true; - } - } - - return false; - } } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs b/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs index 780992b97..fb6d11e6d 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs @@ -107,7 +107,12 @@ public void StepOver() public void SetDebugResuming(DebuggerResumeAction debuggerResumeAction) { _psesHost.SetExit(); - LastStopEventArgs.ResumeAction = debuggerResumeAction; + + if (LastStopEventArgs is not null) + { + LastStopEventArgs.ResumeAction = debuggerResumeAction; + } + // We need to tell whatever is happening right now in the debug prompt to wrap up so we can continue _psesHost.CancelCurrentTask(); } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Handlers/EvaluateHandler.cs b/src/PowerShellEditorServices/Services/PowerShell/Handlers/EvaluateHandler.cs index 5ae13498c..fb0f5764a 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Handlers/EvaluateHandler.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Handlers/EvaluateHandler.cs @@ -7,6 +7,7 @@ using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Services.PowerShell; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; +using Microsoft.PowerShell.EditorServices.Utility; namespace Microsoft.PowerShell.EditorServices.Handlers { @@ -36,7 +37,8 @@ public Task Handle(EvaluateRequestArguments request, Cance _executionService.ExecutePSCommandAsync( new PSCommand().AddScript(request.Expression), CancellationToken.None, - new PowerShellExecutionOptions { WriteInputToHost = true, WriteOutputToHost = true, AddToHistory = true, ThrowOnError = false, InterruptCurrentForeground = true }); + new PowerShellExecutionOptions { WriteInputToHost = true, WriteOutputToHost = true, AddToHistory = true, ThrowOnError = false, InterruptCurrentForeground = true }) + .HandleErrorsAsync(_logger); return Task.FromResult(new EvaluateResponseBody { diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/NullPSHostRawUI.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/NullPSHostRawUI.cs new file mode 100644 index 000000000..cb5c997c1 --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/NullPSHostRawUI.cs @@ -0,0 +1,58 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Management.Automation.Host; + +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Host +{ + internal class NullPSHostRawUI : PSHostRawUserInterface + { + private readonly BufferCell[,] _buffer; + + public NullPSHostRawUI() + { + _buffer = new BufferCell[0, 0]; + } + + public override ConsoleColor BackgroundColor { get; set; } + public override Size BufferSize { get; set; } + public override Coordinates CursorPosition { get; set; } + public override int CursorSize { get; set; } + public override ConsoleColor ForegroundColor { get; set; } + + public override bool KeyAvailable => false; + + public override Size MaxPhysicalWindowSize => MaxWindowSize; + + public override Size MaxWindowSize => new Size { Width = _buffer.GetLength(0), Height = _buffer.GetLength(1) }; + + public override Coordinates WindowPosition { get; set; } + public override Size WindowSize { get; set; } + public override string WindowTitle { get; set; } + + public override void FlushInputBuffer() + { + // Do nothing + } + + public override BufferCell[,] GetBufferContents(Rectangle rectangle) => _buffer; + + public override KeyInfo ReadKey(ReadKeyOptions options) => default; + + public override void ScrollBufferContents(Rectangle source, Coordinates destination, Rectangle clip, BufferCell fill) + { + // Do nothing + } + + public override void SetBufferContents(Coordinates origin, BufferCell[,] contents) + { + // Do nothing + } + + public override void SetBufferContents(Rectangle rectangle, BufferCell fill) + { + // Do nothing + } + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/NullPSHostUI.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/NullPSHostUI.cs new file mode 100644 index 000000000..655fd2fe9 --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/NullPSHostUI.cs @@ -0,0 +1,90 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Management.Automation; +using System.Management.Automation.Host; +using System.Security; + +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Host +{ + internal class NullPSHostUI : PSHostUserInterface + { + public NullPSHostUI() + { + RawUI = new NullPSHostRawUI(); + } + + public override PSHostRawUserInterface RawUI { get; } + + public override Dictionary Prompt(string caption, string message, Collection descriptions) + { + return new Dictionary(); + } + + public override int PromptForChoice(string caption, string message, Collection choices, int defaultChoice) + { + return 0; + } + + public override PSCredential PromptForCredential(string caption, string message, string userName, string targetName, PSCredentialTypes allowedCredentialTypes, PSCredentialUIOptions options) + { + return new PSCredential(userName: string.Empty, password: new SecureString()); + } + + public override PSCredential PromptForCredential(string caption, string message, string userName, string targetName) + => PromptForCredential(caption, message, userName, targetName, PSCredentialTypes.Default, PSCredentialUIOptions.Default); + + public override string ReadLine() + { + return string.Empty; + } + + public override SecureString ReadLineAsSecureString() + { + return new SecureString(); + } + + public override void Write(ConsoleColor foregroundColor, ConsoleColor backgroundColor, string value) + { + // Do nothing + } + + public override void Write(string value) + { + // Do nothing + } + + public override void WriteDebugLine(string message) + { + // Do nothing + } + + public override void WriteErrorLine(string value) + { + // Do nothing + } + + public override void WriteLine(string value) + { + // Do nothing + } + + public override void WriteProgress(long sourceId, ProgressRecord record) + { + // Do nothing + } + + public override void WriteVerboseLine(string message) + { + // Do nothing + } + + public override void WriteWarningLine(string message) + { + // Do nothing + } + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs index c3ebd9bb8..05d9d0a92 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs @@ -57,10 +57,16 @@ internal class PsesInternalHost : PSHost, IHostSupportsInteractiveSession, IRuns private readonly IdempotentLatch _isRunningLatch = new(); + private readonly TaskCompletionSource _started = new(); + + private readonly TaskCompletionSource _stopped = new(); + private EngineIntrinsics _mainRunspaceEngineIntrinsics; private bool _shouldExit = false; + private int _shuttingDown = 0; + private string _localComputerName; private ConsoleKeyInfo? _lastKey; @@ -90,14 +96,19 @@ public PsesInternalHost( Name = "PSES Pipeline Execution Thread", }; - _pipelineThread.SetApartmentState(ApartmentState.STA); + if (VersionUtils.IsWindows) + { + _pipelineThread.SetApartmentState(ApartmentState.STA); + } PublicHost = new EditorServicesConsolePSHost(this); Name = hostInfo.Name; Version = hostInfo.Version; DebugContext = new PowerShellDebugContext(loggerFactory, languageServer, this); - UI = new EditorServicesConsolePSHostUserInterface(loggerFactory, _readLineProvider, hostInfo.PSHost.UI); + UI = hostInfo.ConsoleReplEnabled + ? new EditorServicesConsolePSHostUserInterface(loggerFactory, _readLineProvider, hostInfo.PSHost.UI) + : new NullPSHostUI(); } public override CultureInfo CurrentCulture => _hostInfo.PSHost.CurrentCulture; @@ -128,12 +139,16 @@ public PsesInternalHost( public string InitialWorkingDirectory { get; private set; } + public Task Shutdown => _stopped.Task; + IRunspaceInfo IRunspaceContext.CurrentRunspace => CurrentRunspace; private PowerShellContextFrame CurrentFrame => _psFrameStack.Peek(); public event Action RunspaceChanged; + private bool ShouldExitExecutionLoop => _shouldExit || _shuttingDown != 0; + public override void EnterNestedPrompt() { PushPowerShellAndRunLoop(CreateNestedPowerShell(CurrentRunspace), PowerShellFrameType.Nested); @@ -172,13 +187,22 @@ public override void SetShouldExit(int exitCode) SetExit(); } - public async Task StartAsync(HostStartOptions startOptions, CancellationToken cancellationToken) + /// + /// Try to start the PowerShell loop in the host. + /// If the host is already started, this is idempotent. + /// Returns when the host is in a valid initialized state. + /// + /// Options to configure host startup. + /// A token to cancel startup. + /// A task that resolves when the host has finished startup, with the value true if the caller started the host, and false otherwise. + public async Task TryStartAsync(HostStartOptions startOptions, CancellationToken cancellationToken) { _logger.LogInformation("Host starting"); if (!_isRunningLatch.TryEnter()) { - _logger.LogDebug("Host start requested after already started"); - return; + _logger.LogDebug("Host start requested after already started."); + await _started.Task.ConfigureAwait(false); + return false; } _pipelineThread.Start(); @@ -198,6 +222,15 @@ await ExecuteDelegateAsync( { await SetInitialWorkingDirectoryAsync(startOptions.InitialWorkingDirectory, CancellationToken.None).ConfigureAwait(false); } + + await _started.Task.ConfigureAwait(false); + return true; + } + + public void TriggerShutdown() + { + Interlocked.Exchange(ref _shuttingDown, 1); + _cancellationContext.CancelCurrentTaskStack(); } public void SetExit() @@ -349,11 +382,19 @@ public Task SetInitialWorkingDirectoryAsync(string path, CancellationToken cance private void Run() { - (PowerShell pwsh, RunspaceInfo localRunspaceInfo, EngineIntrinsics engineIntrinsics) = CreateInitialPowerShellSession(); - _mainRunspaceEngineIntrinsics = engineIntrinsics; - _localComputerName = localRunspaceInfo.SessionDetails.ComputerName; - _runspaceStack.Push(new RunspaceFrame(pwsh.Runspace, localRunspaceInfo)); - PushPowerShellAndRunLoop(pwsh, PowerShellFrameType.Normal, localRunspaceInfo); + try + { + (PowerShell pwsh, RunspaceInfo localRunspaceInfo, EngineIntrinsics engineIntrinsics) = CreateInitialPowerShellSession(); + _mainRunspaceEngineIntrinsics = engineIntrinsics; + _localComputerName = localRunspaceInfo.SessionDetails.ComputerName; + _runspaceStack.Push(new RunspaceFrame(pwsh.Runspace, localRunspaceInfo)); + PushPowerShellAndRunLoop(pwsh, PowerShellFrameType.Normal, localRunspaceInfo); + } + catch (Exception e) + { + _started.TrySetException(e); + _stopped.TrySetException(e); + } } private (PowerShell, RunspaceInfo, EngineIntrinsics) CreateInitialPowerShellSession() @@ -465,13 +506,60 @@ private void PopPowerShell(RunspaceChangeAction runspaceChangeAction = RunspaceC private void RunTopLevelExecutionLoop() { - // Make sure we execute any startup tasks first - while (_taskQueue.TryTake(out ISynchronousTask task)) + try { - task.ExecuteSynchronously(CancellationToken.None); + // Make sure we execute any startup tasks first + while (_taskQueue.TryTake(out ISynchronousTask task)) + { + task.ExecuteSynchronously(CancellationToken.None); + } + + // Signal that we are ready for outside services to use + _started.TrySetResult(true); + + if (_hostInfo.ConsoleReplEnabled) + { + RunExecutionLoop(); + } + else + { + RunNoPromptExecutionLoop(); + } + } + catch (Exception e) + { + _logger.LogError(e, "PSES pipeline thread loop experienced an unexpected top-level exception"); + _stopped.TrySetException(e); + return; } - RunExecutionLoop(); + _logger.LogInformation("PSES pipeline thread loop shutting down"); + _stopped.SetResult(true); + } + + private void RunNoPromptExecutionLoop() + { + while (!ShouldExitExecutionLoop) + { + using (CancellationScope cancellationScope = _cancellationContext.EnterScope(isIdleScope: false)) + { + string taskRepresentation = null; + try + { + ISynchronousTask task = _taskQueue.Take(cancellationScope.CancellationToken); + taskRepresentation = task.ToString(); + task.ExecuteSynchronously(cancellationScope.CancellationToken); + } + catch (OperationCanceledException) + { + // Just continue + } + catch (Exception e) + { + _logger.LogError(e, $"Fatal exception occurred with task '{taskRepresentation ?? ""}'"); + } + } + } } private void RunDebugExecutionLoop() @@ -489,13 +577,13 @@ private void RunDebugExecutionLoop() private void RunExecutionLoop() { - while (!_shouldExit) + while (!ShouldExitExecutionLoop) { using (CancellationScope cancellationScope = _cancellationContext.EnterScope(isIdleScope: false)) { DoOneRepl(cancellationScope.CancellationToken); - while (!_shouldExit + while (!ShouldExitExecutionLoop && !cancellationScope.CancellationToken.IsCancellationRequested && _taskQueue.TryTake(out ISynchronousTask task)) { @@ -507,6 +595,11 @@ private void RunExecutionLoop() private void DoOneRepl(CancellationToken cancellationToken) { + if (!_hostInfo.ConsoleReplEnabled) + { + return; + } + // When a task must run in the foreground, we cancel out of the idle loop and return to the top level. // At that point, we would normally run a REPL, but we need to immediately execute the task. // So we set _skipNextPrompt to do that. @@ -774,7 +867,7 @@ private void OnBreakpointUpdated(object sender, BreakpointUpdatedEventArgs break private void OnRunspaceStateChanged(object sender, RunspaceStateEventArgs runspaceStateEventArgs) { - if (!_shouldExit && !_resettingRunspace && !runspaceStateEventArgs.RunspaceStateInfo.IsUsable()) + if (!ShouldExitExecutionLoop && !_resettingRunspace && !runspaceStateEventArgs.RunspaceStateInfo.IsUsable()) { _resettingRunspace = true; PopOrReinitializeRunspaceAsync().HandleErrorsAsync(_logger); diff --git a/src/PowerShellEditorServices/Services/Workspace/Handlers/ConfigurationHandler.cs b/src/PowerShellEditorServices/Services/Workspace/Handlers/ConfigurationHandler.cs index 48f7dacab..00095a8a4 100644 --- a/src/PowerShellEditorServices/Services/Workspace/Handlers/ConfigurationHandler.cs +++ b/src/PowerShellEditorServices/Services/Workspace/Handlers/ConfigurationHandler.cs @@ -92,7 +92,7 @@ public override async Task Handle(DidChangeConfigurationParams request, Ca _logger.LogTrace("Loading profiles..."); } - await _psesHost.StartAsync(new HostStartOptions(), CancellationToken.None).ConfigureAwait(false); + await _psesHost.TryStartAsync(new HostStartOptions { LoadProfiles = loadProfiles }, CancellationToken.None).ConfigureAwait(false); if (loadProfiles) { diff --git a/src/PowerShellEditorServices/Utility/StringEscaping.cs b/src/PowerShellEditorServices/Utility/StringEscaping.cs new file mode 100644 index 000000000..5736a9aad --- /dev/null +++ b/src/PowerShellEditorServices/Utility/StringEscaping.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.PowerShell.EditorServices.Utility +{ + internal static class StringEscaping + { + public static StringBuilder SingleQuoteAndEscape(string s) + { + return new StringBuilder(s.Length) + .Append("'") + .Append(s.Replace("'", "''")) + .Append("'"); + } + + public static bool PowerShellArgumentNeedsEscaping(string argument) + { + foreach (char c in argument) + { + switch (c) + { + case '\'': + case '"': + case '|': + case '&': + case ';': + case ':': + case char w when char.IsWhiteSpace(w): + return true; + } + } + + return false; + } + } +} diff --git a/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs b/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs index bf8b193cc..95efa48a9 100644 --- a/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs +++ b/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; @@ -44,6 +45,13 @@ public async Task InitializeAsync() await _psesProcess.Start().ConfigureAwait(false); var initialized = new TaskCompletionSource(); + + _psesProcess.ProcessExited += (sender, args) => + { + initialized.TrySetException(new ProcessExitedException("Initialization failed due to process failure", args.ExitCode, args.ErrorMessage)); + Started.TrySetException(new ProcessExitedException("Startup failed due to process failure", args.ExitCode, args.ErrorMessage)); + }; + PsesDebugAdapterClient = DebugAdapterClient.Create(options => { options @@ -61,6 +69,11 @@ public async Task InitializeAsync() initialized.SetResult(true); return Task.CompletedTask; }); + + options.OnUnhandledException = (exception) => { + initialized.SetException(exception); + Started.SetException(exception); + }; }); // PSES follows the following flow: @@ -250,7 +263,7 @@ public async Task CanStepPastSystemWindowsForms() string filePath = NewTestFile(string.Join(Environment.NewLine, new [] { "Add-Type -AssemblyName System.Windows.Forms", - "$form = New-Object System.Windows.Forms.Form", + "$global:form = New-Object System.Windows.Forms.Form", "Write-Host $form" })); diff --git a/test/PowerShellEditorServices.Test.E2E/LSPTestsFixures.cs b/test/PowerShellEditorServices.Test.E2E/LSPTestsFixures.cs index 3bdd47096..208ea3f6f 100644 --- a/test/PowerShellEditorServices.Test.E2E/LSPTestsFixures.cs +++ b/test/PowerShellEditorServices.Test.E2E/LSPTestsFixures.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; diff --git a/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs b/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs index 276df5666..6852bff3a 100644 --- a/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs +++ b/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs @@ -24,6 +24,8 @@ using Range = OmniSharp.Extensions.LanguageServer.Protocol.Models.Range; using Microsoft.PowerShell.EditorServices.Logging; using Microsoft.PowerShell.EditorServices.Services.Configuration; +using Microsoft.PowerShell.EditorServices.Services.PowerShell; +using Microsoft.PowerShell.EditorServices.Services.Template; namespace PowerShellEditorServices.Test.E2E { @@ -39,6 +41,7 @@ public class LanguageServerProtocolMessageTests : IClassFixture private readonly List Diagnostics; private readonly List TelemetryEvents; private readonly string PwshExe; + private readonly LSPTestsFixture _fixture; public LanguageServerProtocolMessageTests(ITestOutputHelper output, LSPTestsFixture data) { @@ -48,6 +51,7 @@ public LanguageServerProtocolMessageTests(ITestOutputHelper output, LSPTestsFixt Diagnostics.Clear(); TelemetryEvents = data.TelemetryEvents; TelemetryEvents.Clear(); + _fixture = data; PwshExe = PsesStdioProcess.PwshExe; } @@ -1151,6 +1155,8 @@ await PsesLanguageClient [Fact] public async Task CanSendEvaluateRequestAsync() { + using var cancellationSource = new CancellationTokenSource(millisecondsDelay: 5000); + EvaluateResponseBody evaluateResponseBody = await PsesLanguageClient .SendRequest( @@ -1159,7 +1165,7 @@ await PsesLanguageClient { Expression = "Get-ChildItem" }) - .Returning(CancellationToken.None).ConfigureAwait(false); + .Returning(cancellationSource.Token).ConfigureAwait(false); // These always gets returned so this test really just makes sure we get _any_ response. Assert.Equal("", evaluateResponseBody.Result); diff --git a/test/PowerShellEditorServices.Test.E2E/Processes/LoggingStream.cs b/test/PowerShellEditorServices.Test.E2E/Processes/LoggingStream.cs new file mode 100644 index 000000000..dc5b2e2a5 --- /dev/null +++ b/test/PowerShellEditorServices.Test.E2E/Processes/LoggingStream.cs @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Diagnostics; +using System.IO; +using System.Text; + +namespace PowerShellEditorServices.Test.E2E +{ + internal class LoggingStream : Stream + { + private static readonly string s_banner = new('=', 20); + + private readonly Stream _underlyingStream; + + public LoggingStream(Stream underlyingStream) + { + _underlyingStream = underlyingStream; + } + + public override bool CanRead => _underlyingStream.CanRead; + + public override bool CanSeek => _underlyingStream.CanSeek; + + public override bool CanWrite => _underlyingStream.CanWrite; + + public override long Length => _underlyingStream.Length; + + public override long Position { get => _underlyingStream.Position; set => _underlyingStream.Position = value; } + + public override void Flush() => _underlyingStream.Flush(); + + public override int Read(byte[] buffer, int offset, int count) + { + int actualCount = _underlyingStream.Read(buffer, offset, count); + LogData("READ", buffer, offset, actualCount); + return actualCount; + } + + public override long Seek(long offset, SeekOrigin origin) => _underlyingStream.Seek(offset, origin); + + public override void SetLength(long value) => _underlyingStream.SetLength(value); + + public override void Write(byte[] buffer, int offset, int count) + { + LogData("WRITE", buffer, offset, count); + _underlyingStream.Write(buffer, offset, count); + } + + private static void LogData(string header, byte[] buffer, int offset, int count) + { + Debug.WriteLine($"{header} |{s_banner.Substring(0, Math.Max(s_banner.Length - header.Length - 2, 0))}"); + string data = Encoding.UTF8.GetString(buffer, offset, count); + Debug.WriteLine(data); + Debug.WriteLine(s_banner); + Debug.WriteLine("\n"); + } + } +} diff --git a/test/PowerShellEditorServices.Test.E2E/Processes/ServerProcess.cs b/test/PowerShellEditorServices.Test.E2E/Processes/ServerProcess.cs index 68e323338..ca7360cb8 100644 --- a/test/PowerShellEditorServices.Test.E2E/Processes/ServerProcess.cs +++ b/test/PowerShellEditorServices.Test.E2E/Processes/ServerProcess.cs @@ -15,6 +15,11 @@ namespace PowerShellEditorServices.Test.E2E public abstract class ServerProcess : IDisposable { private readonly ISubject _exitedSubject; + + private readonly Lazy _inStreamLazy; + + private readonly Lazy _outStreamLazy; + /// /// Create a new . /// @@ -37,6 +42,9 @@ protected ServerProcess(ILoggerFactory loggerFactory) ServerExitCompletion.SetResult(null); // Start out as if the server has already exited. Exited = _exitedSubject = new AsyncSubject(); + + _inStreamLazy = new Lazy(() => new LoggingStream(GetInputStream())); + _outStreamLazy = new Lazy(() => new LoggingStream(GetOutputStream())); } /// @@ -105,13 +113,17 @@ protected virtual void Dispose(bool disposing) /// public Task HasExited => ServerExitCompletion.Task; + protected abstract Stream GetInputStream(); + + protected abstract Stream GetOutputStream(); + /// /// The server's input stream. /// /// /// The connection will write to the server's input stream, and read from its output stream. /// - public abstract Stream InputStream { get; } + public Stream InputStream => _inStreamLazy.Value; /// /// The server's output stream. @@ -119,7 +131,7 @@ protected virtual void Dispose(bool disposing) /// /// The connection will read from the server's output stream, and write to its input stream. /// - public abstract Stream OutputStream { get; } + public Stream OutputStream => _outStreamLazy.Value; /// /// Start or connect to the server. diff --git a/test/PowerShellEditorServices.Test.E2E/Processes/StdioServerProcess.cs b/test/PowerShellEditorServices.Test.E2E/Processes/StdioServerProcess.cs index 585e1bf03..4a0c96d16 100644 --- a/test/PowerShellEditorServices.Test.E2E/Processes/StdioServerProcess.cs +++ b/test/PowerShellEditorServices.Test.E2E/Processes/StdioServerProcess.cs @@ -45,6 +45,8 @@ public StdioServerProcess(ILoggerFactory loggerFactory, ProcessStartInfo serverS _serverStartInfo = serverStartInfo; } + public int ProcessId => _serverProcess.Id; + /// /// Dispose of resources being used by the launcher. /// @@ -76,12 +78,12 @@ protected override void Dispose(bool disposing) /// /// The server's input stream. /// - public override Stream InputStream => _serverProcess?.StandardInput?.BaseStream; + protected override Stream GetInputStream() => _serverProcess?.StandardInput?.BaseStream; /// /// The server's output stream. /// - public override Stream OutputStream => _serverProcess?.StandardOutput?.BaseStream; + protected override Stream GetOutputStream() => _serverProcess?.StandardOutput?.BaseStream; /// /// Start or connect to the server. @@ -94,6 +96,7 @@ public override Task Start() _serverStartInfo.UseShellExecute = false; _serverStartInfo.RedirectStandardInput = true; _serverStartInfo.RedirectStandardOutput = true; + _serverStartInfo.RedirectStandardError = true; Process serverProcess = _serverProcess = new Process { @@ -115,17 +118,19 @@ public override Task Start() /// /// Stop or disconnect from the server. /// - public override async Task Stop() + public override Task Stop() { Process serverProcess = Interlocked.Exchange(ref _serverProcess, null); + ServerExitCompletion.TrySetResult(null); if (serverProcess != null && !serverProcess.HasExited) { serverProcess.Kill(); } - - await ServerExitCompletion.Task.ConfigureAwait(false); + return ServerExitCompletion.Task; } + public event EventHandler ProcessExited; + /// /// Called when the server process has exited. /// @@ -139,9 +144,49 @@ void ServerProcess_Exit(object sender, EventArgs args) { Log.LogDebug("Server process has exited."); + var serverProcess = (Process)sender; + + int exitCode = serverProcess.ExitCode; + string errorMsg = serverProcess.StandardError.ReadToEnd(); + OnExited(); - ServerExitCompletion.TrySetResult(null); + ProcessExited?.Invoke(this, new ProcessExitedArgs(exitCode, errorMsg)); + if (exitCode != 0) + { + ServerExitCompletion.TrySetException(new ProcessExitedException("Stdio server process exited unexpectedly", exitCode, errorMsg)); + } + else + { + ServerExitCompletion.TrySetResult(null); + } ServerStartCompletion = new TaskCompletionSource(); } } + + public class ProcessExitedException : Exception + { + public ProcessExitedException(string message, int exitCode, string errorMessage) + : base(message) + { + ExitCode = exitCode; + ErrorMessage = errorMessage; + } + + public int ExitCode { get; init; } + + public string ErrorMessage { get; init; } + } + + public class ProcessExitedArgs : EventArgs + { + public ProcessExitedArgs(int exitCode, string errorMessage) + { + ExitCode = exitCode; + ErrorMessage = errorMessage; + } + + public int ExitCode { get; init; } + + public string ErrorMessage { get; init; } + } } diff --git a/test/PowerShellEditorServices.Test.E2E/xunit.runner.json b/test/PowerShellEditorServices.Test.E2E/xunit.runner.json index 3f3645a0a..2719fd14a 100644 --- a/test/PowerShellEditorServices.Test.E2E/xunit.runner.json +++ b/test/PowerShellEditorServices.Test.E2E/xunit.runner.json @@ -2,5 +2,7 @@ "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json", "appDomain": "denied", "parallelizeTestCollections": false, - "methodDisplay": "method" + "methodDisplay": "method", + "diagnosticMessages": true, + "longRunningTestSeconds": 60 } diff --git a/test/PowerShellEditorServices.Test/Console/ChoicePromptHandlerTests.cs b/test/PowerShellEditorServices.Test/Console/ChoicePromptHandlerTests.cs index a07303472..819be34f0 100644 --- a/test/PowerShellEditorServices.Test/Console/ChoicePromptHandlerTests.cs +++ b/test/PowerShellEditorServices.Test/Console/ChoicePromptHandlerTests.cs @@ -4,11 +4,11 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging.Abstractions; -using Microsoft.PowerShell.EditorServices.Services.PowerShellContext; using Xunit; namespace Microsoft.PowerShell.EditorServices.Test.Console { + /* public class ChoicePromptHandlerTests { private readonly ChoiceDetails[] Choices = @@ -121,5 +121,6 @@ protected override void ShowPrompt(PromptStyle promptStyle) this.TimesPrompted++; } } + */ } diff --git a/test/PowerShellEditorServices.Test/Console/InputPromptHandlerTests.cs b/test/PowerShellEditorServices.Test/Console/InputPromptHandlerTests.cs index abb0acabc..2aadb3658 100644 --- a/test/PowerShellEditorServices.Test/Console/InputPromptHandlerTests.cs +++ b/test/PowerShellEditorServices.Test/Console/InputPromptHandlerTests.cs @@ -8,7 +8,6 @@ using System; using System.Threading; using System.Security; -using Microsoft.PowerShell.EditorServices.Services.PowerShellContext; using Microsoft.Extensions.Logging.Abstractions; namespace Microsoft.PowerShell.EditorServices.Test.Console diff --git a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs index 42386192c..a01ddc20d 100644 --- a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs @@ -3,34 +3,36 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Management.Automation; using System.Threading; using System.Threading.Tasks; +using MediatR; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.PowerShell.EditorServices.Services; using Microsoft.PowerShell.EditorServices.Services.DebugAdapter; -using Microsoft.PowerShell.EditorServices.Services.PowerShellContext; +using Microsoft.PowerShell.EditorServices.Services.PowerShell; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; using Microsoft.PowerShell.EditorServices.Services.TextDocument; using Microsoft.PowerShell.EditorServices.Test.Shared; -using Microsoft.PowerShell.EditorServices.Utility; +using Newtonsoft.Json.Linq; +using OmniSharp.Extensions.JsonRpc; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using OmniSharp.Extensions.LanguageServer.Protocol.Progress; +using OmniSharp.Extensions.LanguageServer.Protocol.Server; using Xunit; namespace Microsoft.PowerShell.EditorServices.Test.Debugging { + /* public class DebugServiceTests : IDisposable { private WorkspaceService workspace; private DebugService debugService; private ScriptFile debugScriptFile; private ScriptFile variableScriptFile; - private PowerShellContextService powerShellContext; - - private AsyncQueue debuggerStoppedQueue = - new AsyncQueue(); - private AsyncQueue sessionStateQueue = - new AsyncQueue(); private ScriptFile GetDebugScript(string fileName) { @@ -44,6 +46,8 @@ private ScriptFile GetDebugScript(string fileName) public DebugServiceTests() { + var loggerFactory = new NullLoggerFactory(); + var logger = NullLogger.Instance; this.powerShellContext = PowerShellContextFactory.Create(logger); @@ -1067,4 +1071,5 @@ await this.powerShellContext.ExecuteCommandAsync( .AddParameter("Script", scriptFile.FilePath)).ConfigureAwait(false); } } + */ } diff --git a/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs b/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs index c7b5ac37c..ec5d3169d 100644 --- a/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs @@ -10,6 +10,7 @@ using Microsoft.Extensions.Logging.Abstractions; using Microsoft.PowerShell.EditorServices.Handlers; using Microsoft.PowerShell.EditorServices.Services; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; using Microsoft.PowerShell.EditorServices.Services.Symbols; using Microsoft.PowerShell.EditorServices.Services.TextDocument; using Microsoft.PowerShell.EditorServices.Test.Shared; @@ -26,12 +27,13 @@ namespace Microsoft.PowerShell.EditorServices.Test.Language { + /* public class LanguageServiceTests : IDisposable { private readonly WorkspaceService workspace; private readonly SymbolsService symbolsService; private readonly PsesCompletionHandler completionHandler; - private readonly PowerShellContextService powerShellContext; + private readonly PsesInternalHost _psesHost; private static readonly string s_baseSharedScriptPath = Path.Combine( Path.GetDirectoryName(VersionUtils.IsWindows @@ -44,16 +46,16 @@ public class LanguageServiceTests : IDisposable public LanguageServiceTests() { - var logger = NullLogger.Instance; - powerShellContext = PowerShellContextFactory.Create(logger); + _psesHost = PsesHostFactory.Create(NullLoggerFactory.Instance); + workspace = new WorkspaceService(NullLoggerFactory.Instance); - symbolsService = new SymbolsService(NullLoggerFactory.Instance, powerShellContext, workspace, new ConfigurationService()); - completionHandler = new PsesCompletionHandler(NullLoggerFactory.Instance, powerShellContext, workspace); + symbolsService = new SymbolsService(NullLoggerFactory.Instance, _psesHost, _psesHost, workspace, new ConfigurationService()); + completionHandler = new PsesCompletionHandler(NullLoggerFactory.Instance, _psesHost, _psesHost, workspace); } public void Dispose() { - this.powerShellContext.Close(); + // TODO: Dispose of the host } [Trait("Category", "Completions")] @@ -463,14 +465,12 @@ await this.completionHandler.GetCompletionsInFileAsync( scriptRegion.StartColumnNumber).ConfigureAwait(false); } - private async Task GetParamSetSignatures(ScriptRegion scriptRegion) + private Task GetParamSetSignatures(ScriptRegion scriptRegion) { - return - await this.symbolsService.FindParameterSetsInFileAsync( + return this.symbolsService.FindParameterSetsInFileAsync( GetScriptFile(scriptRegion), scriptRegion.StartLineNumber, - scriptRegion.StartColumnNumber, - powerShellContext).ConfigureAwait(false); + scriptRegion.StartColumnNumber); } private async Task GetDefinition(ScriptRegion scriptRegion) @@ -526,4 +526,5 @@ private List FindSymbolsInFile(ScriptRegion scriptRegion) GetScriptFile(scriptRegion)); } } + */ } diff --git a/test/PowerShellEditorServices.Test/PowerShellContextFactory.cs b/test/PowerShellEditorServices.Test/PsesHostFactory.cs similarity index 58% rename from test/PowerShellEditorServices.Test/PowerShellContextFactory.cs rename to test/PowerShellEditorServices.Test/PsesHostFactory.cs index bd5f1a27a..6f843f12d 100644 --- a/test/PowerShellEditorServices.Test/PowerShellContextFactory.cs +++ b/test/PowerShellEditorServices.Test/PsesHostFactory.cs @@ -3,21 +3,24 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.IO; +using System.Management.Automation; +using System.Management.Automation.Host; using System.Management.Automation.Runspaces; +using System.Security; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.PowerShell.EditorServices.Hosting; -using Microsoft.PowerShell.EditorServices.Services; -using Microsoft.PowerShell.EditorServices.Services.PowerShellContext; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; using Microsoft.PowerShell.EditorServices.Test.Shared; using Microsoft.PowerShell.EditorServices.Utility; namespace Microsoft.PowerShell.EditorServices.Test { - internal static class PowerShellContextFactory + internal static class PsesHostFactory { // NOTE: These paths are arbitrarily chosen just to verify that the profile paths // can be set to whatever they need to be for the given host. @@ -38,10 +41,8 @@ internal static class PowerShellContextFactory public static System.Management.Automation.Runspaces.Runspace InitialRunspace; - public static PowerShellContextService Create(ILogger logger) + public static PsesInternalHost Create(ILoggerFactory loggerFactory) { - PowerShellContextService powerShellContext = new PowerShellContextService(logger, null, isPSReadLineEnabled: false); - // We intentionally use `CreateDefault2()` as it loads `Microsoft.PowerShell.Core` only, // which is a more minimal and therefore safer state. var initialSessionState = InitialSessionState.CreateDefault2(); @@ -60,66 +61,23 @@ public static PowerShellContextService Create(ILogger logger) "PowerShell Editor Services Test Host", "Test.PowerShellEditorServices", new Version("1.0.0"), - null, + psHost: null, TestProfilePaths, - new List(), - new List(), + featureFlags: Array.Empty(), + additionalModules: Array.Empty(), initialSessionState, - null, - 0, + logPath: null, + (int)LogLevel.None, consoleReplEnabled: false, usesLegacyReadLine: false, bundledModulePath: BundledModulePath); - InitialRunspace = PowerShellContextService.CreateTestRunspace( - testHostDetails, - powerShellContext, - new TestPSHostUserInterface(powerShellContext, logger), - logger); + var psesHost = new PsesInternalHost(loggerFactory, null, testHostDetails); - powerShellContext.Initialize( - TestProfilePaths, - InitialRunspace, - ownsInitialRunspace: true, - consoleHost: null); + psesHost.TryStartAsync(new HostStartOptions { LoadProfiles = true }, CancellationToken.None).GetAwaiter().GetResult(); - return powerShellContext; - } - } - - internal class TestPSHostUserInterface : EditorServicesPSHostUserInterface - { - public TestPSHostUserInterface( - PowerShellContextService powerShellContext, - ILogger logger) - : base( - powerShellContext, - new SimplePSHostRawUserInterface(logger), - NullLogger.Instance) - { - } - - public override void WriteOutput(string outputString, bool includeNewLine, OutputType outputType, ConsoleColor foregroundColor, ConsoleColor backgroundColor) - { - } - - protected override ChoicePromptHandler OnCreateChoicePromptHandler() - { - throw new NotImplementedException(); - } - - protected override InputPromptHandler OnCreateInputPromptHandler() - { - throw new NotImplementedException(); - } - - protected override Task ReadCommandLineAsync(CancellationToken cancellationToken) - { - return Task.FromResult("USER COMMAND"); - } - - protected override void UpdateProgress(long sourceId, ProgressDetails progressDetails) - { + return psesHost; } } } + diff --git a/test/PowerShellEditorServices.Test/Session/PathEscapingTests.cs b/test/PowerShellEditorServices.Test/Session/PathEscapingTests.cs index 510bff2c6..495166e09 100644 --- a/test/PowerShellEditorServices.Test/Session/PathEscapingTests.cs +++ b/test/PowerShellEditorServices.Test/Session/PathEscapingTests.cs @@ -4,6 +4,7 @@ using Xunit; using System.IO; using Microsoft.PowerShell.EditorServices.Services; +using Microsoft.PowerShell.EditorServices.Utility; namespace Microsoft.PowerShell.EditorServices.Test.Session { @@ -28,7 +29,7 @@ public class PathEscapingTests [InlineData("C:\\&nimals\\утка\\qu*ck?.ps1", "C:\\&nimals\\утка\\qu`*ck`?.ps1")] public void CorrectlyWildcardEscapesPaths_NoSpaces(string unescapedPath, string escapedPath) { - string extensionEscapedPath = PowerShellContextService.WildcardEscapePath(unescapedPath); + string extensionEscapedPath = PathUtils.WildcardEscapePath(unescapedPath); Assert.Equal(escapedPath, extensionEscapedPath); } @@ -49,7 +50,7 @@ public void CorrectlyWildcardEscapesPaths_NoSpaces(string unescapedPath, string [InlineData("C:\\&nimals\\утка\\qu*ck?.ps1", "C:\\&nimals\\утка\\qu`*ck`?.ps1")] public void CorrectlyWildcardEscapesPaths_Spaces(string unescapedPath, string escapedPath) { - string extensionEscapedPath = PowerShellContextService.WildcardEscapePath(unescapedPath, escapeSpaces: true); + string extensionEscapedPath = PathUtils.WildcardEscapePath(unescapedPath, escapeSpaces: true); Assert.Equal(escapedPath, extensionEscapedPath); } @@ -71,7 +72,7 @@ public void CorrectlyWildcardEscapesPaths_Spaces(string unescapedPath, string es [InlineData("C:\\&nimals\\утка\\qu*ck?.ps1", "'C:\\&nimals\\утка\\qu*ck?.ps1'")] public void CorrectlyQuoteEscapesPaths(string unquotedPath, string expectedQuotedPath) { - string extensionQuotedPath = PowerShellContextService.QuoteEscapeString(unquotedPath); + string extensionQuotedPath = StringEscaping.SingleQuoteAndEscape(unquotedPath).ToString(); Assert.Equal(expectedQuotedPath, extensionQuotedPath); } @@ -93,31 +94,10 @@ public void CorrectlyQuoteEscapesPaths(string unquotedPath, string expectedQuote [InlineData("C:\\&nimals\\утка\\qu*ck?.ps1", "'C:\\&nimals\\утка\\qu`*ck`?.ps1'")] public void CorrectlyFullyEscapesPaths(string unescapedPath, string escapedPath) { - string extensionEscapedPath = PowerShellContextService.FullyPowerShellEscapePath(unescapedPath); + string extensionEscapedPath = StringEscaping.SingleQuoteAndEscape(PathUtils.WildcardEscapePath(unescapedPath)).ToString(); Assert.Equal(escapedPath, extensionEscapedPath); } - [Trait("Category", "PathEscaping")] - [Theory] - [InlineData("DebugTest.ps1", "DebugTest.ps1")] - [InlineData("../../DebugTest.ps1", "../../DebugTest.ps1")] - [InlineData("C:\\Users\\me\\Documents\\DebugTest.ps1", "C:\\Users\\me\\Documents\\DebugTest.ps1")] - [InlineData("/home/me/Documents/weird&folder/script.ps1", "/home/me/Documents/weird&folder/script.ps1")] - [InlineData("./path/with` some/spaces", "./path/with some/spaces")] - [InlineData("C:\\path\\with`[some`]brackets\\file.ps1", "C:\\path\\with[some]brackets\\file.ps1")] - [InlineData("C:\\look\\an`*\\here.ps1", "C:\\look\\an*\\here.ps1")] - [InlineData("/Users/me/Documents/`?here.ps1", "/Users/me/Documents/?here.ps1")] - [InlineData("/Brackets` `[and` s`]paces/path.ps1", "/Brackets [and s]paces/path.ps1")] - [InlineData("/CJK` chars/脚本/hello.ps1", "/CJK chars/脚本/hello.ps1")] - [InlineData("/CJK` chars/脚本/`[hello`].ps1", "/CJK chars/脚本/[hello].ps1")] - [InlineData("C:\\Animal` s\\утка\\quack.ps1", "C:\\Animal s\\утка\\quack.ps1")] - [InlineData("C:\\&nimals\\утка\\qu`*ck`?.ps1", "C:\\&nimals\\утка\\qu*ck?.ps1")] - public void CorrectlyUnescapesPaths(string escapedPath, string expectedUnescapedPath) - { - string extensionUnescapedPath = PowerShellContextService.UnescapeWildcardEscapedPath(escapedPath); - Assert.Equal(expectedUnescapedPath, extensionUnescapedPath); - } - [Trait("Category", "PathEscaping")] [Theory] [InlineData("NormalScript.ps1")] @@ -126,7 +106,7 @@ public void CorrectlyUnescapesPaths(string escapedPath, string expectedUnescaped public void CanDotSourcePath(string rawFileName) { string fullPath = Path.Combine(ScriptAssetPath, rawFileName); - string quotedPath = PowerShellContextService.QuoteEscapeString(fullPath); + string quotedPath = StringEscaping.SingleQuoteAndEscape(fullPath).ToString(); var psCommand = new System.Management.Automation.PSCommand().AddScript($". {quotedPath}"); diff --git a/test/PowerShellEditorServices.Test/Session/PowerShellContextTests.cs b/test/PowerShellEditorServices.Test/Session/PowerShellContextTests.cs index fc15c7854..d65a186ac 100644 --- a/test/PowerShellEditorServices.Test/Session/PowerShellContextTests.cs +++ b/test/PowerShellEditorServices.Test/Session/PowerShellContextTests.cs @@ -9,13 +9,13 @@ using System.Threading.Tasks; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.PowerShell.EditorServices.Services; -using Microsoft.PowerShell.EditorServices.Services.PowerShellContext; using Microsoft.PowerShell.EditorServices.Test.Shared; using Microsoft.PowerShell.EditorServices.Utility; using Xunit; namespace Microsoft.PowerShell.EditorServices.Test.Console { + /* public class PowerShellContextTests : IDisposable { // Borrowed from `VersionUtils` which can't be used here due to an initialization problem. @@ -174,4 +174,5 @@ private void OnSessionStateChanged(object sender, SessionStateChangedEventArgs e #endregion } + */ } diff --git a/test/PowerShellEditorServices.Test/Session/ScriptFileTests.cs b/test/PowerShellEditorServices.Test/Session/ScriptFileTests.cs index 4e716a1d2..91b6a1e14 100644 --- a/test/PowerShellEditorServices.Test/Session/ScriptFileTests.cs +++ b/test/PowerShellEditorServices.Test/Session/ScriptFileTests.cs @@ -16,7 +16,7 @@ public class ScriptFileChangeTests { #if CoreCLR - private static readonly Version PowerShellVersion = new Version(6, 2); + private static readonly Version PowerShellVersion = new Version(7, 2); #else private static readonly Version PowerShellVersion = new Version(5, 1); #endif diff --git a/test/PowerShellEditorServices.Test/Utility/AsyncLockTests.cs b/test/PowerShellEditorServices.Test/Utility/AsyncLockTests.cs deleted file mode 100644 index 99b8bb05c..000000000 --- a/test/PowerShellEditorServices.Test/Utility/AsyncLockTests.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Microsoft.PowerShell.EditorServices.Utility; -using System; -using System.Threading; -using System.Threading.Tasks; -using Xunit; - -namespace Microsoft.PowerShell.EditorServices.Test.Utility -{ - public class AsyncLockTests - { - [Fact] - public async Task AsyncLockSynchronizesAccess() - { - AsyncLock asyncLock = new AsyncLock(); - - Task lockOne = asyncLock.LockAsync(); - Task lockTwo = asyncLock.LockAsync(); - - Assert.Equal(TaskStatus.RanToCompletion, lockOne.Status); - Assert.Equal(TaskStatus.WaitingForActivation, lockTwo.Status); - lockOne.Result.Dispose(); - - await lockTwo.ConfigureAwait(false); - Assert.Equal(TaskStatus.RanToCompletion, lockTwo.Status); - } - - [Fact] - public void AsyncLockCancelsWhenRequested() - { - CancellationTokenSource cts = new CancellationTokenSource(); - AsyncLock asyncLock = new AsyncLock(); - - Task lockOne = asyncLock.LockAsync(); - Task lockTwo = asyncLock.LockAsync(cts.Token); - - // Cancel the second lock before the first is released - cts.Cancel(); - lockOne.Result.Dispose(); - - Assert.Equal(TaskStatus.RanToCompletion, lockOne.Status); - Assert.Equal(TaskStatus.Canceled, lockTwo.Status); - } - } -} diff --git a/test/PowerShellEditorServices.Test/Utility/AsyncQueueTests.cs b/test/PowerShellEditorServices.Test/Utility/AsyncQueueTests.cs deleted file mode 100644 index 70c31f444..000000000 --- a/test/PowerShellEditorServices.Test/Utility/AsyncQueueTests.cs +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Microsoft.PowerShell.EditorServices.Utility; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Xunit; - -namespace Microsoft.PowerShell.EditorServices.Test.Utility -{ - public class AsyncQueueTests - { - [Fact] - public async Task AsyncQueueSynchronizesAccess() - { - ConcurrentBag outputItems = new ConcurrentBag(); - AsyncQueue inputQueue = new AsyncQueue(Enumerable.Range(0, 100)); - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - - try - { - // Start 5 consumers - await Task.WhenAll( - Task.Run(() => ConsumeItemsAsync(inputQueue, outputItems, cancellationTokenSource.Token)), - Task.Run(() => ConsumeItemsAsync(inputQueue, outputItems, cancellationTokenSource.Token)), - Task.Run(() => ConsumeItemsAsync(inputQueue, outputItems, cancellationTokenSource.Token)), - Task.Run(() => ConsumeItemsAsync(inputQueue, outputItems, cancellationTokenSource.Token)), - Task.Run(() => ConsumeItemsAsync(inputQueue, outputItems, cancellationTokenSource.Token)), - Task.Run( - async () => - { - // Wait for a bit and then add more items to the queue - await Task.Delay(250).ConfigureAwait(false); - - foreach (var i in Enumerable.Range(100, 200)) - { - await inputQueue.EnqueueAsync(i).ConfigureAwait(false); - } - - // Cancel the waiters - cancellationTokenSource.Cancel(); - })).ConfigureAwait(false); - } - catch (TaskCanceledException) - { - // Do nothing, this is expected. - } - - // At this point, numbers 0 through 299 should be in the outputItems - IEnumerable expectedItems = Enumerable.Range(0, 300); - Assert.Empty(expectedItems.Except(outputItems)); - } - - [Fact] - public async Task AsyncQueueSkipsCancelledTasks() - { - AsyncQueue inputQueue = new AsyncQueue(); - - // Queue up a couple of tasks to wait for input - CancellationTokenSource cancellationSource = new CancellationTokenSource(); - Task taskOne = inputQueue.DequeueAsync(cancellationSource.Token); - Task taskTwo = inputQueue.DequeueAsync(); - - // Cancel the first task and then enqueue a number - cancellationSource.Cancel(); - await inputQueue.EnqueueAsync(1).ConfigureAwait(false); - - // Wait for things to propegate. - await Task.Delay(1000).ConfigureAwait(false); - - // Did the second task get the number? - Assert.Equal(TaskStatus.Canceled, taskOne.Status); - Assert.Equal(TaskStatus.RanToCompletion, taskTwo.Status); - Assert.Equal(1, taskTwo.Result); - } - - private static async Task ConsumeItemsAsync( - AsyncQueue inputQueue, - ConcurrentBag outputItems, - CancellationToken cancellationToken) - { - while (!cancellationToken.IsCancellationRequested) - { - int consumedItem = await inputQueue.DequeueAsync(cancellationToken).ConfigureAwait(false); - outputItems.Add(consumedItem); - } - } - } -}