From 527027b93d2109531454e538ede9430d67e90422 Mon Sep 17 00:00:00 2001 From: Yun Chan Date: Wed, 12 Aug 2026 11:42:46 +0900 Subject: [PATCH] =?UTF-8?q?=EC=9B=8C=EC=B9=98=EB=8F=85=20=EC=82=AC?= =?UTF-8?q?=EC=9D=B4=EB=93=9C=EC=B9=B4=20=EC=9E=A5=EC=95=A0=20=EB=B3=B5?= =?UTF-8?q?=EA=B5=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...test_public_runtime_watchdog_provenance.py | 55 +++++++++++++++++++ scripts/watch-public-runtime.ps1 | 13 ++++- 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/scripts/test_public_runtime_watchdog_provenance.py b/scripts/test_public_runtime_watchdog_provenance.py index fb98ad6..86dee61 100644 --- a/scripts/test_public_runtime_watchdog_provenance.py +++ b/scripts/test_public_runtime_watchdog_provenance.py @@ -182,6 +182,61 @@ $local = [pscustomobject]@{ if (-not (Test-VoiceApiReady -Health $local)) { throw 'exact local API was rejected' } $local.limits.uvicorn_ws_max_queue = 5 if (Test-VoiceApiReady -Health $local) { throw 'wrong websocket queue was accepted' } +''', + encoding="utf-8-sig", + ) + completed = subprocess.run( + [ + powershell, + "-NoLogo", + "-NoProfile", + "-NonInteractive", + "-ExecutionPolicy", + "Bypass", + "-File", + str(harness), + ], + capture_output=True, + text=True, + encoding="utf-8", + errors="replace", + timeout=30, + check=False, + ) + self.assertEqual( + completed.returncode, + 0, + msg=f"stdout={completed.stdout}\nstderr={completed.stderr}", + ) + + def test_failed_native_sidecar_probe_returns_unhealthy_under_powershell_51(self) -> None: + powershell = shutil.which("powershell.exe") + python = shutil.which("python.exe") + if powershell is None or python is None: + self.skipTest("Windows PowerShell 5.1 and Python are required") + + watchdog_contract = WATCHDOG_SOURCE[ + WATCHDOG_SOURCE.index("function Test-VoiceSidecarStack") : + WATCHDOG_SOURCE.index("function Test-CloudflaredProcess") + ].strip() + + with tempfile.TemporaryDirectory() as temporary_directory: + harness = Path(temporary_directory) / "voice-sidecar-failure-contract.ps1" + python_escaped = python.replace("'", "''") + probe_escaped = str(VOICE_PROBE).replace("'", "''") + harness.write_text( + "$ErrorActionPreference = 'Stop'\n" + + f"$Python = '{python_escaped}'\n" + + f"$voiceSidecarProbe = '{probe_escaped}'\n" + + "$WhisperPort = 1\n" + + "$MeloTtsPort = 2\n" + + watchdog_contract + + r''' +$result = Test-VoiceSidecarStack +if ($result.Ok) { throw 'unreachable sidecars were accepted' } +if ($result.Detail -ne 'exact readiness probe failed') { + throw "unexpected detail: $($result.Detail)" +} ''', encoding="utf-8-sig", ) diff --git a/scripts/watch-public-runtime.ps1 b/scripts/watch-public-runtime.ps1 index 48e2a70..6513125 100644 --- a/scripts/watch-public-runtime.ps1 +++ b/scripts/watch-public-runtime.ps1 @@ -242,8 +242,17 @@ function Test-VoiceSidecarStack { "--tts-language", "KR", "--timeout-seconds", "5" ) - $output = @(& $Python @probeArgs 2>$null) - $probeExit = $LASTEXITCODE + # Windows PowerShell 5.1은 native stderr를 ErrorRecord로 승격한다. 스크립트 + # 전역의 Stop 정책을 그대로 두면 sidecar 장애를 복구 신호로 반환하기 전에 + # NativeCommandError로 종료하므로, 이 단일 probe 경계에서만 Continue로 낮춘다. + $previousErrorActionPreference = $ErrorActionPreference + try { + $ErrorActionPreference = "Continue" + $output = @(& $Python @probeArgs 2>$null) + $probeExit = $LASTEXITCODE + } finally { + $ErrorActionPreference = $previousErrorActionPreference + } return [pscustomobject]@{ Name = "voice-sidecars" Ok = $probeExit -eq 0