|
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