// 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.Diagnostics; using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; #nullable enable namespace RunTests { public static class ProcessUtil { [DllImport("libc", SetLastError = true, EntryPoint = "kill")] private static extern int sys_kill(int pid, int sig); public static async Task RunAsync( string filename, string arguments, string? workingDirectory = null, bool throwOnError = true, IDictionary? environmentVariables = null, Action? outputDataReceived = null, Action? errorDataReceived = null, Action? onStart = null, CancellationToken cancellationToken = default) { Console.WriteLine($"Running '{filename} {arguments}'"); using var process = new Process() { StartInfo = { FileName = filename, Arguments = arguments, RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true, }, EnableRaisingEvents = true }; if (workingDirectory != null) { process.StartInfo.WorkingDirectory = workingDirectory; } if (environmentVariables != null) { foreach (var kvp in environmentVariables) { process.StartInfo.Environment.Add(kvp); } } var outputBuilder = new StringBuilder(); process.OutputDataReceived += (_, e) => { if (e.Data != null) { if (outputDataReceived != null) { outputDataReceived.Invoke(e.Data); } else { outputBuilder.AppendLine(e.Data); } } }; var errorBuilder = new StringBuilder(); process.ErrorDataReceived += (_, e) => { if (e.Data != null) { if (errorDataReceived != null) { errorDataReceived.Invoke(e.Data); } else { errorBuilder.AppendLine(e.Data); } } }; var processLifetimeTask = new TaskCompletionSource(); process.Exited += (_, e) => { Console.WriteLine($"'{process.StartInfo.FileName} {process.StartInfo.Arguments}' completed with exit code '{process.ExitCode}'"); if (throwOnError && process.ExitCode != 0) { processLifetimeTask.TrySetException(new InvalidOperationException($"Command {filename} {arguments} returned exit code {process.ExitCode}")); } else { processLifetimeTask.TrySetResult(new ProcessResult(outputBuilder.ToString(), errorBuilder.ToString(), process.ExitCode)); } }; process.Start(); onStart?.Invoke(process.Id); process.BeginOutputReadLine(); process.BeginErrorReadLine(); var cancelledTcs = new TaskCompletionSource(); await using var _ = cancellationToken.Register(() => cancelledTcs.TrySetResult(null)); var result = await Task.WhenAny(processLifetimeTask.Task, cancelledTcs.Task); if (result == cancelledTcs.Task) { if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { sys_kill(process.Id, sig: 2); // SIGINT var cancel = new CancellationTokenSource(); await Task.WhenAny(processLifetimeTask.Task, Task.Delay(TimeSpan.FromSeconds(5), cancel.Token)); cancel.Cancel(); } if (!process.HasExited) { process.CloseMainWindow(); if (!process.HasExited) { process.Kill(); } } } return await processLifetimeTask.Task; } public static void KillProcess(int pid) { try { using var process = Process.GetProcessById(pid); process?.Kill(); } catch (ArgumentException) { } catch (InvalidOperationException) { } } } }