160 lines
5.6 KiB
PowerShell
160 lines
5.6 KiB
PowerShell
<#
|
||
.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>'
|
||
}
|