diff --git a/PowerShellEditorServices.build.ps1 b/PowerShellEditorServices.build.ps1 index 6bde9dd8a..bb7fe352a 100644 --- a/PowerShellEditorServices.build.ps1 +++ b/PowerShellEditorServices.build.ps1 @@ -11,12 +11,13 @@ param( [string]$DefaultModuleRepository = "PSGallery", + [string[]]$VerbosityArgs = @("--verbosity", "quiet", "--nologo"), + # See: https://docs.microsoft.com/en-us/dotnet/core/testing/selective-unit-tests [string]$TestFilter = '', # See: https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-test - # E.g. use @("--logger", "console;verbosity=detailed") for detailed console output instead - [string[]]$TestArgs = @("--logger", "trx") + [string[]]$TestArgs = $VerbosityArgs + @("--logger", "console;verbosity=normal", "--logger", "trx") ) #Requires -Modules @{ModuleName="InvokeBuild"; ModuleVersion="5.0.0"} @@ -38,9 +39,9 @@ $script:BuildInfoPath = [System.IO.Path]::Combine($PSScriptRoot, "src", "PowerSh $script:PsesCommonProps = [xml](Get-Content -Raw "$PSScriptRoot/PowerShellEditorServices.Common.props") $script:NetRuntime = @{ - PS7 = 'netcoreapp3.1' - PS72 = 'net6.0' - Desktop = 'net462' + PS7 = 'netcoreapp3.1' + PS72 = 'net6.0' + Desktop = 'net462' Standard = 'netstandard2.0' } @@ -54,31 +55,31 @@ if (Get-Command git -ErrorAction SilentlyContinue) { git update-index --assume-unchanged "$PSScriptRoot/src/PowerShellEditorServices.Hosting/BuildInfo.cs" } -task FindDotNet { - assert (Get-Command dotnet -ErrorAction SilentlyContinue) "dotnet not found, please install it: https://aka.ms/dotnet-cli" +Task FindDotNet { + Assert (Get-Command dotnet -ErrorAction SilentlyContinue) "dotnet not found, please install it: https://aka.ms/dotnet-cli" # Strip out semantic version metadata so it can be cast to `Version` $existingVersion, $null = (dotnet --version) -split '-' - assert ([Version]$existingVersion -ge [Version]("6.0")) ".NET SDK 6.0 or higher is required, please update it: https://aka.ms/dotnet-cli" + Assert ([Version]$existingVersion -ge [Version]("6.0")) ".NET SDK 6.0 or higher is required, please update it: https://aka.ms/dotnet-cli" # Anywhere other than on a Mac with an M1 processor, we additionally # need the .NET 3.1 runtime for our netcoreapp3.1 framework. if (-not $script:IsAppleM1 -and -not $script:IsArm64) { $runtimes = dotnet --list-runtimes - assert ($runtimes -match "Microsoft.NETCore.App 3.1") ".NET Runtime 3.1 required but not found!" + Assert ($runtimes -match "Microsoft.NETCore.App 3.1") ".NET Runtime 3.1 required but not found!" } Write-Host "Using dotnet v$(dotnet --version) at path $((Get-Command dotnet).Source)" -ForegroundColor Green } -task BinClean { +Task BinClean { Remove-Item $PSScriptRoot\.tmp -Recurse -Force -ErrorAction Ignore Remove-Item $PSScriptRoot\module\PowerShellEditorServices\bin -Recurse -Force -ErrorAction Ignore Remove-Item $PSScriptRoot\module\PowerShellEditorServices.VSCode\bin -Recurse -Force -ErrorAction Ignore } -task Clean FindDotNet, BinClean, { - exec { & dotnet clean } +Task Clean FindDotNet, BinClean, { + Exec { & dotnet clean $VerbosityArgs } Get-ChildItem -Recurse $PSScriptRoot\src\*.nupkg | Remove-Item -Force -ErrorAction Ignore Get-ChildItem $PSScriptRoot\PowerShellEditorServices*.zip | Remove-Item -Force -ErrorAction Ignore Get-ChildItem $PSScriptRoot\module\PowerShellEditorServices\Commands\en-US\*-help.xml | Remove-Item -Force -ErrorAction Ignore @@ -93,7 +94,7 @@ task Clean FindDotNet, BinClean, { } } -task CreateBuildInfo { +Task CreateBuildInfo { $buildVersion = "" $buildOrigin = "Development" $buildCommit = git rev-parse HEAD @@ -147,33 +148,33 @@ namespace Microsoft.PowerShell.EditorServices.Hosting "@ if (Compare-Object $buildInfoContents.Split([Environment]::NewLine) (Get-Content $script:BuildInfoPath)) { - Write-Host "Updating Build Info" + Write-Host "Updating build info." Set-Content -LiteralPath $script:BuildInfoPath -Value $buildInfoContents -Force } } -task SetupHelpForTests { +Task SetupHelpForTests { if (-not (Get-Help Write-Host).Examples) { - Write-Host "Updating help for tests" + Write-Host "Updating help for tests." Update-Help -Module Microsoft.PowerShell.Utility -Force -Scope CurrentUser } } Task Build FindDotNet, CreateBuildInfo, { - exec { & dotnet restore } - exec { & dotnet publish -c $Configuration .\src\PowerShellEditorServices\PowerShellEditorServices.csproj -f $script:NetRuntime.Standard } - exec { & dotnet publish -c $Configuration .\src\PowerShellEditorServices.Hosting\PowerShellEditorServices.Hosting.csproj -f $script:NetRuntime.PS7 } + Exec { & dotnet restore $VerbosityArgs } + Exec { & dotnet publish $VerbosityArgs -c $Configuration .\src\PowerShellEditorServices\PowerShellEditorServices.csproj -f $script:NetRuntime.Standard } + Exec { & dotnet publish $VerbosityArgs -c $Configuration .\src\PowerShellEditorServices.Hosting\PowerShellEditorServices.Hosting.csproj -f $script:NetRuntime.PS7 } if (-not $script:IsNix) { - exec { & dotnet publish -c $Configuration .\src\PowerShellEditorServices.Hosting\PowerShellEditorServices.Hosting.csproj -f $script:NetRuntime.Desktop } + Exec { & dotnet publish $VerbosityArgs -c $Configuration .\src\PowerShellEditorServices.Hosting\PowerShellEditorServices.Hosting.csproj -f $script:NetRuntime.Desktop } } # Build PowerShellEditorServices.VSCode module - exec { & dotnet publish -c $Configuration .\src\PowerShellEditorServices.VSCode\PowerShellEditorServices.VSCode.csproj -f $script:NetRuntime.Standard } + Exec { & dotnet publish $VerbosityArgs -c $Configuration .\src\PowerShellEditorServices.VSCode\PowerShellEditorServices.VSCode.csproj -f $script:NetRuntime.Standard } } -task Test TestServer, TestE2E +Task Test TestServer, TestE2E -task TestServer TestServerWinPS, TestServerPS7, TestServerPS72 +Task TestServer TestServerWinPS, TestServerPS7, TestServerPS72 Task TestServerWinPS -If (-not $script:IsNix) Build, SetupHelpForTests, { Set-Location .\test\PowerShellEditorServices.Test\ @@ -181,43 +182,43 @@ Task TestServerWinPS -If (-not $script:IsNix) Build, SetupHelpForTests, { # that is debuggable! If architecture is added, the assembly path gets an # additional folder, necesstiating fixes to find the commands definition # file and test files. - exec { & dotnet $script:dotnetTestArgs $script:NetRuntime.Desktop } + Exec { & dotnet $script:dotnetTestArgs $script:NetRuntime.Desktop } } -task TestServerPS7 -If (-not $script:IsAppleM1 -and -not $script:IsArm64) Build, SetupHelpForTests, { +Task TestServerPS7 -If (-not $script:IsAppleM1 -and -not $script:IsArm64) Build, SetupHelpForTests, { Set-Location .\test\PowerShellEditorServices.Test\ - exec { & dotnet $script:dotnetTestArgs $script:NetRuntime.PS7 } + Exec { & dotnet $script:dotnetTestArgs $script:NetRuntime.PS7 } } -task TestServerPS72 Build, SetupHelpForTests, { +Task TestServerPS72 Build, SetupHelpForTests, { Set-Location .\test\PowerShellEditorServices.Test\ - exec { & dotnet $script:dotnetTestArgs $script:NetRuntime.PS72 } + Exec { & dotnet $script:dotnetTestArgs $script:NetRuntime.PS72 } } -task TestE2E Build, SetupHelpForTests, { +Task TestE2E Build, SetupHelpForTests, { Set-Location .\test\PowerShellEditorServices.Test.E2E\ $env:PWSH_EXE_NAME = if ($IsCoreCLR) { "pwsh" } else { "powershell" } $NetRuntime = if ($IsAppleM1 -or $script:IsArm64) { $script:NetRuntime.PS72 } else { $script:NetRuntime.PS7 } - exec { & dotnet $script:dotnetTestArgs $NetRuntime } + Exec { & dotnet $script:dotnetTestArgs $NetRuntime } - # Run E2E tests in ConstrainedLanguage mode. if (!$script:IsNix) { if (-not [Security.Principal.WindowsIdentity]::GetCurrent().Owner.IsWellKnown("BuiltInAdministratorsSid")) { - Write-Warning 'Skipping E2E CLM tests as they must be ran in an elevated process.' + Write-Warning "Skipping Constrained Language Mode tests as they must be ran in an elevated process." return } try { + Write-Host "Running end-to-end tests in Constrained Language Mode." [System.Environment]::SetEnvironmentVariable("__PSLockdownPolicy", "0x80000007", [System.EnvironmentVariableTarget]::Machine); - exec { & dotnet $script:dotnetTestArgs $script:NetRuntime.PS7 } + Exec { & dotnet $script:dotnetTestArgs $script:NetRuntime.PS7 } } finally { [System.Environment]::SetEnvironmentVariable("__PSLockdownPolicy", $null, [System.EnvironmentVariableTarget]::Machine); } } } -task LayoutModule -After Build { +Task LayoutModule -After Build { $modulesDir = "$PSScriptRoot/module" $psesVSCodeBinOutputPath = "$modulesDir/PowerShellEditorServices.VSCode/bin" $psesOutputPath = "$modulesDir/PowerShellEditorServices" @@ -226,8 +227,8 @@ task LayoutModule -After Build { $psesCoreHostPath = "$psesBinOutputPath/Core" $psesDeskHostPath = "$psesBinOutputPath/Desktop" - foreach ($dir in $psesDepsPath,$psesCoreHostPath,$psesDeskHostPath,$psesVSCodeBinOutputPath) { - New-Item -Force -Path $dir -ItemType Directory + foreach ($dir in $psesDepsPath, $psesCoreHostPath, $psesDeskHostPath, $psesVSCodeBinOutputPath) { + New-Item -Force -Path $dir -ItemType Directory | Out-Null } # Copy Third Party Notices.txt to module folder @@ -310,7 +311,7 @@ task RestorePsesModules -After Build { # Save each module in the modules.json file foreach ($moduleName in $moduleInfos.Keys) { if (Test-Path -Path (Join-Path -Path $submodulePath -ChildPath $moduleName)) { - Write-Host "`tModule '${moduleName}' already detected. Skipping" + Write-Host "`tModule '${moduleName}' already detected, skipping!" continue } @@ -331,9 +332,9 @@ task RestorePsesModules -After Build { } Task BuildCmdletHelp -After LayoutModule { - New-ExternalHelp -Path $PSScriptRoot\module\docs -OutputPath $PSScriptRoot\module\PowerShellEditorServices\Commands\en-US -Force - New-ExternalHelp -Path $PSScriptRoot\module\PowerShellEditorServices.VSCode\docs -OutputPath $PSScriptRoot\module\PowerShellEditorServices.VSCode\en-US -Force + New-ExternalHelp -Path $PSScriptRoot\module\docs -OutputPath $PSScriptRoot\module\PowerShellEditorServices\Commands\en-US -Force | Out-Null + New-ExternalHelp -Path $PSScriptRoot\module\PowerShellEditorServices.VSCode\docs -OutputPath $PSScriptRoot\module\PowerShellEditorServices.VSCode\en-US -Force | Out-Null } # The default task is to run the entire CI build -task . Clean, Build, Test +Task . Clean, Build, Test diff --git a/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs b/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs index c552515de..3c02510b9 100644 --- a/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs +++ b/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs @@ -48,8 +48,9 @@ public static IServiceCollection AddPsesLanguageServices( // is ready, it will be available. NOTE: We cannot await this because it // uses a lazy initialization to avoid a race with the dependency injection // framework, see the EditorObject class for that! +#pragma warning disable VSTHRD110 extensionService.InitializeAsync(); - +#pragma warning restore VSTHRD110 return extensionService; }) .AddSingleton(); diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/DebugEventHandlerService.cs b/src/PowerShellEditorServices/Services/DebugAdapter/DebugEventHandlerService.cs index 69ddb1209..e3237a3b0 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/DebugEventHandlerService.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/DebugEventHandlerService.cs @@ -101,7 +101,7 @@ private void OnRunspaceChanged(object sender, RunspaceChangedEventArgs e) // Sends the InitializedEvent so that the debugger will continue // sending configuration requests _debugStateService.WaitingForAttach = false; - _debugStateService.ServerStarted.SetResult(true); + _debugStateService.ServerStarted.TrySetResult(true); } return; diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs index 4b7cd9acc..580b7cc89 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs @@ -210,7 +210,7 @@ public async Task Handle(PsesLaunchRequestArguments request, Can // Sends the InitializedEvent so that the debugger will continue // sending configuration requests - _debugStateService.ServerStarted.SetResult(true); + _debugStateService.ServerStarted.TrySetResult(true); return new LaunchResponse(); } @@ -440,7 +440,7 @@ await _executionService.ExecutePSCommandAsync( if (runspaceVersion.Version.Major >= 7) { - _debugStateService.ServerStarted.SetResult(true); + _debugStateService.ServerStarted.TrySetResult(true); } return new AttachResponse(); } @@ -453,7 +453,7 @@ await _executionService.ExecutePSCommandAsync( // PSES sends the initialized event at the end of the Launch/Attach handler // The way that the Omnisharp server works is that this OnStarted handler runs after OnInitialized - // (after the Initialize DAP response is sent to the client) but before the _Initalized_ DAP event + // (after the Initialize DAP response is sent to the client) but before the _Initialized_ DAP event // gets sent to the client. Because of the way PSES handles breakpoints, // we can't send the Initialized event until _after_ we finish the Launch/Attach handler. // The flow above depicts this. To achieve this, we wait until _debugStateService.ServerStarted diff --git a/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellContextFrame.cs b/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellContextFrame.cs index d837fb5d2..f75027ba8 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellContextFrame.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellContextFrame.cs @@ -79,7 +79,7 @@ protected virtual void Dispose(bool disposing) public void Dispose() { // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); + Dispose(true); GC.SuppressFinalize(this); } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellVersionDetails.cs b/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellVersionDetails.cs index 957a032f8..4bbdd47e1 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellVersionDetails.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellVersionDetails.cs @@ -1,11 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using Microsoft.Extensions.Logging; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; using System; using System.Collections; using System.Linq; +using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Context { @@ -91,7 +91,7 @@ public PowerShellVersionDetails( /// Gets the PowerShell version details for the given runspace. /// /// An ILogger implementation used for writing log messages. - /// The PowerShell instance for which to to get the version. + /// The PowerShell instance for which to get the version. /// A new PowerShellVersionDetails instance. public static PowerShellVersionDetails GetVersionDetails(ILogger logger, PowerShell pwsh) { diff --git a/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs b/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs index a66b0d93b..36aaad0e2 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs @@ -187,7 +187,7 @@ public void ProcessDebuggerResult(DebuggerCommandResults debuggerResult) RaiseDebuggerResumingEvent(new DebuggerResumingEventArgs(debuggerResult.ResumeAction.Value)); // The Terminate exception is used by the engine for flow control - // when it needs to unwind the callstack out of the debugger. + // when it needs to unwind the call stack out of the debugger. if (debuggerResult.ResumeAction is DebuggerResumeAction.Stop) { throw new TerminateException(); diff --git a/src/PowerShellEditorServices/Services/PowerShell/Handlers/IGetPSHostProcessesHandler.cs b/src/PowerShellEditorServices/Services/PowerShell/Handlers/IGetPSHostProcessesHandler.cs index 896564d1e..0772f4338 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Handlers/IGetPSHostProcessesHandler.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Handlers/IGetPSHostProcessesHandler.cs @@ -7,9 +7,9 @@ namespace Microsoft.PowerShell.EditorServices.Handlers { [Serial, Method("powerShell/getPSHostProcesses")] - internal interface IGetPSHostProcessesHandler : IJsonRpcRequestHandler { } + internal interface IGetPSHostProcessesHandler : IJsonRpcRequestHandler { } - internal class GetPSHostProcesssesParams : IRequest { } + internal class GetPSHostProcessesParams : IRequest { } internal class PSHostProcessResponse { diff --git a/src/PowerShellEditorServices/Services/PowerShell/Handlers/PSHostProcessAndRunspaceHandlers.cs b/src/PowerShellEditorServices/Services/PowerShell/Handlers/PSHostProcessAndRunspaceHandlers.cs index f288a9584..ed0b37b04 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Handlers/PSHostProcessAndRunspaceHandlers.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Handlers/PSHostProcessAndRunspaceHandlers.cs @@ -9,8 +9,8 @@ namespace Microsoft.PowerShell.EditorServices.Handlers { - using Microsoft.PowerShell.EditorServices.Services.PowerShell; using System.Management.Automation; + using Microsoft.PowerShell.EditorServices.Services.PowerShell; internal class PSHostProcessAndRunspaceHandlers : IGetPSHostProcessesHandler, IGetRunspaceHandler { @@ -23,7 +23,7 @@ public PSHostProcessAndRunspaceHandlers(ILoggerFactory factory, IInternalPowerSh _executionService = executionService; } - public Task Handle(GetPSHostProcesssesParams request, CancellationToken cancellationToken) + public Task Handle(GetPSHostProcessesParams request, CancellationToken cancellationToken) { List psHostProcesses = new(); diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs index 828cbf890..97ee2edd7 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System; @@ -291,11 +291,6 @@ public void TriggerShutdown() if (Interlocked.Exchange(ref _shuttingDown, 1) == 0) { _cancellationContext.CancelCurrentTaskStack(); - // NOTE: This is mostly for sanity's sake, as during debugging of tests I became - // concerned that the repeated creation and disposal of the host was not also - // joining and disposing this thread, leaving the tests in a weird state. Because - // the tasks have been canceled, we should be able to join this thread. - _pipelineThread.Join(); } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Utility/CancellationContext.cs b/src/PowerShellEditorServices/Services/PowerShell/Utility/CancellationContext.cs index 10dba79a3..24d121ba0 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Utility/CancellationContext.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Utility/CancellationContext.cs @@ -103,7 +103,18 @@ internal CancellationScope( public CancellationToken CancellationToken => _cancellationSource.Token; - public void Cancel() => _cancellationSource.Cancel(); + public void Cancel() + { + try + { + _cancellationSource.Cancel(); + } + catch (ObjectDisposedException) + { + // We don't want this race condition to cause flaky tests. + // TODO: Find out the cause of the race! + } + } public bool IsIdleScope { get; } diff --git a/test/PowerShellEditorServices.Test.E2E/DebugAdapterClientExtensions.cs b/test/PowerShellEditorServices.Test.E2E/DebugAdapterClientExtensions.cs index 65ac1d20c..3ae9748d1 100644 --- a/test/PowerShellEditorServices.Test.E2E/DebugAdapterClientExtensions.cs +++ b/test/PowerShellEditorServices.Test.E2E/DebugAdapterClientExtensions.cs @@ -6,7 +6,6 @@ using Microsoft.PowerShell.EditorServices.Handlers; using OmniSharp.Extensions.DebugAdapter.Client; using OmniSharp.Extensions.DebugAdapter.Protocol.Requests; -using System.Threading; namespace PowerShellEditorServices.Test.E2E { @@ -29,9 +28,7 @@ public static async Task LaunchScript(this DebugAdapterClient debugAdapterClient } // This will check to see if we received the Initialized event from the server. - await Task.Run( - async () => await started.Task.ConfigureAwait(true), - new CancellationTokenSource(2000).Token).ConfigureAwait(true); + await started.Task.ConfigureAwait(true); } } } diff --git a/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs b/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs index 34352291f..b151b57d0 100644 --- a/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs +++ b/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs @@ -18,6 +18,7 @@ namespace PowerShellEditorServices.Test.E2E { + [Trait("Category", "DAP")] public class DebugAdapterProtocolMessageTests : IAsyncLifetime { private const string TestOutputFileName = "__dapTestOutputFile.txt"; @@ -38,7 +39,7 @@ public async Task InitializeAsync() { LoggerFactory factory = new(); _psesProcess = new PsesStdioProcess(factory, true); - await _psesProcess.Start().ConfigureAwait(false); + await _psesProcess.Start().ConfigureAwait(true); TaskCompletionSource initialized = new(); @@ -89,26 +90,21 @@ public async Task InitializeAsync() // that gets completed when we receive the response to Initialize // This tells us that we are ready to send messages to PSES... but are not stuck waiting for // Initialized. - PsesDebugAdapterClient.Initialize(CancellationToken.None).ConfigureAwait(false); - await initialized.Task.ConfigureAwait(false); +#pragma warning disable CS4014 + PsesDebugAdapterClient.Initialize(CancellationToken.None).ConfigureAwait(true); +#pragma warning restore CS4014 + await initialized.Task.ConfigureAwait(true); } public async Task DisposeAsync() { - try + await PsesDebugAdapterClient.RequestDisconnect(new DisconnectArguments { - await PsesDebugAdapterClient.RequestDisconnect(new DisconnectArguments - { - Restart = false, - TerminateDebuggee = true - }).ConfigureAwait(false); - await _psesProcess.Stop().ConfigureAwait(false); - PsesDebugAdapterClient?.Dispose(); - } - catch (ObjectDisposedException) - { - // Language client has a disposal bug in it - } + Restart = false, + TerminateDebuggee = true + }).ConfigureAwait(true); + await _psesProcess.Stop().ConfigureAwait(true); + PsesDebugAdapterClient?.Dispose(); } private static string NewTestFile(string script, bool isPester = false) @@ -146,9 +142,17 @@ private string GenerateScriptFromLoggingStatements(params string[] logStatements return builder.ToString(); } - private static string[] GetLog() => File.ReadLines(s_testOutputPath).ToArray(); + private static async Task GetLog() + { + while (!File.Exists(s_testOutputPath)) + { + await Task.Delay(1000).ConfigureAwait(true); + } + // Sleep one more time after the file exists so whatever is writing can finish. + await Task.Delay(1000).ConfigureAwait(true); + return File.ReadLines(s_testOutputPath).ToArray(); + } - [Trait("Category", "DAP")] [Fact] public void CanInitializeWithCorrectServerSettings() { @@ -160,31 +164,24 @@ public void CanInitializeWithCorrectServerSettings() Assert.True(PsesDebugAdapterClient.ServerSettings.SupportsSetVariable); } - [Trait("Category", "DAP")] [Fact] public async Task CanLaunchScriptWithNoBreakpointsAsync() { string filePath = NewTestFile(GenerateScriptFromLoggingStatements("works")); - await PsesDebugAdapterClient.LaunchScript(filePath, Started).ConfigureAwait(false); + await PsesDebugAdapterClient.LaunchScript(filePath, Started).ConfigureAwait(true); - ConfigurationDoneResponse configDoneResponse = await PsesDebugAdapterClient.RequestConfigurationDone(new ConfigurationDoneArguments()).ConfigureAwait(false); + ConfigurationDoneResponse configDoneResponse = await PsesDebugAdapterClient.RequestConfigurationDone(new ConfigurationDoneArguments()).ConfigureAwait(true); Assert.NotNull(configDoneResponse); - - // At this point the script should be running so lets give it time - await Task.Delay(2000).ConfigureAwait(false); - - string[] log = GetLog(); - Assert.Equal("works", log[0]); + Assert.Collection(await GetLog().ConfigureAwait(true), + (i) => Assert.Equal("works", i)); } - [Trait("Category", "DAP")] [SkippableFact] public async Task CanSetBreakpointsAsync() { - Skip.If( - PsesStdioProcess.RunningInConstrainedLanguageMode, - "You can't set breakpoints in ConstrainedLanguage mode."); + Skip.If(PsesStdioProcess.RunningInConstrainedLanguageMode, + "Breakpoints can't be set in Constrained Language Mode."); string filePath = NewTestFile(GenerateScriptFromLoggingStatements( "before breakpoint", @@ -192,54 +189,32 @@ public async Task CanSetBreakpointsAsync() "after breakpoint" )); - await PsesDebugAdapterClient.LaunchScript(filePath, Started).ConfigureAwait(false); + await PsesDebugAdapterClient.LaunchScript(filePath, Started).ConfigureAwait(true); // {"command":"setBreakpoints","arguments":{"source":{"name":"dfsdfg.ps1","path":"/Users/tyleonha/Code/PowerShell/Misc/foo/dfsdfg.ps1"},"lines":[2],"breakpoints":[{"line":2}],"sourceModified":false},"type":"request","seq":3} SetBreakpointsResponse setBreakpointsResponse = await PsesDebugAdapterClient.SetBreakpoints(new SetBreakpointsArguments { - Source = new Source - { - Name = Path.GetFileName(filePath), - Path = filePath - }, - Lines = new long[] { 2 }, - Breakpoints = new SourceBreakpoint[] - { - new SourceBreakpoint - { - Line = 2, - } - }, + Source = new Source { Name = Path.GetFileName(filePath), Path = filePath }, + Breakpoints = new SourceBreakpoint[] { new SourceBreakpoint { Line = 2 } }, SourceModified = false, - }).ConfigureAwait(false); + }).ConfigureAwait(true); Breakpoint breakpoint = setBreakpointsResponse.Breakpoints.First(); Assert.True(breakpoint.Verified); Assert.Equal(filePath, breakpoint.Source.Path, ignoreCase: s_isWindows); Assert.Equal(2, breakpoint.Line); - ConfigurationDoneResponse configDoneResponse = await PsesDebugAdapterClient.RequestConfigurationDone(new ConfigurationDoneArguments()).ConfigureAwait(false); + ConfigurationDoneResponse configDoneResponse = await PsesDebugAdapterClient.RequestConfigurationDone(new ConfigurationDoneArguments()).ConfigureAwait(true); Assert.NotNull(configDoneResponse); + Assert.Collection(await GetLog().ConfigureAwait(true), + (i) => Assert.Equal("before breakpoint", i)); + File.Delete(s_testOutputPath); - // At this point the script should be running so lets give it time - await Task.Delay(2000).ConfigureAwait(false); - - string[] log = GetLog(); - Assert.Single(log, (i) => i == "before breakpoint"); - - ContinueResponse continueResponse = await PsesDebugAdapterClient.RequestContinue(new ContinueArguments - { - ThreadId = 1, - }).ConfigureAwait(true); + ContinueResponse continueResponse = await PsesDebugAdapterClient.RequestContinue( + new ContinueArguments { ThreadId = 1 }).ConfigureAwait(true); Assert.NotNull(continueResponse); - - // At this point the script should be running so lets give it time - await Task.Delay(2000).ConfigureAwait(false); - - log = GetLog(); - Assert.Collection(log, - (i) => Assert.Equal("before breakpoint", i), + Assert.Collection(await GetLog().ConfigureAwait(true), (i) => Assert.Equal("at breakpoint", i), (i) => Assert.Equal("after breakpoint", i)); } @@ -255,12 +230,13 @@ public async Task CanSetBreakpointsAsync() // PowerShell, we avoid all issues with our test project (and the xUnit executable) not // having System.Windows.Forms deployed, and can instead rely on the Windows Global Assembly // Cache (GAC) to find it. - [Trait("Category", "DAP")] [SkippableFact] public async Task CanStepPastSystemWindowsForms() { - Skip.IfNot(PsesStdioProcess.IsWindowsPowerShell); - Skip.If(PsesStdioProcess.RunningInConstrainedLanguageMode); + Skip.IfNot(PsesStdioProcess.IsWindowsPowerShell, + "Windows Forms requires Windows PowerShell."); + Skip.If(PsesStdioProcess.RunningInConstrainedLanguageMode, + "Breakpoints can't be set in Constrained Language Mode."); string filePath = NewTestFile(string.Join(Environment.NewLine, new[] { @@ -269,29 +245,24 @@ public async Task CanStepPastSystemWindowsForms() "Write-Host $form" })); - await PsesDebugAdapterClient.LaunchScript(filePath, Started).ConfigureAwait(false); + await PsesDebugAdapterClient.LaunchScript(filePath, Started).ConfigureAwait(true); SetFunctionBreakpointsResponse setBreakpointsResponse = await PsesDebugAdapterClient.SetFunctionBreakpoints( new SetFunctionBreakpointsArguments { Breakpoints = new FunctionBreakpoint[] { new FunctionBreakpoint { Name = "Write-Host", } } - }).ConfigureAwait(false); + }).ConfigureAwait(true); Breakpoint breakpoint = setBreakpointsResponse.Breakpoints.First(); Assert.True(breakpoint.Verified); - ConfigurationDoneResponse configDoneResponse = await PsesDebugAdapterClient.RequestConfigurationDone(new ConfigurationDoneArguments()).ConfigureAwait(false); + ConfigurationDoneResponse configDoneResponse = await PsesDebugAdapterClient.RequestConfigurationDone(new ConfigurationDoneArguments()).ConfigureAwait(true); Assert.NotNull(configDoneResponse); - - // At this point the script should be running so lets give it time - await Task.Delay(2000).ConfigureAwait(false); + await Task.Delay(5000).ConfigureAwait(true); VariablesResponse variablesResponse = await PsesDebugAdapterClient.RequestVariables( - new VariablesArguments - { - VariablesReference = 1 - }).ConfigureAwait(false); + new VariablesArguments { VariablesReference = 1 }).ConfigureAwait(true); Variable form = variablesResponse.Variables.FirstOrDefault(v => v.Name == "$form"); Assert.NotNull(form); @@ -302,7 +273,6 @@ public async Task CanStepPastSystemWindowsForms() // commented. Since in some cases (such as Windows PowerShell, or the script not having a // backing ScriptFile) we just wrap the script with braces, we had a bug where the last // brace would be after the comment. We had to ensure we wrapped with newlines instead. - [Trait("Category", "DAP")] [Fact] public async Task CanLaunchScriptWithCommentedLastLineAsync() { @@ -314,15 +284,12 @@ public async Task CanLaunchScriptWithCommentedLastLineAsync() // PsesLaunchRequestArguments.Script, which is then assigned to // DebugStateService.ScriptToLaunch in that handler, and finally used by the // ConfigurationDoneHandler in LaunchScriptAsync. - await PsesDebugAdapterClient.LaunchScript(script, Started).ConfigureAwait(false); + await PsesDebugAdapterClient.LaunchScript(script, Started).ConfigureAwait(true); - ConfigurationDoneResponse configDoneResponse = await PsesDebugAdapterClient.RequestConfigurationDone(new ConfigurationDoneArguments()).ConfigureAwait(false); + ConfigurationDoneResponse configDoneResponse = await PsesDebugAdapterClient.RequestConfigurationDone(new ConfigurationDoneArguments()).ConfigureAwait(true); Assert.NotNull(configDoneResponse); - - // At this point the script should be running so lets give it time - await Task.Delay(2000).ConfigureAwait(false); - - Assert.Collection(GetLog(), (i) => Assert.Equal("a log statement", i)); + Assert.Collection(await GetLog().ConfigureAwait(true), + (i) => Assert.Equal("a log statement", i)); } } } diff --git a/test/PowerShellEditorServices.Test.E2E/LSPTestsFixures.cs b/test/PowerShellEditorServices.Test.E2E/LSPTestsFixures.cs index 12e29ee07..c31333d89 100644 --- a/test/PowerShellEditorServices.Test.E2E/LSPTestsFixures.cs +++ b/test/PowerShellEditorServices.Test.E2E/LSPTestsFixures.cs @@ -95,16 +95,9 @@ public async Task InitializeAsync() public async Task DisposeAsync() { - try - { - await PsesLanguageClient.Shutdown().ConfigureAwait(false); - await _psesProcess.Stop().ConfigureAwait(false); - PsesLanguageClient?.Dispose(); - } - catch (ObjectDisposedException) - { - // Language client has a disposal bug in it - } + await PsesLanguageClient.Shutdown().ConfigureAwait(false); + await _psesProcess.Stop().ConfigureAwait(false); + PsesLanguageClient?.Dispose(); } } } diff --git a/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs b/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs index 2ecc29ebc..bcfd49de9 100644 --- a/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs +++ b/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs @@ -12,6 +12,10 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.PowerShell.EditorServices.Handlers; +using Microsoft.PowerShell.EditorServices.Logging; +using Microsoft.PowerShell.EditorServices.Services.Configuration; +using Microsoft.PowerShell.EditorServices.Services.PowerShell; +using Microsoft.PowerShell.EditorServices.Services.Template; using Newtonsoft.Json.Linq; using OmniSharp.Extensions.LanguageServer.Protocol; using OmniSharp.Extensions.LanguageServer.Protocol.Client; @@ -21,10 +25,6 @@ using Xunit; using Xunit.Abstractions; 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 { @@ -154,8 +154,7 @@ function CanSendWorkspaceSymbolRequest { [SkippableFact] public async Task CanReceiveDiagnosticsFromFileOpenAsync() { - Skip.If( - PsesStdioProcess.RunningInConstrainedLanguageMode && PsesStdioProcess.IsWindowsPowerShell, + Skip.If(PsesStdioProcess.RunningInConstrainedLanguageMode && PsesStdioProcess.IsWindowsPowerShell, "Windows PowerShell doesn't trust PSScriptAnalyzer by default so it won't load."); NewTestFile("$a = 4"); @@ -177,8 +176,7 @@ public async Task WontReceiveDiagnosticsFromFileOpenThatIsNotPowerShellAsync() [SkippableFact] public async Task CanReceiveDiagnosticsFromFileChangedAsync() { - Skip.If( - PsesStdioProcess.RunningInConstrainedLanguageMode && PsesStdioProcess.IsWindowsPowerShell, + Skip.If(PsesStdioProcess.RunningInConstrainedLanguageMode && PsesStdioProcess.IsWindowsPowerShell, "Windows PowerShell doesn't trust PSScriptAnalyzer by default so it won't load."); string filePath = NewTestFile("$a = 4"); @@ -229,8 +227,7 @@ public async Task CanReceiveDiagnosticsFromFileChangedAsync() [SkippableFact] public async Task CanReceiveDiagnosticsFromConfigurationChangeAsync() { - Skip.If( - PsesStdioProcess.RunningInConstrainedLanguageMode && PsesStdioProcess.IsWindowsPowerShell, + Skip.If(PsesStdioProcess.RunningInConstrainedLanguageMode && PsesStdioProcess.IsWindowsPowerShell, "Windows PowerShell doesn't trust PSScriptAnalyzer by default so it won't load."); NewTestFile("gci | % { $_ }"); @@ -330,8 +327,7 @@ await PsesLanguageClient [SkippableFact] public async Task CanSendFormattingRequestAsync() { - Skip.If( - PsesStdioProcess.RunningInConstrainedLanguageMode && PsesStdioProcess.IsWindowsPowerShell, + Skip.If(PsesStdioProcess.RunningInConstrainedLanguageMode && PsesStdioProcess.IsWindowsPowerShell, "Windows PowerShell doesn't trust PSScriptAnalyzer by default so it won't load."); string scriptPath = NewTestFile(@" @@ -367,8 +363,7 @@ public async Task CanSendFormattingRequestAsync() [SkippableFact] public async Task CanSendRangeFormattingRequestAsync() { - Skip.If( - PsesStdioProcess.RunningInConstrainedLanguageMode && PsesStdioProcess.IsWindowsPowerShell, + Skip.If(PsesStdioProcess.RunningInConstrainedLanguageMode && PsesStdioProcess.IsWindowsPowerShell, "Windows PowerShell doesn't trust PSScriptAnalyzer by default so it won't load."); string scriptPath = NewTestFile(@" @@ -576,7 +571,7 @@ public async Task CanSendPowerShellGetPSHostProcessesRequestAsync() await PsesLanguageClient .SendRequest( "powerShell/getPSHostProcesses", - new GetPSHostProcesssesParams()) + new GetPSHostProcessesParams()) .Returning(CancellationToken.None).ConfigureAwait(true); } finally @@ -891,8 +886,7 @@ function CanSendReferencesCodeLensRequest { [SkippableFact] public async Task CanSendCodeActionRequestAsync() { - Skip.If( - PsesStdioProcess.RunningInConstrainedLanguageMode && PsesStdioProcess.IsWindowsPowerShell, + Skip.If(PsesStdioProcess.RunningInConstrainedLanguageMode && PsesStdioProcess.IsWindowsPowerShell, "Windows PowerShell doesn't trust PSScriptAnalyzer by default so it won't load."); string filePath = NewTestFile("gci"); @@ -971,7 +965,7 @@ public async Task CanSendCompletionAndCompletionResolveRequestAsync() Assert.Contains("Writes customized output to a host", updatedCompletionItem.Documentation.String); } - [SkippableFact(Skip = "This test is too flaky right now.")] + [SkippableFact(Skip = "Completion for Expand-SlowArchive is flaky.")] public async Task CanSendCompletionResolveWithModulePrefixRequestAsync() { await PsesLanguageClient @@ -1090,7 +1084,8 @@ await PsesLanguageClient [SkippableFact] public async Task CanSendGetProjectTemplatesRequestAsync() { - Skip.If(PsesStdioProcess.RunningInConstrainedLanguageMode, "Plaster doesn't work in ConstrainedLanguage mode."); + Skip.If(PsesStdioProcess.RunningInConstrainedLanguageMode, + "Plaster doesn't work in Constrained Language Mode."); GetProjectTemplatesResponse getProjectTemplatesResponse = await PsesLanguageClient @@ -1109,8 +1104,7 @@ await PsesLanguageClient [SkippableFact] public async Task CanSendGetCommentHelpRequestAsync() { - Skip.If( - PsesStdioProcess.RunningInConstrainedLanguageMode && PsesStdioProcess.IsWindowsPowerShell, + Skip.If(PsesStdioProcess.RunningInConstrainedLanguageMode && PsesStdioProcess.IsWindowsPowerShell, "Windows PowerShell doesn't trust PSScriptAnalyzer by default so it won't load."); string scriptPath = NewTestFile(@" @@ -1149,8 +1143,6 @@ await PsesLanguageClient [Fact] public async Task CanSendEvaluateRequestAsync() { - using CancellationTokenSource cancellationSource = new(millisecondsDelay: 5000); - EvaluateResponseBody evaluateResponseBody = await PsesLanguageClient .SendRequest( @@ -1159,7 +1151,7 @@ await PsesLanguageClient { Expression = "Get-ChildItem" }) - .Returning(cancellationSource.Token).ConfigureAwait(true); + .Returning(CancellationToken.None).ConfigureAwait(true); // These always gets returned so this test really just makes sure we get _any_ response. Assert.Equal("", evaluateResponseBody.Result); @@ -1182,9 +1174,8 @@ await PsesLanguageClient [SkippableFact] public async Task CanSendExpandAliasRequestAsync() { - Skip.If( - PsesStdioProcess.RunningInConstrainedLanguageMode, - "This feature currently doesn't support ConstrainedLanguage Mode."); + Skip.If(PsesStdioProcess.RunningInConstrainedLanguageMode, + "The expand alias request doesn't work in Constrained Language Mode."); ExpandAliasResult expandAliasResult = await PsesLanguageClient diff --git a/test/PowerShellEditorServices.Test.E2E/Processes/ServerProcess.cs b/test/PowerShellEditorServices.Test.E2E/Processes/ServerProcess.cs index 020ee8779..8d744dc11 100644 --- a/test/PowerShellEditorServices.Test.E2E/Processes/ServerProcess.cs +++ b/test/PowerShellEditorServices.Test.E2E/Processes/ServerProcess.cs @@ -43,7 +43,7 @@ protected ServerProcess(ILoggerFactory loggerFactory) } /// - /// Finaliser for . + /// Finalizer for . /// ~ServerProcess() { @@ -53,7 +53,11 @@ protected ServerProcess(ILoggerFactory loggerFactory) /// /// Dispose of resources being used by the launcher. /// - public void Dispose() => Dispose(true); + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } /// /// Dispose of resources being used by the launcher. diff --git a/test/PowerShellEditorServices.Test.Shared/Completion/CompleteAttributeValue.cs b/test/PowerShellEditorServices.Test.Shared/Completion/CompleteAttributeValue.cs index d38c430a4..629e50aec 100644 --- a/test/PowerShellEditorServices.Test.Shared/Completion/CompleteAttributeValue.cs +++ b/test/PowerShellEditorServices.Test.Shared/Completion/CompleteAttributeValue.cs @@ -25,7 +25,6 @@ internal static class CompleteAttributeValue FilterText = "ValueFromPipeline", InsertText = "ValueFromPipeline", Label = "ValueFromPipeline", - SortText = "0001ValueFromPipeline", TextEdit = new TextEdit { NewText = "ValueFromPipeline", @@ -44,7 +43,6 @@ internal static class CompleteAttributeValue FilterText = "ValueFromPipelineByPropertyName", InsertText = "ValueFromPipelineByPropertyName", Label = "ValueFromPipelineByPropertyName", - SortText = "0002ValueFromPipelineByPropertyName", TextEdit = new TextEdit { NewText = "ValueFromPipelineByPropertyName", @@ -63,7 +61,6 @@ internal static class CompleteAttributeValue FilterText = "ValueFromRemainingArguments", InsertText = "ValueFromRemainingArguments", Label = "ValueFromRemainingArguments", - SortText = "0003ValueFromRemainingArguments", TextEdit = new TextEdit { NewText = "ValueFromRemainingArguments", diff --git a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs index b7b55ad61..cb018ae5e 100644 --- a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs @@ -63,7 +63,9 @@ public void Dispose() { debugService.Abort(); debuggerStoppedQueue.Dispose(); +#pragma warning disable VSTHRD002 psesHost.StopAsync().Wait(); +#pragma warning restore VSTHRD002 GC.SuppressFinalize(this); } @@ -101,7 +103,7 @@ private Task ExecutePowerShellCommand(string command, params string[] args) private void AssertDebuggerPaused() { - DebuggerStoppedEventArgs eventArgs = debuggerStoppedQueue.Take(new CancellationTokenSource(5000).Token); + DebuggerStoppedEventArgs eventArgs = debuggerStoppedQueue.Take(CancellationToken.None); Assert.Empty(eventArgs.OriginalEvent.Breakpoints); } @@ -110,7 +112,7 @@ private void AssertDebuggerStopped( int lineNumber = -1, CommandBreakpointDetails commandBreakpointDetails = default) { - DebuggerStoppedEventArgs eventArgs = debuggerStoppedQueue.Take(new CancellationTokenSource(5000).Token); + DebuggerStoppedEventArgs eventArgs = debuggerStoppedQueue.Take(CancellationToken.None); Assert.True(psesHost.DebugContext.IsStopped); @@ -183,7 +185,7 @@ await debugService.SetCommandBreakpointsAsync( public async Task DebuggerAcceptsScriptArgs(string[] args) { // The path is intentionally odd (some escaped chars but not all) because we are testing - // the internal path escaping mechanism - it should escape certains chars ([, ] and space) but + // the internal path escaping mechanism - it should escape certain chars ([, ] and space) but // it should not escape already escaped chars. ScriptFile debugWithParamsFile = GetDebugScript("Debug W&ith Params [Test].ps1"); @@ -453,7 +455,7 @@ await debugService.SetLineBreakpointsAsync( } [Fact] - public async Task DebuggerFindsParseableButInvalidSimpleBreakpointConditions() + public async Task DebuggerFindsParsableButInvalidSimpleBreakpointConditions() { BreakpointDetails[] breakpoints = await debugService.SetLineBreakpointsAsync( diff --git a/test/PowerShellEditorServices.Test/Extensions/ExtensionCommandTests.cs b/test/PowerShellEditorServices.Test/Extensions/ExtensionCommandTests.cs index 1f7998ec5..2ae739c26 100644 --- a/test/PowerShellEditorServices.Test/Extensions/ExtensionCommandTests.cs +++ b/test/PowerShellEditorServices.Test/Extensions/ExtensionCommandTests.cs @@ -39,7 +39,9 @@ public ExtensionCommandTests() public void Dispose() { +#pragma warning disable VSTHRD002 psesHost.StopAsync().Wait(); +#pragma warning restore VSTHRD002 GC.SuppressFinalize(this); } diff --git a/test/PowerShellEditorServices.Test/Language/CompletionHandlerTests.cs b/test/PowerShellEditorServices.Test/Language/CompletionHandlerTests.cs index 4ba77bf1f..3ddedb89f 100644 --- a/test/PowerShellEditorServices.Test/Language/CompletionHandlerTests.cs +++ b/test/PowerShellEditorServices.Test/Language/CompletionHandlerTests.cs @@ -35,7 +35,9 @@ public CompletionHandlerTests() public void Dispose() { +#pragma warning disable VSTHRD002 psesHost.StopAsync().Wait(); +#pragma warning restore VSTHRD002 GC.SuppressFinalize(this); } @@ -109,10 +111,13 @@ public async Task CompletesVariableInFile() public async Task CompletesAttributeValue() { (_, IEnumerable results) = await GetCompletionResultsAsync(CompleteAttributeValue.SourceDetails).ConfigureAwait(true); - Assert.Collection(results.OrderBy(c => c.SortText), - actual => Assert.Equal(actual with { Data = null }, CompleteAttributeValue.ExpectedCompletion1), - actual => Assert.Equal(actual with { Data = null }, CompleteAttributeValue.ExpectedCompletion2), - actual => Assert.Equal(actual with { Data = null }, CompleteAttributeValue.ExpectedCompletion3)); + // NOTE: Since the completions come through un-ordered from PowerShell, their SortText + // (which has an index prepended from the original order) will mis-match our assumed + // order; hence we ignore it. + Assert.Collection(results.OrderBy(c => c.Label), + actual => Assert.Equal(actual with { Data = null, SortText = null }, CompleteAttributeValue.ExpectedCompletion1), + actual => Assert.Equal(actual with { Data = null, SortText = null }, CompleteAttributeValue.ExpectedCompletion2), + actual => Assert.Equal(actual with { Data = null, SortText = null }, CompleteAttributeValue.ExpectedCompletion3)); } [Fact] diff --git a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs index 06879666d..959a9b1fa 100644 --- a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs @@ -46,7 +46,9 @@ public SymbolsServiceTests() public void Dispose() { +#pragma warning disable VSTHRD002 psesHost.StopAsync().GetAwaiter().GetResult(); +#pragma warning restore VSTHRD002 CommandHelpers.s_cmdletToAliasCache.Clear(); CommandHelpers.s_aliasToCmdletCache.Clear(); GC.SuppressFinalize(this); diff --git a/test/PowerShellEditorServices.Test/Session/PsesInternalHostTests.cs b/test/PowerShellEditorServices.Test/Session/PsesInternalHostTests.cs index 179da5f8c..7412ac59d 100644 --- a/test/PowerShellEditorServices.Test/Session/PsesInternalHostTests.cs +++ b/test/PowerShellEditorServices.Test/Session/PsesInternalHostTests.cs @@ -27,7 +27,9 @@ public class PsesInternalHostTests : IDisposable public void Dispose() { +#pragma warning disable VSTHRD002 psesHost.StopAsync().Wait(); +#pragma warning restore VSTHRD002 GC.SuppressFinalize(this); } @@ -84,11 +86,12 @@ public async Task CanQueueParallelPSCommands() [Fact] public async Task CanCancelExecutionWithToken() { + using CancellationTokenSource cancellationSource = new(millisecondsDelay: 1000); await Assert.ThrowsAsync(() => { return psesHost.ExecutePSCommandAsync( new PSCommand().AddScript("Start-Sleep 10"), - new CancellationTokenSource(1000).Token); + cancellationSource.Token); }).ConfigureAwait(true); } @@ -137,7 +140,7 @@ public async Task CanResolveAndLoadProfilesForHostId() new PSCommand().AddScript("Assert-ProfileLoaded"), CancellationToken.None).ConfigureAwait(true); - Assert.Collection(profileLoaded, (p) => Assert.True(p)); + Assert.Collection(profileLoaded, Assert.True); } [Fact] @@ -150,7 +153,8 @@ public async Task CanHandleNoProfiles() await psesHost.ExecuteDelegateAsync( "LoadProfiles", executionOptions: null, - (pwsh, _) => { + (pwsh, _) => + { pwsh.LoadProfiles(emptyProfilePaths); Assert.Empty(pwsh.Commands.Commands); }, @@ -162,7 +166,7 @@ public async Task CanLoadPSReadLine() { // NOTE: This is slightly more complicated than one would expect because we explicitly // need it to run on the pipeline thread otherwise Windows complains about the the - // thread's appartment state not matching. + // thread's apartment state not matching. Assert.True(await psesHost.ExecuteDelegateAsync( nameof(psesHost.TryLoadPSReadLine), executionOptions: null, diff --git a/test/PowerShellEditorServices.Test/Session/WorkspaceTests.cs b/test/PowerShellEditorServices.Test/Session/WorkspaceTests.cs index 6d07530ca..5342ee7e6 100644 --- a/test/PowerShellEditorServices.Test/Session/WorkspaceTests.cs +++ b/test/PowerShellEditorServices.Test/Session/WorkspaceTests.cs @@ -12,6 +12,7 @@ namespace Microsoft.PowerShell.EditorServices.Test.Session { + [Trait("Category", "Workspace")] public class WorkspaceTests { private static readonly Lazy s_lazyDriveLetter = new(() => Path.GetFullPath("\\").Substring(0, 1)); @@ -21,7 +22,6 @@ public class WorkspaceTests : string.Empty; [Fact] - [Trait("Category", "Workspace")] public void CanResolveWorkspaceRelativePath() { string workspacePath = TestUtilities.NormalizePath("c:/Test/Workspace/"); @@ -64,29 +64,26 @@ internal static List ExecuteEnumeratePSFiles( string[] excludeGlobs, string[] includeGlobs, int maxDepth, - bool ignoreReparsePoints - ) + bool ignoreReparsePoints) { - IEnumerable result = workspace.EnumeratePSFiles( + List fileList = new(workspace.EnumeratePSFiles( excludeGlobs: excludeGlobs, includeGlobs: includeGlobs, maxDepth: maxDepth, ignoreReparsePoints: ignoreReparsePoints - ); - List fileList = new(); - fileList.AddRange(result); - // Assume order is not important from EnumeratePSFiles and sort the array so we can use deterministic asserts - fileList.Sort(); + )); + // Assume order is not important from EnumeratePSFiles and sort the array so we can use + // deterministic asserts + fileList.Sort(); return fileList; } [Fact] - [Trait("Category", "Workspace")] public void CanRecurseDirectoryTree() { WorkspaceService workspace = FixturesWorkspace(); - List fileList = ExecuteEnumeratePSFiles( + List actual = ExecuteEnumeratePSFiles( workspace: workspace, excludeGlobs: s_defaultExcludeGlobs, includeGlobs: s_defaultIncludeGlobs, @@ -94,65 +91,58 @@ public void CanRecurseDirectoryTree() ignoreReparsePoints: s_defaultIgnoreReparsePoints ); - if (!RuntimeInformation.FrameworkDescription.StartsWith(".NET Framework")) + List expected = new() { - // .Net Core doesn't appear to use the same three letter pattern matching rule although the docs - // suggest it should be find the '.ps1xml' files because we search for the pattern '*.ps1' - // ref https://docs.microsoft.com/en-us/dotnet/api/system.io.directory.getfiles?view=netcore-2.1#System_IO_Directory_GetFiles_System_String_System_String_System_IO_EnumerationOptions_ - Assert.Equal(4, fileList.Count); - Assert.Equal(Path.Combine(workspace.WorkspacePath, "nested", "donotfind.ps1"), fileList[0]); - Assert.Equal(Path.Combine(workspace.WorkspacePath, "nested", "nestedmodule.psd1"), fileList[1]); - Assert.Equal(Path.Combine(workspace.WorkspacePath, "nested", "nestedmodule.psm1"), fileList[2]); - Assert.Equal(Path.Combine(workspace.WorkspacePath, "rootfile.ps1"), fileList[3]); - } - else + Path.Combine(workspace.WorkspacePath, "nested", "donotfind.ps1"), + Path.Combine(workspace.WorkspacePath, "nested", "nestedmodule.psd1"), + Path.Combine(workspace.WorkspacePath, "nested", "nestedmodule.psm1"), + Path.Combine(workspace.WorkspacePath, "rootfile.ps1") + }; + + // .NET Core doesn't appear to use the same three letter pattern matching rule although the docs + // suggest it should be find the '.ps1xml' files because we search for the pattern '*.ps1' + // ref https://docs.microsoft.com/en-us/dotnet/api/system.io.directory.getfiles?view=netcore-2.1#System_IO_Directory_GetFiles_System_String_System_String_System_IO_EnumerationOptions_ + if (RuntimeInformation.FrameworkDescription.StartsWith(".NET Framework")) { - Assert.Equal(5, fileList.Count); - Assert.Equal(Path.Combine(workspace.WorkspacePath, "nested", "donotfind.ps1"), fileList[0]); - Assert.Equal(Path.Combine(workspace.WorkspacePath, "nested", "nestedmodule.psd1"), fileList[1]); - Assert.Equal(Path.Combine(workspace.WorkspacePath, "nested", "nestedmodule.psm1"), fileList[2]); - Assert.Equal(Path.Combine(workspace.WorkspacePath, "other", "other.ps1xml"), fileList[3]); - Assert.Equal(Path.Combine(workspace.WorkspacePath, "rootfile.ps1"), fileList[4]); + expected.Insert(3, Path.Combine(workspace.WorkspacePath, "other", "other.ps1xml")); } + + Assert.Equal(expected, actual); } [Fact] - [Trait("Category", "Workspace")] public void CanRecurseDirectoryTreeWithLimit() { WorkspaceService workspace = FixturesWorkspace(); - List fileList = ExecuteEnumeratePSFiles( + List actual = ExecuteEnumeratePSFiles( workspace: workspace, excludeGlobs: s_defaultExcludeGlobs, includeGlobs: s_defaultIncludeGlobs, maxDepth: 1, ignoreReparsePoints: s_defaultIgnoreReparsePoints ); - - Assert.Single(fileList); - Assert.Equal(Path.Combine(workspace.WorkspacePath, "rootfile.ps1"), fileList[0]); + Assert.Equal(new[] { Path.Combine(workspace.WorkspacePath, "rootfile.ps1") }, actual); } [Fact] - [Trait("Category", "Workspace")] public void CanRecurseDirectoryTreeWithGlobs() { WorkspaceService workspace = FixturesWorkspace(); - List fileList = ExecuteEnumeratePSFiles( + List actual = ExecuteEnumeratePSFiles( workspace: workspace, - excludeGlobs: new[] { "**/donotfind*" }, // Exclude any files starting with donotfind + excludeGlobs: new[] { "**/donotfind*" }, // Exclude any files starting with donotfind includeGlobs: new[] { "**/*.ps1", "**/*.psd1" }, // Only include PS1 and PSD1 files maxDepth: s_defaultMaxDepth, ignoreReparsePoints: s_defaultIgnoreReparsePoints ); - Assert.Equal(2, fileList.Count); - Assert.Equal(Path.Combine(workspace.WorkspacePath, "nested", "nestedmodule.psd1"), fileList[0]); - Assert.Equal(Path.Combine(workspace.WorkspacePath, "rootfile.ps1"), fileList[1]); + Assert.Equal(new[] { + Path.Combine(workspace.WorkspacePath, "nested", "nestedmodule.psd1"), + Path.Combine(workspace.WorkspacePath, "rootfile.ps1") + }, actual); } [Fact] - [Trait("Category", "Workspace")] public void CanDetermineIsPathInMemory() { string tempDir = Path.GetTempPath(); @@ -161,32 +151,29 @@ public void CanDetermineIsPathInMemory() const string shortUriForm = "git:/c%3A/Users/Keith/GitHub/dahlbyk/posh-git/src/PoshGitTypes.ps1?%7B%22path%22%3A%22c%3A%5C%5CUsers%5C%5CKeith%5C%5CGitHub%5C%5Cdahlbyk%5C%5Cposh-git%5C%5Csrc%5C%5CPoshGitTypes.ps1%22%2C%22ref%22%3A%22~%22%7D"; const string longUriForm = "gitlens-git:c%3A%5CUsers%5CKeith%5CGitHub%5Cdahlbyk%5Cposh-git%5Csrc%5CPoshGitTypes%3Ae0022701.ps1?%7B%22fileName%22%3A%22src%2FPoshGitTypes.ps1%22%2C%22repoPath%22%3A%22c%3A%2FUsers%2FKeith%2FGitHub%2Fdahlbyk%2Fposh-git%22%2C%22sha%22%3A%22e0022701fa12e0bc22d0458673d6443c942b974a%22%7D"; - var testCases = new[] { - // Test short file absolute paths - new { IsInMemory = false, Path = shortDirPath }, - new { IsInMemory = false, Path = shortFilePath }, - new { IsInMemory = false, Path = new Uri(shortDirPath).ToString() }, - new { IsInMemory = false, Path = new Uri(shortFilePath).ToString() }, - - // Test short file relative paths - not sure we'll ever get these but just in case - new { IsInMemory = false, Path = "foo.ps1" }, - new { IsInMemory = false, Path = Path.Combine(new [] { "..", "foo.ps1" }) }, - + string[] inMemoryPaths = new[] { // Test short non-file paths - new { IsInMemory = true, Path = "untitled:untitled-1" }, - new { IsInMemory = true, Path = shortUriForm }, - new { IsInMemory = true, Path = "inmemory://foo.ps1" }, + "untitled:untitled-1", + shortUriForm, + "inmemory://foo.ps1", + // Test long non-file path + longUriForm + }; - // Test long non-file path - known to have crashed PSES - new { IsInMemory = true, Path = longUriForm }, + Assert.All(inMemoryPaths, (p) => Assert.True(WorkspaceService.IsPathInMemory(p))); + + string[] notInMemoryPaths = new[] { + // Test short file absolute paths + shortDirPath, + shortFilePath, + new Uri(shortDirPath).ToString(), + new Uri(shortFilePath).ToString(), + // Test short file relative paths + "foo.ps1", + Path.Combine(new[] { "..", "foo.ps1" }) }; - foreach (var testCase in testCases) - { - Assert.True( - WorkspaceService.IsPathInMemory(testCase.Path) == testCase.IsInMemory, - $"Testing path {testCase.Path}"); - } + Assert.All(notInMemoryPaths, (p) => Assert.False(WorkspaceService.IsPathInMemory(p))); } } }