| File: Helpers\TerminalRun.cs | Web Access |
| Project: src\tests\Aspire.Cli.EndToEnd.Tests\Aspire.Cli.EndToEnd.Tests.csproj (Aspire.Cli.EndToEnd.Tests) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using Hex1b.Automation; using Xunit; namespace Aspire.Cli.EndToEnd.Tests.Helpers; /// <summary> /// Wraps a terminal run session and ensures diagnostics are captured and the terminal is properly /// exited on disposal. Use via <see cref="CliE2ETestHelpers.StartRun"/> to consistently capture /// diagnostics at the end of every CLI E2E test. /// </summary> internal sealed class TerminalRun : IAsyncDisposable { private readonly Task _pendingRun; private readonly Hex1bTerminalAutomator _automator; private readonly SequenceCounter _counter; private readonly TemporaryWorkspace _workspace; private readonly ITestOutputHelper _output; internal TerminalRun(Task pendingRun, Hex1bTerminalAutomator automator, SequenceCounter counter, TemporaryWorkspace workspace, ITestOutputHelper output) { _pendingRun = pendingRun; _automator = automator; _counter = counter; _workspace = workspace; _output = output; } public async ValueTask DisposeAsync() { // Capture diagnostics (best effort) try { await _automator.CaptureAspireDiagnosticsAsync(_counter, _workspace); } catch { // Best effort diagnostics capture — don't mask the original test failure. } // Exit the terminal (best effort) try { await _automator.TypeAsync("exit"); await _automator.EnterAsync(); } catch { // Best effort exit — the terminal may already be closed. } // Wait for the terminal process to finish try { await _pendingRun; } catch { // Best effort — if the test body threw, we don't want to mask it. } // Copy workspace diagnostics to the host-side testresults directory so they appear // in CI artifacts. The in-Docker capture (CaptureAspireDiagnosticsAsync / EXIT trap) // writes files to the workspace volume mount, but that temp directory is not in the // CI-uploaded testresults/ path. This step bridges that gap. try { CaptureWorkspaceDiagnosticsToTestResults(); } catch { // Best effort — don't mask the original test failure. } } /// <summary> /// Copies the diagnostics directory from the workspace temp directory to the testresults path /// that CI uploads as artifacts. The in-Docker capture writes everything under a single /// <see cref="CliE2EAutomatorHelpers.DiagnosticsDirectoryName"/> subdirectory, so the host /// side only needs to copy that one directory. /// </summary> private void CaptureWorkspaceDiagnosticsToTestResults() { var diagnosticsSource = Path.Combine(_workspace.WorkspaceRoot.FullName, CliE2EAutomatorHelpers.DiagnosticsDirectoryName); if (!Directory.Exists(diagnosticsSource)) { WriteTestOutput($"[TerminalRun] No diagnostics directory found at: {diagnosticsSource}"); return; } var testName = TestContext.Current?.TestCase is { TestMethodName: { } methodName } ? methodName : "unknown"; var destDir = CliE2ETestHelpers.GetCaptureRootDirectory(testName); CopyDirectoryIfExists(diagnosticsSource, destDir); WriteTestOutput($"[TerminalRun] Captured diagnostics to: {destDir}"); WriteTestOutput($"[TerminalRun] Source workspace: {_workspace.WorkspaceRoot.FullName}"); // Report file counts per subdirectory so CI logs show what was actually captured. foreach (var subDir in Directory.GetDirectories(destDir)) { var fileCount = Directory.GetFiles(subDir, "*", SearchOption.AllDirectories).Length; WriteTestOutput($"[TerminalRun] {Path.GetFileName(subDir)}/: {fileCount} file(s)"); } // Count top-level files (e.g. aspire-start.json) var topLevelFiles = Directory.GetFiles(destDir); if (topLevelFiles.Length > 0) { WriteTestOutput($"[TerminalRun] (root): {topLevelFiles.Length} file(s)"); } } private static void CopyDirectoryIfExists(string source, string destination) { if (!Directory.Exists(source)) { return; } Directory.CreateDirectory(destination); foreach (var file in Directory.GetFiles(source)) { File.Copy(file, Path.Combine(destination, Path.GetFileName(file)), overwrite: true); } foreach (var dir in Directory.GetDirectories(source)) { CopyDirectoryIfExists(dir, Path.Combine(destination, Path.GetFileName(dir))); } } private void WriteTestOutput(string message) { _output.WriteLine(message); } }