| File: Pipelines\PipelineOutputGitTestHelper.cs | Web Access |
| Project: src\tests\Aspire.Hosting.Tests\Aspire.Hosting.Tests.csproj (Aspire.Hosting.Tests) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.ComponentModel; using System.Diagnostics; using Aspire.Hosting.Pipelines; using Microsoft.Extensions.FileSystemGlobbing; namespace Aspire.Hosting.Tests.Pipelines; internal static class PipelineOutputGitTestHelper { internal static Matcher AllFilesMatcher() { var matcher = new Matcher(PipelineOutputRoot.PathComparison, preserveFilterOrder: true); matcher.AddInclude("**/*"); return matcher; } internal static async Task<string> RunAsync(string directory, params string[] arguments) { using var process = new Process { StartInfo = new ProcessStartInfo("git", arguments) { WorkingDirectory = directory, RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true } }; // Git setup must write only this test's index and configuration, even with ambient Git overrides. process.StartInfo.Environment["GIT_DIR"] = Path.Combine(directory, ".git"); process.StartInfo.Environment["GIT_WORK_TREE"] = directory; process.StartInfo.Environment["GIT_INDEX_FILE"] = Path.Combine(directory, ".git", "index"); process.StartInfo.Environment["GIT_CONFIG_COUNT"] = "0"; foreach (var name in new[] { "GIT_COMMON_DIR", "GIT_CONFIG", "GIT_CONFIG_PARAMETERS" }) { process.StartInfo.Environment.Remove(name); } return await RunProcessAsync(process); } internal static async Task CreateDirectoryLinkAsync(string path, string target, bool junction) { if (!junction) { Directory.CreateSymbolicLink(path, target); return; } using var process = new Process { StartInfo = new ProcessStartInfo("cmd.exe", ["/c", "mklink", "/J", path, target]) { RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true } }; await RunProcessAsync(process); } private static async Task<string> RunProcessAsync(Process process) { using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(30)); using var cancellation = CancellationTokenSource.CreateLinkedTokenSource(timeout.Token, TestContext.Current.CancellationToken); process.Start(); var stdout = process.StandardOutput.ReadToEndAsync(cancellation.Token); var stderr = process.StandardError.ReadToEndAsync(cancellation.Token); try { await Task.WhenAll(process.WaitForExitAsync(cancellation.Token), stdout, stderr); } catch (Exception failure) { try { if (!process.HasExited) { process.Kill(entireProcessTree: true); } } catch (Exception race) when ((race is InvalidOperationException or Win32Exception) && process.HasExited) { failure.Data["ProcessTermination"] = race.Message; } await process.WaitForExitAsync(CancellationToken.None).WaitAsync(TimeSpan.FromSeconds(5)); throw; } Assert.True(process.ExitCode == 0, await stderr); return await stdout; } }