Files
ComfyUI-OpenClaw/scripts/run_full_tests_windows.ps1
T

323 lines
11 KiB
PowerShell

Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
$root = Split-Path -Parent $MyInvocation.MyCommand.Path | Split-Path -Parent
Set-Location $root
Write-Host "[tests] repo: $root"
# Cache isolation for pre-commit + black
$env:PRE_COMMIT_HOME = if ($env:PRE_COMMIT_HOME) { $env:PRE_COMMIT_HOME } else { "$root\.tmp\pre-commit-win" }
$env:BLACK_CACHE_DIR = if ($env:BLACK_CACHE_DIR) { $env:BLACK_CACHE_DIR } else { "$root\.tmp\black-cache" }
New-Item -ItemType Directory -Force $env:PRE_COMMIT_HOME | Out-Null
New-Item -ItemType Directory -Force $env:BLACK_CACHE_DIR | Out-Null
function Require-Cmd($cmd) {
if (-not (Get-Command $cmd -ErrorAction SilentlyContinue)) {
throw "[tests] ERROR: missing command: $cmd"
}
}
function Invoke-Checked {
param(
[Parameter(Mandatory = $true)][string]$Label,
[Parameter(Mandatory = $true)][scriptblock]$Command
)
& $Command
if ($LASTEXITCODE -ne 0) {
throw "[tests] ERROR: $Label failed with exit code $LASTEXITCODE"
}
}
function Get-GitDiffSnapshot {
param([switch]$Cached)
# IMPORTANT: compare both worktree and index; pre-commit can mutate staged files while exiting 0.
if ($Cached) {
return (& git diff --cached --binary -- . | Out-String)
}
return (& git diff --binary -- . | Out-String)
}
function Assert-PreCommitDidNotMutateRepo {
param(
[Parameter(Mandatory = $true)][string]$BeforeWorktree,
[Parameter(Mandatory = $true)][string]$BeforeIndex
)
$afterWorktree = Get-GitDiffSnapshot
$afterIndex = Get-GitDiffSnapshot -Cached
if ($BeforeWorktree -ne $afterWorktree -or $BeforeIndex -ne $afterIndex) {
throw "[tests] ERROR: pre-commit hooks modified tracked files (worktree or index). Review/stage the hook changes, then rerun the acceptance gate.`n$(& git status --short | Out-String)"
}
}
Require-Cmd node
Require-Cmd npm
function Ensure-NpmDeps {
$playwrightPkg = Join-Path $root "node_modules\@playwright\test\package.json"
if (Test-Path $playwrightPkg) {
return
}
Write-Host "[tests] Installing frontend dependencies via npm install ..."
Invoke-Checked "npm install" { npm install }
}
# Prefer project-local virtualenv to avoid global PATH / cache conflicts on Windows.
$venvPython = Join-Path $root ".venv\Scripts\python.exe"
function New-ProjectVenv {
Write-Host "[tests] Creating project venv at $root\.venv ..."
if (Get-Command py -ErrorAction SilentlyContinue) {
& py -3 -m venv .venv
}
elseif (Get-Command python -ErrorAction SilentlyContinue) {
& python -m venv .venv
}
else {
throw "[tests] ERROR: no bootstrap Python found (need py or python)"
}
}
function Test-VenvPython {
param([string]$PythonExe)
if (-not (Test-Path $PythonExe)) {
return $false
}
try {
& $PythonExe -c "import sys; print(sys.executable)" | Out-Null
return $true
}
catch {
return $false
}
}
function Test-VenvCfgWindowsCompatible {
$cfg = Join-Path $root ".venv\pyvenv.cfg"
if (-not (Test-Path $cfg)) {
return $false
}
try {
$content = Get-Content $cfg -Raw
# WSL/Linux-built venvs typically contain POSIX home paths (e.g. /usr/bin)
if ($content -match "home\s*=\s*/") {
return $false
}
return $true
}
catch {
return $false
}
}
if (-not (Test-Path $venvPython)) {
New-ProjectVenv
}
elseif (-not (Test-VenvCfgWindowsCompatible)) {
Write-Host "[tests] WARN: existing .venv was created from non-Windows interpreter; recreating ..."
Remove-Item -Recurse -Force ".venv"
New-ProjectVenv
}
elseif (-not (Test-VenvPython -PythonExe $venvPython)) {
Write-Host "[tests] WARN: existing .venv is invalid for current OS/interpreter; recreating ..."
Remove-Item -Recurse -Force ".venv"
New-ProjectVenv
}
if (-not (Test-VenvPython -PythonExe $venvPython)) {
throw "[tests] ERROR: project venv python is not runnable: $venvPython"
}
$hasPreCommit = $true
& $venvPython -m pre_commit --version | Out-Null
if ($LASTEXITCODE -ne 0) {
$hasPreCommit = $false
}
if (-not $hasPreCommit) {
Write-Host "[tests] Installing pre-commit into project venv ..."
Invoke-Checked "pip install pre-commit" { & $venvPython -m pip install -U pip pre-commit }
}
$hasAiohttp = $true
& $venvPython -c "import aiohttp" | Out-Null
if ($LASTEXITCODE -ne 0) {
$hasAiohttp = $false
}
if (-not $hasAiohttp) {
Write-Host "[tests] Installing aiohttp into project venv ..."
Invoke-Checked "pip install aiohttp" { & $venvPython -m pip install aiohttp }
}
$hasCrypto = $true
& $venvPython -c "import Cryptodome" | Out-Null
if ($LASTEXITCODE -ne 0) {
$hasCrypto = $false
}
if (-not $hasCrypto) {
Write-Host "[tests] Installing pycryptodomex into project venv (R82 AES) ..."
Invoke-Checked "pip install pycryptodomex" { & $venvPython -m pip install pycryptodomex }
}
$hasCryptography = $true
& $venvPython -c "import cryptography" | Out-Null
if ($LASTEXITCODE -ne 0) {
$hasCryptography = $false
}
if (-not $hasCryptography) {
Write-Host "[tests] Installing cryptography into project venv (S57 Fernet AEAD) ..."
Invoke-Checked "pip install cryptography" { & $venvPython -m pip install cryptography }
}
$hasDefusedXml = $true
& $venvPython -c "import defusedxml" | Out-Null
if ($LASTEXITCODE -ne 0) {
$hasDefusedXml = $false
}
if (-not $hasDefusedXml) {
# IMPORTANT: keep Windows full-test bootstrap aligned with requirements.txt.
Write-Host "[tests] Installing defusedxml into project venv (S85 fail-closed XML parsing) ..."
Invoke-Checked "pip install defusedxml" { & $venvPython -m pip install defusedxml }
}
# Ensure Node >= 18
$nodeMajor = [int]((& node -p "process.versions.node.split('.')[0]").Trim())
if ($nodeMajor -lt 18) {
Write-Host "[tests] WARN: Node < 18 detected. Trying nvm use 18..."
if (Get-Command nvm -ErrorAction SilentlyContinue) {
nvm use 18 | Out-Null
$nodeMajor = [int]((& node -p "process.versions.node.split('.')[0]").Trim())
}
}
if ($nodeMajor -lt 18) {
throw "[tests] ERROR: Node >=18 required, current=$(node -v)"
}
function Test-PortBindable {
param([Parameter(Mandatory = $true)][int]$Port)
$listener = $null
try {
$listener = [System.Net.Sockets.TcpListener]::new([System.Net.IPAddress]::Parse("127.0.0.1"), $Port)
$listener.Server.ExclusiveAddressUse = $true
$listener.Start()
return $true
}
catch {
return $false
}
finally {
if ($listener -ne $null) {
$listener.Stop()
}
}
}
function Resolve-E2EPort {
$requested = $env:OPENCLAW_E2E_PORT
if ($requested) {
[int]$requestedPort = 0
if (-not [int]::TryParse($requested, [ref]$requestedPort)) {
throw "[tests] ERROR: OPENCLAW_E2E_PORT must be an integer, got '$requested'"
}
if (-not (Test-PortBindable -Port $requestedPort)) {
throw "[tests] ERROR: OPENCLAW_E2E_PORT=$requestedPort is not bindable on 127.0.0.1"
}
return $requestedPort
}
foreach ($candidate in @(3000, 3300, 3400, 3500, 3600)) {
if (Test-PortBindable -Port $candidate) {
return $candidate
}
}
throw "[tests] ERROR: no bindable local port found for Playwright webServer (tried 3000,3300,3400,3500,3600)"
}
$selectedE2EPort = Resolve-E2EPort
$env:OPENCLAW_E2E_PORT = "$selectedE2EPort"
if ($selectedE2EPort -ne 3000) {
Write-Host "[tests] WARN: port 3000 unavailable; using OPENCLAW_E2E_PORT=$selectedE2EPort for Playwright."
}
else {
Write-Host "[tests] INFO: using OPENCLAW_E2E_PORT=$selectedE2EPort for Playwright."
}
Write-Host "[tests] Node version: $(node -v)"
Ensure-NpmDeps
Write-Host "[tests] 0/8 R120 dependency preflight"
Invoke-Checked "preflight_check" { & $venvPython scripts\preflight_check.py --strict }
Write-Host "[tests] 1/8 detect-secrets"
Invoke-Checked "detect-secrets" { & $venvPython -m pre_commit run detect-secrets --all-files }
Write-Host "[tests] 2/8 pre-commit all hooks (pass 1: autofix)"
$preCommitWorktreeBefore = Get-GitDiffSnapshot
$preCommitIndexBefore = Get-GitDiffSnapshot -Cached
& $venvPython -m pre_commit run --all-files --show-diff-on-failure
if ($LASTEXITCODE -ne 0) {
Write-Host "[tests] INFO: pre-commit reported changes/issues; running pass 2 verification..."
Invoke-Checked "pre-commit all hooks (pass 2 verify)" { & $venvPython -m pre_commit run --all-files --show-diff-on-failure }
}
Assert-PreCommitDidNotMutateRepo -BeforeWorktree $preCommitWorktreeBefore -BeforeIndex $preCommitIndexBefore
Write-Host "[tests] 3/9 coverage governance check"
Invoke-Checked "coverage governance check" {
& $venvPython scripts\verify_quality_governance.py
}
Write-Host "[tests] 4/9 backend unit tests"
$env:MOLTBOT_STATE_DIR = "$root\moltbot_state\_local_unit"
Invoke-Checked "backend unit tests" {
& $venvPython scripts\run_unittests.py --start-dir tests --pattern "test_*.py" --enforce-skip-policy tests\skip_policy.json
}
if ($env:OPENCLAW_IMPL_RECORD_PATH) {
Write-Host "[tests] 4.5/9 implementation record lint (strict)"
# IMPORTANT: strict mode is opt-in via OPENCLAW_IMPL_RECORD_PATH to avoid retroactive legacy record failures.
Invoke-Checked "implementation record lint" {
& $venvPython scripts\lint_implementation_record.py --path $env:OPENCLAW_IMPL_RECORD_PATH --strict
}
}
Write-Host "[tests] 5/9 backend real E2E lanes (R122/R123)"
$env:MOLTBOT_STATE_DIR = "$root\moltbot_state\_local_backend_e2e_real"
Invoke-Checked "backend real E2E lane R122" {
& $venvPython scripts\run_unittests.py --module tests.test_r122_real_backend_lane --enforce-skip-policy tests\skip_policy.json --max-skipped 0
}
Invoke-Checked "backend real E2E lane R123" {
& $venvPython scripts\run_unittests.py --module tests.test_r123_real_backend_model_list_lane --enforce-skip-policy tests\skip_policy.json --max-skipped 0
}
Write-Host "[tests] 6/9 R121 retry partition contract"
Invoke-Checked "R121 retry partition contract" {
& $venvPython scripts\run_unittests.py --module tests.test_r121_retry_partition_contract --enforce-skip-policy tests\skip_policy.json --max-skipped 0
}
Write-Host "[tests] 7/9 Slack integration gates (R124/R125/R117/F57)"
Invoke-Checked "Slack integration gates" {
& $venvPython scripts\run_unittests.py --module tests.test_r124_slack_ingress_contract --enforce-skip-policy tests\skip_policy.json --max-skipped 0
& $venvPython scripts\run_unittests.py --module tests.test_r125_slack_real_backend_lane --enforce-skip-policy tests\skip_policy.json --max-skipped 0
& $venvPython scripts\run_unittests.py --module tests.test_r117_observability_redaction_e2e --enforce-skip-policy tests\skip_policy.json --max-skipped 0
& $venvPython scripts\run_unittests.py --module tests.test_r117_observability_redaction_endpoints --enforce-skip-policy tests\skip_policy.json --max-skipped 0
& $venvPython scripts\run_unittests.py --module tests.test_f57_slack_transport_parity --enforce-skip-policy tests\skip_policy.json --max-skipped 0
& $venvPython scripts\run_unittests.py --module tests.test_f57_slack_socket_mode_startup --enforce-skip-policy tests\skip_policy.json --max-skipped 0
}
Write-Host "[tests] 8/9 R118 adversarial gate (adaptive: smoke/extended)"
$env:MOLTBOT_STATE_DIR = "$root\moltbot_state\_local_adversarial"
Invoke-Checked "R118 adversarial adaptive" {
& $venvPython scripts\run_adversarial_gate.py --profile auto --seed 42 --artifact-dir .tmp\adversarial
}
Write-Host "[tests] 9/9 frontend E2E"
$env:OPENCLAW_PLAYWRIGHT_INSTALL = "1"
$env:OPENCLAW_PLAYWRIGHT_BROWSERS = "chromium"
# IMPORTANT: full-gate acceptance must provision Playwright browsers itself; do
# not assume a warmed local browser cache when running on fresh Windows hosts.
Invoke-Checked "frontend E2E" { npm test }
Write-Host "[tests] PASS"