/
githubmirror
/
aspnetcore
Обзор
Документация
Войти
/
githubmirror
/
aspnetcore
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Shared/Process/ProcessEx.cs
260 строк
8 KB
Stephen Halter
Update Identity Components in Blazor project template (#51134)
12 окт 2023, 02:25
Не верифицирован
12 окт 2023, 02:25
4a957dd
Код
Авторство
О чём код?
// 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.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Internal; using Xunit; using Xunit.Abstractions; namespace Microsoft.AspNetCore.Internal; internal sealed class ProcessEx : IDisposable { private static readonly TimeSpan DefaultProcessTimeout = TimeSpan.FromMinutes(15); private static readonly string NUGET_PACKAGES = GetNugetPackagesRestorePath(); private readonly ITestOutputHelper _output; private readonly Process _process; private readonly StringBuilder _stderrCapture; private readonly StringBuilder _stdoutCapture; private readonly object _pipeCaptureLock = new object(); private readonly object _testOutputLock = new object(); private BlockingCollection<string> _stdoutLines; private readonly TaskCompletionSource<int> _exited; private readonly CancellationTokenSource _stdoutLinesCancellationSource = new CancellationTokenSource(TimeSpan.FromMinutes(5)); private readonly CancellationTokenSource _processTimeoutCts; private bool _disposed; public ProcessEx(ITestOutputHelper output, Process proc, TimeSpan timeout) { _output = output; _stdoutCapture = new StringBuilder(); _stderrCapture = new StringBuilder(); _stdoutLines = new BlockingCollection<string>(); _exited = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously); _process = proc; proc.EnableRaisingEvents = true; proc.OutputDataReceived += OnOutputData; proc.ErrorDataReceived += OnErrorData; proc.Exited += OnProcessExited; proc.BeginOutputReadLine(); proc.BeginErrorReadLine(); if (proc.HasExited) { OnProcessExited(); } // We greedily create a timeout exception message even though a timeout is unlikely to happen for two reasons: // 1. To make it less likely for Process getters to throw exceptions like "System.InvalidOperationException: Process has exited, ..." // 2. To ensure if/when exceptions are thrown from Process getters, these exceptions can easily be observed. var timeoutExMessage = $"Process proc {proc.ProcessName} {proc.StartInfo.Arguments} timed out after {timeout}."; _processTimeoutCts = new CancellationTokenSource(timeout); _processTimeoutCts.Token.Register(() => { _exited.TrySetException(new TimeoutException(timeoutExMessage)); }); } public Process Process => _process; public Task Exited => _exited.Task; public bool HasExited => _process.HasExited; public string Error { get { lock (_pipeCaptureLock) { return _stderrCapture.ToString(); } } } public string Output { get { lock (_pipeCaptureLock) { return _stdoutCapture.ToString(); } } } public IEnumerable<string> OutputLinesAsEnumerable => _stdoutLines.GetConsumingEnumerable(_stdoutLinesCancellationSource.Token); public int ExitCode => _process.ExitCode; public object Id => _process.Id; public static ProcessEx Run(ITestOutputHelper output, string workingDirectory, string command, string args = null, IDictionary<string, string> envVars = null, TimeSpan? timeout = default) { var startInfo = new ProcessStartInfo(command, args) { RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true, WorkingDirectory = workingDirectory }; if (envVars != null) { foreach (var envVar in envVars) { startInfo.EnvironmentVariables[envVar.Key] = envVar.Value; } } startInfo.EnvironmentVariables["NUGET_PACKAGES"] = NUGET_PACKAGES; if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("helix"))) { startInfo.EnvironmentVariables["NUGET_FALLBACK_PACKAGES"] = Environment.GetEnvironmentVariable("NUGET_FALLBACK_PACKAGES"); } output.WriteLine($"==> {startInfo.FileName} {startInfo.Arguments} [{startInfo.WorkingDirectory}]"); var proc = Process.Start(startInfo); return new ProcessEx(output, proc, timeout ?? DefaultProcessTimeout); } private void OnErrorData(object sender, DataReceivedEventArgs e) { if (e.Data == null) { return; } lock (_pipeCaptureLock) { _stderrCapture.AppendLine(e.Data); } lock (_testOutputLock) { if (!_disposed) { _output.WriteLine("[ERROR] " + e.Data); } } } private void OnOutputData(object sender, DataReceivedEventArgs e) { if (e.Data == null) { return; } lock (_pipeCaptureLock) { _stdoutCapture.AppendLine(e.Data); } lock (_testOutputLock) { if (!_disposed) { _output.WriteLine(e.Data); } } _stdoutLines?.Add(e.Data); } private void OnProcessExited(object sender = null, EventArgs e = null) { lock (_testOutputLock) { if (!_disposed) { _output.WriteLine("Process exited."); } } // Don't remove this line - There is a race condition where the process exits and we grab the output before the stdout/stderr completed writing. _process.WaitForExit(); _stdoutLines?.CompleteAdding(); _stdoutLines = null; _exited.TrySetResult(_process.ExitCode); } internal string GetFormattedOutput() { if (!_process.HasExited) { Assert.Fail($"Process {_process.ProcessName} with pid: {_process.Id} has not finished running."); } return $"Process exited with code {_process.ExitCode}\nStdErr: {Error}\nStdOut: {Output}"; } public void WaitForExit(bool assertSuccess, TimeSpan? timeSpan = null) { if (!timeSpan.HasValue) { timeSpan = TimeSpan.FromSeconds(600); } var exited = Exited.Wait(timeSpan.Value); if (!exited) { lock (_testOutputLock) { _output.WriteLine($"The process didn't exit within the allotted time ({timeSpan.Value.TotalSeconds} seconds)."); } _process.Dispose(); } else if (assertSuccess && _process.ExitCode != 0) { Assert.Fail($"Process exited with code {_process.ExitCode}\nStdErr: {Error}\nStdOut: {Output}"); } } private static string GetNugetPackagesRestorePath() => (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("NUGET_RESTORE"))) ? typeof(ProcessEx).Assembly .GetCustomAttributes<AssemblyMetadataAttribute>() .FirstOrDefault(attribute => attribute.Key == "TestPackageRestorePath") ?.Value : Environment.GetEnvironmentVariable("NUGET_RESTORE"); public void Dispose() { _processTimeoutCts.Dispose(); lock (_testOutputLock) { _disposed = true; } if (_process != null && !_process.HasExited) { _process.KillTree(); } if (_process != null) { _process.CancelOutputRead(); _process.CancelErrorRead(); _process.ErrorDataReceived -= OnErrorData; _process.OutputDataReceived -= OnOutputData; _process.Exited -= OnProcessExited; _process.Dispose(); } } }