aps-agent/scripts/run-pi-real-workbook-e2e.ps1

160 lines
5.6 KiB
PowerShell
Raw Permalink Normal View History

<#
.SYNOPSIS
Run the explicitly opted-in real Pi CLI / real LLM / real workbook E2E.
.DESCRIPTION
Loads only the LLM_* values needed by the product runner, points every APS
persistence path at a fresh temporary artifact root, and invokes the opt-in
pytest module. It never starts or connects to the live 8000/5173 services.
#>
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$SourcePath,
[string]$ArtifactRoot,
[string]$Python,
[string]$PiCli,
[string]$NodeBin,
[int]$PiTimeoutSec = 300
)
$ErrorActionPreference = 'Stop'
$RepoRoot = (Resolve-Path -LiteralPath (Join-Path $PSScriptRoot '..')).Path
if ([string]::IsNullOrWhiteSpace($ArtifactRoot)) {
$stamp = Get-Date -Format 'yyyyMMdd-HHmmss'
$ArtifactRoot = Join-Path ([System.IO.Path]::GetTempPath()) ("aps-pi-real-e2e-" + $stamp)
}
$ArtifactRoot = [System.IO.Path]::GetFullPath($ArtifactRoot)
New-Item -ItemType Directory -Path $ArtifactRoot -Force | Out-Null
$ResolvedSource = (Resolve-Path -LiteralPath $SourcePath).Path
if (-not (Test-Path -LiteralPath $ResolvedSource -PathType Leaf)) {
throw "SourcePath must be a file: $ResolvedSource"
}
if ([string]::IsNullOrWhiteSpace($Python)) {
$venvPython = Join-Path $RepoRoot '.venv\Scripts\python.exe'
if (Test-Path -LiteralPath $venvPython -PathType Leaf) {
$Python = $venvPython
} else {
$Python = (Get-Command python -ErrorAction Stop).Source
}
}
if (-not (Test-Path -LiteralPath $Python -PathType Leaf)) {
throw "Python executable not found: $Python"
}
if ([string]::IsNullOrWhiteSpace($PiCli)) {
$PiCli = Join-Path $RepoRoot 'poc\pi-fallback\runtime\node_modules\@mariozechner\pi-coding-agent\dist\cli.js'
}
if (-not (Test-Path -LiteralPath $PiCli -PathType Leaf)) {
throw "Real Pi CLI not found: $PiCli"
}
if ([string]::IsNullOrWhiteSpace($NodeBin)) {
if (-not [string]::IsNullOrWhiteSpace($env:APS_FALLBACK_NODE)) {
$NodeBin = $env:APS_FALLBACK_NODE
} else {
$NodeBin = (Get-Command node -ErrorAction Stop).Source
}
}
if (-not (Test-Path -LiteralPath $NodeBin -PathType Leaf)) {
$resolvedNode = Get-Command $NodeBin -ErrorAction SilentlyContinue
if ($null -eq $resolvedNode) {
throw "Node executable not found: $NodeBin"
}
$NodeBin = $resolvedNode.Source
}
function Read-DotEnvValue {
param([string]$Path, [string]$Name)
if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) {
return $null
}
$pattern = '^\s*(?:export\s+)?' + [regex]::Escape($Name) + '\s*=\s*(.*)$'
foreach ($line in [System.IO.File]::ReadAllLines($Path)) {
if ($line -match $pattern) {
$value = $Matches[1].Trim()
if ($value.Length -ge 2) {
$first = $value.Substring(0, 1)
$last = $value.Substring($value.Length - 1, 1)
if (($first -eq '"' -and $last -eq '"') -or ($first -eq "'" -and $last -eq "'")) {
$value = $value.Substring(1, $value.Length - 2)
}
}
return $value
}
}
return $null
}
$DotEnv = Join-Path $RepoRoot '.env'
foreach ($name in @('LLM_PROVIDER', 'LLM_API_KEY', 'LLM_BASE_URL', 'LLM_MODEL')) {
if ([string]::IsNullOrWhiteSpace([Environment]::GetEnvironmentVariable($name))) {
$value = Read-DotEnvValue -Path $DotEnv -Name $name
if (-not [string]::IsNullOrWhiteSpace($value)) {
[Environment]::SetEnvironmentVariable($name, $value, 'Process')
}
}
}
if ([string]::IsNullOrWhiteSpace($env:LLM_BASE_URL) -or [string]::IsNullOrWhiteSpace($env:LLM_API_KEY)) {
throw "LLM_BASE_URL and LLM_API_KEY are required; fill the whitelisted values in .env or the caller environment."
}
$env:ROUND87_SOURCE = $ResolvedSource
$env:ROUND87_REQUIRED = '1'
$env:APS_REAL_PI_E2E = '1'
$env:APS_REAL_PI_E2E_ARTIFACT_DIR = $ArtifactRoot
$env:APS_FALLBACK_PI_CLI = $PiCli
$env:APS_FALLBACK_NODE = $NodeBin
$env:APS_FALLBACK_TIMEOUT_SEC = [string]$PiTimeoutSec
$env:APS_FALLBACK_EXEC_TIMEOUT_SEC = [string]$PiTimeoutSec
$PytestArgs = @(
'-m', 'pytest',
'tests\e2e\test_pi_real_workbook_e2e.py',
'-q', '-s', '--tb=short', '-p', 'no:cacheprovider'
)
$Reproduce = '.\scripts\run-pi-real-workbook-e2e.ps1 -SourcePath "$env:ROUND87_SOURCE" -ArtifactRoot "' + $ArtifactRoot + '"'
Write-Host "[real-pi-e2e] repo: $RepoRoot"
Write-Host "[real-pi-e2e] artifacts: $ArtifactRoot"
Write-Host "[real-pi-e2e] source: $ResolvedSource"
Write-Host "[real-pi-e2e] pi cli: $PiCli"
Write-Host "[real-pi-e2e] llm configured: $([bool]$env:LLM_API_KEY) provider=$($env:LLM_PROVIDER) model=$($env:LLM_MODEL)"
Write-Host "[real-pi-e2e] live services: not used; in-process TestClient only"
Push-Location -LiteralPath $RepoRoot
try {
& $Python $PytestArgs
$ExitCode = $LASTEXITCODE
} finally {
Pop-Location
}
if ($ExitCode -ne 0) {
Write-Host "[real-pi-e2e] FAILED exit=$ExitCode"
Write-Host "[real-pi-e2e] artifacts preserved: $ArtifactRoot"
Write-Host "[real-pi-e2e] reproduce: $Reproduce"
exit $ExitCode
}
Write-Host "[real-pi-e2e] PASSED"
# 运行时目录(Pi 会话 / 临时工作目录)可能在断言结束后被回收:
# 汇总查找必须是尽力而为,否则会把已通过的验收误报成脚本失败。
$Summary = $null
try {
$Summary = Get-ChildItem -LiteralPath $ArtifactRoot -Recurse -Filter 'latest-summary.json' -File `
-ErrorAction SilentlyContinue |
Sort-Object LastWriteTime -Descending | Select-Object -First 1
} catch {
$Summary = $null
}
if ($null -ne $Summary) {
Write-Host "[real-pi-e2e] evidence summary: $($Summary.FullName)"
} else {
Write-Host '[real-pi-e2e] evidence summary: <not found; inspect the artifact root>'
}