Skip to content

Commit 48481da

Browse files
Fix various bugs for how retry and test host controllers start child processes by @Youssef1313 in #6462 (backport to rel/3.10) (#6463)
Co-authored-by: Youssef1313 <youssefvictor00@gmail.com>
1 parent bb3fa45 commit 48481da

File tree

3 files changed

+118
-16
lines changed

3 files changed

+118
-16
lines changed

src/Platform/Microsoft.Testing.Extensions.Retry/RetryOrchestrator.cs

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -169,23 +169,23 @@ public async Task<int> OrchestrateTestHostExecutionAsync()
169169
finalArguments.Add($"--{RetryCommandLineOptionsProvider.RetryFailedTestsPipeNameOptionName}");
170170
finalArguments.Add(retryFailedTestsPipeServer.PipeName);
171171

172-
// Prepare the process start
173-
ProcessStartInfo processStartInfo = new()
172+
#if NET8_0_OR_GREATER
173+
List<string> arguments = finalArguments;
174+
#else
175+
var builder = new StringBuilder();
176+
foreach (string arg in finalArguments)
174177
{
175-
FileName = executableInfo.FilePath,
176-
#if !NETCOREAPP
177-
UseShellExecute = false,
178+
PasteArguments.AppendArgument(builder, arg);
179+
}
180+
181+
string arguments = builder.ToString();
178182
#endif
179-
};
180183

181-
foreach (string argument in finalArguments)
184+
// Prepare the process start
185+
ProcessStartInfo processStartInfo = new(executableInfo.FilePath, arguments)
182186
{
183-
#if !NETCOREAPP
184-
processStartInfo.Arguments += argument + " ";
185-
#else
186-
processStartInfo.ArgumentList.Add(argument);
187-
#endif
188-
}
187+
UseShellExecute = false,
188+
};
189189

190190
await logger.LogDebugAsync($"Starting test host process, attempt {attemptCount}/{userMaxRetryCount}").ConfigureAwait(false);
191191
IProcess testHostProcess = _serviceProvider.GetProcessHandler().Start(processStartInfo)
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
namespace Microsoft.Testing.Platform.Helpers;
5+
6+
// https://github.com/dotnet/runtime/blob/019c8d72effbdb4f69564695f1df7c4417ec060d/src/libraries/System.Private.CoreLib/src/System/PasteArguments.cs
7+
internal static partial class PasteArguments
8+
{
9+
// WARNING: Don't change the signature of this internal method. It's exposed via IVT to other assemblies.
10+
// Renaming it is a BREAKING CHANGE.
11+
internal static void AppendArgument(StringBuilder stringBuilder, string argument)
12+
{
13+
if (stringBuilder.Length != 0)
14+
{
15+
stringBuilder.Append(' ');
16+
}
17+
18+
// Parsing rules for non-argv[0] arguments:
19+
// - Backslash is a normal character except followed by a quote.
20+
// - 2N backslashes followed by a quote ==> N literal backslashes followed by unescaped quote
21+
// - 2N+1 backslashes followed by a quote ==> N literal backslashes followed by a literal quote
22+
// - Parsing stops at first whitespace outside of quoted region.
23+
// - (post 2008 rule): A closing quote followed by another quote ==> literal quote, and parsing remains in quoting mode.
24+
if (argument.Length != 0 && ContainsNoWhitespaceOrQuotes(argument))
25+
{
26+
// Simple case - no quoting or changes needed.
27+
stringBuilder.Append(argument);
28+
}
29+
else
30+
{
31+
stringBuilder.Append(Quote);
32+
int idx = 0;
33+
while (idx < argument.Length)
34+
{
35+
char c = argument[idx++];
36+
if (c == Backslash)
37+
{
38+
int numBackSlash = 1;
39+
while (idx < argument.Length && argument[idx] == Backslash)
40+
{
41+
idx++;
42+
numBackSlash++;
43+
}
44+
45+
if (idx == argument.Length)
46+
{
47+
// We'll emit an end quote after this so must double the number of backslashes.
48+
stringBuilder.Append(Backslash, numBackSlash * 2);
49+
}
50+
else if (argument[idx] == Quote)
51+
{
52+
// Backslashes will be followed by a quote. Must double the number of backslashes.
53+
stringBuilder.Append(Backslash, (numBackSlash * 2) + 1);
54+
stringBuilder.Append(Quote);
55+
idx++;
56+
}
57+
else
58+
{
59+
// Backslash will not be followed by a quote, so emit as normal characters.
60+
stringBuilder.Append(Backslash, numBackSlash);
61+
}
62+
63+
continue;
64+
}
65+
66+
if (c == Quote)
67+
{
68+
// Escape the quote so it appears as a literal. This also guarantees that we won't end up generating a closing quote followed
69+
// by another quote (which parses differently pre-2008 vs. post-2008.)
70+
stringBuilder.Append(Backslash);
71+
stringBuilder.Append(Quote);
72+
continue;
73+
}
74+
75+
stringBuilder.Append(c);
76+
}
77+
78+
stringBuilder.Append(Quote);
79+
}
80+
}
81+
82+
private static bool ContainsNoWhitespaceOrQuotes(string s)
83+
{
84+
for (int i = 0; i < s.Length; i++)
85+
{
86+
char c = s[i];
87+
if (char.IsWhiteSpace(c) || c == Quote)
88+
{
89+
return false;
90+
}
91+
}
92+
93+
return true;
94+
}
95+
96+
private const char Quote = '\"';
97+
private const char Backslash = '\\';
98+
}

src/Platform/Microsoft.Testing.Platform/Hosts/TestHostControllersTestHost.cs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,13 @@ protected override async Task<int> InternalRunAsync()
104104
#if NET8_0_OR_GREATER
105105
IEnumerable<string> arguments = partialCommandLine;
106106
#else
107-
string arguments = string.Join(' ', partialCommandLine);
107+
var builder = new StringBuilder();
108+
foreach (string arg in partialCommandLine)
109+
{
110+
PasteArguments.AppendArgument(builder, arg);
111+
}
112+
113+
string arguments = builder.ToString();
108114
#endif
109115

110116
#pragma warning disable CA1416 // Validate platform compatibility
@@ -119,9 +125,7 @@ protected override async Task<int> InternalRunAsync()
119125
{ $"{EnvironmentVariableConstants.TESTINGPLATFORM_TESTHOSTCONTROLLER_SKIPEXTENSION}_{currentPid}", "1" },
120126
{ $"{EnvironmentVariableConstants.TESTINGPLATFORM_TESTHOSTCONTROLLER_PIPENAME}_{currentPid}", testHostControllerIpc.PipeName.Name },
121127
},
122-
#if !NETCOREAPP
123128
UseShellExecute = false,
124-
#endif
125129
};
126130
#pragma warning restore CA1416
127131

0 commit comments

Comments
 (0)