Files
orca/config/scripts/orcad-windows-prebuild-workflow.test.mjs
T
Neil 6153fbcfe4 Reduce redundant headless server CI work (#24527)
* ci: avoid unrelated headless server qualification

* ci: skip headless detection for ineligible draft PRs

* ci: preserve cross-host qualification and skip supplied prerequisites

* ci: include Windows server cache validation in change detection
2026-10-02 01:42:41 -07:00

233 lines
9.1 KiB
JavaScript

import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { runInNewContext } from 'node:vm'
import { describe, expect, it } from 'vitest'
import { parse } from 'yaml'
import { runProcessSync } from './script-child-process.mjs'
const workflow = parse(readFileSync('.github/workflows/node-server-tests.yml', 'utf8'))
const steps = workflow.jobs.persistence.steps
const identity = steps.find((step) => step.id === 'orcad-prebuild-cache-identity')
const restore = steps.find((step) => step.id === 'orcad-prebuild-cache-restore')
const build = steps.find((step) => step.name?.startsWith('Build and smoke this runner'))
const save = steps.find((step) => step.uses === 'actions/cache/save@v5')
function evaluate(expression, context) {
return runInNewContext(
expression.replaceAll(
'steps.orcad-prebuild-cache-identity',
'steps["orcad-prebuild-cache-identity"]'
),
context
)
}
function context(os, arch, event, ref, template = false) {
return {
runner: { os, arch },
github: { event_name: event, ref },
inputs: { build_template: template, ref: '' },
steps: {
'orcad-prebuild-cache-identity': { outcome: 'success', outputs: { key: 'exact-key' } }
},
success: () => true,
fromJSON: JSON.parse,
contains: (values, value) => values.includes(value)
}
}
describe('Windows server prebuild cache workflow', () => {
it.each([
['pull_request', 'refs/pull/1/merge', false, true, false],
['push', 'refs/heads/main', false, true, true],
['schedule', 'refs/heads/main', false, false, true],
['workflow_dispatch', 'refs/heads/main', false, false, true],
['workflow_call', 'refs/heads/main', false, false, false],
['push', 'refs/tags/v1', false, false, false],
['workflow_dispatch', 'refs/heads/feature', false, false, false],
['pull_request', 'refs/pull/1/merge', true, false, false],
['workflow_dispatch', 'refs/heads/main', true, false, false]
])('%s %s template=%s restores=%s saves=%s', (event, ref, template, reads, writes) => {
for (const [os, arch] of [
['Windows', 'X64'],
['Windows', 'ARM64'],
['Windows', 'X86'],
['Linux', 'X64'],
['macOS', 'ARM64']
]) {
const ctx = context(os, arch, event, ref, template)
const windows = os === 'Windows' && ['X64', 'ARM64'].includes(arch)
const resolves = evaluate(identity.if, ctx)
expect(resolves).toBe(windows && (reads || writes))
ctx.steps['orcad-prebuild-cache-identity'].outcome = resolves ? 'success' : 'skipped'
expect(evaluate(restore.if, ctx)).toBe(windows && reads)
expect(evaluate(save.if, ctx)).toBe(windows && writes)
ctx.success = () => false
expect(evaluate(save.if, ctx)).toBe(false)
}
})
it('treats missing identities and explicit release refs as fresh builds', () => {
for (const event of ['pull_request', 'push']) {
const ctx = context('Windows', 'X64', event, 'refs/heads/main')
for (const outcome of ['failure', 'skipped']) {
ctx.steps['orcad-prebuild-cache-identity'].outcome = outcome
expect(evaluate(restore.if, ctx)).toBe(false)
expect(evaluate(save.if, ctx)).toBe(false)
}
ctx.steps['orcad-prebuild-cache-identity'] = { outcome: 'success', outputs: { key: '' } }
expect(evaluate(restore.if, ctx)).toBe(false)
expect(evaluate(save.if, ctx)).toBe(false)
ctx.inputs.ref = 'refs/tags/v1'
expect(evaluate(identity.if, ctx)).toBe(false)
expect(evaluate(save.if, ctx)).toBe(false)
}
})
it.each(['push', 'schedule', 'workflow_dispatch', 'pull_request'])(
'keeps reusable release/template calls fresh under the inherited %s event',
(event) => {
for (const inputs of [
{ build_template: true, ref: '' },
{ build_template: false, ref: 'refs/tags/v1' },
{ build_template: true, ref: 'refs/tags/v1' }
]) {
const ctx = context('Windows', 'X64', event, 'refs/heads/main')
ctx.inputs = inputs
expect(evaluate(identity.if, ctx)).toBe(false)
expect(evaluate(save.if, ctx)).toBe(false)
ctx.steps['orcad-prebuild-cache-identity'].outcome = 'skipped'
expect(evaluate(restore.if, ctx)).toBe(false)
}
}
)
it('restores only the exact key and saves after every qualification gate', () => {
expect(identity['continue-on-error']).toBe(true)
expect(identity.run).toBe('node config/scripts/orcad-windows-prebuild-cache.mjs --fingerprint')
expect(restore.uses).toBe('actions/cache/restore@v5')
expect(restore['continue-on-error']).toBe(true)
expect(restore.with['restore-keys']).toBeUndefined()
expect(restore.with).toEqual(save.with)
expect(restore.with.key).toBe('${{ steps.orcad-prebuild-cache-identity.outputs.key }}')
expect(restore.with.path).toBe('${{ steps.orcad-prebuild-cache-identity.outputs.path }}')
expect(steps.indexOf(restore)).toBeLessThan(steps.indexOf(build))
expect(save['continue-on-error']).toBe(true)
expect(build.if).toBeUndefined()
expect(build['continue-on-error']).toBeUndefined()
for (const gate of steps.filter((step) =>
/require-slots|--smoke|pnpm build:orcad$|test:node-server|--orcad-smoke-load-check/.test(
step.run ?? ''
)
)) {
expect(steps.indexOf(save)).toBeGreaterThan(steps.indexOf(gate))
expect(gate['continue-on-error']).toBeUndefined()
}
for (const lane of [
'linux_glibc_floor',
'linux_glibc217_compat',
'linux_musl',
'desktop_template'
]) {
expect(
workflow.jobs[lane].steps.some((step) => step.id?.startsWith('orcad-prebuild-cache'))
).toBe(false)
}
})
})
const commandMocks = `
node() {
echo "node $*" >> "$COMMAND_LOG"
case "$*" in
*--validate)
IFS= read -r verdict < out/orcad-prebuilds/manifest.json
[ "$verdict" = valid ] ;;
*--print-slot) echo win32-x64 ;;
*) return 41 ;;
esac
}
pnpm() {
echo "pnpm $*" >> "$COMMAND_LOG"
if [ "$*" = build:orcad-prebuilds ]; then
if [ "$RUNNER_OS" = Windows ]; then
[ ! -e out/orcad-prebuilds/old-payload ] || return 42
fi
[ "$COMPILE_STATUS" = 0 ] || return "$COMPILE_STATUS"
mkdir -p out/orcad-prebuilds
echo valid > out/orcad-prebuilds/manifest.json
fi
}
`
function runBuild({ os = 'Windows', hit = '', outcome = '', manifest = 'valid', compile = '0' }) {
const directory = mkdtempSync(join(tmpdir(), 'orca-windows-prebuild-workflow-'))
const log = join(directory, 'commands')
try {
mkdirSync(join(directory, 'out/orcad-prebuilds'), { recursive: true })
writeFileSync(join(directory, 'out/orcad-prebuilds/manifest.json'), `${manifest}\n`)
writeFileSync(join(directory, 'out/orcad-prebuilds/old-payload'), 'old payload')
writeFileSync(join(directory, 'out/preserve-other-output'), 'keep')
writeFileSync(log, '')
const result = runProcessSync({
program: 'bash',
args: ['-e', '-o', 'pipefail', '-c', `${commandMocks}\n${build.run}`],
cwd: directory,
env: {
...process.env,
ORCA_BACKGROUND_LAUNCH: '1',
RUNNER_OS: os,
WINDOWS_PREBUILD_CACHE_HIT: hit,
WINDOWS_PREBUILD_CACHE_RESTORE_OUTCOME: outcome,
COMMAND_LOG: log,
COMPILE_STATUS: compile
},
timeoutMs: 10_000
})
expect(readFileSync(join(directory, 'out/preserve-other-output'), 'utf8')).toBe('keep')
return { result, commands: readFileSync(log, 'utf8').trim().split('\n') }
} finally {
rmSync(directory, { recursive: true, force: true })
}
}
describe.skipIf(process.platform === 'win32')('Windows prebuild Bash fallback', () => {
it.each([
['warm valid', 'true', 'success', 'valid', false, true],
['invalid manifest', 'true', 'success', '{broken', true, true],
['restore error', 'true', 'failure', 'valid', true, false],
['cache miss', 'false', 'success', 'valid', true, false],
['missing identity', '', '', 'valid', true, false]
])('%s retains fresh gates', (_name, hit, outcome, manifest, compiles, validates) => {
const { result, commands } = runBuild({ hit, outcome, manifest })
expect(result.code).toBe(0)
expect(commands.includes('pnpm build:orcad-prebuilds')).toBe(compiles)
expect(
commands.includes('node config/scripts/orcad-windows-prebuild-cache.mjs --validate')
).toBe(validates)
expect(commands.slice(-3)).toEqual([
'node config/scripts/build-orcad-prebuilds.mjs --print-slot',
'pnpm build:orcad-prebuilds --require-slots win32-x64',
'pnpm build:orcad-prebuilds --smoke'
])
})
it.each(['Linux', 'macOS'])('%s keeps its fresh compilation and other output', (os) => {
const { result, commands } = runBuild({ os, hit: 'true', outcome: 'success' })
expect(result.code).toBe(0)
expect(commands[0]).toBe('pnpm build:orcad-prebuilds')
})
it('stops qualification when fallback compilation fails', () => {
const { result, commands } = runBuild({
hit: 'true',
outcome: 'success',
manifest: '{broken',
compile: '9'
})
expect(result.code).toBe(9)
expect(commands.at(-1)).toBe('pnpm build:orcad-prebuilds')
})
})