재부팅 복구와 학습자 패널을 정리
This commit is contained in:
parent
dcb76a2373
commit
99779a6ab2
10 changed files with 442 additions and 25 deletions
|
|
@ -16,6 +16,160 @@ WHISPER_START = (SCRIPTS / "start-local-whisper-stt.ps1").read_text(
|
|||
|
||||
|
||||
class PublicRuntimeVoiceContractTest(unittest.TestCase):
|
||||
def test_recovery_is_serialized_and_lock_is_always_released(self) -> None:
|
||||
lock = PUBLIC_RUNTIME.index("$recoveryLock = Enter-RecoveryLock")
|
||||
main_try = PUBLIC_RUNTIME.index("try {", lock)
|
||||
first_runtime_mutation = PUBLIC_RUNTIME.index("Stop-UvicornByPort", main_try)
|
||||
finalizer = PUBLIC_RUNTIME.rindex("} finally {")
|
||||
dispose = PUBLIC_RUNTIME.index("$recoveryLock.Dispose()", finalizer)
|
||||
self.assertLess(lock, main_try)
|
||||
self.assertLess(main_try, first_runtime_mutation)
|
||||
self.assertLess(finalizer, dispose)
|
||||
self.assertIn(
|
||||
'"$env:LOCALAPPDATA\\Vignette\\public-runtime-start.lock"',
|
||||
PUBLIC_RUNTIME,
|
||||
)
|
||||
|
||||
def test_recovery_lock_is_exclusive_and_reusable_in_windows_powershell(self) -> None:
|
||||
powershell = shutil.which("powershell.exe")
|
||||
if powershell is None:
|
||||
self.skipTest("Windows PowerShell 5.1 is not available")
|
||||
|
||||
lock_function = PUBLIC_RUNTIME[
|
||||
PUBLIC_RUNTIME.index("function Enter-RecoveryLock") :
|
||||
PUBLIC_RUNTIME.index(
|
||||
"# boot task, watchdog, 수동 승격이 같은 포트와 프로세스를 동시에 교체하지 못하게 한다."
|
||||
)
|
||||
].strip()
|
||||
with tempfile.TemporaryDirectory() as temporary_directory:
|
||||
lock_path = str(Path(temporary_directory) / "runtime.lock").replace("'", "''")
|
||||
harness = Path(temporary_directory) / "recovery-lock.ps1"
|
||||
harness.write_text(
|
||||
lock_function
|
||||
+ f"""
|
||||
$first = Enter-RecoveryLock -LockPath '{lock_path}' -WaitSeconds 0
|
||||
try {{
|
||||
try {{
|
||||
$second = Enter-RecoveryLock -LockPath '{lock_path}' -WaitSeconds 0
|
||||
exit 2
|
||||
}} catch {{
|
||||
if ($_.Exception.Message -notlike '*already in progress*') {{ exit 3 }}
|
||||
}}
|
||||
}} finally {{
|
||||
$first.Dispose()
|
||||
}}
|
||||
$third = Enter-RecoveryLock -LockPath '{lock_path}' -WaitSeconds 0
|
||||
$third.Dispose()
|
||||
exit 0
|
||||
""",
|
||||
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_cold_start_waits_are_bounded_and_configurable(self) -> None:
|
||||
for expected in (
|
||||
"[int]$ApiReadySeconds = 180",
|
||||
"[int]$VoiceApiReadySeconds = 90",
|
||||
"[int]$WebBuildTimeoutSeconds = 600",
|
||||
"-TimeoutSec $ApiReadySeconds",
|
||||
"-TimeoutSec $VoiceApiReadySeconds",
|
||||
"$build.WaitForExit($WebBuildTimeoutSeconds * 1000)",
|
||||
"Web build timed out after $WebBuildTimeoutSeconds seconds",
|
||||
):
|
||||
with self.subTest(expected=expected):
|
||||
self.assertIn(expected, PUBLIC_RUNTIME)
|
||||
|
||||
def test_web_build_timeout_stops_the_owned_process_tree(self) -> None:
|
||||
powershell = shutil.which("powershell.exe")
|
||||
if powershell is None:
|
||||
self.skipTest("Windows PowerShell 5.1 is not available")
|
||||
|
||||
tree_functions = PUBLIC_RUNTIME[
|
||||
PUBLIC_RUNTIME.index("function Stop-ProcessTreeBounded") :
|
||||
PUBLIC_RUNTIME.index("function Get-UvicornProcessesByPort")
|
||||
].strip()
|
||||
with tempfile.TemporaryDirectory() as temporary_directory:
|
||||
harness = Path(temporary_directory) / "process-tree.ps1"
|
||||
child = Path(temporary_directory) / "child-sleeper.ps1"
|
||||
child_pid = Path(temporary_directory) / "child.pid"
|
||||
quoted_child_pid = str(child_pid).replace("'", "''")
|
||||
child.write_text(
|
||||
f"$PID | Set-Content -LiteralPath '{quoted_child_pid}' -Encoding ascii\n"
|
||||
"Start-Sleep -Seconds 300\n",
|
||||
encoding="utf-8-sig",
|
||||
)
|
||||
quoted_child = str(child).replace("'", "''")
|
||||
quoted_child_pid_for_harness = str(child_pid).replace("'", "''")
|
||||
harness.write_text(
|
||||
tree_functions
|
||||
+ f'''
|
||||
$root = Start-Process -FilePath 'cmd.exe' `
|
||||
-ArgumentList @('/c', 'powershell.exe -NoProfile -ExecutionPolicy Bypass -File "{quoted_child}"') `
|
||||
-WindowStyle Hidden `
|
||||
-PassThru
|
||||
try {{
|
||||
$deadline = (Get-Date).AddSeconds(10)
|
||||
while (-not (Test-Path -LiteralPath '{quoted_child_pid_for_harness}') -and (Get-Date) -lt $deadline) {{
|
||||
Start-Sleep -Milliseconds 100
|
||||
}}
|
||||
if (-not (Test-Path -LiteralPath '{quoted_child_pid_for_harness}')) {{ exit 2 }}
|
||||
$childProcessId = [int](Get-Content -LiteralPath '{quoted_child_pid_for_harness}' -Raw)
|
||||
$stopped = @(Stop-ProcessTreeBounded -RootProcessId $root.Id -TimeoutSec 10 -Role 'test tree')
|
||||
if ($null -ne (Get-Process -Id $root.Id -ErrorAction SilentlyContinue)) {{ exit 3 }}
|
||||
if ($null -ne (Get-Process -Id $childProcessId -ErrorAction SilentlyContinue)) {{ exit 4 }}
|
||||
exit 0
|
||||
}} finally {{
|
||||
Stop-Process -Id $root.Id -Force -ErrorAction SilentlyContinue
|
||||
}}
|
||||
''',
|
||||
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_public_provider_model_and_loopback_environment_are_explicit(self) -> None:
|
||||
for expected in (
|
||||
'$WhisperModel = "small"',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue