From dfee772a883987d579bb60ec0672b5b0dae44eba Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 14 Oct 2021 09:28:30 -0700 Subject: [PATCH 01/17] Initial test build --- integrated.sln | 153 ------------------ .../Handlers/ConfigurationDoneHandler.cs | 29 +--- .../Utility/StringEscaping.cs | 37 +++++ .../LanguageServerProtocolMessageTests.cs | 2 + .../Console/ChoicePromptHandlerTests.cs | 3 +- .../Console/InputPromptHandlerTests.cs | 1 - .../Debugging/DebugServiceTests.cs | 21 ++- .../Language/LanguageServiceTests.cs | 2 + .../PowerShellContextFactory.cs | 3 +- .../Session/PathEscapingTests.cs | 32 +--- .../Session/PowerShellContextTests.cs | 3 +- .../Session/ScriptFileTests.cs | 2 +- .../Utility/AsyncLockTests.cs | 47 ------ .../Utility/AsyncQueueTests.cs | 92 ----------- 14 files changed, 71 insertions(+), 356 deletions(-) delete mode 100644 integrated.sln create mode 100644 src/PowerShellEditorServices/Utility/StringEscaping.cs delete mode 100644 test/PowerShellEditorServices.Test/Utility/AsyncLockTests.cs delete mode 100644 test/PowerShellEditorServices.Test/Utility/AsyncQueueTests.cs 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/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/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/LanguageServerProtocolMessageTests.cs b/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs index 276df5666..1661cebeb 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 { 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..fdb709c1b 100644 --- a/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs @@ -26,6 +26,7 @@ namespace Microsoft.PowerShell.EditorServices.Test.Language { + /* public class LanguageServiceTests : IDisposable { private readonly WorkspaceService workspace; @@ -526,4 +527,5 @@ private List FindSymbolsInFile(ScriptRegion scriptRegion) GetScriptFile(scriptRegion)); } } + */ } diff --git a/test/PowerShellEditorServices.Test/PowerShellContextFactory.cs b/test/PowerShellEditorServices.Test/PowerShellContextFactory.cs index bd5f1a27a..64daeab62 100644 --- a/test/PowerShellEditorServices.Test/PowerShellContextFactory.cs +++ b/test/PowerShellEditorServices.Test/PowerShellContextFactory.cs @@ -11,12 +11,12 @@ 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.Test.Shared; using Microsoft.PowerShell.EditorServices.Utility; namespace Microsoft.PowerShell.EditorServices.Test { + /* internal static class PowerShellContextFactory { // NOTE: These paths are arbitrarily chosen just to verify that the profile paths @@ -122,4 +122,5 @@ protected override void UpdateProgress(long sourceId, ProgressDetails progressDe { } } + */ } 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); - } - } - } -} From 61ae8f23b6e3dae81305d152685d8f494a440b0b Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 14 Oct 2021 15:03:06 -0700 Subject: [PATCH 02/17] Add Host factory --- .../Language/LanguageServiceTests.cs | 23 +++--- ...llContextFactory.cs => PsesHostFactory.cs} | 75 ++++--------------- 2 files changed, 26 insertions(+), 72 deletions(-) rename test/PowerShellEditorServices.Test/{PowerShellContextFactory.cs => PsesHostFactory.cs} (59%) diff --git a/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs b/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs index fdb709c1b..ca6487012 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,13 +27,12 @@ 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 @@ -45,16 +45,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")] @@ -464,14 +464,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) @@ -527,5 +525,4 @@ private List FindSymbolsInFile(ScriptRegion scriptRegion) GetScriptFile(scriptRegion)); } } - */ } diff --git a/test/PowerShellEditorServices.Test/PowerShellContextFactory.cs b/test/PowerShellEditorServices.Test/PsesHostFactory.cs similarity index 59% rename from test/PowerShellEditorServices.Test/PowerShellContextFactory.cs rename to test/PowerShellEditorServices.Test/PsesHostFactory.cs index 64daeab62..5d11ea02f 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.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,67 +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.StartAsync(new HostStartOptions { LoadProfiles = true }, CancellationToken.None).GetAwaiter().GetResult(); - return powerShellContext; + return psesHost; } } - - 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) - { - } - } - */ } + From def819ef630a46e12fd640c5ca33011016268ba0 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Tue, 19 Oct 2021 15:42:07 -0700 Subject: [PATCH 03/17] Enable host with no repl --- .../PowerShell/Host/PsesInternalHost.cs | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs index c3ebd9bb8..db7f3b765 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs @@ -471,7 +471,39 @@ private void RunTopLevelExecutionLoop() task.ExecuteSynchronously(CancellationToken.None); } - RunExecutionLoop(); + if (_hostInfo.ConsoleReplEnabled) + { + RunExecutionLoop(); + } + else + { + RunNoPromptExecutionLoop(); + } + } + + private void RunNoPromptExecutionLoop() + { + while (!_shouldExit) + { + 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() From e6f21dcdc19ce561c5bf9b21b0807dc4f78f2789 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Tue, 19 Oct 2021 15:56:38 -0700 Subject: [PATCH 04/17] Comment out LSP unit tests --- .../Language/LanguageServiceTests.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs b/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs index ca6487012..ec5d3169d 100644 --- a/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs @@ -27,6 +27,7 @@ namespace Microsoft.PowerShell.EditorServices.Test.Language { + /* public class LanguageServiceTests : IDisposable { private readonly WorkspaceService workspace; @@ -525,4 +526,5 @@ private List FindSymbolsInFile(ScriptRegion scriptRegion) GetScriptFile(scriptRegion)); } } + */ } From 2daa962984064071b8ecfb111031e5e7eecd1b8c Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Tue, 19 Oct 2021 16:00:12 -0700 Subject: [PATCH 05/17] Bump CI From 702636c23f8fe2ae5fb64db36275b060a62c9800 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Wed, 20 Oct 2021 10:29:11 -0700 Subject: [PATCH 06/17] Add timeout to test --- .../LanguageServerProtocolMessageTests.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs b/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs index 1661cebeb..6edf649b0 100644 --- a/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs +++ b/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs @@ -1153,6 +1153,8 @@ await PsesLanguageClient [Fact] public async Task CanSendEvaluateRequestAsync() { + using var cancellationSource = new CancellationTokenSource(millisecondsDelay: 5000); + EvaluateResponseBody evaluateResponseBody = await PsesLanguageClient .SendRequest( @@ -1161,7 +1163,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); From d5aebd6d2b56eb3f6b3084c6415b8bf2f476ae23 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Wed, 20 Oct 2021 10:52:08 -0700 Subject: [PATCH 07/17] Ensure CI is triggered for PRs not for master --- .vsts-ci/azure-pipelines-ci.yml | 8 -------- 1 file changed, 8 deletions(-) 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 From d1acc64e9d492a91e30c3d1c176a7c6b94e7c837 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Wed, 20 Oct 2021 16:59:17 -0700 Subject: [PATCH 08/17] Route startup exceptions through and ensure startup has completed before proceeding --- .../Server/PsesDebugServer.cs | 23 ++++- .../Server/PsesLanguageServer.cs | 17 +++- .../Debugging/PowerShellDebugContext.cs | 7 +- .../PowerShell/Host/PsesInternalHost.cs | 93 ++++++++++++++----- .../Handlers/ConfigurationHandler.cs | 2 +- .../DebugAdapterProtocolMessageTests.cs | 13 +++ .../LSPTestsFixures.cs | 2 + .../LanguageServerProtocolMessageTests.cs | 2 + .../Processes/StdioServerProcess.cs | 47 +++++++++- .../xunit.runner.json | 4 +- 10 files changed, 178 insertions(+), 32 deletions(-) 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/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/Host/PsesInternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs index db7f3b765..9d56816c3 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; @@ -128,12 +134,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 +182,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 +217,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 +377,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,25 +501,40 @@ 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); + } - if (_hostInfo.ConsoleReplEnabled) - { - RunExecutionLoop(); + // Signal that we are ready for outside services to use + _started.TrySetResult(true); + + if (_hostInfo.ConsoleReplEnabled) + { + RunExecutionLoop(); + } + else + { + RunNoPromptExecutionLoop(); + } } - else + catch (Exception e) { - RunNoPromptExecutionLoop(); + _logger.LogError(e, "PSES pipeline thread loop experienced an unexpected top-level exception"); + _stopped.TrySetException(e); + return; } + + _logger.LogInformation("PSES pipeline thread loop shutting down"); + _stopped.SetResult(true); } private void RunNoPromptExecutionLoop() { - while (!_shouldExit) + while (!ShouldExitExecutionLoop) { using (CancellationScope cancellationScope = _cancellationContext.EnterScope(isIdleScope: false)) { @@ -521,13 +572,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)) { @@ -806,7 +857,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/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs b/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs index bf8b193cc..68722d272 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: diff --git a/test/PowerShellEditorServices.Test.E2E/LSPTestsFixures.cs b/test/PowerShellEditorServices.Test.E2E/LSPTestsFixures.cs index 3bdd47096..173554dd9 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; @@ -44,6 +45,7 @@ public async Task InitializeAsync() var factory = new LoggerFactory(); _psesProcess = new PsesStdioProcess(factory, IsDebugAdapterTests); await _psesProcess.Start().ConfigureAwait(false); + //Debugger.Launch(); Diagnostics = new List(); TelemetryEvents = new List(); diff --git a/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs b/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs index 6edf649b0..6852bff3a 100644 --- a/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs +++ b/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs @@ -41,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) { @@ -50,6 +51,7 @@ public LanguageServerProtocolMessageTests(ITestOutputHelper output, LSPTestsFixt Diagnostics.Clear(); TelemetryEvents = data.TelemetryEvents; TelemetryEvents.Clear(); + _fixture = data; PwshExe = PsesStdioProcess.PwshExe; } diff --git a/test/PowerShellEditorServices.Test.E2E/Processes/StdioServerProcess.cs b/test/PowerShellEditorServices.Test.E2E/Processes/StdioServerProcess.cs index 585e1bf03..2d0a9958c 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. /// @@ -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 { @@ -126,6 +129,8 @@ public override async Task Stop() await ServerExitCompletion.Task.ConfigureAwait(false); } + 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 } From 5d94ad6ccecceaa6ac0c126f5017e8b184bd1520 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Wed, 20 Oct 2021 17:07:06 -0700 Subject: [PATCH 09/17] Fix test compile error --- test/PowerShellEditorServices.Test/PsesHostFactory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/PowerShellEditorServices.Test/PsesHostFactory.cs b/test/PowerShellEditorServices.Test/PsesHostFactory.cs index 5d11ea02f..6f843f12d 100644 --- a/test/PowerShellEditorServices.Test/PsesHostFactory.cs +++ b/test/PowerShellEditorServices.Test/PsesHostFactory.cs @@ -74,7 +74,7 @@ public static PsesInternalHost Create(ILoggerFactory loggerFactory) var psesHost = new PsesInternalHost(loggerFactory, null, testHostDetails); - psesHost.StartAsync(new HostStartOptions { LoadProfiles = true }, CancellationToken.None).GetAwaiter().GetResult(); + psesHost.TryStartAsync(new HostStartOptions { LoadProfiles = true }, CancellationToken.None).GetAwaiter().GetResult(); return psesHost; } From b0edca921d028ed0d6eef7ce809fb47bccc9f08e Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 25 Oct 2021 12:20:00 -0700 Subject: [PATCH 10/17] Fix process exit throwing --- .../Services/PowerShell/Handlers/EvaluateHandler.cs | 4 +++- .../Processes/StdioServerProcess.cs | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) 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/test/PowerShellEditorServices.Test.E2E/Processes/StdioServerProcess.cs b/test/PowerShellEditorServices.Test.E2E/Processes/StdioServerProcess.cs index 2d0a9958c..1885a655e 100644 --- a/test/PowerShellEditorServices.Test.E2E/Processes/StdioServerProcess.cs +++ b/test/PowerShellEditorServices.Test.E2E/Processes/StdioServerProcess.cs @@ -118,15 +118,15 @@ 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; From 2846bef932ff8c77c771bdbc0aa87d95a6b15628 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 25 Oct 2021 13:28:40 -0700 Subject: [PATCH 11/17] Fix bad output when console repl is disabled --- .../PowerShell/Host/NullPSHostRawUI.cs | 58 ++++++++++++ .../Services/PowerShell/Host/NullPSHostUI.cs | 90 +++++++++++++++++++ .../PowerShell/Host/PsesInternalHost.cs | 9 +- 3 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 src/PowerShellEditorServices/Services/PowerShell/Host/NullPSHostRawUI.cs create mode 100644 src/PowerShellEditorServices/Services/PowerShell/Host/NullPSHostUI.cs 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 9d56816c3..6a787657e 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs @@ -103,7 +103,9 @@ public PsesInternalHost( 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; @@ -590,6 +592,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. From 0681d3703117644de64447f87ffc8ef05b1160fe Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 25 Oct 2021 13:41:29 -0700 Subject: [PATCH 12/17] Add debug logging for integration test streams --- .../Processes/LoggingStream.cs | 60 +++++++++++++++++++ .../Processes/ServerProcess.cs | 16 ++++- 2 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 test/PowerShellEditorServices.Test.E2E/Processes/LoggingStream.cs 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. From 196ec3b841d0f59244cc7d87c345af49970e03aa Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 25 Oct 2021 13:43:56 -0700 Subject: [PATCH 13/17] Fix overrides --- .../Processes/StdioServerProcess.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/PowerShellEditorServices.Test.E2E/Processes/StdioServerProcess.cs b/test/PowerShellEditorServices.Test.E2E/Processes/StdioServerProcess.cs index 1885a655e..4a0c96d16 100644 --- a/test/PowerShellEditorServices.Test.E2E/Processes/StdioServerProcess.cs +++ b/test/PowerShellEditorServices.Test.E2E/Processes/StdioServerProcess.cs @@ -78,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. From d22b57cfa1cad176b891c2a95d6c6b6b6d8328be Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 25 Oct 2021 15:18:06 -0700 Subject: [PATCH 14/17] Fix variable test with scope change --- .../DebugAdapterProtocolMessageTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs b/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs index 68722d272..95efa48a9 100644 --- a/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs +++ b/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs @@ -263,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" })); From 5699e481ec81d27956c20d88b01efa42926d5f02 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 25 Oct 2021 15:18:17 -0700 Subject: [PATCH 15/17] Fix assembly log exception --- src/PowerShellEditorServices.Hosting/EditorServicesLoader.cs | 5 +++++ 1 file changed, 5 insertions(+) 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}'"); From ca7c81532ac22214ebeddb555bd27e615bb5c6dd Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Tue, 26 Oct 2021 14:55:02 -0700 Subject: [PATCH 16/17] Only set pipeline thread apartment state on Windows --- .../Services/PowerShell/Host/PsesInternalHost.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs index 6a787657e..05d9d0a92 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs @@ -96,7 +96,10 @@ 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; From fcc4cd8f37ecb9915bfa076a1fb190daa411ef0b Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Tue, 26 Oct 2021 16:00:39 -0700 Subject: [PATCH 17/17] Remove debug code --- test/PowerShellEditorServices.Test.E2E/LSPTestsFixures.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/test/PowerShellEditorServices.Test.E2E/LSPTestsFixures.cs b/test/PowerShellEditorServices.Test.E2E/LSPTestsFixures.cs index 173554dd9..208ea3f6f 100644 --- a/test/PowerShellEditorServices.Test.E2E/LSPTestsFixures.cs +++ b/test/PowerShellEditorServices.Test.E2E/LSPTestsFixures.cs @@ -45,7 +45,6 @@ public async Task InitializeAsync() var factory = new LoggerFactory(); _psesProcess = new PsesStdioProcess(factory, IsDebugAdapterTests); await _psesProcess.Start().ConfigureAwait(false); - //Debugger.Launch(); Diagnostics = new List(); TelemetryEvents = new List();