Skip to content

Commit e6c0083

Browse files
Merge pull request #1861 from PowerShell/andschwa/improve-untitled-test
Add `DebuggerBreaksInUntitledScript` unit test
2 parents 932d3b2 + eec176d commit e6c0083

File tree

3 files changed

+52
-9
lines changed

3 files changed

+52
-9
lines changed

src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs

+9-5
Original file line numberDiff line numberDiff line change
@@ -102,14 +102,17 @@ public Task<ConfigurationDoneResponse> Handle(ConfigurationDoneArguments request
102102
return Task.FromResult(new ConfigurationDoneResponse());
103103
}
104104

105-
private async Task LaunchScriptAsync(string scriptToLaunch)
105+
// NOTE: We test this function in `DebugServiceTests` so it both needs to be internal, and
106+
// use conditional-access on `_debugStateService` and `_debugAdapterServer` as its not set
107+
// by tests.
108+
internal async Task LaunchScriptAsync(string scriptToLaunch)
106109
{
107110
PSCommand command;
108111
if (System.IO.File.Exists(scriptToLaunch))
109112
{
110113
// For a saved file we just execute its path (after escaping it).
111114
command = PSCommandHelpers.BuildDotSourceCommandWithArguments(
112-
string.Concat('"', scriptToLaunch, '"'), _debugStateService.Arguments);
115+
string.Concat('"', scriptToLaunch, '"'), _debugStateService?.Arguments);
113116
}
114117
else // It's a URI to an untitled script, or a raw script.
115118
{
@@ -135,7 +138,7 @@ private async Task LaunchScriptAsync(string scriptToLaunch)
135138
// on each invocation, so passing the user's arguments directly in the initial
136139
// `AddScript` surprisingly works.
137140
command = PSCommandHelpers
138-
.BuildDotSourceCommandWithArguments("$args[0]", _debugStateService.Arguments)
141+
.BuildDotSourceCommandWithArguments("$args[0]", _debugStateService?.Arguments)
139142
.AddArgument(ast.GetScriptBlock());
140143
}
141144
else
@@ -148,15 +151,16 @@ private async Task LaunchScriptAsync(string scriptToLaunch)
148151
"{" + System.Environment.NewLine,
149152
isScriptFile ? untitledScript.Contents : scriptToLaunch,
150153
System.Environment.NewLine + "}"),
151-
_debugStateService.Arguments);
154+
_debugStateService?.Arguments);
152155
}
153156
}
154157

155158
await _executionService.ExecutePSCommandAsync(
156159
command,
157160
CancellationToken.None,
158161
s_debuggerExecutionOptions).ConfigureAwait(false);
159-
_debugAdapterServer.SendNotification(EventNames.Terminated);
162+
163+
_debugAdapterServer?.SendNotification(EventNames.Terminated);
160164
}
161165
}
162166
}

test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs

+8-4
Original file line numberDiff line numberDiff line change
@@ -305,9 +305,8 @@ public async Task CanStepPastSystemWindowsForms()
305305
[Fact]
306306
public async Task CanLaunchScriptWithCommentedLastLineAsync()
307307
{
308-
string script = GenerateScriptFromLoggingStatements("a log statement") + "# a comment at the end";
309-
Assert.Contains(Environment.NewLine + "# a comment", script);
310-
Assert.EndsWith("at the end", script);
308+
string script = GenerateScriptFromLoggingStatements("$($MyInvocation.Line)") + "# a comment at the end";
309+
Assert.EndsWith(Environment.NewLine + "# a comment at the end", script);
311310

312311
// NOTE: This is horribly complicated, but the "script" parameter here is assigned to
313312
// PsesLaunchRequestArguments.Script, which is then assigned to
@@ -317,8 +316,13 @@ public async Task CanLaunchScriptWithCommentedLastLineAsync()
317316

318317
ConfigurationDoneResponse configDoneResponse = await PsesDebugAdapterClient.RequestConfigurationDone(new ConfigurationDoneArguments()).ConfigureAwait(true);
319318
Assert.NotNull(configDoneResponse);
319+
// We can check that the script was invoked as expected, which is to dot-source a script
320+
// block with the contents surrounded by newlines. While we can't check that the last
321+
// line was a curly brace by itself, we did check that the contents ended with a
322+
// comment, so if this output exists then the bug did not recur.
320323
Assert.Collection(await GetLog().ConfigureAwait(true),
321-
(i) => Assert.Equal("a log statement", i));
324+
(i) => Assert.Equal(". {", i),
325+
(i) => Assert.Equal("", i));
322326
}
323327

324328
[SkippableFact]

test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs

+35
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using System.Threading;
1111
using System.Threading.Tasks;
1212
using Microsoft.Extensions.Logging.Abstractions;
13+
using Microsoft.PowerShell.EditorServices.Handlers;
1314
using Microsoft.PowerShell.EditorServices.Services;
1415
using Microsoft.PowerShell.EditorServices.Services.DebugAdapter;
1516
using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host;
@@ -521,6 +522,40 @@ await debugService.SetCommandBreakpointsAsync(
521522
Assert.Equal("\"True > \"", prompt.ValueString);
522523
}
523524

525+
[SkippableFact]
526+
public async Task DebuggerBreaksInUntitledScript()
527+
{
528+
Skip.IfNot(VersionUtils.PSEdition == "Core", "Untitled script breakpoints only supported in PowerShell Core");
529+
const string contents = "Write-Output $($MyInvocation.Line)";
530+
const string scriptPath = "untitled:Untitled-1";
531+
Assert.True(ScriptFile.IsUntitledPath(scriptPath));
532+
ScriptFile scriptFile = workspace.GetFileBuffer(scriptPath, contents);
533+
Assert.Equal(scriptFile.DocumentUri, scriptPath);
534+
Assert.Equal(scriptFile.Contents, contents);
535+
Assert.True(workspace.TryGetFile(scriptPath, out ScriptFile _));
536+
537+
await debugService.SetCommandBreakpointsAsync(
538+
new[] { CommandBreakpointDetails.Create("Write-Output") }).ConfigureAwait(true);
539+
540+
ConfigurationDoneHandler configurationDoneHandler = new(
541+
NullLoggerFactory.Instance, null, debugService, null, null, psesHost, workspace, null, psesHost);
542+
543+
Task _ = configurationDoneHandler.LaunchScriptAsync(scriptPath);
544+
AssertDebuggerStopped(scriptPath, 1);
545+
546+
VariableDetailsBase[] variables = GetVariables(VariableContainerDetails.CommandVariablesName);
547+
VariableDetailsBase myInvocation = Array.Find(variables, v => v.Name == "$MyInvocation");
548+
Assert.NotNull(myInvocation);
549+
Assert.True(myInvocation.IsExpandable);
550+
551+
// Here we're asserting that our hacky workaround to support breakpoints in untitled
552+
// scripts is working, namely that we're actually dot-sourcing our first argument, which
553+
// should be a cached script block. See the `LaunchScriptAsync` for more info.
554+
VariableDetailsBase[] myInvocationChildren = debugService.GetVariables(myInvocation.Id);
555+
VariableDetailsBase myInvocationLine = Array.Find(myInvocationChildren, v => v.Name == "Line");
556+
Assert.Equal("\". $args[0]\"", myInvocationLine.ValueString);
557+
}
558+
524559
[Fact]
525560
public async Task DebuggerVariableStringDisplaysCorrectly()
526561
{

0 commit comments

Comments
 (0)