diff --git a/src/PowerShellEditorServices/PowerShellEditorServices.csproj b/src/PowerShellEditorServices/PowerShellEditorServices.csproj index 562239ded..a18cc4be0 100644 --- a/src/PowerShellEditorServices/PowerShellEditorServices.csproj +++ b/src/PowerShellEditorServices/PowerShellEditorServices.csproj @@ -28,8 +28,8 @@ - - + + diff --git a/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs b/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs index 5c922e886..6e23abff2 100644 --- a/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs +++ b/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs @@ -228,5 +228,64 @@ public async Task CanSetBreakpointsAsync() (i) => Assert.Equal("at breakpoint", i), (i) => Assert.Equal("after breakpoint", i)); } + + // This is a regression test for a bug where user code causes a new synchronization context + // to be created, breaking the extension. It's most evident when debugging PowerShell + // scripts that use System.Windows.Forms. It required fixing both Editor Services and + // OmniSharp. + // + // This test depends on PowerShell being able to load System.Windows.Forms, which only works + // reliably with Windows PowerShell. It works with PowerShell Core in the real-world; + // however, our host executable is xUnit, not PowerShell. So by restricting to Windows + // 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.RunningInConstainedLanguageMode); + + string filePath = NewTestFile(string.Join(Environment.NewLine, new [] + { + "Add-Type -AssemblyName System.Windows.Forms", + "$form = New-Object System.Windows.Forms.Form", + "Write-Host $form" + })); + + await PsesDebugAdapterClient.LaunchScript(filePath, Started).ConfigureAwait(false); + + var setBreakpointsResponse = await PsesDebugAdapterClient.SetFunctionBreakpoints( + new SetFunctionBreakpointsArguments + { + Breakpoints = new FunctionBreakpoint[] + { + new FunctionBreakpoint + { + Name = "Write-Host", + } + } + }).ConfigureAwait(false); + + var breakpoint = setBreakpointsResponse.Breakpoints.First(); + Assert.True(breakpoint.Verified); + + ConfigurationDoneResponse configDoneResponse = await PsesDebugAdapterClient.RequestConfigurationDone(new ConfigurationDoneArguments()).ConfigureAwait(false); + Assert.NotNull(configDoneResponse); + + // At this point the script should be running so lets give it time + await Task.Delay(2000).ConfigureAwait(false); + + var variablesResponse = await PsesDebugAdapterClient.RequestVariables( + new VariablesArguments + { + VariablesReference = 1 + }).ConfigureAwait(false); + + var form = variablesResponse.Variables.FirstOrDefault(v => v.Name == "$form"); + Assert.NotNull(form); + Assert.Equal("System.Windows.Forms.Form, Text: ", form.Value); + } } } diff --git a/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj b/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj index 756ac8bd0..e7c907a00 100644 --- a/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj +++ b/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj @@ -10,8 +10,8 @@ - - + + diff --git a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs index b3cedbd14..8dd322d0a 100644 --- a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs @@ -32,6 +32,16 @@ public class DebugServiceTests : IDisposable private AsyncQueue sessionStateQueue = new AsyncQueue(); + private ScriptFile GetDebugScript(string fileName) + { + return this.workspace.GetFile( + TestUtilities.NormalizePath(Path.Combine( + Path.GetDirectoryName(typeof(DebugServiceTests).Assembly.Location), + "../../../../PowerShellEditorServices.Test.Shared/Debugging", + fileName + ))); + } + public DebugServiceTests() { var logger = NullLogger.Instance; @@ -41,18 +51,9 @@ public DebugServiceTests() this.workspace = new WorkspaceService(NullLoggerFactory.Instance); - // Load the test debug file - this.debugScriptFile = - this.workspace.GetFile( - TestUtilities.NormalizePath(Path.Combine( - Path.GetDirectoryName(typeof(DebugServiceTests).Assembly.Location), - "../../../../PowerShellEditorServices.Test.Shared/Debugging/VariableTest.ps1"))); - - this.variableScriptFile = - this.workspace.GetFile( - TestUtilities.NormalizePath(Path.Combine( - Path.GetDirectoryName(typeof(DebugServiceTests).Assembly.Location), - "../../../../PowerShellEditorServices.Test.Shared/Debugging/VariableTest.ps1"))); + // Load the test debug files + this.debugScriptFile = GetDebugScript("DebugTest.ps1"); + this.variableScriptFile = GetDebugScript("VariableTest.ps1"); this.debugService = new DebugService( this.powerShellContext, @@ -65,13 +66,6 @@ public DebugServiceTests() this.debugService.DebuggerStopped += debugService_DebuggerStopped; this.debugService.BreakpointUpdated += debugService_BreakpointUpdated; - - // Load the test debug file - this.debugScriptFile = - this.workspace.GetFile( - TestUtilities.NormalizePath(Path.Combine( - Path.GetDirectoryName(typeof(DebugServiceTests).Assembly.Location), - "../../../../PowerShellEditorServices.Test.Shared/Debugging/DebugTest.ps1"))); } async void powerShellContext_SessionStateChanged(object sender, SessionStateChangedEventArgs e) @@ -123,11 +117,7 @@ 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 // it should not escape already escaped chars. - ScriptFile debugWithParamsFile = - this.workspace.GetFile( - TestUtilities.NormalizePath(Path.Combine( - Path.GetDirectoryName(typeof(DebugServiceTests).Assembly.Location), - "../../../../PowerShellEditorServices.Test.Shared/Debugging/Debug W&ith Params [Test].ps1"))); + ScriptFile debugWithParamsFile = GetDebugScript("Debug W&ith Params [Test].ps1"); await this.debugService.SetLineBreakpointsAsync( debugWithParamsFile, @@ -889,7 +879,7 @@ await this.debugService.SetLineBreakpointsAsync( var nullStringVar = variables.FirstOrDefault(v => v.Name == "$nullString"); Assert.NotNull(nullStringVar); - Assert.True("[NullString]".Equals(nullStringVar.ValueString)); + Assert.Equal("[NullString]", nullStringVar.ValueString); Assert.True(nullStringVar.IsExpandable); // Abort script execution early and wait for completion @@ -973,6 +963,7 @@ await this.debugService.SetLineBreakpointsAsync( // Verifies fix for issue #86, $proc = Get-Process foo displays just the ETS property set // and not all process properties. + [Trait("Category", "DebugService")] [Fact] public async Task DebuggerVariableProcessObjDisplaysCorrectly() { diff --git a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj index 9c146f426..c801384ab 100644 --- a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj +++ b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj @@ -34,8 +34,7 @@ - - +