| File: ComputeBatchTimeout.cs | Web Access |
| Project: src\runtime\src\tasks\HelixTestTasks\HelixTestTasks.csproj (HelixTestTasks) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Collections.Generic; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; namespace Microsoft.DotNet.HelixTestTasks; /// <summary> /// Computes per-batch Helix work item metadata (name, staging directory, timeout). /// Timeout is <see cref="MinutesPerSuite"/> minutes per suite (default 20) with a /// <see cref="MinimumMinutes"/> floor (default 30) to handle the heaviest individual /// suites (e.g. Cryptography ~17 min), capped at <see cref="MaximumMinutes"/>. /// Slower test scopes (e.g. outerloop) can raise these knobs. /// </summary> public class ComputeBatchTimeout : Task { [Required] public ITaskItem[] GroupedItems { get; set; } = Array.Empty<ITaskItem>(); [Required] public ITaskItem[] BatchIds { get; set; } = Array.Empty<ITaskItem>(); [Required] public string ItemPrefix { get; set; } = string.Empty; [Required] public string BatchOutputDir { get; set; } = string.Empty; /// <summary>Minutes budgeted per test suite in a batch. Default 20.</summary> public int MinutesPerSuite { get; set; } = 20; /// <summary>Minimum per-batch timeout in minutes. Default 30.</summary> public int MinimumMinutes { get; set; } = 30; /// <summary> /// Maximum per-batch timeout in minutes. Default 1439 (capped below 24h to /// prevent hh format wrapping). Keep this below the AzDO job timeout (minus /// build/send overhead) so the synchronous send-to-Helix wait cannot outlast /// the job. /// </summary> public int MaximumMinutes { get; set; } = 1439; [Output] public ITaskItem[] TimedItems { get; set; } = Array.Empty<ITaskItem>(); public override bool Execute() { // Sanitize MSBuild-provided knobs: fall back to defaults on non-positive values, and cap // MaximumMinutes below 24h so the "hh" specifier in the formatted Timeout cannot wrap // (e.g. 1440 minutes would render as 00:00:00). Also ensure the floor never exceeds the cap. // Warn whenever a value is corrected so a pipeline misconfiguration is diagnosable in the log. if (MinutesPerSuite <= 0) { Log.LogWarning("{0} must be positive but was {1}; falling back to default 20.", nameof(MinutesPerSuite), MinutesPerSuite); MinutesPerSuite = 20; } if (MinimumMinutes <= 0) { Log.LogWarning("{0} must be positive but was {1}; falling back to default 30.", nameof(MinimumMinutes), MinimumMinutes); MinimumMinutes = 30; } if (MaximumMinutes <= 0 || MaximumMinutes > 1439) { Log.LogWarning("{0} must be in the range [1, 1439] but was {1}; falling back to default 1439.", nameof(MaximumMinutes), MaximumMinutes); MaximumMinutes = 1439; } if (MinimumMinutes > MaximumMinutes) { Log.LogWarning("{0} ({1}) exceeds {2} ({3}); clamping {0} to {3}.", nameof(MinimumMinutes), MinimumMinutes, nameof(MaximumMinutes), MaximumMinutes); MinimumMinutes = MaximumMinutes; } var counts = new Dictionary<string, int>(); foreach (var item in GroupedItems) { string bid = item.GetMetadata("BatchId"); counts.TryGetValue(bid, out int current); counts[bid] = current + 1; } var result = new List<ITaskItem>(); foreach (var batchId in BatchIds) { string bid = batchId.ItemSpec; int count = counts.GetValueOrDefault(bid, 1); // MinutesPerSuite minutes per suite to account for WASM startup overhead + test // execution; MinimumMinutes floor handles the heaviest individual suites // (e.g. Cryptography ~17m); capped at MaximumMinutes (kept below 24h to prevent // hh format wrapping, and intended to stay under the AzDO job timeout). // Multiply in long so an outsized MinutesPerSuite override cannot overflow int; the // result is bounded by MaximumMinutes (< 24h), so casting back to int is safe. long perSuiteMinutes = (long)count * MinutesPerSuite; int totalMinutes = (int)Math.Min(MaximumMinutes, Math.Max(MinimumMinutes, perSuiteMinutes)); var ts = TimeSpan.FromMinutes(totalMinutes); var helixItem = new TaskItem(ItemPrefix + "Batch-" + bid); helixItem.SetMetadata("BatchDir", BatchOutputDir + "batch-" + bid + "/"); helixItem.SetMetadata("Timeout", ts.ToString(@"hh\:mm\:ss")); result.Add(helixItem); } TimedItems = result.ToArray(); return !Log.HasLoggedErrors; } }