namespace StellaOps.Interop.Tests; using System.Diagnostics; using System.Text; /// /// Manages execution of external tools for interop testing. /// public sealed class ToolManager { private readonly string _workDir; public ToolManager(string workDir) { _workDir = workDir; } /// /// Verify that a tool is available and executable. /// public async Task VerifyToolAsync(string toolName, string testArgs, CancellationToken ct = default) { try { var result = await RunAsync(toolName, testArgs, ct); return result.Success || result.ExitCode == 0; // Some tools return 0 even on --version } catch { return false; } } /// /// Run an external tool with arguments. /// public async Task RunAsync( string toolName, string arguments, CancellationToken ct = default, int timeoutMs = 300000) // 5 minute default timeout { var startInfo = new ProcessStartInfo { FileName = toolName, Arguments = arguments, WorkingDirectory = _workDir, RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true }; using var process = new Process { StartInfo = startInfo }; var outputBuilder = new StringBuilder(); var errorBuilder = new StringBuilder(); process.OutputDataReceived += (sender, e) => { if (e.Data != null) outputBuilder.AppendLine(e.Data); }; process.ErrorDataReceived += (sender, e) => { if (e.Data != null) errorBuilder.AppendLine(e.Data); }; try { process.Start(); process.BeginOutputReadLine(); process.BeginErrorReadLine(); using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct); cts.CancelAfter(timeoutMs); await process.WaitForExitAsync(cts.Token); var output = outputBuilder.ToString(); var error = errorBuilder.ToString(); var exitCode = process.ExitCode; return new ToolResult( Success: exitCode == 0, ExitCode: exitCode, Output: output, Error: string.IsNullOrWhiteSpace(error) ? null : error); } catch (OperationCanceledException) { try { if (!process.HasExited) process.Kill(); } catch { // Ignore kill failures } return new ToolResult( Success: false, ExitCode: -1, Output: outputBuilder.ToString(), Error: $"Tool execution timed out after {timeoutMs}ms"); } catch (Exception ex) { return new ToolResult( Success: false, ExitCode: -1, Output: outputBuilder.ToString(), Error: $"Tool execution failed: {ex.Message}"); } } } public sealed record ToolResult( bool Success, int ExitCode, string Output, string? Error = null);