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();