Skip to content

Commit d2d325b

Browse files
committed
Add wait to test before attaching
1 parent b8021f4 commit d2d325b

File tree

1 file changed

+63
-11
lines changed

1 file changed

+63
-11
lines changed

test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs

Lines changed: 63 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using System.Text;
1010
using System.Threading;
1111
using System.Threading.Tasks;
12+
using System.Xml.Linq;
1213
using Microsoft.PowerShell.EditorServices.Handlers;
1314
using Nerdbank.Streams;
1415
using OmniSharp.Extensions.DebugAdapter.Client;
@@ -608,6 +609,13 @@ await RunWithAttachableProcess(logStatements, async (filePath, processId) =>
608609

609610
private async Task RunWithAttachableProcess(string[] logStatements, Func<string, int, Task> action)
610611
{
612+
// Ensures that we don't try and attach until the PowerShell proc
613+
// has fully started and is ready to accept the attach request.
614+
string startMarker = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
615+
File.WriteAllText(startMarker, "");
616+
617+
string filePath = NewTestFile(GenerateLoggingScript(logStatements));
618+
611619
// PowerShell has no public API for waiting when Debug-Runspace
612620
// has attached itself to a Runspace. We use this reflection
613621
// hackery to wait until the AvailabilityChanged event is
@@ -616,8 +624,14 @@ private async Task RunWithAttachableProcess(string[] logStatements, Func<string,
616624
// Use https://github.com/PowerShell/PowerShell/pull/25788 once it
617625
// is available.
618626
string scriptEntrypoint = @"
627+
param([string]$StartMarker, [string]$TestScript)
628+
619629
$ErrorActionPreference = 'Stop'
620630
631+
# Removing this file tells the runner that the script has
632+
# started and pwsh is ready to accept the attach request.
633+
Remove-Item -LiteralPath $StartMarker -Force
634+
621635
$debugRunspaceCmd = Get-Command Debug-Runspace -Module Microsoft.PowerShell.Utility
622636
$runspace = [Runspace]::DefaultRunspace
623637
$runspaceBase = [PSObject].Assembly.GetType(
@@ -644,22 +658,44 @@ private async Task RunWithAttachableProcess(string[] logStatements, Func<string,
644658
645659
# Needed to sync breakpoints on WinPS 5.1
646660
Wait-Debugger
647-
";
648661
649-
string filePath = NewTestFile(GenerateLoggingScript(logStatements));
650-
scriptEntrypoint += Environment.NewLine + $"& '{filePath}'";
662+
& $TestScript
651663
652-
// This allows us to ensure the process won't end after the script
653-
// has run. Without this we run the risk that the process ends
654-
// before the debug task completes causing a test failure.
655-
scriptEntrypoint += Environment.NewLine + $"while (Test-Path -Path '{filePath}') {{ Start-Sleep -Seconds 1 }}";
664+
# Keep running until the runner has deleted the test script to
665+
# ensure the process doesn't finish before the test does in
666+
# normal circumstances.
667+
while (Test-Path -Path $TestScript) {
668+
Start-Sleep -Seconds 1
669+
}
670+
";
656671

672+
// Only way to pass args to -EncodedCommand is to use CLIXML with
673+
// -EncodedArguments. Not pretty but the structure isn't too
674+
// complex.
675+
string clixmlNamespace = "http://schemas.microsoft.com/powershell/2004/04";
676+
string clixmlArg = new XDocument(
677+
new XDeclaration("1.0", "utf-16", "yes"),
678+
new XElement(XName.Get("Objs", clixmlNamespace),
679+
new XAttribute("Version", "1.1.0.1"),
680+
new XElement(XName.Get("Obj", clixmlNamespace),
681+
new XAttribute("RefId", "0"),
682+
new XElement(XName.Get("TN", clixmlNamespace),
683+
new XAttribute("RefId", "0"),
684+
new XElement(XName.Get("T", clixmlNamespace), "System.Collections.ArrayList"),
685+
new XElement(XName.Get("T", clixmlNamespace), "System.Object")
686+
),
687+
new XElement(XName.Get("LST", clixmlNamespace),
688+
new XElement(XName.Get("S", clixmlNamespace), startMarker),
689+
new XElement(XName.Get("S", clixmlNamespace), filePath)
690+
)
691+
))).ToString(SaveOptions.DisableFormatting);
692+
string encArgs = Convert.ToBase64String(Encoding.Unicode.GetBytes(clixmlArg));
657693
string encCommand = Convert.ToBase64String(Encoding.Unicode.GetBytes(scriptEntrypoint));
658694

659695
ProcessStartInfo psi = new ProcessStartInfo
660696
{
661697
FileName = PsesStdioLanguageServerProcessHost.PwshExe,
662-
Arguments = $"-NoLogo -NoProfile -EncodedCommand {encCommand}",
698+
Arguments = $"-NoLogo -NoProfile -EncodedCommand {encCommand} -EncodedArguments {encArgs}",
663699
RedirectStandardOutput = true,
664700
RedirectStandardError = true,
665701
UseShellExecute = false,
@@ -691,13 +727,29 @@ private async Task RunWithAttachableProcess(string[] logStatements, Func<string,
691727
psProc.BeginErrorReadLine();
692728

693729
Task procExited = psProc.WaitForExitAsync(debugTaskCts.Token);
694-
Task debugTask = action(filePath, psProc.Id);
730+
Task waitStart = Task.Run(async () =>
731+
{
732+
while (File.Exists(startMarker))
733+
{
734+
await Task.Delay(100, debugTaskCts.Token);
735+
}
736+
});
737+
738+
// Wait for the process to fail or the script to start.
739+
Task finishedTask = await Task.WhenAny(waitStart, procExited);
740+
if (finishedTask == procExited)
741+
{
742+
await procExited;
743+
Assert.Fail("The attached process exited before the PowerShell entrypoint could start.");
744+
}
745+
await waitStart;
695746

696-
Task finishedTask = await Task.WhenAny(procExited, debugTask);
747+
Task debugTask = action(filePath, psProc.Id);
748+
finishedTask = await Task.WhenAny(procExited, debugTask);
697749
if (finishedTask == procExited)
698750
{
699751
await procExited;
700-
Assert.Fail("Attached process exited before the debug task completed.");
752+
Assert.Fail("Attached process exited before the script could start.");
701753
}
702754

703755
await debugTask;

0 commit comments

Comments
 (0)