From 0edeed419cb04e58c5201e45757addebd6d04d58 Mon Sep 17 00:00:00 2001 From: Justin Grote Date: Tue, 23 Nov 2021 09:59:23 -0800 Subject: [PATCH 01/14] Feature: Add "Raw View" to IEnumerables and IDictionaries --- .../DebugAdapter/Debugging/VariableDetails.cs | 35 +++++++++++++++++-- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetails.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetails.cs index 0b17ba8f7..338ad6515 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetails.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetails.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Linq.Expressions; using System.Management.Automation; using System.Reflection; using Microsoft.Extensions.Logging; @@ -27,7 +28,7 @@ internal class VariableDetails : VariableDetailsBase /// public const string DollarPrefix = "$"; - private object valueObject; + protected object valueObject; private VariableDetails[] cachedChildren; #endregion @@ -366,10 +367,23 @@ private VariableDetails[] GetChildren(object obj, ILogger logger) return childVariables.ToArray(); } - - private static void AddDotNetProperties(object obj, List childVariables) + protected static void AddDotNetProperties(object obj, List childVariables, bool noRawView = false) { Type objectType = obj.GetType(); + + // For certain array or dictionary types, we want to hide additional properties under a "raw view" header + // to reduce noise. This is inspired by the C# vscode extension. + if (!noRawView && + ( + obj is IEnumerable + || obj is IDictionary + ) + ) + { + childVariables.Add(new VariableDetailsRawView(obj)); + return; + } + var properties = objectType.GetProperties( BindingFlags.Public | BindingFlags.Instance); @@ -424,4 +438,19 @@ public override string ToString() } } } + + /// + /// A VariableDetails that only returns the raw view properties of the object, rather than its values. + /// + internal class VariableDetailsRawView : VariableDetails + { + private const string RawViewName = "Raw View"; + public VariableDetailsRawView(object value) : base(RawViewName, value) { } + public override VariableDetailsBase[] GetChildren(ILogger logger) + { + List childVariables = new(); + AddDotNetProperties(valueObject, childVariables, noRawView: true); + return childVariables.ToArray(); + } + } } From 1447e94f144d525063e6c53b601d37ec8e6ed23d Mon Sep 17 00:00:00 2001 From: Justin Grote Date: Wed, 24 Nov 2021 09:06:57 -0800 Subject: [PATCH 02/14] Update src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetails.cs Co-authored-by: Patrick Meinecke --- .../Services/DebugAdapter/Debugging/VariableDetails.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetails.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetails.cs index 338ad6515..7b4d05bc3 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetails.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetails.cs @@ -445,6 +445,7 @@ public override string ToString() internal class VariableDetailsRawView : VariableDetails { private const string RawViewName = "Raw View"; + public VariableDetailsRawView(object value) : base(RawViewName, value) { } public override VariableDetailsBase[] GetChildren(ILogger logger) { From f9612cebd8b34af898b8d3fe9ba994aa2c0da757 Mon Sep 17 00:00:00 2001 From: Justin Grote Date: Wed, 24 Nov 2021 09:07:06 -0800 Subject: [PATCH 03/14] Update src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetails.cs Co-authored-by: Patrick Meinecke --- .../Services/DebugAdapter/Debugging/VariableDetails.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetails.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetails.cs index 7b4d05bc3..70906f80d 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetails.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetails.cs @@ -446,7 +446,10 @@ internal class VariableDetailsRawView : VariableDetails { private const string RawViewName = "Raw View"; - public VariableDetailsRawView(object value) : base(RawViewName, value) { } + public VariableDetailsRawView(object value) : base(RawViewName, value) + { + } + public override VariableDetailsBase[] GetChildren(ILogger logger) { List childVariables = new(); From 2f8fee8ed1444aaf61b11793cb557e5469f5cc03 Mon Sep 17 00:00:00 2001 From: Justin Grote Date: Wed, 24 Nov 2021 09:07:27 -0800 Subject: [PATCH 04/14] Update src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetails.cs Co-authored-by: Patrick Meinecke --- .../Services/DebugAdapter/Debugging/VariableDetails.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetails.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetails.cs index 70906f80d..ec2e60d40 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetails.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetails.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using System.Linq.Expressions; using System.Management.Automation; using System.Reflection; using Microsoft.Extensions.Logging; From 7824c51394361b2212007ca56a721caf2bedbc95 Mon Sep 17 00:00:00 2001 From: Justin Grote Date: Wed, 24 Nov 2021 09:07:35 -0800 Subject: [PATCH 05/14] Update src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetails.cs Co-authored-by: Patrick Meinecke --- .../Services/DebugAdapter/Debugging/VariableDetails.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetails.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetails.cs index ec2e60d40..c5839ce3e 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetails.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetails.cs @@ -441,7 +441,7 @@ public override string ToString() /// /// A VariableDetails that only returns the raw view properties of the object, rather than its values. /// - internal class VariableDetailsRawView : VariableDetails + internal sealed class VariableDetailsRawView : VariableDetails { private const string RawViewName = "Raw View"; From cfda423801e3fd34f3ee55dc4e3852508385db7c Mon Sep 17 00:00:00 2001 From: Justin Grote Date: Wed, 24 Nov 2021 09:30:46 -0800 Subject: [PATCH 06/14] Change VariableObject to property from field since it is now derived --- .../DebugAdapter/Debugging/VariableDetails.cs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetails.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetails.cs index c5839ce3e..9dc8ebf5e 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetails.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetails.cs @@ -26,8 +26,7 @@ internal class VariableDetails : VariableDetailsBase /// Provides a constant for the dollar sign variable prefix string. /// public const string DollarPrefix = "$"; - - protected object valueObject; + protected object ValueObject { get; } private VariableDetails[] cachedChildren; #endregion @@ -81,7 +80,7 @@ public VariableDetails(PSPropertyInfo psProperty) /// The variable's value. public VariableDetails(string name, object value) { - this.valueObject = value; + this.ValueObject = value; this.Id = -1; // Not been assigned a variable reference id yet this.Name = name; @@ -109,7 +108,7 @@ public override VariableDetailsBase[] GetChildren(ILogger logger) { if (this.cachedChildren == null) { - this.cachedChildren = GetChildren(this.valueObject, logger); + this.cachedChildren = GetChildren(this.ValueObject, logger); } return this.cachedChildren; @@ -175,13 +174,13 @@ private static string GetValueStringAndType(object value, bool isExpandable, out if (value is bool) { // Set to identifier recognized by PowerShell to make setVariable from the debug UI more natural. - valueString = (bool) value ? "$true" : "$false"; + valueString = (bool)value ? "$true" : "$false"; // We need to use this "magic value" to highlight in vscode properly // These "magic values" are analagous to TypeScript and are visible in VSCode here: // https://github.com/microsoft/vscode/blob/57ca9b99d5b6a59f2d2e0f082ae186559f45f1d8/src/vs/workbench/contrib/debug/browser/baseDebugView.ts#L68-L78 - // NOTE: we don't do numbers and strings since they (so far) seem to get detected properly by - //serialization, and the original .NET type can be preserved so it shows up in the variable name + // NOTE: we don't do numbers and strings since they (so far) seem to get detected properly by + //serialization, and the original .NET type can be preserved so it shows up in the variable name //type hover as the original .NET type. typeName = "boolean"; } @@ -452,7 +451,7 @@ public VariableDetailsRawView(object value) : base(RawViewName, value) public override VariableDetailsBase[] GetChildren(ILogger logger) { List childVariables = new(); - AddDotNetProperties(valueObject, childVariables, noRawView: true); + AddDotNetProperties(ValueObject, childVariables, noRawView: true); return childVariables.ToArray(); } } From a92d9d748dcb2c729b4e7469d0fc02767aba7f41 Mon Sep 17 00:00:00 2001 From: Justin Grote Date: Wed, 24 Nov 2021 09:33:16 -0800 Subject: [PATCH 07/14] Implement Suggestion https://github.com/PowerShell/PowerShellEditorServices/pull/1634#discussion_r756254993 --- .../Services/DebugAdapter/Debugging/VariableDetails.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetails.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetails.cs index 9dc8ebf5e..5e2d1350a 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetails.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetails.cs @@ -371,12 +371,7 @@ protected static void AddDotNetProperties(object obj, List chil // For certain array or dictionary types, we want to hide additional properties under a "raw view" header // to reduce noise. This is inspired by the C# vscode extension. - if (!noRawView && - ( - obj is IEnumerable - || obj is IDictionary - ) - ) + if (!noRawView && obj is IEnumerable) { childVariables.Add(new VariableDetailsRawView(obj)); return; From 7df4dd5531ba4acdabbcffa92483da4a9a64a410 Mon Sep 17 00:00:00 2001 From: Justin Grote Date: Sun, 23 Jan 2022 15:31:04 -0800 Subject: [PATCH 08/14] =?UTF-8?q?=F0=9F=A7=AA=20Initial=20Test=20Attempt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Debugging/VariableEnumerableTest.ps1 | 17 ++++++ .../Debugging/DebugServiceTests.cs | 55 +++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 test/PowerShellEditorServices.Test.Shared/Debugging/VariableEnumerableTest.ps1 diff --git a/test/PowerShellEditorServices.Test.Shared/Debugging/VariableEnumerableTest.ps1 b/test/PowerShellEditorServices.Test.Shared/Debugging/VariableEnumerableTest.ps1 new file mode 100644 index 000000000..8167b38ac --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Debugging/VariableEnumerableTest.ps1 @@ -0,0 +1,17 @@ +$SCRIPT:simpleArray = @( + 1 + 2 + 'red' + 'blue' +) +$SCRIPT:nestedArray = @( + 1 + 2 + @( + 'red' + 'blue' + ) +) +function __BreakDebuggerEnumerableShowsSummaryOnly{}; __BreakDebuggerEnumerableShowsSummaryOnly + +#This is a dummy function that the test will use to stop and evaluate the debug environment diff --git a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs index 51e2f49ca..5f55cf25e 100644 --- a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs @@ -134,6 +134,28 @@ private void AssertDebuggerStopped( } } + private void AssertDebuggerStoppedCommand( + CommandBreakpointDetails commandBreakpointDetails, + string scriptPath = "" + ) + { + var eventArgs = debuggerStoppedQueue.Take(new CancellationTokenSource(5000).Token); + + Assert.True(psesHost.DebugContext.IsStopped); + + if (scriptPath != "") + { + // TODO: The drive letter becomes lower cased on Windows for some reason. + Assert.Equal(scriptPath, eventArgs.ScriptPath, ignoreCase: true); + } + else + { + Assert.Equal(string.Empty, scriptPath); + } + + Assert.Equal(commandBreakpointDetails.Name, eventArgs.OriginalEvent.InvocationInfo.MyCommand.Name); + } + private Task> GetConfirmedBreakpoints(ScriptFile scriptFile) { // TODO: Should we use the APIs in BreakpointService to get these? @@ -772,6 +794,39 @@ await debugService.SetLineBreakpointsAsync( Assert.Equal("\"John\"", childVars["Name"]); } + [Fact] + public async Task DebuggerEnumerableShowsSummaryOnly() + { + var variableEnumerableScriptFile = GetDebugScript("VariableEnumerableTest.ps1"); + CommandBreakpointDetails breakpoint = CommandBreakpointDetails.Create( + name: "__BreakDebuggerEnumerableShowsSummaryOnly" + ); + await debugService.SetCommandBreakpointsAsync( + new[] { breakpoint } + ).ConfigureAwait(true); + + // Execute the script and wait for the breakpoint to be hit + _ = ExecutePowerShellCommand(variableEnumerableScriptFile.FilePath); + AssertDebuggerStoppedCommand(breakpoint); + + StackFrameDetails[] stackFrames = await debugService.GetStackFramesAsync().ConfigureAwait(true); + VariableScope scriptScope = Array.Find( + debugService.GetVariableScopes(0), + scope => scope.Name == "Script" + ); + Assert.NotNull(scriptScope); + VariableDetailsBase simpleArrayVariable = Array.Find( + debugService.GetVariables(scriptScope.Id), + variable => variable.Name == "$simpleArray" + ); + Assert.NotNull(simpleArrayVariable); + VariableDetailsBase rawDetailsView = Array.Find( + simpleArrayVariable.GetChildren(NullLogger.Instance), + variable => variable.Name == "Raw View" + ); + Assert.NotNull(rawDetailsView); + } + [Fact] public async Task DebuggerVariablePSCustomObjectDisplaysCorrectly() { From 049cb97477b2e27c962c7f229404e02df3558edd Mon Sep 17 00:00:00 2001 From: Justin Grote Date: Sun, 23 Jan 2022 17:40:24 -0800 Subject: [PATCH 09/14] =?UTF-8?q?fixup!=20=F0=9F=A7=AA=20Initial=20Test=20?= =?UTF-8?q?Attempt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Debugging/DebugServiceTests.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs index 5f55cf25e..45bed9cb1 100644 --- a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs @@ -232,7 +232,8 @@ public async Task DebuggerAcceptsScriptArgs() Assert.True(var.IsExpandable); var childVars = debugService.GetVariables(var.Id); - Assert.Equal(9, childVars.Length); + // 2 variables plus "Raw View" + Assert.Equal(3, childVars.Length); Assert.Equal("\"Bar\"", childVars[0].ValueString); Assert.Equal("\"Baz\"", childVars[1].ValueString); @@ -554,7 +555,8 @@ await debugService.SetLineBreakpointsAsync( Assert.True(objVar.IsExpandable); var objChildren = debugService.GetVariables(objVar.Id); - Assert.Equal(9, objChildren.Length); + // Two variables plus "Raw View" + Assert.Equal(3, objChildren.Length); var arrVar = Array.Find(variables, v => v.Name == "$arrVar"); Assert.NotNull(arrVar); @@ -731,7 +733,8 @@ await debugService.SetLineBreakpointsAsync( Assert.True(var.IsExpandable); VariableDetailsBase[] childVars = debugService.GetVariables(var.Id); - Assert.Equal(9, childVars.Length); + // 2 variables plus "Raw View" + Assert.Equal(3, childVars.Length); Assert.Equal("[0]", childVars[0].Name); Assert.Equal("[1]", childVars[1].Name); From 0a999054885a52cbdfff9c46c182d20bfca2f87d Mon Sep 17 00:00:00 2001 From: Justin Grote Date: Sun, 23 Jan 2022 17:50:43 -0800 Subject: [PATCH 10/14] =?UTF-8?q?fixup!=20=F0=9F=A7=AA=20Initial=20Test=20?= =?UTF-8?q?Attempt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Debugging/DebugServiceTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs index 45bed9cb1..2770e33eb 100644 --- a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs @@ -250,7 +250,7 @@ public async Task DebuggerAcceptsScriptArgs() Assert.True(var.IsExpandable); childVars = debugService.GetVariables(var.Id); - Assert.Equal(8, childVars.Length); + Assert.Equal(2, childVars.Length); Assert.Equal("\"Extra1\"", childVars[0].ValueString); } @@ -563,7 +563,7 @@ await debugService.SetLineBreakpointsAsync( Assert.True(arrVar.IsExpandable); var arrChildren = debugService.GetVariables(arrVar.Id); - Assert.Equal(11, arrChildren.Length); + Assert.Equal(5, arrChildren.Length); var classVar = Array.Find(variables, v => v.Name == "$classVar"); Assert.NotNull(classVar); From 37518cb5f45c7f1d13ae760e942bd64844b0c109 Mon Sep 17 00:00:00 2001 From: Justin Grote Date: Sun, 23 Jan 2022 19:14:35 -0800 Subject: [PATCH 11/14] Simplify Raw View Presentation and more tests --- .../DebugAdapter/Debugging/VariableDetails.cs | 2 ++ .../Debugging/VariableEnumerableTest.ps1 | 16 +++++++------- .../Debugging/DebugServiceTests.cs | 21 +++++++++++++++++-- 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetails.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetails.cs index 5e2d1350a..0852839a6 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetails.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetails.cs @@ -441,6 +441,8 @@ internal sealed class VariableDetailsRawView : VariableDetails public VariableDetailsRawView(object value) : base(RawViewName, value) { + this.ValueString = ""; + this.Type = ""; } public override VariableDetailsBase[] GetChildren(ILogger logger) diff --git a/test/PowerShellEditorServices.Test.Shared/Debugging/VariableEnumerableTest.ps1 b/test/PowerShellEditorServices.Test.Shared/Debugging/VariableEnumerableTest.ps1 index 8167b38ac..f745b5bae 100644 --- a/test/PowerShellEditorServices.Test.Shared/Debugging/VariableEnumerableTest.ps1 +++ b/test/PowerShellEditorServices.Test.Shared/Debugging/VariableEnumerableTest.ps1 @@ -4,14 +4,12 @@ $SCRIPT:simpleArray = @( 'red' 'blue' ) -$SCRIPT:nestedArray = @( - 1 - 2 - @( - 'red' - 'blue' - ) -) -function __BreakDebuggerEnumerableShowsSummaryOnly{}; __BreakDebuggerEnumerableShowsSummaryOnly +$SCRIPT:simpleDictionary = @{ + item1 = 1 + item2 = 2 + item3 = 'red' + item4 = 'blue' +} +function __BreakDebuggerEnumerableShowsRawView{}; __BreakDebuggerEnumerableShowsRawView #This is a dummy function that the test will use to stop and evaluate the debug environment diff --git a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs index 2770e33eb..72b470166 100644 --- a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs @@ -798,11 +798,11 @@ await debugService.SetLineBreakpointsAsync( } [Fact] - public async Task DebuggerEnumerableShowsSummaryOnly() + public async Task DebuggerEnumerableShowsRawView() { var variableEnumerableScriptFile = GetDebugScript("VariableEnumerableTest.ps1"); CommandBreakpointDetails breakpoint = CommandBreakpointDetails.Create( - name: "__BreakDebuggerEnumerableShowsSummaryOnly" + name: "__BreakDebuggerEnumerableShowsRawView" ); await debugService.SetCommandBreakpointsAsync( new[] { breakpoint } @@ -828,6 +828,23 @@ await debugService.SetCommandBreakpointsAsync( variable => variable.Name == "Raw View" ); Assert.NotNull(rawDetailsView); + Assert.Empty(rawDetailsView.Type); + Assert.Empty(rawDetailsView.ValueString); + VariableDetailsBase[] rawViewChildren = rawDetailsView.GetChildren(NullLogger.Instance); + Assert.Equal(7, rawViewChildren.Length); + Assert.Equal("Length", rawViewChildren[0].Name); + Assert.Equal("4", rawViewChildren[0].ValueString); + Assert.Equal("LongLength", rawViewChildren[1].Name); + Assert.Equal("4", rawViewChildren[1].ValueString); + Assert.Equal("Rank", rawViewChildren[2].Name); + Assert.Equal("1", rawViewChildren[2].ValueString); + Assert.Equal("SyncRoot", rawViewChildren[3].Name); + Assert.Equal("IsReadOnly", rawViewChildren[4].Name); + Assert.Equal("$false", rawViewChildren[4].ValueString); + Assert.Equal("IsFixedSize", rawViewChildren[5].Name); + Assert.Equal("$true", rawViewChildren[5].ValueString); + Assert.Equal("IsSynchronized", rawViewChildren[6].Name); + Assert.Equal("$false", rawViewChildren[6].ValueString); } [Fact] From ee205627efbcfd926ab260827af5efe69e2500fc Mon Sep 17 00:00:00 2001 From: Justin Grote Date: Sun, 23 Jan 2022 19:49:35 -0800 Subject: [PATCH 12/14] Add Dictionary Test and Consolidate VariableTest File --- .../Debugging/VariableEnumerableTest.ps1 | 15 ------ .../Debugging/VariableTest.ps1 | 19 +++++++ .../Debugging/DebugServiceTests.cs | 51 ++++++++++++++++++- 3 files changed, 68 insertions(+), 17 deletions(-) delete mode 100644 test/PowerShellEditorServices.Test.Shared/Debugging/VariableEnumerableTest.ps1 diff --git a/test/PowerShellEditorServices.Test.Shared/Debugging/VariableEnumerableTest.ps1 b/test/PowerShellEditorServices.Test.Shared/Debugging/VariableEnumerableTest.ps1 deleted file mode 100644 index f745b5bae..000000000 --- a/test/PowerShellEditorServices.Test.Shared/Debugging/VariableEnumerableTest.ps1 +++ /dev/null @@ -1,15 +0,0 @@ -$SCRIPT:simpleArray = @( - 1 - 2 - 'red' - 'blue' -) -$SCRIPT:simpleDictionary = @{ - item1 = 1 - item2 = 2 - item3 = 'red' - item4 = 'blue' -} -function __BreakDebuggerEnumerableShowsRawView{}; __BreakDebuggerEnumerableShowsRawView - -#This is a dummy function that the test will use to stop and evaluate the debug environment diff --git a/test/PowerShellEditorServices.Test.Shared/Debugging/VariableTest.ps1 b/test/PowerShellEditorServices.Test.Shared/Debugging/VariableTest.ps1 index 63bf044dc..6b3adb4a7 100644 --- a/test/PowerShellEditorServices.Test.Shared/Debugging/VariableTest.ps1 +++ b/test/PowerShellEditorServices.Test.Shared/Debugging/VariableTest.ps1 @@ -24,3 +24,22 @@ function Test-Variables { Test-Variables # NOTE: If a line is added to the function above, the line numbers in the # associated unit tests MUST be adjusted accordingly. + +$SCRIPT:simpleDictionary = @{ + item1 = 1 + item2 = 2 + item3 = 'red' + item4 = 'blue' +} +#This is a dummy function that the test will use to stop and evaluate the debug environment +function __BreakDebuggerEnumerableShowsRawView{}; __BreakDebuggerEnumerableShowsRawView + +$SCRIPT:simpleArray = @( + 1 + 2 + 'red' + 'blue' +) + +# This is a dummy function that the test will use to stop and evaluate the debug environment +function __BreakDebuggerDictionaryShowsRawView{}; __BreakDebuggerDictionaryShowsRawView diff --git a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs index 72b470166..412523a95 100644 --- a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs @@ -800,7 +800,6 @@ await debugService.SetLineBreakpointsAsync( [Fact] public async Task DebuggerEnumerableShowsRawView() { - var variableEnumerableScriptFile = GetDebugScript("VariableEnumerableTest.ps1"); CommandBreakpointDetails breakpoint = CommandBreakpointDetails.Create( name: "__BreakDebuggerEnumerableShowsRawView" ); @@ -809,7 +808,7 @@ await debugService.SetCommandBreakpointsAsync( ).ConfigureAwait(true); // Execute the script and wait for the breakpoint to be hit - _ = ExecutePowerShellCommand(variableEnumerableScriptFile.FilePath); + Task _ = ExecuteVariableScriptFile(); AssertDebuggerStoppedCommand(breakpoint); StackFrameDetails[] stackFrames = await debugService.GetStackFramesAsync().ConfigureAwait(true); @@ -847,6 +846,54 @@ await debugService.SetCommandBreakpointsAsync( Assert.Equal("$false", rawViewChildren[6].ValueString); } + [Fact] + public async Task DebuggerDictionaryShowsRawView() + { + CommandBreakpointDetails breakpoint = CommandBreakpointDetails.Create( + name: "__BreakDebuggerDictionaryShowsRawView" + ); + await debugService.SetCommandBreakpointsAsync( + new[] { breakpoint } + ).ConfigureAwait(true); + + // Execute the script and wait for the breakpoint to be hit + Task _ = ExecuteVariableScriptFile(); + AssertDebuggerStoppedCommand(breakpoint); + + StackFrameDetails[] stackFrames = await debugService.GetStackFramesAsync().ConfigureAwait(true); + VariableScope scriptScope = Array.Find( + debugService.GetVariableScopes(0), + scope => scope.Name == "Script" + ); + Assert.NotNull(scriptScope); + VariableDetailsBase simpleDictionaryVariable = Array.Find( + debugService.GetVariables(scriptScope.Id), + variable => variable.Name == "$simpleDictionary" + ); + Assert.NotNull(simpleDictionaryVariable); + VariableDetailsBase rawDetailsView = Array.Find( + simpleDictionaryVariable.GetChildren(NullLogger.Instance), + variable => variable.Name == "Raw View" + ); + Assert.NotNull(rawDetailsView); + Assert.Empty(rawDetailsView.Type); + Assert.Empty(rawDetailsView.ValueString); + VariableDetailsBase[] rawViewChildren = rawDetailsView.GetChildren(NullLogger.Instance); + Assert.Equal(7, rawViewChildren.Length); + Assert.Equal("IsReadOnly", rawViewChildren[0].Name); + Assert.Equal("$false", rawViewChildren[0].ValueString); + Assert.Equal("IsFixedSize", rawViewChildren[1].Name); + Assert.Equal("$false", rawViewChildren[1].ValueString); + Assert.Equal("IsSynchronized", rawViewChildren[2].Name); + Assert.Equal("$false", rawViewChildren[2].ValueString); + Assert.Equal("Keys", rawViewChildren[3].Name); + Assert.Equal("Values", rawViewChildren[4].Name); + Assert.Equal("[ValueCollection: 4]", rawViewChildren[4].ValueString); + Assert.Equal("SyncRoot", rawViewChildren[5].Name); + Assert.Equal("Count", rawViewChildren[6].Name); + Assert.Equal("4", rawViewChildren[6].ValueString); + } + [Fact] public async Task DebuggerVariablePSCustomObjectDisplaysCorrectly() { From 7e02666dc3ad9c9599ed45aec894c20b4d864645 Mon Sep 17 00:00:00 2001 From: Justin Grote Date: Sun, 23 Jan 2022 20:15:13 -0800 Subject: [PATCH 13/14] Add Derived Dictionary Test --- .../Debugging/VariableTest.ps1 | 27 +++++++----- .../Debugging/DebugServiceTests.cs | 42 +++++++++++++++++++ 2 files changed, 59 insertions(+), 10 deletions(-) diff --git a/test/PowerShellEditorServices.Test.Shared/Debugging/VariableTest.ps1 b/test/PowerShellEditorServices.Test.Shared/Debugging/VariableTest.ps1 index 6b3adb4a7..a5213e5c1 100644 --- a/test/PowerShellEditorServices.Test.Shared/Debugging/VariableTest.ps1 +++ b/test/PowerShellEditorServices.Test.Shared/Debugging/VariableTest.ps1 @@ -25,21 +25,28 @@ Test-Variables # NOTE: If a line is added to the function above, the line numbers in the # associated unit tests MUST be adjusted accordingly. -$SCRIPT:simpleDictionary = @{ - item1 = 1 - item2 = 2 - item3 = 'red' - item4 = 'blue' -} -#This is a dummy function that the test will use to stop and evaluate the debug environment -function __BreakDebuggerEnumerableShowsRawView{}; __BreakDebuggerEnumerableShowsRawView - $SCRIPT:simpleArray = @( 1 2 'red' 'blue' ) +#This is a dummy function that the test will use to stop and evaluate the debug environment +function __BreakDebuggerEnumerableShowsRawView{}; __BreakDebuggerEnumerableShowsRawView -# This is a dummy function that the test will use to stop and evaluate the debug environment +$SCRIPT:simpleDictionary = @{ + item1 = 1 + item2 = 2 + item3 = 'red' + item4 = 'blue' +} function __BreakDebuggerDictionaryShowsRawView{}; __BreakDebuggerDictionaryShowsRawView + +$SCRIPT:sortedDictionary = [Collections.Generic.SortedDictionary[string, object]]::new() +$sortedDictionary[1] = 1 +$sortedDictionary[2] = 2 +$sortedDictionary['red'] = 'red' +$sortedDictionary['blue'] = 'red' +# This is a dummy function that the test will use to stop and evaluate the debug environment +function __BreakDebuggerDerivedDictionaryPropertyInRawView{}; __BreakDebuggerDerivedDictionaryPropertyInRawView + diff --git a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs index 412523a95..9e33a7639 100644 --- a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs @@ -894,6 +894,48 @@ await debugService.SetCommandBreakpointsAsync( Assert.Equal("4", rawViewChildren[6].ValueString); } + [Fact] + public async Task DebuggerDerivedDictionaryPropertyInRawView() + { + CommandBreakpointDetails breakpoint = CommandBreakpointDetails.Create( + name: "__BreakDebuggerDerivedDictionaryPropertyInRawView" + ); + await debugService.SetCommandBreakpointsAsync( + new[] { breakpoint } + ).ConfigureAwait(true); + + // Execute the script and wait for the breakpoint to be hit + Task _ = ExecuteVariableScriptFile(); + AssertDebuggerStoppedCommand(breakpoint); + + StackFrameDetails[] stackFrames = await debugService.GetStackFramesAsync().ConfigureAwait(true); + VariableScope scriptScope = Array.Find( + debugService.GetVariableScopes(0), + scope => scope.Name == "Script" + ); + Assert.NotNull(scriptScope); + VariableDetailsBase simpleDictionaryVariable = Array.Find( + debugService.GetVariables(scriptScope.Id), + variable => variable.Name == "$sortedDictionary" + ); + Assert.NotNull(simpleDictionaryVariable); + var simpleDictionaryChildren = simpleDictionaryVariable.GetChildren(NullLogger.Instance); + // 4 items + Raw View + Assert.Equal(5, simpleDictionaryChildren.Length); + VariableDetailsBase rawDetailsView = Array.Find( + simpleDictionaryChildren, + variable => variable.Name == "Raw View" + ); + Assert.NotNull(rawDetailsView); + Assert.Empty(rawDetailsView.Type); + Assert.Empty(rawDetailsView.ValueString); + VariableDetailsBase[] rawViewChildren = rawDetailsView.GetChildren(NullLogger.Instance); + Assert.Equal(4, rawViewChildren.Length); + Assert.NotNull( + Array.Find(rawViewChildren, variable => variable.Name == "Comparer") + ); + } + [Fact] public async Task DebuggerVariablePSCustomObjectDisplaysCorrectly() { From 719ad9aa7103b800f5ed400d042933ec78324a18 Mon Sep 17 00:00:00 2001 From: Andrew Schwartzmeyer Date: Mon, 24 Jan 2022 12:29:00 -0800 Subject: [PATCH 14/14] Simplify tests and cleanup --- .../DebugAdapter/Debugging/VariableDetails.cs | 10 +- .../Debugging/VariableTest.ps1 | 5 +- .../Debugging/DebugServiceTests.cs | 118 +++++------------- 3 files changed, 39 insertions(+), 94 deletions(-) diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetails.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetails.cs index 0852839a6..739885883 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetails.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetails.cs @@ -180,13 +180,12 @@ private static string GetValueStringAndType(object value, bool isExpandable, out // These "magic values" are analagous to TypeScript and are visible in VSCode here: // https://github.com/microsoft/vscode/blob/57ca9b99d5b6a59f2d2e0f082ae186559f45f1d8/src/vs/workbench/contrib/debug/browser/baseDebugView.ts#L68-L78 // NOTE: we don't do numbers and strings since they (so far) seem to get detected properly by - //serialization, and the original .NET type can be preserved so it shows up in the variable name - //type hover as the original .NET type. + // serialization, and the original .NET type can be preserved so it shows up in the variable name + // type hover as the original .NET type. typeName = "boolean"; } else if (isExpandable) { - // Get the "value" for an expandable object. if (value is DictionaryEntry) { @@ -365,6 +364,7 @@ private VariableDetails[] GetChildren(object obj, ILogger logger) return childVariables.ToArray(); } + protected static void AddDotNetProperties(object obj, List childVariables, bool noRawView = false) { Type objectType = obj.GetType(); @@ -377,9 +377,7 @@ protected static void AddDotNetProperties(object obj, List chil return; } - var properties = - objectType.GetProperties( - BindingFlags.Public | BindingFlags.Instance); + var properties = objectType.GetProperties(BindingFlags.Public | BindingFlags.Instance); foreach (var property in properties) { diff --git a/test/PowerShellEditorServices.Test.Shared/Debugging/VariableTest.ps1 b/test/PowerShellEditorServices.Test.Shared/Debugging/VariableTest.ps1 index a5213e5c1..e8c23d9a0 100644 --- a/test/PowerShellEditorServices.Test.Shared/Debugging/VariableTest.ps1 +++ b/test/PowerShellEditorServices.Test.Shared/Debugging/VariableTest.ps1 @@ -31,7 +31,8 @@ $SCRIPT:simpleArray = @( 'red' 'blue' ) -#This is a dummy function that the test will use to stop and evaluate the debug environment + +# This is a dummy function that the test will use to stop and evaluate the debug environment function __BreakDebuggerEnumerableShowsRawView{}; __BreakDebuggerEnumerableShowsRawView $SCRIPT:simpleDictionary = @{ @@ -47,6 +48,6 @@ $sortedDictionary[1] = 1 $sortedDictionary[2] = 2 $sortedDictionary['red'] = 'red' $sortedDictionary['blue'] = 'red' + # This is a dummy function that the test will use to stop and evaluate the debug environment function __BreakDebuggerDerivedDictionaryPropertyInRawView{}; __BreakDebuggerDerivedDictionaryPropertyInRawView - diff --git a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs index 9e33a7639..d8507afba 100644 --- a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs @@ -112,7 +112,8 @@ private void AssertDebuggerPaused() private void AssertDebuggerStopped( string scriptPath = "", - int lineNumber = -1) + int lineNumber = -1, + CommandBreakpointDetails commandBreakpointDetails = default) { var eventArgs = debuggerStoppedQueue.Take(new CancellationTokenSource(5000).Token); @@ -132,28 +133,11 @@ private void AssertDebuggerStopped( { Assert.Equal(lineNumber, eventArgs.LineNumber); } - } - - private void AssertDebuggerStoppedCommand( - CommandBreakpointDetails commandBreakpointDetails, - string scriptPath = "" - ) - { - var eventArgs = debuggerStoppedQueue.Take(new CancellationTokenSource(5000).Token); - - Assert.True(psesHost.DebugContext.IsStopped); - if (scriptPath != "") + if (commandBreakpointDetails is not null) { - // TODO: The drive letter becomes lower cased on Windows for some reason. - Assert.Equal(scriptPath, eventArgs.ScriptPath, ignoreCase: true); + Assert.Equal(commandBreakpointDetails.Name, eventArgs.OriginalEvent.InvocationInfo.MyCommand.Name); } - else - { - Assert.Equal(string.Empty, scriptPath); - } - - Assert.Equal(commandBreakpointDetails.Name, eventArgs.OriginalEvent.InvocationInfo.MyCommand.Name); } private Task> GetConfirmedBreakpoints(ScriptFile scriptFile) @@ -800,32 +784,20 @@ await debugService.SetLineBreakpointsAsync( [Fact] public async Task DebuggerEnumerableShowsRawView() { - CommandBreakpointDetails breakpoint = CommandBreakpointDetails.Create( - name: "__BreakDebuggerEnumerableShowsRawView" - ); - await debugService.SetCommandBreakpointsAsync( - new[] { breakpoint } - ).ConfigureAwait(true); + CommandBreakpointDetails breakpoint = CommandBreakpointDetails.Create("__BreakDebuggerEnumerableShowsRawView"); + await debugService.SetCommandBreakpointsAsync(new[] { breakpoint }).ConfigureAwait(true); // Execute the script and wait for the breakpoint to be hit Task _ = ExecuteVariableScriptFile(); - AssertDebuggerStoppedCommand(breakpoint); + AssertDebuggerStopped(commandBreakpointDetails: breakpoint); - StackFrameDetails[] stackFrames = await debugService.GetStackFramesAsync().ConfigureAwait(true); - VariableScope scriptScope = Array.Find( - debugService.GetVariableScopes(0), - scope => scope.Name == "Script" - ); - Assert.NotNull(scriptScope); - VariableDetailsBase simpleArrayVariable = Array.Find( - debugService.GetVariables(scriptScope.Id), - variable => variable.Name == "$simpleArray" - ); - Assert.NotNull(simpleArrayVariable); + VariableDetailsBase simpleArrayVar = Array.Find( + GetVariables(VariableContainerDetails.ScriptScopeName), + v => v.Name == "$simpleArray"); + Assert.NotNull(simpleArrayVar); VariableDetailsBase rawDetailsView = Array.Find( - simpleArrayVariable.GetChildren(NullLogger.Instance), - variable => variable.Name == "Raw View" - ); + simpleArrayVar.GetChildren(NullLogger.Instance), + v => v.Name == "Raw View"); Assert.NotNull(rawDetailsView); Assert.Empty(rawDetailsView.Type); Assert.Empty(rawDetailsView.ValueString); @@ -849,32 +821,20 @@ await debugService.SetCommandBreakpointsAsync( [Fact] public async Task DebuggerDictionaryShowsRawView() { - CommandBreakpointDetails breakpoint = CommandBreakpointDetails.Create( - name: "__BreakDebuggerDictionaryShowsRawView" - ); - await debugService.SetCommandBreakpointsAsync( - new[] { breakpoint } - ).ConfigureAwait(true); + CommandBreakpointDetails breakpoint = CommandBreakpointDetails.Create("__BreakDebuggerDictionaryShowsRawView"); + await debugService.SetCommandBreakpointsAsync(new[] { breakpoint }).ConfigureAwait(true); // Execute the script and wait for the breakpoint to be hit Task _ = ExecuteVariableScriptFile(); - AssertDebuggerStoppedCommand(breakpoint); + AssertDebuggerStopped(commandBreakpointDetails: breakpoint); - StackFrameDetails[] stackFrames = await debugService.GetStackFramesAsync().ConfigureAwait(true); - VariableScope scriptScope = Array.Find( - debugService.GetVariableScopes(0), - scope => scope.Name == "Script" - ); - Assert.NotNull(scriptScope); - VariableDetailsBase simpleDictionaryVariable = Array.Find( - debugService.GetVariables(scriptScope.Id), - variable => variable.Name == "$simpleDictionary" - ); - Assert.NotNull(simpleDictionaryVariable); + VariableDetailsBase simpleDictionaryVar = Array.Find( + GetVariables(VariableContainerDetails.ScriptScopeName), + v => v.Name == "$simpleDictionary"); + Assert.NotNull(simpleDictionaryVar); VariableDetailsBase rawDetailsView = Array.Find( - simpleDictionaryVariable.GetChildren(NullLogger.Instance), - variable => variable.Name == "Raw View" - ); + simpleDictionaryVar.GetChildren(NullLogger.Instance), + v => v.Name == "Raw View"); Assert.NotNull(rawDetailsView); Assert.Empty(rawDetailsView.Type); Assert.Empty(rawDetailsView.ValueString); @@ -897,43 +857,29 @@ await debugService.SetCommandBreakpointsAsync( [Fact] public async Task DebuggerDerivedDictionaryPropertyInRawView() { - CommandBreakpointDetails breakpoint = CommandBreakpointDetails.Create( - name: "__BreakDebuggerDerivedDictionaryPropertyInRawView" - ); - await debugService.SetCommandBreakpointsAsync( - new[] { breakpoint } - ).ConfigureAwait(true); + CommandBreakpointDetails breakpoint = CommandBreakpointDetails.Create("__BreakDebuggerDerivedDictionaryPropertyInRawView"); + await debugService.SetCommandBreakpointsAsync(new[] { breakpoint }).ConfigureAwait(true); // Execute the script and wait for the breakpoint to be hit Task _ = ExecuteVariableScriptFile(); - AssertDebuggerStoppedCommand(breakpoint); + AssertDebuggerStopped(commandBreakpointDetails: breakpoint); - StackFrameDetails[] stackFrames = await debugService.GetStackFramesAsync().ConfigureAwait(true); - VariableScope scriptScope = Array.Find( - debugService.GetVariableScopes(0), - scope => scope.Name == "Script" - ); - Assert.NotNull(scriptScope); - VariableDetailsBase simpleDictionaryVariable = Array.Find( - debugService.GetVariables(scriptScope.Id), - variable => variable.Name == "$sortedDictionary" - ); - Assert.NotNull(simpleDictionaryVariable); - var simpleDictionaryChildren = simpleDictionaryVariable.GetChildren(NullLogger.Instance); + VariableDetailsBase sortedDictionaryVar = Array.Find( + GetVariables(VariableContainerDetails.ScriptScopeName), + v => v.Name == "$sortedDictionary"); + Assert.NotNull(sortedDictionaryVar); + VariableDetailsBase[] simpleDictionaryChildren = sortedDictionaryVar.GetChildren(NullLogger.Instance); // 4 items + Raw View Assert.Equal(5, simpleDictionaryChildren.Length); VariableDetailsBase rawDetailsView = Array.Find( simpleDictionaryChildren, - variable => variable.Name == "Raw View" - ); + v => v.Name == "Raw View"); Assert.NotNull(rawDetailsView); Assert.Empty(rawDetailsView.Type); Assert.Empty(rawDetailsView.ValueString); VariableDetailsBase[] rawViewChildren = rawDetailsView.GetChildren(NullLogger.Instance); Assert.Equal(4, rawViewChildren.Length); - Assert.NotNull( - Array.Find(rawViewChildren, variable => variable.Name == "Comparer") - ); + Assert.NotNull(Array.Find(rawViewChildren, v => v .Name == "Comparer")); } [Fact]