Skip to content

Commit cb2c757

Browse files
authored
Ensure dotnet test messages are received in the right timings/modes (#50727)
1 parent 6a4321b commit cb2c757

18 files changed

+258
-50
lines changed

src/Cli/dotnet/Commands/CliCommandStrings.resx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2682,4 +2682,13 @@ Proceed?</value>
26822682
<data name="UnexpectedHelpMessage" xml:space="preserve">
26832683
<value>An unexpected help-related message was received when `--help` wasn't used.</value>
26842684
</data>
2685-
</root>
2685+
<data name="MismatchingHandshakeInfo" xml:space="preserve">
2686+
<value>A new handshake '{0}' was received for the test application that doesn't match the previous handshake '{1}'.</value>
2687+
</data>
2688+
<data name="UnexpectedMessageInHelpMode" xml:space="preserve">
2689+
<value>A message of type '{0}' was received in help mode, which is not expected.</value>
2690+
</data>
2691+
<data name="UnexpectedMessageWithoutHandshake" xml:space="preserve">
2692+
<value>A message of type '{0}' was received before we got any handshakes, which is unexpected.</value>
2693+
</data>
2694+
</root>

src/Cli/dotnet/Commands/Test/MTP/IPC/Models/HandshakeMessage.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33

44
namespace Microsoft.DotNet.Cli.Commands.Test.IPC.Models;
55

6-
internal sealed record HandshakeMessage(Dictionary<byte, string>? Properties) : IRequest, IResponse;
6+
internal sealed record HandshakeMessage(Dictionary<byte, string> Properties) : IRequest, IResponse;

src/Cli/dotnet/Commands/Test/MTP/IPC/Serializers/HandshakeMessageSerializer.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
#nullable disable
5-
64
using System.Diagnostics;
75
using Microsoft.DotNet.Cli.Commands.Test.IPC.Models;
86

src/Cli/dotnet/Commands/Test/MTP/TestApplication.cs

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
#nullable disable
5-
64
using System.Diagnostics;
75
using System.Globalization;
86
using System.IO.Pipes;
7+
using System.Threading;
98
using Microsoft.DotNet.Cli.Commands.Test.IPC;
109
using Microsoft.DotNet.Cli.Commands.Test.IPC.Models;
1110
using Microsoft.DotNet.Cli.Commands.Test.IPC.Serializers;
@@ -28,9 +27,7 @@ internal sealed class TestApplication(
2827
private readonly List<string> _outputData = [];
2928
private readonly List<string> _errorData = [];
3029
private readonly string _pipeName = NamedPipeServer.GetPipeName(Guid.NewGuid().ToString("N"));
31-
private readonly CancellationTokenSource _cancellationToken = new();
3230

33-
private Task _testAppPipeConnectionLoop;
3431
private readonly List<NamedPipeServer> _testAppPipeConnections = [];
3532
private readonly Dictionary<NamedPipeServer, HandshakeMessage> _handshakes = new();
3633

@@ -45,17 +42,26 @@ public async Task<int> RunAsync()
4542
// Consider throwing an exception if it's called more than once.
4643
var processStartInfo = CreateProcessStartInfo();
4744

48-
_testAppPipeConnectionLoop = Task.Run(async () => await WaitConnectionAsync(_cancellationToken.Token), _cancellationToken.Token);
49-
var testProcessExitCode = await StartProcess(processStartInfo);
45+
var cancellationTokenSource = new CancellationTokenSource();
46+
var cancellationToken = cancellationTokenSource.Token;
47+
Task? testAppPipeConnectionLoop = null;
5048

51-
WaitOnTestApplicationPipeConnectionLoop();
49+
try
50+
{
51+
testAppPipeConnectionLoop = Task.Run(async () => await WaitConnectionAsync(cancellationToken), cancellationToken);
52+
var testProcessExitCode = await StartProcess(processStartInfo);
53+
if (_handler.HasMismatchingTestSessionEventCount())
54+
{
55+
throw new InvalidOperationException(CliCommandStrings.MissingTestSessionEnd);
56+
}
5257

53-
if (_handler.HasMismatchingTestSessionEventCount())
58+
return testProcessExitCode;
59+
}
60+
finally
5461
{
55-
throw new InvalidOperationException(CliCommandStrings.MissingTestSessionEnd);
62+
cancellationTokenSource.Cancel();
63+
testAppPipeConnectionLoop?.Wait((int)TimeSpan.FromSeconds(30).TotalMilliseconds);
5664
}
57-
58-
return testProcessExitCode;
5965
}
6066

6167
private ProcessStartInfo CreateProcessStartInfo()
@@ -147,12 +153,6 @@ private string GetArguments()
147153
return builder.ToString();
148154
}
149155

