using System; using System.IO; using System.Threading; using System.Threading.Tasks; using Paca.Core.Platform; using Xunit; namespace Paca.Tests.Core; /// /// Cycle 5: 극한 RED 시나리오 - 프로세스 실행 시 따옴표 감싸인 ExePath, Null 인자 방어 및 고속 취소 시 스트림 무결성 검증 /// public class ProcessExecutionExtremeRedTests { [Fact] public async Task Cycle5_Gate1_DesktopProcessRunner_QuotedExePath_MustBeUnquotedAndExecute() { // Windows 에서 UseShellExecute=false 일 때 ExePath 에 따옴표가 있으면 Win32Exception 발생 // DesktopProcessRunner 는 따옴표를 자동 정제하여 정상 실행해야 함 var runner = new DesktopProcessRunner(); var cmdExe = Environment.GetEnvironmentVariable("ComSpec") ?? "cmd.exe"; var quotedCmd = $"\"{cmdExe}\""; var spec = new ProcessSpec(quotedCmd, new[] { "/c", "echo", "PACA_DESKTOP_PROCESS_RUNNER_OK" }); var result = await runner.RunAsync(spec, null, CancellationToken.None); Assert.Equal(0, result.ExitCode); Assert.Contains("PACA_DESKTOP_PROCESS_RUNNER_OK", result.Stdout); } [Fact] public async Task Cycle5_Gate2_DesktopProcessRunner_NullArgsInSpec_MustNotThrowArgumentNullException() { var runner = new DesktopProcessRunner(); var cmdExe = Environment.GetEnvironmentVariable("ComSpec") ?? "cmd.exe"; // 동적 인자 빌더 등에서 전달될 수 있는 null 원소 방어 var args = new string[] { "/c", "echo", null!, "NULL_ARG_DEFENSE_OK" }; var spec = new ProcessSpec(cmdExe, args); var result = await runner.RunAsync(spec, null, CancellationToken.None); Assert.Equal(0, result.ExitCode); Assert.Contains("NULL_ARG_DEFENSE_OK", result.Stdout); } [Fact] public async Task Cycle5_Gate3_DesktopProcessRunner_FastCancellation_MustKillTreeWithoutDisposedStreamFault() { var runner = new DesktopProcessRunner(); var cmdExe = Environment.GetEnvironmentVariable("ComSpec") ?? "cmd.exe"; // 10초간 대기하는 자식 프로세스 var spec = new ProcessSpec(cmdExe, new[] { "/c", "ping", "127.0.0.1", "-n", "10" }); using var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(50)); // 고속 취소 발생 시 ObjectDisposedException 이나 UnobservedTaskException 없이 OperationCanceledException 으로 종료 await Assert.ThrowsAnyAsync(async () => { await runner.RunAsync(spec, null, cts.Token); }); } }