Skip to content

Commit 991ae4a

Browse files
Support AdditionalMSBuildParameters (dotnet#42251)
1 parent 11bdcef commit 991ae4a

21 files changed

+286
-36
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Microsoft.DotNet.Tools.Test;
5+
6+
internal sealed record class UnknownMessage(int SerializerId) : IRequest;

src/Cli/dotnet/commands/dotnet-test/IPC/NamedPipeBase.cs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,27 @@ public void RegisterSerializer(INamedPipeSerializer namedPipeSerializer, Type ty
2020
_idSerializer.Add(namedPipeSerializer.Id, namedPipeSerializer);
2121
}
2222

23-
protected INamedPipeSerializer GetSerializer(int id)
24-
=> _idSerializer.TryGetValue(id, out object serializer)
25-
? (INamedPipeSerializer)serializer
26-
: throw new ArgumentException((string.Format(
23+
protected INamedPipeSerializer GetSerializer(int id, bool skipUnknownMessages = false)
24+
{
25+
if (_idSerializer.TryGetValue(id, out object serializer))
26+
{
27+
return (INamedPipeSerializer)serializer;
28+
}
29+
else
30+
{
31+
return skipUnknownMessages
32+
? new UnknownMessageSerializer(id)
33+
: throw new ArgumentException((string.Format(
2734
CultureInfo.InvariantCulture,
2835
#if dotnet
2936
LocalizableStrings.NoSerializerRegisteredWithIdErrorMessage,
3037
#else
3138
"No serializer registered with ID '{0}'",
3239
#endif
3340
id)));
41+
}
42+
}
43+
3444

3545
protected INamedPipeSerializer GetSerializer(Type type)
3646
=> _typeSerializer.TryGetValue(type, out object serializer)

src/Cli/dotnet/commands/dotnet-test/IPC/NamedPipeServer.cs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ internal sealed class NamedPipeServer : NamedPipeBase, IServer
1919
private readonly MemoryStream _serializationBuffer = new();
2020
private readonly MemoryStream _messageBuffer = new();
2121
private readonly byte[] _readBuffer = new byte[250000];
22+
private readonly bool _skipUnknownMessages;
2223
private Task _loopTask;
2324
private bool _disposed;
2425

@@ -44,11 +45,24 @@ public NamedPipeServer(
4445
int maxNumberOfServerInstances,
4546
CancellationToken cancellationToken)
4647
{
47-
_namedPipeServerStream = new((PipeName = pipeNameDescription).Name, PipeDirection.InOut, maxNumberOfServerInstances);
48+
_namedPipeServerStream = new((PipeName = pipeNameDescription).Name, PipeDirection.InOut, maxNumberOfServerInstances, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
4849
_callback = callback;
4950
_cancellationToken = cancellationToken;
5051
}
5152

53+
public NamedPipeServer(
54+
PipeNameDescription pipeNameDescription,
55+
Func<IRequest, Task<IResponse>> callback,
56+
int maxNumberOfServerInstances,
57+
CancellationToken cancellationToken,
58+
bool skipUnknownMessages)
59+
{
60+
_namedPipeServerStream = new((PipeName = pipeNameDescription).Name, PipeDirection.InOut, maxNumberOfServerInstances, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
61+
_callback = callback;
62+
_cancellationToken = cancellationToken;
63+
_skipUnknownMessages = skipUnknownMessages;
64+
}
65+
5266
public PipeNameDescription PipeName { get; private set; }
5367

5468
public bool WasConnected { get; private set; }
@@ -135,7 +149,7 @@ private async Task InternalLoopAsync(CancellationToken cancellationToken)
135149
int serializerId = BitConverter.ToInt32(_messageBuffer.GetBuffer(), 0);
136150

137151
// Get the serializer
138-
INamedPipeSerializer requestNamedPipeSerializer = GetSerializer(serializerId);
152+
INamedPipeSerializer requestNamedPipeSerializer = GetSerializer(serializerId, _skipUnknownMessages);
139153

140154
// Deserialize the message
141155
_messageBuffer.Position += sizeof(int); // Skip the serializer id
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Microsoft.DotNet.Tools.Test;
5+
6+
internal sealed class UnknownMessageSerializer : BaseSerializer, INamedPipeSerializer
7+
{
8+
public int Id { get; }
9+
10+
public UnknownMessageSerializer(int SerializerId) => Id = SerializerId;
11+
12+
public object Deserialize(Stream _)
13+
{
14+
return new UnknownMessage(Id);
15+
}
16+
17+
public void Serialize(object _, Stream stream)
18+
{
19+
WriteInt(stream, Id);
20+
}
21+
}

src/Cli/dotnet/commands/dotnet-test/LocalizableStrings.resx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,4 +291,10 @@ Examples:
291291
<data name="NoSerializerRegisteredWithTypeErrorMessage" xml:space="preserve">
292292
<value>No serializer registered with type '{0}'</value>
293293
</data>
294+
<data name="CmdMaxParallelTestModulesDescription" xml:space="preserve">
295+
<value>The max number of test modules that can run in parallel.</value>
296+
</data>
297+
<data name="CmdAdditionalMSBuildParametersDescription" xml:space="preserve">
298+
<value>The additional msbuild parameters to pass.</value>
299+
</data>
294300
</root>

src/Cli/dotnet/commands/dotnet-test/TestApplication.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,10 @@ public async Task RunAsync()
4040
Arguments = BuildArgs(isDll)
4141
};
4242

43-
VSTestTrace.SafeWriteTrace(() => $"Updated args: {processStartInfo.Arguments}");
43+
if (VSTestTrace.TraceEnabled)
44+
{
45+
VSTestTrace.SafeWriteTrace(() => $"Updated args: {processStartInfo.Arguments}");
46+
}
4447

4548
await Process.Start(processStartInfo).WaitForExitAsync();
4649
}
@@ -61,7 +64,10 @@ public async Task RunHelpAsync()
6164
Arguments = BuildHelpArgs(isDll)
6265
};
6366

64-
VSTestTrace.SafeWriteTrace(() => $"Updated args: {processStartInfo.Arguments}");
67+
if (VSTestTrace.TraceEnabled)
68+
{
69+
VSTestTrace.SafeWriteTrace(() => $"Updated args: {processStartInfo.Arguments}");
70+
}
6571

6672
await Process.Start(processStartInfo).WaitForExitAsync();
6773
}

src/Cli/dotnet/commands/dotnet-test/TestCommandParser.cs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,15 @@ internal static class TestCommandParser
1111
{
1212
public static readonly string DocsLink = "https://aka.ms/dotnet-test";
1313

14-
public static readonly CliOption<string> DegreeOfParallelism = new ForwardedOption<string>("--degree-of-parallelism", "-dop")
14+
public static readonly CliOption<string> MaxParallelTestModules = new ForwardedOption<string>("--max-parallel-test-modules", "-mptm")
1515
{
16-
Description = "degree of parallelism",
17-
HelpName = "dop"
18-
}.ForwardAs("-property:VSTestNoLogo=true");
16+
Description = LocalizableStrings.CmdMaxParallelTestModulesDescription,
17+
};
18+
19+
public static readonly CliOption<string> AdditionalMSBuildParameters = new ForwardedOption<string>("--additional-msbuild-parameters")
20+
{
21+
Description = LocalizableStrings.CmdAdditionalMSBuildParametersDescription,
22+
};
1923

2024
public static readonly CliOption<string> SettingsOption = new ForwardedOption<string>("--settings", "-s")
2125
{
@@ -197,7 +201,8 @@ private static CliCommand GetTestingPlatformCliCommand()
197201
{
198202
var command = new TestingPlatformCommand("test");
199203
command.SetAction((parseResult) => command.Run(parseResult));
200-
command.Options.Add(DegreeOfParallelism);
204+
command.Options.Add(MaxParallelTestModules);
205+
command.Options.Add(AdditionalMSBuildParameters);
201206

202207
return command;
203208
}

src/Cli/dotnet/commands/dotnet-test/TestingPlatformCommand.cs

Lines changed: 75 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public int Run(ParseResult parseResult)
3737

3838
// User can decide what the degree of parallelism should be
3939
// If not specified, we will default to the number of processors
40-
if (!int.TryParse(parseResult.GetValue(TestCommandParser.DegreeOfParallelism), out int degreeOfParallelism))
40+
if (!int.TryParse(parseResult.GetValue(TestCommandParser.MaxParallelTestModules), out int degreeOfParallelism))
4141
degreeOfParallelism = Environment.ProcessorCount;
4242

4343
if (ContainsHelpOption(_args))
@@ -64,12 +64,15 @@ public int Run(ParseResult parseResult)
6464
_namedPipeConnectionLoop = Task.Run(async () => await WaitConnectionAsync(_cancellationToken.Token));
6565

6666
bool containsNoBuild = parseResult.UnmatchedTokens.Any(token => token == CliConstants.NoBuildOptionKey);
67+
List<string> msbuildCommandlineArgs = [$"-t:{(containsNoBuild ? string.Empty : "Build;")}_GetTestsProject",
68+
$"-p:GetTestsProjectPipeName={_pipeNameDescription.Name}",
69+
"-verbosity:q"];
6770

68-
ForwardingAppImplementation msBuildForwardingApp = new(
69-
GetMSBuildExePath(),
70-
[$"-t:{(containsNoBuild ? string.Empty : "Build;")}_GetTestsProject",
71-
$"-p:GetTestsProjectPipeName={_pipeNameDescription.Name}",
72-
"-verbosity:q"]);
71+
AddAdditionalMSBuildParameters(parseResult, msbuildCommandlineArgs);
72+
73+
VSTestTrace.SafeWriteTrace(() => $"MSBuild command line arguments: {msbuildCommandlineArgs}");
74+
75+
ForwardingAppImplementation msBuildForwardingApp = new(GetMSBuildExePath(), msbuildCommandlineArgs);
7376
int testsProjectResult = msBuildForwardingApp.Execute();
7477

7578
if (testsProjectResult != 0)
@@ -89,13 +92,19 @@ public int Run(ParseResult parseResult)
8992
return 0;
9093
}
9194

95+
private static void AddAdditionalMSBuildParameters(ParseResult parseResult, List<string> parameters)
96+
{
97+
string msBuildParameters = parseResult.GetValue(TestCommandParser.AdditionalMSBuildParameters);
98+
parameters.AddRange(!string.IsNullOrEmpty(msBuildParameters) ? msBuildParameters.Split(" ", StringSplitOptions.RemoveEmptyEntries) : []);
99+
}
100+
92101
private async Task WaitConnectionAsync(CancellationToken token)
93102
{
94103
try
95104
{
96105
while (true)
97106
{
98-
NamedPipeServer namedPipeServer = new(_pipeNameDescription, OnRequest, NamedPipeServerStream.MaxAllowedServerInstances, token);
107+
NamedPipeServer namedPipeServer = new(_pipeNameDescription, OnRequest, NamedPipeServerStream.MaxAllowedServerInstances, token, skipUnknownMessages: true);
99108
namedPipeServer.RegisterAllSerializers();
100109

101110
await namedPipeServer.WaitConnectionAsync(token);
@@ -109,32 +118,60 @@ private async Task WaitConnectionAsync(CancellationToken token)
109118
}
110119
catch (Exception ex)
111120
{
112-
VSTestTrace.SafeWriteTrace(() => ex.ToString());
113-
throw;
121+
if (VSTestTrace.TraceEnabled)
122+
{
123+
VSTestTrace.SafeWriteTrace(() => ex.ToString());
124+
}
125+
126+
Environment.FailFast(ex.ToString());
114127
}
115128
}
116129

117130
private Task<IResponse> OnRequest(IRequest request)
118131
{
119-
if (TryGetModulePath(request, out string modulePath))
132+
try
120133
{
121-
_testApplications[modulePath] = new TestApplication(modulePath, _pipeNameDescription.Name, _args);
122-
// Write the test application to the channel
123-
_actionQueue.Enqueue(_testApplications[modulePath]);
134+
if (TryGetModulePath(request, out string modulePath))
135+
{
136+
_testApplications[modulePath] = new TestApplication(modulePath, _pipeNameDescription.Name, _args);
137+
// Write the test application to the channel
138+
_actionQueue.Enqueue(_testApplications[modulePath]);
124139

125-
return Task.FromResult((IResponse)VoidResponse.CachedInstance);
126-
}
140+
return Task.FromResult((IResponse)VoidResponse.CachedInstance);
141+
}
127142

128-
if (TryGetHelpResponse(request, out CommandLineOptionMessages commandLineOptionMessages))
129-
{
130-
var testApplication = _testApplications[commandLineOptionMessages.ModulePath];
131-
Debug.Assert(testApplication is not null);
132-
testApplication.OnCommandLineOptionMessages(commandLineOptionMessages);
143+
if (TryGetHelpResponse(request, out CommandLineOptionMessages commandLineOptionMessages))
144+
{
145+
var testApplication = _testApplications[commandLineOptionMessages.ModulePath];
146+
Debug.Assert(testApplication is not null);
147+
testApplication.OnCommandLineOptionMessages(commandLineOptionMessages);
148+
149+
return Task.FromResult((IResponse)VoidResponse.CachedInstance);
150+
}
151+
152+
// If we don't recognize the message, log and skip it
153+
if (TryGetUnknownMessage(request, out UnknownMessage unknownMessage))
154+
{
155+
if (VSTestTrace.TraceEnabled)
156+
{
157+
VSTestTrace.SafeWriteTrace(() => $"Request '{request.GetType()}' with Serializer ID = {unknownMessage.SerializerId} is unsupported.");
158+
}
159+
return Task.FromResult((IResponse)VoidResponse.CachedInstance);
160+
}
133161

134-
return Task.FromResult((IResponse)VoidResponse.CachedInstance);
162+
// If it doesn't match any of the above, throw an exception
163+
throw new NotSupportedException($"Request '{request.GetType()}' is unsupported.");
135164
}
165+
catch (Exception ex)
166+
{
167+
if (VSTestTrace.TraceEnabled)
168+
{
169+
VSTestTrace.SafeWriteTrace(() => ex.ToString());
170+
}
136171

137-
throw new NotSupportedException($"Request '{request.GetType()}' is unsupported.");
172+
Environment.FailFast(ex.ToString());
173+
}
174+
return Task.FromResult((IResponse)VoidResponse.CachedInstance);
138175
}
139176

140177
private static bool TryGetModulePath(IRequest request, out string modulePath)
@@ -161,9 +198,24 @@ private static bool TryGetHelpResponse(IRequest request, out CommandLineOptionMe
161198
return false;
162199
}
163200

201+
private static bool TryGetUnknownMessage(IRequest request, out UnknownMessage unknownMessage)
202+
{
203+
if (request is UnknownMessage result)
204+
{
205+
unknownMessage = result;
206+
return true;
207+
}
208+
209+
unknownMessage = null;
210+
return false;
211+
}
212+
164213
private void OnErrorReceived(object sender, ErrorEventArgs args)
165214
{
166-
VSTestTrace.SafeWriteTrace(() => args.ErrorMessage);
215+
if (VSTestTrace.TraceEnabled)
216+
{
217+
VSTestTrace.SafeWriteTrace(() => args.ErrorMessage);
218+
}
167219
}
168220

169221
private static bool ContainsHelpOption(IEnumerable<string> args) => args.Contains(CliConstants.HelpOptionKey) || args.Contains(CliConstants.HelpOptionKey.Substring(0, 2));

src/Cli/dotnet/commands/dotnet-test/xlf/LocalizableStrings.cs.xlf

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Cli/dotnet/commands/dotnet-test/xlf/LocalizableStrings.de.xlf

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)