pi-oracle 0.7.4 → 0.7.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +32 -0
- package/README.md +53 -18
- package/docs/ORACLE_DESIGN.md +16 -8
- package/docs/platform-smoke.md +156 -0
- package/extensions/oracle/index.ts +10 -4
- package/extensions/oracle/lib/config.ts +53 -27
- package/extensions/oracle/lib/jobs.ts +9 -5
- package/extensions/oracle/lib/poller.ts +1 -0
- package/extensions/oracle/lib/runtime.ts +107 -32
- package/extensions/oracle/lib/tools.ts +138 -12
- package/extensions/oracle/shared/browser-profile-helpers.d.mts +59 -0
- package/extensions/oracle/shared/browser-profile-helpers.mjs +395 -0
- package/extensions/oracle/shared/process-helpers.mjs +12 -1
- package/extensions/oracle/shared/state-coordination-helpers.mjs +8 -2
- package/extensions/oracle/worker/auth-bootstrap.mjs +39 -10
- package/extensions/oracle/worker/chatgpt-ui-helpers.d.mts +2 -0
- package/extensions/oracle/worker/chatgpt-ui-helpers.mjs +157 -1
- package/extensions/oracle/worker/chromium-cookie-source.mjs +2 -1
- package/extensions/oracle/worker/run-job.mjs +107 -25
- package/package.json +30 -9
- package/platform-smoke.config.mjs +66 -0
- package/scripts/oracle-real-smoke.mjs +500 -0
- package/scripts/platform-smoke/Dockerfile.ubuntu +8 -0
- package/scripts/platform-smoke/artifacts.mjs +87 -0
- package/scripts/platform-smoke/assertions.mjs +34 -0
- package/scripts/platform-smoke/crabbox-runner.mjs +135 -0
- package/scripts/platform-smoke/doctor.mjs +239 -0
- package/scripts/platform-smoke/invariants.mjs +124 -0
- package/scripts/platform-smoke/platform-build-windows.ps1 +168 -0
- package/scripts/platform-smoke/targets.mjs +434 -0
- package/scripts/platform-smoke.mjs +152 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
param(
|
|
2
|
+
[string]$PackageName = "pi-oracle",
|
|
3
|
+
[int]$NodeValidationMajor = 24
|
|
4
|
+
)
|
|
5
|
+
|
|
6
|
+
$ErrorActionPreference = "Continue"
|
|
7
|
+
|
|
8
|
+
function Exit-CodeFromLastCommand {
|
|
9
|
+
if ($null -ne $global:LASTEXITCODE) { return [int]$global:LASTEXITCODE }
|
|
10
|
+
if ($?) { return 0 }
|
|
11
|
+
return 1
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function Write-SectionFile {
|
|
15
|
+
param([string]$Name, [string]$Path)
|
|
16
|
+
Write-Output "--- $Name START ---"
|
|
17
|
+
if (Test-Path -LiteralPath $Path) {
|
|
18
|
+
Get-Content -LiteralPath $Path -ErrorAction SilentlyContinue
|
|
19
|
+
}
|
|
20
|
+
Write-Output "--- $Name END ---"
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
Write-Output "Starting pi-oracle platform-build in $(Get-Location) at $(Get-Date -Format o)"
|
|
24
|
+
$SourceRoot = (Get-Location).Path
|
|
25
|
+
$RunRoot = Join-Path $SourceRoot (Join-Path ".platform-smoke-runs" ("platform-build-" + (Get-Date -Format "yyyyMMddTHHmmssZ") + "-" + $PID))
|
|
26
|
+
$PackDir = Join-Path $RunRoot "pack"
|
|
27
|
+
$TestWorkspace = Join-Path $RunRoot "test-workspace"
|
|
28
|
+
$PiProject = Join-Path $RunRoot "pi-project"
|
|
29
|
+
New-Item -ItemType Directory -Force -Path $PackDir, $TestWorkspace, $PiProject | Out-Null
|
|
30
|
+
Write-Output "PLATFORM_RUN_ROOT=$RunRoot"
|
|
31
|
+
Write-Output "PLATFORM_TEST_WORKSPACE=$TestWorkspace"
|
|
32
|
+
Write-Output "PLATFORM_PI_PROJECT=$PiProject"
|
|
33
|
+
|
|
34
|
+
$NodeVersion = (& node.exe --version).Trim()
|
|
35
|
+
$NodeMajor = [int](($NodeVersion -replace '^v', '').Split('.')[0])
|
|
36
|
+
if ($NodeMajor -ge $NodeValidationMajor) { $NODE_VERSION_EXIT = 0 } else { $NODE_VERSION_EXIT = 1 }
|
|
37
|
+
Write-Output "PLATFORM_NODE_VERSION=$NodeVersion"
|
|
38
|
+
Write-Output "PLATFORM_NODE_VERSION_EXIT=$NODE_VERSION_EXIT"
|
|
39
|
+
|
|
40
|
+
Write-Output "=== npm ci ==="
|
|
41
|
+
& npm.cmd ci *>&1
|
|
42
|
+
$CI_EXIT = Exit-CodeFromLastCommand
|
|
43
|
+
Write-Output "PLATFORM_NPM_CI_EXIT=$CI_EXIT"
|
|
44
|
+
|
|
45
|
+
Write-Output "=== platform dependencies ==="
|
|
46
|
+
$DEPS_EXIT = 0
|
|
47
|
+
$ZstdCommand = Get-Command zstd -ErrorAction SilentlyContinue
|
|
48
|
+
if ($ZstdCommand) { $ZSTD_INSTALL_EXIT = 0 } else { Write-Output "zstd missing on Windows target PATH; update pi-extension-windows-template/crabbox-ready"; $ZSTD_INSTALL_EXIT = 1 }
|
|
49
|
+
if ($ZSTD_INSTALL_EXIT -ne 0) { $DEPS_EXIT = 1 }
|
|
50
|
+
$AgentBrowserCommand = Get-Command agent-browser -ErrorAction SilentlyContinue
|
|
51
|
+
if (-not $AgentBrowserCommand) { $AgentBrowserCommand = Get-Command agent-browser.cmd -ErrorAction SilentlyContinue }
|
|
52
|
+
if ($AgentBrowserCommand) { $AGENT_BROWSER_INSTALL_EXIT = 0; $AgentBrowserPath = $AgentBrowserCommand.Source } else { Write-Output "agent-browser missing on Windows target PATH; update pi-extension-windows-template/crabbox-ready"; $AGENT_BROWSER_INSTALL_EXIT = 1; $AgentBrowserPath = "" }
|
|
53
|
+
if ($AGENT_BROWSER_INSTALL_EXIT -ne 0) { $DEPS_EXIT = 1 }
|
|
54
|
+
Write-Output "PLATFORM_ZSTD_INSTALL_EXIT=$ZSTD_INSTALL_EXIT"
|
|
55
|
+
Write-Output "PLATFORM_AGENT_BROWSER_INSTALL_EXIT=$AGENT_BROWSER_INSTALL_EXIT"
|
|
56
|
+
Write-Output "PLATFORM_DEPS_EXIT=$DEPS_EXIT"
|
|
57
|
+
if ($ZstdCommand) { Write-Output $ZstdCommand.Source }
|
|
58
|
+
if ($AgentBrowserPath) { Write-Output $AgentBrowserPath }
|
|
59
|
+
|
|
60
|
+
Write-Output "=== platform verification ==="
|
|
61
|
+
$PreviousAgentBrowserPath = $env:AGENT_BROWSER_PATH
|
|
62
|
+
$PreviousSanityProgress = $env:PI_ORACLE_SANITY_PROGRESS
|
|
63
|
+
$env:AGENT_BROWSER_PATH = $AgentBrowserPath
|
|
64
|
+
$env:PI_ORACLE_SANITY_PROGRESS = "1"
|
|
65
|
+
& npm.cmd run verify:oracle:platform *>&1
|
|
66
|
+
$TEST_EXIT = Exit-CodeFromLastCommand
|
|
67
|
+
if ($null -eq $PreviousAgentBrowserPath) { Remove-Item Env:\AGENT_BROWSER_PATH -ErrorAction SilentlyContinue } else { $env:AGENT_BROWSER_PATH = $PreviousAgentBrowserPath }
|
|
68
|
+
if ($null -eq $PreviousSanityProgress) { Remove-Item Env:\PI_ORACLE_SANITY_PROGRESS -ErrorAction SilentlyContinue } else { $env:PI_ORACLE_SANITY_PROGRESS = $PreviousSanityProgress }
|
|
69
|
+
Write-Output "PLATFORM_NPM_TEST_EXIT=$TEST_EXIT"
|
|
70
|
+
|
|
71
|
+
Write-Output "=== npm pack ==="
|
|
72
|
+
$NpmPackErr = Join-Path $PackDir "npm-pack.stderr.txt"
|
|
73
|
+
$PackOutput = @(& npm.cmd pack --silent 2> $NpmPackErr)
|
|
74
|
+
$PACK_EXIT = Exit-CodeFromLastCommand
|
|
75
|
+
Get-Content -LiteralPath $NpmPackErr -ErrorAction SilentlyContinue
|
|
76
|
+
$PackTarball = ($PackOutput | Select-Object -First 1)
|
|
77
|
+
if ($PackTarball) { $PackTarball = $PackTarball.Trim() }
|
|
78
|
+
Write-Output "PLATFORM_NPM_PACK_EXIT=$PACK_EXIT"
|
|
79
|
+
if ($PackTarball -and (Test-Path -LiteralPath $PackTarball)) {
|
|
80
|
+
Move-Item -LiteralPath $PackTarball -Destination (Join-Path $PackDir $PackTarball) -Force
|
|
81
|
+
}
|
|
82
|
+
Write-Output "PLATFORM_PACKED_TARBALL=$PackTarball"
|
|
83
|
+
Set-Content -Path (Join-Path $PackDir "packed-tarball.txt") -Value $PackTarball
|
|
84
|
+
|
|
85
|
+
Write-Output "=== fixture workspace ==="
|
|
86
|
+
Copy-Item -LiteralPath package.json, README.md -Destination $TestWorkspace -ErrorAction SilentlyContinue
|
|
87
|
+
Copy-Item -LiteralPath extensions, prompts, docs -Destination $TestWorkspace -Recurse -ErrorAction SilentlyContinue
|
|
88
|
+
if ((Test-Path -LiteralPath (Join-Path $TestWorkspace "package.json")) -and (Test-Path -LiteralPath (Join-Path $TestWorkspace "README.md")) -and (Test-Path -LiteralPath (Join-Path $TestWorkspace "extensions")) -and (Test-Path -LiteralPath (Join-Path $TestWorkspace "prompts")) -and (Test-Path -LiteralPath (Join-Path $TestWorkspace "docs"))) {
|
|
89
|
+
$FIXTURE_EXIT = 0
|
|
90
|
+
} else {
|
|
91
|
+
$FIXTURE_EXIT = 1
|
|
92
|
+
}
|
|
93
|
+
Write-Output "PLATFORM_FIXTURE_EXIT=$FIXTURE_EXIT"
|
|
94
|
+
|
|
95
|
+
$PiCli = Join-Path (Get-Location) "node_modules\.bin\pi.cmd"
|
|
96
|
+
if (-not (Test-Path -LiteralPath $PiCli)) { $PiCli = Join-Path (Get-Location) "node_modules\.bin\pi" }
|
|
97
|
+
if (-not (Test-Path -LiteralPath $PiCli)) {
|
|
98
|
+
$Command = Get-Command pi -ErrorAction SilentlyContinue
|
|
99
|
+
$PiCli = $Command.Source
|
|
100
|
+
}
|
|
101
|
+
Write-Output "PLATFORM_PI_CLI=$PiCli"
|
|
102
|
+
|
|
103
|
+
$PackedNodeInstallOut = Join-Path $PackDir "packed-node-install.stdout.txt"
|
|
104
|
+
$PackedNodeInstallErr = Join-Path $PackDir "packed-node-install.stderr.txt"
|
|
105
|
+
$PiInstallOut = Join-Path $PackDir "pi-install.stdout.txt"
|
|
106
|
+
$PiInstallErr = Join-Path $PackDir "pi-install.stderr.txt"
|
|
107
|
+
$PiListOut = Join-Path $PackDir "pi-list.stdout.txt"
|
|
108
|
+
$PiListErr = Join-Path $PackDir "pi-list.stderr.txt"
|
|
109
|
+
|
|
110
|
+
Write-Output "=== pi install packed package ==="
|
|
111
|
+
$TarballPath = Join-Path $PackDir $PackTarball
|
|
112
|
+
if ($PackTarball -and $PiCli -and (Test-Path -LiteralPath $TarballPath)) {
|
|
113
|
+
Push-Location $PiProject
|
|
114
|
+
& npm.cmd init -y 1> $PackedNodeInstallOut 2> $PackedNodeInstallErr
|
|
115
|
+
$NPM_INIT_EXIT = Exit-CodeFromLastCommand
|
|
116
|
+
if ($NPM_INIT_EXIT -eq 0) {
|
|
117
|
+
& npm.cmd install --no-save $TarballPath 1>> $PackedNodeInstallOut 2>> $PackedNodeInstallErr
|
|
118
|
+
$PACKED_NODE_INSTALL_EXIT = Exit-CodeFromLastCommand
|
|
119
|
+
} else {
|
|
120
|
+
$PACKED_NODE_INSTALL_EXIT = $NPM_INIT_EXIT
|
|
121
|
+
}
|
|
122
|
+
if ($PACKED_NODE_INSTALL_EXIT -eq 0) {
|
|
123
|
+
$PreviousPiOffline = $env:PI_OFFLINE
|
|
124
|
+
$env:PI_OFFLINE = "1"
|
|
125
|
+
& $PiCli install -l (Join-Path ".\node_modules" $PackageName) 1> $PiInstallOut 2> $PiInstallErr
|
|
126
|
+
$PI_INSTALL_EXIT = Exit-CodeFromLastCommand
|
|
127
|
+
if ($null -eq $PreviousPiOffline) { Remove-Item Env:\PI_OFFLINE -ErrorAction SilentlyContinue } else { $env:PI_OFFLINE = $PreviousPiOffline }
|
|
128
|
+
} else {
|
|
129
|
+
Set-Content -LiteralPath $PiInstallErr -Value "packed npm install failed"
|
|
130
|
+
$PI_INSTALL_EXIT = 1
|
|
131
|
+
}
|
|
132
|
+
Pop-Location
|
|
133
|
+
} else {
|
|
134
|
+
Set-Content -LiteralPath $PackedNodeInstallErr -Value "missing pi cli or tarball"
|
|
135
|
+
Set-Content -LiteralPath $PiInstallErr -Value "missing pi cli or tarball"
|
|
136
|
+
$PACKED_NODE_INSTALL_EXIT = 1
|
|
137
|
+
$PI_INSTALL_EXIT = 1
|
|
138
|
+
}
|
|
139
|
+
Write-Output "PLATFORM_PACKED_NODE_INSTALL_EXIT=$PACKED_NODE_INSTALL_EXIT"
|
|
140
|
+
Write-SectionFile "PACKED_NODE_INSTALL_STDOUT" $PackedNodeInstallOut
|
|
141
|
+
Write-SectionFile "PACKED_NODE_INSTALL_STDERR" $PackedNodeInstallErr
|
|
142
|
+
Write-Output "PLATFORM_PI_INSTALL_EXIT=$PI_INSTALL_EXIT"
|
|
143
|
+
Write-SectionFile "PI_INSTALL_STDOUT" $PiInstallOut
|
|
144
|
+
Write-SectionFile "PI_INSTALL_STDERR" $PiInstallErr
|
|
145
|
+
|
|
146
|
+
Write-Output "=== pi list ==="
|
|
147
|
+
if ($PiCli) {
|
|
148
|
+
Push-Location $PiProject
|
|
149
|
+
$PreviousPiOffline = $env:PI_OFFLINE
|
|
150
|
+
$env:PI_OFFLINE = "1"
|
|
151
|
+
& $PiCli list 1> $PiListOut 2> $PiListErr
|
|
152
|
+
$PI_LIST_EXIT = Exit-CodeFromLastCommand
|
|
153
|
+
if ($null -eq $PreviousPiOffline) { Remove-Item Env:\PI_OFFLINE -ErrorAction SilentlyContinue } else { $env:PI_OFFLINE = $PreviousPiOffline }
|
|
154
|
+
Pop-Location
|
|
155
|
+
} else {
|
|
156
|
+
Set-Content -LiteralPath $PiListErr -Value "missing pi cli"
|
|
157
|
+
$PI_LIST_EXIT = 1
|
|
158
|
+
}
|
|
159
|
+
Write-Output "PLATFORM_PI_LIST_EXIT=$PI_LIST_EXIT"
|
|
160
|
+
Write-SectionFile "PI_LIST_STDOUT" $PiListOut
|
|
161
|
+
Write-SectionFile "PI_LIST_STDERR" $PiListErr
|
|
162
|
+
|
|
163
|
+
Write-Output "node=$NODE_VERSION_EXIT ci=$CI_EXIT deps=$DEPS_EXIT test=$TEST_EXIT pack=$PACK_EXIT fixture=$FIXTURE_EXIT packedNodeInstall=$PACKED_NODE_INSTALL_EXIT install=$PI_INSTALL_EXIT list=$PI_LIST_EXIT"
|
|
164
|
+
if ($NODE_VERSION_EXIT -ne 0 -or $CI_EXIT -ne 0 -or $DEPS_EXIT -ne 0 -or $TEST_EXIT -ne 0 -or $PACK_EXIT -ne 0 -or $FIXTURE_EXIT -ne 0 -or $PACKED_NODE_INSTALL_EXIT -ne 0 -or $PI_INSTALL_EXIT -ne 0 -or $PI_LIST_EXIT -ne 0) {
|
|
165
|
+
Write-Output "PLATFORM_BUILD_FAILED: node=$NODE_VERSION_EXIT ci=$CI_EXIT deps=$DEPS_EXIT test=$TEST_EXIT pack=$PACK_EXIT fixture=$FIXTURE_EXIT packedNodeInstall=$PACKED_NODE_INSTALL_EXIT install=$PI_INSTALL_EXIT list=$PI_LIST_EXIT"
|
|
166
|
+
exit 1
|
|
167
|
+
}
|
|
168
|
+
Write-Output "PLATFORM_BUILD_OK"
|
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-platform smoke suites for pi-oracle.
|
|
3
|
+
* The suites prove the package builds, packs, installs, loads, and runs through pi's package path.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { resolve } from "node:path";
|
|
8
|
+
|
|
9
|
+
import { runAssertions } from "./assertions.mjs";
|
|
10
|
+
import { createSuiteDir, scanArtifacts, scanForSecrets, writeCommand, writeExitCode, writeManifest, writeSummary } from "./artifacts.mjs";
|
|
11
|
+
import { runOnLease, stopLease, warmupLease } from "./crabbox-runner.mjs";
|
|
12
|
+
|
|
13
|
+
function makeRunId() {
|
|
14
|
+
return `run-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function platformForTarget(targetName) {
|
|
18
|
+
if (targetName === "macos") return "darwin";
|
|
19
|
+
if (targetName === "windows-native") return "win32";
|
|
20
|
+
return "linux";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function section(text, name) {
|
|
24
|
+
const start = `--- ${name} START ---`;
|
|
25
|
+
const end = `--- ${name} END ---`;
|
|
26
|
+
const startIndex = text.indexOf(start);
|
|
27
|
+
if (startIndex === -1) return "";
|
|
28
|
+
const contentStart = startIndex + start.length;
|
|
29
|
+
const endIndex = text.indexOf(end, contentStart);
|
|
30
|
+
const raw = endIndex === -1 ? text.slice(contentStart) : text.slice(contentStart, endIndex);
|
|
31
|
+
return raw.replace(/^\r?\n/, "").replace(/\r?\n$/, "");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function markerValue(text, name) {
|
|
35
|
+
return text.match(new RegExp(`^${name}=(.*)$`, "m"))?.[1]?.trim() ?? "";
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function writeExtracts(suiteDir, stdout) {
|
|
39
|
+
writeFileSync(resolve(suiteDir, "packed-tarball.txt"), `${markerValue(stdout, "PLATFORM_PACKED_TARBALL")}\n`);
|
|
40
|
+
writeFileSync(resolve(suiteDir, "packed-node-install.stdout.txt"), section(stdout, "PACKED_NODE_INSTALL_STDOUT"));
|
|
41
|
+
writeFileSync(resolve(suiteDir, "packed-node-install.stderr.txt"), section(stdout, "PACKED_NODE_INSTALL_STDERR"));
|
|
42
|
+
writeFileSync(resolve(suiteDir, "pi-install.stdout.txt"), section(stdout, "PI_INSTALL_STDOUT"));
|
|
43
|
+
writeFileSync(resolve(suiteDir, "pi-install.stderr.txt"), section(stdout, "PI_INSTALL_STDERR"));
|
|
44
|
+
writeFileSync(resolve(suiteDir, "pi-list.stdout.txt"), section(stdout, "PI_LIST_STDOUT"));
|
|
45
|
+
writeFileSync(resolve(suiteDir, "pi-list.stderr.txt"), section(stdout, "PI_LIST_STDERR"));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function posixSection(name, command) {
|
|
49
|
+
return [`echo "--- ${name} START ---"`, command, `echo "--- ${name} END ---"`];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function realSmokeProvider(config = {}) {
|
|
53
|
+
return process.env.PI_ORACLE_REAL_TEST_PROVIDER || config.realSmoke?.defaultProvider || "zai";
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function realSmokeModel(config = {}) {
|
|
57
|
+
return process.env.PI_ORACLE_REAL_TEST_MODEL || config.realSmoke?.defaultModel || "glm-5.1";
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function truthy(value) {
|
|
61
|
+
return /^(1|true|yes|on)$/i.test(String(value ?? ""));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function realSmokeUsesModelAgent() {
|
|
65
|
+
return truthy(process.env.PI_ORACLE_REAL_TEST_MODEL_AGENT);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function realSmokeAllowedEnvNames(config = {}) {
|
|
69
|
+
const provider = realSmokeProvider(config);
|
|
70
|
+
const authNames = realSmokeUsesModelAgent() ? config.realSmoke?.authEnvByProvider?.[provider] ?? [] : [];
|
|
71
|
+
return [
|
|
72
|
+
"PI_ORACLE_REAL_TEST_PROVIDER",
|
|
73
|
+
"PI_ORACLE_REAL_TEST_MODEL",
|
|
74
|
+
"PI_ORACLE_REAL_TEST_MODEL_AGENT",
|
|
75
|
+
"PI_ORACLE_REAL_TEST_TIMEOUT_MS",
|
|
76
|
+
...authNames,
|
|
77
|
+
];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function shellQuote(value) {
|
|
81
|
+
return `'${String(value).replaceAll("'", "'\\''")}'`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function powershellSingleQuote(value) {
|
|
85
|
+
return `'${String(value).replaceAll("'", "''")}'`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function buildRealExtensionCommand(targetName = "ubuntu", config = {}) {
|
|
89
|
+
const provider = realSmokeProvider(config);
|
|
90
|
+
const model = realSmokeModel(config);
|
|
91
|
+
const useModelAgent = realSmokeUsesModelAgent();
|
|
92
|
+
if (targetName === "windows-native") {
|
|
93
|
+
return [
|
|
94
|
+
"$ErrorActionPreference = 'Continue'",
|
|
95
|
+
`$env:PI_ORACLE_REAL_TEST_PROVIDER = ${powershellSingleQuote(provider)}`,
|
|
96
|
+
`$env:PI_ORACLE_REAL_TEST_MODEL = ${powershellSingleQuote(model)}`,
|
|
97
|
+
`$env:PI_ORACLE_REAL_TEST_MODEL_AGENT = ${powershellSingleQuote(useModelAgent ? "1" : "")}`,
|
|
98
|
+
"$env:PI_ORACLE_REAL_TEST_TIMEOUT_MS = '360000'",
|
|
99
|
+
"if (-not (Test-Path -LiteralPath 'node_modules')) { npm.cmd ci; if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } }",
|
|
100
|
+
"npm.cmd run smoke:real:doctor",
|
|
101
|
+
"if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }",
|
|
102
|
+
"npm.cmd run smoke:real:packed",
|
|
103
|
+
"exit $LASTEXITCODE",
|
|
104
|
+
].join("; ");
|
|
105
|
+
}
|
|
106
|
+
return [
|
|
107
|
+
"set -o pipefail",
|
|
108
|
+
`export PI_ORACLE_REAL_TEST_PROVIDER=${shellQuote(provider)}`,
|
|
109
|
+
`export PI_ORACLE_REAL_TEST_MODEL=${shellQuote(model)}`,
|
|
110
|
+
`export PI_ORACLE_REAL_TEST_MODEL_AGENT=${shellQuote(useModelAgent ? "1" : "")}`,
|
|
111
|
+
'if [ ! -d node_modules ]; then npm ci; fi',
|
|
112
|
+
"npm run smoke:real:doctor",
|
|
113
|
+
"npm run smoke:real:packed",
|
|
114
|
+
].join("\n");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function buildPlatformBuildCommand(targetName = "ubuntu", packageName = "pi-oracle", nodeValidationMajor = 24) {
|
|
118
|
+
if (targetName === "windows-native") {
|
|
119
|
+
return `powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -File .\\scripts\\platform-smoke\\platform-build-windows.ps1 -PackageName ${packageName} -NodeValidationMajor ${nodeValidationMajor}`;
|
|
120
|
+
}
|
|
121
|
+
const lines = [];
|
|
122
|
+
lines.push("set -o pipefail");
|
|
123
|
+
if (targetName === "macos") {
|
|
124
|
+
lines.push('if [ -d "$HOME/.local/share/mise/installs/node/24/bin" ]; then export PATH="$HOME/.local/share/mise/installs/node/24/bin:$PATH"; fi');
|
|
125
|
+
}
|
|
126
|
+
lines.push('echo "Starting pi-oracle platform-build in $(pwd) at $(date -u +%Y-%m-%dT%H:%M:%SZ)"');
|
|
127
|
+
lines.push('RUN_ROOT=".platform-smoke-runs/platform-build-$(date -u +%Y%m%dT%H%M%SZ)-$$"');
|
|
128
|
+
lines.push('SOURCE_ROOT="$(pwd)"');
|
|
129
|
+
lines.push('PACK_DIR="$SOURCE_ROOT/$RUN_ROOT/pack"');
|
|
130
|
+
lines.push('TEST_WORKSPACE="$SOURCE_ROOT/$RUN_ROOT/test-workspace"');
|
|
131
|
+
lines.push('PI_PROJECT="$SOURCE_ROOT/$RUN_ROOT/pi-project"');
|
|
132
|
+
lines.push('mkdir -p "$PACK_DIR" "$TEST_WORKSPACE" "$PI_PROJECT"');
|
|
133
|
+
lines.push('echo "PLATFORM_RUN_ROOT=$RUN_ROOT"');
|
|
134
|
+
lines.push('echo "PLATFORM_TEST_WORKSPACE=$TEST_WORKSPACE"');
|
|
135
|
+
lines.push('echo "PLATFORM_PI_PROJECT=$PI_PROJECT"');
|
|
136
|
+
lines.push("");
|
|
137
|
+
lines.push('NODE_VERSION=$(node --version)');
|
|
138
|
+
lines.push('NODE_MAJOR=${NODE_VERSION#v}');
|
|
139
|
+
lines.push('NODE_MAJOR=${NODE_MAJOR%%.*}');
|
|
140
|
+
lines.push('echo "PLATFORM_NODE_VERSION=$NODE_VERSION"');
|
|
141
|
+
lines.push(`if [ "$NODE_MAJOR" -ge ${nodeValidationMajor} ]; then NODE_VERSION_EXIT=0; else NODE_VERSION_EXIT=1; fi`);
|
|
142
|
+
lines.push('echo "PLATFORM_NODE_VERSION_EXIT=$NODE_VERSION_EXIT"');
|
|
143
|
+
lines.push("");
|
|
144
|
+
lines.push('echo "=== npm ci ==="');
|
|
145
|
+
lines.push('npm ci 2>&1');
|
|
146
|
+
lines.push('CI_EXIT=$?');
|
|
147
|
+
lines.push('echo "PLATFORM_NPM_CI_EXIT=$CI_EXIT"');
|
|
148
|
+
lines.push("");
|
|
149
|
+
lines.push('echo "=== platform dependencies ==="');
|
|
150
|
+
lines.push('DEPS_EXIT=0');
|
|
151
|
+
if (targetName === "ubuntu") {
|
|
152
|
+
lines.push('if command -v zstd >/dev/null 2>&1; then ZSTD_INSTALL_EXIT=0; else echo "zstd missing on Ubuntu target; use a smoke image with zstd installed before running platform smoke"; ZSTD_INSTALL_EXIT=1; fi');
|
|
153
|
+
} else {
|
|
154
|
+
lines.push('if command -v zstd >/dev/null 2>&1; then ZSTD_INSTALL_EXIT=0; else echo "zstd missing on macOS target; install it on the host before running platform smoke"; ZSTD_INSTALL_EXIT=1; fi');
|
|
155
|
+
}
|
|
156
|
+
lines.push('if [ "$ZSTD_INSTALL_EXIT" -ne 0 ]; then DEPS_EXIT=1; fi');
|
|
157
|
+
lines.push('AGENT_BROWSER_BIN="$(command -v agent-browser || true)"');
|
|
158
|
+
lines.push('if [ -n "$AGENT_BROWSER_BIN" ]; then AGENT_BROWSER_INSTALL_EXIT=0; else echo "agent-browser missing on target PATH; install it in target setup before running platform smoke"; AGENT_BROWSER_INSTALL_EXIT=1; fi');
|
|
159
|
+
lines.push('if [ "$AGENT_BROWSER_INSTALL_EXIT" -ne 0 ]; then DEPS_EXIT=1; fi');
|
|
160
|
+
lines.push('echo "PLATFORM_ZSTD_INSTALL_EXIT=$ZSTD_INSTALL_EXIT"');
|
|
161
|
+
lines.push('echo "PLATFORM_AGENT_BROWSER_INSTALL_EXIT=$AGENT_BROWSER_INSTALL_EXIT"');
|
|
162
|
+
lines.push('echo "PLATFORM_DEPS_EXIT=$DEPS_EXIT"');
|
|
163
|
+
lines.push('command -v zstd || true');
|
|
164
|
+
lines.push('if [ -n "$AGENT_BROWSER_BIN" ]; then echo "$AGENT_BROWSER_BIN"; fi');
|
|
165
|
+
lines.push("");
|
|
166
|
+
lines.push('echo "=== platform verification ==="');
|
|
167
|
+
lines.push('AGENT_BROWSER_PATH="$AGENT_BROWSER_BIN" npm run verify:oracle:platform 2>&1');
|
|
168
|
+
lines.push('TEST_EXIT=$?');
|
|
169
|
+
lines.push('echo "PLATFORM_NPM_TEST_EXIT=$TEST_EXIT"');
|
|
170
|
+
lines.push("");
|
|
171
|
+
lines.push('echo "=== npm pack ==="');
|
|
172
|
+
lines.push('PACK_TARBALL=$(npm pack --silent 2>"$PACK_DIR/npm-pack.stderr.txt")');
|
|
173
|
+
lines.push('PACK_EXIT=$?');
|
|
174
|
+
lines.push('cat "$PACK_DIR/npm-pack.stderr.txt"');
|
|
175
|
+
lines.push('echo "PLATFORM_NPM_PACK_EXIT=$PACK_EXIT"');
|
|
176
|
+
lines.push('if [ -n "$PACK_TARBALL" ] && [ -f "$PACK_TARBALL" ]; then mv "$PACK_TARBALL" "$PACK_DIR/$PACK_TARBALL"; fi');
|
|
177
|
+
lines.push('echo "PLATFORM_PACKED_TARBALL=$PACK_TARBALL"');
|
|
178
|
+
lines.push('printf "%s\\n" "$PACK_TARBALL" > "$PACK_DIR/packed-tarball.txt"');
|
|
179
|
+
lines.push("");
|
|
180
|
+
lines.push('echo "=== fixture workspace ==="');
|
|
181
|
+
lines.push('cp package.json README.md "$TEST_WORKSPACE"/ 2>"$PACK_DIR/fixture.stderr.txt"');
|
|
182
|
+
lines.push('FIXTURE_COPY_EXIT=$?');
|
|
183
|
+
lines.push('cp -R extensions prompts docs "$TEST_WORKSPACE"/ 2>>"$PACK_DIR/fixture.stderr.txt"');
|
|
184
|
+
lines.push('TREE_COPY_EXIT=$?');
|
|
185
|
+
lines.push('if [ "$FIXTURE_COPY_EXIT" -eq 0 ] && [ "$TREE_COPY_EXIT" -eq 0 ]; then FIXTURE_EXIT=0; else FIXTURE_EXIT=1; fi');
|
|
186
|
+
lines.push('cat "$PACK_DIR/fixture.stderr.txt"');
|
|
187
|
+
lines.push('echo "PLATFORM_FIXTURE_EXIT=$FIXTURE_EXIT"');
|
|
188
|
+
lines.push("");
|
|
189
|
+
lines.push('echo "=== pi install packed package ==="');
|
|
190
|
+
lines.push('PI_CLI="$(pwd)/node_modules/.bin/pi"');
|
|
191
|
+
lines.push('if [ ! -x "$PI_CLI" ]; then PI_CLI="$(command -v pi || true)"; fi');
|
|
192
|
+
lines.push('echo "PLATFORM_PI_CLI=$PI_CLI"');
|
|
193
|
+
lines.push('if [ -n "$PACK_TARBALL" ] && [ -n "$PI_CLI" ] && [ -f "$PACK_DIR/$PACK_TARBALL" ]; then (cd "$PI_PROJECT" && npm init -y >"$PACK_DIR/packed-node-install.stdout.txt" 2>"$PACK_DIR/packed-node-install.stderr.txt" && npm install --no-save "$PACK_DIR/$PACK_TARBALL" >>"$PACK_DIR/packed-node-install.stdout.txt" 2>>"$PACK_DIR/packed-node-install.stderr.txt"); PACKED_NODE_INSTALL_EXIT=$?; else echo "missing pi cli or tarball" >"$PACK_DIR/packed-node-install.stderr.txt"; PACKED_NODE_INSTALL_EXIT=1; fi');
|
|
194
|
+
lines.push('echo "PLATFORM_PACKED_NODE_INSTALL_EXIT=$PACKED_NODE_INSTALL_EXIT"');
|
|
195
|
+
lines.push(...posixSection("PACKED_NODE_INSTALL_STDOUT", 'cat "$PACK_DIR/packed-node-install.stdout.txt" 2>/dev/null || true'));
|
|
196
|
+
lines.push(...posixSection("PACKED_NODE_INSTALL_STDERR", 'cat "$PACK_DIR/packed-node-install.stderr.txt" 2>/dev/null || true'));
|
|
197
|
+
lines.push(`if [ "$PACKED_NODE_INSTALL_EXIT" -eq 0 ] && [ -n "$PI_CLI" ]; then (cd "$PI_PROJECT" && PI_OFFLINE=1 "$PI_CLI" install -l ./node_modules/${packageName} >"$PACK_DIR/pi-install.stdout.txt" 2>"$PACK_DIR/pi-install.stderr.txt"); PI_INSTALL_EXIT=$?; else echo "packed npm install failed or missing pi cli" >"$PACK_DIR/pi-install.stderr.txt"; PI_INSTALL_EXIT=1; fi`);
|
|
198
|
+
lines.push('echo "PLATFORM_PI_INSTALL_EXIT=$PI_INSTALL_EXIT"');
|
|
199
|
+
lines.push(...posixSection("PI_INSTALL_STDOUT", 'cat "$PACK_DIR/pi-install.stdout.txt" 2>/dev/null || true'));
|
|
200
|
+
lines.push(...posixSection("PI_INSTALL_STDERR", 'cat "$PACK_DIR/pi-install.stderr.txt" 2>/dev/null || true'));
|
|
201
|
+
lines.push("");
|
|
202
|
+
lines.push('echo "=== pi list ==="');
|
|
203
|
+
lines.push('if [ -n "$PI_CLI" ]; then (cd "$PI_PROJECT" && PI_OFFLINE=1 "$PI_CLI" list >"$PACK_DIR/pi-list.stdout.txt" 2>"$PACK_DIR/pi-list.stderr.txt"); PI_LIST_EXIT=$?; else echo "missing pi cli" >"$PACK_DIR/pi-list.stderr.txt"; PI_LIST_EXIT=1; fi');
|
|
204
|
+
lines.push('echo "PLATFORM_PI_LIST_EXIT=$PI_LIST_EXIT"');
|
|
205
|
+
lines.push(...posixSection("PI_LIST_STDOUT", 'cat "$PACK_DIR/pi-list.stdout.txt" 2>/dev/null || true'));
|
|
206
|
+
lines.push(...posixSection("PI_LIST_STDERR", 'cat "$PACK_DIR/pi-list.stderr.txt" 2>/dev/null || true'));
|
|
207
|
+
lines.push("");
|
|
208
|
+
lines.push('echo "node=$NODE_VERSION_EXIT ci=$CI_EXIT deps=$DEPS_EXIT test=$TEST_EXIT pack=$PACK_EXIT fixture=$FIXTURE_EXIT packedNodeInstall=$PACKED_NODE_INSTALL_EXIT install=$PI_INSTALL_EXIT list=$PI_LIST_EXIT"');
|
|
209
|
+
lines.push('if [ "$NODE_VERSION_EXIT" -ne 0 ] || [ "$CI_EXIT" -ne 0 ] || [ "$DEPS_EXIT" -ne 0 ] || [ "$TEST_EXIT" -ne 0 ] || [ "$PACK_EXIT" -ne 0 ] || [ "$FIXTURE_EXIT" -ne 0 ] || [ "$PACKED_NODE_INSTALL_EXIT" -ne 0 ] || [ "$PI_INSTALL_EXIT" -ne 0 ] || [ "$PI_LIST_EXIT" -ne 0 ]; then');
|
|
210
|
+
lines.push(' echo "PLATFORM_BUILD_FAILED: node=$NODE_VERSION_EXIT ci=$CI_EXIT deps=$DEPS_EXIT test=$TEST_EXIT pack=$PACK_EXIT fixture=$FIXTURE_EXIT packedNodeInstall=$PACKED_NODE_INSTALL_EXIT install=$PI_INSTALL_EXIT list=$PI_LIST_EXIT"');
|
|
211
|
+
lines.push(' exit 1');
|
|
212
|
+
lines.push('fi');
|
|
213
|
+
lines.push('echo "PLATFORM_BUILD_OK"');
|
|
214
|
+
return lines.join("\n");
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export async function runTargetSuite(config, targetName, suiteName, leaseSession) {
|
|
218
|
+
if (!["macos", "ubuntu", "windows-native"].includes(targetName)) throw new Error(`unknown target: ${targetName}`);
|
|
219
|
+
if (!["platform-build", "real-extension"].includes(suiteName)) throw new Error(`unknown suite: ${suiteName}`);
|
|
220
|
+
const runId = makeRunId();
|
|
221
|
+
const suiteDir = createSuiteDir(config.artifactRoot, runId, targetName, suiteName);
|
|
222
|
+
const slug = `${config.packageName ?? "pi-oracle"}-${targetName}`;
|
|
223
|
+
writeFileSync(resolve(suiteDir, "target.json"), JSON.stringify({ targetName, platform: platformForTarget(targetName), slug, runId, writtenAt: new Date().toISOString() }, null, 2));
|
|
224
|
+
writeFileSync(resolve(suiteDir, "suite.json"), JSON.stringify({ suiteName, writtenAt: new Date().toISOString() }, null, 2));
|
|
225
|
+
|
|
226
|
+
const command = suiteName === "platform-build"
|
|
227
|
+
? buildPlatformBuildCommand(targetName, config.packageName ?? "pi-oracle", config.nodeValidationMajor ?? 24)
|
|
228
|
+
: buildRealExtensionCommand(targetName, config);
|
|
229
|
+
writeCommand(suiteDir, command);
|
|
230
|
+
|
|
231
|
+
let warmup = leaseSession;
|
|
232
|
+
const ownsLease = !warmup;
|
|
233
|
+
if (!warmup) {
|
|
234
|
+
console.log(` warmup ${targetName}...`);
|
|
235
|
+
warmup = await warmupLease(config, targetName, slug);
|
|
236
|
+
if (!warmup.ok) return failTransportSuite(suiteDir, targetName, suiteName, warmup, "warmup");
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const startedAt = Date.now();
|
|
240
|
+
console.log(` executing ${suiteName} on ${targetName}...`);
|
|
241
|
+
const result = await runOnLease(config, targetName, warmup.leaseId, command, {
|
|
242
|
+
shell: true,
|
|
243
|
+
timeout: suiteName === "real-extension" ? 900_000 : 900_000,
|
|
244
|
+
sync: leaseSession?.sync,
|
|
245
|
+
allowEnvNames: suiteName === "real-extension" ? realSmokeAllowedEnvNames(config) : undefined,
|
|
246
|
+
});
|
|
247
|
+
const elapsedMs = Date.now() - startedAt;
|
|
248
|
+
|
|
249
|
+
writeFileSync(resolve(suiteDir, "crabbox.stdout.txt"), result.stdout);
|
|
250
|
+
writeFileSync(resolve(suiteDir, "crabbox.stderr.txt"), result.stderr);
|
|
251
|
+
writeFileSync(resolve(suiteDir, "crabbox.timing.json"), JSON.stringify({ startedAt: new Date(startedAt).toISOString(), elapsedMs, code: result.code, signal: result.signal }, null, 2));
|
|
252
|
+
writeExitCode(suiteDir, result.code, result.signal);
|
|
253
|
+
if (suiteName === "platform-build") writeExtracts(suiteDir, result.stdout);
|
|
254
|
+
|
|
255
|
+
let stopResult;
|
|
256
|
+
if (ownsLease) {
|
|
257
|
+
stopResult = await stopLease(config, targetName, warmup.leaseId);
|
|
258
|
+
writeStopArtifacts(suiteDir, stopResult);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const violations = [
|
|
262
|
+
...scanForSecrets(`${result.stdout}\n${result.stderr}`),
|
|
263
|
+
...scanArtifacts(suiteDir).map((finding) => `${finding.file}: ${finding.violation}`),
|
|
264
|
+
];
|
|
265
|
+
if (violations.length > 0) writeFileSync(resolve(suiteDir, "redaction-violations.json"), JSON.stringify(violations, null, 2));
|
|
266
|
+
|
|
267
|
+
const stdout = result.stdout;
|
|
268
|
+
const packageName = config.packageName ?? "pi-oracle";
|
|
269
|
+
let checks;
|
|
270
|
+
let expectedFiles;
|
|
271
|
+
if (suiteName === "platform-build") {
|
|
272
|
+
const listOutput = section(stdout, "PI_LIST_STDOUT");
|
|
273
|
+
const escapedPackageName = packageName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
274
|
+
const packageInstallPattern = new RegExp(`node_modules[\\\\/]${escapedPackageName}`);
|
|
275
|
+
const nodeMajor = Number(stdout.match(/PLATFORM_NODE_VERSION=v?(\d+)\./)?.[1] ?? 0);
|
|
276
|
+
checks = [
|
|
277
|
+
{ id: "build-exit-zero", fn: () => result.code === 0, error: `exit ${result.code}` },
|
|
278
|
+
{ id: "build-marker", fn: () => stdout.includes("PLATFORM_BUILD_OK") },
|
|
279
|
+
{ id: "node-version", fn: () => nodeMajor >= (config.nodeValidationMajor ?? 24) },
|
|
280
|
+
{ id: "npm-ci", fn: () => /PLATFORM_NPM_CI_EXIT=0/.test(stdout) },
|
|
281
|
+
{ id: "platform-dependencies", fn: () => /PLATFORM_DEPS_EXIT=0/.test(stdout) },
|
|
282
|
+
{ id: "platform-verification", fn: () => /PLATFORM_NPM_TEST_EXIT=0/.test(stdout) },
|
|
283
|
+
{ id: "npm-pack", fn: () => /PLATFORM_NPM_PACK_EXIT=0/.test(stdout) && /PLATFORM_PACKED_TARBALL=\S+/.test(stdout) },
|
|
284
|
+
{ id: "fixture-workspace", fn: () => /PLATFORM_FIXTURE_EXIT=0/.test(stdout) },
|
|
285
|
+
{ id: "packed-node-install", fn: () => /PLATFORM_PACKED_NODE_INSTALL_EXIT=0/.test(stdout) },
|
|
286
|
+
{ id: "pi-install", fn: () => /PLATFORM_PI_INSTALL_EXIT=0/.test(stdout) },
|
|
287
|
+
{ id: "pi-list", fn: () => /PLATFORM_PI_LIST_EXIT=0/.test(stdout) && listOutput.includes(packageName) && packageInstallPattern.test(listOutput) },
|
|
288
|
+
{ id: "no-source-extension-path", fn: () => !/\bpi\s+(?:-e|--extension)\s+\./.test(stdout) },
|
|
289
|
+
{ id: "no-secrets", fn: () => violations.length === 0, error: "redaction violations found" },
|
|
290
|
+
];
|
|
291
|
+
expectedFiles = [
|
|
292
|
+
"summary.json", "target.json", "suite.json", "command.txt", "exit-code.txt",
|
|
293
|
+
"crabbox.stdout.txt", "crabbox.stderr.txt", "crabbox.timing.json",
|
|
294
|
+
"packed-tarball.txt", "packed-node-install.stdout.txt", "packed-node-install.stderr.txt",
|
|
295
|
+
"pi-install.stdout.txt", "pi-install.stderr.txt", "pi-list.stdout.txt", "pi-list.stderr.txt",
|
|
296
|
+
"assertions.json",
|
|
297
|
+
];
|
|
298
|
+
} else {
|
|
299
|
+
const provider = process.env.PI_ORACLE_REAL_TEST_PROVIDER || "zai";
|
|
300
|
+
checks = [
|
|
301
|
+
{ id: "real-smoke-exit-zero", fn: () => result.code === 0, error: `exit ${result.code}` },
|
|
302
|
+
{ id: "real-smoke-doctor", fn: () => stdout.includes("Oracle real smoke doctor") && stdout.includes(`provider: ${provider}`) },
|
|
303
|
+
{ id: "real-smoke-marker", fn: () => stdout.includes("Oracle real smoke passed:") },
|
|
304
|
+
{ id: "real-smoke-packed-install", fn: () => stdout.includes("mode=packed") && stdout.includes("extension=./node_modules/pi-oracle") },
|
|
305
|
+
{ id: "real-smoke-no-source-extension", fn: () => !stdout.includes("extensions/oracle/index.ts") && !/\bpi\s+(?:-e|--extension)\s+extensions\/oracle/.test(stdout) },
|
|
306
|
+
{ id: "no-secrets", fn: () => violations.length === 0, error: "redaction violations found" },
|
|
307
|
+
];
|
|
308
|
+
expectedFiles = [
|
|
309
|
+
"summary.json", "target.json", "suite.json", "command.txt", "exit-code.txt",
|
|
310
|
+
"crabbox.stdout.txt", "crabbox.stderr.txt", "crabbox.timing.json", "assertions.json",
|
|
311
|
+
];
|
|
312
|
+
}
|
|
313
|
+
if (stopResult) checks.push({ id: "lease-stop", fn: () => stopResult.code === 0, error: `stop exit ${stopResult.code}` });
|
|
314
|
+
if (stopResult) expectedFiles.push("crabbox.stop.stdout.txt", "crabbox.stop.stderr.txt", "crabbox.stop.exit-code.txt");
|
|
315
|
+
const assertions = finalizeSuiteArtifacts(suiteDir, checks, { target: targetName, suite: suiteName, exitCode: result.code, signal: result.signal, elapsedMs }, expectedFiles);
|
|
316
|
+
console.log(` ${assertions.ok ? "PASS" : "FAIL"} ${suiteName} on ${targetName} (${elapsedMs}ms)`);
|
|
317
|
+
return { ok: assertions.ok, suiteDir, assertions };
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export async function runTargetSuites(config, targetName, suiteNames) {
|
|
321
|
+
const slug = `${config.packageName ?? "pi-oracle"}-${targetName}`;
|
|
322
|
+
console.log(` warmup ${targetName}...`);
|
|
323
|
+
const warmup = await warmupLease(config, targetName, slug);
|
|
324
|
+
if (!warmup.ok) {
|
|
325
|
+
const result = createWarmupFailureResult(config, targetName, suiteNames[0] ?? "platform-build", warmup);
|
|
326
|
+
return { ok: false, results: [result] };
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const results = [];
|
|
330
|
+
let sync = true;
|
|
331
|
+
let stopResult;
|
|
332
|
+
try {
|
|
333
|
+
for (const suiteName of suiteNames) {
|
|
334
|
+
const result = await runTargetSuite(config, targetName, suiteName, { ...warmup, sync });
|
|
335
|
+
results.push(result);
|
|
336
|
+
sync = false;
|
|
337
|
+
if (!result.ok) break;
|
|
338
|
+
}
|
|
339
|
+
} finally {
|
|
340
|
+
stopResult = await stopLease(config, targetName, warmup.leaseId);
|
|
341
|
+
for (const result of results) recordStopResultOnSuite(result.suiteDir, stopResult);
|
|
342
|
+
}
|
|
343
|
+
if (stopResult?.code !== 0) results.push(createStopFailureResult(config, targetName, warmup.leaseId, stopResult));
|
|
344
|
+
return { ok: results.every((result) => result.ok), results };
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function writeStopArtifacts(suiteDir, stopResult) {
|
|
348
|
+
writeFileSync(resolve(suiteDir, "crabbox.stop.stdout.txt"), stopResult.stdout ?? "");
|
|
349
|
+
writeFileSync(resolve(suiteDir, "crabbox.stop.stderr.txt"), stopResult.stderr ?? "");
|
|
350
|
+
writeFileSync(resolve(suiteDir, "crabbox.stop.exit-code.txt"), `code=${stopResult.code}\nsignal=${stopResult.signal ?? "none"}\n`);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function recordStopResultOnSuite(suiteDir, stopResult) {
|
|
354
|
+
if (!suiteDir || !stopResult) return;
|
|
355
|
+
writeStopArtifacts(suiteDir, stopResult);
|
|
356
|
+
const assertionsPath = resolve(suiteDir, "assertions.json");
|
|
357
|
+
const summaryPath = resolve(suiteDir, "summary.json");
|
|
358
|
+
const manifestPath = resolve(suiteDir, "artifact-manifest.json");
|
|
359
|
+
const assertions = JSON.parse(readFileSync(assertionsPath, "utf8"));
|
|
360
|
+
const summary = JSON.parse(readFileSync(summaryPath, "utf8"));
|
|
361
|
+
const stopCheck = { id: "lease-stop", ok: stopResult.code === 0, ...(stopResult.code === 0 ? {} : { error: `stop exit ${stopResult.code}` }) };
|
|
362
|
+
assertions.checks = [...(assertions.checks ?? []).filter((check) => check.id !== "lease-stop"), stopCheck];
|
|
363
|
+
assertions.ok = assertions.checks.every((check) => check.ok);
|
|
364
|
+
writeFileSync(assertionsPath, JSON.stringify({ ...assertions, writtenAt: new Date().toISOString() }, null, 2));
|
|
365
|
+
if (!assertions.ok) {
|
|
366
|
+
const failed = assertions.checks.filter((check) => !check.ok);
|
|
367
|
+
writeFileSync(resolve(suiteDir, "failures.md"), `# Assertion Failures\n\n${failed.map((check) => `- **${check.id}**: ${check.error ?? "failed"}`).join("\n")}\n\nTotal: ${failed.length} failure(s)\n`);
|
|
368
|
+
}
|
|
369
|
+
writeSummary(suiteDir, { ...summary, ok: Boolean(summary.ok && assertions.ok) });
|
|
370
|
+
const previousManifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
371
|
+
const expected = [...new Set([...(previousManifest.expected ?? []), "crabbox.stop.stdout.txt", "crabbox.stop.stderr.txt", "crabbox.stop.exit-code.txt", "artifact-manifest.json"])]
|
|
372
|
+
writeManifest(suiteDir, expected);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function finalizeSuiteArtifacts(suiteDir, checks, summary, expectedFiles) {
|
|
376
|
+
let assertions = runAssertions(suiteDir, checks);
|
|
377
|
+
writeSummary(suiteDir, { ...summary, ok: assertions.ok });
|
|
378
|
+
let manifest = writeManifest(suiteDir, assertions.ok ? expectedFiles : [...expectedFiles, "failures.md"]);
|
|
379
|
+
if (manifest.missing.length > 0) {
|
|
380
|
+
assertions = runAssertions(suiteDir, [
|
|
381
|
+
...checks,
|
|
382
|
+
{ id: "artifact-manifest-complete", fn: () => false, error: `missing required artifact(s): ${manifest.missing.join(", ")}` },
|
|
383
|
+
]);
|
|
384
|
+
writeSummary(suiteDir, { ...summary, ok: false });
|
|
385
|
+
manifest = writeManifest(suiteDir, [...expectedFiles, "failures.md"]);
|
|
386
|
+
}
|
|
387
|
+
void manifest;
|
|
388
|
+
return assertions;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function failTransportSuite(suiteDir, targetName, suiteName, result, phase) {
|
|
392
|
+
writeFileSync(resolve(suiteDir, `crabbox.${phase}.stdout.txt`), result.stdout ?? "");
|
|
393
|
+
writeFileSync(resolve(suiteDir, `crabbox.${phase}.stderr.txt`), result.stderr ?? "");
|
|
394
|
+
writeExitCode(suiteDir, result.code ?? 1, result.signal);
|
|
395
|
+
const assertions = finalizeSuiteArtifacts(
|
|
396
|
+
suiteDir,
|
|
397
|
+
[{ id: phase, fn: () => false, error: `Crabbox ${phase} failed: ${String(result.stderr || result.stdout || "unknown").slice(-500)}` }],
|
|
398
|
+
{ target: targetName, suite: suiteName, exitCode: result.code ?? 1, signal: result.signal, phase },
|
|
399
|
+
["summary.json", "target.json", "suite.json", "command.txt", "exit-code.txt", `crabbox.${phase}.stdout.txt`, `crabbox.${phase}.stderr.txt`, "assertions.json"],
|
|
400
|
+
);
|
|
401
|
+
return { ok: false, suiteDir, assertions };
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function createWarmupFailureResult(config, targetName, suiteName, warmup) {
|
|
405
|
+
const runId = makeRunId();
|
|
406
|
+
const suiteDir = createSuiteDir(config.artifactRoot, runId, targetName, suiteName);
|
|
407
|
+
const slug = `${config.packageName ?? "pi-oracle"}-${targetName}`;
|
|
408
|
+
writeFileSync(resolve(suiteDir, "target.json"), JSON.stringify({ targetName, platform: platformForTarget(targetName), slug, runId, writtenAt: new Date().toISOString() }, null, 2));
|
|
409
|
+
writeFileSync(resolve(suiteDir, "suite.json"), JSON.stringify({ suiteName, writtenAt: new Date().toISOString() }, null, 2));
|
|
410
|
+
writeCommand(suiteDir, `crabbox warmup ${targetName}`);
|
|
411
|
+
return failTransportSuite(suiteDir, targetName, suiteName, warmup, "warmup");
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function createStopFailureResult(config, targetName, leaseId, stopResult) {
|
|
415
|
+
const suiteName = "lease-cleanup";
|
|
416
|
+
const runId = makeRunId();
|
|
417
|
+
const suiteDir = createSuiteDir(config.artifactRoot, runId, targetName, suiteName);
|
|
418
|
+
writeFileSync(resolve(suiteDir, "target.json"), JSON.stringify({ targetName, platform: platformForTarget(targetName), leaseId, runId, writtenAt: new Date().toISOString() }, null, 2));
|
|
419
|
+
writeFileSync(resolve(suiteDir, "suite.json"), JSON.stringify({ suiteName, writtenAt: new Date().toISOString() }, null, 2));
|
|
420
|
+
writeCommand(suiteDir, `crabbox stop ${targetName} --id ${leaseId}`);
|
|
421
|
+
writeExitCode(suiteDir, stopResult.code, stopResult.signal);
|
|
422
|
+
writeStopArtifacts(suiteDir, stopResult);
|
|
423
|
+
const assertions = finalizeSuiteArtifacts(
|
|
424
|
+
suiteDir,
|
|
425
|
+
[{ id: "lease-stop", fn: () => false, error: `stop exit ${stopResult.code}` }],
|
|
426
|
+
{ target: targetName, suite: suiteName, exitCode: stopResult.code, signal: stopResult.signal },
|
|
427
|
+
["summary.json", "target.json", "suite.json", "command.txt", "exit-code.txt", "crabbox.stop.stdout.txt", "crabbox.stop.stderr.txt", "crabbox.stop.exit-code.txt", "assertions.json"],
|
|
428
|
+
);
|
|
429
|
+
return { ok: false, suiteDir, assertions };
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
export function readSuiteSummary(suiteDir) {
|
|
433
|
+
return JSON.parse(readFileSync(resolve(suiteDir, "summary.json"), "utf8"));
|
|
434
|
+
}
|