| 1 | import assert from "node:assert/strict"; |
| 2 | import { mkdtempSync, readFileSync, rmSync, writeFileSync, mkdirSync } from "node:fs"; |
| 3 | import { spawnSync } from "node:child_process"; |
| 4 | import os from "node:os"; |
| 5 | import path from "node:path"; |
| 6 | import vm from "node:vm"; |
| 7 | import test from "node:test"; |
| 8 | import { groups as windowsDesktopGroups, testArgs as windowsDesktopTestArgs } from "./desktop-windows-go-tests.mjs"; |
| 9 | |
| 10 | const workflow = name => readFileSync(new URL(`../.github/workflows/${name}.yml`, import.meta.url), "utf8"); |
| 11 | function job(source, name) { |
| 12 | const body = source.match(new RegExp(`\\n ${name}:\\n([\\s\\S]*?)(?=\\n [a-z][a-z0-9-]*:|$)`))?.[1]; |
| 13 | assert.ok(body, name); |
| 14 | return body; |
| 15 | } |
| 16 | function condition(body, context) { |
| 17 | const expression = body.match(/^ if: (.+)$/m)[1].replace(/^\$\{\{\s*|\s*\}\}$/g, "") |
| 18 | .replace(/needs\.([a-z][a-z0-9-]*)/g, 'needs["$1"]'); |
| 19 | return vm.runInNewContext(expression, { always: () => true, cancelled: () => false, ...context }); |
| 20 | } |
| 21 | function shellStep(body, name) { |
| 22 | return body.split(` - name: ${name}\n`)[1].match(/ run: \|\n((?: .*\n|\n)+)/)[1] |
| 23 | .replace(/^ /gm, ""); |
| 24 | } |
| 25 | const ci = workflow("ci"); |
| 26 | const release = workflow("release-desktop"); |
| 27 | const promote = workflow("release-promote"); |
| 28 | const appMemory = workflow("app-memory"); |
| 29 | |
| 30 | test("App memory artifacts support rerunning only failed shards", () => { |
| 31 | const prepare = job(appMemory, "prepare"); |
| 32 | const shard = job(appMemory, "shard"); |
| 33 | const aggregate = job(appMemory, "app-memory"); |
| 34 | const upload = body => body.match(/uses: actions\/upload-artifact@[^\n]+\n (?:if: [^\n]+\n )?with:\n name: ([^\n]+)([\s\S]*?)(?=\n - |$)/); |
| 35 | const download = body => [...body.matchAll(/uses: actions\/download-artifact@[^\n]+\n (?:if: [^\n]+\n )?with:\n (name|pattern): ([^\n]+)/g)]; |
| 36 | const render = (name, attempt, shardId = 1) => name |
| 37 | .replaceAll("${{ github.run_id }}", "123") |
| 38 | .replaceAll("${{ github.run_attempt }}", String(attempt)) |
| 39 | .replaceAll("${{ matrix.shard }}", String(shardId)); |
| 40 | const build = upload(prepare); |
| 41 | const shardReport = upload(shard); |
| 42 | const shardBuild = download(shard)[0][2]; |
| 43 | const [reports, aggregateBuild] = download(aggregate).map(match => match[2]); |
| 44 | assert.ok(build && shardReport && shardBuild && reports && aggregateBuild); |
| 45 | assert.match(build[2], /\n overwrite: true\n/); |
| 46 | assert.match(shardReport[2], /\n overwrite: true\n/); |
| 47 | assert.equal(render(build[1], 1), render(shardBuild, 2)); |
| 48 | assert.equal(render(build[1], 1), render(aggregateBuild, 2)); |
| 49 | for (const shardId of [1, 2, 3]) { |
| 50 | assert.equal(render(shardReport[1], shardId === 1 ? 2 : 1, shardId), |
| 51 | render(reports, 2).replaceAll("*", String(shardId))); |
| 52 | } |
| 53 | }); |
| 54 | |
| 55 | test("frontend artifact workflows share one exact Node runtime", () => { |
| 56 | const version = readFileSync(new URL("../.node-version", import.meta.url), "utf8").trim(); |
| 57 | assert.match(version, /^\d+\.\d+\.\d+$/); |
| 58 | for (const [name, source] of [["ci", ci], ["app-memory", appMemory], ["release-desktop", release]]) { |
| 59 | assert.doesNotMatch(source, /node-version: ["']?24(?:["']|\s)/, name); |
| 60 | assert.match(source, /node-version-file: \.node-version/, name); |
| 61 | } |
| 62 | const names = [...ci.matchAll(/^ ([a-z][a-z0-9-]*):$/gm)].map(match => match[1]); |
| 63 | const participants = names.map(name => [name, job(ci, name)]) |
| 64 | .filter(([, body]) => /artifact-identity\.mjs (create|verify)/.test(body)); |
| 65 | assert.ok(participants.length > 1, "cover both producer and consumers"); |
| 66 | for (const [name, body] of participants) { |
| 67 | assert.match(body, /uses: actions\/setup-node@[^\n]+\n\s+with:\n\s+node-version-file: \.node-version/, name); |
| 68 | assert.doesNotMatch(body, /node-version:/, `${name} must not override the shared runtime`); |
| 69 | } |
| 70 | for (const name of ["prepare", "shard", "app-memory"]) { |
| 71 | const body = job(appMemory, name); |
| 72 | assert.match(body, /node-version-file: \.node-version/, name); |
| 73 | assert.doesNotMatch(body, /node-version:/, name); |
| 74 | } |
| 75 | }); |
| 76 | |
| 77 | test("Windows PR verifies credential aliases before full push CI", () => { |
| 78 | assert.match(ci, /name: test \(Windows credential ACL identity\)[\s\S]*?runner\.os == 'Windows' && github\.event_name == 'pull_request'[\s\S]*?go test -timeout=2m -run '\^TestCredentialAccessRepairsLegacyCredentialDeny\|\^TestRepairLegacyCredentialDenyMatchesFileAcrossPathAliases\$' \.\/internal\/config \.\/internal\/winaclresidue/); |
| 79 | }); |
| 80 | |
| 81 | test("release tag mutation follows approval with an explicit identity and read-only default token", () => { |
| 82 | assert.match(job(promote, "preflight"), /permissions:\n actions: read\n attestations: read\n contents: read/); |
| 83 | assert.match(job(promote, "authorize"), /environment: release[\s\S]*permissions:\n contents: read/); |
| 84 | const activation = job(promote, "activate"); |
| 85 | assert.match(activation, /needs: \[preflight, authorize\]/); |
| 86 | assert.match(activation, /permissions:\n contents: read/); |
| 87 | assert.match(activation, /persist-credentials: false/); |
| 88 | assert.match(activation, /GH_TOKEN: \$\{\{ secrets.RELEASE_TAG_TOKEN \}\}/); |
| 89 | assert.match(activation, /RELEASE_TAG_ACTOR_ID: \$\{\{ needs.preflight.outputs.tag_actor_id \}\}/); |
| 90 | assert.doesNotMatch(job(promote, "preflight"), /release-candidate-tags.sh activate|git push/); |
| 91 | }); |
| 92 | |
| 93 | test("cancelled CI releases workers, aggregates, and metrics without hiding live failures", () => { |
| 94 | for (const name of ["test", "windows-control", "windows-isolated", "race", "sdk", "desktop-prepare", |
| 95 | "desktop-frontend", "desktop-browser-group", "desktop-go", "desktop-go-race", "desktop-macos", |
| 96 | "desktop-windows", "desktop-windows-go-group", "desktop-windows-package", "lint-code", "coverage", "prune-go-cache"]) { |
| 97 | assert.equal(condition(job(ci, name), { cancelled: () => true }), false, name); |
| 98 | } |
| 99 | for (const name of ["root", "lint", "desktop", "desktop-browser", "desktop-windows-go", "ci-metrics"]) { |
| 100 | assert.equal(condition(job(ci, name), { cancelled: () => true }), false, name); |
| 101 | // A failed dependency must still reach the fail-closed shell assertions. |
| 102 | assert.equal(condition(job(ci, name), { cancelled: () => false, success: () => false, |
| 103 | failure: () => true, needs: { child: { result: "failure" } } }), true, name); |
| 104 | } |
| 105 | }); |
| 106 | |
| 107 | test("packaging changes run native installer acceptance before merge", () => { |
| 108 | assert.match(job(ci, "desktop-prepare"), /REASONIX_COMMIT: \$\{\{ github.sha \}\}/, |
| 109 | "prepared frontend must budget the full source identity used by native packaging"); |
| 110 | const body = job(ci, "desktop-windows-package"); |
| 111 | assert.doesNotMatch(ci, /performance-benchmark\.mjs/); |
| 112 | const diagnostic = workflow("diagnostic-overhead"); |
| 113 | assert.match(diagnostic, /schedule:/); |
| 114 | assert.match(diagnostic, /workflow_dispatch:/); |
| 115 | assert.match(diagnostic, /desktop\/electron\/\*\*/); |
| 116 | assert.match(diagnostic, /desktop\/frontend\/\*\*/); |
| 117 | assert.match(diagnostic, /run: pnpm install --frozen-lockfile/); |
| 118 | assert.match(diagnostic, /run: node electron\/scripts\/performance-benchmark\.mjs/); |
| 119 | assert.match(diagnostic, /if-no-files-found: error/); |
| 120 | assert.doesNotMatch(diagnostic, /continue-on-error/); |
| 121 | for (const event of ["pull_request", "push"]) { |
| 122 | for (const packaging of ["true", "false", ""]) { |
| 123 | const context = { github: { event_name: event }, needs: { |
| 124 | "desktop-prepare": { result: "success" }, changes: { outputs: { packaging, notes_only: "false" } }, |
| 125 | } }; |
| 126 | assert.equal(condition(body, context), event === "push" || packaging !== "false"); |
| 127 | const aggregate = job(ci, "desktop").match(/PACKAGE_REQUIRED: \$\{\{ (.+) \}\}/)[1]; |
| 128 | assert.equal(vm.runInNewContext(aggregate, context), condition(body, context)); |
| 129 | } |
| 130 | } |
| 131 | }); |
| 132 | |
| 133 | test("notes pushes preserve required ancestor CI while code pushes cancel obsolete runs", () => { |
| 134 | const dir = mkdtempSync(path.join(os.tmpdir(), "reasonix-ci-cancel-")); |
| 135 | const git = (...args) => { |
| 136 | const result = spawnSync("git", args, { cwd: dir, encoding: "utf8" }); |
| 137 | assert.equal(result.status, 0, result.stderr); |
| 138 | return result.stdout.trim(); |
| 139 | }; |
| 140 | try { |
| 141 | git("init", "-q"); |
| 142 | git("config", "user.name", "test"); |
| 143 | git("config", "user.email", "test@example.invalid"); |
| 144 | const commit = (file, content) => { |
| 145 | writeFileSync(path.join(dir, file), content); |
| 146 | git("add", "."); git("commit", "-qm", "fixture"); |
| 147 | return git("rev-parse", "HEAD"); |
| 148 | }; |
| 149 | const old = commit("code", "old"); |
| 150 | const code = commit("code", "current"); |
| 151 | mkdirSync(path.join(dir, "release-notes")); |
| 152 | const notes = commit("release-notes/record", "first"); |
| 153 | const head = commit("release-notes/record", "reviewed"); |
| 154 | const run = candidate => { |
| 155 | const script = `set -euo pipefail |
| 156 | sleep() { :; } |
| 157 | gh() { |
| 158 | if [ "$2" = "-X" ]; then printf '%s\\n' "$4" >> "$CANCEL_LOG"; |
| 159 | elif [[ "$2" == *workflows/ci.yml/runs* ]]; then printf '%s\\n' "$ACTIVE_RUNS"; |
| 160 | else echo completed; fi |
| 161 | } |
| 162 | ${shellStep(job(workflow("supersede-ci"), "cancel-superseded"), "Cancel CI runs this push supersedes")}`; |
| 163 | const log = path.join(dir, "cancel-log"); |
| 164 | writeFileSync(log, ""); |
| 165 | const result = spawnSync("bash", ["-c", script], { cwd: dir, encoding: "utf8", env: { |
| 166 | ...process.env, GITHUB_REPOSITORY: "example/repo", GITHUB_SHA: candidate, CANCEL_LOG: log, |
| 167 | ACTIVE_RUNS: `11 ${old}\n12 ${code}\n13 ${notes}\n14 ${head}`, |
| 168 | } }); |
| 169 | assert.equal(result.status, 0, result.stderr); |
| 170 | return readFileSync(log, "utf8").trim().split("\n"); |
| 171 | }; |
| 172 | assert.deepEqual(run(head), ["repos/example/repo/actions/runs/11/cancel"]); |
| 173 | const next = commit("code", "next"); |
| 174 | assert.deepEqual(run(next), [11, 12, 13, 14].map(id => `repos/example/repo/actions/runs/${id}/cancel`)); |
| 175 | const group = ci.match(/ group: (ci-.+)/)[1]; |
| 176 | assert.match(group, /github.event_name == 'push' && github.sha \|\| github.ref/); |
| 177 | } finally { |
| 178 | rmSync(dir, { recursive: true, force: true }); |
| 179 | } |
| 180 | }); |
| 181 | |
| 182 | test("Certum signing survives skipped ancestor gates but requires successful inputs", () => { |
| 183 | const body = job(release, "windows-sign"); |
| 184 | // A status function is required to override GitHub's implicit success(), |
| 185 | // which otherwise propagates a skipped standalone/orchestrator ancestor. |
| 186 | assert.match(body, /if:.*always\(\)/); |
| 187 | const context = { |
| 188 | needs: { resolve: { result: "success" }, "windows-build": { result: "success" }, "signing-contract": { result: "success" } }, |
| 189 | github: { repository: "esengine/DeepSeek-Reasonix" }, |
| 190 | inputs: { desktop_manual_only: false }, |
| 191 | }; |
| 192 | assert.equal(condition(body, context), true); |
| 193 | assert.equal(condition(body, { ...context, cancelled: () => true }), false); |
| 194 | for (const name of Object.keys(context.needs)) { |
| 195 | for (const result of ["failure", "skipped", "cancelled"]) { |
| 196 | assert.equal(condition(body, { ...context, needs: { ...context.needs, [name]: { result } } }), false); |
| 197 | } |
| 198 | } |
| 199 | assert.equal(condition(body, { ...context, inputs: { desktop_manual_only: true } }), false); |
| 200 | assert.equal(condition(body, { ...context, github: { repository: "example/fork" } }), false); |
| 201 | }); |
| 202 | |
| 203 | test("Windows full runs use the partitioned suite without a duplicate module sweep", () => { |
| 204 | const body = job(ci, "test"); |
| 205 | const enabled = (name, os, event, run = "true") => { |
| 206 | const step = body.split(` - name: ${name}\n`)[1].split(/\n - /)[0]; |
| 207 | const expression = step.match(/^ if: (.+)$/m)[1]; |
| 208 | return vm.runInNewContext(expression, { |
| 209 | env: { RUN_STEPS: run }, runner: { os }, github: { event_name: event }, |
| 210 | }); |
| 211 | }; |
| 212 | for (const event of ["pull_request", "push", "workflow_dispatch"]) { |
| 213 | for (const os of ["Linux", "macOS", "Windows"]) { |
| 214 | assert.equal(enabled("test", os, event), os === "Linux" || (os === "macOS" && event !== "pull_request")); |
| 215 | assert.equal(enabled("test (full)", os, event), os === "Windows" && event !== "pull_request"); |
| 216 | assert.equal(enabled("test (Windows smoke)", os, event), os === "Windows" && event === "pull_request"); |
| 217 | assert.equal(enabled("test", os, event, "false"), false); |
| 218 | assert.equal(enabled("test (full)", os, event, "false"), false); |
| 219 | } |
| 220 | } |
| 221 | assert.match(body, /run: node scripts\/windows-go-tests\.mjs full/); |
| 222 | assert.match(job(ci, "windows-isolated"), /group: \[acp, agent, boot, bot, serve, session, worktree\]/); |
| 223 | assert.match(job(ci, "windows-control"), /run: node scripts\/windows-go-tests\.mjs control/); |
| 224 | }); |
| 225 | |
| 226 | test("App memory workflow tiers pull requests and keeps full scheduled coverage", t => { |
| 227 | assert.match(appMemory, /schedule:\n - cron: "17 3 \* \* \*"/); |
| 228 | assert.match(appMemory, /\[ "\$EVENT_NAME" = workflow_dispatch \] \|\| \[ "\$EVENT_NAME" = schedule \]/); |
| 229 | assert.match(appMemory, /matrix:\n shard: \$\{\{ fromJSON\(needs\.changes\.outputs\.memory_shards\) \}\}/); |
| 230 | assert.match(appMemory, /REASONIX_APP_MEMORY_PROFILE: \$\{\{ needs\.changes\.outputs\.memory_profile \}\}/); |
| 231 | const script = shellStep(job(appMemory, "changes"), "Select memory profile"); |
| 232 | const root = mkdtempSync(path.join(os.tmpdir(), "reasonix-memory-workflow-")); |
| 233 | t.after(() => rmSync(root, { recursive: true, force: true })); |
| 234 | let index = 0; |
| 235 | const run = env => { |
| 236 | const output = path.join(root, `output-${index++}`); |
| 237 | const result = spawnSync("bash", ["-e", "-c", script], { |
| 238 | env: { ...process.env, GITHUB_OUTPUT: output, GITHUB_STEP_SUMMARY: path.join(root, "summary"), ...env }, encoding: "utf8", |
| 239 | }); |
| 240 | return { ...result, workflowOutput: result.status === 0 ? readFileSync(output, "utf8") : "" }; |
| 241 | }; |
| 242 | for (const [env, expected] of [ |
| 243 | [{ EVENT_NAME: "pull_request", MEMORY: "true", MEMORY_FULL: "false" }, "profile=short\nshards=[1]\n"], |
| 244 | [{ EVENT_NAME: "pull_request", MEMORY: "true", MEMORY_FULL: "true" }, "profile=full\nshards=[1,2,3]\n"], |
| 245 | [{ EVENT_NAME: "push", MEMORY: "true", MEMORY_FULL: "false" }, "profile=full\nshards=[1,2,3]\n"], |
| 246 | [{ EVENT_NAME: "pull_request", MEMORY: "false", MEMORY_FULL: "false" }, "profile=off\nshards=[1]\n"], |
| 247 | ]) { |
| 248 | const result = run(env); |
| 249 | assert.equal(result.status, 0, result.stderr); |
| 250 | assert.equal(result.workflowOutput, expected); |
| 251 | } |
| 252 | }); |
| 253 | |
| 254 | test("macOS signing diagnostics require protected main and cannot publish", () => { |
| 255 | const source = workflow("macos-signing-check"); |
| 256 | const verify = job(source, "verify"); |
| 257 | const github = { repository: "esengine/DeepSeek-Reasonix", ref: "refs/heads/main-v2", ref_protected: true }; |
| 258 | assert.equal(condition(verify, { github }), true); |
| 259 | for (const changed of [{ repository: "fork/Reasonix" }, { ref: "refs/tags/v1.0.0" }, { ref_protected: false }]) { |
| 260 | assert.equal(condition(verify, { github: { ...github, ...changed } }), false); |
| 261 | } |
| 262 | assert.match(verify, /environment: release/); |
| 263 | assert.match(verify, /ref: \$\{\{ github.sha \}\}/); |
| 264 | assert.match(source, /permissions:\n contents: read\n/); |
| 265 | assert.doesNotMatch(source, /: write|secrets\.(R2_|SIGNPATH_|MINISIGN_|NPM_)/); |
| 266 | assert.match(verify, /HAS_APPLE_CERT: "true"/); |
| 267 | assert.match(verify, /scripts\/desktop-build.sh darwin\/universal v0.0.0-signing-check stable/); |
| 268 | assert.match(verify, /path: \$\{\{ runner.temp \}\}\/apple-notarization\/\*\.json/); |
| 269 | assert.match(verify, /if: always\(\)/); |
| 270 | }); |
| 271 | |
| 272 | test("required desktop aggregate rejects every failed, cancelled or unexpectedly skipped child", () => { |
| 273 | const script = shellStep(job(ci, "desktop"), "Verify desktop validation jobs"); |
| 274 | const success = { CHANGES_RESULT: "success", PREPARE_REQUIRED: "true", NATIVE_REQUIRED: "true", FRONTEND_REQUIRED: "true", BROWSER_REQUIRED: "true", |
| 275 | PACKAGE_REQUIRED: "true", RACE_REQUIRED: "true", PREPARE_RESULT: "success", GO_RESULT: "success", GO_RACE_RESULT: "success", FRONTEND_RESULT: "success", BROWSER_RESULT: "success", |
| 276 | MACOS_RESULT: "success", WINDOWS_RESULT: "success", WINDOWS_GO_RESULT: "success", PACKAGE_RESULT: "success" }; |
| 277 | const run = env => spawnSync("bash", ["-e", "-c", script], { env: { ...process.env, ...env } }).status; |
| 278 | assert.equal(run(success), 0); |
| 279 | for (const key of ["PREPARE_RESULT", "GO_RESULT", "GO_RACE_RESULT", "FRONTEND_RESULT", "BROWSER_RESULT", "CHANGES_RESULT", |
| 280 | "MACOS_RESULT", "WINDOWS_RESULT", "WINDOWS_GO_RESULT", "PACKAGE_RESULT"]) { |
| 281 | for (const value of ["failure", "cancelled", "skipped", ""]) assert.notEqual(run({ ...success, [key]: value }), 0, `${key}=${value}`); |
| 282 | } |
| 283 | // A pull request that cannot affect the desktop module: every child skips |
| 284 | // except the browser and Windows Go aggregates, which validate their groups. |
| 285 | assert.equal(run({ ...success, PREPARE_REQUIRED: "false", NATIVE_REQUIRED: "false", FRONTEND_REQUIRED: "false", BROWSER_REQUIRED: "false", |
| 286 | PACKAGE_REQUIRED: "false", RACE_REQUIRED: "false", PREPARE_RESULT: "skipped", GO_RESULT: "skipped", GO_RACE_RESULT: "skipped", FRONTEND_RESULT: "skipped", |
| 287 | BROWSER_RESULT: "success", MACOS_RESULT: "skipped", WINDOWS_RESULT: "skipped", WINDOWS_GO_RESULT: "success", PACKAGE_RESULT: "skipped" }), 0); |
| 288 | // A pull request that touches native code runs every native child except the race sweep. |
| 289 | assert.equal(run({ ...success, RACE_REQUIRED: "false", GO_RACE_RESULT: "skipped" }), 0); |
| 290 | assert.notEqual(run({ ...success, RACE_REQUIRED: "false", GO_RACE_RESULT: "success" }), 0); |
| 291 | // A pull request unrelated to packaging must skip it. |
| 292 | assert.equal(run({ ...success, PACKAGE_REQUIRED: "false", PACKAGE_RESULT: "skipped" }), 0); |
| 293 | assert.notEqual(run({ ...success, PACKAGE_REQUIRED: "false", PACKAGE_RESULT: "success" }), 0); |
| 294 | assert.equal(run({ ...success, FRONTEND_REQUIRED: "false", BROWSER_REQUIRED: "false", FRONTEND_RESULT: "skipped", BROWSER_RESULT: "success" }), 0); |
| 295 | const browserScript = shellStep(job(ci, "desktop-browser"), "Verify desktop browser groups"); |
| 296 | const browser = spawnSync("bash", ["-e", "-c", browserScript], { env: { ...process.env, |
| 297 | CHANGES_RESULT: "success", SHOULD_RUN: "false", PREPARE_RESULT: "skipped", GROUP_RESULT: "skipped" } }); |
| 298 | assert.equal(browser.status, 0, "an unneeded browser aggregate succeeds after validating skipped groups"); |
| 299 | assert.notEqual(run({ ...success, BROWSER_REQUIRED: "false", BROWSER_RESULT: "skipped" }), 0); |
| 300 | assert.notEqual(run({ ...success, NATIVE_REQUIRED: "false", WINDOWS_GO_RESULT: "skipped" }), 0); |
| 301 | }); |
| 302 | |
| 303 | test("required lint aggregates code lint and the deduplicated frontend suite", () => { |
| 304 | const body = job(ci, "lint"); |
| 305 | const script = shellStep(body, "Verify lint and frontend validation jobs"); |
| 306 | const success = { CHANGES_RESULT: "success", LINT_CODE_RESULT: "success", LINT_CODE_REQUIRED: "true", |
| 307 | RELEASE_CONTROL_RESULT: "success", RELEASE_CONTROL_REQUIRED: "true", |
| 308 | PREPARE_RESULT: "success", FRONTEND_RESULT: "success", FRONTEND_REQUIRED: "true" }; |
| 309 | const run = env => spawnSync("bash", ["-e", "-c", script], { env: { ...process.env, ...env } }).status; |
| 310 | assert.equal(run(success), 0); |
| 311 | for (const key of ["CHANGES_RESULT", "LINT_CODE_RESULT", "RELEASE_CONTROL_RESULT", "PREPARE_RESULT", "FRONTEND_RESULT"]) |
| 312 | for (const value of ["failure", "cancelled", "skipped", ""]) assert.notEqual(run({ ...success, [key]: value }), 0, `${key}=${value}`); |
| 313 | assert.equal(run({ ...success, LINT_CODE_REQUIRED: "false", LINT_CODE_RESULT: "skipped", |
| 314 | RELEASE_CONTROL_REQUIRED: "false", RELEASE_CONTROL_RESULT: "skipped", |
| 315 | FRONTEND_REQUIRED: "false", PREPARE_RESULT: "skipped", FRONTEND_RESULT: "skipped" }), 0); |
| 316 | assert.equal(run({ ...success, FRONTEND_REQUIRED: "false", PREPARE_RESULT: "success", FRONTEND_RESULT: "skipped" }), 0); |
| 317 | assert.doesNotMatch(job(ci, "lint-code"), /test:motion/); |
| 318 | assert.match(body, /needs: \[changes, lint-code, release-control, desktop-prepare, desktop-frontend\]/); |
| 319 | }); |
| 320 | |
| 321 | test("required root aggregate covers the jobs the per-OS test legs do not", () => { |
| 322 | const body = job(ci, "root"); |
| 323 | const script = shellStep(body, "Verify root validation jobs"); |
| 324 | const success = { CHANGES_RESULT: "success", CODE_REQUIRED: "true", COVERAGE_REQUIRED: "true", |
| 325 | CONTROL_RESULT: "success", ISOLATED_RESULT: "success", SDK_RESULT: "success", COVERAGE_RESULT: "success" }; |
| 326 | const run = env => spawnSync("bash", ["-e", "-c", script], { env: { ...process.env, ...env } }).status; |
| 327 | assert.equal(run(success), 0); |
| 328 | for (const key of ["CHANGES_RESULT", "CONTROL_RESULT", "ISOLATED_RESULT", "SDK_RESULT", "COVERAGE_RESULT"]) |
| 329 | for (const value of ["failure", "cancelled", "skipped", ""]) assert.notEqual(run({ ...success, [key]: value }), 0, `${key}=${value}`); |
| 330 | // A pull request unrelated to code: the internally-gated jobs still |
| 331 | // report success, the skippable ones must actually be skipped. |
| 332 | assert.equal(run({ ...success, CODE_REQUIRED: "false", COVERAGE_REQUIRED: "false", |
| 333 | ISOLATED_RESULT: "skipped", COVERAGE_RESULT: "skipped" }), 0); |
| 334 | // Coverage is push-only; a pull request that ran it is a routing defect. |
| 335 | assert.notEqual(run({ ...success, COVERAGE_REQUIRED: "false", COVERAGE_RESULT: "success" }), 0); |
| 336 | assert.match(body, /needs: \[changes, windows-control, windows-isolated, sdk, coverage\]/); |
| 337 | // govulncheck sets continue-on-error, so needs.*.result is success even when |
| 338 | // it fails; aggregating it would be a tautology that reads like coverage. |
| 339 | assert.match(job(ci, "govulncheck"), /continue-on-error: true/); |
| 340 | assert.doesNotMatch(body, /GOVULN/); |
| 341 | }); |
| 342 | |
| 343 | // The gap that let a failing desktop-windows-go merge was a job nobody had |
| 344 | // wired into a required aggregate. Keep that unrepeatable: every job must be |
| 345 | // reachable from a required check, or be named here with a reason. |
| 346 | test("every ci job is reachable from a required aggregate", () => { |
| 347 | const required = ["test", "race", "lint", "desktop", "root"]; |
| 348 | const advisory = { |
| 349 | changes: "asserted by line one of every aggregate", |
| 350 | "ci-metrics": "reports queue and stage timing; failure must not block merges", |
| 351 | "prune-go-cache": "push-only cache housekeeping; cannot report on a pull request", |
| 352 | govulncheck: "continue-on-error by design — stdlib advisories precede Go patch releases", |
| 353 | }; |
| 354 | const jobs = ci.slice(ci.indexOf("\njobs:\n")); // `on:` also nests two-space keys |
| 355 | const names = [...jobs.matchAll(/^ {2}([a-z][a-z0-9-]*):$/gm)].map(match => match[1]); |
| 356 | assert.ok(names.length > 20, `expected the full job list, got ${names.length}`); |
| 357 | // A bracketed list may wrap across lines, so consume up to its closing ]. |
| 358 | const edges = new Map(names.map(name => [name, (job(ci, name).match(/^ {4}needs:\s*(\[[^\]]*\]|\S.*)$/m)?.[1] ?? "") |
| 359 | .replace(/[[\]]/g, "").split(",").map(entry => entry.trim()).filter(Boolean)])); |
| 360 | const reachable = new Set(required); |
| 361 | for (const name of required) for (const dependency of edges.get(name) ?? []) reachable.add(dependency); |
| 362 | for (let size = 0; size !== reachable.size;) { |
| 363 | size = reachable.size; |
| 364 | for (const name of [...reachable]) for (const dependency of edges.get(name) ?? []) reachable.add(dependency); |
| 365 | } |
| 366 | for (const name of names) { |
| 367 | if (reachable.has(name)) continue; |
| 368 | assert.ok(advisory[name], `${name} is gated by no required check and is not declared advisory`); |
| 369 | } |
| 370 | for (const name of Object.keys(advisory)) |
| 371 | assert.ok(names.includes(name), `${name} is declared advisory but no longer exists`); |
| 372 | }); |
| 373 | |
| 374 | test("reuse skips only build work and still gates every publisher on validation", () => { |
| 375 | const context = { |
| 376 | inputs: { preflight_artifact_prefix: "desktop-123-1-preflight", orchestrated: true, signing_preflight_verified: true, signing_preflight: false, production_signing_smoke: false }, |
| 377 | needs: { resolve: { result: "success" }, "signing-contract": { result: "success" }, "mac-universal-intel": { result: "skipped" }, "windows-build": { result: "skipped" }, "windows-sign": { result: "skipped" }, "windows-runtime-acceptance": { result: "skipped" }, build: { result: "skipped" } }, |
| 378 | }; |
| 379 | assert.equal(condition(job(release, "build"), context), false); |
| 380 | assert.equal(condition(job(release, "publish"), context), true); |
| 381 | for (const key of ["resolve", "signing-contract", "mac-universal-intel", "windows-build", "windows-sign", "build"]) { |
| 382 | for (const result of ["failure", "cancelled"]) { |
| 383 | const changed = structuredClone(context); |
| 384 | changed.needs[key].result = result; |
| 385 | assert.equal(condition(job(release, "publish"), changed), false, `${key}=${result}`); |
| 386 | } |
| 387 | } |
| 388 | for (const key of ["orchestrated", "signing_preflight_verified"]) { |
| 389 | assert.equal(condition(job(release, "publish"), { ...context, inputs: { ...context.inputs, [key]: false } }), false); |
| 390 | } |
| 391 | for (const key of ["signing_preflight", "production_signing_smoke"]) { |
| 392 | assert.equal(condition(job(release, "publish"), { ...context, inputs: { ...context.inputs, [key]: true } }), false); |
| 393 | } |
| 394 | const fresh = structuredClone(context); |
| 395 | fresh.inputs.preflight_artifact_prefix = ""; |
| 396 | assert.equal(condition(job(release, "build"), fresh), true); |
| 397 | assert.equal(condition(job(release, "publish"), fresh), false); |
| 398 | fresh.needs.build.result = "success"; |
| 399 | fresh.needs["windows-build"].result = "success"; |
| 400 | fresh.needs["mac-universal-intel"].result = "success"; |
| 401 | assert.equal(condition(job(release, "publish"), fresh), false, "unsigned Windows bundles cannot publish"); |
| 402 | fresh.needs["windows-sign"].result = "success"; |
| 403 | assert.equal(condition(job(release, "publish"), fresh), false, "signed Windows installers must pass native runtime acceptance"); |
| 404 | fresh.needs["windows-runtime-acceptance"].result = "success"; |
| 405 | assert.equal(condition(job(release, "publish"), fresh), true); |
| 406 | }); |
| 407 | |
| 408 | test("Certum signing preserves native builds and gates publication and attestation", () => { |
| 409 | const packageJob = job(ci, "desktop-windows-package"); |
| 410 | assert.match(packageJob, /test-windows-installer-startup\.ps1/); |
| 411 | assert.match(packageJob, /ExpectedVersion v0\.0\.0-ci/); |
| 412 | const windowsBuild = job(release, "windows-build"); |
| 413 | const signer = job(release, "windows-sign"); |
| 414 | assert.match(windowsBuild, /runner: windows-latest, platform: windows\/amd64/); |
| 415 | assert.match(windowsBuild, /runner: windows-11-arm, platform: windows\/arm64/); |
| 416 | assert.match(windowsBuild, /Smoke-test packaged Electron startup/); |
| 417 | assert.match(windowsBuild, /Upload Windows signing inputs/); |
| 418 | assert.match(job(ci, 'test'), /test-windows-installer-startup\.test\.ps1/); |
| 419 | assert.match(signer, /needs: \[resolve, windows-build, signing-contract\]/); |
| 420 | assert.match(signer, /runs-on: windows-2022/); |
| 421 | assert.match(signer, /ref: \$\{\{ github.workflow_sha \}\}/); |
| 422 | assert.equal(signer.match(/setup-certum/g)?.length, 1, "both architectures share one Certum session"); |
| 423 | // Certum work stays in the one session; only the credential-free packaging |
| 424 | // of the two architectures runs at the same time, between the two. |
| 425 | const phases = [ |
| 426 | "Sign both payloads in the shared Certum session", |
| 427 | "Package both architectures in parallel", |
| 428 | "Seal amd64 in the shared Certum session", |
| 429 | "Seal arm64 in the shared Certum session", |
| 430 | ].map((name) => signer.indexOf(`name: ${name}`)); |
| 431 | assert.ok(phases.every((index) => index >= 0), "windows-sign names its sign, package and seal phases"); |
| 432 | assert.deepEqual([...phases].sort((a, b) => a - b), phases, "sign, package, then seal each architecture"); |
| 433 | for (const phase of ["sign", "package", "seal"]) { |
| 434 | assert.match(signer, new RegExp(`FINALIZE_PHASE=${phase} bash `)); |
| 435 | } |
| 436 | assert.ok(signer.indexOf("Seal amd64 in the shared Certum session") |
| 437 | < signer.indexOf("name: ${{ needs.resolve.outputs.artifact_prefix }}-windows-amd64")); |
| 438 | assert.ok(signer.indexOf("Seal arm64 in the shared Certum session") |
| 439 | < signer.indexOf("name: ${{ needs.resolve.outputs.artifact_prefix }}-windows-arm64")); |
| 440 | assert.ok(!release.includes("secrets.SIGNPATH_API_TOKEN")); |
| 441 | const runtimeAcceptance = job(release, "windows-runtime-acceptance"); |
| 442 | assert.match(runtimeAcceptance, /runner: windows-latest, arch: amd64/); |
| 443 | assert.match(runtimeAcceptance, /runner: windows-11-arm, arch: arm64/); |
| 444 | assert.match(runtimeAcceptance, /test-windows-installer-startup\.ps1/); |
| 445 | assert.match(runtimeAcceptance, /ExpectedVersion "\$\{\{ needs\.resolve\.outputs\.version \}\}"/); |
| 446 | const attestation = job(release, "attest-signing-contract"); |
| 447 | assert.ok(!attestation.includes("gh api --method"), "GITHUB_TOKEN cannot mutate repository variables"); |
| 448 | assert.match(attestation, /uses: actions\/upload-artifact@[0-9a-f]{40} # v7\b/); |
| 449 | assert.match(attestation, /verified-contract\.json/); |
| 450 | assert.match(attestation, /gh variable set/); |
| 451 | const context = { github: { repository: "esengine/DeepSeek-Reasonix" }, inputs: { signing_preflight: true, orchestrated: false }, |
| 452 | needs: { "signing-contract": { result: "success" }, build: { result: "success" }, "windows-build": { result: "success" }, "windows-sign": { result: "success" }, "windows-runtime-acceptance": { result: "success" } } }; |
| 453 | assert.equal(condition(attestation, context), true); |
| 454 | for (const key of ["windows-build", "windows-sign", "windows-runtime-acceptance"]) { |
| 455 | for (const result of ["failure", "cancelled", "skipped"]) { |
| 456 | assert.equal(condition(attestation, { ...context, needs: { ...context.needs, [key]: { result } } }), false); |
| 457 | } |
| 458 | } |
| 459 | }); |
| 460 | |
| 461 | test("reuse never moves artifact verification past public mutation or trusts candidate scripts", () => { |
| 462 | const publisher = job(release, "publish"); |
| 463 | assert.ok(publisher.indexOf("Verify complete signed artifact handoff") < publisher.indexOf("name: Publish GitHub release")); |
| 464 | assert.ok(publisher.includes("node release-control/scripts/desktop-release-artifacts.mjs collect")); |
| 465 | assert.ok(publisher.includes("ref: ${{ github.workflow_sha }}")); |
| 466 | assert.ok(!publisher.includes("merge-multiple: true")); |
| 467 | const stable = workflow("release-stable"); |
| 468 | assert.ok(job(stable, "desktop").includes("preflight_artifact_prefix: ${{ needs.signpath-preflight.outputs.artifact_prefix }}")); |
| 469 | for (const name of ["desktop", "cli", "npm"]) assert.ok(job(stable, name).includes("needs: [authorize, signpath-preflight]")); |
| 470 | }); |
| 471 | |
| 472 | test("all desktop consumers verify the prepared build and reject a failed preparation", () => { |
| 473 | const context = { github: { event_name: "pull_request" }, |
| 474 | needs: { changes: { outputs: { desktop: "true" } }, "desktop-prepare": { result: "success" } } }; |
| 475 | const aggregate = job(ci, "desktop"); |
| 476 | const verifications = ci.match(/artifact-identity\.mjs verify/g)?.length ?? 0; |
| 477 | assert.equal(ci.match(/--attempt "\$\{\{ needs\.desktop-prepare\.outputs\.producer_attempt \}\}"/g)?.length, verifications); |
| 478 | assert.equal(ci.match(/test -n "\$\{\{ needs\.desktop-prepare\.outputs\.producer_attempt \}\}"/g)?.length, verifications); |
| 479 | for (const [name, variant] of [ |
| 480 | ["desktop-go", "stable"], ["desktop-frontend", "stable"], ["desktop-browser-group", "stable"], |
| 481 | ["desktop-macos", "stable"], ["desktop-windows", "canary"], ["desktop-windows-go-group", "stable"], |
| 482 | ]) { |
| 483 | const body = job(ci, name); |
| 484 | if (["desktop-go", "desktop-frontend"].includes(name)) assert.ok(aggregate.includes(name)); |
| 485 | assert.ok(body.includes("needs: [changes, desktop-prepare]")); |
| 486 | assert.ok(body.includes(`name: \${{ needs.desktop-prepare.outputs.${variant}_artifact_name }}`)); |
| 487 | assert.ok(body.includes(`--shell electron --channel ${variant}`)); |
| 488 | assert.ok(body.includes('test -n "${{ needs.desktop-prepare.outputs.producer_attempt }}"')); |
| 489 | assert.ok(body.includes('--attempt "${{ needs.desktop-prepare.outputs.producer_attempt }}"')); |
| 490 | assert.doesNotMatch(body, /artifact-identity\.mjs verify[^]*?--attempt "\$GITHUB_RUN_ATTEMPT"/); |
| 491 | assert.ok(!body.includes("pnpm --dir frontend build")); |
| 492 | assert.equal(condition(body, context), true); |
| 493 | assert.equal(condition(body, { ...context, needs: { ...context.needs, "desktop-prepare": { result: "failure" } } }), false); |
| 494 | } |
| 495 | for (const name of ["desktop-windows", "desktop-windows-package"]) { |
| 496 | const body = job(ci, name); |
| 497 | assert.match(body, /REASONIX_PACKAGE_REUSE_FRONTEND: "1"/); |
| 498 | assert.match(body, /REASONIX_FRONTEND_PNPM_VERSION="\$\(pnpm --version\)"\n\s+export REASONIX_FRONTEND_PNPM_VERSION/); |
| 499 | assert.match(body, /canary_artifact_name/); |
| 500 | } |
| 501 | assert.match(job(ci, "desktop-macos"), /REASONIX_FRONTEND_PNPM_VERSION="\$\(pnpm --version\)"\n\s+export REASONIX_FRONTEND_PNPM_VERSION/); |
| 502 | for (const name of ["desktop-macos", "desktop-windows", "desktop-windows-package"]) { |
| 503 | assert.ok(job(ci, name).includes("REASONIX_FRONTEND_PRODUCER_ATTEMPT: ${{ needs.desktop-prepare.outputs.producer_attempt }}")); |
| 504 | } |
| 505 | const prepare = job(ci, "desktop-prepare"); |
| 506 | assert.match(prepare, /producer_attempt: \$\{\{ steps\.artifact-identity\.outputs\.attempt \}\}/); |
| 507 | assert.match(prepare, /id: artifact-identity\n\s+run: echo "attempt=\$GITHUB_RUN_ATTEMPT" >> "\$GITHUB_OUTPUT"/); |
| 508 | assert.match(prepare, /stable_artifact_name: desktop-frontend-stable-\$\{\{ github\.run_id \}\}-\$\{\{ steps\.artifact-identity\.outputs\.attempt \}\}/); |
| 509 | assert.equal(prepare.match(/desktop\/frontend\/sourcemaps\/\$\{\{ github\.sha \}\}/g)?.length, 2); |
| 510 | }); |
| 511 | |
| 512 | |
| 513 | |
| 514 | test("browser matrix preserves five entry points and fails closed through desktop-browser", () => { |
| 515 | const groups = job(ci, "desktop-browser-group"); |
| 516 | assert.match(groups, /max-parallel: 2/); |
| 517 | assert.match(groups, /fail-fast: false/); |
| 518 | assert.match(groups, /group: \[app-settings-motion, transcript\]/); |
| 519 | assert.match(groups, /REASONIX_TRANSCRIPT_MODE=native-scrollbar REASONIX_LAYOUT_ARTIFACTS="\$evidence\/native-scrollbar"/); |
| 520 | assert.match(groups, /REASONIX_TRANSCRIPT_MODE=headless-reader REASONIX_LAYOUT_ARTIFACTS="\$evidence\/headless-reader"/); |
| 521 | assert.doesNotMatch(groups, /group: \[app-settings, motion, transcript\]/); |
| 522 | for (const command of ["test:app-browser", "test:settings-browser", "test:motion-browser", "test:transcript-browser", "test:transcript-reader-browser"]) |
| 523 | assert.equal(ci.match(new RegExp(`pnpm --dir frontend ${command}(?:\\s|$)`, "g"))?.length, 1, command); |
| 524 | const summary = job(ci, "desktop-browser"); |
| 525 | assert.match(summary, /needs: \[changes, desktop-prepare, desktop-browser-group\]/); |
| 526 | const script = shellStep(summary, "Verify desktop browser groups"); |
| 527 | const run = env => spawnSync("bash", ["-e", "-c", script], { env: { ...process.env, ...env } }).status; |
| 528 | assert.equal(run({ CHANGES_RESULT: "success", SHOULD_RUN: "true", PREPARE_RESULT: "success", GROUP_RESULT: "success" }), 0); |
| 529 | for (const result of ["failure", "cancelled", "skipped", ""]) |
| 530 | assert.notEqual(run({ CHANGES_RESULT: "success", SHOULD_RUN: "true", PREPARE_RESULT: "success", GROUP_RESULT: result }), 0); |
| 531 | assert.equal(run({ CHANGES_RESULT: "success", SHOULD_RUN: "false", PREPARE_RESULT: "success", GROUP_RESULT: "skipped" }), 0); |
| 532 | }); |
| 533 | |
| 534 | test("Desktop race uses every verified partition and one shared cache writer", () => { |
| 535 | const body = job(ci, "desktop-go-race"); |
| 536 | assert.deepEqual(body.match(/group: \[([^\]]+)\]/)[1].split(",").map(value => value.trim()), windowsDesktopGroups); |
| 537 | assert.match(body, /fail-fast: false/); |
| 538 | assert.match(body, /run: node \.\.\/scripts\/desktop-windows-go-tests\.mjs \$\{\{ matrix.group \}\} --race/); |
| 539 | for (const group of windowsDesktopGroups) { |
| 540 | const args = windowsDesktopTestArgs(group, true); |
| 541 | assert.deepEqual(args.filter(arg => arg !== "-race"), windowsDesktopTestArgs(group)); |
| 542 | assert.equal(args.filter(arg => arg === "-race").length, 1); |
| 543 | } |
| 544 | assert.match(body, /matrix.group == 'A-B' && steps.gocache.outputs.key/); |
| 545 | assert.match(job(ci, "desktop"), /GO_RACE_RESULT: \$\{\{ needs.desktop-go-race.result \}\}/); |
| 546 | }); |
| 547 | |
| 548 | test("Desktop race runs on pushes, never on pull requests, and the aggregate expects exactly that", () => { |
| 549 | const body = job(ci, "desktop-go-race"); |
| 550 | const aggregate = job(ci, "desktop").match(/RACE_REQUIRED: \$\{\{ (.+) \}\}/)[1]; |
| 551 | assert.match(job(ci, "desktop"), /test "\$GO_RACE_RESULT" = "\$\(expected "\$RACE_REQUIRED"\)"/); |
| 552 | for (const event of ["pull_request", "push", "workflow_dispatch"]) { |
| 553 | for (const native of ["true", "false"]) { |
| 554 | for (const notes_only of ["true", "false"]) { |
| 555 | const context = { github: { event_name: event }, needs: { |
| 556 | "desktop-prepare": { result: "success" }, changes: { outputs: { native, notes_only } }, |
| 557 | } }; |
| 558 | const runs = condition(body, context); |
| 559 | assert.equal(runs, event !== "pull_request" && notes_only !== "true", `${event} native=${native} notes=${notes_only}`); |
| 560 | assert.equal(vm.runInNewContext(aggregate, context), runs); |
| 561 | } |
| 562 | } |
| 563 | } |
| 564 | }); |
| 565 | |
| 566 | test("installer evidence excludes running payloads and cache files on every publisher", () => { |
| 567 | for (const body of [job(ci, "desktop-windows-package"), job(release, "windows-runtime-acceptance")]) { |
| 568 | const upload = body.match(/name: Upload (?:signed )?Windows installer acceptance evidence\n([\s\S]*?)(?=\n - |$)/)?.[1]; |
| 569 | assert.ok(upload); |
| 570 | for (const extension of ["json", "png", "log"]) |
| 571 | assert.ok(upload.includes(`reasonix-installer-acceptance/**/*.${extension}`)); |
| 572 | for (const excluded of ["installed/**", "**/cache/**"]) |
| 573 | assert.ok(upload.includes(`!\${{ runner.temp }}/reasonix-installer-acceptance/${excluded}`)); |
| 574 | } |
| 575 | }); |
| 576 | |
| 577 | test("Windows desktop Go partitions tests without verbose JSON cache overhead", () => { |
| 578 | const windowsGo = job(ci, "desktop-windows-go-group"); |
| 579 | const context = { github: { event_name: "pull_request" }, needs: { |
| 580 | "desktop-prepare": { result: "success" }, changes: { outputs: { native: "true" } }, |
| 581 | } }; |
| 582 | assert.equal(condition(windowsGo, context), true); |
| 583 | assert.equal(condition(windowsGo, { ...context, cancelled: () => true }), false, |
| 584 | "superseded Windows workers must release the workflow concurrency slot"); |
| 585 | assert.match(windowsGo, /run: node \.\.\/scripts\/desktop-windows-go-tests\.mjs \$\{\{ matrix.group \}\}/); |
| 586 | const commands = windowsDesktopGroups.map(group => { |
| 587 | const args = windowsDesktopTestArgs(group); |
| 588 | assert.equal(args[0], "test"); |
| 589 | assert.equal(args.at(-1), "./..."); |
| 590 | assert.ok(args.includes("-timeout=25m")); |
| 591 | assert.ok(!args.some(arg => (arg.startsWith("-timeout") && arg !== "-timeout=25m") || arg === "-json" || arg === "-v")); |
| 592 | return { run: args.includes("-run") ? args[args.indexOf("-run") + 1] : undefined, |
| 593 | skip: args.includes("-skip") ? args[args.indexOf("-skip") + 1] : undefined }; |
| 594 | }); |
| 595 | assert.equal(commands.length, windowsDesktopGroups.length); |
| 596 | // Include non-test entry points and every possible first suffix character. |
| 597 | // The complement group retains names outside the selected ranges. |
| 598 | const names = ["Example", "ExampleSession", "FuzzSession", "Test"]; |
| 599 | for (let code = 0; code <= 127; code++) names.push(`Test${String.fromCharCode(code)}Session`); |
| 600 | names.push("Test会话", "TestΩSession", "TestWindowsTerminalProcessConPTYSmoke"); |
| 601 | for (const name of names) { |
| 602 | const owners = commands.filter(command => |
| 603 | (!command.run || new RegExp(command.run).test(name)) |
| 604 | && (!command.skip || !new RegExp(command.skip).test(name))); |
| 605 | if (name === "TestWindowsTerminalProcessConPTYSmoke") { |
| 606 | assert.equal(owners.length, 0, `${name} must be isolated from the correctness partition`); |
| 607 | continue; |
| 608 | } |
| 609 | assert.equal(owners.length, 1, `${name} must run in exactly one group`); |
| 610 | } |
| 611 | assert.doesNotMatch(windowsGo, /go test -json/); |
| 612 | assert.doesNotMatch(windowsGo, /go-test-timing/); |
| 613 | assert.doesNotMatch(windowsGo, /go test -run ['"]?\^\$/); |
| 614 | |
| 615 | const groups = windowsGo.match(/group: \[([^\]]+)\]/)[1].split(",").map(value => value.trim()); |
| 616 | assert.deepEqual(groups, windowsDesktopGroups); |
| 617 | assert.match(windowsGo, /fail-fast: false/); |
| 618 | |
| 619 | assert.match(windowsGo, /name: probe \(Windows ConPTY host integration\)[\s\S]*?continue-on-error: true[\s\S]*?run: go test -run '\^TestWindowsTerminalProcessConPTYSmoke\$' \./); |
| 620 | assert.match(windowsGo, /name: probe \(Windows ConPTY host integration\)\n\s+if: matrix.group == 'T-Z'/); |
| 621 | assert.match(windowsGo, /name: test \(vendored systray identity\)\n\s+if: matrix.group == 'T-Z'/); |
| 622 | assert.match(windowsGo, /steps\.conpty-smoke\.outcome == 'failure'/); |
| 623 | }); |
| 624 | |
| 625 | test("Windows desktop Go aggregate rejects incomplete matrix results", () => { |
| 626 | const summary = job(ci, "desktop-windows-go"); |
| 627 | assert.match(summary, /needs: \[changes, desktop-prepare, desktop-windows-go-group\]/); |
| 628 | const script = shellStep(summary, "Verify Windows desktop Go groups"); |
| 629 | const success = { CHANGES_RESULT: "success", SHOULD_RUN: "true", PREPARE_RESULT: "success", GROUP_RESULT: "success" }; |
| 630 | const run = patch => spawnSync("bash", ["-e", "-c", script], { env: { ...process.env, ...success, ...patch } }).status; |
| 631 | assert.equal(run({}), 0); |
| 632 | for (const key of ["CHANGES_RESULT", "PREPARE_RESULT", "GROUP_RESULT"]) |
| 633 | for (const result of ["failure", "cancelled", "skipped", ""]) |
| 634 | assert.notEqual(run({ [key]: result }), 0, `${key}=${result}`); |
| 635 | assert.equal(run({ SHOULD_RUN: "false", PREPARE_RESULT: "skipped", GROUP_RESULT: "skipped" }), 0); |
| 636 | assert.notEqual(run({ SHOULD_RUN: "false" }), 0); |
| 637 | }); |
| 638 |