Skip to content

Commit f3569b1

Browse files
Youssef1313Copilotnohwnd
authored
Cleanup TestProgressState (#50056)
Co-authored-by: Copilot <[email protected]> Co-authored-by: Jakub Jareš <[email protected]>
1 parent 90e525a commit f3569b1

File tree

4 files changed

+379
-72
lines changed

4 files changed

+379
-72
lines changed

src/Cli/dotnet/Commands/Test/Terminal/TerminalTestReporter.cs

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -172,10 +172,10 @@ public void TestExecutionStarted(DateTimeOffset testStartTime, int workerCount,
172172
_terminalWithProgress.StartShowingProgress(workerCount);
173173
}
174174

175-
public void AssemblyRunStarted(string assembly, string? targetFramework, string? architecture, string executionId)
175+
public void AssemblyRunStarted(string assembly, string? targetFramework, string? architecture, string executionId, string instanceId)
176176
{
177177
var assemblyRun = GetOrAddAssemblyRun(assembly, targetFramework, architecture, executionId);
178-
assemblyRun.TryCount++;
178+
assemblyRun.NotifyHandshake(instanceId);
179179

180180
// If we fail to parse out the parameter correctly this will enable retry on re-run of the assembly within the same execution.
181181
// Not good enough for general use, because we want to show (try 1) even on the first try, but this will at
@@ -494,14 +494,6 @@ internal void TestCompleted(
494494
break;
495495
}
496496

497-
if (_isRetry && asm.TryCount > 1 && outcome == TestOutcome.Passed)
498-
{
499-
// This is a retry of a test, and the test succeeded, so these tests are potentially flaky.
500-
// Tests that come from dynamic data sources and previously succeeded will also run on the second attempt,
501-
// and most likely will succeed as well, so we will get them here, even though they are probably not flaky.
502-
asm.FlakyTests.Add(testNodeUid);
503-
504-
}
505497
_terminalWithProgress.UpdateWorker(asm.SlotIndex);
506498
if (outcome != TestOutcome.Passed || GetShowPassedTests())
507499
{
@@ -806,7 +798,6 @@ internal void AssemblyRunCompleted(string executionId,
806798
int exitCode, string? outputData, string? errorData)
807799
{
808800
TestProgressState assemblyRun = _assemblies[executionId];
809-
assemblyRun.ExitCode = exitCode;
810801
assemblyRun.Success = exitCode == 0 && assemblyRun.FailedTests == 0;
811802
assemblyRun.Stopwatch.Stop();
812803

@@ -959,6 +950,14 @@ internal void TestDiscovered(
959950
string? displayName,
960951
string? uid)
961952
{
953+
if (!_isDiscovery)
954+
{
955+
// Don't count discovered tests if we are not in discovery mode.
956+
// NOTE: DiscoverTest call increments PassedTests, so it must not be called when we get discovery message in run mode.
957+
// Also, don't bother adding discovered tests to the DiscoveredTests list when the list is only used in discovery mode.
958+
return;
959+
}
960+
962961
TestProgressState asm = _assemblies[executionId];
963962

964963
// TODO: add mode for discovered tests to the progress bar - jajares
Lines changed: 78 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

4+
using System.Diagnostics;
5+
using TestNodeInfoEntry = (int Passed, int Skipped, int Failed, int LastAttemptNumber);
6+
47
namespace Microsoft.DotNet.Cli.Commands.Test.Terminal;
58

69
internal sealed class TestProgressState(long id, string assembly, string? targetFramework, string? architecture, IStopwatch stopwatch)
710
{
8-
private readonly Dictionary<string, (int Passed, int Skipped, int Failed, string LastInstanceId)> _testUidToResults = new();
11+
private readonly Dictionary<string, TestNodeInfoEntry> _testUidToResults = new();
12+
13+
// In most cases, retries don't happen. So we start with a capacity of 1.
14+
// Resizes will be rare and will be okay with such small sizes.
15+
private readonly List<string> _orderedInstanceIds = new(capacity: 1);
916

1017
public string Assembly { get; } = assembly;
1118

@@ -37,98 +44,114 @@ internal sealed class TestProgressState(long id, string assembly, string? target
3744

3845
public List<(string? DisplayName, string? UID)> DiscoveredTests { get; internal set; } = [];
3946

40-
public int? ExitCode { get; internal set; }
41-
4247
public bool Success { get; internal set; }
4348

44-
public int TryCount { get; internal set; }
45-
46-
public HashSet<string> FlakyTests { get; } = [];
49+
public int TryCount { get; private set; }
4750

48-
public void ReportPassingTest(string testNodeUid, string instanceId)
51+
private void ReportGenericTestResult(
52+
string testNodeUid,
53+
string instanceId,
54+
Func<TestNodeInfoEntry, TestNodeInfoEntry> incrementTestNodeInfoEntry,
55+
Action<TestProgressState> incrementCountAction)
4956
{
57+
var currentAttemptNumber = GetAttemptNumberFromInstanceId(instanceId);
58+
5059
if (_testUidToResults.TryGetValue(testNodeUid, out var value))
5160
{
52-
if (value.LastInstanceId == instanceId)
61+
// We received a result for this test node uid before.
62+
if (value.LastAttemptNumber == currentAttemptNumber)
5363
{
54-
// We are getting a test result for the same instance id.
55-
value.Passed++;
56-
_testUidToResults[testNodeUid] = value;
64+
// We are getting a test result for the same attempt.
65+
// This means that the test framework is reporting multiple results for the same test node uid.
66+
// We will just increment the count of the result.
67+
_testUidToResults[testNodeUid] = incrementTestNodeInfoEntry(value);
5768
}
58-
else
69+
else if (currentAttemptNumber > value.LastAttemptNumber)
5970
{
71+
// This is a retry!
6072
// We are getting a test result for a different instance id.
6173
// This means that the test was retried.
6274
// We discard the results from the previous instance id
63-
RetriedFailedTests++;
75+
RetriedFailedTests += value.Failed;
6476
PassedTests -= value.Passed;
6577
SkippedTests -= value.Skipped;
6678
FailedTests -= value.Failed;
67-
_testUidToResults[testNodeUid] = (Passed: 1, Skipped: 0, Failed: 0, LastInstanceId: instanceId);
79+
_testUidToResults[testNodeUid] = incrementTestNodeInfoEntry((Passed: 0, Skipped: 0, Failed: 0, LastAttemptNumber: currentAttemptNumber));
80+
}
81+
else
82+
{
83+
// This is an unexpected case where we received a result for an instance id that is older than the last one we saw.
84+
throw new UnreachableException($"Unexpected test result for attempt '{currentAttemptNumber}' while the last attempt is '{value.LastAttemptNumber}'");
6885
}
6986
}
7087
else
7188
{
7289
// This is the first time we see this test node.
73-
_testUidToResults.Add(testNodeUid, (Passed: 1, Skipped: 0, Failed: 0, LastInstanceId: instanceId));
90+
_testUidToResults.Add(testNodeUid, incrementTestNodeInfoEntry((Passed: 0, Skipped: 0, Failed: 0, LastAttemptNumber: currentAttemptNumber)));
7491
}
7592

76-
PassedTests++;
93+
incrementCountAction(this);
94+
}
95+
96+
public void ReportPassingTest(string testNodeUid, string instanceId)
97+
{
98+
ReportGenericTestResult(testNodeUid, instanceId, static entry =>
99+
{
100+
entry.Passed++;
101+
return entry;
102+
}, static @this => @this.PassedTests++);
77103
}
78104

79105
public void ReportSkippedTest(string testNodeUid, string instanceId)
80106
{
81-
if (_testUidToResults.TryGetValue(testNodeUid, out var value))
107+
ReportGenericTestResult(testNodeUid, instanceId, static entry =>
82108
{
83-
if (value.LastInstanceId == instanceId)
84-
{
85-
value.Skipped++;
86-
_testUidToResults[testNodeUid] = value;
87-
}
88-
else
89-
{
90-
PassedTests -= value.Passed;
91-
SkippedTests -= value.Skipped;
92-
FailedTests -= value.Failed;
93-
_testUidToResults[testNodeUid] = (Passed: 0, Skipped: 1, Failed: 0, LastInstanceId: instanceId);
94-
}
95-
}
96-
else
109+
entry.Skipped++;
110+
return entry;
111+
}, static @this => @this.SkippedTests++);
112+
}
113+
114+
public void ReportFailedTest(string testNodeUid, string instanceId)
115+
{
116+
ReportGenericTestResult(testNodeUid, instanceId, static entry =>
97117
{
98-
_testUidToResults.Add(testNodeUid, (Passed: 0, Skipped: 1, Failed: 0, LastInstanceId: instanceId));
99-
}
118+
entry.Failed++;
119+
return entry;
120+
}, static @this => @this.FailedTests++);
121+
}
100122

101-
SkippedTests++;
123+
public void DiscoverTest(string? displayName, string? uid)
124+
{
125+
PassedTests++;
126+
DiscoveredTests.Add(new(displayName, uid));
102127
}
103128

104-
public void ReportFailedTest(string testNodeUid, string instanceId)
129+
internal void NotifyHandshake(string instanceId)
105130
{
106-
if (_testUidToResults.TryGetValue(testNodeUid, out var value))
131+
var index = _orderedInstanceIds.IndexOf(instanceId);
132+
if (index < 0)
107133
{
108-
if (value.LastInstanceId == instanceId)
109-
{
110-
value.Failed++;
111-
_testUidToResults[testNodeUid] = value;
112-
}
113-
else
114-
{
115-
PassedTests -= value.Passed;
116-
SkippedTests -= value.Skipped;
117-
FailedTests -= value.Failed;
118-
_testUidToResults[testNodeUid] = (Passed: 0, Skipped: 0, Failed: 1, LastInstanceId: instanceId);
119-
}
134+
// New instanceId for a retry. We add it to _orderedInstanceIds.
135+
_orderedInstanceIds.Add(instanceId);
136+
TryCount++;
120137
}
121-
else
138+
else if (index != _orderedInstanceIds.Count - 1)
122139
{
123-
_testUidToResults.Add(testNodeUid, (Passed: 0, Skipped: 0, Failed: 1, LastInstanceId: instanceId));
140+
// This is an unexpected case where we received a handshake for an instance id that is not the last one we saw.
141+
// This means that the test framework is trying to report results for an instance id that is not the last one.
142+
throw new UnreachableException($"Unexpected handshake for instance id '{instanceId}' at index '{index}' while the last index is '{_orderedInstanceIds.Count - 1}'");
124143
}
125-
126-
FailedTests++;
127144
}
128145

129-
public void DiscoverTest(string? displayName, string? uid)
146+
private int GetAttemptNumberFromInstanceId(string instanceId)
130147
{
131-
PassedTests++;
132-
DiscoveredTests.Add(new(displayName, uid));
148+
var index = _orderedInstanceIds.IndexOf(instanceId);
149+
if (index < 0)
150+
{
151+
throw new UnreachableException($"The instanceId '{instanceId}' not found.");
152+
}
153+
154+
// Attempt numbers are 1-based, so we add 1 to the index.
155+
return index + 1;
133156
}
134157
}

src/Cli/dotnet/Commands/Test/TestApplicationEventHandlers.cs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,12 @@ public void OnHandshakeReceived(object sender, HandshakeArgs args)
2626
// Calling it for both test host and test host controllers means we will count retries incorrectly, and will messages twice.
2727
var testApplication = (TestApplication)sender;
2828
var executionId = args.Handshake.Properties[HandshakeMessagePropertyNames.ExecutionId];
29+
var instanceId = args.Handshake.Properties[HandshakeMessagePropertyNames.InstanceId];
2930
var arch = args.Handshake.Properties[HandshakeMessagePropertyNames.Architecture]?.ToLower();
3031
var tfm = TargetFrameworkParser.GetShortTargetFramework(args.Handshake.Properties[HandshakeMessagePropertyNames.Framework]);
3132
(string ModulePath, string TargetFramework, string Architecture, string ExecutionId) appInfo = new(testApplication.Module.TargetPath, tfm, arch, executionId);
3233
_executions[testApplication] = appInfo;
33-
_output.AssemblyRunStarted(appInfo.ModulePath, appInfo.TargetFramework, appInfo.Architecture, appInfo.ExecutionId);
34+
_output.AssemblyRunStarted(appInfo.ModulePath, appInfo.TargetFramework, appInfo.Architecture, appInfo.ExecutionId, instanceId);
3435
}
3536

3637
LogHandshake(args);
@@ -68,11 +69,6 @@ public void OnDiscoveredTestsReceived(object sender, DiscoveredTestEventArgs arg
6869

6970
public void OnTestResultsReceived(object sender, TestResultEventArgs args)
7071
{
71-
// TODO: If we got some results for ExecutionId1 and InstanceId1
72-
// Then we started getting ExecutionId1 and InstanceId2,
73-
// Then we started getting ExecutionId1 and InstanceId1 again.
74-
// Should we discard the last result from ExecutionId1 and InstanceId1 completely?
75-
// Or is it considered a violation of the protocol and should never happen? (in that case maybe we should throw?)
7672
var testApp = (TestApplication)sender;
7773
var appInfo = _executions[testApp];
7874

0 commit comments

Comments
 (0)