diff --git a/.github/workflows/ci-test.yml b/.github/workflows/ci-test.yml index c40c57d8c..383e53dfe 100644 --- a/.github/workflows/ci-test.yml +++ b/.github/workflows/ci-test.yml @@ -14,6 +14,10 @@ jobs: matrix: os: [ windows-latest, macos-latest, ubuntu-latest ] runs-on: ${{ matrix.os }} + # A normal run finishes in well under an hour (Windows, the slowest, is + # ~12-14 minutes); this caps a hung test instead of letting it ride + # GitHub's 6-hour default. + timeout-minutes: 60 env: DOTNET_NOLOGO: true DOTNET_GENERATE_ASPNET_CERTIFICATE: false diff --git a/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs b/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs index 332005d39..27e153751 100644 --- a/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs +++ b/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs @@ -24,6 +24,14 @@ namespace PowerShellEditorServices.Test.E2E { + /// + /// Every test in this class is skipped at discovery time on in-box Windows + /// PowerShell (via and + /// ) because the shared + /// debug-adapter startup can wedge there since the + /// 20260614 runner image, riding the job timeout. See + /// https://github.com/PowerShell/PowerShellEditorServices/issues/2323. + /// [Trait("Category", "DAP")] // ITestOutputHelper is injected by XUnit // https://xunit.net/docs/capturing-output @@ -238,16 +246,25 @@ private async Task ReadScriptLogLineAsync() } } - // return valid lines only - string nextLine = string.Empty; - while (nextLine is null || nextLine.Length == 0) + // Tail the log until a non-empty line is available. The awaited + // delay between reads matters: at EOF ReadLineAsync completes + // synchronously with null, so without it this is a tight loop that + // never releases its thread-pool thread and needlessly pressures + // the pool on constrained CI runners. Yielding keeps the tail loop + // cheap while we wait for the script to write. + while (true) { - nextLine = await scriptLogReader.ReadLineAsync(); //Might return null if at EOF because we created it above but the script hasn't written to it yet + string nextLine = await scriptLogReader.ReadLineAsync(); + if (!string.IsNullOrEmpty(nextLine)) + { + return nextLine; + } + + await Task.Delay(100); } - return nextLine; } - [Fact] + [SkippableFactOnWindowsPowerShell] public void CanInitializeWithCorrectServerSettings() { Assert.True(client.ServerSettings.SupportsConditionalBreakpoints); @@ -259,7 +276,7 @@ public void CanInitializeWithCorrectServerSettings() Assert.True(client.ServerSettings.SupportsDelayedStackTraceLoading); } - [Fact] + [SkippableFactOnWindowsPowerShell] public async Task UsesDotSourceOperatorAndQuotesAsync() { string filePath = NewTestFile(GenerateLoggingScript("$($MyInvocation.Line)")); @@ -271,7 +288,7 @@ public async Task UsesDotSourceOperatorAndQuotesAsync() Assert.StartsWith(". '", actual); } - [Fact] + [SkippableFactOnWindowsPowerShell] public async Task UsesCallOperatorWithSettingAsync() { string filePath = NewTestFile(GenerateLoggingScript("$($MyInvocation.Line)")); @@ -283,7 +300,7 @@ public async Task UsesCallOperatorWithSettingAsync() Assert.StartsWith("& '", actual); } - [Fact] + [SkippableFactOnWindowsPowerShell] public async Task CanLaunchScriptWithNoBreakpointsAsync() { string filePath = NewTestFile(GenerateLoggingScript("works")); @@ -297,7 +314,7 @@ public async Task CanLaunchScriptWithNoBreakpointsAsync() Assert.Equal("works", actual); } - [SkippableFact] + [SkippableFactOnWindowsPowerShell] public async Task CanSetBreakpointsAsync() { Skip.If(PsesStdioLanguageServerProcessHost.RunningInConstrainedLanguageMode, @@ -348,7 +365,7 @@ public async Task CanSetBreakpointsAsync() Assert.Equal("after breakpoint", afterBreakpointActual); } - [SkippableFact] + [SkippableFactOnWindowsPowerShell] public async Task FailsIfStacktraceRequestedWhenNotPaused() { Skip.If(PsesStdioLanguageServerProcessHost.RunningInConstrainedLanguageMode, @@ -379,7 +396,7 @@ await Assert.ThrowsAsync(() => client.RequestStackTrace( )); } - [SkippableFact] + [SkippableFactOnWindowsPowerShell] public async Task SendsInitialLabelBreakpointForPerformanceReasons() { Skip.If(PsesStdioLanguageServerProcessHost.RunningInConstrainedLanguageMode, @@ -437,7 +454,7 @@ public async Task SendsInitialLabelBreakpointForPerformanceReasons() // 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. - [SkippableFact] + [SkippableFactOnWindowsPowerShell] public async Task CanStepPastSystemWindowsForms() { Skip.IfNot(PsesStdioLanguageServerProcessHost.IsWindowsPowerShell, @@ -480,7 +497,7 @@ 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. - [Fact] + [SkippableFactOnWindowsPowerShell] public async Task CanLaunchScriptWithCommentedLastLineAsync() { string script = GenerateLoggingScript("$($MyInvocation.Line)", "$(1+1)") + "# a comment at the end"; @@ -504,7 +521,7 @@ public async Task CanLaunchScriptWithCommentedLastLineAsync() Assert.Equal("2", await ReadScriptLogLineAsync()); } - [SkippableFact] + [SkippableFactOnWindowsPowerShell] public async Task CanRunPesterTestFile() { Skip.If(true, "Pester test is broken."); @@ -549,7 +566,7 @@ public async Task CanRunPesterTestFile() [InlineData("-ProcessId 1234 -RunspaceId 5678", null, null, 1234, 5678, null)] [InlineData("-ProcessId 1234 -RunspaceId 5678 -ComputerName comp", "comp", null, 1234, 5678, null)] [InlineData("-CustomPipeName testpipe -RunspaceName rs-name", null, "testpipe", 0, 0, "rs-name")] - [SkippableTheory] + [SkippableTheoryOnWindowsPowerShell] public async Task CanLaunchScriptWithNewChildAttachSession( string paramString, string? expectedComputerName, @@ -587,7 +604,7 @@ public async Task CanLaunchScriptWithNewChildAttachSession( await terminatedTcs.Task; } - [SkippableFact] + [SkippableFactOnWindowsPowerShell] public async Task CanLaunchScriptWithNewChildAttachSessionAsJob() { Skip.If(PsesStdioLanguageServerProcessHost.RunningInConstrainedLanguageMode, @@ -621,7 +638,9 @@ public async Task CanLaunchScriptWithNewChildAttachSessionAsJob() await terminatedTcs.Task; } - [SkippableFact(Timeout = 15000)] + // Timeout is a per-test backstop; the Windows PowerShell skip happens at + // discovery time via the attribute (see the class remarks). + [SkippableFactOnWindowsPowerShell(Timeout = 15000)] public async Task CanAttachScriptWithPathMappings() { Skip.If(PsesStdioLanguageServerProcessHost.RunningInConstrainedLanguageMode, @@ -762,6 +781,10 @@ WinPS will always need this. if (((Get-Date) - $start).TotalSeconds -gt 10) { throw 'Timeout waiting for Debug-Runspace to be subscribed.' } + + # Yield a slice so this poll doesn't peg a core while the + # runner is also servicing the attach handshake. + Start-Sleep -Milliseconds 100 } $ps.Invoke() diff --git a/test/PowerShellEditorServices.Test.E2E/Hosts/SkippableFactOnWindowsPowerShellAttribute.cs b/test/PowerShellEditorServices.Test.E2E/Hosts/SkippableFactOnWindowsPowerShellAttribute.cs new file mode 100644 index 000000000..5abb62a09 --- /dev/null +++ b/test/PowerShellEditorServices.Test.E2E/Hosts/SkippableFactOnWindowsPowerShellAttribute.cs @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Xunit; +using Xunit.Sdk; + +namespace PowerShellEditorServices.Test.E2E; + +/// +/// The shared skip reason used by the discovery-time Windows PowerShell skip +/// attributes for the end-to-end tests. +/// +/// +/// This is a runner-image regression, not a PSES code change: re-running a +/// commit that predates all recent PRs (and previously passed) reproduces the +/// same hang on the current windows-latest image, while macOS and Linux stay +/// green. The wedge is in the in-box Windows PowerShell server's startup, so it +/// affects both the debug adapter and language server end-to-end suites. +/// +internal static class WindowsPowerShellServerStartupSkip +{ + public const string Reason = "The in-box Windows PowerShell server can wedge during startup on the current windows-latest runner image (a runner-image regression, not our code); see https://github.com/PowerShell/PowerShellEditorServices/issues/2323."; +} + +/// +/// A that additionally skips the test at +/// discovery time when running under in-box Windows PowerShell. +/// +/// +/// A runtime in the test body cannot prevent +/// the per-test IAsyncLifetime.InitializeAsync from running first, because +/// xUnit invokes the lifetime setup (which starts the PSES server) before the +/// method body. When the hang occurs during that setup, a body-level skip is never +/// reached. Setting here makes xUnit treat the +/// test as statically skipped, so it never instantiates the test class or runs +/// InitializeAsync. The discoverer is +/// retained so runtime calls (e.g. for Constrained Language +/// Mode) still work when the test is not skipped at discovery time. +/// +/// Caveat: xUnit still creates an even when +/// every test method in the class is skipped at discovery time, so a fixture that +/// starts the server in its own InitializeAsync (e.g. LSPTestsFixture) +/// must additionally guard against starting it under Windows PowerShell. +/// +/// +[XunitTestCaseDiscoverer("Xunit.Sdk.SkippableFactDiscoverer", "Xunit.SkippableFact")] +public sealed class SkippableFactOnWindowsPowerShellAttribute : SkippableFactAttribute +{ + public SkippableFactOnWindowsPowerShellAttribute() + { + if (PsesStdioLanguageServerProcessHost.IsWindowsPowerShell) + { + Skip = WindowsPowerShellServerStartupSkip.Reason; + } + } +} diff --git a/test/PowerShellEditorServices.Test.E2E/Hosts/SkippableTheoryOnWindowsPowerShellAttribute.cs b/test/PowerShellEditorServices.Test.E2E/Hosts/SkippableTheoryOnWindowsPowerShellAttribute.cs new file mode 100644 index 000000000..3f7ee8621 --- /dev/null +++ b/test/PowerShellEditorServices.Test.E2E/Hosts/SkippableTheoryOnWindowsPowerShellAttribute.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Xunit; +using Xunit.Sdk; + +namespace PowerShellEditorServices.Test.E2E; + +/// +/// A that additionally skips the theory at +/// discovery time when running under in-box Windows PowerShell. See +/// for why the skip must +/// happen at discovery time rather than via an in-body call. +/// +[XunitTestCaseDiscoverer("Xunit.Sdk.SkippableTheoryDiscoverer", "Xunit.SkippableFact")] +public sealed class SkippableTheoryOnWindowsPowerShellAttribute : SkippableTheoryAttribute +{ + public SkippableTheoryOnWindowsPowerShellAttribute() + { + if (PsesStdioLanguageServerProcessHost.IsWindowsPowerShell) + { + Skip = WindowsPowerShellServerStartupSkip.Reason; + } + } +} diff --git a/test/PowerShellEditorServices.Test.E2E/LSPTestsFixtures.cs b/test/PowerShellEditorServices.Test.E2E/LSPTestsFixtures.cs index 6eb8e4569..6db41ae37 100644 --- a/test/PowerShellEditorServices.Test.E2E/LSPTestsFixtures.cs +++ b/test/PowerShellEditorServices.Test.E2E/LSPTestsFixtures.cs @@ -42,6 +42,18 @@ public class LSPTestsFixture : IAsyncLifetime public async Task InitializeAsync() { + // All LSP end-to-end tests are skipped at discovery time on Windows + // PowerShell (see SkippableFactOnWindowsPowerShell), but xUnit still + // creates this class fixture even when every test method is skipped. + // The in-box Windows PowerShell server can wedge during startup on the + // current windows-latest runner image (a runner-image regression, not + // our code); see https://github.com/PowerShell/PowerShellEditorServices/issues/2323. + // So we must not start the server here on Windows PowerShell. + if (PsesStdioLanguageServerProcessHost.IsWindowsPowerShell) + { + return; + } + (StreamReader stdout, StreamWriter stdin) = await _psesHost.Start(); // Splice the streams together and enable debug logging of all messages sent and received @@ -100,9 +112,16 @@ public async Task InitializeAsync() public async Task DisposeAsync() { + // The server is never started on Windows PowerShell (see + // InitializeAsync), so there is nothing to shut down there. + if (PsesLanguageClient is null) + { + return; + } + await PsesLanguageClient.Shutdown(); await _psesHost.Stop(); - PsesLanguageClient?.Dispose(); + PsesLanguageClient.Dispose(); } } } diff --git a/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs b/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs index d764807e4..f9e9439e9 100644 --- a/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs +++ b/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs @@ -25,6 +25,15 @@ namespace PowerShellEditorServices.Test.E2E { + /// + /// Every test in this class is skipped at discovery time on in-box Windows + /// PowerShell (via ) + /// because the shared startup spawns an in-box + /// Windows PowerShell server that can wedge there since the 20260614 runner + /// image, riding the job timeout. This is the same server-startup hang that + /// affects the debug adapter tests; it is not specific to either protocol. + /// See https://github.com/PowerShell/PowerShellEditorServices/issues/2323. + /// [Trait("Category", "LSP")] public class LanguageServerProtocolMessageTests : IClassFixture, IDisposable { @@ -92,7 +101,7 @@ private async Task WaitForDiagnosticsAsync() } } - [Fact] + [SkippableFactOnWindowsPowerShell] public async Task CanSendPowerShellGetVersionRequestAsync() { PowerShellVersion details @@ -112,7 +121,7 @@ PowerShellVersion details } } - [Fact] + [SkippableFactOnWindowsPowerShell] public async Task CanSendWorkspaceSymbolRequestAsync() { NewTestFile(@" @@ -134,7 +143,7 @@ function CanSendWorkspaceSymbolRequest { Assert.Equal("function CanSendWorkspaceSymbolRequest ()", symbol.Name); } - [SkippableFact] + [SkippableFactOnWindowsPowerShell] public async Task CanReceiveDiagnosticsFromFileOpenAsync() { Skip.If(PsesStdioLanguageServerProcessHost.RunningInConstrainedLanguageMode && PsesStdioLanguageServerProcessHost.IsWindowsPowerShell, @@ -147,7 +156,7 @@ public async Task CanReceiveDiagnosticsFromFileOpenAsync() Assert.Equal("PSUseDeclaredVarsMoreThanAssignments", diagnostic.Code); } - [Fact] + [SkippableFactOnWindowsPowerShell] public async Task WontReceiveDiagnosticsFromFileOpenThatIsNotPowerShellAsync() { NewTestFile("$a = 4", languageId: "plaintext"); @@ -156,7 +165,7 @@ public async Task WontReceiveDiagnosticsFromFileOpenThatIsNotPowerShellAsync() Assert.Empty(Diagnostics); } - [SkippableFact] + [SkippableFactOnWindowsPowerShell] public async Task CanReceiveDiagnosticsFromFileChangedAsync() { Skip.If(PsesStdioLanguageServerProcessHost.RunningInConstrainedLanguageMode && PsesStdioLanguageServerProcessHost.IsWindowsPowerShell, @@ -207,7 +216,7 @@ public async Task CanReceiveDiagnosticsFromFileChangedAsync() Assert.Equal("PSUseDeclaredVarsMoreThanAssignments", diagnostic.Code); } - [SkippableFact] + [SkippableFactOnWindowsPowerShell] public async Task CanReceiveDiagnosticsFromConfigurationChangeAsync() { Skip.If(PsesStdioLanguageServerProcessHost.RunningInConstrainedLanguageMode && PsesStdioLanguageServerProcessHost.IsWindowsPowerShell, @@ -266,7 +275,7 @@ public async Task CanReceiveDiagnosticsFromConfigurationChangeAsync() Assert.Equal("PSUseDeclaredVarsMoreThanAssignments", diagnostic.Code); } - [Fact] + [SkippableFactOnWindowsPowerShell] public async Task CanSendFoldingRangeRequestAsync() { string scriptPath = NewTestFile(@"gci | % { @@ -307,7 +316,7 @@ await PsesLanguageClient }); } - [SkippableFact] + [SkippableFactOnWindowsPowerShell] public async Task CanSendFormattingRequestAsync() { Skip.If(PsesStdioLanguageServerProcessHost.RunningInConstrainedLanguageMode && PsesStdioLanguageServerProcessHost.IsWindowsPowerShell, @@ -343,7 +352,7 @@ public async Task CanSendFormattingRequestAsync() Assert.Contains("\t", textEdit.NewText); } - [SkippableFact] + [SkippableFactOnWindowsPowerShell] public async Task CanSendRangeFormattingRequestAsync() { Skip.If(PsesStdioLanguageServerProcessHost.RunningInConstrainedLanguageMode && PsesStdioLanguageServerProcessHost.IsWindowsPowerShell, @@ -392,7 +401,7 @@ public async Task CanSendRangeFormattingRequestAsync() Assert.Contains("\t", textEdit.NewText); } - [Fact] + [SkippableFactOnWindowsPowerShell] public async Task CanSendDocumentSymbolRequestAsync() { string scriptPath = NewTestFile(@" @@ -438,7 +447,7 @@ await PsesLanguageClient }); } - [Fact] + [SkippableFactOnWindowsPowerShell] public async Task CanSendReferencesRequestAsync() { string scriptPath = NewTestFile(@" @@ -481,7 +490,7 @@ function CanSendReferencesRequest { }); } - [Fact] + [SkippableFactOnWindowsPowerShell] public async Task CanSendDocumentHighlightRequestAsync() { string scriptPath = NewTestFile(@" @@ -527,7 +536,7 @@ await PsesLanguageClient }); } - [Fact] + [SkippableFactOnWindowsPowerShell] public async Task CanSendPowerShellGetPSHostProcessesRequestAsync() { Process process = new(); @@ -568,7 +577,7 @@ await PsesLanguageClient Assert.NotEmpty(pSHostProcessResponses); } - [Fact] + [SkippableFactOnWindowsPowerShell] public async Task CanSendPowerShellGetRunspaceRequestAsync() { Process process = new(); @@ -611,7 +620,7 @@ await PsesLanguageClient Assert.NotEmpty(runspaceResponses); } - [Fact] + [SkippableFactOnWindowsPowerShell] public async Task CanSendPesterLegacyCodeLensRequestAsync() { // Make sure LegacyCodeLens is enabled because we'll need it in this test. @@ -677,7 +686,7 @@ public async Task CanSendPesterLegacyCodeLensRequestAsync() }); } - [Fact] + [SkippableFactOnWindowsPowerShell] public async Task CanSendPesterCodeLensRequestAsync() { // Make sure Pester legacy CodeLens is disabled because we'll need it in this test. @@ -787,7 +796,7 @@ public async Task CanSendPesterCodeLensRequestAsync() }); } - [Fact] + [SkippableFactOnWindowsPowerShell] public async Task NoMessageIfPesterCodeLensDisabled() { // Make sure Pester legacy CodeLens is disabled because we'll need it in this test. @@ -830,7 +839,7 @@ public async Task NoMessageIfPesterCodeLensDisabled() Assert.Empty(codeLenses); } - [Fact] + [SkippableFactOnWindowsPowerShell] public async Task CanSendFunctionReferencesCodeLensRequestAsync() { string filePath = NewTestFile(@" @@ -868,7 +877,7 @@ function CanSendReferencesCodeLensRequest { Assert.Equal("1 reference", codeLensResolveResult.Command.Title); } - [Fact] + [SkippableFactOnWindowsPowerShell] public async Task CanSendClassReferencesCodeLensRequestAsync() { string filePath = NewTestFile(@" @@ -927,7 +936,7 @@ class ChildClass : MyBaseClass, System.IDisposable { Assert.Equal("4 references", codeLensResolveResult.Command.Title); } - [Fact] + [SkippableFactOnWindowsPowerShell] public async Task CanSendEnumReferencesCodeLensRequestAsync() { string filePath = NewTestFile(@" @@ -972,7 +981,7 @@ enum MyEnum { Assert.Equal("3 references", codeLensResolveResult.Command.Title); } - [SkippableFact] + [SkippableFactOnWindowsPowerShell] public async Task CanSendCodeActionRequestAsync() { Skip.If(PsesStdioLanguageServerProcessHost.RunningInConstrainedLanguageMode && PsesStdioLanguageServerProcessHost.IsWindowsPowerShell, @@ -1028,7 +1037,7 @@ await PsesLanguageClient }); } - [Fact] + [SkippableFactOnWindowsPowerShell] public async Task CanSendCompletionAndCompletionResolveRequestAsync() { CompletionList completionItems = await PsesLanguageClient.TextDocument.RequestCompletion( @@ -1051,7 +1060,7 @@ public async Task CanSendCompletionAndCompletionResolveRequestAsync() Assert.Contains(testDescription, updatedCompletionItem.Documentation.String); } - [Fact] + [SkippableFactOnWindowsPowerShell] public async Task CanSendCompletionResolveWithModulePrefixRequestAsync() { await PsesLanguageClient @@ -1098,7 +1107,7 @@ await PsesLanguageClient } } - [Fact] + [SkippableFactOnWindowsPowerShell] public async Task CanSendHoverRequestAsync() { string filePath = NewTestFile(testCommand); @@ -1123,7 +1132,7 @@ public async Task CanSendHoverRequestAsync() }); } - [Fact] + [SkippableFactOnWindowsPowerShell] public async Task CanSendSignatureHelpRequestAsync() { string filePath = NewTestFile($"{testCommand} -"); @@ -1147,7 +1156,7 @@ public async Task CanSendSignatureHelpRequestAsync() Assert.Contains(testCommand, signatureHelp.Signatures.First().Label); } - [Fact] + [SkippableFactOnWindowsPowerShell] public async Task CanSendDefinitionRequestAsync() { string scriptPath = NewTestFile(@" @@ -1178,7 +1187,7 @@ await PsesLanguageClient Assert.Equal(33, locationOrLocationLink.Location.Range.End.Character); } - [SkippableFact] + [SkippableFactOnWindowsPowerShell] public async Task CanSendGetCommentHelpRequestAsync() { Skip.If(PsesStdioLanguageServerProcessHost.RunningInConstrainedLanguageMode && PsesStdioLanguageServerProcessHost.IsWindowsPowerShell, @@ -1217,7 +1226,7 @@ await PsesLanguageClient Assert.Contains("myParam", commentHelpRequestResult.Content[7]); } - [Fact] + [SkippableFactOnWindowsPowerShell] public async Task CanSendEvaluateRequestAsync() { EvaluateResponseBody evaluateResponseBody = @@ -1236,7 +1245,7 @@ await PsesLanguageClient } // getCommand gets all the commands in the system, and is not optimized and can take forever on CI systems - [SkippableFact(Timeout = 120000)] + [SkippableFactOnWindowsPowerShell(Timeout = 120000)] public async Task CanSendGetCommandRequestAsync() { Skip.If( @@ -1255,7 +1264,7 @@ await PsesLanguageClient Assert.True(pSCommandMessages.Count > 20); } - [SkippableFact] + [SkippableFactOnWindowsPowerShell] public async Task CanSendExpandAliasRequestAsync() { Skip.If(PsesStdioLanguageServerProcessHost.RunningInConstrainedLanguageMode, @@ -1274,7 +1283,7 @@ await PsesLanguageClient Assert.Equal("Get-ChildItem", expandAliasResult.Text); } - [SkippableFact] + [SkippableFactOnWindowsPowerShell] public async Task CanSendExpandAliasRequestWithMultipleAliasesAsync() { Skip.If(PsesStdioLanguageServerProcessHost.RunningInConstrainedLanguageMode, @@ -1297,7 +1306,7 @@ await PsesLanguageClient Assert.Equal("Get-ChildItem | Where-Object Name | ForEach-Object Name", expandAliasResult.Text); } - [Fact] + [SkippableFactOnWindowsPowerShell] public async Task CanSendSemanticTokenRequestAsync() { const string scriptContent = "function";