Skip to content

Commit 89fb98f

Browse files
committed
Guard expansion of PSPropertyInfo.Value
Which fixes our test that gets the properties of a process object, since the value for `ExitCode` fails (as the process hasn't ended), we now handle that a bit more gracefully and can get all 130 properties. This also seemed to fix the overall bug where lots of expected properties failed to show up. While investigating, I also found many properties duplicated, seemingly due to being retrieved both off the PSObject and off the .NET object, so now when we do the latter we check (by name) that it hasn't already been added.
1 parent e438e76 commit 89fb98f

File tree

3 files changed

+155
-48
lines changed

3 files changed

+155
-48
lines changed

Diff for: src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetails.cs

+27-15
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ public VariableDetails(PSObject psVariableObject)
6868
/// The PSPropertyInfo instance from which variable details will be obtained.
6969
/// </param>
7070
public VariableDetails(PSPropertyInfo psProperty)
71-
: this(psProperty.Name, psProperty.Value)
71+
: this(psProperty.Name, SafeGetValue(psProperty))
7272
{
7373
}
7474

@@ -113,6 +113,20 @@ public override VariableDetailsBase[] GetChildren(ILogger logger)
113113

114114
#region Private Methods
115115

116+
private static object SafeGetValue(PSPropertyInfo psProperty)
117+
{
118+
try
119+
{
120+
return psProperty.Value;
121+
}
122+
catch (GetValueInvocationException ex)
123+
{
124+
// Sometimes we can't get the value, like ExitCode, for reasons beyond our control,
125+
// so just return the message from the exception that arises.
126+
return new UnableToRetrievePropertyMessage { Name = psProperty.Name, Message = ex.Message };
127+
}
128+
}
129+
116130
private static bool GetIsExpandable(object valueObject)
117131
{
118132
if (valueObject == null)
@@ -331,9 +345,8 @@ protected static void AddDotNetProperties(object obj, List<VariableDetails> chil
331345
return;
332346
}
333347

334-
PropertyInfo[] properties = objectType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
335-
336-
foreach (PropertyInfo property in properties)
348+
// Search all the public instance properties and add those missing.
349+
foreach (PropertyInfo property in objectType.GetProperties(BindingFlags.Public | BindingFlags.Instance))
337350
{
338351
// Don't display indexer properties, it causes an exception anyway.
339352
if (property.GetIndexParameters().Length > 0)
@@ -343,10 +356,11 @@ protected static void AddDotNetProperties(object obj, List<VariableDetails> chil
343356

344357
try
345358
{
346-
childVariables.Add(
347-
new VariableDetails(
348-
property.Name,
349-
property.GetValue(obj)));
359+
// Only add unique properties because we may have already added some.
360+
if (!childVariables.Exists(p => p.Name == property.Name))
361+
{
362+
childVariables.Add(new VariableDetails(property.Name, property.GetValue(obj)));
363+
}
350364
}
351365
catch (Exception ex)
352366
{
@@ -360,21 +374,19 @@ protected static void AddDotNetProperties(object obj, List<VariableDetails> chil
360374
childVariables.Add(
361375
new VariableDetails(
362376
property.Name,
363-
new UnableToRetrievePropertyMessage(
364-
"Error retrieving property - " + ex.GetType().Name)));
377+
new UnableToRetrievePropertyMessage { Name = property.Name, Message = ex.Message }));
365378
}
366379
}
367380
}
368381

369382
#endregion
370383

371-
private readonly struct UnableToRetrievePropertyMessage
384+
private record UnableToRetrievePropertyMessage
372385
{
373-
public UnableToRetrievePropertyMessage(string message) => Message = message;
374-
375-
public string Message { get; }
386+
public string Name { get; init; }
387+
public string Message { get; init; }
376388

377-
public override string ToString() => "<" + Message + ">";
389+
public override string ToString() => $"Error retrieving property '${Name}': ${Message}";
378390
}
379391
}
380392

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
$file = Get-ChildItem -Path "." | Select-Object -First 1
2+
Write-Host "Debug over"

Diff for: test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs

