From 9b8af17e59eab25cbe216d4169a1c9a0f1ceecc1 Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Thu, 14 Feb 2019 10:44:08 -0800 Subject: [PATCH 01/19] initial New-PSBreakpoint --- .../commands/utility/New-PSBreakpoint.cs | 215 ++++++++++++++++++ .../Microsoft.PowerShell.Utility.psd1 | 2 +- .../Microsoft.PowerShell.Utility.psd1 | 2 +- 3 files changed, 217 insertions(+), 2 deletions(-) create mode 100644 src/Microsoft.PowerShell.Commands.Utility/commands/utility/New-PSBreakpoint.cs diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/New-PSBreakpoint.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/New-PSBreakpoint.cs new file mode 100644 index 00000000000..3a432daf73b --- /dev/null +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/New-PSBreakpoint.cs @@ -0,0 +1,215 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.IO; +using System.Management.Automation; +using System.Management.Automation.Internal; + +namespace Microsoft.PowerShell.Commands +{ + /// + /// This class implements New-PSBreakpoint command. + /// + [Cmdlet(VerbsCommon.New, "PSBreakpoint", DefaultParameterSetName = "Line", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113449")] + [OutputType(typeof(VariableBreakpoint), typeof(CommandBreakpoint), typeof(LineBreakpoint))] + public class NewPSBreakpointCommand : PSCmdlet + { + #region parameters + + /// + /// The action to take when hitting this breakpoint. + /// + [Parameter(ParameterSetName = "Command")] + [Parameter(ParameterSetName = "Line")] + [Parameter(ParameterSetName = "Variable")] + public ScriptBlock Action { get; set; } = null; + + /// + /// The column to set the breakpoint on. + /// + [Parameter(Position = 2, ParameterSetName = "Line")] + [ValidateRange(1, int.MaxValue)] + public int Column + { + get + { + return _column ?? 0; + } + + set + { + _column = value; + } + } + + private int? _column = null; + + /// + /// The command(s) to set the breakpoint on. + /// + [Alias("C")] + [Parameter(ParameterSetName = "Command", Mandatory = true)] + [ValidateNotNull] + public string[] Command { get; set; } = null; + + /// + /// The line to set the breakpoint on. + /// + [Parameter(Position = 1, ParameterSetName = "Line", Mandatory = true)] + [ValidateNotNull] + public int[] Line { get; set; } = null; + + /// + /// The script to set the breakpoint on. + /// + [Parameter(ParameterSetName = "Command", Position = 0)] + [Parameter(ParameterSetName = "Line", Mandatory = true, Position = 0)] + [Parameter(ParameterSetName = "Variable", Position = 0)] + [ValidateNotNull] + public string[] Script { get; set; } = null; + + /// + /// The variables to set the breakpoint(s) on. + /// + [Alias("V")] + [Parameter(ParameterSetName = "Variable", Mandatory = true)] + [ValidateNotNull] + public string[] Variable { get; set; } = null; + + /// + /// + [Parameter(ParameterSetName = "Variable")] + public VariableAccessMode Mode { get; set; } = VariableAccessMode.Write; + + #endregion parameters + + /// + /// Create a new breakpoint. + /// + protected override void ProcessRecord() + { + // If there is a script, resolve its path + Collection scripts = new Collection(); + + if (Script != null) + { + foreach (string script in Script) + { + Collection scriptPaths = SessionState.Path.GetResolvedPSPathFromPSPath(script); + + for (int i = 0; i < scriptPaths.Count; i++) + { + string providerPath = scriptPaths[i].ProviderPath; + + if (!File.Exists(providerPath)) + { + WriteError( + new ErrorRecord( + new ArgumentException(StringUtil.Format(Debugger.FileDoesNotExist, providerPath)), + "NewPSBreakpoint:FileDoesNotExist", + ErrorCategory.InvalidArgument, + null)); + + continue; + } + + string extension = Path.GetExtension(providerPath); + + if (!extension.Equals(".ps1", StringComparison.OrdinalIgnoreCase) && !extension.Equals(".psm1", StringComparison.OrdinalIgnoreCase)) + { + WriteError( + new ErrorRecord( + new ArgumentException(StringUtil.Format(Debugger.WrongExtension, providerPath)), + "NewPSBreakpoint:WrongExtension", + ErrorCategory.InvalidArgument, + null)); + continue; + } + + scripts.Add(Path.GetFullPath(providerPath)); + } + } + } + + // + // If it is a command breakpoint... + // + if (ParameterSetName.Equals("Command", StringComparison.OrdinalIgnoreCase)) + { + for (int i = 0; i < Command.Length; i++) + { + if (scripts.Count > 0) + { + foreach (string path in scripts) + { + WildcardPattern pattern = WildcardPattern.Get(Command[i], WildcardOptions.Compiled | WildcardOptions.IgnoreCase); + WriteObject(new CommandBreakpoint(path, pattern, Command[i], Action)); + } + } + else + { + WildcardPattern pattern = WildcardPattern.Get(Command[i], WildcardOptions.Compiled | WildcardOptions.IgnoreCase); + WriteObject(new CommandBreakpoint(null, pattern, Command[i], Action)); + } + } + } + // + // If it is a variable breakpoint... + // + else if (ParameterSetName.Equals("Variable", StringComparison.OrdinalIgnoreCase)) + { + for (int i = 0; i < Variable.Length; i++) + { + if (scripts.Count > 0) + { + foreach (string path in scripts) + { + WriteObject(new VariableBreakpoint(path, Variable[i], Mode, Action)); + } + } + else + { + WriteObject(new VariableBreakpoint(null, Variable[i], Mode, Action)); + } + } + } + // + // Else it is the default parameter set (Line breakpoint)... + // + else + { + Debug.Assert(ParameterSetName.Equals("Line", StringComparison.OrdinalIgnoreCase)); + + for (int i = 0; i < Line.Length; i++) + { + if (Line[i] < 1) + { + WriteError( + new ErrorRecord( + new ArgumentException(Debugger.LineLessThanOne), + "NewPSBreakpoint:LineLessThanOne", + ErrorCategory.InvalidArgument, + null)); + + continue; + } + + foreach (string path in scripts) + { + if (_column != null) + { + WriteObject(new LineBreakpoint(path, Line[i], Column, Action)); + } + else + { + WriteObject(new LineBreakpoint(path, Line[i], Action)); + } + } + } + } + } + } +} diff --git a/src/Modules/Unix/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 b/src/Modules/Unix/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 index 863b38f7498..678c32e1cb2 100644 --- a/src/Modules/Unix/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 +++ b/src/Modules/Unix/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 @@ -18,7 +18,7 @@ CmdletsToExport = @( 'Set-MarkdownOption', 'Add-Member', 'Get-Member', 'Compare-Object', 'Group-Object', 'Measure-Object', 'New-Object', 'Select-Object', 'Sort-Object', 'Tee-Object', 'Register-ObjectEvent', 'Write-Output', 'Import-PowerShellDataFile', 'Write-Progress', 'Disable-PSBreakpoint', 'Enable-PSBreakpoint', - 'Get-PSBreakpoint', 'Remove-PSBreakpoint', 'Set-PSBreakpoint', 'Get-PSCallStack', 'Export-PSSession', + 'Get-PSBreakpoint', 'Remove-PSBreakpoint', 'Set-PSBreakpoint', 'New-PSBreakpoint', 'Get-PSCallStack', 'Export-PSSession', 'Import-PSSession', 'Get-Random', 'Invoke-RestMethod', 'Debug-Runspace', 'Get-Runspace', 'Disable-RunspaceDebug', 'Enable-RunspaceDebug', 'Get-RunspaceDebug', 'Start-Sleep', 'Join-String', 'Out-String', 'Select-String', 'ConvertFrom-StringData', 'Format-Table', 'New-TemporaryFile', 'New-TimeSpan', diff --git a/src/Modules/Windows/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 b/src/Modules/Windows/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 index 2db43e68531..152f8832688 100644 --- a/src/Modules/Windows/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 +++ b/src/Modules/Windows/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 @@ -17,7 +17,7 @@ CmdletsToExport = @( 'Show-Markdown', 'Get-MarkdownOption', 'Set-MarkdownOption', 'Add-Member', 'Get-Member', 'Compare-Object', 'Group-Object', 'Measure-Object', 'New-Object', 'Select-Object', 'Sort-Object', 'Tee-Object', 'Register-ObjectEvent', 'Write-Output', 'Import-PowerShellDataFile', 'Write-Progress', 'Disable-PSBreakpoint', 'Enable-PSBreakpoint', 'Get-PSBreakpoint', - 'Remove-PSBreakpoint', 'Set-PSBreakpoint', 'Get-PSCallStack', 'Export-PSSession', 'Import-PSSession', 'Get-Random', + 'Remove-PSBreakpoint', 'Set-PSBreakpoint', 'New-PSBreakpoint', 'Get-PSCallStack', 'Export-PSSession', 'Import-PSSession', 'Get-Random', 'Invoke-RestMethod', 'Debug-Runspace', 'Get-Runspace', 'Disable-RunspaceDebug', 'Enable-RunspaceDebug', 'Get-RunspaceDebug', 'ConvertFrom-SddlString', 'Start-Sleep', 'Join-String', 'Out-String', 'Select-String', 'ConvertFrom-StringData', 'Format-Table', 'New-TemporaryFile', 'New-TimeSpan', 'Get-TraceSource', 'Set-TraceSource', From dcfa770f1643e1eff8c3395b827d2d6af7ae5fa4 Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Fri, 15 Feb 2019 12:37:58 -0800 Subject: [PATCH 02/19] refactor new/set-psbreakpoint --- .../commands/utility/Disable-PSBreakpoint.cs | 2 +- .../commands/utility/Enable-PSBreakpoint.cs | 4 +- .../commands/utility/New-PSBreakpoint.cs | 115 +---------------- .../utility/PSBreakpointCreationBase.cs | 120 ++++++++++++++++++ .../commands/utility/Remove-PSBreakpoint.cs | 2 +- .../commands/utility/Set-PSBreakpoint.cs | 115 +---------------- 6 files changed, 130 insertions(+), 228 deletions(-) create mode 100644 src/Microsoft.PowerShell.Commands.Utility/commands/utility/PSBreakpointCreationBase.cs diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Disable-PSBreakpoint.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Disable-PSBreakpoint.cs index e8008650ccb..f0dce3e3302 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Disable-PSBreakpoint.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Disable-PSBreakpoint.cs @@ -10,7 +10,7 @@ namespace Microsoft.PowerShell.Commands /// [Cmdlet(VerbsLifecycle.Disable, "PSBreakpoint", SupportsShouldProcess = true, DefaultParameterSetName = "Breakpoint", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113294")] [OutputType(typeof(Breakpoint))] - public class DisablePSBreakpointCommand : PSBreakpointCommandBase + public class DisablePSBreakpointCommand : PSBreakpointStatusBase { /// /// Gets or sets the parameter -passThru which states whether the diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Enable-PSBreakpoint.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Enable-PSBreakpoint.cs index e697d36bc2b..57eeb4f638a 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Enable-PSBreakpoint.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Enable-PSBreakpoint.cs @@ -11,7 +11,7 @@ namespace Microsoft.PowerShell.Commands /// /// Base class for Enable/Disable/Remove-PSBreakpoint. /// - public abstract class PSBreakpointCommandBase : PSCmdlet + public abstract class PSBreakpointStatusBase : PSCmdlet { /// /// The breakpoint to enable. @@ -120,7 +120,7 @@ private bool ShouldProcessInternal(string target) /// [Cmdlet(VerbsLifecycle.Enable, "PSBreakpoint", SupportsShouldProcess = true, DefaultParameterSetName = "Id", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113295")] [OutputType(typeof(Breakpoint))] - public class EnablePSBreakpointCommand : PSBreakpointCommandBase + public class EnablePSBreakpointCommand : PSBreakpointStatusBase { /// /// Gets or sets the parameter -passThru which states whether the diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/New-PSBreakpoint.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/New-PSBreakpoint.cs index 3a432daf73b..3b6f8df869f 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/New-PSBreakpoint.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/New-PSBreakpoint.cs @@ -15,124 +15,15 @@ namespace Microsoft.PowerShell.Commands /// [Cmdlet(VerbsCommon.New, "PSBreakpoint", DefaultParameterSetName = "Line", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113449")] [OutputType(typeof(VariableBreakpoint), typeof(CommandBreakpoint), typeof(LineBreakpoint))] - public class NewPSBreakpointCommand : PSCmdlet + public class NewPSBreakpointCommand : PSBreakpointCreationBase { - #region parameters - - /// - /// The action to take when hitting this breakpoint. - /// - [Parameter(ParameterSetName = "Command")] - [Parameter(ParameterSetName = "Line")] - [Parameter(ParameterSetName = "Variable")] - public ScriptBlock Action { get; set; } = null; - - /// - /// The column to set the breakpoint on. - /// - [Parameter(Position = 2, ParameterSetName = "Line")] - [ValidateRange(1, int.MaxValue)] - public int Column - { - get - { - return _column ?? 0; - } - - set - { - _column = value; - } - } - - private int? _column = null; - - /// - /// The command(s) to set the breakpoint on. - /// - [Alias("C")] - [Parameter(ParameterSetName = "Command", Mandatory = true)] - [ValidateNotNull] - public string[] Command { get; set; } = null; - - /// - /// The line to set the breakpoint on. - /// - [Parameter(Position = 1, ParameterSetName = "Line", Mandatory = true)] - [ValidateNotNull] - public int[] Line { get; set; } = null; - - /// - /// The script to set the breakpoint on. - /// - [Parameter(ParameterSetName = "Command", Position = 0)] - [Parameter(ParameterSetName = "Line", Mandatory = true, Position = 0)] - [Parameter(ParameterSetName = "Variable", Position = 0)] - [ValidateNotNull] - public string[] Script { get; set; } = null; - - /// - /// The variables to set the breakpoint(s) on. - /// - [Alias("V")] - [Parameter(ParameterSetName = "Variable", Mandatory = true)] - [ValidateNotNull] - public string[] Variable { get; set; } = null; - - /// - /// - [Parameter(ParameterSetName = "Variable")] - public VariableAccessMode Mode { get; set; } = VariableAccessMode.Write; - - #endregion parameters - /// /// Create a new breakpoint. /// protected override void ProcessRecord() { // If there is a script, resolve its path - Collection scripts = new Collection(); - - if (Script != null) - { - foreach (string script in Script) - { - Collection scriptPaths = SessionState.Path.GetResolvedPSPathFromPSPath(script); - - for (int i = 0; i < scriptPaths.Count; i++) - { - string providerPath = scriptPaths[i].ProviderPath; - - if (!File.Exists(providerPath)) - { - WriteError( - new ErrorRecord( - new ArgumentException(StringUtil.Format(Debugger.FileDoesNotExist, providerPath)), - "NewPSBreakpoint:FileDoesNotExist", - ErrorCategory.InvalidArgument, - null)); - - continue; - } - - string extension = Path.GetExtension(providerPath); - - if (!extension.Equals(".ps1", StringComparison.OrdinalIgnoreCase) && !extension.Equals(".psm1", StringComparison.OrdinalIgnoreCase)) - { - WriteError( - new ErrorRecord( - new ArgumentException(StringUtil.Format(Debugger.WrongExtension, providerPath)), - "NewPSBreakpoint:WrongExtension", - ErrorCategory.InvalidArgument, - null)); - continue; - } - - scripts.Add(Path.GetFullPath(providerPath)); - } - } - } + Collection scripts = ResolveScriptPaths(); // // If it is a command breakpoint... @@ -199,7 +90,7 @@ protected override void ProcessRecord() foreach (string path in scripts) { - if (_column != null) + if (Column != 0) { WriteObject(new LineBreakpoint(path, Line[i], Column, Action)); } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/PSBreakpointCreationBase.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/PSBreakpointCreationBase.cs new file mode 100644 index 00000000000..d9428a2963d --- /dev/null +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/PSBreakpointCreationBase.cs @@ -0,0 +1,120 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.ObjectModel; +using System.IO; +using System.Management.Automation; +using System.Management.Automation.Internal; + +namespace Microsoft.PowerShell.Commands +{ + /// + /// Base class for Set/New-PSBreakpoint. + /// + public class PSBreakpointCreationBase : PSCmdlet + { + #region parameters + + /// + /// The action to take when hitting this breakpoint. + /// + [Parameter(ParameterSetName = "Command")] + [Parameter(ParameterSetName = "Line")] + [Parameter(ParameterSetName = "Variable")] + public ScriptBlock Action { get; set; } = null; + + /// + /// The column to set the breakpoint on. + /// + [Parameter(Position = 2, ParameterSetName = "Line")] + [ValidateRange(1, int.MaxValue)] + public int Column { get; set; } = 0; + + /// + /// The command(s) to set the breakpoint on. + /// + [Alias("C")] + [Parameter(ParameterSetName = "Command", Mandatory = true)] + [ValidateNotNull] + public string[] Command { get; set; } = null; + + /// + /// The line to set the breakpoint on. + /// + [Parameter(Position = 1, ParameterSetName = "Line", Mandatory = true)] + [ValidateNotNull] + public int[] Line { get; set; } = null; + + /// + /// The script to set the breakpoint on. + /// + [Parameter(ParameterSetName = "Command", Position = 0)] + [Parameter(ParameterSetName = "Line", Mandatory = true, Position = 0)] + [Parameter(ParameterSetName = "Variable", Position = 0)] + [ValidateNotNull] + public string[] Script { get; set; } = null; + + /// + /// The variables to set the breakpoint(s) on. + /// + [Alias("V")] + [Parameter(ParameterSetName = "Variable", Mandatory = true)] + [ValidateNotNull] + public string[] Variable { get; set; } = null; + + /// + /// + [Parameter(ParameterSetName = "Variable")] + public VariableAccessMode Mode { get; set; } = VariableAccessMode.Write; + + #endregion parameters + + internal Collection ResolveScriptPaths() + { + Collection scripts = new Collection(); + + if (Script != null) + { + foreach (string script in Script) + { + Collection scriptPaths = SessionState.Path.GetResolvedPSPathFromPSPath(script); + + for (int i = 0; i < scriptPaths.Count; i++) + { + string providerPath = scriptPaths[i].ProviderPath; + + if (!File.Exists(providerPath)) + { + WriteError( + new ErrorRecord( + new ArgumentException(StringUtil.Format(Debugger.FileDoesNotExist, providerPath)), + "NewPSBreakpoint:FileDoesNotExist", + ErrorCategory.InvalidArgument, + null)); + + continue; + } + + string extension = Path.GetExtension(providerPath); + + if (!extension.Equals(".ps1", StringComparison.OrdinalIgnoreCase) && !extension.Equals(".psm1", StringComparison.OrdinalIgnoreCase)) + { + WriteError( + new ErrorRecord( + new ArgumentException(StringUtil.Format(Debugger.WrongExtension, providerPath)), + "NewPSBreakpoint:WrongExtension", + ErrorCategory.InvalidArgument, + null)); + continue; + } + + scripts.Add(Path.GetFullPath(providerPath)); + } + } + } + + return scripts; + } + } +} diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Remove-PSBreakpoint.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Remove-PSBreakpoint.cs index a02f9dec059..47541147ac4 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Remove-PSBreakpoint.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Remove-PSBreakpoint.cs @@ -10,7 +10,7 @@ namespace Microsoft.PowerShell.Commands /// [Cmdlet(VerbsCommon.Remove, "PSBreakpoint", SupportsShouldProcess = true, DefaultParameterSetName = "Breakpoint", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113375")] - public class RemovePSBreakpointCommand : PSBreakpointCommandBase + public class RemovePSBreakpointCommand : PSBreakpointStatusBase { /// /// Removes the given breakpoint. diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Set-PSBreakpoint.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Set-PSBreakpoint.cs index 1a68ee4ce31..22be98222ca 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Set-PSBreakpoint.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Set-PSBreakpoint.cs @@ -15,77 +15,8 @@ namespace Microsoft.PowerShell.Commands /// [Cmdlet(VerbsCommon.Set, "PSBreakpoint", DefaultParameterSetName = "Line", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113449")] [OutputType(typeof(VariableBreakpoint), typeof(CommandBreakpoint), typeof(LineBreakpoint))] - public class SetPSBreakpointCommand : PSCmdlet + public class SetPSBreakpointCommand : PSBreakpointCreationBase { - #region parameters - - /// - /// The action to take when hitting this breakpoint. - /// - [Parameter(ParameterSetName = "Command")] - [Parameter(ParameterSetName = "Line")] - [Parameter(ParameterSetName = "Variable")] - public ScriptBlock Action { get; set; } = null; - - /// - /// The column to set the breakpoint on. - /// - [Parameter(Position = 2, ParameterSetName = "Line")] - [ValidateRange(1, int.MaxValue)] - public int Column - { - get - { - return _column ?? 0; - } - - set - { - _column = value; - } - } - - private int? _column = null; - - /// - /// The command(s) to set the breakpoint on. - /// - [Alias("C")] - [Parameter(ParameterSetName = "Command", Mandatory = true)] - [ValidateNotNull] - public string[] Command { get; set; } = null; - - /// - /// The line to set the breakpoint on. - /// - [Parameter(Position = 1, ParameterSetName = "Line", Mandatory = true)] - [ValidateNotNull] - public int[] Line { get; set; } = null; - - /// - /// The script to set the breakpoint on. - /// - [Parameter(ParameterSetName = "Command", Position = 0)] - [Parameter(ParameterSetName = "Line", Mandatory = true, Position = 0)] - [Parameter(ParameterSetName = "Variable", Position = 0)] - [ValidateNotNull] - public string[] Script { get; set; } = null; - - /// - /// The variables to set the breakpoint(s) on. - /// - [Alias("V")] - [Parameter(ParameterSetName = "Variable", Mandatory = true)] - [ValidateNotNull] - public string[] Variable { get; set; } = null; - - /// - /// - [Parameter(ParameterSetName = "Variable")] - public VariableAccessMode Mode { get; set; } = VariableAccessMode.Write; - - #endregion parameters - /// /// Verifies that debugging is supported. /// @@ -130,47 +61,7 @@ protected override void BeginProcessing() protected override void ProcessRecord() { // If there is a script, resolve its path - Collection scripts = new Collection(); - - if (Script != null) - { - foreach (string script in Script) - { - Collection scriptPaths = SessionState.Path.GetResolvedPSPathFromPSPath(script); - - for (int i = 0; i < scriptPaths.Count; i++) - { - string providerPath = scriptPaths[i].ProviderPath; - - if (!File.Exists(providerPath)) - { - WriteError( - new ErrorRecord( - new ArgumentException(StringUtil.Format(Debugger.FileDoesNotExist, providerPath)), - "SetPSBreakpoint:FileDoesNotExist", - ErrorCategory.InvalidArgument, - null)); - - continue; - } - - string extension = Path.GetExtension(providerPath); - - if (!extension.Equals(".ps1", StringComparison.OrdinalIgnoreCase) && !extension.Equals(".psm1", StringComparison.OrdinalIgnoreCase)) - { - WriteError( - new ErrorRecord( - new ArgumentException(StringUtil.Format(Debugger.WrongExtension, providerPath)), - "SetPSBreakpoint:WrongExtension", - ErrorCategory.InvalidArgument, - null)); - continue; - } - - scripts.Add(Path.GetFullPath(providerPath)); - } - } - } + Collection scripts = ResolveScriptPaths(); // // If it is a command breakpoint... @@ -239,7 +130,7 @@ protected override void ProcessRecord() foreach (string path in scripts) { - if (_column != null) + if (Column != 0) { WriteObject( Context.Debugger.NewStatementBreakpoint(path, Line[i], Column, Action)); From 60eb271e279a490f08b8f12f1caa55e441ab2d87 Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Tue, 19 Feb 2019 11:25:24 -0800 Subject: [PATCH 03/19] Debug-Runspace -Breakpoint --- .../commands/utility/DebugRunspaceCommand.cs | 32 ++++++++++++++++++- .../engine/debugger/debugger.cs | 20 ++++++++++-- .../server/ServerRunspacePoolDriver.cs | 10 ++++++ 3 files changed, 59 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/DebugRunspaceCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/DebugRunspaceCommand.cs index 9282a39df02..75b388a12f2 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/DebugRunspaceCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/DebugRunspaceCommand.cs @@ -100,6 +100,20 @@ public Guid InstanceId set; } + /// + /// The optional breakpoint objects to use for debugging. + /// + [Parameter(Position = 1, + ParameterSetName = DebugRunspaceCommand.InstanceIdParameterSet)] + [Parameter(ParameterSetName = DebugRunspaceCommand.RunspaceParameterSet)] + [Parameter(ParameterSetName = DebugRunspaceCommand.IdParameterSet)] + [Parameter(ParameterSetName = DebugRunspaceCommand.NameParameterSet)] + public Breakpoint[] Breakpoint + { + get; + set; + } + #endregion #region Overrides @@ -260,7 +274,14 @@ private void WaitAndReceiveRunspaceOutput() _debugger.SetDebugMode(DebugModes.LocalScript | DebugModes.RemoteScript); // Set up host script debugger to debug the runspace. - _debugger.DebugRunspace(_runspace); + if (Breakpoint.Length > 0) + { + _debugger.DebugRunspace(_runspace, disableBreakAll: true); + } + else + { + _debugger.DebugRunspace(_runspace); + } while (_debugging) { @@ -517,6 +538,10 @@ private void PrepareRunspace(Runspace runspace) { SetLocalMode(runspace.Debugger, true); EnableHostDebugger(runspace, false); + if (Breakpoint.Length > 0) + { + AddBreakpoints(runspace.Debugger); + } } private void RestoreRunspace(Runspace runspace) @@ -551,6 +576,11 @@ private void SetLocalMode(System.Management.Automation.Debugger debugger, bool l } } + private void AddBreakpoints(System.Management.Automation.Debugger debugger) + { + debugger?.SetBreakpoints(Breakpoint); + } + #endregion } } diff --git a/src/System.Management.Automation/engine/debugger/debugger.cs b/src/System.Management.Automation/engine/debugger/debugger.cs index b78c8b66aaf..cf28977e122 100644 --- a/src/System.Management.Automation/engine/debugger/debugger.cs +++ b/src/System.Management.Automation/engine/debugger/debugger.cs @@ -742,6 +742,16 @@ internal virtual void DebugRunspace(Runspace runspace) throw new PSNotImplementedException(); } + /// + /// Sets up debugger to debug provided Runspace in a nested debug session. + /// + /// Runspace to debug. + /// + internal virtual void DebugRunspace(Runspace runspace, bool disableBreakAll) + { + throw new PSNotImplementedException(); + } + /// /// Removes the provided Runspace from the nested "active" debugger state. /// @@ -2657,11 +2667,17 @@ internal static void SetDebugJobAsync(IJobDebugger debuggableJob, bool isAsync) #region Runspace Debugging + internal override void DebugRunspace(Runspace runspace) + { + DebugRunspace(runspace, disableBreakAll:false); + } + /// /// Sets up debugger to debug provided Runspace in a nested debug session. /// /// Runspace to debug. - internal override void DebugRunspace(Runspace runspace) + /// When specified, it will not turn on BreakAll. + internal override void DebugRunspace(Runspace runspace, bool disableBreakAll) { if (runspace == null) { @@ -2696,7 +2712,7 @@ internal override void DebugRunspace(Runspace runspace) AddToRunningRunspaceList(new PSStandaloneMonitorRunspaceInfo(runspace)); - if (!runspace.Debugger.InBreakpoint) + if (!runspace.Debugger.InBreakpoint && !disableBreakAll) { EnableDebuggerStepping(EnableNestedType.NestedRunspace); } diff --git a/src/System.Management.Automation/engine/remoting/server/ServerRunspacePoolDriver.cs b/src/System.Management.Automation/engine/remoting/server/ServerRunspacePoolDriver.cs index 5ed66313f72..b502eead148 100644 --- a/src/System.Management.Automation/engine/remoting/server/ServerRunspacePoolDriver.cs +++ b/src/System.Management.Automation/engine/remoting/server/ServerRunspacePoolDriver.cs @@ -1921,6 +1921,16 @@ internal override void DebugRunspace(Runspace runspace) _wrappedDebugger.Value.DebugRunspace(runspace); } + /// + /// Sets up debugger to debug provided Runspace in a nested debug session. + /// + /// Runspace to debug. + /// + internal override void DebugRunspace(Runspace runspace, bool disableBreakAll) + { + _wrappedDebugger.Value.DebugRunspace(runspace, disableBreakAll); + } + /// /// Removes the provided Runspace from the nested "active" debugger state. /// From 7156b885258f67aba742449cf0843b7fb6fc7a75 Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Wed, 20 Feb 2019 11:11:23 -0800 Subject: [PATCH 04/19] make constructors public and add a few more --- .../engine/debugger/Breakpoint.cs | 108 ++++++++++++++++-- 1 file changed, 99 insertions(+), 9 deletions(-) diff --git a/src/System.Management.Automation/engine/debugger/Breakpoint.cs b/src/System.Management.Automation/engine/debugger/Breakpoint.cs index 07c0f26ec01..a668f69bb6e 100644 --- a/src/System.Management.Automation/engine/debugger/Breakpoint.cs +++ b/src/System.Management.Automation/engine/debugger/Breakpoint.cs @@ -58,7 +58,17 @@ internal bool IsScriptBreakpoint #region constructors - internal Breakpoint(string script, ScriptBlock action) + /// + /// Creates a new instance of a + /// + public Breakpoint(string script) + : this(script, null) + {} + + /// + /// Creates a new instance of a + /// + public Breakpoint(string script, ScriptBlock action) { Enabled = true; Script = script; @@ -67,7 +77,17 @@ internal Breakpoint(string script, ScriptBlock action) HitCount = 0; } - internal Breakpoint(string script, ScriptBlock action, int id) + /// + /// Creates a new instance of a + /// + public Breakpoint(string script, int id) + : this(script, null, id) + {} + + /// + /// Creates a new instance of a + /// + public Breakpoint(string script, ScriptBlock action, int id) { Enabled = true; Script = script; @@ -135,14 +155,34 @@ internal enum BreakpointAction /// public class CommandBreakpoint : Breakpoint { - internal CommandBreakpoint(string script, WildcardPattern command, string commandString, ScriptBlock action) + /// + /// Creates a new instance of a + /// + public CommandBreakpoint(string script, WildcardPattern command, string commandString) + : this(script, command, commandString, null) + {} + + /// + /// Creates a new instance of a + /// + public CommandBreakpoint(string script, WildcardPattern command, string commandString, ScriptBlock action) : base(script, action) { CommandPattern = command; Command = commandString; } - internal CommandBreakpoint(string script, WildcardPattern command, string commandString, ScriptBlock action, int id) + /// + /// Creates a new instance of a + /// + public CommandBreakpoint(string script, WildcardPattern command, string commandString, int id) + : this(script, command, commandString, null, id) + {} + + /// + /// Creates a new instance of a + /// + public CommandBreakpoint(string script, WildcardPattern command, string commandString, ScriptBlock action, int id) : base(script, action, id) { CommandPattern = command; @@ -238,14 +278,34 @@ public enum VariableAccessMode /// public class VariableBreakpoint : Breakpoint { - internal VariableBreakpoint(string script, string variable, VariableAccessMode accessMode, ScriptBlock action) + /// + /// Creates a new instance of a + /// + public VariableBreakpoint(string script, string variable, VariableAccessMode accessMode) + : this(script, variable, accessMode, null) + {} + + /// + /// Creates a new instance of a + /// + public VariableBreakpoint(string script, string variable, VariableAccessMode accessMode, ScriptBlock action) : base(script, action) { Variable = variable; AccessMode = accessMode; } - internal VariableBreakpoint(string script, string variable, VariableAccessMode accessMode, ScriptBlock action, int id) + /// + /// Creates a new instance of a + /// + public VariableBreakpoint(string script, string variable, VariableAccessMode accessMode, int id) + : this(script, variable, accessMode, null, id) + {} + + /// + /// Creates a new instance of a + /// + public VariableBreakpoint(string script, string variable, VariableAccessMode accessMode, ScriptBlock action, int id) : base(script, action, id) { Variable = variable; @@ -300,7 +360,17 @@ internal override void RemoveSelf(ScriptDebugger debugger) /// public class LineBreakpoint : Breakpoint { - internal LineBreakpoint(string script, int line, ScriptBlock action) + /// + /// Creates a new instance of a + /// + public LineBreakpoint(string script, int line) + : this(script, line, null) + {} + + /// + /// Creates a new instance of a + /// + public LineBreakpoint(string script, int line, ScriptBlock action) : base(script, action) { Diagnostics.Assert(!string.IsNullOrEmpty(script), "Caller to verify script parameter is not null or empty."); @@ -309,7 +379,17 @@ internal LineBreakpoint(string script, int line, ScriptBlock action) SequencePointIndex = -1; } - internal LineBreakpoint(string script, int line, int column, ScriptBlock action) + /// + /// Creates a new instance of a + /// + public LineBreakpoint(string script, int line, int column) + : this(script, line, column, null) + {} + + /// + /// Creates a new instance of a + /// + public LineBreakpoint(string script, int line, int column, ScriptBlock action) : base(script, action) { Diagnostics.Assert(!string.IsNullOrEmpty(script), "Caller to verify script parameter is not null or empty."); @@ -318,7 +398,17 @@ internal LineBreakpoint(string script, int line, int column, ScriptBlock action) SequencePointIndex = -1; } - internal LineBreakpoint(string script, int line, int column, ScriptBlock action, int id) + /// + /// Creates a new instance of a + /// + public LineBreakpoint(string script, int line, int column, int id) + : this(script, line, column, null, id) + {} + + /// + /// Creates a new instance of a + /// + public LineBreakpoint(string script, int line, int column, ScriptBlock action, int id) : base(script, action, id) { Diagnostics.Assert(!string.IsNullOrEmpty(script), "Caller to verify script parameter is not null or empty."); From b4f97a682d58af814be4bde83f89b517404751d8 Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Wed, 20 Feb 2019 16:59:58 -0800 Subject: [PATCH 05/19] add experimental feature --- .../commands/utility/DebugRunspaceCommand.cs | 1 + .../commands/utility/New-PSBreakpoint.cs | 1 + .../Microsoft.PowerShell.Utility.psd1 | 10 ++++++++++ 3 files changed, 12 insertions(+) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/DebugRunspaceCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/DebugRunspaceCommand.cs index 75b388a12f2..88a89ce0f4a 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/DebugRunspaceCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/DebugRunspaceCommand.cs @@ -103,6 +103,7 @@ public Guid InstanceId /// /// The optional breakpoint objects to use for debugging. /// + [Experimental("Microsoft.PowerShell.Utility.PSDebugRunspaceWithBreakpoints", ExperimentAction.Show)] [Parameter(Position = 1, ParameterSetName = DebugRunspaceCommand.InstanceIdParameterSet)] [Parameter(ParameterSetName = DebugRunspaceCommand.RunspaceParameterSet)] diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/New-PSBreakpoint.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/New-PSBreakpoint.cs index 3b6f8df869f..2753d005fde 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/New-PSBreakpoint.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/New-PSBreakpoint.cs @@ -13,6 +13,7 @@ namespace Microsoft.PowerShell.Commands /// /// This class implements New-PSBreakpoint command. /// + [Experimental("Microsoft.PowerShell.Utility.PSDebugRunspaceWithBreakpoints", ExperimentAction.Show)] [Cmdlet(VerbsCommon.New, "PSBreakpoint", DefaultParameterSetName = "Line", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113449")] [OutputType(typeof(VariableBreakpoint), typeof(CommandBreakpoint), typeof(LineBreakpoint))] public class NewPSBreakpointCommand : PSBreakpointCreationBase diff --git a/src/Modules/Unix/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 b/src/Modules/Unix/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 index 678c32e1cb2..71edf2b7031 100644 --- a/src/Modules/Unix/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 +++ b/src/Modules/Unix/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 @@ -31,4 +31,14 @@ FunctionsToExport = @() AliasesToExport = @('fhx') NestedModules = @("Microsoft.PowerShell.Commands.Utility.dll") HelpInfoURI = 'https://go.microsoft.com/fwlink/?linkid=855960' +PrivateData = @{ + PSData = @{ + ExperimentalFeatures = @( + @{ + Name = 'Microsoft.PowerShell.Utility.PSDebugRunspaceWithBreakpoints' + Description = "Enables the New-PSBreakpoint cmdlet and the -Breakpoint parameter on Debug-Runspace to set breakpoints in another Runspace upfront." + } + ) + } +} } From 4e2d729d862ddbd9e4d59407f2f20563addb0ecf Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Wed, 20 Feb 2019 18:01:06 -0800 Subject: [PATCH 06/19] [feature] safe null check --- .../commands/utility/DebugRunspaceCommand.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/DebugRunspaceCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/DebugRunspaceCommand.cs index 88a89ce0f4a..7c92e5fa4ea 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/DebugRunspaceCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/DebugRunspaceCommand.cs @@ -539,7 +539,7 @@ private void PrepareRunspace(Runspace runspace) { SetLocalMode(runspace.Debugger, true); EnableHostDebugger(runspace, false); - if (Breakpoint.Length > 0) + if (Breakpoint?.Length > 0) { AddBreakpoints(runspace.Debugger); } From d2ed73f47107753f6ab7c74ea031c892c6ca2cbf Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Wed, 20 Feb 2019 18:38:39 -0800 Subject: [PATCH 07/19] [feature] add new-psbreakpoint tests --- .../engine/InitialSessionState.cs | 1 + .../New-PSBreakpoint.Tests.ps1 | 185 ++++++++++++++++++ 2 files changed, 186 insertions(+) create mode 100644 test/powershell/Modules/Microsoft.PowerShell.Utility/New-PSBreakpoint.Tests.ps1 diff --git a/src/System.Management.Automation/engine/InitialSessionState.cs b/src/System.Management.Automation/engine/InitialSessionState.cs index 68860b90e23..cf2a32295ef 100644 --- a/src/System.Management.Automation/engine/InitialSessionState.cs +++ b/src/System.Management.Automation/engine/InitialSessionState.cs @@ -4532,6 +4532,7 @@ internal static SessionStateAliasEntry[] BuiltInAliases new SessionStateAliasEntry("mi", "Move-Item", string.Empty, ReadOnly), new SessionStateAliasEntry("mp", "Move-ItemProperty", string.Empty, ReadOnly), new SessionStateAliasEntry("nal", "New-Alias", string.Empty, ReadOnly), + new SessionStateAliasEntry("nbp", "New-PSBreakpoint", string.Empty, ReadOnly), new SessionStateAliasEntry("ndr", "New-PSDrive", string.Empty, ReadOnly), new SessionStateAliasEntry("ni", "New-Item", string.Empty, ReadOnly), new SessionStateAliasEntry("nv", "New-Variable", string.Empty, ReadOnly), diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/New-PSBreakpoint.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/New-PSBreakpoint.Tests.ps1 new file mode 100644 index 00000000000..dfeb033c7f3 --- /dev/null +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/New-PSBreakpoint.Tests.ps1 @@ -0,0 +1,185 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +$ps = Join-Path -Path $PsHome -ChildPath "pwsh" + +Describe "New-PSBreakpoint DRT Unit Tests" -Tags "CI" { + #Set up + $scriptFileName = Join-Path $TestDrive -ChildPath breakpointTestScript.ps1 + + $contents = @" +function Hello +{ + `$greeting = 'Hello, world!' + write-host `$greeting +} + +function Goodbye +{ + `$message = 'Good bye, cruel world!' + write-host `$message +} + +Hello +Goodbye + +# The following 2 statements produce null tokens (needed to verify 105473) +# +`$table = @{} + +return +"@ + + $contents > $scriptFileName + + It "Should be able to set psbreakpoints for -Line" { + $brk = New-PSBreakpoint -Line 13 -Script $scriptFileName + $brk.Line | Should -Be 13 + } + + It "Should be able to set psbreakpoints for -Line and -column" { + $brk = New-PSBreakpoint -line 13 -column 1 -script $scriptFileName + $brk.Line | Should -Be 13 + $brk.Column | Should -Be 1 + } + + It "Should be able to set psbreakpoints for -Line and -action" { + $brk = New-PSBreakpoint -line 13 -action {{ break; }} -script $scriptFileName + $brk.Line | Should -Be 13 + $brk.Action | Should -Match "break" + } + + It "Should be able to set psbreakpoints for -Line, -column and -action" { + $brk = New-PSBreakpoint -line 13 -column 1 -action {{ break; }} -script $scriptFileName + $brk.Line | Should -Be 13 + $brk.Column | Should -Be 1 + $brk.Action | Should -Match "break" + } + + It "-script and -line can take multiple items" { + $brk = New-PSBreakpoint -line 11,12,13 -column 1 -script $scriptFileName,$scriptFileName + $brk.Line | Should -BeIn 11,12,13 + $brk.Column | Should -BeIn 1 + } + + It "-script and -line are positional" { + $brk = New-PSBreakpoint $scriptFileName 13 + $brk.Line | Should -Be 13 + } + + It "-script, -line and -column are positional" { + $brk = New-PSBreakpoint $scriptFileName 13 1 + $brk.Line | Should -Be 13 + $brk.Column | Should -Be 1 + } + + It "Should throw Exception when missing mandatory parameter -line" -Pending { + $output = & $ps -noninteractive -command "nbp -column 1 -script $scriptFileName" + [system.string]::Join(" ", $output) | Should -Match "MissingMandatoryParameter,Microsoft.PowerShell.Commands.NewPSBreakpointCommand" + } + + It "Should throw Exception when missing mandatory parameter" -Pending { + $output = & $ps -noprofile -noninteractive -command "nbp -line 1" + [system.string]::Join(" ", $output) | Should -Match "MissingMandatoryParameter,Microsoft.PowerShell.Commands.NewPSBreakpointCommand" + } + + It "Should be able to set psbreakpoints for -command" { + $brk = New-PSBreakpoint -command "write-host" + $brk.Command | Should -BeExactly "write-host" + } + + It "Should be able to set psbreakpoints for -command, -script" { + $brk = New-PSBreakpoint -command "write-host" -script $scriptFileName + $brk.Command | Should -BeExactly "write-host" + } + + It "Should be able to set psbreakpoints for -command, -action and -script" { + $brk = New-PSBreakpoint -command "write-host" -action {{ break; }} -script $scriptFileName + $brk.Action | Should -Match "break" + } + + It "-Command can take multiple items" { + $brk = New-PSBreakpoint -command write-host,Hello + $brk.Command | Should -Be write-host,Hello + } + + It "-Script is positional" { + $brk = New-PSBreakpoint -command "Hello" $scriptFileName + $brk.Command | Should -BeExactly "Hello" + + $brk = New-PSBreakpoint $scriptFileName -command "Hello" + $brk.Command | Should -BeExactly "Hello" + } + + It "Should be able to set breakpoints on functions" { + $brk = New-PSBreakpoint -command Hello,Goodbye -script $scriptFileName + $brk.Command | Should -Be Hello,Goodbye + } + + It "Should be throw Exception when Column number less than 1" { + { New-PSBreakpoint -line 1 -column -1 -script $scriptFileName } | Should -Throw -ErrorId "ParameterArgumentValidationError,Microsoft.PowerShell.Commands.NewPSBreakpointCommand" + } + + It "Should be throw Exception when Line number less than 1" { + $ErrorActionPreference = "Stop" + { New-PSBreakpoint -line -1 -script $scriptFileName } | Should -Throw -ErrorId "NewPSBreakpoint:LineLessThanOne,Microsoft.PowerShell.Commands.NewPSBreakpointCommand" + $ErrorActionPreference = "SilentlyContinue" + } + + It "Fail to set psbreakpoints when script is a file of wrong type" { + $tempFile = [System.IO.Path]::GetTempFileName() + $ErrorActionPreference = "Stop" + { + New-PSBreakpoint -Script $tempFile -Line 1 + } | Should -Throw + $ErrorActionPreference = "SilentlyContinue" + Remove-Item $tempFile -Force + } + + It "Fail to set psbreakpoints when script file does not exist" { + $ErrorActionPreference = "Stop" + ${script.ps1} = 10 + { + New-PSBreakpoint -Script variable:\script.ps1 -Line 1 + } | Should -Throw + $ErrorActionPreference = "SilentlyContinue" + } + + # clean up + Remove-Item -Path $scriptFileName -Force +} + +Describe "New-PSBreakpoint" -Tags "CI" { + # Set up test script + $testScript = Join-Path -Path $PSScriptRoot -ChildPath psbreakpointtestscript.ps1 + + "`$var = 1 " > $testScript + + It "Should be able to set a psbreakpoint on a line" { + $lineNumber = 1 + $brk = New-PSBreakpoint -Line $lineNumber -Script $testScript + $brk.Line | Should -Be $lineNumber + } + + It "Should throw when a string is entered for a line number" { + { + $lineNumber = "one" + New-PSBreakpoint -Line $lineNumber -Script $testScript + + } | Should -Throw + } + + It "Should be able to set a psbreakpoint on a Command" { + $command = "theCommand" + $brk = New-PSBreakpoint -Command $command -Script $testScript + $brk.Command | Should -Be $command + } + + It "Should be able to set a psbreakpoint on a variable" { + $var = "theVariable" + $brk = New-PSBreakpoint -Command $var -Script $testScript + $brk.Command | Should -Be $var + } + + # clean up after ourselves + Remove-Item -Path $testScript +} From ba693db097d2f3199adc44766f9a50fcc24b911c Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Wed, 20 Feb 2019 18:49:55 -0800 Subject: [PATCH 08/19] [feature] other safe null check --- .../commands/utility/DebugRunspaceCommand.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/DebugRunspaceCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/DebugRunspaceCommand.cs index 7c92e5fa4ea..f3a523b6b3d 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/DebugRunspaceCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/DebugRunspaceCommand.cs @@ -275,7 +275,7 @@ private void WaitAndReceiveRunspaceOutput() _debugger.SetDebugMode(DebugModes.LocalScript | DebugModes.RemoteScript); // Set up host script debugger to debug the runspace. - if (Breakpoint.Length > 0) + if (Breakpoint?.Length > 0) { _debugger.DebugRunspace(_runspace, disableBreakAll: true); } From 0decf0bd00c0fe30b13bb103ff0de11826520299 Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Wed, 20 Feb 2019 19:31:12 -0800 Subject: [PATCH 09/19] [feature] add experimental feature to TestMetadata.json --- test/tools/TestMetadata.json | 1 + 1 file changed, 1 insertion(+) diff --git a/test/tools/TestMetadata.json b/test/tools/TestMetadata.json index c49ab1758c6..bc172948978 100644 --- a/test/tools/TestMetadata.json +++ b/test/tools/TestMetadata.json @@ -1,5 +1,6 @@ { "ExperimentalFeatures": { + "Microsoft.PowerShell.Utility.PSDebugRunspaceWithBreakpoints": ["test/powershell/Modules/Microsoft.PowerShell.Utility/New-PSBreakpoint.Tests.ps1"], "ExpTest.FeatureOne": [ "test/powershell/engine/ExperimentalFeature/ExperimentalFeature.Basic.Tests.ps1" ] } } From c4fa3569d3564050cf0656facf630398a4350434 Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Thu, 21 Feb 2019 15:02:41 -0800 Subject: [PATCH 10/19] handle feature enabled disabled --- .../New-PSBreakpoint.Tests.ps1 | 109 ++++++++++++------ 1 file changed, 73 insertions(+), 36 deletions(-) diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/New-PSBreakpoint.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/New-PSBreakpoint.Tests.ps1 index dfeb033c7f3..a648271891e 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/New-PSBreakpoint.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/New-PSBreakpoint.Tests.ps1 @@ -1,12 +1,22 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. $ps = Join-Path -Path $PsHome -ChildPath "pwsh" +$FeatureEnabled = $EnabledExperimentalFeatures.Contains('Microsoft.PowerShell.Utility.PSDebugRunspaceWithBreakpoints') -Describe "New-PSBreakpoint DRT Unit Tests" -Tags "CI" { - #Set up - $scriptFileName = Join-Path $TestDrive -ChildPath breakpointTestScript.ps1 +Describe "New-PSBreakpoint Unit Tests - Feature-Enabled" -Tags "CI" { - $contents = @" + BeforeAll { + if (!$FeatureEnabled) { + Write-Verbose "Test Suite Skipped. The test suite requires the experimental feature 'Microsoft.PowerShell.Utility.PSDebugRunspaceWithBreakpoints' to be enabled." -Verbose + $originalDefaultParameterValues = $PSDefaultParameterValues.Clone() + $PSDefaultParameterValues["it:skip"] = $true + return + } + + #Set up script file 1 + $scriptFileName1 = Join-Path $TestDrive -ChildPath breakpointTestScript.ps1 + + $contents = @" function Hello { `$greeting = 'Hello, world!' @@ -29,51 +39,68 @@ Goodbye return "@ - $contents > $scriptFileName + $contents > $scriptFileName1 + + # Set up script file 2 + $scriptFileName2 = Join-Path -Path $PSScriptRoot -ChildPath psbreakpointtestscript.ps1 + + "`$var = 1 " > $scriptFileName2 + } + + AfterAll { + if (!$FeatureEnabled) { + $global:PSDefaultParameterValues = $originalDefaultParameterValues + return + } + + # clean up + Remove-Item -Path $scriptFileName1 -Force -ErrorAction SilentlyContinue + Remove-Item -Path $scriptFileName2 -Force -ErrorAction SilentlyContinue + } It "Should be able to set psbreakpoints for -Line" { - $brk = New-PSBreakpoint -Line 13 -Script $scriptFileName + $brk = New-PSBreakpoint -Line 13 -Script $scriptFileName1 $brk.Line | Should -Be 13 } It "Should be able to set psbreakpoints for -Line and -column" { - $brk = New-PSBreakpoint -line 13 -column 1 -script $scriptFileName + $brk = New-PSBreakpoint -line 13 -column 1 -script $scriptFileName1 $brk.Line | Should -Be 13 $brk.Column | Should -Be 1 } It "Should be able to set psbreakpoints for -Line and -action" { - $brk = New-PSBreakpoint -line 13 -action {{ break; }} -script $scriptFileName + $brk = New-PSBreakpoint -line 13 -action {{ break; }} -script $scriptFileName1 $brk.Line | Should -Be 13 $brk.Action | Should -Match "break" } It "Should be able to set psbreakpoints for -Line, -column and -action" { - $brk = New-PSBreakpoint -line 13 -column 1 -action {{ break; }} -script $scriptFileName + $brk = New-PSBreakpoint -line 13 -column 1 -action {{ break; }} -script $scriptFileName1 $brk.Line | Should -Be 13 $brk.Column | Should -Be 1 $brk.Action | Should -Match "break" } It "-script and -line can take multiple items" { - $brk = New-PSBreakpoint -line 11,12,13 -column 1 -script $scriptFileName,$scriptFileName + $brk = New-PSBreakpoint -line 11,12,13 -column 1 -script $scriptFileName1,$scriptFileName1 $brk.Line | Should -BeIn 11,12,13 $brk.Column | Should -BeIn 1 } It "-script and -line are positional" { - $brk = New-PSBreakpoint $scriptFileName 13 + $brk = New-PSBreakpoint $scriptFileName1 13 $brk.Line | Should -Be 13 } It "-script, -line and -column are positional" { - $brk = New-PSBreakpoint $scriptFileName 13 1 + $brk = New-PSBreakpoint $scriptFileName1 13 1 $brk.Line | Should -Be 13 $brk.Column | Should -Be 1 } It "Should throw Exception when missing mandatory parameter -line" -Pending { - $output = & $ps -noninteractive -command "nbp -column 1 -script $scriptFileName" + $output = & $ps -noninteractive -command "nbp -column 1 -script $scriptFileName1" [system.string]::Join(" ", $output) | Should -Match "MissingMandatoryParameter,Microsoft.PowerShell.Commands.NewPSBreakpointCommand" } @@ -88,12 +115,12 @@ return } It "Should be able to set psbreakpoints for -command, -script" { - $brk = New-PSBreakpoint -command "write-host" -script $scriptFileName + $brk = New-PSBreakpoint -command "write-host" -script $scriptFileName1 $brk.Command | Should -BeExactly "write-host" } It "Should be able to set psbreakpoints for -command, -action and -script" { - $brk = New-PSBreakpoint -command "write-host" -action {{ break; }} -script $scriptFileName + $brk = New-PSBreakpoint -command "write-host" -action {{ break; }} -script $scriptFileName1 $brk.Action | Should -Match "break" } @@ -103,25 +130,25 @@ return } It "-Script is positional" { - $brk = New-PSBreakpoint -command "Hello" $scriptFileName + $brk = New-PSBreakpoint -command "Hello" $scriptFileName1 $brk.Command | Should -BeExactly "Hello" - $brk = New-PSBreakpoint $scriptFileName -command "Hello" + $brk = New-PSBreakpoint $scriptFileName1 -command "Hello" $brk.Command | Should -BeExactly "Hello" } It "Should be able to set breakpoints on functions" { - $brk = New-PSBreakpoint -command Hello,Goodbye -script $scriptFileName + $brk = New-PSBreakpoint -command Hello,Goodbye -script $scriptFileName1 $brk.Command | Should -Be Hello,Goodbye } It "Should be throw Exception when Column number less than 1" { - { New-PSBreakpoint -line 1 -column -1 -script $scriptFileName } | Should -Throw -ErrorId "ParameterArgumentValidationError,Microsoft.PowerShell.Commands.NewPSBreakpointCommand" + { New-PSBreakpoint -line 1 -column -1 -script $scriptFileName1 } | Should -Throw -ErrorId "ParameterArgumentValidationError,Microsoft.PowerShell.Commands.NewPSBreakpointCommand" } It "Should be throw Exception when Line number less than 1" { $ErrorActionPreference = "Stop" - { New-PSBreakpoint -line -1 -script $scriptFileName } | Should -Throw -ErrorId "NewPSBreakpoint:LineLessThanOne,Microsoft.PowerShell.Commands.NewPSBreakpointCommand" + { New-PSBreakpoint -line -1 -script $scriptFileName1 } | Should -Throw -ErrorId "NewPSBreakpoint:LineLessThanOne,Microsoft.PowerShell.Commands.NewPSBreakpointCommand" $ErrorActionPreference = "SilentlyContinue" } @@ -144,42 +171,52 @@ return $ErrorActionPreference = "SilentlyContinue" } - # clean up - Remove-Item -Path $scriptFileName -Force -} - -Describe "New-PSBreakpoint" -Tags "CI" { - # Set up test script - $testScript = Join-Path -Path $PSScriptRoot -ChildPath psbreakpointtestscript.ps1 - - "`$var = 1 " > $testScript - It "Should be able to set a psbreakpoint on a line" { $lineNumber = 1 - $brk = New-PSBreakpoint -Line $lineNumber -Script $testScript + $brk = New-PSBreakpoint -Line $lineNumber -Script $scriptFileName2 $brk.Line | Should -Be $lineNumber } It "Should throw when a string is entered for a line number" { { $lineNumber = "one" - New-PSBreakpoint -Line $lineNumber -Script $testScript + New-PSBreakpoint -Line $lineNumber -Script $scriptFileName2 } | Should -Throw } It "Should be able to set a psbreakpoint on a Command" { $command = "theCommand" - $brk = New-PSBreakpoint -Command $command -Script $testScript + $brk = New-PSBreakpoint -Command $command -Script $scriptFileName2 $brk.Command | Should -Be $command } It "Should be able to set a psbreakpoint on a variable" { $var = "theVariable" - $brk = New-PSBreakpoint -Command $var -Script $testScript + $brk = New-PSBreakpoint -Command $var -Script $scriptFileName2 $brk.Command | Should -Be $var } +} - # clean up after ourselves - Remove-Item -Path $testScript +Describe "New-PSBreakpoint Unit Tests - Feature-Disabled" -Tags "CI" { + + BeforeAll { + if ($FeatureEnabled) { + Write-Verbose "Test Suite Skipped. The test suite requires the experimental feature 'Microsoft.PowerShell.Utility.PSDebugRunspaceWithBreakpoints' to be disabled." -Verbose + $originalDefaultParameterValues = $PSDefaultParameterValues.Clone() + $PSDefaultParameterValues["it:skip"] = $true + return + } + } + + AfterAll { + if ($FeatureEnabled) { + $global:PSDefaultParameterValues = $originalDefaultParameterValues + return + } + } + + It "Should not have New-PSBreakpoint available" { + { New-PSBreakpoint } | Should -Throw -ErrorId CommandNotFoundException + } } From b26675b776db878184fb09c4399ac56d2d57f8da Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Thu, 21 Feb 2019 15:38:06 -0800 Subject: [PATCH 11/19] add nbp --- test/powershell/engine/Basic/DefaultCommands.Tests.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/test/powershell/engine/Basic/DefaultCommands.Tests.ps1 b/test/powershell/engine/Basic/DefaultCommands.Tests.ps1 index b33e243606d..750ecfbd518 100644 --- a/test/powershell/engine/Basic/DefaultCommands.Tests.ps1 +++ b/test/powershell/engine/Basic/DefaultCommands.Tests.ps1 @@ -105,6 +105,7 @@ Describe "Verify approved aliases list" -Tags "CI" { "Alias", "move", "Move-Item", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "AllScope", "" "Alias", "mp", "Move-ItemProperty", $($FullCLR -or $CoreWindows -or $CoreUnix), "ReadOnly", "", "" "Alias", "mv", "Move-Item", $($FullCLR -or $CoreWindows ), "", "", "" +"Alias", "nbp", "New-PSBreakpoint", $($FullCLR -or $CoreWindows -or $CoreUnix), "ReadOnly", "", "" "Alias", "nal", "New-Alias", $($FullCLR -or $CoreWindows -or $CoreUnix), "ReadOnly", "", "" "Alias", "ndr", "New-PSDrive", $($FullCLR -or $CoreWindows -or $CoreUnix), "ReadOnly", "", "" "Alias", "ni", "New-Item", $($FullCLR -or $CoreWindows -or $CoreUnix), "ReadOnly", "", "" From 49fac4ad961b0888fc1edb5453b53a6e9c67d114 Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Mon, 1 Apr 2019 14:35:09 -0700 Subject: [PATCH 12/19] address feedback --- .../commands/utility/DebugRunspaceCommand.cs | 16 +--- .../commands/utility/Disable-PSBreakpoint.cs | 2 +- .../commands/utility/Enable-PSBreakpoint.cs | 4 +- .../commands/utility/New-PSBreakpoint.cs | 6 +- .../utility/PSBreakpointCreationBase.cs | 15 ++-- .../commands/utility/Remove-PSBreakpoint.cs | 2 +- .../engine/debugger/Breakpoint.cs | 3 +- .../engine/debugger/debugger.cs | 77 +++++++++---------- 8 files changed, 52 insertions(+), 73 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/DebugRunspaceCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/DebugRunspaceCommand.cs index f3a523b6b3d..6724b6d50bb 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/DebugRunspaceCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/DebugRunspaceCommand.cs @@ -275,14 +275,7 @@ private void WaitAndReceiveRunspaceOutput() _debugger.SetDebugMode(DebugModes.LocalScript | DebugModes.RemoteScript); // Set up host script debugger to debug the runspace. - if (Breakpoint?.Length > 0) - { - _debugger.DebugRunspace(_runspace, disableBreakAll: true); - } - else - { - _debugger.DebugRunspace(_runspace); - } + _debugger.DebugRunspace(_runspace, disableBreakAll: Breakpoint?.Length > 0); while (_debugging) { @@ -541,7 +534,7 @@ private void PrepareRunspace(Runspace runspace) EnableHostDebugger(runspace, false); if (Breakpoint?.Length > 0) { - AddBreakpoints(runspace.Debugger); + runspace.Debugger?.SetBreakpoints(Breakpoint); } } @@ -577,11 +570,6 @@ private void SetLocalMode(System.Management.Automation.Debugger debugger, bool l } } - private void AddBreakpoints(System.Management.Automation.Debugger debugger) - { - debugger?.SetBreakpoints(Breakpoint); - } - #endregion } } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Disable-PSBreakpoint.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Disable-PSBreakpoint.cs index f0dce3e3302..e8008650ccb 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Disable-PSBreakpoint.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Disable-PSBreakpoint.cs @@ -10,7 +10,7 @@ namespace Microsoft.PowerShell.Commands /// [Cmdlet(VerbsLifecycle.Disable, "PSBreakpoint", SupportsShouldProcess = true, DefaultParameterSetName = "Breakpoint", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113294")] [OutputType(typeof(Breakpoint))] - public class DisablePSBreakpointCommand : PSBreakpointStatusBase + public class DisablePSBreakpointCommand : PSBreakpointCommandBase { /// /// Gets or sets the parameter -passThru which states whether the diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Enable-PSBreakpoint.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Enable-PSBreakpoint.cs index 57eeb4f638a..e697d36bc2b 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Enable-PSBreakpoint.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Enable-PSBreakpoint.cs @@ -11,7 +11,7 @@ namespace Microsoft.PowerShell.Commands /// /// Base class for Enable/Disable/Remove-PSBreakpoint. /// - public abstract class PSBreakpointStatusBase : PSCmdlet + public abstract class PSBreakpointCommandBase : PSCmdlet { /// /// The breakpoint to enable. @@ -120,7 +120,7 @@ private bool ShouldProcessInternal(string target) /// [Cmdlet(VerbsLifecycle.Enable, "PSBreakpoint", SupportsShouldProcess = true, DefaultParameterSetName = "Id", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113295")] [OutputType(typeof(Breakpoint))] - public class EnablePSBreakpointCommand : PSBreakpointStatusBase + public class EnablePSBreakpointCommand : PSBreakpointCommandBase { /// /// Gets or sets the parameter -passThru which states whether the diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/New-PSBreakpoint.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/New-PSBreakpoint.cs index 2753d005fde..2bd7b5bd728 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/New-PSBreakpoint.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/New-PSBreakpoint.cs @@ -26,9 +26,7 @@ protected override void ProcessRecord() // If there is a script, resolve its path Collection scripts = ResolveScriptPaths(); - // // If it is a command breakpoint... - // if (ParameterSetName.Equals("Command", StringComparison.OrdinalIgnoreCase)) { for (int i = 0; i < Command.Length; i++) @@ -48,11 +46,9 @@ protected override void ProcessRecord() } } } - // - // If it is a variable breakpoint... - // else if (ParameterSetName.Equals("Variable", StringComparison.OrdinalIgnoreCase)) { + // If it is a variable breakpoint... for (int i = 0; i < Variable.Length; i++) { if (scripts.Count > 0) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/PSBreakpointCreationBase.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/PSBreakpointCreationBase.cs index d9428a2963d..358d90a8316 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/PSBreakpointCreationBase.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/PSBreakpointCreationBase.cs @@ -22,29 +22,27 @@ public class PSBreakpointCreationBase : PSCmdlet [Parameter(ParameterSetName = "Command")] [Parameter(ParameterSetName = "Line")] [Parameter(ParameterSetName = "Variable")] - public ScriptBlock Action { get; set; } = null; + public ScriptBlock Action { get; set; } /// /// The column to set the breakpoint on. /// [Parameter(Position = 2, ParameterSetName = "Line")] [ValidateRange(1, int.MaxValue)] - public int Column { get; set; } = 0; + public int Column { get; set; } /// /// The command(s) to set the breakpoint on. /// [Alias("C")] [Parameter(ParameterSetName = "Command", Mandatory = true)] - [ValidateNotNull] - public string[] Command { get; set; } = null; + public string[] Command { get; set; } /// /// The line to set the breakpoint on. /// [Parameter(Position = 1, ParameterSetName = "Line", Mandatory = true)] - [ValidateNotNull] - public int[] Line { get; set; } = null; + public int[] Line { get; set; } /// /// The script to set the breakpoint on. @@ -53,15 +51,14 @@ public class PSBreakpointCreationBase : PSCmdlet [Parameter(ParameterSetName = "Line", Mandatory = true, Position = 0)] [Parameter(ParameterSetName = "Variable", Position = 0)] [ValidateNotNull] - public string[] Script { get; set; } = null; + public string[] Script { get; set; } /// /// The variables to set the breakpoint(s) on. /// [Alias("V")] [Parameter(ParameterSetName = "Variable", Mandatory = true)] - [ValidateNotNull] - public string[] Variable { get; set; } = null; + public string[] Variable { get; set; } /// /// diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Remove-PSBreakpoint.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Remove-PSBreakpoint.cs index 47541147ac4..a02f9dec059 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Remove-PSBreakpoint.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Remove-PSBreakpoint.cs @@ -10,7 +10,7 @@ namespace Microsoft.PowerShell.Commands /// [Cmdlet(VerbsCommon.Remove, "PSBreakpoint", SupportsShouldProcess = true, DefaultParameterSetName = "Breakpoint", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113375")] - public class RemovePSBreakpointCommand : PSBreakpointStatusBase + public class RemovePSBreakpointCommand : PSBreakpointCommandBase { /// /// Removes the given breakpoint. diff --git a/src/System.Management.Automation/engine/debugger/Breakpoint.cs b/src/System.Management.Automation/engine/debugger/Breakpoint.cs index a668f69bb6e..67c9bfb2d2c 100644 --- a/src/System.Management.Automation/engine/debugger/Breakpoint.cs +++ b/src/System.Management.Automation/engine/debugger/Breakpoint.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Management.Automation.Internal; using System.Management.Automation.Language; +using System.Threading; namespace System.Management.Automation { @@ -72,7 +73,7 @@ public Breakpoint(string script, ScriptBlock action) { Enabled = true; Script = script; - Id = s_lastID++; + Id = Interlocked.Increment(ref s_lastID); Action = action; HitCount = 0; } diff --git a/src/System.Management.Automation/engine/debugger/debugger.cs b/src/System.Management.Automation/engine/debugger/debugger.cs index cf28977e122..dd2f8f3ec44 100644 --- a/src/System.Management.Automation/engine/debugger/debugger.cs +++ b/src/System.Management.Automation/engine/debugger/debugger.cs @@ -832,11 +832,11 @@ internal ScriptDebugger(ExecutionContext context) { _context = context; _inBreakpoint = false; - _idToBreakpoint = new Dictionary(); - _pendingBreakpoints = new List(); - _boundBreakpoints = new Dictionary>>(StringComparer.OrdinalIgnoreCase); - _commandBreakpoints = new List(); - _variableBreakpoints = new Dictionary>(StringComparer.OrdinalIgnoreCase); + _idToBreakpoint = new ConcurrentDictionary(); + _pendingBreakpoints = new ConcurrentDictionary(); + _boundBreakpoints = new ConcurrentDictionary>>(StringComparer.OrdinalIgnoreCase); + _commandBreakpoints = new ConcurrentDictionary(); + _variableBreakpoints = new ConcurrentDictionary>(StringComparer.OrdinalIgnoreCase); _steppingMode = SteppingMode.None; _callStack = new CallStackList { _callStackList = new List() }; @@ -1071,10 +1071,10 @@ internal void RegisterScriptFile(ExternalScriptInfo scriptCommandInfo) internal void RegisterScriptFile(string path, string scriptContents) { - Tuple> boundBreakpoints; + Tuple> boundBreakpoints; if (!_boundBreakpoints.TryGetValue(path, out boundBreakpoints)) { - _boundBreakpoints.Add(path, Tuple.Create(new WeakReference(scriptContents), new List())); + _boundBreakpoints[path] = Tuple.Create(new WeakReference(scriptContents), new ConcurrentDictionary()); } else { @@ -1083,8 +1083,8 @@ internal void RegisterScriptFile(string path, string scriptContents) boundBreakpoints.Item1.TryGetTarget(out oldScriptContents); if (oldScriptContents == null || !oldScriptContents.Equals(scriptContents, StringComparison.Ordinal)) { - UnbindBoundBreakpoints(boundBreakpoints.Item2); - _boundBreakpoints[path] = Tuple.Create(new WeakReference(scriptContents), new List()); + UnbindBoundBreakpoints(boundBreakpoints.Item2.Values.ToList()); + _boundBreakpoints[path] = Tuple.Create(new WeakReference(scriptContents), new ConcurrentDictionary()); } } } @@ -1107,7 +1107,7 @@ internal void AddBreakpointCommon(Breakpoint breakpoint) private Breakpoint AddCommandBreakpoint(CommandBreakpoint breakpoint) { AddBreakpointCommon(breakpoint); - _commandBreakpoints.Add(breakpoint); + _commandBreakpoints[breakpoint.Id] = breakpoint; return breakpoint; } @@ -1126,7 +1126,7 @@ internal Breakpoint NewCommandBreakpoint(string command, ScriptBlock action) private Breakpoint AddLineBreakpoint(LineBreakpoint breakpoint) { AddBreakpointCommon(breakpoint); - _pendingBreakpoints.Add(breakpoint); + _pendingBreakpoints[breakpoint.Id] = breakpoint; return breakpoint; } @@ -1171,14 +1171,13 @@ internal VariableBreakpoint AddVariableBreakpoint(VariableBreakpoint breakpoint) { AddBreakpointCommon(breakpoint); - List breakpoints; - if (!_variableBreakpoints.TryGetValue(breakpoint.Variable, out breakpoints)) + if (!_variableBreakpoints.TryGetValue(breakpoint.Variable, out ConcurrentDictionary breakpoints)) { - breakpoints = new List(); - _variableBreakpoints.Add(breakpoint.Variable, breakpoints); + breakpoints = new ConcurrentDictionary(); + _variableBreakpoints[breakpoint.Variable] = breakpoints; } - breakpoints.Add(breakpoint); + breakpoints[breakpoint.Id] = breakpoint; return breakpoint; } @@ -1208,7 +1207,7 @@ private void OnBreakpointUpdated(BreakpointUpdatedEventArgs e) // This is the implementation of the Remove-PSBreakpoint cmdlet. internal void RemoveBreakpoint(Breakpoint breakpoint) { - _idToBreakpoint.Remove(breakpoint.Id); + _idToBreakpoint.Remove(breakpoint.Id, out _); breakpoint.RemoveSelf(this); @@ -1223,22 +1222,22 @@ internal void RemoveBreakpoint(Breakpoint breakpoint) internal void RemoveVariableBreakpoint(VariableBreakpoint breakpoint) { - _variableBreakpoints[breakpoint.Variable].Remove(breakpoint); + _variableBreakpoints[breakpoint.Variable].Remove(breakpoint.Id, out _); } internal void RemoveCommandBreakpoint(CommandBreakpoint breakpoint) { - _commandBreakpoints.Remove(breakpoint); + _commandBreakpoints.Remove(breakpoint.Id, out _); } internal void RemoveLineBreakpoint(LineBreakpoint breakpoint) { - _pendingBreakpoints.Remove(breakpoint); + _pendingBreakpoints.Remove(breakpoint.Id, out _); - Tuple> value; + Tuple> value; if (_boundBreakpoints.TryGetValue(breakpoint.Script, out value)) { - value.Item2.Remove(breakpoint); + value.Item2.Remove(breakpoint.Id, out _); } } @@ -1265,7 +1264,7 @@ internal bool CheckCommand(InvocationInfo invocationInfo) } List breakpoints = - _commandBreakpoints.Where(bp => bp.Enabled && bp.Trigger(invocationInfo)).ToList(); + _commandBreakpoints.Values.Where(bp => bp.Enabled && bp.Trigger(invocationInfo)).ToList(); bool checkLineBp = true; if (breakpoints.Any()) @@ -1318,7 +1317,7 @@ private List GetVariableBreakpointsToTrigger(string variable { SetInternalDebugMode(InternalDebugMode.Disabled); - List breakpoints; + ConcurrentDictionary breakpoints; if (!_variableBreakpoints.TryGetValue(variableName, out breakpoints)) { // $PSItem is an alias for $_. We don't use PSItem internally, but a user might @@ -1334,7 +1333,7 @@ private List GetVariableBreakpointsToTrigger(string variable var callStackInfo = _callStack.Last(); var currentScriptFile = (callStackInfo != null) ? callStackInfo.File : null; - return breakpoints.Where(bp => bp.Trigger(currentScriptFile, read: read)).ToList(); + return breakpoints.Values.Where(bp => bp.Trigger(currentScriptFile, read: read)).ToList(); } finally { @@ -1354,8 +1353,7 @@ internal void TriggerVariableBreakpoints(List breakpoints) /// internal Breakpoint GetBreakpoint(int id) { - Breakpoint breakpoint; - _idToBreakpoint.TryGetValue(id, out breakpoint); + _idToBreakpoint.TryGetValue(id, out Breakpoint breakpoint); return breakpoint; } @@ -1509,7 +1507,7 @@ private void UpdateBreakpoints(FunctionContext functionContext) if (string.IsNullOrEmpty(functionContext._file)) { return; } bool havePendingBreakpoint = false; - foreach (var item in _pendingBreakpoints) + foreach ((int breakpointId, LineBreakpoint item) in _pendingBreakpoints) { if (item.IsScriptBreakpoint && item.Script.Equals(functionContext._file, StringComparison.OrdinalIgnoreCase)) { @@ -1628,11 +1626,11 @@ internal void Clear() } private readonly ExecutionContext _context; - private List _pendingBreakpoints; - private readonly Dictionary>> _boundBreakpoints; - private readonly List _commandBreakpoints; - private readonly Dictionary> _variableBreakpoints; - private readonly Dictionary _idToBreakpoint; + private ConcurrentDictionary _pendingBreakpoints; + private readonly ConcurrentDictionary>> _boundBreakpoints; + private readonly ConcurrentDictionary _commandBreakpoints; + private readonly ConcurrentDictionary> _variableBreakpoints; + private readonly ConcurrentDictionary _idToBreakpoint; private SteppingMode _steppingMode; private CallStackInfo _overOrOutFrame; private CallStackList _callStack; @@ -1945,7 +1943,7 @@ private void UnbindBoundBreakpoints(List boundBreakpoints) breakpoint.SequencePoints = null; breakpoint.SequencePointIndex = -1; breakpoint.BreakpointBitArray = null; - _pendingBreakpoints.Add(breakpoint); + _pendingBreakpoints[breakpoint.Id] = breakpoint; } boundBreakpoints.Clear(); @@ -1956,7 +1954,7 @@ private void SetPendingBreakpoints(FunctionContext functionContext) if (!_pendingBreakpoints.Any()) return; - var newPendingBreakpoints = new List(); + var newPendingBreakpoints = new Dictionary(); var currentScriptFile = functionContext._file; // If we're not in a file, we can't have any line breakpoints. @@ -1977,7 +1975,7 @@ private void SetPendingBreakpoints(FunctionContext functionContext) Diagnostics.Assert(tuple.Item1 == functionContext._boundBreakpoints, "What's up?"); - foreach (var breakpoint in _pendingBreakpoints) + foreach ((int breakpointId, LineBreakpoint breakpoint) in _pendingBreakpoints) { bool bound = false; if (breakpoint.TrySetBreakpoint(currentScriptFile, functionContext)) @@ -1993,17 +1991,16 @@ private void SetPendingBreakpoints(FunctionContext functionContext) // We need to keep track of any breakpoints that are bound in each script because they may // need to be rebound if the script changes. var boundBreakpoints = _boundBreakpoints[currentScriptFile].Item2; - Diagnostics.Assert(boundBreakpoints.IndexOf(breakpoint) < 0, "Don't add more than once."); - boundBreakpoints.Add(breakpoint); + boundBreakpoints[breakpoint.Id] = breakpoint; } if (!bound) { - newPendingBreakpoints.Add(breakpoint); + newPendingBreakpoints.Add(breakpoint.Id, breakpoint); } } - _pendingBreakpoints = newPendingBreakpoints; + _pendingBreakpoints = new ConcurrentDictionary(newPendingBreakpoints); } private void StopOnSequencePoint(FunctionContext functionContext, List breakpoints) From f9fa5f0c31d347ba073203becc9dbf199e1ec81b Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Mon, 1 Apr 2019 19:06:46 -0700 Subject: [PATCH 13/19] support other debuggers --- .../EnableDisableRunspaceDebugCommand.cs | 98 +++++++++++-------- .../engine/debugger/debugger.cs | 39 ++++---- .../engine/remoting/client/Job.cs | 9 ++ .../engine/remoting/client/remoterunspace.cs | 9 ++ .../server/ServerRunspacePoolDriver.cs | 9 ++ 5 files changed, 108 insertions(+), 56 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/EnableDisableRunspaceDebugCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/EnableDisableRunspaceDebugCommand.cs index 92dcae2d056..cfbbe4c808c 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/EnableDisableRunspaceDebugCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/EnableDisableRunspaceDebugCommand.cs @@ -350,6 +350,21 @@ public SwitchParameter BreakAll set; } + /// + /// The optional breakpoint objects to use for debugging. + /// + [Parameter(Position = 1, + ParameterSetName = CommonRunspaceCommandBase.RunspaceParameterSet)] + [Parameter(Position = 1, + ParameterSetName = CommonRunspaceCommandBase.RunspaceNameParameterSet)] + [Parameter(Position = 1, + ParameterSetName = CommonRunspaceCommandBase.RunspaceIdParameterSet)] + public Breakpoint[] Breakpoint + { + get; + set; + } + #endregion #region Overrides @@ -362,58 +377,63 @@ protected override void ProcessRecord() if (this.ParameterSetName.Equals(CommonRunspaceCommandBase.ProcessNameParameterSet)) { SetDebugPreferenceHelper(ProcessName, AppDomainName, true, "EnableRunspaceDebugCommandPersistDebugPreferenceFailure"); + return; } - else - { - IReadOnlyList results = GetRunspaces(); - foreach (var runspace in results) + IReadOnlyList results = GetRunspaces(); + + foreach (var runspace in results) + { + if (runspace.RunspaceStateInfo.State != RunspaceState.Opened) { - if (runspace.RunspaceStateInfo.State != RunspaceState.Opened) - { - WriteError( - new ErrorRecord(new PSInvalidOperationException(string.Format(CultureInfo.InvariantCulture, Debugger.RunspaceOptionInvalidRunspaceState, runspace.Name)), - "SetRunspaceDebugOptionCommandInvalidRunspaceState", - ErrorCategory.InvalidOperation, - this) - ); + WriteError( + new ErrorRecord(new PSInvalidOperationException(string.Format(CultureInfo.InvariantCulture, Debugger.RunspaceOptionInvalidRunspaceState, runspace.Name)), + "SetRunspaceDebugOptionCommandInvalidRunspaceState", + ErrorCategory.InvalidOperation, + this) + ); - continue; - } + continue; + } - System.Management.Automation.Debugger debugger = GetDebuggerFromRunspace(runspace); - if (debugger == null) - { - continue; - } + System.Management.Automation.Debugger debugger = GetDebuggerFromRunspace(runspace); + if (debugger == null) + { + continue; + } - // Enable debugging by preserving debug stop events. - debugger.UnhandledBreakpointMode = UnhandledBreakpointProcessingMode.Wait; + // Enable debugging by preserving debug stop events. + debugger.UnhandledBreakpointMode = UnhandledBreakpointProcessingMode.Wait; - if (this.MyInvocation.BoundParameters.ContainsKey(nameof(BreakAll))) + if (this.MyInvocation.BoundParameters.ContainsKey(nameof(BreakAll))) + { + if (BreakAll) { - if (BreakAll) + try { - try - { - debugger.SetDebuggerStepMode(true); - } - catch (PSInvalidOperationException e) - { - WriteError( - new ErrorRecord( - e, - "SetRunspaceDebugOptionCommandCannotEnableDebuggerStepping", - ErrorCategory.InvalidOperation, - this) - ); - } + debugger.SetDebuggerStepMode(true); } - else + catch (PSInvalidOperationException e) { - debugger.SetDebuggerStepMode(false); + WriteError( + new ErrorRecord( + e, + "SetRunspaceDebugOptionCommandCannotEnableDebuggerStepping", + ErrorCategory.InvalidOperation, + this) + ); } } + else + { + debugger.SetDebuggerStepMode(false); + } + } + + // If any breakpoints were provided, set those in the debugger. + if (Breakpoint?.Length > 0) + { + debugger.SetBreakpoints(Breakpoint); } } } diff --git a/src/System.Management.Automation/engine/debugger/debugger.cs b/src/System.Management.Automation/engine/debugger/debugger.cs index dd2f8f3ec44..9fa21f811d5 100644 --- a/src/System.Management.Automation/engine/debugger/debugger.cs +++ b/src/System.Management.Automation/engine/debugger/debugger.cs @@ -2362,24 +2362,20 @@ public override void SetBreakpoints(IEnumerable breakpoints) { if (_idToBreakpoint.ContainsKey(breakpoint.Id)) { continue; } - LineBreakpoint lineBp = breakpoint as LineBreakpoint; - if (lineBp != null) + switch (breakpoint) { - AddLineBreakpoint(lineBp); - continue; - } - - CommandBreakpoint cmdBp = breakpoint as CommandBreakpoint; - if (cmdBp != null) - { - AddCommandBreakpoint(cmdBp); - continue; - } - - VariableBreakpoint variableBp = breakpoint as VariableBreakpoint; - if (variableBp != null) - { - AddVariableBreakpoint(variableBp); + case LineBreakpoint lineBp: + AddLineBreakpoint(lineBp); + continue; + case CommandBreakpoint cmdBp: + AddCommandBreakpoint(cmdBp); + continue; + case VariableBreakpoint variableBp: + AddVariableBreakpoint(variableBp); + continue; + default: + // Unreachable default block + break; } } } @@ -4001,6 +3997,15 @@ public override DebuggerCommandResults ProcessCommand(PSCommand command, PSDataC return _wrappedDebugger.ProcessCommand(command, output); } + /// + /// Adds the provided set of breakpoints to the debugger. + /// + /// Breakpoints. + public override void SetBreakpoints(IEnumerable breakpoints) + { + _wrappedDebugger?.SetBreakpoints(breakpoints); + } + /// /// SetDebuggerAction. /// diff --git a/src/System.Management.Automation/engine/remoting/client/Job.cs b/src/System.Management.Automation/engine/remoting/client/Job.cs index 39a5e35ff74..aead4948d73 100644 --- a/src/System.Management.Automation/engine/remoting/client/Job.cs +++ b/src/System.Management.Automation/engine/remoting/client/Job.cs @@ -3907,6 +3907,15 @@ public override DebuggerCommandResults ProcessCommand(PSCommand command, PSDataC return _wrappedDebugger.ProcessCommand(command, output); } + /// + /// Adds the provided set of breakpoints to the debugger. + /// + /// Breakpoints. + public override void SetBreakpoints(IEnumerable breakpoints) + { + _wrappedDebugger?.SetBreakpoints(breakpoints); + } + /// /// Sets the debugger resume action. /// diff --git a/src/System.Management.Automation/engine/remoting/client/remoterunspace.cs b/src/System.Management.Automation/engine/remoting/client/remoterunspace.cs index b390e3f9b69..87bb117a2bd 100644 --- a/src/System.Management.Automation/engine/remoting/client/remoterunspace.cs +++ b/src/System.Management.Automation/engine/remoting/client/remoterunspace.cs @@ -2007,6 +2007,15 @@ public override void StopProcessCommand() } } + /// + /// Adds the provided set of breakpoints to the debugger. + /// + /// Breakpoints. + public override void SetBreakpoints(IEnumerable breakpoints) + { + _runspace.Debugger?.SetBreakpoints(breakpoints); + } + /// /// SetDebuggerAction. /// diff --git a/src/System.Management.Automation/engine/remoting/server/ServerRunspacePoolDriver.cs b/src/System.Management.Automation/engine/remoting/server/ServerRunspacePoolDriver.cs index b502eead148..a9998783da8 100644 --- a/src/System.Management.Automation/engine/remoting/server/ServerRunspacePoolDriver.cs +++ b/src/System.Management.Automation/engine/remoting/server/ServerRunspacePoolDriver.cs @@ -1767,6 +1767,15 @@ public override bool InBreakpoint get { return _inDebugMode; } } + /// + /// Adds the provided set of breakpoints to the debugger. + /// + /// Breakpoints. + public override void SetBreakpoints(IEnumerable breakpoints) + { + _wrappedDebugger.Value?.SetBreakpoints(breakpoints); + } + /// /// Exits debugger mode with the provided resume action. /// From 7e683659b73c795224acef80aea0692c3a80447e Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Tue, 2 Apr 2019 13:56:24 -0700 Subject: [PATCH 14/19] no null check needed --- src/System.Management.Automation/engine/debugger/debugger.cs | 2 +- src/System.Management.Automation/engine/remoting/client/Job.cs | 2 +- .../engine/remoting/server/ServerRunspacePoolDriver.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/System.Management.Automation/engine/debugger/debugger.cs b/src/System.Management.Automation/engine/debugger/debugger.cs index 9fa21f811d5..fd8f24d420c 100644 --- a/src/System.Management.Automation/engine/debugger/debugger.cs +++ b/src/System.Management.Automation/engine/debugger/debugger.cs @@ -4003,7 +4003,7 @@ public override DebuggerCommandResults ProcessCommand(PSCommand command, PSDataC /// Breakpoints. public override void SetBreakpoints(IEnumerable breakpoints) { - _wrappedDebugger?.SetBreakpoints(breakpoints); + _wrappedDebugger.SetBreakpoints(breakpoints); } /// diff --git a/src/System.Management.Automation/engine/remoting/client/Job.cs b/src/System.Management.Automation/engine/remoting/client/Job.cs index aead4948d73..bc047fe5c4a 100644 --- a/src/System.Management.Automation/engine/remoting/client/Job.cs +++ b/src/System.Management.Automation/engine/remoting/client/Job.cs @@ -3913,7 +3913,7 @@ public override DebuggerCommandResults ProcessCommand(PSCommand command, PSDataC /// Breakpoints. public override void SetBreakpoints(IEnumerable breakpoints) { - _wrappedDebugger?.SetBreakpoints(breakpoints); + _wrappedDebugger.SetBreakpoints(breakpoints); } /// diff --git a/src/System.Management.Automation/engine/remoting/server/ServerRunspacePoolDriver.cs b/src/System.Management.Automation/engine/remoting/server/ServerRunspacePoolDriver.cs index a9998783da8..806677ed942 100644 --- a/src/System.Management.Automation/engine/remoting/server/ServerRunspacePoolDriver.cs +++ b/src/System.Management.Automation/engine/remoting/server/ServerRunspacePoolDriver.cs @@ -1773,7 +1773,7 @@ public override bool InBreakpoint /// Breakpoints. public override void SetBreakpoints(IEnumerable breakpoints) { - _wrappedDebugger.Value?.SetBreakpoints(breakpoints); + _wrappedDebugger.Value.SetBreakpoints(breakpoints); } /// From 699ca217406235b478d516edb33ed37ffedee78a Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Wed, 3 Apr 2019 13:49:27 -0700 Subject: [PATCH 15/19] make GetBreakpoint* public and add tests --- .../EnableDisableRunspaceDebugCommand.cs | 1 + .../engine/debugger/debugger.cs | 35 ++++++- .../engine/remoting/client/Job.cs | 13 +++ .../engine/remoting/client/remoterunspace.cs | 13 +++ .../server/ServerRunspacePoolDriver.cs | 13 +++ .../Enable-RunspaceDebug.Tests.ps1 | 99 +++++++++++++++++++ test/xUnit/csharp/test_Runspace.cs | 27 +++++ 7 files changed, 199 insertions(+), 2 deletions(-) create mode 100644 test/powershell/Modules/Microsoft.PowerShell.Utility/Enable-RunspaceDebug.Tests.ps1 diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/EnableDisableRunspaceDebugCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/EnableDisableRunspaceDebugCommand.cs index cfbbe4c808c..98c26bc5337 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/EnableDisableRunspaceDebugCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/EnableDisableRunspaceDebugCommand.cs @@ -353,6 +353,7 @@ public SwitchParameter BreakAll /// /// The optional breakpoint objects to use for debugging. /// + [Experimental("Microsoft.PowerShell.Utility.PSDebugRunspaceWithBreakpoints", ExperimentAction.Show)] [Parameter(Position = 1, ParameterSetName = CommonRunspaceCommandBase.RunspaceParameterSet)] [Parameter(Position = 1, diff --git a/src/System.Management.Automation/engine/debugger/debugger.cs b/src/System.Management.Automation/engine/debugger/debugger.cs index fd8f24d420c..1d2c1624d0f 100644 --- a/src/System.Management.Automation/engine/debugger/debugger.cs +++ b/src/System.Management.Automation/engine/debugger/debugger.cs @@ -626,6 +626,23 @@ public virtual void SetBreakpoints(IEnumerable breakpoints) throw new PSNotImplementedException(); } + /// + /// Get a breakpoint by id, primarily for Enable/Disable/Remove-PSBreakpoint cmdlets. + /// + /// Id of the breakpoint you want. + public virtual Breakpoint GetBreakpoint(int id) + { + throw new PSNotImplementedException(); + } + + /// + /// Returns breakpoints primarily for the Get-PSBreakpoint cmdlet. + /// + public virtual List GetBreakpoints() + { + throw new PSNotImplementedException(); + } + /// /// Resets the command processor source information so that it is /// updated with latest information on the next debug stop. @@ -1351,7 +1368,8 @@ internal void TriggerVariableBreakpoints(List breakpoints) /// /// Get a breakpoint by id, primarily for Enable/Disable/Remove-PSBreakpoint cmdlets. /// - internal Breakpoint GetBreakpoint(int id) + /// Id of the breakpoint you want. + public override Breakpoint GetBreakpoint(int id) { _idToBreakpoint.TryGetValue(id, out Breakpoint breakpoint); return breakpoint; @@ -1360,7 +1378,7 @@ internal Breakpoint GetBreakpoint(int id) /// /// Returns breakpoints primarily for the Get-PSBreakpoint cmdlet. /// - internal List GetBreakpoints() + public override List GetBreakpoints() { return (from bp in _idToBreakpoint.Values orderby bp.Id select bp).ToList(); } @@ -4006,6 +4024,19 @@ public override void SetBreakpoints(IEnumerable breakpoints) _wrappedDebugger.SetBreakpoints(breakpoints); } + /// + /// Get a breakpoint by id, primarily for Enable/Disable/Remove-PSBreakpoint cmdlets. + /// + /// Id of the breakpoint you want. + public override Breakpoint GetBreakpoint(int id) => + _wrappedDebugger.GetBreakpoint(id); + + /// + /// Returns breakpoints primarily for the Get-PSBreakpoint cmdlet. + /// + public override List GetBreakpoints() => + _wrappedDebugger.GetBreakpoints(); + /// /// SetDebuggerAction. /// diff --git a/src/System.Management.Automation/engine/remoting/client/Job.cs b/src/System.Management.Automation/engine/remoting/client/Job.cs index bc047fe5c4a..78397a2d906 100644 --- a/src/System.Management.Automation/engine/remoting/client/Job.cs +++ b/src/System.Management.Automation/engine/remoting/client/Job.cs @@ -3916,6 +3916,19 @@ public override void SetBreakpoints(IEnumerable breakpoints) _wrappedDebugger.SetBreakpoints(breakpoints); } + /// + /// Get a breakpoint by id, primarily for Enable/Disable/Remove-PSBreakpoint cmdlets. + /// + /// Id of the breakpoint you want. + public override Breakpoint GetBreakpoint(int id) => + _wrappedDebugger.GetBreakpoint(id); + + /// + /// Returns breakpoints primarily for the Get-PSBreakpoint cmdlet. + /// + public override List GetBreakpoints() => + _wrappedDebugger.GetBreakpoints(); + /// /// Sets the debugger resume action. /// diff --git a/src/System.Management.Automation/engine/remoting/client/remoterunspace.cs b/src/System.Management.Automation/engine/remoting/client/remoterunspace.cs index 87bb117a2bd..9d673b5a24c 100644 --- a/src/System.Management.Automation/engine/remoting/client/remoterunspace.cs +++ b/src/System.Management.Automation/engine/remoting/client/remoterunspace.cs @@ -2016,6 +2016,19 @@ public override void SetBreakpoints(IEnumerable breakpoints) _runspace.Debugger?.SetBreakpoints(breakpoints); } + /// + /// Get a breakpoint by id, primarily for Enable/Disable/Remove-PSBreakpoint cmdlets. + /// + /// Id of the breakpoint you want. + public override Breakpoint GetBreakpoint(int id) => + _runspace.Debugger?.GetBreakpoint(id); + + /// + /// Returns breakpoints primarily for the Get-PSBreakpoint cmdlet. + /// + public override List GetBreakpoints() => + _runspace.Debugger?.GetBreakpoints(); + /// /// SetDebuggerAction. /// diff --git a/src/System.Management.Automation/engine/remoting/server/ServerRunspacePoolDriver.cs b/src/System.Management.Automation/engine/remoting/server/ServerRunspacePoolDriver.cs index 806677ed942..29ecb409455 100644 --- a/src/System.Management.Automation/engine/remoting/server/ServerRunspacePoolDriver.cs +++ b/src/System.Management.Automation/engine/remoting/server/ServerRunspacePoolDriver.cs @@ -1776,6 +1776,19 @@ public override void SetBreakpoints(IEnumerable breakpoints) _wrappedDebugger.Value.SetBreakpoints(breakpoints); } + /// + /// Get a breakpoint by id, primarily for Enable/Disable/Remove-PSBreakpoint cmdlets. + /// + /// Id of the breakpoint you want. + public override Breakpoint GetBreakpoint(int id) => + _wrappedDebugger.Value.GetBreakpoint(id); + + /// + /// Returns breakpoints primarily for the Get-PSBreakpoint cmdlet. + /// + public override List GetBreakpoints() => + _wrappedDebugger.Value.GetBreakpoints(); + /// /// Exits debugger mode with the provided resume action. /// diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Enable-RunspaceDebug.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Enable-RunspaceDebug.Tests.ps1 new file mode 100644 index 00000000000..b3c9ef27ed8 --- /dev/null +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Enable-RunspaceDebug.Tests.ps1 @@ -0,0 +1,99 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +$FeatureEnabled = $EnabledExperimentalFeatures.Contains('Microsoft.PowerShell.Utility.PSDebugRunspaceWithBreakpoints') + +Describe "`Enable-RunspaceDebug -Breakpoint` Unit Tests - Feature-Enabled" -Tags "CI" { + + BeforeAll { + if (!$FeatureEnabled) { + Write-Verbose "Test Suite Skipped. The test suite requires the experimental feature 'Microsoft.PowerShell.Utility.PSDebugRunspaceWithBreakpoints' to be enabled." -Verbose + $originalDefaultParameterValues = $PSDefaultParameterValues.Clone() + $PSDefaultParameterValues["it:skip"] = $true + return + } + + #Set up script file 1 + $scriptFileName1 = Join-Path $TestDrive -ChildPath breakpointTestScript.ps1 + + $contents = @" +function Hello +{ + `$greeting = 'Hello, world!' + write-host `$greeting +} + +function Goodbye +{ + `$message = 'Good bye, cruel world!' + write-host `$message +} + +Hello +Goodbye +"@ + + $contents > $scriptFileName1 + + # Set up script file 2 + $scriptFileName2 = Join-Path -Path $PSScriptRoot -ChildPath psbreakpointtestscript.ps1 + + "`$var = 1 " > $scriptFileName2 + + $iss = [initialsessionstate]::CreateDefault2(); + $testRunspace1 = [runspacefactory]::CreateRunspace($iss) + $testRunspace1.Name = "TestRunspaceDebuggerReset" + $testRunspace1.Open() + } + + AfterAll { + if (!$FeatureEnabled) { + $global:PSDefaultParameterValues = $originalDefaultParameterValues + return + } + + # clean up + Remove-Item -Path $scriptFileName1 -Force -ErrorAction SilentlyContinue + Remove-Item -Path $scriptFileName2 -Force -ErrorAction SilentlyContinue + $testRunspace1.Dispose() + } + + It "Can set breakpoints in the runspace - " -TestCases @( + @{ + Name = "Current runspace" + Runspace = [System.Management.Automation.Runspaces.Runspace]::DefaultRunspace + Breakpoints = New-PSBreakpoint -Line 13 -Script $scriptFileName1 + }, + @{ + Name = $testRunspace1.Name + Runspace = $testRunspace1 + Breakpoints = New-PSBreakpoint -Line 13 -Script $scriptFileName1 + } + ) { + param($Runspace, $Breakpoints) + Enable-RunspaceDebug -Breakpoint $Breakpoints -Runspace $Runspace + $Runspace.Debugger.GetBreakpoints() | Should -Be @($Breakpoints) + } +} + +Describe "`Enable-RunspaceDebug -Breakpoint` Unit Tests - Feature-Disabled" -Tags "CI" { + + BeforeAll { + if ($FeatureEnabled) { + Write-Verbose "Test Suite Skipped. The test suite requires the experimental feature 'Microsoft.PowerShell.Utility.PSDebugRunspaceWithBreakpoints' to be disabled." -Verbose + $originalDefaultParameterValues = $PSDefaultParameterValues.Clone() + $PSDefaultParameterValues["it:skip"] = $true + return + } + } + + AfterAll { + if ($FeatureEnabled) { + $global:PSDefaultParameterValues = $originalDefaultParameterValues + return + } + } + + It "Should not have `Enable-RunspaceDebug -Breakpoint` available" { + { Enable-RunspaceDebug -Breakpoint } | Should -Throw -ErrorId NamedParameterNotFound + } +} diff --git a/test/xUnit/csharp/test_Runspace.cs b/test/xUnit/csharp/test_Runspace.cs index 7bf0bc3f85f..3519bf2f371 100644 --- a/test/xUnit/csharp/test_Runspace.cs +++ b/test/xUnit/csharp/test_Runspace.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.Collections.Generic; using System.Management.Automation; using System.Management.Automation.Runspaces; using Xunit; @@ -100,5 +101,31 @@ public void TestRunspaceWithPowerShellAndInitialSessionState() runspace.Close(); } } + + [Fact] + public void TestRunspaceSetBreakpoints() + { + using (var runspace = RunspaceFactory.CreateRunspace()) + { + var expectedBreakpoints = new Breakpoint[] { + new LineBreakpoint("./path/to/some/file.ps1", 1), + new CommandBreakpoint("./path/to/some/file.ps1", new WildcardPattern("Write-Host"), "Write-Host"), + }; + + runspace.Open(); + + try + { + runspace.Debugger.SetBreakpoints(expectedBreakpoints); + List actualBreakpoints = runspace.Debugger.GetBreakpoints(); + Assert.Equal(expectedBreakpoints.Length, actualBreakpoints.Count); + Assert.Equal(expectedBreakpoints, actualBreakpoints); + } + finally + { + runspace.Close(); + } + } + } } } From da054327ce3a835c8e46aa1680c8c42404629405 Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Wed, 3 Apr 2019 14:32:49 -0700 Subject: [PATCH 16/19] [feature] fix test --- .../Enable-RunspaceDebug.Tests.ps1 | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Enable-RunspaceDebug.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Enable-RunspaceDebug.Tests.ps1 index b3c9ef27ed8..2a3be8fc387 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Enable-RunspaceDebug.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Enable-RunspaceDebug.Tests.ps1 @@ -34,10 +34,7 @@ Goodbye $contents > $scriptFileName1 - # Set up script file 2 - $scriptFileName2 = Join-Path -Path $PSScriptRoot -ChildPath psbreakpointtestscript.ps1 - - "`$var = 1 " > $scriptFileName2 + $breakpoint1 = New-PSBreakpoint -Line 12 $scriptFileName1 $iss = [initialsessionstate]::CreateDefault2(); $testRunspace1 = [runspacefactory]::CreateRunspace($iss) @@ -51,9 +48,7 @@ Goodbye return } - # clean up - Remove-Item -Path $scriptFileName1 -Force -ErrorAction SilentlyContinue - Remove-Item -Path $scriptFileName2 -Force -ErrorAction SilentlyContinue + # Clean up $testRunspace1.Dispose() } @@ -61,12 +56,12 @@ Goodbye @{ Name = "Current runspace" Runspace = [System.Management.Automation.Runspaces.Runspace]::DefaultRunspace - Breakpoints = New-PSBreakpoint -Line 13 -Script $scriptFileName1 + Breakpoints = $breakpoint1 }, @{ Name = $testRunspace1.Name Runspace = $testRunspace1 - Breakpoints = New-PSBreakpoint -Line 13 -Script $scriptFileName1 + Breakpoints = $breakpoint1 } ) { param($Runspace, $Breakpoints) From 986f37938f52aab04d48706eb2aebe79940c5300 Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Wed, 3 Apr 2019 17:09:37 -0700 Subject: [PATCH 17/19] [feature] codacy cleanup - make abstract constructors protected --- .../engine/debugger/Breakpoint.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/System.Management.Automation/engine/debugger/Breakpoint.cs b/src/System.Management.Automation/engine/debugger/Breakpoint.cs index 67c9bfb2d2c..866bef82f24 100644 --- a/src/System.Management.Automation/engine/debugger/Breakpoint.cs +++ b/src/System.Management.Automation/engine/debugger/Breakpoint.cs @@ -62,14 +62,14 @@ internal bool IsScriptBreakpoint /// /// Creates a new instance of a /// - public Breakpoint(string script) + protected Breakpoint(string script) : this(script, null) {} /// /// Creates a new instance of a /// - public Breakpoint(string script, ScriptBlock action) + protected Breakpoint(string script, ScriptBlock action) { Enabled = true; Script = script; @@ -81,14 +81,14 @@ public Breakpoint(string script, ScriptBlock action) /// /// Creates a new instance of a /// - public Breakpoint(string script, int id) + protected Breakpoint(string script, int id) : this(script, null, id) {} /// /// Creates a new instance of a /// - public Breakpoint(string script, ScriptBlock action, int id) + protected Breakpoint(string script, ScriptBlock action, int id) { Enabled = true; Script = script; From 77b7feca68743b45803fe092c405a51d112c23d2 Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Fri, 5 Apr 2019 16:01:02 -0700 Subject: [PATCH 18/19] address steve's feedback --- .../commands/utility/DebugRunspaceCommand.cs | 2 +- .../commands/utility/New-PSBreakpoint.cs | 8 ++--- .../utility/PSBreakpointCreationBase.cs | 26 ++++++++------ .../commands/utility/Set-PSBreakpoint.cs | 8 ++--- .../Enable-RunspaceDebug.Tests.ps1 | 35 +++++-------------- .../New-PSBreakpoint.Tests.ps1 | 35 +++---------------- 6 files changed, 37 insertions(+), 77 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/DebugRunspaceCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/DebugRunspaceCommand.cs index 6724b6d50bb..6ad1f929c31 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/DebugRunspaceCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/DebugRunspaceCommand.cs @@ -275,7 +275,7 @@ private void WaitAndReceiveRunspaceOutput() _debugger.SetDebugMode(DebugModes.LocalScript | DebugModes.RemoteScript); // Set up host script debugger to debug the runspace. - _debugger.DebugRunspace(_runspace, disableBreakAll: Breakpoint?.Length > 0); + _debugger.DebugRunspace(_runspace, disableBreakAll: Breakpoint?.Length > 0); while (_debugging) { diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/New-PSBreakpoint.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/New-PSBreakpoint.cs index 2bd7b5bd728..da386276627 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/New-PSBreakpoint.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/New-PSBreakpoint.cs @@ -14,7 +14,7 @@ namespace Microsoft.PowerShell.Commands /// This class implements New-PSBreakpoint command. /// [Experimental("Microsoft.PowerShell.Utility.PSDebugRunspaceWithBreakpoints", ExperimentAction.Show)] - [Cmdlet(VerbsCommon.New, "PSBreakpoint", DefaultParameterSetName = "Line", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113449")] + [Cmdlet(VerbsCommon.New, "PSBreakpoint", DefaultParameterSetName = LineParameterSetName, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113449")] [OutputType(typeof(VariableBreakpoint), typeof(CommandBreakpoint), typeof(LineBreakpoint))] public class NewPSBreakpointCommand : PSBreakpointCreationBase { @@ -27,7 +27,7 @@ protected override void ProcessRecord() Collection scripts = ResolveScriptPaths(); // If it is a command breakpoint... - if (ParameterSetName.Equals("Command", StringComparison.OrdinalIgnoreCase)) + if (ParameterSetName.Equals(CommandParameterSetName, StringComparison.OrdinalIgnoreCase)) { for (int i = 0; i < Command.Length; i++) { @@ -46,7 +46,7 @@ protected override void ProcessRecord() } } } - else if (ParameterSetName.Equals("Variable", StringComparison.OrdinalIgnoreCase)) + else if (ParameterSetName.Equals(VariableParameterSetName, StringComparison.OrdinalIgnoreCase)) { // If it is a variable breakpoint... for (int i = 0; i < Variable.Length; i++) @@ -69,7 +69,7 @@ protected override void ProcessRecord() // else { - Debug.Assert(ParameterSetName.Equals("Line", StringComparison.OrdinalIgnoreCase)); + Debug.Assert(ParameterSetName.Equals(LineParameterSetName, StringComparison.OrdinalIgnoreCase)); for (int i = 0; i < Line.Length; i++) { diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/PSBreakpointCreationBase.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/PSBreakpointCreationBase.cs index 358d90a8316..acbd6c8685a 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/PSBreakpointCreationBase.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/PSBreakpointCreationBase.cs @@ -14,20 +14,24 @@ namespace Microsoft.PowerShell.Commands /// public class PSBreakpointCreationBase : PSCmdlet { + internal const string CommandParameterSetName = "Command"; + internal const string LineParameterSetName = "Line"; + internal const string VariableParameterSetName = "Variable"; + #region parameters /// /// The action to take when hitting this breakpoint. /// - [Parameter(ParameterSetName = "Command")] - [Parameter(ParameterSetName = "Line")] - [Parameter(ParameterSetName = "Variable")] + [Parameter(ParameterSetName = CommandParameterSetName)] + [Parameter(ParameterSetName = LineParameterSetName)] + [Parameter(ParameterSetName = VariableParameterSetName)] public ScriptBlock Action { get; set; } /// /// The column to set the breakpoint on. /// - [Parameter(Position = 2, ParameterSetName = "Line")] + [Parameter(Position = 2, ParameterSetName = LineParameterSetName)] [ValidateRange(1, int.MaxValue)] public int Column { get; set; } @@ -35,21 +39,21 @@ public class PSBreakpointCreationBase : PSCmdlet /// The command(s) to set the breakpoint on. /// [Alias("C")] - [Parameter(ParameterSetName = "Command", Mandatory = true)] + [Parameter(ParameterSetName = CommandParameterSetName, Mandatory = true)] public string[] Command { get; set; } /// /// The line to set the breakpoint on. /// - [Parameter(Position = 1, ParameterSetName = "Line", Mandatory = true)] + [Parameter(Position = 1, ParameterSetName = LineParameterSetName, Mandatory = true)] public int[] Line { get; set; } /// /// The script to set the breakpoint on. /// - [Parameter(ParameterSetName = "Command", Position = 0)] - [Parameter(ParameterSetName = "Line", Mandatory = true, Position = 0)] - [Parameter(ParameterSetName = "Variable", Position = 0)] + [Parameter(ParameterSetName = CommandParameterSetName, Position = 0)] + [Parameter(ParameterSetName = LineParameterSetName, Mandatory = true, Position = 0)] + [Parameter(ParameterSetName = VariableParameterSetName, Position = 0)] [ValidateNotNull] public string[] Script { get; set; } @@ -57,12 +61,12 @@ public class PSBreakpointCreationBase : PSCmdlet /// The variables to set the breakpoint(s) on. /// [Alias("V")] - [Parameter(ParameterSetName = "Variable", Mandatory = true)] + [Parameter(ParameterSetName = VariableParameterSetName, Mandatory = true)] public string[] Variable { get; set; } /// /// - [Parameter(ParameterSetName = "Variable")] + [Parameter(ParameterSetName = VariableParameterSetName)] public VariableAccessMode Mode { get; set; } = VariableAccessMode.Write; #endregion parameters diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Set-PSBreakpoint.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Set-PSBreakpoint.cs index 22be98222ca..23773fd4477 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Set-PSBreakpoint.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Set-PSBreakpoint.cs @@ -13,7 +13,7 @@ namespace Microsoft.PowerShell.Commands /// /// This class implements Set-PSBreakpoint command. /// - [Cmdlet(VerbsCommon.Set, "PSBreakpoint", DefaultParameterSetName = "Line", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113449")] + [Cmdlet(VerbsCommon.Set, "PSBreakpoint", DefaultParameterSetName = LineParameterSetName, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113449")] [OutputType(typeof(VariableBreakpoint), typeof(CommandBreakpoint), typeof(LineBreakpoint))] public class SetPSBreakpointCommand : PSBreakpointCreationBase { @@ -66,7 +66,7 @@ protected override void ProcessRecord() // // If it is a command breakpoint... // - if (ParameterSetName.Equals("Command", StringComparison.OrdinalIgnoreCase)) + if (ParameterSetName.Equals(CommandParameterSetName, StringComparison.OrdinalIgnoreCase)) { for (int i = 0; i < Command.Length; i++) { @@ -88,7 +88,7 @@ protected override void ProcessRecord() // // If it is a variable breakpoint... // - else if (ParameterSetName.Equals("Variable", StringComparison.OrdinalIgnoreCase)) + else if (ParameterSetName.Equals(VariableParameterSetName, StringComparison.OrdinalIgnoreCase)) { for (int i = 0; i < Variable.Length; i++) { @@ -112,7 +112,7 @@ protected override void ProcessRecord() // else { - Debug.Assert(ParameterSetName.Equals("Line", StringComparison.OrdinalIgnoreCase)); + Debug.Assert(ParameterSetName.Equals(LineParameterSetName, StringComparison.OrdinalIgnoreCase)); for (int i = 0; i < Line.Length; i++) { diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Enable-RunspaceDebug.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Enable-RunspaceDebug.Tests.ps1 index 2a3be8fc387..176d980f551 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Enable-RunspaceDebug.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Enable-RunspaceDebug.Tests.ps1 @@ -1,5 +1,6 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. + $FeatureEnabled = $EnabledExperimentalFeatures.Contains('Microsoft.PowerShell.Utility.PSDebugRunspaceWithBreakpoints') Describe "`Enable-RunspaceDebug -Breakpoint` Unit Tests - Feature-Enabled" -Tags "CI" { @@ -34,7 +35,12 @@ Goodbye $contents > $scriptFileName1 - $breakpoint1 = New-PSBreakpoint -Line 12 $scriptFileName1 + # The breakpoints are created here because when the tests are run with the experimental feature off, + # this command does not exist and the Pester tests fail to work + $breakpointArr = @( + New-PSBreakpoint -Line 12 $scriptFileName1 + New-PSBreakpoint -Line 13 $scriptFileName1 + ) $iss = [initialsessionstate]::CreateDefault2(); $testRunspace1 = [runspacefactory]::CreateRunspace($iss) @@ -56,12 +62,12 @@ Goodbye @{ Name = "Current runspace" Runspace = [System.Management.Automation.Runspaces.Runspace]::DefaultRunspace - Breakpoints = $breakpoint1 + Breakpoints = $breakpointArr }, @{ Name = $testRunspace1.Name Runspace = $testRunspace1 - Breakpoints = $breakpoint1 + Breakpoints = $breakpointArr } ) { param($Runspace, $Breakpoints) @@ -69,26 +75,3 @@ Goodbye $Runspace.Debugger.GetBreakpoints() | Should -Be @($Breakpoints) } } - -Describe "`Enable-RunspaceDebug -Breakpoint` Unit Tests - Feature-Disabled" -Tags "CI" { - - BeforeAll { - if ($FeatureEnabled) { - Write-Verbose "Test Suite Skipped. The test suite requires the experimental feature 'Microsoft.PowerShell.Utility.PSDebugRunspaceWithBreakpoints' to be disabled." -Verbose - $originalDefaultParameterValues = $PSDefaultParameterValues.Clone() - $PSDefaultParameterValues["it:skip"] = $true - return - } - } - - AfterAll { - if ($FeatureEnabled) { - $global:PSDefaultParameterValues = $originalDefaultParameterValues - return - } - } - - It "Should not have `Enable-RunspaceDebug -Breakpoint` available" { - { Enable-RunspaceDebug -Breakpoint } | Should -Throw -ErrorId NamedParameterNotFound - } -} diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/New-PSBreakpoint.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/New-PSBreakpoint.Tests.ps1 index a648271891e..544cdff2902 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/New-PSBreakpoint.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/New-PSBreakpoint.Tests.ps1 @@ -1,6 +1,6 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -$ps = Join-Path -Path $PsHome -ChildPath "pwsh" + $FeatureEnabled = $EnabledExperimentalFeatures.Contains('Microsoft.PowerShell.Utility.PSDebugRunspaceWithBreakpoints') Describe "New-PSBreakpoint Unit Tests - Feature-Enabled" -Tags "CI" { @@ -42,7 +42,7 @@ return $contents > $scriptFileName1 # Set up script file 2 - $scriptFileName2 = Join-Path -Path $PSScriptRoot -ChildPath psbreakpointtestscript.ps1 + $scriptFileName2 = Join-Path -Path $TestDrive -ChildPath psbreakpointtestscript.ps1 "`$var = 1 " > $scriptFileName2 } @@ -52,10 +52,6 @@ return $global:PSDefaultParameterValues = $originalDefaultParameterValues return } - - # clean up - Remove-Item -Path $scriptFileName1 -Force -ErrorAction SilentlyContinue - Remove-Item -Path $scriptFileName2 -Force -ErrorAction SilentlyContinue } It "Should be able to set psbreakpoints for -Line" { @@ -100,12 +96,12 @@ return } It "Should throw Exception when missing mandatory parameter -line" -Pending { - $output = & $ps -noninteractive -command "nbp -column 1 -script $scriptFileName1" + $output = pwsh -noninteractive -command "nbp -column 1 -script $scriptFileName1" [system.string]::Join(" ", $output) | Should -Match "MissingMandatoryParameter,Microsoft.PowerShell.Commands.NewPSBreakpointCommand" } It "Should throw Exception when missing mandatory parameter" -Pending { - $output = & $ps -noprofile -noninteractive -command "nbp -line 1" + $output = pwsh -noprofile -noninteractive -command "nbp -line 1" [system.string]::Join(" ", $output) | Should -Match "MissingMandatoryParameter,Microsoft.PowerShell.Commands.NewPSBreakpointCommand" } @@ -197,26 +193,3 @@ return $brk.Command | Should -Be $var } } - -Describe "New-PSBreakpoint Unit Tests - Feature-Disabled" -Tags "CI" { - - BeforeAll { - if ($FeatureEnabled) { - Write-Verbose "Test Suite Skipped. The test suite requires the experimental feature 'Microsoft.PowerShell.Utility.PSDebugRunspaceWithBreakpoints' to be disabled." -Verbose - $originalDefaultParameterValues = $PSDefaultParameterValues.Clone() - $PSDefaultParameterValues["it:skip"] = $true - return - } - } - - AfterAll { - if ($FeatureEnabled) { - $global:PSDefaultParameterValues = $originalDefaultParameterValues - return - } - } - - It "Should not have New-PSBreakpoint available" { - { New-PSBreakpoint } | Should -Throw -ErrorId CommandNotFoundException - } -} From 15957721ad396bdd958d2084feda4922610c55ba Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Mon, 8 Apr 2019 12:47:36 -0700 Subject: [PATCH 19/19] Ilya's comments --- .../utility/EnableDisableRunspaceDebugCommand.cs | 6 ++---- .../commands/utility/New-PSBreakpoint.cs | 4 +--- .../commands/utility/PSBreakpointCreationBase.cs | 1 + .../engine/debugger/Breakpoint.cs | 10 +++++----- test/xUnit/csharp/test_Runspace.cs | 4 ++-- 5 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/EnableDisableRunspaceDebugCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/EnableDisableRunspaceDebugCommand.cs index 98c26bc5337..5c1b61f3601 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/EnableDisableRunspaceDebugCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/EnableDisableRunspaceDebugCommand.cs @@ -391,8 +391,7 @@ protected override void ProcessRecord() new ErrorRecord(new PSInvalidOperationException(string.Format(CultureInfo.InvariantCulture, Debugger.RunspaceOptionInvalidRunspaceState, runspace.Name)), "SetRunspaceDebugOptionCommandInvalidRunspaceState", ErrorCategory.InvalidOperation, - this) - ); + this)); continue; } @@ -421,8 +420,7 @@ protected override void ProcessRecord() e, "SetRunspaceDebugOptionCommandCannotEnableDebuggerStepping", ErrorCategory.InvalidOperation, - this) - ); + this)); } } else diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/New-PSBreakpoint.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/New-PSBreakpoint.cs index da386276627..b5837cc0da3 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/New-PSBreakpoint.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/New-PSBreakpoint.cs @@ -64,11 +64,9 @@ protected override void ProcessRecord() } } } - // - // Else it is the default parameter set (Line breakpoint)... - // else { + // Else it is the default parameter set (Line breakpoint)... Debug.Assert(ParameterSetName.Equals(LineParameterSetName, StringComparison.OrdinalIgnoreCase)); for (int i = 0; i < Line.Length; i++) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/PSBreakpointCreationBase.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/PSBreakpointCreationBase.cs index acbd6c8685a..43e3e93839d 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/PSBreakpointCreationBase.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/PSBreakpointCreationBase.cs @@ -65,6 +65,7 @@ public class PSBreakpointCreationBase : PSCmdlet public string[] Variable { get; set; } /// + /// The access type for variable breakpoints to break on. /// [Parameter(ParameterSetName = VariableParameterSetName)] public VariableAccessMode Mode { get; set; } = VariableAccessMode.Write; diff --git a/src/System.Management.Automation/engine/debugger/Breakpoint.cs b/src/System.Management.Automation/engine/debugger/Breakpoint.cs index 866bef82f24..f7372a27520 100644 --- a/src/System.Management.Automation/engine/debugger/Breakpoint.cs +++ b/src/System.Management.Automation/engine/debugger/Breakpoint.cs @@ -265,7 +265,7 @@ public enum VariableAccessMode /// Read, /// - /// Break on write access only (default) + /// Break on write access only (default). /// Write, /// @@ -280,14 +280,14 @@ public enum VariableAccessMode public class VariableBreakpoint : Breakpoint { /// - /// Creates a new instance of a + /// Creates a new instance of a . /// public VariableBreakpoint(string script, string variable, VariableAccessMode accessMode) : this(script, variable, accessMode, null) {} /// - /// Creates a new instance of a + /// Creates a new instance of a . /// public VariableBreakpoint(string script, string variable, VariableAccessMode accessMode, ScriptBlock action) : base(script, action) @@ -297,14 +297,14 @@ public VariableBreakpoint(string script, string variable, VariableAccessMode acc } /// - /// Creates a new instance of a + /// Creates a new instance of a . /// public VariableBreakpoint(string script, string variable, VariableAccessMode accessMode, int id) : this(script, variable, accessMode, null, id) {} /// - /// Creates a new instance of a + /// Creates a new instance of a . /// public VariableBreakpoint(string script, string variable, VariableAccessMode accessMode, ScriptBlock action, int id) : base(script, action, id) diff --git a/test/xUnit/csharp/test_Runspace.cs b/test/xUnit/csharp/test_Runspace.cs index 3519bf2f371..38a6dfd1bf3 100644 --- a/test/xUnit/csharp/test_Runspace.cs +++ b/test/xUnit/csharp/test_Runspace.cs @@ -108,8 +108,8 @@ public void TestRunspaceSetBreakpoints() using (var runspace = RunspaceFactory.CreateRunspace()) { var expectedBreakpoints = new Breakpoint[] { - new LineBreakpoint("./path/to/some/file.ps1", 1), - new CommandBreakpoint("./path/to/some/file.ps1", new WildcardPattern("Write-Host"), "Write-Host"), + new LineBreakpoint(@"./path/to/some/file.ps1", 1), + new CommandBreakpoint(@"./path/to/some/file.ps1", new WildcardPattern("Write-Host"), "Write-Host"), }; runspace.Open();