| File: Commands\Test\MTP\Terminal\TestNodeResultsState.cs | Web Access |
| Project: src\sdk\src\Cli\dotnet\dotnet.csproj (dotnet) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Concurrent; using System.Globalization; namespace Microsoft.DotNet.Cli.Commands.Test.Terminal; internal sealed class TestNodeResultsState(long id) { public long Id { get; } = id; private readonly TestDetailState _summaryDetail = new(id, stopwatch: null, text: string.Empty); private readonly ConcurrentDictionary<string, TestDetailState> _testNodeProgressStates = new(); private readonly ConcurrentDictionary<string, byte> _completed = new(); private int _cachedFullTestsCount; private CultureInfo? _cachedFullTestsCulture; private CultureInfo? _cachedFullTestsUICulture; private string _cachedFullTestsText = string.Empty; private int _cachedMoreTestsCount; private CultureInfo? _cachedMoreTestsCulture; private CultureInfo? _cachedMoreTestsUICulture; private string _cachedMoreTestsText = string.Empty; public int Count => _testNodeProgressStates.Count; public void AddRunningTestNode(int id, string instanceId, string uid, string name, IStopwatch stopwatch) { string key = MakeKey(instanceId, uid); // Guard against stale "in-progress" notifications that arrive after the // test already completed. Without this we could surface a "running" // entry that will never be removed. if (_completed.ContainsKey(key)) { return; } _testNodeProgressStates[key] = new TestDetailState(id, stopwatch, name); } public void RemoveRunningTestNode(string instanceId, string uid) { string key = MakeKey(instanceId, uid); _completed[key] = 0; _testNodeProgressStates.TryRemove(key, out _); } private static string MakeKey(string instanceId, string uid) => $"{instanceId}\u0000{uid}"; public IEnumerable<TestDetailState> GetRunningTasks(int maxCount) { var sortedDetails = _testNodeProgressStates .Select(d => d.Value) .OrderByDescending(d => d.Stopwatch?.Elapsed ?? TimeSpan.Zero) .ToList(); bool tooManyItems = sortedDetails.Count > maxCount; if (tooManyItems) { // Note: If there's too many items to display, the summary will take up one line. // As such, we can only take maxCount - 1 items. int itemsToTake = maxCount - 1; _summaryDetail.Text = itemsToTake == 0 // Note: If itemsToTake is 0, then we only show two lines, the project summary and the number of running tests. ? GetFullTestsCountText(sortedDetails.Count) // If itemsToTake is larger, then we show the project summary, active tests, and the number of active tests that are not shown. : GetMoreTestsCountText(sortedDetails.Count - itemsToTake); sortedDetails = [.. sortedDetails.Take(itemsToTake)]; } foreach (TestDetailState? detail in sortedDetails) { yield return detail; } if (tooManyItems) { yield return _summaryDetail; } } private string GetFullTestsCountText(int count) { CultureInfo culture = CultureInfo.CurrentCulture; CultureInfo uiCulture = CultureInfo.CurrentUICulture; if (_cachedFullTestsCount != count || !ReferenceEquals(_cachedFullTestsCulture, culture) || !ReferenceEquals(_cachedFullTestsUICulture, uiCulture)) { _cachedFullTestsText = string.Format(culture, CliCommandStrings.ActiveTestsRunning_FullTestsCount, count); _cachedFullTestsCount = count; _cachedFullTestsCulture = culture; _cachedFullTestsUICulture = uiCulture; } return _cachedFullTestsText; } private string GetMoreTestsCountText(int count) { CultureInfo culture = CultureInfo.CurrentCulture; CultureInfo uiCulture = CultureInfo.CurrentUICulture; if (_cachedMoreTestsCount != count || !ReferenceEquals(_cachedMoreTestsCulture, culture) || !ReferenceEquals(_cachedMoreTestsUICulture, uiCulture)) { _cachedMoreTestsText = $"... {string.Format(culture, CliCommandStrings.ActiveTestsRunning_MoreTestsCount, count)}"; _cachedMoreTestsCount = count; _cachedMoreTestsCulture = culture; _cachedMoreTestsUICulture = uiCulture; } return _cachedMoreTestsText; } }