diff --git a/src/PowerShellEditorServices.Protocol/DebugAdapter/Breakpoint.cs b/src/PowerShellEditorServices.Protocol/DebugAdapter/Breakpoint.cs index 9dae1f1fc..fb51a727e 100644 --- a/src/PowerShellEditorServices.Protocol/DebugAdapter/Breakpoint.cs +++ b/src/PowerShellEditorServices.Protocol/DebugAdapter/Breakpoint.cs @@ -7,10 +7,24 @@ namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter { public class Breakpoint { + /// + /// Gets an boolean indicator that if true, breakpoint could be set + /// (but not necessarily at the desired location). + /// public bool Verified { get; set; } + /// + /// Gets an optional message about the state of the breakpoint. This is shown to the user + /// and can be used to explain why a breakpoint could not be verified. + /// + public string Message { get; set; } + + public string Source { get; set; } + public int Line { get; set; } + public int? Column { get; set; } + private Breakpoint() { } @@ -20,8 +34,11 @@ public static Breakpoint Create( { return new Breakpoint { + Verified = breakpointDetails.Verified, + Message = breakpointDetails.Message, + Source = breakpointDetails.Source, Line = breakpointDetails.LineNumber, - Verified = true + Column = breakpointDetails.ColumnNumber }; } } diff --git a/src/PowerShellEditorServices.Protocol/DebugAdapter/SetBreakpointsRequest.cs b/src/PowerShellEditorServices.Protocol/DebugAdapter/SetBreakpointsRequest.cs index ff505f7e0..a4993291d 100644 --- a/src/PowerShellEditorServices.Protocol/DebugAdapter/SetBreakpointsRequest.cs +++ b/src/PowerShellEditorServices.Protocol/DebugAdapter/SetBreakpointsRequest.cs @@ -7,11 +7,12 @@ namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter { - // /** SetBreakpoints request; value of command field is "setBreakpoints". - // Sets multiple breakpoints for a single source and clears all previous breakpoints in that source. - // To clear all breakpoint for a source, specify an empty array. - // When a breakpoint is hit, a StoppedEvent (event type 'breakpoint') is generated. - // */ + /// + /// SetBreakpoints request; value of command field is "setBreakpoints". + /// Sets multiple breakpoints for a single source and clears all previous breakpoints in that source. + /// To clear all breakpoint for a source, specify an empty array. + /// When a breakpoint is hit, a StoppedEvent (event type 'breakpoint') is generated. + /// public class SetBreakpointsRequest { public static readonly @@ -23,7 +24,16 @@ public class SetBreakpointsRequestArguments { public Source Source { get; set; } - public int[] Lines { get; set; } + public SourceBreakpoint[] Breakpoints { get; set; } + } + + public class SourceBreakpoint + { + public int Line { get; set; } + + public int? Column { get; set; } + + public string Condition { get; set; } } public class SetBreakpointsResponseBody @@ -31,4 +41,3 @@ public class SetBreakpointsResponseBody public Breakpoint[] Breakpoints { get; set; } } } - diff --git a/src/PowerShellEditorServices.Protocol/Server/DebugAdapter.cs b/src/PowerShellEditorServices.Protocol/Server/DebugAdapter.cs index ebcee82bf..d5c67774c 100644 --- a/src/PowerShellEditorServices.Protocol/Server/DebugAdapter.cs +++ b/src/PowerShellEditorServices.Protocol/Server/DebugAdapter.cs @@ -172,10 +172,21 @@ protected async Task HandleSetBreakpointsRequest( editorSession.Workspace.GetFile( setBreakpointsParams.Source.Path); + var breakpointDetails = new BreakpointDetails[setBreakpointsParams.Breakpoints.Length]; + for (int i = 0; i < breakpointDetails.Length; i++) + { + SourceBreakpoint srcBreakpoint = setBreakpointsParams.Breakpoints[i]; + breakpointDetails[i] = BreakpointDetails.Create( + scriptFile.FilePath, + srcBreakpoint.Line, + srcBreakpoint.Column, + srcBreakpoint.Condition); + } + BreakpointDetails[] breakpoints = - await editorSession.DebugService.SetBreakpoints( + await editorSession.DebugService.SetLineBreakpoints( scriptFile, - setBreakpointsParams.Lines); + breakpointDetails); await requestContext.SendResult( new SetBreakpointsResponseBody diff --git a/src/PowerShellEditorServices.Protocol/Server/DebugAdapterBase.cs b/src/PowerShellEditorServices.Protocol/Server/DebugAdapterBase.cs index eed67a92b..4ea424073 100644 --- a/src/PowerShellEditorServices.Protocol/Server/DebugAdapterBase.cs +++ b/src/PowerShellEditorServices.Protocol/Server/DebugAdapterBase.cs @@ -60,7 +60,11 @@ await requestContext.SendEvent( null); // Now send the Initialize response to continue setup - await requestContext.SendResult(new InitializeResponseBody()); + await requestContext.SendResult( + new InitializeResponseBody + { + SupportsConditionalBreakpoints = true, + }); } } } diff --git a/src/PowerShellEditorServices/Debugging/BreakpointDetails.cs b/src/PowerShellEditorServices/Debugging/BreakpointDetails.cs index 6d50ee790..cf096ca56 100644 --- a/src/PowerShellEditorServices/Debugging/BreakpointDetails.cs +++ b/src/PowerShellEditorServices/Debugging/BreakpointDetails.cs @@ -3,9 +3,9 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // -using Microsoft.PowerShell.EditorServices.Utility; using System; using System.Management.Automation; +using Microsoft.PowerShell.EditorServices.Utility; namespace Microsoft.PowerShell.EditorServices { @@ -15,11 +15,68 @@ namespace Microsoft.PowerShell.EditorServices /// public class BreakpointDetails { + /// + /// Gets or sets a boolean indicator that if true, breakpoint could be set + /// (but not necessarily at the desired location). + /// + public bool Verified { get; set; } + + /// + /// Gets or set an optional message about the state of the breakpoint. This is shown to the user + /// and can be used to explain why a breakpoint could not be verified. + /// + public string Message { get; set; } + + /// + /// Gets the source where the breakpoint is located. Used only for debug purposes. + /// + public string Source { get; private set; } + /// /// Gets the line number at which the breakpoint is set. /// public int LineNumber { get; private set; } + /// + /// Gets the column number at which the breakpoint is set. If null, the default of 1 is used. + /// + public int? ColumnNumber { get; private set; } + + /// + /// Gets the breakpoint condition string. + /// + public string Condition { get; private set; } + + private BreakpointDetails() + { + } + + /// + /// Creates an instance of the BreakpointDetails class from the individual + /// pieces of breakpoint information provided by the client. + /// + /// + /// + /// + /// + /// + public static BreakpointDetails Create( + string source, + int line, + int? column = null, + string condition = null) + { + Validate.IsNotNull("source", source); + + return new BreakpointDetails + { + Source = source, + LineNumber = line, + ColumnNumber = column, + Condition = condition + }; + } + /// /// Creates an instance of the BreakpointDetails class from a /// PowerShell Breakpoint object. @@ -31,18 +88,26 @@ public static BreakpointDetails Create(Breakpoint breakpoint) Validate.IsNotNull("breakpoint", breakpoint); LineBreakpoint lineBreakpoint = breakpoint as LineBreakpoint; - if (lineBreakpoint != null) - { - return new BreakpointDetails - { - LineNumber = lineBreakpoint.Line - }; - } - else + if (lineBreakpoint == null) { throw new ArgumentException( "Expected breakpoint type:" + breakpoint.GetType().Name); } + + var breakpointDetails = new BreakpointDetails + { + Verified = true, + Source = lineBreakpoint.Script, + LineNumber = lineBreakpoint.Line, + Condition = lineBreakpoint.Action?.ToString() + }; + + if (lineBreakpoint.Column > 0) + { + breakpointDetails.ColumnNumber = lineBreakpoint.Column; + } + + return breakpointDetails; } } } diff --git a/src/PowerShellEditorServices/Debugging/DebugService.cs b/src/PowerShellEditorServices/Debugging/DebugService.cs index 53095f872..8e846a3af 100644 --- a/src/PowerShellEditorServices/Debugging/DebugService.cs +++ b/src/PowerShellEditorServices/Debugging/DebugService.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Linq; using System.Management.Automation; +using System.Management.Automation.Language; using System.Threading.Tasks; using Microsoft.PowerShell.EditorServices.Utility; @@ -57,47 +58,130 @@ public DebugService(PowerShellContext powerShellContext) #region Public Methods /// - /// Sets the list of breakpoints for the current debugging session. + /// Sets the list of line breakpoints for the current debugging session. /// /// The ScriptFile in which breakpoints will be set. - /// The line numbers at which breakpoints will be set. + /// BreakpointDetails for each breakpoint that will be set. /// If true, causes all existing breakpoints to be cleared before setting new ones. /// An awaitable Task that will provide details about the breakpoints that were set. - public async Task SetBreakpoints( + public async Task SetLineBreakpoints( ScriptFile scriptFile, - int[] lineNumbers, + BreakpointDetails[] breakpoints, bool clearExisting = true) { - IEnumerable resultBreakpoints = null; + var resultBreakpointDetails = new List(); if (clearExisting) { await this.ClearBreakpointsInFile(scriptFile); } - if (lineNumbers.Length > 0) + if (breakpoints.Length > 0) { // Fix for issue #123 - file paths that contain wildcard chars [ and ] need to // quoted and have those wildcard chars escaped. string escapedScriptPath = PowerShellContext.EscapePath(scriptFile.FilePath, escapeSpaces: false); - PSCommand psCommand = new PSCommand(); - psCommand.AddCommand("Set-PSBreakpoint"); - psCommand.AddParameter("Script", escapedScriptPath); - psCommand.AddParameter("Line", lineNumbers.Length > 0 ? lineNumbers : null); + // Line breakpoints with no condition and no column number are the most common, + // so let's optimize for that case by making a single call to Set-PSBreakpoint + // with all the lines to set a breakpoint on. + int[] lineOnlyBreakpoints = + breakpoints.Where(b => (b.ColumnNumber == null) && (b.Condition == null)) + .Select(b => b.LineNumber) + .ToArray(); + + if (lineOnlyBreakpoints.Length > 0) + { + PSCommand psCommand = new PSCommand(); + psCommand.AddCommand("Set-PSBreakpoint"); + psCommand.AddParameter("Script", escapedScriptPath); + psCommand.AddParameter("Line", lineOnlyBreakpoints); - resultBreakpoints = - await this.powerShellContext.ExecuteCommand( - psCommand); + var configuredBreakpoints = + await this.powerShellContext.ExecuteCommand(psCommand); - return - resultBreakpoints - .Select(BreakpointDetails.Create) + resultBreakpointDetails.AddRange( + configuredBreakpoints.Select(BreakpointDetails.Create)); + } + + // Process the rest of the breakpoints + var advancedLineBreakpoints = + breakpoints.Where(b => (b.ColumnNumber != null) || (b.Condition != null)) .ToArray(); + + foreach (BreakpointDetails breakpoint in advancedLineBreakpoints) + { + PSCommand psCommand = new PSCommand(); + psCommand.AddCommand("Set-PSBreakpoint"); + psCommand.AddParameter("Script", escapedScriptPath); + psCommand.AddParameter("Line", breakpoint.LineNumber); + + // Check if the user has specified the column number for the breakpoint. + if (breakpoint.ColumnNumber.HasValue) + { + // It bums me out that PowerShell will silently ignore a breakpoint + // where either the line or the column is invalid. I'd rather have an + // error message I could rely back to the client. + psCommand.AddParameter("Column", breakpoint.ColumnNumber.Value); + } + + // Check if this is a "conditional" line breakpoint. + if (breakpoint.Condition != null) + { + try + { + ScriptBlock actionScriptBlock = ScriptBlock.Create(breakpoint.Condition); + + // Check for simple, common errors that ScriptBlock parsing will not catch + // e.g. $i == 3 and $i > 3 + string message; + if (!ValidateBreakpointConditionAst(actionScriptBlock.Ast, out message)) + { + breakpoint.Verified = false; + breakpoint.Message = message; + resultBreakpointDetails.Add(breakpoint); + continue; + } + + // Check for "advanced" condition syntax i.e. if the user has specified + // a "break" or "continue" statement anywhere in their scriptblock, + // pass their scriptblock through to the Action parameter as-is. + Ast breakOrContinueStatementAst = + actionScriptBlock.Ast.Find( + ast => (ast is BreakStatementAst || ast is ContinueStatementAst), true); + + // If this isn't advanced syntax then the conditions string should be a simple + // expression that needs to be wrapped in a "if" test that conditionally executes + // a break statement. + if (breakOrContinueStatementAst == null) + { + string wrappedCondition = $"if ({breakpoint.Condition}) {{ break }}"; + actionScriptBlock = ScriptBlock.Create(wrappedCondition); + } + + psCommand.AddParameter("Action", actionScriptBlock); + } + catch (ParseException ex) + { + // Failed to create conditional breakpoint likely because the user provided an + // invalid PowerShell expression. Let the user know why. + breakpoint.Verified = false; + breakpoint.Message = ExtractAndScrubParseExceptionMessage(ex, breakpoint.Condition); + resultBreakpointDetails.Add(breakpoint); + continue; + } + } + + IEnumerable configuredBreakpoints = + await this.powerShellContext.ExecuteCommand(psCommand); + + resultBreakpointDetails.AddRange( + configuredBreakpoints.Select(BreakpointDetails.Create)); + } } - return new BreakpointDetails[0]; + return resultBreakpointDetails.ToArray(); } /// @@ -463,6 +547,85 @@ private async Task FetchStackFrames() } } + private bool ValidateBreakpointConditionAst(Ast conditionAst, out string message) + { + message = string.Empty; + + // We are only inspecting a few simple scenarios in the EndBlock only. + ScriptBlockAst scriptBlockAst = conditionAst as ScriptBlockAst; + if ((scriptBlockAst != null) && + (scriptBlockAst.BeginBlock == null) && + (scriptBlockAst.ProcessBlock == null) && + (scriptBlockAst.EndBlock != null) && + (scriptBlockAst.EndBlock.Statements.Count == 1)) + { + StatementAst statementAst = scriptBlockAst.EndBlock.Statements[0]; + string condition = statementAst.Extent.Text; + + if (statementAst is AssignmentStatementAst) + { + message = FormatInvalidBreakpointConditionMessage(condition, "Use '-eq' instead of '=='."); + return false; + } + + PipelineAst pipelineAst = statementAst as PipelineAst; + if ((pipelineAst != null) && (pipelineAst.PipelineElements.Count == 1) && + (pipelineAst.PipelineElements[0].Redirections.Count > 0)) + { + message = FormatInvalidBreakpointConditionMessage(condition, "Use '-gt' instead of '>'."); + return false; + } + } + + return true; + } + + private string ExtractAndScrubParseExceptionMessage(ParseException parseException, string condition) + { + string[] messageLines = parseException.Message.Split('\n'); + + // Skip first line - it is a location indicator "At line:1 char: 4" + for (int i = 1; i < messageLines.Length; i++) + { + string line = messageLines[i]; + if (line.StartsWith("+")) + { + continue; + } + + if (!string.IsNullOrWhiteSpace(line)) + { + // Note '==' and '>" do not generate parse errors + if (line.Contains("'!='")) + { + line += " Use operator '-ne' instead of '!='."; + } + else if (line.Contains("'<'") && condition.Contains("<=")) + { + line += " Use operator '-le' instead of '<='."; + } + else if (line.Contains("'<'")) + { + line += " Use operator '-lt' instead of '<'."; + } + else if (condition.Contains(">=")) + { + line += " Use operator '-ge' instead of '>='."; + } + + return FormatInvalidBreakpointConditionMessage(condition, line); + } + } + + // If the message format isn't in a form we expect, just return the whole message. + return FormatInvalidBreakpointConditionMessage(condition, parseException.Message); + } + + private string FormatInvalidBreakpointConditionMessage(string condition, string message) + { + return $"'{condition}' is not a valid PowerShell expression. {message}"; + } + #endregion #region Events diff --git a/test/PowerShellEditorServices.Test.Host/DebugAdapterTests.cs b/test/PowerShellEditorServices.Test.Host/DebugAdapterTests.cs index 8c5e20d4f..d2a283aff 100644 --- a/test/PowerShellEditorServices.Test.Host/DebugAdapterTests.cs +++ b/test/PowerShellEditorServices.Test.Host/DebugAdapterTests.cs @@ -48,7 +48,7 @@ public Task DisposeAsync() } [Fact] - public async Task DebugAdapterStopsOnBreakpoints() + public async Task DebugAdapterStopsOnLineBreakpoints() { await this.SendRequest( SetBreakpointsRequest.Type, @@ -58,7 +58,11 @@ await this.SendRequest( { Path = DebugScriptPath }, - Lines = new int[] { 5, 7 } + Breakpoints = new [] + { + new SourceBreakpoint { Line = 5 }, + new SourceBreakpoint { Line = 7 } + } }); Task breakEventTask = this.WaitForEvent(StoppedEvent.Type); diff --git a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs index bf7e6b71e..44c18ec0c 100644 --- a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs @@ -19,6 +19,7 @@ public class DebugServiceTests : IDisposable private Workspace workspace; private DebugService debugService; private ScriptFile debugScriptFile; + private ScriptFile variableScriptFile; private PowerShellContext powerShellContext; private SynchronizationContext runnerContext; @@ -36,6 +37,10 @@ public DebugServiceTests() this.workspace.GetFile( @"..\..\..\PowerShellEditorServices.Test.Shared\Debugging\DebugTest.ps1"); + this.variableScriptFile = + this.workspace.GetFile( + @"..\..\..\PowerShellEditorServices.Test.Shared\Debugging\VariableTest.ps1"); + this.powerShellContext = new PowerShellContext(); this.powerShellContext.SessionStateChanged += powerShellContext_SessionStateChanged; @@ -94,9 +99,9 @@ public async Task DebuggerAcceptsScriptArgs(string[] args) this.workspace.GetFile( @"..\..\..\PowerShellEditorServices.Test.Shared\Debugging\Debug` With Params `[Test].ps1"); - await this.debugService.SetBreakpoints( + await this.debugService.SetLineBreakpoints( debugWithParamsFile, - new int[] { 3 }); + new[] { BreakpointDetails.Create("", 3) }); string arguments = string.Join(" ", args); @@ -146,37 +151,44 @@ await this.debugService.SetBreakpoints( public async Task DebuggerSetsAndClearsBreakpoints() { BreakpointDetails[] breakpoints = - await this.debugService.SetBreakpoints( - this.debugScriptFile, - new int[] { 5, 9 }); + await this.debugService.SetLineBreakpoints( + this.debugScriptFile, + new[] { + BreakpointDetails.Create("", 5), + BreakpointDetails.Create("", 10) + }); Assert.Equal(2, breakpoints.Length); Assert.Equal(5, breakpoints[0].LineNumber); - Assert.Equal(9, breakpoints[1].LineNumber); + Assert.Equal(10, breakpoints[1].LineNumber); breakpoints = - await this.debugService.SetBreakpoints( - this.debugScriptFile, - new int[] { 2 }); + await this.debugService.SetLineBreakpoints( + this.debugScriptFile, + new[] { BreakpointDetails.Create("", 2) }); Assert.Equal(1, breakpoints.Length); Assert.Equal(2, breakpoints[0].LineNumber); breakpoints = - await this.debugService.SetBreakpoints( - this.debugScriptFile, - new int[0]); + await this.debugService.SetLineBreakpoints( + this.debugScriptFile, + new[] { BreakpointDetails.Create("", 0) }); Assert.Equal(0, breakpoints.Length); } [Fact] - public async Task DebuggerStopsOnBreakpoints() + public async Task DebuggerStopsOnLineBreakpoints() { BreakpointDetails[] breakpoints = - await this.debugService.SetBreakpoints( - this.debugScriptFile, - new int[] { 5, 7 }); + await this.debugService.SetLineBreakpoints( + this.debugScriptFile, + new[] { + BreakpointDetails.Create("", 5), + BreakpointDetails.Create("", 7) + }); + await this.AssertStateChange(PowerShellContextState.Ready); Task executeTask = @@ -194,6 +206,101 @@ await this.debugService.SetBreakpoints( await executeTask; } + [Fact] + public async Task DebuggerStopsOnConditionalBreakpoints() + { + const int breakpointValue1 = 10; + const int breakpointValue2 = 20; + + BreakpointDetails[] breakpoints = + await this.debugService.SetLineBreakpoints( + this.debugScriptFile, + new[] { + BreakpointDetails.Create("", 7, null, $"$i -eq {breakpointValue1} -or $i -eq {breakpointValue2}"), + }); + + await this.AssertStateChange(PowerShellContextState.Ready); + + Task executeTask = + this.powerShellContext.ExecuteScriptAtPath( + this.debugScriptFile.FilePath); + + // Wait for conditional breakpoint to hit + await this.AssertDebuggerStopped(this.debugScriptFile.FilePath, 7); + + StackFrameDetails[] stackFrames = debugService.GetStackFrames(); + VariableDetailsBase[] variables = + debugService.GetVariables(stackFrames[0].LocalVariables.Id); + + // Verify the breakpoint only broke at the condition ie. $i -eq breakpointValue1 + var i = variables.FirstOrDefault(v => v.Name == "$i"); + Assert.NotNull(i); + Assert.False(i.IsExpandable); + Assert.Equal($"{breakpointValue1}", i.ValueString); + + // The conditional breakpoint should not fire again, until the value of + // i reaches breakpointValue2. + this.debugService.Continue(); + await this.AssertDebuggerStopped(this.debugScriptFile.FilePath, 7); + + stackFrames = debugService.GetStackFrames(); + variables = debugService.GetVariables(stackFrames[0].LocalVariables.Id); + + // Verify the breakpoint only broke at the condition ie. $i -eq breakpointValue1 + i = variables.FirstOrDefault(v => v.Name == "$i"); + Assert.NotNull(i); + Assert.False(i.IsExpandable); + Assert.Equal($"{breakpointValue2}", i.ValueString); + + // Abort script execution early and wait for completion + this.debugService.Abort(); + await executeTask; + } + + [Fact] + public async Task DebuggerProvidesMessageForInvalidConditionalBreakpoint() + { + BreakpointDetails[] breakpoints = + await this.debugService.SetLineBreakpoints( + this.debugScriptFile, + new[] { + BreakpointDetails.Create("", 5), + BreakpointDetails.Create("", 10, column: null, condition: "$i -ez 100") + }); + + Assert.Equal(2, breakpoints.Length); + Assert.Equal(5, breakpoints[0].LineNumber); + Assert.True(breakpoints[0].Verified); + Assert.Null(breakpoints[0].Message); + + Assert.Equal(10, breakpoints[1].LineNumber); + Assert.False(breakpoints[1].Verified); + Assert.NotNull(breakpoints[1].Message); + Assert.Contains("Unexpected token '-ez'", breakpoints[1].Message); + } + + [Fact] + public async Task DebuggerFindsParseableButInvalidSimpleBreakpointConditions() + { + BreakpointDetails[] breakpoints = + await this.debugService.SetLineBreakpoints( + this.debugScriptFile, + new[] { + BreakpointDetails.Create("", 5, column: null, condition: "$i == 100"), + BreakpointDetails.Create("", 7, column: null, condition: "$i > 100") + }); + + Assert.Equal(2, breakpoints.Length); + Assert.Equal(5, breakpoints[0].LineNumber); + Assert.False(breakpoints[0].Verified); + Assert.Contains("Use '-eq' instead of '=='", breakpoints[0].Message); + + Assert.Equal(7, breakpoints[1].LineNumber); + Assert.False(breakpoints[1].Verified); + Assert.NotNull(breakpoints[1].Message); + Assert.Contains("Use '-gt' instead of '>'", breakpoints[1].Message); + } + [Fact] public async Task DebuggerBreaksWhenRequested() { @@ -243,20 +350,16 @@ await this.AssertStateChange( [Fact] public async Task DebuggerVariableStringDisplaysCorrectly() { - ScriptFile variablesFile = - this.workspace.GetFile( - @"..\..\..\PowerShellEditorServices.Test.Shared\Debugging\VariableTest.ps1"); - - await this.debugService.SetBreakpoints( - variablesFile, - new int[] { 18 }); + await this.debugService.SetLineBreakpoints( + this.variableScriptFile, + new[] { BreakpointDetails.Create("", 18) }); // Execute the script and wait for the breakpoint to be hit Task executeTask = this.powerShellContext.ExecuteScriptString( - variablesFile.FilePath); + this.variableScriptFile.FilePath); - await this.AssertDebuggerStopped(variablesFile.FilePath); + await this.AssertDebuggerStopped(this.variableScriptFile.FilePath); StackFrameDetails[] stackFrames = debugService.GetStackFrames(); @@ -275,20 +378,16 @@ await this.debugService.SetBreakpoints( [Fact] public async Task DebuggerGetsVariables() { - ScriptFile variablesFile = - this.workspace.GetFile( - @"..\..\..\PowerShellEditorServices.Test.Shared\Debugging\VariableTest.ps1"); - - await this.debugService.SetBreakpoints( - variablesFile, - new int[] { 14 }); + await this.debugService.SetLineBreakpoints( + this.variableScriptFile, + new[] { BreakpointDetails.Create("", 14) }); // Execute the script and wait for the breakpoint to be hit Task executeTask = this.powerShellContext.ExecuteScriptString( - variablesFile.FilePath); + this.variableScriptFile.FilePath); - await this.AssertDebuggerStopped(variablesFile.FilePath); + await this.AssertDebuggerStopped(this.variableScriptFile.FilePath); StackFrameDetails[] stackFrames = debugService.GetStackFrames(); @@ -329,20 +428,16 @@ await this.debugService.SetBreakpoints( [Fact] public async Task DebuggerVariableEnumDisplaysCorrectly() { - ScriptFile variablesFile = - this.workspace.GetFile( - @"..\..\..\PowerShellEditorServices.Test.Shared\Debugging\VariableTest.ps1"); - - await this.debugService.SetBreakpoints( - variablesFile, - new int[] { 18 }); + await this.debugService.SetLineBreakpoints( + this.variableScriptFile, + new[] { BreakpointDetails.Create("", 18) }); // Execute the script and wait for the breakpoint to be hit Task executeTask = this.powerShellContext.ExecuteScriptString( - variablesFile.FilePath); + this.variableScriptFile.FilePath); - await this.AssertDebuggerStopped(variablesFile.FilePath); + await this.AssertDebuggerStopped(this.variableScriptFile.FilePath); StackFrameDetails[] stackFrames = debugService.GetStackFrames(); @@ -361,20 +456,16 @@ await this.debugService.SetBreakpoints( [Fact] public async Task DebuggerVariableHashtableDisplaysCorrectly() { - ScriptFile variablesFile = - this.workspace.GetFile( - @"..\..\..\PowerShellEditorServices.Test.Shared\Debugging\VariableTest.ps1"); - - await this.debugService.SetBreakpoints( - variablesFile, - new int[] { 18 }); + await this.debugService.SetLineBreakpoints( + this.variableScriptFile, + new[] { BreakpointDetails.Create("", 18) }); // Execute the script and wait for the breakpoint to be hit Task executeTask = this.powerShellContext.ExecuteScriptString( - variablesFile.FilePath); + this.variableScriptFile.FilePath); - await this.AssertDebuggerStopped(variablesFile.FilePath); + await this.AssertDebuggerStopped(this.variableScriptFile.FilePath); StackFrameDetails[] stackFrames = debugService.GetStackFrames(); @@ -400,20 +491,16 @@ await this.debugService.SetBreakpoints( [Fact] public async Task DebuggerVariablePSObjectDisplaysCorrectly() { - ScriptFile variablesFile = - this.workspace.GetFile( - @"..\..\..\PowerShellEditorServices.Test.Shared\Debugging\VariableTest.ps1"); - - await this.debugService.SetBreakpoints( - variablesFile, - new int[] { 18 }); + await this.debugService.SetLineBreakpoints( + this.variableScriptFile, + new[] { BreakpointDetails.Create("", 18) }); // Execute the script and wait for the breakpoint to be hit Task executeTask = this.powerShellContext.ExecuteScriptString( - variablesFile.FilePath); + this.variableScriptFile.FilePath); - await this.AssertDebuggerStopped(variablesFile.FilePath); + await this.AssertDebuggerStopped(this.variableScriptFile.FilePath); StackFrameDetails[] stackFrames = debugService.GetStackFrames(); @@ -439,20 +526,16 @@ await this.debugService.SetBreakpoints( [Fact] public async Task DebuggerVariablePSCustomObjectDisplaysCorrectly() { - ScriptFile variablesFile = - this.workspace.GetFile( - @"..\..\..\PowerShellEditorServices.Test.Shared\Debugging\VariableTest.ps1"); - - await this.debugService.SetBreakpoints( - variablesFile, - new int[] { 18 }); + await this.debugService.SetLineBreakpoints( + this.variableScriptFile, + new[] { BreakpointDetails.Create("", 18) }); // Execute the script and wait for the breakpoint to be hit Task executeTask = this.powerShellContext.ExecuteScriptString( - variablesFile.FilePath); + this.variableScriptFile.FilePath); - await this.AssertDebuggerStopped(variablesFile.FilePath); + await this.AssertDebuggerStopped(this.variableScriptFile.FilePath); StackFrameDetails[] stackFrames = debugService.GetStackFrames(); @@ -480,20 +563,16 @@ await this.debugService.SetBreakpoints( [Fact] public async Task DebuggerVariableProcessObjDisplaysCorrectly() { - ScriptFile variablesFile = - this.workspace.GetFile( - @"..\..\..\PowerShellEditorServices.Test.Shared\Debugging\VariableTest.ps1"); - - await this.debugService.SetBreakpoints( - variablesFile, - new int[] { 18 }); + await this.debugService.SetLineBreakpoints( + this.variableScriptFile, + new[] { BreakpointDetails.Create("", 18) }); // Execute the script and wait for the breakpoint to be hit Task executeTask = this.powerShellContext.ExecuteScriptString( - variablesFile.FilePath); + this.variableScriptFile.FilePath); - await this.AssertDebuggerStopped(variablesFile.FilePath); + await this.AssertDebuggerStopped(this.variableScriptFile.FilePath); StackFrameDetails[] stackFrames = debugService.GetStackFrames();