param( [string]$HostName = "127.0.0.1", [int]$Port = 8384, [double]$Minutes = 30, [int]$Pairs = 2, [string]$CliDll = "$PSScriptRoot/../src/VoiceCat.Cli/bin/Release/net10.0/VoiceCat.Cli.dll" ) $ErrorActionPreference = "Stop" if ($Minutes -le 0 -or $Pairs -lt 1) { throw "Minutes and Pairs must be positive." } $tempRoot = [IO.Path]::GetFullPath([IO.Path]::GetTempPath()) $work = [IO.Path]::GetFullPath((Join-Path $tempRoot ("voicecat-soak-" + [Guid]::NewGuid().ToString("N")))) if (-not $work.StartsWith($tempRoot, [StringComparison]::OrdinalIgnoreCase) -or (Split-Path $work -Leaf) -notlike 'voicecat-soak-*') { throw "Unsafe soak workspace path." } [IO.Directory]::CreateDirectory($work) | Out-Null $deadline = [DateTime]::UtcNow.AddMinutes($Minutes) $cycles = 0 try { while ([DateTime]::UtcNow -lt $deadline) { $processes = @() for ($pair = 0; $pair -lt $Pairs; $pair++) { $stamp = "$cycles-$pair" foreach ($side in 0,1) { $name = "Soak-$stamp-$side"; $send = "message-$stamp-$side"; $expect = "message-$stamp-$([int](1-$side))" $start = [Diagnostics.ProcessStartInfo]::new("dotnet") $start.UseShellExecute = $false; $start.RedirectStandardOutput = $true; $start.RedirectStandardError = $true; $start.CreateNoWindow = $true $arguments = @($CliDll,"--host",$HostName,"--port","$Port","--nickname",$name,"--pins",(Join-Path $work "$name.pins"),"--trust-first","--voice","--expect-voice","--send-text",$send,"--expect-text",$expect,"--start-delay-ms","1000","--timeout-seconds","20") $start.Arguments = ($arguments | ForEach-Object { '"' + $_.Replace('"','\"') + '"' }) -join ' ' $processes += [Diagnostics.Process]::Start($start) } } foreach ($process in $processes) { $stdout = $process.StandardOutput.ReadToEndAsync(); $stderr = $process.StandardError.ReadToEndAsync() if (-not $process.WaitForExit(30000)) { $process.Kill($true); throw "Soak client timed out." } if ($process.ExitCode -ne 0) { throw "Soak client failed: $($stderr.Result) $($stdout.Result)" } $process.Dispose() } $cycles++ Write-Host "Completed soak cycle $cycles ($($Pairs * 2) clients)." } Write-Host "Soak passed: $cycles cycles, $($cycles * $Pairs * 2) client sessions." } finally { if (Test-Path $work) { Remove-Item -LiteralPath $work -Recurse -Force } }