150-
private void WaitOnTestApplicationPipeConnectionLoop()
151-
{
152-
_cancellationToken.Cancel();
153-
_testAppPipeConnectionLoop?.Wait((int)TimeSpan.FromSeconds(30).TotalMilliseconds);
154-
}
155-
156156
private async Task WaitConnectionAsync(CancellationToken token)
157157
{
158158
try
@@ -242,7 +242,7 @@ private Task<IResponse> OnRequest(NamedPipeServer server, IRequest request)
242242

243243
private static string GetSupportedProtocolVersion(HandshakeMessage handshakeMessage)
244244
{
245-
if (!handshakeMessage.Properties.TryGetValue(HandshakeMessagePropertyNames.SupportedProtocolVersions, out string protocolVersions) ||
245+
if (!handshakeMessage.Properties.TryGetValue(HandshakeMessagePropertyNames.SupportedProtocolVersions, out string? protocolVersions) ||
246246
protocolVersions is null)
247247
{
248248
// It's not expected we hit this.
@@ -265,7 +265,7 @@ private static string GetSupportedProtocolVersion(HandshakeMessage handshakeMess
265265
}
266266

267267
private static HandshakeMessage CreateHandshakeMessage(string version) =>
268-
new(new Dictionary<byte, string>(capacity: 5)
268+
new HandshakeMessage(new Dictionary<byte, string>(capacity: 5)
269269
{
270270
{ HandshakeMessagePropertyNames.PID, Environment.ProcessId.ToString(CultureInfo.InvariantCulture) },
271271
{ HandshakeMessagePropertyNames.Architecture, RuntimeInformation.ProcessArchitecture.ToString() },
@@ -278,7 +278,7 @@ private async Task<int> StartProcess(ProcessStartInfo processStartInfo)
278278
{
279279
Logger.LogTrace($"Starting test process with command '{processStartInfo.FileName}' and arguments '{processStartInfo.Arguments}'.");
280280

281-
using var process = Process.Start(processStartInfo);
281+
using var process = Process.Start(processStartInfo)!;
282282
StoreOutputAndErrorData(process);
283283
await process.WaitForExitAsync();
284284

@@ -400,7 +400,5 @@ public void Dispose()
400400
Reporter.Error.WriteLine(messageBuilder.ToString());
401401
}
402402
}
403-
404-
WaitOnTestApplicationPipeConnectionLoop();
405403
}
406404
}

src/Cli/dotnet/Commands/Test/MTP/TestApplicationHandler.cs

Lines changed: 32 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -46,17 +46,18 @@ internal void OnHandshakeReceived(HandshakeMessage handshakeMessage, bool gotSup
4646
return;
4747
}
4848

49+
var executionId = handshakeMessage.Properties[HandshakeMessagePropertyNames.ExecutionId];
50+
var arch = handshakeMessage.Properties[HandshakeMessagePropertyNames.Architecture]?.ToLower();
51+
var tfm = TargetFrameworkParser.GetShortTargetFramework(handshakeMessage.Properties[HandshakeMessagePropertyNames.Framework]);
52+
var currentHandshakeInfo = (tfm, arch, executionId);
53+
4954
if (!_handshakeInfo.HasValue)
5055
{
51-
var executionId = handshakeMessage.Properties[HandshakeMessagePropertyNames.ExecutionId];
52-
var arch = handshakeMessage.Properties[HandshakeMessagePropertyNames.Architecture]?.ToLower();
53-
var tfm = TargetFrameworkParser.GetShortTargetFramework(handshakeMessage.Properties[HandshakeMessagePropertyNames.Framework]);
54-
55-
_handshakeInfo = (tfm, arch, executionId);
56+
_handshakeInfo = currentHandshakeInfo;
5657
}
57-
else
58+
else if (_handshakeInfo.Value != currentHandshakeInfo)
5859
{
59-
// TODO: Verify we get the same info.
60+
throw new InvalidOperationException(string.Format(CliCommandStrings.MismatchingHandshakeInfo, currentHandshakeInfo, _handshakeInfo.Value));
6061
}
6162

6263
var hostType = handshakeMessage.Properties[HandshakeMessagePropertyNames.HostType];
@@ -91,13 +92,14 @@ internal void OnDiscoveredTestsReceived(DiscoveredTestMessages discoveredTestMes
9192
{
9293
LogDiscoveredTests(discoveredTestMessages);
9394

94-
// TODO: If _handshakeInfo is null, we should error.
95-
// We shouldn't be getting any discovered test messages without a previous handshake.
96-
9795
if (_options.IsHelp)
9896
{
99-
// TODO: Better to throw exception?
100-
return;
97+
throw new InvalidOperationException(string.Format(CliCommandStrings.UnexpectedMessageInHelpMode, nameof(DiscoveredTestMessages)));
98+
}
99+
100+
if (!_handshakeInfo.HasValue)
101+
{
102+
throw new InvalidOperationException(string.Format(CliCommandStrings.UnexpectedMessageWithoutHandshake, nameof(DiscoveredTestMessages)));
101103
}
102104

103105
foreach (var test in discoveredTestMessages.DiscoveredMessages)
@@ -112,13 +114,14 @@ internal void OnTestResultsReceived(TestResultMessages testResultMessage)
112114
{
113115
LogTestResults(testResultMessage);
114116

115-
// TODO: If _handshakeInfo is null, we should error.
116-
// We shouldn't be getting any test result messages without a previous handshake.
117-
118117
if (_options.IsHelp)
119118
{
120-
// TODO: Better to throw exception?
121-
return;
119+
throw new InvalidOperationException(string.Format(CliCommandStrings.UnexpectedMessageInHelpMode, nameof(TestResultMessages)));
120+
}
121+
122+
if (!_handshakeInfo.HasValue)
123+
{
124+
throw new InvalidOperationException(string.Format(CliCommandStrings.UnexpectedMessageWithoutHandshake, nameof(TestResultMessages)));
122125
}
123126

124127
var handshakeInfo = _handshakeInfo.Value;
@@ -158,13 +161,14 @@ internal void OnFileArtifactsReceived(FileArtifactMessages fileArtifactMessages)
158161
{
159162
LogFileArtifacts(fileArtifactMessages);
160163

161-
// TODO: If _handshakeInfo is null, we should error.
162-
// We shouldn't be getting any file artifact messages without a previous handshake.
163-
164164
if (_options.IsHelp)
165165
{
166-
// TODO: Better to throw exception?
167-
return;
166+
throw new InvalidOperationException(string.Format(CliCommandStrings.UnexpectedMessageInHelpMode, nameof(FileArtifactMessages)));
167+
}
168+
169+
if (!_handshakeInfo.HasValue)
170+
{
171+
throw new InvalidOperationException(string.Format(CliCommandStrings.UnexpectedMessageWithoutHandshake, nameof(FileArtifactMessages)));
168172
}
169173

170174
var handshakeInfo = _handshakeInfo.Value;
@@ -183,8 +187,12 @@ internal void OnSessionEventReceived(TestSessionEvent sessionEvent)
183187
{
184188
LogSessionEvent(sessionEvent);
185189

186-
// TODO: If _handshakeInfo is null, we should error.
187-
// We shouldn't be getting any session event messages without a previous handshake.
190+
// TODO: Validate if we should get this message in help mode or not.
191+
192+
if (!_handshakeInfo.HasValue)
193+
{
194+
throw new InvalidOperationException(string.Format(CliCommandStrings.UnexpectedMessageWithoutHandshake, nameof(DiscoveredTestMessages)));
195+
}
188196

189197
if (sessionEvent.SessionType == SessionEventTypes.TestSessionStart)
190198
{

src/Cli/dotnet/Commands/xlf/CliCommandStrings.cs.xlf

Lines changed: 15 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/xlf/CliCommandStrings.de.xlf

Lines changed: 15 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/xlf/CliCommandStrings.es.xlf

Lines changed: 15 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/xlf/CliCommandStrings.fr.xlf

Lines changed: 15 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)