+126-33
Original file line numberDiff line numberDiff line change
@@ -920,20 +920,40 @@ await GetVariables(VariableContainerDetails.ScriptScopeName).ConfigureAwait(true
920920
Assert.Empty(rawDetailsView.Type);
921921
Assert.Empty(rawDetailsView.ValueString);
922922
VariableDetailsBase[] rawViewChildren = rawDetailsView.GetChildren(NullLogger.Instance);
923-
Assert.Equal(7, rawViewChildren.Length);
924-
Assert.Equal("Length", rawViewChildren[0].Name);
925-
Assert.Equal("4", rawViewChildren[0].ValueString);
926-
Assert.Equal("LongLength", rawViewChildren[1].Name);
927-
Assert.Equal("4", rawViewChildren[1].ValueString);
928-
Assert.Equal("Rank", rawViewChildren[2].Name);
929-
Assert.Equal("1", rawViewChildren[2].ValueString);
930-
Assert.Equal("SyncRoot", rawViewChildren[3].Name);
931-
Assert.Equal("IsReadOnly", rawViewChildren[4].Name);
932-
Assert.Equal("$false", rawViewChildren[4].ValueString);
933-
Assert.Equal("IsFixedSize", rawViewChildren[5].Name);
934-
Assert.Equal("$true", rawViewChildren[5].ValueString);
935-
Assert.Equal("IsSynchronized", rawViewChildren[6].Name);
936-
Assert.Equal("$false", rawViewChildren[6].ValueString);
923+
Assert.Collection(rawViewChildren,
924+
(i) =>
925+
{
926+
Assert.Equal("Length", i.Name);
927+
Assert.Equal("4", i.ValueString);
928+
},
929+
(i) =>
930+
{
931+
Assert.Equal("LongLength", i.Name);
932+
Assert.Equal("4", i.ValueString);
933+
},
934+
(i) =>
935+
{
936+
Assert.Equal("Rank", i.Name);
937+
Assert.Equal("1", i.ValueString);
938+
},
939+
(i) =>
940+
{
941+
Assert.Equal("SyncRoot", i.Name);
942+
Assert.True(i.IsExpandable);
943+
},
944+
(i) =>
945+
{
946+
Assert.Equal("IsReadOnly", i.Name);
947+
Assert.Equal("$false", i.ValueString);
948+
}, (i) =>
949+
{
950+
Assert.Equal("IsFixedSize", i.Name);
951+
Assert.Equal("$true", i.ValueString);
952+
}, (i) =>
953+
{
954+
Assert.Equal("IsSynchronized", i.Name);
955+
Assert.Equal("$false", i.ValueString);
956+
});
937957
}
938958

939959
[Fact]
@@ -956,20 +976,47 @@ await GetVariables(VariableContainerDetails.ScriptScopeName).ConfigureAwait(true
956976
Assert.NotNull(rawDetailsView);
957977
Assert.Empty(rawDetailsView.Type);
958978
Assert.Empty(rawDetailsView.ValueString);
959-
VariableDetailsBase[] rawViewChildren = rawDetailsView.GetChildren(NullLogger.Instance);
960-
Assert.Equal(7, rawViewChildren.Length);
961-
Assert.Equal("IsReadOnly", rawViewChildren[0].Name);
962-
Assert.Equal("$false", rawViewChildren[0].ValueString);
963-
Assert.Equal("IsFixedSize", rawViewChildren[1].Name);
964-
Assert.Equal("$false", rawViewChildren[1].ValueString);
965-
Assert.Equal("IsSynchronized", rawViewChildren[2].Name);
966-
Assert.Equal("$false", rawViewChildren[2].ValueString);
967-
Assert.Equal("Keys", rawViewChildren[3].Name);
968-
Assert.Equal("Values", rawViewChildren[4].Name);
969-
Assert.Equal("[ValueCollection: 4]", rawViewChildren[4].ValueString);
970-
Assert.Equal("SyncRoot", rawViewChildren[5].Name);
971-
Assert.Equal("Count", rawViewChildren[6].Name);
972-
Assert.Equal("4", rawViewChildren[6].ValueString);
979+
VariableDetailsBase[] rawDetailsChildren = rawDetailsView.GetChildren(NullLogger.Instance);
980+
Assert.Collection(rawDetailsChildren,
981+
(i) =>
982+
{
983+
Assert.Equal("IsReadOnly", i.Name);
984+
Assert.Equal("$false", i.ValueString);
985+
},
986+
(i) =>
987+
{
988+
Assert.Equal("IsFixedSize", i.Name);
989+
Assert.Equal("$false", i.ValueString);
990+
},
991+
(i) =>
992+
{
993+
Assert.Equal("IsSynchronized", i.Name);
994+
Assert.Equal("$false", i.ValueString);
995+
},
996+
(i) =>
997+
{
998+
Assert.Equal("Keys", i.Name);
999+
Assert.Equal("[KeyCollection: 4]", i.ValueString);
1000+
},
1001+
(i) =>
1002+
{
1003+
Assert.Equal("Values", i.Name);
1004+
Assert.Equal("[ValueCollection: 4]", i.ValueString);
1005+
},
1006+
(i) =>
1007+
{
1008+
Assert.Equal("SyncRoot", i.Name);
1009+
#if CoreCLR
1010+
Assert.Equal("[Hashtable: 4]", i.ValueString);
1011+
#else
1012+
Assert.Equal("[Object]", i.ValueString);
1013+
#endif
1014+
},
1015+
(i) =>
1016+
{
1017+
Assert.Equal("Count", i.Name);
1018+
Assert.Equal("4", i.ValueString);
1019+
});
9731020
}
9741021

