diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 8ca0dad97470e..43658242cbbac 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -37,6 +37,8 @@ pr: - '*.md' variables: + - template: /eng/common/templates/variables/pool-providers.yml + # This value is conditionally overriden by the variable group DotNet-HelixApi-Access. # ADO only lets us conditionally include a variable group on individual stages. - name: HelixApiAccessToken @@ -199,7 +201,7 @@ stages: name: 'Managed' container: 'mcr.microsoft.com/dotnet-buildtools/prereqs:centos-stream9' -- stage: Windows_Debug_Desktop +- stage: Windows_Debug_Tests dependsOn: Windows_Debug_Build variables: - ${{ if ne(variables['System.TeamProject'], 'public') }}: @@ -216,53 +218,6 @@ stages: helixApiAccessToken: $(HelixApiAccessToken) poolParameters: ${{ parameters.windowsPool }} -- stage: Windows_Release_Desktop - dependsOn: Windows_Release_Build - variables: - - ${{ if ne(variables['System.TeamProject'], 'public') }}: - - group: DotNet-HelixApi-Access - jobs: - - ${{ if ne(variables['Build.Reason'], 'PullRequest') }}: - - template: eng/pipelines/test-windows-job.yml - parameters: - testRunName: 'Test Windows Desktop Release 32' - jobName: Test_Windows_Desktop_Release_32 - testArtifactName: Transport_Artifacts_Windows_Release - configuration: Release - testArguments: -testDesktop -testArch x86 - helixQueueName: $(HelixWindowsVsQueueName) - helixApiAccessToken: $(HelixApiAccessToken) - poolParameters: ${{ parameters.windowsPool }} - - - template: eng/pipelines/test-windows-job.yml - parameters: - testRunName: 'Test Windows Desktop Release 64' - jobName: Test_Windows_Desktop_Release_64 - testArtifactName: Transport_Artifacts_Windows_Release - configuration: Release - testArguments: -testDesktop -testArch x64 - helixQueueName: $(HelixWindowsVsQueueName) - helixApiAccessToken: $(HelixApiAccessToken) - poolParameters: ${{ parameters.windowsPool }} - - - ${{ if ne(variables['Build.Reason'], 'PullRequest') }}: - - template: eng/pipelines/test-windows-job.yml - parameters: - testRunName: 'Test Windows Desktop Spanish Release 64' - jobName: Test_Windows_Desktop_Spanish_Release_64 - testArtifactName: Transport_Artifacts_Windows_Release - configuration: Release - testArguments: -testDesktop -testArch x64 - helixQueueName: $(HelixWindowsSpanishQueueName) - helixApiAccessToken: $(HelixApiAccessToken) - poolParameters: ${{ parameters.windowsPool }} - -- stage: Windows_Debug_CoreClr - dependsOn: Windows_Debug_Build - variables: - - ${{ if ne(variables['System.TeamProject'], 'public') }}: - - group: DotNet-HelixApi-Access - jobs: - template: eng/pipelines/test-windows-job.yml parameters: testRunName: 'Test Windows CoreClr Debug' @@ -331,12 +286,51 @@ stages: helixApiAccessToken: $(HelixApiAccessToken) poolParameters: ${{ parameters.windowsPool }} -- stage: Windows_Release_CoreClr + - template: /eng/common/core-templates/job/helix-job-monitor.yml + parameters: + helixAccessToken: $(HelixApiAccessToken) + +- stage: Windows_Release_Tests dependsOn: Windows_Release_Build variables: - ${{ if ne(variables['System.TeamProject'], 'public') }}: - group: DotNet-HelixApi-Access jobs: + - ${{ if ne(variables['Build.Reason'], 'PullRequest') }}: + - template: eng/pipelines/test-windows-job.yml + parameters: + testRunName: 'Test Windows Desktop Release 32' + jobName: Test_Windows_Desktop_Release_32 + testArtifactName: Transport_Artifacts_Windows_Release + configuration: Release + testArguments: -testDesktop -testArch x86 + helixQueueName: $(HelixWindowsVsQueueName) + helixApiAccessToken: $(HelixApiAccessToken) + poolParameters: ${{ parameters.windowsPool }} + + - template: eng/pipelines/test-windows-job.yml + parameters: + testRunName: 'Test Windows Desktop Release 64' + jobName: Test_Windows_Desktop_Release_64 + testArtifactName: Transport_Artifacts_Windows_Release + configuration: Release + testArguments: -testDesktop -testArch x64 + helixQueueName: $(HelixWindowsVsQueueName) + helixApiAccessToken: $(HelixApiAccessToken) + poolParameters: ${{ parameters.windowsPool }} + + - ${{ if ne(variables['Build.Reason'], 'PullRequest') }}: + - template: eng/pipelines/test-windows-job.yml + parameters: + testRunName: 'Test Windows Desktop Spanish Release 64' + jobName: Test_Windows_Desktop_Spanish_Release_64 + testArtifactName: Transport_Artifacts_Windows_Release + configuration: Release + testArguments: -testDesktop -testArch x64 + helixQueueName: $(HelixWindowsSpanishQueueName) + helixApiAccessToken: $(HelixApiAccessToken) + poolParameters: ${{ parameters.windowsPool }} + - template: eng/pipelines/test-windows-job.yml parameters: testRunName: 'Test Windows CoreClr Release' @@ -348,6 +342,10 @@ stages: helixApiAccessToken: $(HelixApiAccessToken) poolParameters: ${{ parameters.windowsPool }} + - template: /eng/common/core-templates/job/helix-job-monitor.yml + parameters: + helixAccessToken: $(HelixApiAccessToken) + - stage: Unix_Debug_CoreClr dependsOn: Unix_Build variables: @@ -392,6 +390,10 @@ stages: helixApiAccessToken: $(HelixApiAccessToken) poolParameters: ${{ parameters.ubuntuPool }} + - template: /eng/common/core-templates/job/helix-job-monitor.yml + parameters: + helixAccessToken: $(HelixApiAccessToken) + - stage: Correctness dependsOn: [] jobs: diff --git a/dotnet-tools.json b/dotnet-tools.json index 96cae116fea1e..81c6c4f3696e6 100644 --- a/dotnet-tools.json +++ b/dotnet-tools.json @@ -7,6 +7,12 @@ "pwsh" ], "rollForward": false + }, + "microsoft.dotnet.helix.jobmonitor": { + "version": "11.0.0-beta.26412.4", + "commands": [ + "dotnet-helix-job-monitor" + ] } } } diff --git a/eng/Version.Details.props b/eng/Version.Details.props index 583877bb27946..cdc85adc434c0 100644 --- a/eng/Version.Details.props +++ b/eng/Version.Details.props @@ -7,6 +7,7 @@ This file should be imported by eng/Versions.props 11.0.0-beta.26412.4 + 11.0.0-beta.26412.4 11.0.0-beta.26412.4 11.0.0-beta.26412.4 @@ -50,6 +51,7 @@ This file should be imported by eng/Versions.props $(MicrosoftDotNetArcadeSdkPackageVersion) + $(MicrosoftDotNetHelixJobMonitorPackageVersion) $(MicrosoftDotNetHelixSdkPackageVersion) $(MicrosoftDotNetXliffTasksPackageVersion) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 9b7daa1eb5b39..9615344f1602b 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -153,5 +153,9 @@ https://github.com/dotnet/roslyn 3d32d464e2949f054086fbb5346e4beea0c6df56 + + https://github.com/dotnet/arcade + 41232a67a0c1a77d425a6bef59ded8a81a2fdf3c + diff --git a/eng/common/dotnet.sh b/eng/common/dotnet.sh old mode 100644 new mode 100755 diff --git a/src/Tools/RunTests/HelixApi.cs b/src/Tools/RunTests/HelixApi.cs deleted file mode 100644 index 26a4a4fcda742..0000000000000 --- a/src/Tools/RunTests/HelixApi.cs +++ /dev/null @@ -1,207 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Generic; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Text.Json; -using System.Text.Json.Serialization; -using System.Threading; -using System.Threading.Tasks; - -namespace RunTests; - -/// -/// Helper class for interacting with the Helix API to query job and work item status, fetch logs, and cancel jobs. Based -/// on the API documented at https://helix.dot.net/swagger/ui -/// -internal sealed class HelixApi : IDisposable -{ - private const string HelixBaseUrl = "https://helix.dot.net"; - private const string ApiVersion = "2019-06-17"; - - private readonly HttpClient _httpClient; - - public HelixApi(string? bearerToken = null) - { - _httpClient = new HttpClient - { - BaseAddress = new Uri(HelixBaseUrl) - }; - _httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - - if (!string.IsNullOrEmpty(bearerToken)) - { - _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", bearerToken); - } - } - - /// - /// GET /api/jobs/{job}/details - /// Provides job summary data along with aggregated work item statistics. - /// - public async Task GetJobDetailsAsync(string job, CancellationToken cancellationToken = default) - { - var url = $"/api/jobs/{Uri.EscapeDataString(job)}/details?api-version={ApiVersion}"; - return await GetAsync(url, cancellationToken).ConfigureAwait(false); - } - - /// - /// POST /api/jobs/{job}/cancel - /// Cancels a running job. Requires authentication. - /// - public async Task CancelJobAsync(string job, string? jobCancellationToken = null, CancellationToken cancellationToken = default) - { - var url = $"/api/jobs/{Uri.EscapeDataString(job)}/cancel?api-version={ApiVersion}"; - if (!string.IsNullOrEmpty(jobCancellationToken)) - { - url += $"&jobCancellationToken={Uri.EscapeDataString(jobCancellationToken)}"; - } - - using var response = await _httpClient.PostAsync(url, content: null, cancellationToken).ConfigureAwait(false); - response.EnsureSuccessStatusCode(); - } - - /// - /// GET /api/jobs/{job}/workitems/{id} - /// Fetches details about a single work item including logs and uploaded files. - /// - public async Task GetWorkItemDetailsAsync(string job, string id, CancellationToken cancellationToken = default) - { - var url = $"/api/jobs/{Uri.EscapeDataString(job)}/workitems/{Uri.EscapeDataString(id)}?api-version={ApiVersion}"; - return await GetAsync(url, cancellationToken).ConfigureAwait(false); - } - - /// - /// GET /api/jobs/{job}/workitems - /// Lists all work items for a given job. - /// - public async Task> GetWorkItemsAsync(string job, CancellationToken cancellationToken = default) - { - var url = $"/api/jobs/{Uri.EscapeDataString(job)}/workitems?api-version={ApiVersion}"; - return await GetAsync>(url, cancellationToken).ConfigureAwait(false); - } - - private async Task GetAsync(string url, CancellationToken cancellationToken) - { - using var response = await _httpClient.GetAsync(url, cancellationToken).ConfigureAwait(false); - response.EnsureSuccessStatusCode(); - var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); - return await JsonSerializer.DeserializeAsync(stream, JsonOptions, cancellationToken).ConfigureAwait(false) - ?? throw new InvalidOperationException($"Failed to deserialize response from {url}"); - } - - /// - /// Gets the URL for a work item details page. - /// - public static string GetWorkItemUrl(string job, string workItemName) - => $"{HelixBaseUrl}/api/jobs/{Uri.EscapeDataString(job)}/workitems/{Uri.EscapeDataString(workItemName)}?api-version={ApiVersion}"; - - /// - /// Gets the URL for a work item's console output. - /// - public static string GetWorkItemConsoleUrl(string job, string workItemName) - => $"{HelixBaseUrl}/api/jobs/{Uri.EscapeDataString(job)}/workitems/{Uri.EscapeDataString(workItemName)}/console?api-version={ApiVersion}"; - - public void Dispose() => _httpClient.Dispose(); - - private static readonly JsonSerializerOptions JsonOptions = new() - { - PropertyNameCaseInsensitive = true - }; -} - -// ---- Response model types ---- - -internal sealed class HelixFailureReasonPart -{ - public string? Display { get; set; } - public string? Link { get; set; } -} - -internal sealed class HelixFailureReason -{ - public HelixFailureReasonPart? Issue { get; set; } - public HelixFailureReasonPart? Owner { get; set; } -} - -internal sealed class HelixWorkItemError -{ - public string Id { get; set; } = ""; - public string Message { get; set; } = ""; - public string? LogUri { get; set; } -} - -internal sealed class HelixJobWorkItemCounts -{ - public int Unscheduled { get; set; } - public int Waiting { get; set; } - public int Running { get; set; } - public int Finished { get; set; } - public string ListUrl { get; set; } = ""; -} - -internal sealed class HelixJobDetails -{ - public HelixFailureReason? FailureReason { get; set; } - public string? QueueId { get; set; } - public string JobList { get; set; } = ""; - public HelixJobWorkItemCounts WorkItems { get; set; } = new(); - public string Name { get; set; } = ""; - public string? Creator { get; set; } - public string? Created { get; set; } - public string? Finished { get; set; } - public int? InitialWorkItemCount { get; set; } - public string WaitUrl { get; set; } = ""; - public string Source { get; set; } = ""; - public string Type { get; set; } = ""; - public string Build { get; set; } = ""; - public object? Properties { get; set; } - public List? Errors { get; set; } -} - -internal sealed class HelixWorkItemSummary -{ - public string DetailsUrl { get; set; } = ""; - public int? ExitCode { get; set; } - public string? ConsoleOutputUri { get; set; } - public string Job { get; set; } = ""; - public string Name { get; set; } = ""; - public string State { get; set; } = ""; -} - -internal sealed class HelixWorkItemLog -{ - public string Module { get; set; } = ""; - public string Uri { get; set; } = ""; -} - -internal sealed class HelixWorkItemFile -{ - public string FileName { get; set; } = ""; - public string Uri { get; set; } = ""; -} - -internal sealed class HelixWorkItemDetails -{ - public HelixFailureReason? FailureReason { get; set; } - public string? Queued { get; set; } - public string? Started { get; set; } - public string? Finished { get; set; } - public string? Delay { get; set; } - public string? Duration { get; set; } - public string Id { get; set; } = ""; - public string MachineName { get; set; } = ""; - public int? ExitCode { get; set; } - public string? ConsoleOutputUri { get; set; } - public List? Errors { get; set; } - public List? Warnings { get; set; } - public List? Logs { get; set; } - public List? Files { get; set; } - public List? OtherEvents { get; set; } - public string Job { get; set; } = ""; - public string Name { get; set; } = ""; - public string State { get; set; } = ""; -} diff --git a/src/Tools/RunTests/HelixTestRunner.cs b/src/Tools/RunTests/HelixTestRunner.cs index 8c85434038dd0..ff01ddba83844 100644 --- a/src/Tools/RunTests/HelixTestRunner.cs +++ b/src/Tools/RunTests/HelixTestRunner.cs @@ -10,7 +10,6 @@ using System.Linq; using System.Runtime.InteropServices; using System.Text; -using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; @@ -33,7 +32,7 @@ public sealed class HelixWorkItem( public override string ToString() => DisplayName; } -internal sealed partial class HelixTestRunner +internal sealed class HelixTestRunner { /// /// The amount of time we will allocate for each helix work item. When changing this value, consider that test execution time is only part of the @@ -53,8 +52,7 @@ internal sealed partial class HelixTestRunner /// /// This is the maximum time a Helix work item is allowed to execute. This value is passed to Helix - /// as the work item timeout. We also monitor work items against this value and report warnings when - /// it is exceeded. + /// as the work item timeout. /// internal static TimeSpan WorkItemExecutionTimeout { get; } = WorkItemScheduleTime * 3; @@ -63,14 +61,6 @@ internal sealed partial class HelixTestRunner /// internal static TimeSpan TestMethodTimeout { get; } = TimeSpan.FromMinutes(15); - /// - /// This is the amount of time we will wait between polling the Helix service for updates. - /// - private static TimeSpan HelixPollTime { get; } = TimeSpan.FromMinutes(2.5); - - [GeneratedRegex(@"HelixJobId=(\S+) HelixJobCancellationToken=(\S+)")] - private static partial Regex HelixJobInfoRegex(); - internal enum TestOS { Windows, @@ -87,24 +77,19 @@ internal static async Task RunAsync(Options options, ImmutableArray RunAsync(Options options, ImmutableArray - /// This method will wait for the helix job to complete using the timeout for Helix work item execution. - /// - /// The most important factor is the local helix process itself. If that exits then the assumption is that it - /// successfully reported the status of work items. Hence the moment it is done we can stop waiting and - /// return. The polling for Helix information is all about adding context and real errors to the tests - /// in the case helix has issues and cannot complete the process successfully. - /// - private static async Task WaitForHelixJobCompletionAsync(Process process, string helixJobId, string helixCancellationToken, string? helixApiAccessToken, string testResultsDirectory, CancellationToken cancellationToken) - { - ConsoleUtil.WriteLine($"Waiting for Helix job {helixJobId} to complete..."); - using var helixApi = new HelixApi(helixApiAccessToken); - var processWaitTask = process.WaitForExitAsync(cancellationToken); - - try - { - await WaitForAllWorkItemsRunningAsync(helixApi, helixJobId, processWaitTask, cancellationToken); - await WaitForHelixJob(helixApi, helixJobId, testResultsDirectory, processWaitTask, cancellationToken); - await process.WaitForExitAsync(cancellationToken); - return process.ExitCode; - } - catch (OperationCanceledException) - { - ConsoleUtil.WriteLine($"Cancellation requested. Attempting to cancel Helix job {helixJobId}..."); - await helixApi.CancelJobAsync(helixJobId, helixCancellationToken, CancellationToken.None); - throw; - } - - // Wait until all of the work items have started running - static async Task WaitForAllWorkItemsRunningAsync(HelixApi helixApi, string helixJobId, Task processWaitTask, CancellationToken cancellationToken) - { - var startTime = DateTime.UtcNow; - Console.WriteLine($"Waiting for all work items in Helix job {helixJobId} to start running..."); - do - { - var delayTask = Task.Delay(HelixPollTime, cancellationToken); - var completedTask = await Task.WhenAny(processWaitTask, delayTask); - if (completedTask == processWaitTask) - { - return; - } - - cancellationToken.ThrowIfCancellationRequested(); - - try - { - var details = await helixApi.GetJobDetailsAsync(helixJobId, cancellationToken); - var workItems = details.WorkItems; - if (workItems.Unscheduled == 0 && workItems.Waiting == 0) - { - Console.WriteLine($"All work items are running"); - return; - } - - var elapsed = DateTime.UtcNow - startTime; - Console.WriteLine($"Job Time: {elapsed:hh\\:mm} Work Item States Running: {workItems.Running} Unscheduled: {workItems.Unscheduled} Waiting: {workItems.Waiting} Finished: {workItems.Finished}"); - - if (workItems.Waiting > 0 && elapsed > TimeSpan.FromMinutes(20)) - { - ConsoleUtil.Warning($"Helix job {helixJobId} has {details.WorkItems.Waiting} queued work items after {elapsed:hh\\:mm}. This indicates a queue backup"); - } - } - catch (Exception ex) - { - ConsoleUtil.Warning($"Error while polling Helix API for job status: {ex.Message}"); - } - - } while (true); - } - - static async Task WaitForHelixJob(HelixApi helixApi, string helixJobId, string testResultsDirectory, Task processWaitTask, CancellationToken cancellationToken) - { - var reportedTimeouts = new HashSet(); - do - { - var delayTask = Task.Delay(HelixPollTime, cancellationToken); - await Task.WhenAny(processWaitTask, delayTask); - - if (processWaitTask.IsCompleted) - { - return; - } - - cancellationToken.ThrowIfCancellationRequested(); - - try - { - var workItemList = await helixApi.GetWorkItemsAsync(helixJobId, cancellationToken); - var workItemGroups = workItemList.GroupBy(x => x.State).Select(g => $"{g.Key}: {g.Count()}"); - Console.WriteLine($"Work item states: {string.Join(", ", workItemGroups)}"); - - var timedOutWorkItems = new List(); - foreach (var workItem in workItemList.Where(x => x.State == "Running")) - { - var details = await helixApi.GetWorkItemDetailsAsync(helixJobId, workItem.Name, cancellationToken); - if (details.Finished is not null) - { - continue; - } - - if (HasTimedOut(details) && reportedTimeouts.Add(workItem.Name)) - { - ConsoleUtil.Warning($"Helix Job {helixJobId} Work item {workItem.Name} has been in state {details.State} for more than {WorkItemExecutionTimeout}."); - timedOutWorkItems.Add(workItem.Name); - } - } - - if (timedOutWorkItems.Count > 0) - { - WriteSyntheticTimeoutResults(testResultsDirectory, helixJobId, timedOutWorkItems); - } - } - catch (Exception ex) when (ex is not OperationCanceledException and not TimeoutException) - { - ConsoleUtil.Warning($"Error while using Helix API for job status: {ex.Message}"); - } - } while (true); - } - - // If a work item has been running for more than WorkItemExecutionTimeout, consider it timed out. - static bool HasTimedOut(HelixWorkItemDetails details) - { - if (details.Started is null) - { - return false; - } - - var started = DateTimeOffset.Parse(details.Started); - return DateTimeOffset.UtcNow - started > WorkItemExecutionTimeout; - } - - static void WriteSyntheticTimeoutResults(string testResultsDirectory, string helixJobId, List timedOutWorkItems) - { - try - { - if (!Directory.Exists(testResultsDirectory)) - { - Directory.CreateDirectory(testResultsDirectory); - } - - foreach (var workItemName in timedOutWorkItems) - { - var escapedWorkItemName = System.Security.SecurityElement.Escape(workItemName); - var escapedJobId = System.Security.SecurityElement.Escape(helixJobId); - var workItemUrl = System.Security.SecurityElement.Escape(HelixApi.GetWorkItemUrl(helixJobId, workItemName)); - var consoleUrl = System.Security.SecurityElement.Escape(HelixApi.GetWorkItemConsoleUrl(helixJobId, workItemName)); - var xml = $""" - - - - - - - Helix work item '{escapedWorkItemName}' in job '{escapedJobId}' exceeded the maximum execution time of {WorkItemExecutionTimeout}. - Work Item: {workItemUrl} - Console: {consoleUrl} - The work item was still in the Running state when the timeout was detected. - See {workItemUrl} for more details. - - - - - - """; - - var fileName = $"helix_timeout_{workItemName}.xml"; - var filePath = Path.Combine(testResultsDirectory, fileName); - File.WriteAllText(filePath, xml); - ConsoleUtil.WriteLine($"Wrote synthetic timeout result to {filePath}"); - } - } - catch (Exception ex) - { - ConsoleUtil.Warning($"Failed to write synthetic timeout test results: {ex.Message}"); - } - } - } - /// /// Creates the helix project file and payload artifacts on disk. Returns the path to the /// generated helix project file. @@ -344,10 +151,9 @@ internal static async Task CreateHelixArtifactsAsync(Options options, Im } /// - /// Constructs the dotnet build arguments, launches the process, and returns the process - /// along with a task that completes with the helix job id once it is parsed from process output. + /// Constructs the dotnet build arguments and launches the Helix submission process. /// - internal static (Process Process, Task<(string HelixJobId, string HelixJobCancellationToken)> HelixJobInfo) StartHelixJob(Options options, string helixProjectFilePath) + internal static Process StartHelixJob(Options options, string helixProjectFilePath) { var logsDir = Path.Combine(options.ArtifactsDirectory, "log", options.Configuration); var arguments = $"build -bl:{Path.Combine(logsDir, "helix.binlog")} {helixProjectFilePath}"; @@ -364,8 +170,6 @@ internal static (Process Process, Task<(string HelixJobId, string HelixJobCancel arguments += $" -p:Creator=\"{queuedBy}\""; } - var helixJobInfoSource = new TaskCompletionSource<(string HelixJobId, string HelixJobCancellationToken)>(TaskCreationOptions.RunContinuationsAsynchronously); - var process = new Process { StartInfo = new ProcessStartInfo @@ -385,15 +189,6 @@ internal static (Process Process, Task<(string HelixJobId, string HelixJobCancel return; ConsoleUtil.WriteLine(e.Data); - - if (!helixJobInfoSource.Task.IsCompleted) - { - var match = HelixJobInfoRegex().Match(e.Data); - if (match.Success) - { - helixJobInfoSource.TrySetResult((match.Groups[1].Value, match.Groups[2].Value)); - } - } }; process.ErrorDataReceived += (_, e) => @@ -408,7 +203,7 @@ internal static (Process Process, Task<(string HelixJobId, string HelixJobCancel process.BeginOutputReadLine(); process.BeginErrorReadLine(); - return (process, helixJobInfoSource.Task); + return process; } /// @@ -433,7 +228,7 @@ private static string GetHelixProjectFileContent( // setup makes running the RunTests program destructive to the environment variables // of the runner // - var sourceBranch = SetEnv("BUILD_SOURCEBRANCH", "local"); + _ = SetEnv("BUILD_SOURCEBRANCH", "local"); _ = SetEnv("BUILD_REPOSITORY_NAME", "dotnet/roslyn"); _ = SetEnv("SYSTEM_TEAMPROJECT", "dnceng-public"); _ = SetEnv("BUILD_REASON", "pr"); @@ -451,14 +246,13 @@ private static string GetHelixProjectFileContent( {jobName}_ - pr/{sourceBranch} test {buildNumber} {helixQueueName} true {dotnetSdkVersion} sdk - true + true @@ -473,13 +267,8 @@ private static string GetHelixProjectFileContent( builder.AppendLine(""" - - - + + diff --git a/src/Tools/RunTests/Options.cs b/src/Tools/RunTests/Options.cs index c4eea038c1dc6..454f7e34ae961 100644 --- a/src/Tools/RunTests/Options.cs +++ b/src/Tools/RunTests/Options.cs @@ -76,7 +76,7 @@ internal class Options public bool Sequential { get; set; } /// - /// Whether to run test partitions as Helix work items. + /// Whether to submit test partitions as Helix work items for the external job monitor. /// public bool UseHelix { get; set; } @@ -170,7 +170,7 @@ public Options( { "arch=", "Architecture to test on: x86, x64 or arm64", s => architecture = s }, { "html", "Include HTML file output", o => includeHtml = o is object }, { "sequential", "Run tests sequentially", o => sequential = o is object }, - { "helix", "Run tests on Helix", o => helix = o is object }, + { "helix", "Submit tests to Helix for the external job monitor", o => helix = o is object }, { "helixQueueName=", "Name of the Helix queue to run tests on", s => helixQueueName = s }, { "helixApiAccessToken=", "Access token for internal helix queues", s => helixApiAccessToken = s }, { "testfilter=", "xUnit string to pass to --filter, e.g. FullyQualifiedName~TestClass1|Category=CategoryA", s => testFilter = s },