|
1 | 1 | // Copyright (c) Microsoft Corporation. All rights reserved.
|
2 | 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
3 | 3 |
|
| 4 | +using System.Diagnostics; |
| 5 | +using TestNodeInfoEntry = (int Passed, int Skipped, int Failed, int LastAttemptNumber); |
| 6 | + |
4 | 7 | namespace Microsoft.DotNet.Cli.Commands.Test.Terminal;
|
5 | 8 |
|
6 | 9 | internal sealed class TestProgressState(long id, string assembly, string? targetFramework, string? architecture, IStopwatch stopwatch)
|
7 | 10 | {
|
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); |
9 | 16 |
|
10 | 17 | public string Assembly { get; } = assembly;
|
11 | 18 |
|
@@ -37,98 +44,114 @@ internal sealed class TestProgressState(long id, string assembly, string? target
|
37 | 44 |
|
38 | 45 | public List<(string? DisplayName, string? UID)> DiscoveredTests { get; internal set; } = [];
|
39 | 46 |
|
40 |
| - public int? ExitCode { get; internal set; } |
41 |
| - |
42 | 47 | public bool Success { get; internal set; }
|
43 | 48 |
|
44 |
| - public int TryCount { get; internal set; } |
45 |
| - |
46 |
| - public HashSet<string> FlakyTests { get; } = []; |
| 49 | + public int TryCount { get; private set; } |
47 | 50 |
|
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) |
49 | 56 | {
|
| 57 | + var currentAttemptNumber = GetAttemptNumberFromInstanceId(instanceId); |
| 58 | + |
50 | 59 | if (_testUidToResults.TryGetValue(testNodeUid, out var value))
|
51 | 60 | {
|
52 |
| - if (value.LastInstanceId == instanceId) |
| 61 | + // We received a result for this test node uid before. |
| 62 | + if (value.LastAttemptNumber == currentAttemptNumber) |
53 | 63 | {
|
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); |
57 | 68 | }
|
58 |
| - else |
| 69 | + else if (currentAttemptNumber > value.LastAttemptNumber) |
59 | 70 | {
|
| 71 | + // This is a retry! |
60 | 72 | // We are getting a test result for a different instance id.
|
61 | 73 | // This means that the test was retried.
|
62 | 74 | // We discard the results from the previous instance id
|
63 |
| - RetriedFailedTests++; |
| 75 | + RetriedFailedTests += value.Failed; |
64 | 76 | PassedTests -= value.Passed;
|
65 | 77 | SkippedTests -= value.Skipped;
|
66 | 78 | 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}'"); |
68 | 85 | }
|
69 | 86 | }
|
70 | 87 | else
|
71 | 88 | {
|
72 | 89 | // 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))); |
74 | 91 | }
|
75 | 92 |
|
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++); |
77 | 103 | }
|
78 | 104 |
|
79 | 105 | public void ReportSkippedTest(string testNodeUid, string instanceId)
|
80 | 106 | {
|
81 |
| - if (_testUidToResults.TryGetValue(testNodeUid, out var value)) |
| 107 | + ReportGenericTestResult(testNodeUid, instanceId, static entry => |
82 | 108 | {
|
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 => |
97 | 117 | {
|
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 | + } |
100 | 122 |
|
101 |
| - SkippedTests++; |
| 123 | + public void DiscoverTest(string? displayName, string? uid) |
| 124 | + { |
| 125 | + PassedTests++; |
| 126 | + DiscoveredTests.Add(new(displayName, uid)); |
102 | 127 | }
|
103 | 128 |
|
104 |
| - public void ReportFailedTest(string testNodeUid, string instanceId) |
| 129 | + internal void NotifyHandshake(string instanceId) |
105 | 130 | {
|
106 |
| - if (_testUidToResults.TryGetValue(testNodeUid, out var value)) |
| 131 | + var index = _orderedInstanceIds.IndexOf(instanceId); |
| 132 | + if (index < 0) |
107 | 133 | {
|
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++; |
120 | 137 | }
|
121 |
| - else |
| 138 | + else if (index != _orderedInstanceIds.Count - 1) |
122 | 139 | {
|
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}'"); |
124 | 143 | }
|
125 |
| - |
126 |
| - FailedTests++; |
127 | 144 | }
|
128 | 145 |
|
129 |
| - public void DiscoverTest(string? displayName, string? uid) |
| 146 | + private int GetAttemptNumberFromInstanceId(string instanceId) |
130 | 147 | {
|
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; |
133 | 156 | }
|
134 | 157 | }
|
0 commit comments