9751022
[Fact]
@@ -996,8 +1043,28 @@ await GetVariables(VariableContainerDetails.ScriptScopeName).ConfigureAwait(true
9961043
Assert.Empty(rawDetailsView.Type);
9971044
Assert.Empty(rawDetailsView.ValueString);
9981045
VariableDetailsBase[] rawViewChildren = rawDetailsView.GetChildren(NullLogger.Instance);
999-
Assert.Equal(4, rawViewChildren.Length);
1000-
Assert.NotNull(Array.Find(rawViewChildren, v => v.Name == "Comparer"));
1046+
Assert.Collection(rawViewChildren,
1047+
(i) =>
1048+
{
1049+
Assert.Equal("Count", i.Name);
1050+
Assert.Equal("4", i.ValueString);
1051+
},
1052+
(i) =>
1053+
{
1054+
Assert.Equal("Comparer", i.Name);
1055+
Assert.Equal("[GenericComparer`1]", i.ValueString);
1056+
},
1057+
(i) =>
1058+
{
1059+
Assert.Equal("Keys", i.Name);
1060+
Assert.Equal("[KeyCollection: 4]", i.ValueString);
1061+
},
1062+
(i) =>
1063+
{
1064+
Assert.Equal("Values", i.Name);
1065+
Assert.Equal("[ValueCollection: 4]", i.ValueString);
1066+
}
1067+
);
10011068
}
10021069

10031070
[Fact]
@@ -1029,8 +1096,8 @@ await debugService.SetLineBreakpointsAsync(
10291096

10301097
// Verifies fix for issue #86, $proc = Get-Process foo displays just the ETS property set
10311098
// and not all process properties.
1032-
[Fact(Skip = "Length of child vars is wrong now")]
1033-
public async Task DebuggerVariableProcessObjDisplaysCorrectly()
1099+
[Fact]
1100+
public async Task DebuggerVariableProcessObjectDisplaysCorrectly()
10341101
{
10351102
await debugService.SetLineBreakpointsAsync(
10361103
variableScriptFile,
@@ -1049,7 +1116,33 @@ await debugService.SetLineBreakpointsAsync(
10491116
Assert.True(var.IsExpandable);
10501117

10511118
VariableDetailsBase[] childVars = await debugService.GetVariables(var.Id, CancellationToken.None).ConfigureAwait(true);
1052-
Assert.Equal(53, childVars.Length);
1119+
Assert.Contains(childVars, i => i.Name is "Name");
1120+
Assert.Contains(childVars, i => i.Name is "Handles");
1121+
#if CoreCLR
1122+
Assert.Contains(childVars, i => i.Name is "CommandLine");
1123+
Assert.Contains(childVars, i => i.Name is "ExitCode");
1124+
Assert.Contains(childVars, i => i.Name is "HasExited" && i.ValueString is "$false");
1125+
#endif
1126+
Assert.Contains(childVars, i => i.Name is "Id");
1127+
}
1128+
1129+
[Fact]
1130+
public async Task DebuggerVariableFileObjectDisplaysCorrectly()
1131+
{
1132+
await debugService.SetCommandBreakpointsAsync(
1133+
new[] { CommandBreakpointDetails.Create("Write-Host") }).ConfigureAwait(true);
1134+
1135+
ScriptFile testScript = GetDebugScript("GetChildItemTest.ps1");
1136+
Task _ = ExecuteScriptFileAsync(testScript.FilePath);
1137+
AssertDebuggerStopped(testScript.FilePath, 2);
1138+
1139+
VariableDetailsBase[] variables = await GetVariables(VariableContainerDetails.LocalScopeName).ConfigureAwait(true);
1140+
VariableDetailsBase var = Array.Find(variables, v => v.Name == "$file");
1141+
VariableDetailsBase[] childVars = await debugService.GetVariables(var.Id, CancellationToken.None).ConfigureAwait(true);
1142+
Assert.Contains(childVars, i => i.Name is "PSPath");
1143+
Assert.Contains(childVars, i => i.Name is "PSProvider" && i.ValueString is "Microsoft.PowerShell.Core\\FileSystem");
1144+
Assert.Contains(childVars, i => i.Name is "Exists" && i.ValueString is "$true");
1145+
Assert.Contains(childVars, i => i.Name is "LastAccessTime");
10531146
}
10541147
}
10551148
}

0 commit comments

Comments
 (0)