[CmdletBinding()] param([Parameter(Mandatory=$true)][string]$PythonPath, [Parameter(Mandatory=$true)][string]$TestRoot) . (Join-Path $PSScriptRoot 'TaskHost.Common.ps1') if (Test-Path -LiteralPath $TestRoot) { throw '请选择新的隔离测试目录,避免覆盖现有文件。' } $root = [IO.Path]::GetFullPath($TestRoot) $app = Join-Path $root 'backend/app' New-Item -ItemType Directory -Path $app -Force | Out-Null '' | Set-Content -LiteralPath (Join-Path $app '__init__.py') -Encoding UTF8 @' import os,sys,time from pathlib import Path if 'tplus-maintain' in sys.argv: raise SystemExit(7) stop=Path(os.environ['T_PLUS_STOP_FILE']) stop.parent.mkdir(parents=True,exist_ok=True) (stop.parent/'worker.started').write_text('ready',encoding='utf-8') while not stop.exists(): time.sleep(0.05) (stop.parent/'worker.stopped').write_text('stopped',encoding='utf-8') '@ | Set-Content -LiteralPath (Join-Path $app 'cli.py') -Encoding UTF8 $connection = Join-Path $root 'dummy reader.json' '{}' | Set-Content -LiteralPath $connection -Encoding UTF8 $runtime = Join-Path $root 'runtime with spaces' $shell = Join-Path $env:WINDIR 'System32/WindowsPowerShell/v1.0/powershell.exe' $launcher = Join-Path $PSScriptRoot 'Run-PayrollHost.ps1' function Start-TestHost([string]$Role) { $argsText = '-NoProfile -NonInteractive -File ' + (Quote-TaskArgument $launcher) + ' -Role ' + $Role + ' -ProjectRoot ' + (Quote-TaskArgument $root) + ' -PythonPath ' + (Quote-TaskArgument $PythonPath) + ' -RuntimeDirectory ' + (Quote-TaskArgument $runtime) + ' -ConnectionFile ' + (Quote-TaskArgument $connection) $child = Start-Process -FilePath $shell -ArgumentList $argsText -WindowStyle Hidden -PassThru ` -RedirectStandardOutput (Join-Path $root "$Role-host.out.log") -RedirectStandardError (Join-Path $root "$Role-host.err.log") $null = $child.Handle return $child } $taskHostProcess = Start-TestHost 'worker' $deadline = (Get-Date).AddSeconds(20) while (-not (Test-Path -LiteralPath (Join-Path $runtime 'worker.started')) -and (Get-Date) -lt $deadline) { if ($taskHostProcess.HasExited) { throw '隔离worker未能启动,请检查测试目录日志。' } Start-Sleep -Milliseconds 100 } if (-not (Test-Path -LiteralPath (Join-Path $runtime 'worker.started'))) { throw '等待隔离worker启动超时。' } 'stop' | Set-Content -LiteralPath (Join-Path $runtime 'stop.requested') -Encoding UTF8 if (-not $taskHostProcess.WaitForExit(15000)) { throw '停止标记未能结束隔离worker。' } if ($taskHostProcess.ExitCode -ne 0 -or -not (Test-Path -LiteralPath (Join-Path $runtime 'worker.stopped'))) { throw '隔离worker未正常停止。' } Remove-Item -LiteralPath (Join-Path $runtime 'stop.requested') $failureProcess = Start-TestHost 'maintenance' if (-not $failureProcess.WaitForExit(15000)) { throw '隔离失败进程未退出。' } if ($failureProcess.ExitCode -ne 7) { throw '启动器没有保留子进程失败退出码。' } Write-Output 'Windows PowerShell5 隔离托管验证通过:路径含空格、隐藏子进程启动、停止标记退出、失败退出码传递;未注册计划任务或连接业务数据库。'