Skip to content

Commit 149f5ac

Browse files
Merge pull request #1635 from PowerShell/andschwa/debugservicetests
Re-enable `DebugServiceTests` suite
2 parents f1c4ae5 + f14163d commit 149f5ac

File tree

15 files changed

+519
-802
lines changed

15 files changed

+519
-802
lines changed

PowerShellEditorServices.build.ps1

+4
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,10 @@ task TestServer TestServerWinPS,TestServerPS7,TestServerPS72
238238

239239
task TestServerWinPS -If (-not $script:IsNix) {
240240
Set-Location .\test\PowerShellEditorServices.Test\
241+
# TODO: See https://github.com/dotnet/sdk/issues/18353 for x64 test host
242+
# that is debuggable! If architecture is added, the assembly path gets an
243+
# additional folder, necesstiating fixes to find the commands definition
244+
# file and test files.
241245
exec { & $script:dotnetExe $script:dotnetTestArgs $script:NetRuntime.Desktop }
242246
}
243247

src/PowerShellEditorServices/Services/DebugAdapter/DebugEventHandlerService.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) Microsoft Corporation.
1+
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

44
using System.Management.Automation;
@@ -131,8 +131,8 @@ private void OnDebuggerResuming(object sender, DebuggerResumingEventArgs e)
131131
_debugAdapterServer.SendNotification(EventNames.Continued,
132132
new ContinuedEvent
133133
{
134-
AllThreadsContinued = true,
135134
ThreadId = ThreadsHandler.PipelineThread.Id,
135+
AllThreadsContinued = true,
136136
});
137137
}
138138

@@ -151,7 +151,7 @@ private void OnBreakpointUpdated(object sender, BreakpointUpdatedEventArgs e)
151151
BreakpointDetails.Create(e.Breakpoint, e.UpdateType)
152152
);
153153

154-
string reason = (e.UpdateType) switch {
154+
string reason = e.UpdateType switch {
155155
BreakpointUpdateType.Set => BreakpointEventReason.New,
156156
BreakpointUpdateType.Removed => BreakpointEventReason.Removed,
157157
BreakpointUpdateType.Enabled => BreakpointEventReason.Changed,

src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs

+32-71
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ internal class DebugService
3535
private readonly ILogger _logger;
3636
private readonly IInternalPowerShellExecutionService _executionService;
3737
private readonly BreakpointService _breakpointService;
38-
private readonly RemoteFileManagerService remoteFileManager;
38+
private readonly RemoteFileManagerService _remoteFileManager;
3939

4040
private readonly PsesInternalHost _psesHost;
4141

@@ -45,7 +45,7 @@ internal class DebugService
4545
private int nextVariableId;
4646
private string temporaryScriptListingPath;
4747
private List<VariableDetailsBase> variables;
48-
private VariableContainerDetails globalScopeVariables;
48+
internal VariableContainerDetails globalScopeVariables; // Internal for unit testing.
4949
private VariableContainerDetails scriptScopeVariables;
5050
private VariableContainerDetails localScopeVariables;
5151
private StackFrameDetails[] stackFrameDetails;
@@ -80,28 +80,8 @@ internal class DebugService
8080

8181
/// <summary>
8282
/// Initializes a new instance of the DebugService class and uses
83-
/// the given PowerShellContext for all future operations.
83+
/// the given execution service for all future operations.
8484
/// </summary>
85-
/// <param name="powerShellContext">
86-
/// The PowerShellContext to use for all debugging operations.
87-
/// </param>
88-
/// <param name="logger">An ILogger implementation used for writing log messages.</param>
89-
//public DebugService(PowerShellContextService powerShellContext, ILogger logger)
90-
// : this(powerShellContext, null, logger)
91-
//{
92-
//}
93-
94-
/// <summary>
95-
/// Initializes a new instance of the DebugService class and uses
96-
/// the given PowerShellContext for all future operations.
97-
/// </summary>
98-
/// <param name="powerShellContext">
99-
/// The PowerShellContext to use for all debugging operations.
100-
/// </param>
101-
//// <param name = "remoteFileManager" >
102-
//// A RemoteFileManagerService instance to use for accessing files in remote sessions.
103-
//// </param>
104-
/// <param name="logger">An ILogger implementation used for writing log messages.</param>
10585
public DebugService(
10686
IInternalPowerShellExecutionService executionService,
10787
IPowerShellDebugContext debugContext,
@@ -120,8 +100,7 @@ public DebugService(
120100
_debugContext.DebuggerStopped += OnDebuggerStopAsync;
121101
_debugContext.DebuggerResuming += OnDebuggerResuming;
122102
_debugContext.BreakpointUpdated += OnBreakpointUpdated;
123-
124-
this.remoteFileManager = remoteFileManager;
103+
_remoteFileManager = remoteFileManager;
125104

126105
invocationTypeScriptPositionProperty =
127106
typeof(InvocationInfo)
@@ -150,32 +129,27 @@ public async Task<BreakpointDetails[]> SetLineBreakpointsAsync(
150129

151130
string scriptPath = scriptFile.FilePath;
152131
// Make sure we're using the remote script path
153-
if (_psesHost.CurrentRunspace.IsOnRemoteMachine && remoteFileManager is not null)
132+
if (_psesHost.CurrentRunspace.IsOnRemoteMachine && _remoteFileManager is not null)
154133
{
155-
if (!remoteFileManager.IsUnderRemoteTempPath(scriptPath))
134+
if (!_remoteFileManager.IsUnderRemoteTempPath(scriptPath))
156135
{
157136
_logger.LogTrace($"Could not set breakpoints for local path '{scriptPath}' in a remote session.");
158-
159137
return Array.Empty<BreakpointDetails>();
160138
}
161139

162-
string mappedPath = remoteFileManager.GetMappedPath(scriptPath, _psesHost.CurrentRunspace);
163-
164-
scriptPath = mappedPath;
140+
scriptPath = _remoteFileManager.GetMappedPath(scriptPath, _psesHost.CurrentRunspace);
165141
}
166-
else if (temporaryScriptListingPath is not null
167-
&& temporaryScriptListingPath.Equals(scriptPath, StringComparison.CurrentCultureIgnoreCase))
142+
else if (temporaryScriptListingPath?.Equals(scriptPath, StringComparison.CurrentCultureIgnoreCase) == true)
168143
{
169144
_logger.LogTrace($"Could not set breakpoint on temporary script listing path '{scriptPath}'.");
170-
171145
return Array.Empty<BreakpointDetails>();
172146
}
173147

174148
// Fix for issue #123 - file paths that contain wildcard chars [ and ] need to
175149
// quoted and have those wildcard chars escaped.
176150
string escapedScriptPath = PathUtils.WildcardEscapePath(scriptPath);
177151

178-
if (dscBreakpoints is null || !dscBreakpoints.IsDscResourcePath(escapedScriptPath))
152+
if (dscBreakpoints?.IsDscResourcePath(escapedScriptPath) != true)
179153
{
180154
if (clearExisting)
181155
{
@@ -185,10 +159,8 @@ public async Task<BreakpointDetails[]> SetLineBreakpointsAsync(
185159
return (await _breakpointService.SetBreakpointsAsync(escapedScriptPath, breakpoints).ConfigureAwait(false)).ToArray();
186160
}
187161

188-
return await dscBreakpoints.SetLineBreakpointsAsync(
189-
_executionService,
190-
escapedScriptPath,
191-
breakpoints)
162+
return await dscBreakpoints
163+
.SetLineBreakpointsAsync(_executionService, escapedScriptPath, breakpoints)
192164
.ConfigureAwait(false);
193165
}
194166

@@ -362,7 +334,7 @@ public VariableDetailsBase GetVariableFromExpression(string variableExpression)
362334
variableName,
363335
StringComparison.CurrentCultureIgnoreCase));
364336

365-
if (resolvedVariable is not null && resolvedVariable.IsExpandable)
337+
if (resolvedVariable?.IsExpandable == true)
366338
{
367339
// Continue by searching in this variable's children.
368340
variableList = GetVariables(resolvedVariable.Id);
@@ -383,6 +355,7 @@ public VariableDetailsBase GetVariableFromExpression(string variableExpression)
383355
/// <param name="value">The new string value. This value must not be null. If you want to set the variable to $null
384356
/// pass in the string "$null".</param>
385357
/// <returns>The string representation of the value the variable was set to.</returns>
358+
/// <exception cref="InvalidPowerShellExpressionException"></exception>
386359
public async Task<string> SetVariableAsync(int variableContainerReferenceId, string name, string value)
387360
{
388361
Validate.IsNotNull(nameof(name), name);
@@ -476,20 +449,18 @@ public async Task<string> SetVariableAsync(int variableContainerReferenceId, str
476449
{
477450
_logger.LogTrace($"Setting variable '{name}' using conversion to value: {expressionResult ?? "<null>"}");
478451

479-
psVariable.Value = await _executionService.ExecuteDelegateAsync<object>(
452+
psVariable.Value = await _executionService.ExecuteDelegateAsync(
480453
"PS debugger argument converter",
481454
ExecutionOptions.Default,
482-
(pwsh, cancellationToken) =>
455+
(pwsh, _) =>
483456
{
484457
var engineIntrinsics = (EngineIntrinsics)pwsh.Runspace.SessionStateProxy.GetVariable("ExecutionContext");
485458

486459
// TODO: This is almost (but not quite) the same as LanguagePrimitives.Convert(), which does not require the pipeline thread.
487460
// We should investigate changing it.
488461
return argTypeConverterAttr.Transform(engineIntrinsics, expressionResult);
489-
490462
},
491463
CancellationToken.None).ConfigureAwait(false);
492-
493464
}
494465
else
495466
{
@@ -634,17 +605,14 @@ private async Task FetchStackFramesAndVariablesAsync(string scriptNameOverride)
634605
nextVariableId = VariableDetailsBase.FirstVariableId;
635606
variables = new List<VariableDetailsBase>
636607
{
637-
638608
// Create a dummy variable for index 0, should never see this.
639609
new VariableDetails("Dummy", null)
640610
};
641611

642612
// Must retrieve in order of broadest to narrowest scope for efficient
643613
// deduplication: global, script, local.
644614
globalScopeVariables = await FetchVariableContainerAsync(VariableContainerDetails.GlobalScopeName).ConfigureAwait(false);
645-
646615
scriptScopeVariables = await FetchVariableContainerAsync(VariableContainerDetails.ScriptScopeName).ConfigureAwait(false);
647-
648616
localScopeVariables = await FetchVariableContainerAsync(VariableContainerDetails.LocalScopeName).ConfigureAwait(false);
649617

650618
await FetchStackFramesAsync(scriptNameOverride).ConfigureAwait(false);
@@ -662,9 +630,7 @@ private Task<VariableContainerDetails> FetchVariableContainerAsync(string scope)
662630

663631
private async Task<VariableContainerDetails> FetchVariableContainerAsync(string scope, bool autoVarsOnly)
664632
{
665-
PSCommand psCommand = new PSCommand()
666-
.AddCommand("Get-Variable")
667-
.AddParameter("Scope", scope);
633+
PSCommand psCommand = new PSCommand().AddCommand("Get-Variable").AddParameter("Scope", scope);
668634

669635
var scopeVariableContainer = new VariableContainerDetails(nextVariableId++, "Scope: " + scope);
670636
variables.Add(scopeVariableContainer);
@@ -674,17 +640,13 @@ private async Task<VariableContainerDetails> FetchVariableContainerAsync(string
674640
{
675641
results = await _executionService.ExecutePSCommandAsync<PSObject>(psCommand, CancellationToken.None).ConfigureAwait(false);
676642
}
677-
catch (CmdletInvocationException ex)
643+
// It's possible to be asked to run `Get-Variable -Scope N` where N is a number that
644+
// exceeds the available scopes. In this case, the command throws this exception, but
645+
// there's nothing we can do about it, nor can we know the number of scopes that exist,
646+
// and we shouldn't crash the debugger, so we just return no results instead. All other
647+
// exceptions should be thrown again.
648+
catch (CmdletInvocationException ex) when (ex.ErrorRecord.CategoryInfo.Reason.Equals("PSArgumentOutOfRangeException"))
678649
{
679-
// It's possible to be asked to run `Get-Variable -Scope N` where N is a number that
680-
// exceeds the available scopes. In this case, the command throws this exception,
681-
// but there's nothing we can do about it, nor can we know the number of scopes that
682-
// exist, and we shouldn't crash the debugger, so we just return no results instead.
683-
// All other exceptions should be thrown again.
684-
if (!ex.ErrorRecord.CategoryInfo.Reason.Equals("PSArgumentOutOfRangeException"))
685-
{
686-
throw;
687-
}
688650
results = null;
689651
}
690652

@@ -749,8 +711,8 @@ private static bool ShouldAddAsVariable(VariableInfo variableInfo)
749711
{
750712
// Filter built-in constant or readonly variables like $true, $false, $null, etc.
751713
ScopedItemOptions variableScope = variableInfo.Variable.Options;
752-
var constantAllScope = ScopedItemOptions.AllScope | ScopedItemOptions.Constant;
753-
var readonlyAllScope = ScopedItemOptions.AllScope | ScopedItemOptions.ReadOnly;
714+
const ScopedItemOptions constantAllScope = ScopedItemOptions.AllScope | ScopedItemOptions.Constant;
715+
const ScopedItemOptions readonlyAllScope = ScopedItemOptions.AllScope | ScopedItemOptions.ReadOnly;
754716
if (((variableScope & constantAllScope) == constantAllScope)
755717
|| ((variableScope & readonlyAllScope) == readonlyAllScope))
756718
{
@@ -909,11 +871,11 @@ private async Task FetchStackFramesAsync(string scriptNameOverride)
909871
stackFrameDetailsEntry.ScriptPath = scriptNameOverride;
910872
}
911873
else if (isOnRemoteMachine
912-
&& remoteFileManager is not null
874+
&& _remoteFileManager is not null
913875
&& !string.Equals(stackFrameScriptPath, StackFrameDetails.NoFileScriptPath))
914876
{
915877
stackFrameDetailsEntry.ScriptPath =
916-
remoteFileManager.GetMappedPath(stackFrameScriptPath, _psesHost.CurrentRunspace);
878+
_remoteFileManager.GetMappedPath(stackFrameScriptPath, _psesHost.CurrentRunspace);
917879
}
918880

919881
stackFrameDetailList.Add(stackFrameDetailsEntry);
@@ -956,7 +918,7 @@ internal async void OnDebuggerStopAsync(object sender, DebuggerStopEventArgs e)
956918
string localScriptPath = e.InvocationInfo.ScriptName;
957919

958920
// If there's no ScriptName, get the "list" of the current source
959-
if (remoteFileManager is not null && string.IsNullOrEmpty(localScriptPath))
921+
if (_remoteFileManager is not null && string.IsNullOrEmpty(localScriptPath))
960922
{
961923
// Get the current script listing and create the buffer
962924
PSCommand command = new PSCommand().AddScript($"list 1 {int.MaxValue}");
@@ -977,7 +939,7 @@ await _executionService.ExecutePSCommandAsync<PSObject>(
977939
.Where(s => s is not null));
978940

979941
temporaryScriptListingPath =
980-
remoteFileManager.CreateTemporaryFile(
942+
_remoteFileManager.CreateTemporaryFile(
981943
$"[{_psesHost.CurrentRunspace.SessionDetails.ComputerName}] {TemporaryScriptFileName}",
982944
scriptListing,
983945
_psesHost.CurrentRunspace);
@@ -1000,19 +962,18 @@ await _executionService.ExecutePSCommandAsync<PSObject>(
1000962
// If this is a remote connection and the debugger stopped at a line
1001963
// in a script file, get the file contents
1002964
if (_psesHost.CurrentRunspace.IsOnRemoteMachine
1003-
&& remoteFileManager is not null
965+
&& _remoteFileManager is not null
1004966
&& !noScriptName)
1005967
{
1006968
localScriptPath =
1007-
await remoteFileManager.FetchRemoteFileAsync(
969+
await _remoteFileManager.FetchRemoteFileAsync(
1008970
e.InvocationInfo.ScriptName,
1009971
_psesHost.CurrentRunspace).ConfigureAwait(false);
1010972
}
1011973

1012974
if (stackFrameDetails.Length > 0)
1013975
{
1014976
// Augment the top stack frame with details from the stop event
1015-
1016977
if (invocationTypeScriptPositionProperty.GetValue(e.InvocationInfo) is IScriptExtent scriptExtent)
1017978
{
1018979
stackFrameDetails[0].StartLineNumber = scriptExtent.StartLineNumber;
@@ -1054,9 +1015,9 @@ private void OnBreakpointUpdated(object sender, BreakpointUpdatedEventArgs e)
10541015
// TODO: This could be either a path or a script block!
10551016
string scriptPath = lineBreakpoint.Script;
10561017
if (_psesHost.CurrentRunspace.IsOnRemoteMachine
1057-
&& remoteFileManager is not null)
1018+
&& _remoteFileManager is not null)
10581019
{
1059-
string mappedPath = remoteFileManager.GetMappedPath(scriptPath, _psesHost.CurrentRunspace);
1020+
string mappedPath = _remoteFileManager.GetMappedPath(scriptPath, _psesHost.CurrentRunspace);
10601021

10611022
if (mappedPath is null)
10621023
{

src/PowerShellEditorServices/Services/DebugAdapter/DebugStateService.cs

+1-2
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,7 @@ internal void ReleaseSetBreakpointHandle()
4646

4747
internal async Task WaitForSetBreakpointHandleAsync()
4848
{
49-
await _setBreakpointInProgressHandle.WaitAsync()
50-
.ConfigureAwait(continueOnCapturedContext: false);
49+
await _setBreakpointInProgressHandle.WaitAsync().ConfigureAwait(false);
5150
}
5251
}
5352
}

src/PowerShellEditorServices/Services/DebugAdapter/Debugging/DebuggerStoppedEventArgs.cs

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
// Copyright (c) Microsoft Corporation.
1+
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4+
using System.Management.Automation;
5+
using System.Security.Cryptography;
46
using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace;
57
using Microsoft.PowerShell.EditorServices.Utility;
6-
using System.Management.Automation;
78

89
namespace Microsoft.PowerShell.EditorServices.Services.DebugAdapter
910
{

0 commit comments

Comments
 (0)