Reduce CI import, frame-capture, teardown, and shard costs (#28879)

* Avoid unused module imports and trim cosmetic test coverage

* Capture hidden popup frames and attach CDP before guest creation

* Overlap relay test projects within the existing worker budget

* Consolidate native preparation and add an opt-in transform cache comparison

* Release inherited debugger sockets during final E2E teardown

* Refresh unit scheduling weights from all five qualified ARM shards

* Avoid retaining unasserted RPC call history in the delivery model

* Use the supported unconditional background wait syntax

* Overlap unconditional output upload with native preparation

* Verify relay integration stays enabled for ordinary unit callers
This commit is contained in:
Neil
2026-10-14 17:10:55 -07:00
committed by GitHub
parent 8a6cc6c138
commit 39815a086f
57 changed files with 14718 additions and 12054 deletions
+5 -2
View File
@@ -4,12 +4,14 @@ on:
pull_request:
paths:
- cloud/**
- src/packages/process-host/**
- .github/workflows/cloud-*.yml
- .github/actions/cloud-sql-rollout-lease/**
push:
branches: [main]
paths:
- cloud/**
- src/packages/process-host/**
- .github/workflows/cloud-*.yml
- .github/actions/cloud-sql-rollout-lease/**
@@ -93,8 +95,8 @@ jobs:
- run: pnpm build
- run: pnpm typecheck
# Runs in parallel with build. `pnpm test` compiles the one workspace
# package it needs through the relay pretest hook, so it does not depend on
# Runs in parallel with build. `pnpm test` compiles the workspace
# packages it needs through pretest hooks, so it does not depend on
# `pnpm build` having run.
test:
runs-on: ubuntu-22.04
@@ -113,6 +115,7 @@ jobs:
--health-timeout 5s
--health-retries 10
env:
ORCA_BACKGROUND_LAUNCH: '1'
ORCA_PUSH_TEST_DATABASE_URL: postgres://relay_test:relay_test@127.0.0.1:5432/orca_relay_test
ORCA_RELAY_TEST_POSTGRES_URL: postgres://relay_test:relay_test@127.0.0.1:5432/orca_relay_test
steps:
+64 -37
View File
@@ -52,7 +52,9 @@ jobs:
name: build e2e app
if: inputs.test_files == '' || github.event_name != 'pull_request' || inputs.needs_build
runs-on: ubuntu-latest
timeout-minutes: 10
timeout-minutes: 15
env:
ORCA_BACKGROUND_LAUNCH: '1'
steps:
- name: Checkout
@@ -60,10 +62,44 @@ jobs:
with:
ref: ${{ inputs.ref || github.ref }}
- name: Install native build tools
env:
ORCA_E2E_APT_PACKAGES: build-essential python3
run: &install_e2e_tools |
if [ "$ORCA_E2E_APT_PACKAGES" = 'build-essential python3' ] &&
command -v make >/dev/null && command -v g++ >/dev/null && command -v python3 >/dev/null; then
exit 0
fi
read -r -a packages <<< "$ORCA_E2E_APT_PACKAGES"
# Why apt-mirrors.txt: runner images resolve mirror+file: sources through it, so editing the lists alone leaves Azure in use.
for source in /etc/apt/apt-mirrors.txt /etc/apt/sources.list /etc/apt/sources.list.d/*.list /etc/apt/sources.list.d/*.sources; do
if [ -f "$source" ]; then
sudo sed -i 's|https*://azure\.archive\.ubuntu\.com/ubuntu|https://archive.ubuntu.com/ubuntu|g' "$source"
fi
done
sudo tee /etc/apt/apt.conf.d/99-orca-e2e >/dev/null <<'APTCONF'
Acquire::http::Timeout "15";
Acquire::https::Timeout "15";
Acquire::Retries "1";
APTCONF
timeout 120 sudo apt-get update
timeout 300 sudo apt-get install -y "${packages[@]}" || timeout 300 sudo apt-get install -y --fix-missing "${packages[@]}"
# Why: the build's plain-Node daemon smoke load resolves node-pty.
- uses: ./.github/actions/install-node-dependencies
id: deps
with:
native-runtime: node
persist-native-cache: 'false'
cache-electron-package: 'true'
# Save Node's ABI before the Electron preparation replaces the same build paths.
- name: Save compiled Node native modules
if: steps.deps.outputs.native-cache-key != '' && steps.deps.outputs.native-cache-hit != 'true'
uses: actions/cache/save@v5
with:
path: ${{ steps.deps.outputs.native-cache-path }}
key: ${{ steps.deps.outputs.native-cache-key }}
# Why: building here avoids parallel builds inside Playwright globalSetup;
# paired-browser specs also need the standalone web bundle.
@@ -88,7 +124,19 @@ jobs:
- wait: e2e-cli
- name: Check shared native runtime action
id: native-runtime-action
run: |
if [ -f .github/actions/prepare-native-runtime/action.yml ]; then
echo 'available=true' >> "$GITHUB_OUTPUT"
else
echo 'available=false' >> "$GITHUB_OUTPUT"
fi
# Only the completed output upload overlaps the change from Node to Electron ABI.
- name: Upload E2E build output
id: e2e-build-upload
background: true
uses: actions/upload-artifact@v7
with:
name: e2e-build-out${{ inputs.artifact_suffix }}
@@ -97,46 +145,25 @@ jobs:
retention-days: 1
if-no-files-found: error
# Build Electron-native dependencies once per workflow. Consumer shards restore
# this immutable cache instead of compiling the same ABI concurrently.
prepare-native-cache:
name: prepare Electron native cache
if: inputs.test_files == '' || github.event_name != 'pull_request' || inputs.needs_build
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Prepare shared Electron native runtime
id: electron-native-runtime
if: steps.native-runtime-action.outputs.available == 'true' && steps.deps.outputs.node-version != ''
uses: ./.github/actions/prepare-native-runtime
with:
ref: ${{ inputs.ref || github.ref }}
native-runtime: electron
node-version: ${{ steps.deps.outputs.node-version }}
- name: Install native build tools
env:
ORCA_E2E_APT_PACKAGES: build-essential python3
run: &install_e2e_tools |
read -r -a packages <<< "$ORCA_E2E_APT_PACKAGES"
# Why apt-mirrors.txt: runner images resolve mirror+file: sources through it, so editing the lists alone leaves Azure in use.
for source in /etc/apt/apt-mirrors.txt /etc/apt/sources.list /etc/apt/sources.list.d/*.list /etc/apt/sources.list.d/*.sources; do
if [ -f "$source" ]; then
sudo sed -i 's|https*://azure\.archive\.ubuntu\.com/ubuntu|https://archive.ubuntu.com/ubuntu|g' "$source"
fi
done
sudo tee /etc/apt/apt.conf.d/99-orca-e2e >/dev/null <<'APTCONF'
Acquire::http::Timeout "15";
Acquire::https::Timeout "15";
Acquire::Retries "1";
APTCONF
timeout 120 sudo apt-get update
timeout 300 sudo apt-get install -y "${packages[@]}" || timeout 300 sudo apt-get install -y --fix-missing "${packages[@]}"
- uses: ./.github/actions/install-node-dependencies
- name: Prepare Electron natives for older refs
if: steps.native-runtime-action.outputs.available != 'true' || steps.deps.outputs.node-version == ''
uses: ./.github/actions/install-node-dependencies
with:
native-runtime: electron
- wait: e2e-build-upload
e2e:
name: e2e ${{ matrix.shard_name }}
needs: [build, prepare-native-cache]
needs: [build]
if: inputs.test_files == ''
runs-on: ubuntu-latest
timeout-minutes: 30
@@ -263,7 +290,7 @@ jobs:
changed-e2e:
name: changed e2e specs
needs: [build, prepare-native-cache]
needs: [build]
if: inputs.test_files != '' && (github.event_name != 'pull_request' || inputs.run_changed_e2e)
runs-on: ubuntu-latest
timeout-minutes: 45
@@ -351,7 +378,7 @@ jobs:
orcad-serve-mode-switch:
name: orca serve Electron/orcad mode switch (D7)
needs: [build, prepare-native-cache]
needs: [build]
if: >-
inputs.test_files == '' ||
contains(inputs.test_files, 'tests/e2e/orcad-serve-mode-switch.spec.ts') ||
@@ -411,7 +438,7 @@ jobs:
# orcad template for the fixture's target (Debian, linux-x64-glibc), which only this job builds.
orcad-ssh-docker:
name: ssh on managed orcad (Docker ${{ matrix.shard }})
needs: [build, prepare-native-cache]
needs: [build]
if: inputs.test_files == '' || contains(inputs.test_files, 'tests/e2e/ssh-orcad-')
runs-on: ubuntu-latest
timeout-minutes: 60
@@ -0,0 +1,49 @@
name: Unit transform cache comparison
on:
workflow_dispatch:
permissions:
contents: read
concurrency:
group: unit-module-cache-comparison-${{ github.ref }}
cancel-in-progress: true
jobs:
baseline:
uses: ./.github/workflows/unit-tests.yml
with:
node_versions: '["24.21.0"]'
runner: ubuntu-24.04-arm
shards: '[{"index":1,"count":5},{"index":2,"count":5},{"index":3,"count":5},{"index":4,"count":5},{"index":5,"count":5}]'
artifact_prefix: baseline-unit
phase_diagnostics: true
run_relay_integration: false
producer:
needs: baseline
uses: ./.github/workflows/unit-tests.yml
with:
node_versions: '["24.21.0"]'
runner: ubuntu-24.04-arm
shards: '[{"index":1,"count":5},{"index":2,"count":5},{"index":3,"count":5},{"index":4,"count":5},{"index":5,"count":5}]'
artifact_prefix: producer-unit
module_cache: cold
module_cache_namespace: ${{ github.run_id }}-${{ github.run_attempt }}
module_cache_seed: true
phase_diagnostics: true
run_relay_integration: false
warm:
needs: producer
uses: ./.github/workflows/unit-tests.yml
with:
node_versions: '["24.21.0"]'
runner: ubuntu-24.04-arm
shards: '[{"index":1,"count":5},{"index":2,"count":5},{"index":3,"count":5},{"index":4,"count":5},{"index":5,"count":5}]'
artifact_prefix: warm-unit
module_cache: warm
module_cache_namespace: ${{ github.run_id }}-${{ github.run_attempt }}
phase_diagnostics: true
run_relay_integration: false
+85 -2
View File
@@ -31,13 +31,43 @@ on:
required: true
type: string
module_cache:
description: Transform cache policy (off, cold producer, or warm exact restore).
default: 'off'
type: string
module_cache_namespace:
description: Isolate a same-source cache comparison from existing archives.
default: ''
type: string
module_cache_seed:
description: Publish comparison caches from a manually dispatched workflow.
default: false
type: boolean
phase_diagnostics:
description: Attach bounded per-file worker phase diagnostics.
default: false
type: boolean
artifact_prefix:
description: Distinguish evidence from repeated same-source comparison lanes.
default: unit
type: string
run_relay_integration:
description: Run relay contracts alongside normal unit checks.
default: true
type: boolean
permissions:
contents: read
jobs:
test:
if: inputs.run_shards
name: tests node ${{ matrix.node }} ${{ matrix.shard.index }}/${{ matrix.shard.count }}
name: ${{ inputs.artifact_prefix != 'unit' && format('{0} ', inputs.artifact_prefix) || '' }}tests node ${{ matrix.node }} ${{ matrix.shard.index }}/${{ matrix.shard.count }}
runs-on: ${{ inputs.runner }}
timeout-minutes: 60
strategy:
@@ -65,17 +95,51 @@ jobs:
with:
bun-version-file: config/.bun-version
- name: Resolve unit module cache
id: module-cache
if: inputs.module_cache == 'cold' || inputs.module_cache == 'warm'
env:
ORCA_UNIT_SHARD_INDEX: ${{ matrix.shard.index }}
ORCA_UNIT_SHARD_COUNT: ${{ matrix.shard.count }}
ORCA_UNIT_CACHE_NAMESPACE: ${{ inputs.module_cache_namespace }}
run: node config/scripts/ci-unit-module-cache.mjs identity
- name: Restore exact unit module cache
id: module-cache-restore
if: inputs.module_cache == 'warm' && steps.module-cache.outputs.key != ''
continue-on-error: true
uses: actions/cache/restore@v5
with:
path: ${{ steps.module-cache.outputs.path }}
key: ${{ steps.module-cache.outputs.key }}
- name: Validate restored unit module cache or use cold transforms
if: steps.module-cache.outputs.key != ''
env:
ORCA_UNIT_CACHE_RESTORE_HIT: ${{ steps.module-cache-restore.outputs.cache-hit }}
run: node config/scripts/ci-unit-module-cache.mjs prepare
- uses: actions/download-artifact@v8
continue-on-error: true
with:
name: unit-selection-attempt-${{ github.run_attempt }}
path: ci-shards/
- name: Record full unit discovery
if: inputs.phase_diagnostics
continue-on-error: true
run: >-
node --input-type=module -e '
import { discoverUnitFiles } from "./config/scripts/ci-unit-files.mjs";
import { writeAssignment } from "./config/scripts/ci-shard-assignment.mjs";
writeAssignment("ci-shards/unit-discovery.json", { files: discoverUnitFiles() });'
- name: Test shard
env:
NODE_COMPILE_CACHE: ${{ runner.temp }}/orca-node-compile-cache-${{ github.run_id }}-${{ github.run_attempt }}-${{ matrix.node }}-${{ matrix.shard.index }}
ORCA_BALANCE_UNIT_SHARDS: '1'
ORCA_BACKGROUND_LAUNCH: '1'
ORCA_UNIT_PHASE_REPORT: ${{ inputs.phase_diagnostics && 'ci-shards/unit-phases.json' || '' }}
run: |
ORCA_SHARD_SOURCE_SHA="$(git rev-parse HEAD)"
export ORCA_SHARD_SOURCE_SHA
@@ -83,18 +147,37 @@ jobs:
--shard=${{ matrix.shard.index }}/${{ matrix.shard.count }} \
${{ inputs.runner == 'ubuntu-24.04-arm' && '--maxWorkers=4' || '' }}
- name: Seal unit module cache
id: module-cache-seal
if: >-
success() && steps.module-cache.outputs.key != '' &&
steps.module-cache-restore.outputs.cache-hit != 'true' &&
(github.event_name == 'pull_request' ||
github.ref == 'refs/heads/main' && github.event_name != 'pull_request' ||
inputs.module_cache_seed && github.event_name == 'workflow_dispatch')
run: node config/scripts/ci-unit-module-cache.mjs seal
- name: Save unit module cache
if: steps.module-cache-seal.outputs.ready == 'true'
continue-on-error: true
uses: actions/cache/save@v5
with:
path: ${{ steps.module-cache.outputs.path }}
key: ${{ steps.module-cache.outputs.key }}
- name: Upload unit shard assignment
if: always()
# Diagnostic upload outages must not change the test verdict.
continue-on-error: true
uses: actions/upload-artifact@v7
with:
name: unit-shard-node-${{ matrix.node }}-${{ matrix.shard.index }}-attempt-${{ github.run_attempt }}
name: ${{ inputs.artifact_prefix }}-shard-node-${{ matrix.node }}-${{ matrix.shard.index }}-attempt-${{ github.run_attempt }}
path: ci-shards/
retention-days: 14
if-no-files-found: warn
relay_integration:
if: inputs.run_relay_integration
name: relay integration node ${{ matrix.node }}
strategy:
fail-fast: false
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "@orca-cloud/relay",
"private": true,
"version": "0.0.0",
"private": true,
"type": "module",
"main": "dist/index.js",
"scripts": {
@@ -11,7 +11,7 @@
"lint": "tsc -p tsconfig.json --noEmit",
"pretest": "pnpm --filter @orca-cloud/postgres-schema build && pnpm --filter @orca-cloud/relay-contract build",
"start": "node dist/index.js",
"test": "vitest run",
"test": "node --import tsx --conditions=orca-source ../../dev/scripts/run-relay-tests.mjs",
"typecheck": "tsc -p tsconfig.json --noEmit"
},
"dependencies": {
@@ -22,6 +22,7 @@ const PROBE_TABLE = 'relay_lost_reply_probe'
async function blackholeProxy(target: URL, trigger: string, delayMs?: number) {
const sockets = new Set<Socket>()
const afterTrigger: Buffer[] = []
let forwardingResumed = false
const server: Server = createServer((client) => {
const upstream = connect(Number(target.port || 5432), target.hostname)
sockets.add(client)
@@ -33,7 +34,7 @@ async function blackholeProxy(target: URL, trigger: string, delayMs?: number) {
upstream.write(chunk)
})
upstream.on('data', (chunk: Buffer) => {
if (!swallowing) client.write(chunk)
if (!swallowing || forwardingResumed) client.write(chunk)
else if (delayMs !== undefined) setTimeout(() => client.write(chunk), delayMs)
})
for (const socket of [client, upstream]) {
@@ -52,6 +53,9 @@ async function blackholeProxy(target: URL, trigger: string, delayMs?: number) {
url.port = String(address.port)
return {
url: url.toString(),
resumeForwarding: () => {
forwardingResumed = true
},
sentAfterTrigger: () => Buffer.concat(afterTrigger).toString('latin1'),
close: async () => {
for (const socket of sockets) socket.destroy()
@@ -174,6 +178,7 @@ describePostgres('a lost PostgreSQL reply against a real server', () => {
{ probe: 'stalled_reply_probe' }
])
expect(performance.now() - startedAt).toBeGreaterThan(6_000)
proxy.resumeForwarding()
// Not destroyed: the one pooled connection is still the same backend.
expect(await pid()).toBe(before)
}, 30_000)
+5 -9
View File
@@ -1,26 +1,22 @@
import { readdirSync, readFileSync } from 'node:fs'
import { join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { defaultExclude, defineConfig } from 'vitest/config'
// Every test file that opens ORCA_RELAY_TEST_POSTGRES_URL shares one CI
// database, and those tests take session-level locks with a 1s lock_timeout.
// Running them alongside anything else collides into lock timeouts, capacity
// exhaustion, and afterAll hangs. Keep them in their own serialized project so
// only they give up file parallelism; the rest of the suite opens SQLite data
// directories and stays fully parallel.
// PostgreSQL files share session locks and a small connection budget.
// The runner overlaps this serialized project with SQLite tests after other packages finish.
const sourceDirectory = fileURLToPath(new URL('src', import.meta.url))
const sharedPostgresTests = readdirSync(sourceDirectory)
.filter((entry) => entry.endsWith('.test.ts'))
.filter((entry) =>
readFileSync(`${sourceDirectory}/${entry}`, 'utf8').includes(
'ORCA_RELAY_TEST_POSTGRES_URL'
)
readFileSync(join(sourceDirectory, entry), 'utf8').includes('ORCA_RELAY_TEST_POSTGRES_URL')
)
.map((entry) => `src/${entry}`)
const timeouts = { testTimeout: 15_000, hookTimeout: 15_000 }
export default defineConfig({
cacheDir: process.env.ORCA_RELAY_TEST_CACHE_DIR,
test: {
projects: [
{
+126
View File
@@ -0,0 +1,126 @@
import { runProcess } from '@orca/process-host'
import {
forceTerminateProcessTree,
signalProcessTree
} from '@orca/process-host/process-tree-termination'
import { mkdtempSync, mkdirSync, realpathSync, rmSync } from 'node:fs'
import { createRequire } from 'node:module'
import { availableParallelism, tmpdir } from 'node:os'
import { dirname, join } from 'node:path'
import { fileURLToPath, pathToFileURL } from 'node:url'
const require = createRequire(import.meta.url)
const vitest = join(dirname(require.resolve('vitest/package.json')), 'vitest.mjs')
const relayDirectory = fileURLToPath(new URL('../../apps/relay', import.meta.url))
export async function runRelayTests({
args = [],
cwd = relayDirectory,
env = process.env,
parallelism = availableParallelism(),
cli = vitest,
run = runProcess,
events = process,
report = console.error,
scratchParent = tmpdir()
} = {}) {
const workerBudget = Math.max(parallelism - 1, 1)
// Explicit selectors and alternate database fixtures retain their existing execution path.
const serial =
args.length > 0 || workerBudget <= 1 || env.ORCA_IDLE_REHOME_POSTGRES_URL || env.DATABASE_URL
const roles = serial
? [{ name: 'all', args }]
: [
{ name: 'relay', args: ['--project=relay', `--maxWorkers=${workerBudget - 1}`] },
{ name: 'relay-postgres', args: ['--project=relay-postgres', '--maxWorkers=1'] }
]
const scratch = realpathSync(mkdtempSync(join(scratchParent, 'orca-relay-tests-')))
const abort = new AbortController()
const closed = new Set()
const terminatedTrees = new Set()
let signalExitCode
const onInterrupt = () => {
signalExitCode ??= 130
abort.abort()
}
const onTerminate = () => {
signalExitCode ??= 143
abort.abort()
}
events.on('SIGINT', onInterrupt)
events.on('SIGTERM', onTerminate)
async function runRole(role) {
const owned = join(scratch, role.name)
try {
mkdirSync(owned)
return await run({
program: process.execPath,
args: [cli, 'run', ...role.args],
cwd,
env: {
...env,
TMPDIR: owned,
TMP: owned,
TEMP: owned,
ORCA_BACKGROUND_LAUNCH: '1',
ORCA_RELAY_TEST_CACHE_DIR: join(owned, 'cache')
},
stdio: 'inherit',
timeoutMs: null,
signal: abort.signal,
terminationBarrier: {
async signal(child, signal) {
const terminated = await signalProcessTree(child, signal)
if (process.platform === 'win32' && terminated) {
terminatedTrees.add(role.name)
}
return terminated
},
async force(child) {
const terminated = await forceTerminateProcessTree(child)
if (terminated) {
terminatedTrees.add(role.name)
}
return terminated
}
},
onChildTerminated: () => closed.add(role.name)
})
} catch (error) {
abort.abort()
throw error
}
}
let exitCode = 0
try {
const results = await Promise.allSettled(roles.map(runRole))
for (const result of results) {
if (result.status === 'rejected') {
report(result.reason)
exitCode ||= 1
} else if (result.value.code !== 0 || result.value.signal || result.value.timedOut) {
exitCode ||= result.value.code || 1
}
}
} finally {
events.removeListener('SIGINT', onInterrupt)
events.removeListener('SIGTERM', onTerminate)
// A cancelled root's close event alone does not prove its descendants stopped using scratch.
if (
closed.size === roles.length &&
(!abort.signal.aborted || terminatedTrees.size === roles.length)
) {
rmSync(scratch, { recursive: true, force: true })
} else {
report(`Relay test process closure is unverifiable; retained ${scratch}`)
exitCode ||= 1
}
}
return signalExitCode ?? exitCode
}
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
process.exitCode = await runRelayTests({ args: process.argv.slice(2) })
}
+274
View File
@@ -0,0 +1,274 @@
import assert from 'node:assert/strict'
import { EventEmitter } from 'node:events'
import {
cpSync,
existsSync,
mkdirSync,
mkdtempSync,
readFileSync,
readdirSync,
realpathSync,
rmSync,
symlinkSync,
writeFileSync
} from 'node:fs'
import { createRequire } from 'node:module'
import { tmpdir } from 'node:os'
import { join, resolve } from 'node:path'
import { setImmediate } from 'node:timers/promises'
import { pathToFileURL } from 'node:url'
import test from 'node:test'
import { runProcess } from '@orca/process-host'
import { runRelayTests } from './run-relay-tests.mjs'
const success = { code: 0, signal: null, timedOut: false }
const databaseUrl = 'postgres://relay_test:relay_test@127.0.0.1:5432/orca_relay_test'
const require = createRequire(import.meta.url)
const cloud = resolve(import.meta.dirname, '../..')
function temporary(t) {
const directory = realpathSync(mkdtempSync(join(tmpdir(), 'orca-relay-runner-contract-')))
t.after(() => rmSync(directory, { recursive: true, force: true }))
return directory
}
function start(t, options = {}) {
const scratchParent = temporary(t)
const calls = []
const reports = []
const events = new EventEmitter()
const env = { ORCA_RELAY_TEST_POSTGRES_URL: databaseUrl, PRESERVED: 'yes', ...options.env }
const pending = runRelayTests({
cwd: join(scratchParent, 'relay'),
cli: join(scratchParent, 'vitest.mjs'),
parallelism: 4,
...options,
env,
events,
scratchParent,
report: (message) => reports.push(message),
run: (spec) => {
const deferred = Promise.withResolvers()
calls.push({ spec, ...deferred })
return deferred.promise
}
})
return { pending, calls, reports, events, scratchParent, env }
}
function finish(call, result = success, closed = true) {
if (closed) {
call.spec.onChildTerminated()
}
call.resolve(result)
}
function assertClean(harness) {
assert.deepEqual(readdirSync(harness.scratchParent), [])
assert.equal(harness.events.listenerCount('SIGINT'), 0)
assert.equal(harness.events.listenerCount('SIGTERM'), 0)
}
test('overlaps both projects within the worker budget and isolates their scratch and caches', async (t) => {
const h = start(t)
assert.equal(h.calls.length, 2)
const [regular, postgres] = h.calls.map((call) => call.spec)
assert.deepEqual(regular.args.slice(1), ['run', '--project=relay', '--maxWorkers=2'])
assert.deepEqual(postgres.args.slice(1), ['run', '--project=relay-postgres', '--maxWorkers=1'])
assert.notEqual(regular.env.TMPDIR, postgres.env.TMPDIR)
assert.notEqual(regular.env.ORCA_RELAY_TEST_CACHE_DIR, postgres.env.ORCA_RELAY_TEST_CACHE_DIR)
for (const spec of [regular, postgres]) {
assert.equal(spec.env.TMP, spec.env.TMPDIR)
assert.equal(spec.env.TEMP, spec.env.TMPDIR)
assert.equal(spec.env.ORCA_RELAY_TEST_CACHE_DIR, join(spec.env.TMPDIR, 'cache'))
assert.equal(spec.env.ORCA_RELAY_TEST_POSTGRES_URL, databaseUrl)
assert.equal(spec.env.PRESERVED, 'yes')
assert.equal(spec.env.ORCA_BACKGROUND_LAUNCH, '1')
assert.ok(existsSync(spec.env.TMPDIR))
assert.equal(spec.timeoutMs, null)
assert.equal(typeof spec.terminationBarrier.force, 'function')
}
assert.equal(regular.signal, postgres.signal)
let settled = false
h.pending.then(() => {
settled = true
})
finish(h.calls[0], { ...success, code: 7 })
await setImmediate()
assert.equal(settled, false)
assert.equal(postgres.signal.aborted, false)
assert.ok(existsSync(postgres.env.TMPDIR))
finish(h.calls[1])
assert.equal(await h.pending, 7)
assertClean(h)
})
for (const [name, options] of [
['selectors', { args: ['src/assignment.test.ts', '-t', 'requested behavior'] }],
[
'alternate PostgreSQL',
{ env: { ORCA_IDLE_REHOME_POSTGRES_URL: 'postgres://localhost:55440/relay' } }
],
['application database', { env: { DATABASE_URL: databaseUrl } }],
['one CPU', { parallelism: 1 }],
['two CPUs', { parallelism: 2 }]
]) {
test(`retains the original single run for ${name}`, async (t) => {
const h = start(t, options)
assert.equal(h.calls.length, 1)
const { spec } = h.calls[0]
assert.deepEqual(spec.args.slice(1), ['run', ...(options.args ?? [])])
for (const [key, value] of Object.entries(h.env)) {
assert.equal(spec.env[key], value)
}
finish(h.calls[0])
assert.equal(await h.pending, 0)
assertClean(h)
})
}
for (const [name, result] of [
['null exit code', { ...success, code: null }],
['termination signal', { ...success, signal: 'SIGTERM' }],
['timeout', { ...success, timedOut: true }]
]) {
test(`fails a ${name} even when other result fields indicate success`, async (t) => {
const h = start(t)
finish(h.calls[0], result)
finish(h.calls[1])
assert.equal(await h.pending, 1)
assertClean(h)
})
}
test('fails and retains scratch when a child never reports closure', async (t) => {
const h = start(t)
finish(h.calls[0], success, false)
finish(h.calls[1])
assert.equal(await h.pending, 1)
assert.ok(existsSync(h.calls[0].spec.env.TMPDIR))
assert.match(String(h.reports[0]), /closure is unverifiable; retained/)
assert.equal(h.events.listenerCount('SIGINT'), 0)
assert.equal(h.events.listenerCount('SIGTERM'), 0)
})
for (const [signal, exitCode, laterSignal] of [
['SIGINT', 130, 'SIGTERM'],
['SIGTERM', 143, 'SIGINT']
]) {
test(`${signal} cancels every child and retains scratch despite root close callbacks`, async (t) => {
const h = start(t)
h.events.emit(signal)
h.events.emit(laterSignal)
for (const call of h.calls) {
assert.equal(call.spec.signal.aborted, true)
finish(call)
}
assert.equal(await h.pending, exitCode)
for (const call of h.calls) {
assert.ok(existsSync(call.spec.env.TMPDIR))
}
assert.match(String(h.reports[0]), /closure is unverifiable; retained/)
assert.equal(h.events.listenerCount('SIGINT'), 0)
assert.equal(h.events.listenerCount('SIGTERM'), 0)
})
}
for (const verified of [1, 2]) {
test(`requires verification of both cancelled trees (${verified} verified)`, async (t) => {
const h = start(t)
h.events.emit('SIGINT')
for (const call of h.calls.slice(0, verified)) {
// An unspawned handle proves absence without signalling a real process.
assert.equal(await call.spec.terminationBarrier.force({ kill: () => true }), true)
}
for (const call of h.calls) {
finish(call)
}
assert.equal(await h.pending, 130)
if (verified === 2) {
assertClean(h)
} else {
assert.ok(existsSync(h.calls[0].spec.env.TMPDIR))
}
})
}
test('reports a spawn failure and waits for its cancelled peer before returning', async (t) => {
const h = start(t)
const failure = new Error('fixture spawn failed')
h.calls[0].reject(failure)
let settled = false
h.pending.then(() => {
settled = true
})
await setImmediate()
assert.equal(h.calls[1].spec.signal.aborted, true)
assert.equal(settled, false)
finish(h.calls[1])
assert.equal(await h.pending, 1)
assert.equal(h.reports[0], failure)
assert.ok(existsSync(h.calls[1].spec.env.TMPDIR))
})
test('launches with only the Cloud dependency link and no root install or compiled host package', async (t) => {
const root = temporary(t)
const standalone = join(root, 'cloud')
const manifest = JSON.parse(readFileSync(join(cloud, 'package.json'), 'utf8'))
const dependency = manifest.devDependencies['@orca/process-host']
assert.equal(dependency, 'link:../src/packages/process-host')
const packageDir = resolve(standalone, dependency.slice('link:'.length))
mkdirSync(packageDir, { recursive: true })
for (const file of ['package.json', 'src']) {
cpSync(join(cloud, '..', 'src', 'packages', 'process-host', file), join(packageDir, file), {
recursive: true
})
}
const scripts = join(standalone, 'dev', 'scripts')
mkdirSync(scripts, { recursive: true })
cpSync(join(cloud, 'dev', 'scripts', 'run-relay-tests.mjs'), join(scripts, 'run-relay-tests.mjs'))
writeFileSync(join(standalone, 'package.json'), JSON.stringify(manifest))
mkdirSync(join(standalone, 'apps', 'relay'), { recursive: true })
mkdirSync(join(standalone, 'node_modules', '@orca'), { recursive: true })
symlinkSync(
packageDir,
join(standalone, 'node_modules', '@orca', 'process-host'),
process.platform === 'win32' ? 'junction' : 'dir'
)
const fakeVitest = join(standalone, 'node_modules', 'vitest')
mkdirSync(fakeVitest)
writeFileSync(join(fakeVitest, 'package.json'), '{"name":"vitest","version":"0.0.0"}')
writeFileSync(
join(fakeVitest, 'vitest.mjs'),
'console.log(JSON.stringify({args:process.argv.slice(2), background:process.env.ORCA_BACKGROUND_LAUNCH, database:process.env.ORCA_RELAY_TEST_POSTGRES_URL}))'
)
assert.equal(existsSync(join(root, 'node_modules')), false)
assert.equal(existsSync(join(packageDir, 'dist')), false)
const result = await runProcess({
program: process.execPath,
args: [
'--import',
pathToFileURL(require.resolve('tsx')).href,
'--conditions=orca-source',
join(scripts, 'run-relay-tests.mjs'),
'src/requested.test.ts'
],
cwd: standalone,
timeoutMs: 15_000,
terminationBarrier: true,
env: {
...process.env,
NODE_PATH: '',
NODE_OPTIONS: '',
ORCA_BACKGROUND_LAUNCH: '1',
ORCA_RELAY_TEST_POSTGRES_URL: databaseUrl
}
})
assert.equal(result.code, 0, result.stderr)
assert.deepEqual(JSON.parse(result.stdout), {
args: ['run', 'src/requested.test.ts'],
background: '1',
database: databaseUrl
})
assert.equal(existsSync(join(packageDir, 'dist')), false)
})
+12 -10
View File
@@ -1,12 +1,7 @@
{
"name": "orca-cloud",
"private": true,
"version": "0.0.0",
"packageManager": "pnpm@10.34.6",
"engines": {
"node": ">=24 <27",
"pnpm": ">=10"
},
"private": true,
"scripts": {
"build": "pnpm -r build",
"dev": "pnpm --filter @orca-cloud/relay dev",
@@ -21,14 +16,21 @@
"load:relay:model": "node dev/scripts/run-relay-load-model.mjs",
"load:relay:recovery-gate": "node dev/scripts/run-relay-recovery-wave-gate.mjs",
"ops:relay": "pnpm --filter @orca-cloud/relay-ops dev",
"pretest": "node --test dev/scripts/capture-terraform-plan-baseline.test.mjs dev/scripts/operate-relay-asia-admission.test.mjs dev/scripts/prepare-relay-asia-director-cells.test.mjs dev/scripts/prepare-relay-asia-topology-input.test.mjs dev/scripts/production-cloud-sql-rollout-lock.test.mjs dev/scripts/read-relay-serving-regional-placement-version.test.mjs dev/scripts/relay-asia-rollout-evidence.test.mjs dev/scripts/relay-asia-topology-workflow.test.mjs dev/scripts/relay-cloud-sql-connection-budget.test.mjs dev/scripts/relay-load-reader-evidence.test.mjs dev/scripts/relay-control-flag-alerts.test.mjs dev/scripts/relay-db-fault-alerts.test.mjs dev/scripts/relay-lock-contention-alerts.test.mjs dev/scripts/relay-reconnect-herd-alerts.test.mjs dev/scripts/relay-reserve-dead-man-alert.test.mjs dev/scripts/relay-region-hint-metrics.test.mjs dev/scripts/relay-staging-deploy-identity.test.mjs dev/scripts/sanitize-relay-asia-admission-result.test.mjs dev/scripts/terraform-root-partition.test.mjs dev/scripts/validate-relay-asia-topology-plan.test.mjs ../.github/actions/cloud-sql-rollout-lease/action-contract.test.mjs ../.github/actions/cloud-sql-rollout-lease/storage-lease.test.mjs",
"test": "pnpm -r test && node --test dev/scripts/check-relay-same-cap-headroom.test.mjs dev/scripts/classify-relay-production-capacity-director.test.mjs dev/scripts/classify-relay-staging-bootstrap.test.mjs dev/scripts/deploy-relay-blue-green.test.mjs dev/scripts/deploy-relay-gce-candidate.test.mjs dev/scripts/deploy-relay-gce-multi-target.test.mjs dev/scripts/drive-relay-director-deploy.test.mjs dev/scripts/github-smoke-token.test.mjs dev/scripts/infra.test.mjs dev/scripts/measure-relay-drain-disconnect-gap.test.mjs dev/scripts/operate-relay-cell-flags.test.mjs dev/scripts/operate-relay-regional-rehome.test.mjs dev/scripts/power-staging-relay.test.mjs dev/scripts/prepare-relay-capacity-canary.test.mjs dev/scripts/prepare-relay-production-capacity-canary.test.mjs dev/scripts/probe-relay-legacy-admission.test.mjs dev/scripts/probe-relay-rehome-trust.test.mjs dev/scripts/production-cell-image-digest-consistency.test.mjs dev/scripts/push-gateway-workflow.test.mjs dev/scripts/push-gateway-recovery.test.mjs dev/scripts/read-relay-production-capacity-identity.test.mjs dev/scripts/relay-admin-endpoint-retry-workflow.test.mjs dev/scripts/relay-admin-transient-retry.test.mjs dev/scripts/relay-admission-selector.test.mjs dev/scripts/relay-gce-terraform-fence.test.mjs dev/scripts/relay-load-connection-failure.test.mjs dev/scripts/relay-load-control-peer.test.mjs dev/scripts/relay-load-director-capacity-gate.test.mjs dev/scripts/relay-load-duplicate-assign.test.mjs dev/scripts/relay-load-model.test.mjs dev/scripts/relay-load-phase-barrier.test.mjs dev/scripts/relay-load-placement-boundary.test.mjs dev/scripts/relay-load-profile.test.mjs dev/scripts/relay-load-rebind-boundary.test.mjs dev/scripts/relay-load-region-behavior.test.mjs dev/scripts/relay-load-request-unit-boundary.test.mjs dev/scripts/relay-load-run-lifecycle.test.mjs dev/scripts/relay-load-splice-retry.test.mjs dev/scripts/relay-monitor-evidence.test.mjs dev/scripts/relay-production-capacity-wave.test.mjs dev/scripts/relay-production-capacity-workflow.test.mjs dev/scripts/relay-production-identity-boundaries.test.mjs dev/scripts/relay-production-same-cap-wave.test.mjs dev/scripts/relay-public-workflow-contract.test.mjs dev/scripts/relay-recovery-wave-gate.test.mjs dev/scripts/relay-region-observation-evidence.test.mjs dev/scripts/relay-rehome-aggregate-evidence.test.mjs dev/scripts/relay-repository.test.mjs dev/scripts/relay-same-cap-job-mode-conditions.test.mjs dev/scripts/relay-same-cap-script-census.test.mjs dev/scripts/relay-same-cap-shadow-gate.test.mjs dev/scripts/relay-staging-c4-refresh-workflow.test.mjs dev/scripts/relay-staging-capacity-identity.test.mjs dev/scripts/staging-relay-apply-guard.test.mjs dev/scripts/validate-relay-capacity-plan.test.mjs dev/scripts/verify-relay-capacity-transition.test.mjs dev/scripts/verify-relay-legacy-bootstrap.test.mjs dev/scripts/workload-identity-attribute-conditions.test.mjs",
"typecheck": "pnpm -r typecheck"
"pretest": "node --test dev/scripts/capture-terraform-plan-baseline.test.mjs dev/scripts/operate-relay-asia-admission.test.mjs dev/scripts/prepare-relay-asia-director-cells.test.mjs dev/scripts/prepare-relay-asia-topology-input.test.mjs dev/scripts/production-cloud-sql-rollout-lock.test.mjs dev/scripts/read-relay-serving-regional-placement-version.test.mjs dev/scripts/relay-asia-rollout-evidence.test.mjs dev/scripts/relay-asia-topology-workflow.test.mjs dev/scripts/relay-cloud-sql-connection-budget.test.mjs dev/scripts/relay-load-reader-evidence.test.mjs dev/scripts/relay-control-flag-alerts.test.mjs dev/scripts/relay-db-fault-alerts.test.mjs dev/scripts/relay-lock-contention-alerts.test.mjs dev/scripts/relay-reconnect-herd-alerts.test.mjs dev/scripts/relay-reserve-dead-man-alert.test.mjs dev/scripts/relay-region-hint-metrics.test.mjs dev/scripts/relay-staging-deploy-identity.test.mjs dev/scripts/sanitize-relay-asia-admission-result.test.mjs dev/scripts/terraform-root-partition.test.mjs dev/scripts/validate-relay-asia-topology-plan.test.mjs ../.github/actions/cloud-sql-rollout-lease/action-contract.test.mjs ../.github/actions/cloud-sql-rollout-lease/storage-lease.test.mjs && pnpm test:runner",
"test": "pnpm -r --filter \"!@orca-cloud/relay\" test && pnpm --filter @orca-cloud/relay test && node --test dev/scripts/check-relay-same-cap-headroom.test.mjs dev/scripts/classify-relay-production-capacity-director.test.mjs dev/scripts/classify-relay-staging-bootstrap.test.mjs dev/scripts/deploy-relay-blue-green.test.mjs dev/scripts/deploy-relay-gce-candidate.test.mjs dev/scripts/deploy-relay-gce-multi-target.test.mjs dev/scripts/drive-relay-director-deploy.test.mjs dev/scripts/github-smoke-token.test.mjs dev/scripts/infra.test.mjs dev/scripts/measure-relay-drain-disconnect-gap.test.mjs dev/scripts/operate-relay-cell-flags.test.mjs dev/scripts/operate-relay-regional-rehome.test.mjs dev/scripts/power-staging-relay.test.mjs dev/scripts/prepare-relay-capacity-canary.test.mjs dev/scripts/prepare-relay-production-capacity-canary.test.mjs dev/scripts/probe-relay-legacy-admission.test.mjs dev/scripts/probe-relay-rehome-trust.test.mjs dev/scripts/production-cell-image-digest-consistency.test.mjs dev/scripts/push-gateway-workflow.test.mjs dev/scripts/push-gateway-recovery.test.mjs dev/scripts/read-relay-production-capacity-identity.test.mjs dev/scripts/relay-admin-endpoint-retry-workflow.test.mjs dev/scripts/relay-admin-transient-retry.test.mjs dev/scripts/relay-admission-selector.test.mjs dev/scripts/relay-gce-terraform-fence.test.mjs dev/scripts/relay-load-connection-failure.test.mjs dev/scripts/relay-load-control-peer.test.mjs dev/scripts/relay-load-director-capacity-gate.test.mjs dev/scripts/relay-load-duplicate-assign.test.mjs dev/scripts/relay-load-model.test.mjs dev/scripts/relay-load-phase-barrier.test.mjs dev/scripts/relay-load-placement-boundary.test.mjs dev/scripts/relay-load-profile.test.mjs dev/scripts/relay-load-rebind-boundary.test.mjs dev/scripts/relay-load-region-behavior.test.mjs dev/scripts/relay-load-request-unit-boundary.test.mjs dev/scripts/relay-load-run-lifecycle.test.mjs dev/scripts/relay-load-splice-retry.test.mjs dev/scripts/relay-monitor-evidence.test.mjs dev/scripts/relay-production-capacity-wave.test.mjs dev/scripts/relay-production-capacity-workflow.test.mjs dev/scripts/relay-production-identity-boundaries.test.mjs dev/scripts/relay-production-same-cap-wave.test.mjs dev/scripts/relay-public-workflow-contract.test.mjs dev/scripts/relay-recovery-wave-gate.test.mjs dev/scripts/relay-region-observation-evidence.test.mjs dev/scripts/relay-rehome-aggregate-evidence.test.mjs dev/scripts/relay-repository.test.mjs dev/scripts/relay-same-cap-job-mode-conditions.test.mjs dev/scripts/relay-same-cap-script-census.test.mjs dev/scripts/relay-same-cap-shadow-gate.test.mjs dev/scripts/relay-staging-c4-refresh-workflow.test.mjs dev/scripts/relay-staging-capacity-identity.test.mjs dev/scripts/staging-relay-apply-guard.test.mjs dev/scripts/validate-relay-capacity-plan.test.mjs dev/scripts/verify-relay-capacity-transition.test.mjs dev/scripts/verify-relay-legacy-bootstrap.test.mjs dev/scripts/workload-identity-attribute-conditions.test.mjs",
"typecheck": "pnpm -r typecheck",
"test:runner": "node --import tsx --conditions=orca-source --test dev/scripts/run-relay-tests.test.mjs"
},
"devDependencies": {
"@orca/process-host": "link:../src/packages/process-host",
"@types/node": "^24.19.0",
"tsx": "^4.23.15",
"typescript": "^5.9.3",
"vitest": "^4.1.11"
}
},
"engines": {
"node": ">=24 <27",
"pnpm": ">=10"
},
"packageManager": "pnpm@10.34.6"
}
+3
View File
@@ -8,6 +8,9 @@ importers:
.:
devDependencies:
'@orca/process-host':
specifier: link:../src/packages/process-host
version: link:../src/packages/process-host
'@types/node':
specifier: ^24.19.0
version: 24.19.0
+4 -6
View File
@@ -97,7 +97,7 @@ it('keeps Electron build and native prerequisites for every consumer requiring t
}
expect(workflow.jobs['ssh-browser-network-route'].needs).toBeUndefined()
for (const name of ['e2e', 'changed-e2e']) {
expect(workflow.jobs[name].needs, name).toEqual(['build', 'prepare-native-cache'])
expect(workflow.jobs[name].needs, name).toEqual(['build'])
}
})
@@ -132,11 +132,9 @@ it('applies allocation hints only to PRs and retains other callers and full refe
default: true
})
}
for (const name of ['build', 'prepare-native-cache']) {
expect(workflow.jobs[name].if).toBe(
"inputs.test_files == '' || github.event_name != 'pull_request' || inputs.needs_build"
)
}
expect(workflow.jobs.build.if).toBe(
"inputs.test_files == '' || github.event_name != 'pull_request' || inputs.needs_build"
)
expect(workflow.jobs.e2e.if).toBe("inputs.test_files == ''")
const changed = workflow.jobs['changed-e2e']
expect(changed.if).toContain("github.event_name != 'pull_request' || inputs.run_changed_e2e")
+136 -1
View File
@@ -1,4 +1,4 @@
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { parse } from 'yaml'
@@ -10,6 +10,7 @@ const installerPath = '.github/actions/install-node-dependencies/action.yml'
const nativePath = '.github/actions/prepare-native-runtime/action.yml'
const installer = parse(readFileSync(installerPath, 'utf8'))
const native = parse(readFileSync(nativePath, 'utf8'))
const e2e = parse(readFileSync('.github/workflows/e2e.yml', 'utf8'))
const identity = native.runs.steps.find((step) => step.id === 'native-cache-scope')
const inputs = [...identity.env.NATIVE_SOURCE_HASH.matchAll(/'([^']+)'/g)].map((match) => match[1])
const paths = [
@@ -176,4 +177,138 @@ describe('CI native cache ownership', () => {
}
}
)
it('prepares both native ABIs in the E2E build before releasing its consumers', () => {
expect(e2e.jobs['prepare-native-cache']).toBeUndefined()
const { steps } = e2e.jobs.build
const install = steps.find((step) => step.id === 'deps')
const save = steps.find((step) => step.name === 'Save compiled Node native modules')
const build = steps.find((step) => step.name === 'Build E2E outputs')
const cliWait = steps.find((step) => step.wait === 'e2e-cli')
const electron = steps.find((step) => step.id === 'electron-native-runtime')
const upload = steps.find((step) => step.name === 'Upload E2E build output')
const uploadWait = steps.find((step) => step.wait === upload.id)
expect(install.with).toMatchObject({
'native-runtime': 'node',
'persist-native-cache': 'false',
'cache-electron-package': 'true'
})
expect(save.if).toContain("steps.deps.outputs.native-cache-key != ''")
expect(save.if).toContain("steps.deps.outputs.native-cache-hit != 'true'")
expect(save.with).toEqual({
key: '${{ steps.deps.outputs.native-cache-key }}',
path: '${{ steps.deps.outputs.native-cache-path }}'
})
const ordered = [install, save, build, cliWait, upload, electron, uploadWait]
expect(ordered.map((step) => steps.indexOf(step))).toEqual(
ordered.map((step) => steps.indexOf(step)).sort((left, right) => left - right)
)
expect(upload.background).toBe(true)
expect(upload.if).toBeUndefined()
expect(electron.background).toBeUndefined()
expect(electron.uses).toBe('./.github/actions/prepare-native-runtime')
expect(electron.with).toEqual({
'native-runtime': 'electron',
'node-version': '${{ steps.deps.outputs.node-version }}'
})
expect(uploadWait).toEqual({ wait: upload.id })
for (const name of ['e2e', 'changed-e2e', 'orcad-serve-mode-switch', 'orcad-ssh-docker']) {
expect(e2e.jobs[name].needs, name).toEqual(['build'])
expect(
e2e.jobs[name].steps.find(
(step) => step.uses === './.github/actions/install-node-dependencies'
).with['native-runtime'],
name
).toBe('electron')
}
})
it.skipIf(process.platform === 'win32').each([false, true])(
'detects shared native action availability=%s for older dispatch refs',
(available) => {
const directory = mkdtempSync(join(tmpdir(), 'orca-e2e-native-action-'))
try {
const output = join(directory, 'output')
if (available) {
mkdirSync(join(directory, '.github/actions/prepare-native-runtime'), { recursive: true })
writeFileSync(join(directory, nativePath), '')
}
const step = e2e.jobs.build.steps.find(
(candidate) => candidate.id === 'native-runtime-action'
)
const execution = runProcessSync({
program: 'bash',
args: ['-e', '-o', 'pipefail', '-c', step.run],
cwd: directory,
env: { ...process.env, GITHUB_OUTPUT: output },
timeoutMs: 10_000
})
expect(execution.code, execution.stderr).toBe(0)
expect(readFileSync(output, 'utf8')).toBe(`available=${available}\n`)
const fallback = e2e.jobs.build.steps.find(
(candidate) => candidate.name === 'Prepare Electron natives for older refs'
)
expect(fallback.if).toBe(
"steps.native-runtime-action.outputs.available != 'true' || steps.deps.outputs.node-version == ''"
)
expect(fallback.uses).toBe('./.github/actions/install-node-dependencies')
expect(fallback.with['native-runtime']).toBe('electron')
} finally {
rmSync(directory, { recursive: true, force: true })
}
}
)
it.skipIf(process.platform === 'win32').each([
['true', 'build-essential python3', '', 0, false],
['false', 'build-essential python3', '', 0, true],
['true', 'build-essential python3 xvfb', '', 0, true],
['false', 'build-essential python3', 'update', 7, true],
['false', 'build-essential python3', 'install', 7, true]
])(
'tools=%s packages=%s failure=%s retains bounded setup',
(present, packages, failure, status, installs) => {
const directory = mkdtempSync(join(tmpdir(), 'orca-e2e-native-tools-'))
try {
const log = join(directory, 'commands')
writeFileSync(log, '')
const step = e2e.jobs.build.steps.find(
(candidate) => candidate.name === 'Install native build tools'
)
const execution = runProcessSync({
program: 'bash',
args: [
'-e',
'-o',
'pipefail',
'-c',
`
command() { [ "$TOOLS_PRESENT" = true ]; }
sudo() { printf 'sudo %s\\n' "$*" >> "$COMMAND_LOG"; cat >/dev/null; }
timeout() {
printf 'timeout %s\\n' "$*" >> "$COMMAND_LOG"
if [ "$4" = "$FAIL_OPERATION" ]; then return 7; fi
}
${step.run}`
],
env: {
...process.env,
TOOLS_PRESENT: present,
ORCA_E2E_APT_PACKAGES: packages,
FAIL_OPERATION: failure,
COMMAND_LOG: log
},
timeoutMs: 10_000
})
expect(execution.code, execution.stderr).toBe(status)
const commands = readFileSync(log, 'utf8')
expect(commands.includes('timeout 120 sudo apt-get update')).toBe(installs)
expect(commands.includes(`timeout 300 sudo apt-get install -y ${packages}`)).toBe(
installs && failure !== 'update'
)
} finally {
rmSync(directory, { recursive: true, force: true })
}
}
)
})
File diff suppressed because it is too large Load Diff
+27 -2
View File
@@ -1,6 +1,6 @@
# Timing-based CI shards
The eight unit shards and fourteen general E2E shards use longest-processing-time
Unit shards and general E2E shards use longest-processing-time
assignment of whole files to the currently lightest shard. Ties use file path and
then shard index, independent of filesystem enumeration and locale. Unknown,
zero, or invalid durations use the baseline's positive median (1 second when no
@@ -46,7 +46,32 @@ age as specs change. Full CI runs on the existing runner classes are required to
measure elapsed-time and occupancy improvements, including discovery overhead.
No retries, assertions, coverage exclusions, runner classes or shard counts changed.
## Current unit refresh: September 27, 2026
## Current unit refresh: October 14, 2026
The baseline imports all five successful ARM unit shards from
[run 38646706136, attempt 1](https://github.com/stablyai/orca/actions/runs/38646706136/attempts/1).
The tested merge was `8d7f46198b99432efd104ac7803174173b367c66`, using Node
24.21.0, Bun 1.4.3, `ubuntu-24.04-arm`, and four workers per shard. All 12,314
files appear exactly once: 12,163 passed and 151 skipped, with zero unhandled
errors. Artifact digests, all 20 report hashes, source/runtime provenance, full
discovery, and each saved assignment were checked before importing. The E2E
weights remain unchanged.
| Shard | Job ID | Measured worker-seconds | Job seconds |
| --- | --- | ---: | ---: |
| 1 | 116137259336 | 1,966.470 | 568 |
| 2 | 116137259460 | 2,080.624 | 604 |
| 3 | 116137259318 | 2,121.495 | 611 |
| 4 | 116137259783 | 2,094.487 | 614 |
| 5 | 116137259540 | 2,190.636 | 641 |
Retrospectively assigning those measurements projects 2,090.726–2,090.747
worker-seconds per shard, instead of 1,966.470–2,190.636. That is a scheduling
projection, not a hosted speedup: module times overlap across workers, and total
runner occupancy was 50.63 minutes. Runner capacity, selection, workers, and the
assignment algorithm are unchanged. Later source changes need fresh validation.
## Historical unit refresh: September 27, 2026
The baseline imports every successful Node 24 shard from
[run 36294142683, attempt 1](https://github.com/stablyai/orca/actions/runs/36294142683/attempts/1).
@@ -0,0 +1,217 @@
import { createHash } from 'node:crypto'
import { constants } from 'node:fs'
import { lstat, open, readdir } from 'node:fs/promises'
import { join } from 'node:path'
import { checkModuleTrailer } from './ci-unit-module-cache-trailer.mjs'
export const MODULE_CACHE_MANIFEST = '_orca_manifest.json'
export const MODULE_CACHE_LIMITS = Object.freeze({
entries: 50_000,
totalBytes: 2 ** 30,
entryBytes: 32 * 2 ** 20
})
const METADATA = '_metadata.json'
const MODULE_NAME = /^[a-f0-9]{40}$/
const isRecord = (value) => value !== null && typeof value === 'object' && !Array.isArray(value)
const fail = (message) => {
throw new Error(`Unit module cache: ${message}`)
}
async function readRegularFile(path) {
const before = await lstat(path)
if (!before.isFile()) {
fail(`not a regular file: ${path}`)
}
const file = await open(
path,
constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0) | (constants.O_NONBLOCK ?? 0)
)
try {
const info = await file.stat()
if (!info.isFile() || info.size > MODULE_CACHE_LIMITS.entryBytes) {
fail(`invalid file size or type: ${path}`)
}
const buffer = Buffer.alloc(info.size + 1)
let bytes = 0
while (bytes < buffer.length) {
const result = await file.read(buffer, bytes, buffer.length - bytes, bytes)
if (!result.bytesRead) {
break
}
bytes += result.bytesRead
}
if (bytes !== info.size) {
fail(`file changed while reading: ${path}`)
}
return buffer.subarray(0, bytes)
} finally {
await file.close()
}
}
function checkLockfileHash(lockfileHash) {
if (typeof lockfileHash !== 'string' || !/^[a-f0-9]{8}$/.test(lockfileHash)) {
fail('invalid expected lockfile hash')
}
}
export async function inventoryModuleCache({ path, lockfileHash }) {
checkLockfileHash(lockfileHash)
if (!(await lstat(path)).isDirectory()) {
fail('cache root is not a regular directory')
}
const names = (await readdir(path)).sort()
if (names.length > MODULE_CACHE_LIMITS.entries) {
fail('too many cache entries')
}
if (!names.includes(METADATA)) {
fail('cache metadata is missing')
}
let totalBytes = 0
let manifestBytes = 0
const entries = Array.from({ length: names.length })
let next = 0
const workers = await Promise.allSettled(
Array.from({ length: Math.min(4, names.length) }, async () => {
while (next < names.length) {
const index = next++
const name = names[index]
if (name !== METADATA && name !== MODULE_CACHE_MANIFEST && !MODULE_NAME.test(name)) {
fail(`invalid cache entry name: ${name}`)
}
const buffer = await readRegularFile(join(path, name))
if (name === MODULE_CACHE_MANIFEST) {
manifestBytes = buffer.length
} else {
totalBytes += buffer.length
}
if (totalBytes + manifestBytes > MODULE_CACHE_LIMITS.totalBytes) {
fail('cache byte limit exceeded')
}
if (name === MODULE_CACHE_MANIFEST) {
continue
}
if (name === METADATA) {
const metadata = JSON.parse(buffer.toString('utf8'))
if (!isRecord(metadata) || metadata.lockfileHash !== lockfileHash) {
fail('cache lockfile hash mismatch')
}
} else {
checkModuleTrailer(buffer)
}
entries[index] = {
name,
bytes: buffer.length,
sha256: createHash('sha256').update(buffer).digest('hex')
}
}
})
)
const failure = workers.find((result) => result.status === 'rejected')
if (failure) {
throw failure.reason
}
return { entries: entries.filter(Boolean), totalBytes }
}
function checkIdentity({ key, format, versions, sourceSha, lockfileHash }) {
checkLockfileHash(lockfileHash)
if (typeof key !== 'string' || !key || typeof format !== 'string' || !format) {
fail('invalid cache identity')
}
if (
!isRecord(versions) ||
!['node', 'bun', 'vitest'].every((name) => typeof versions[name] === 'string' && versions[name])
) {
fail('invalid cache versions')
}
if (sourceSha !== undefined && (typeof sourceSha !== 'string' || !MODULE_NAME.test(sourceSha))) {
fail('invalid source SHA')
}
}
export async function sealModuleCache(options) {
checkIdentity(options)
if (!options.sourceSha) {
fail('source SHA is required when sealing')
}
const inventory = await inventoryModuleCache(options)
const manifest = {
version: 1,
key: options.key,
format: options.format,
versions: options.versions,
sourceSha: options.sourceSha,
lockfileHash: options.lockfileHash,
...inventory
}
const content = `${JSON.stringify(manifest)}\n`
if (
Buffer.byteLength(content) > MODULE_CACHE_LIMITS.entryBytes ||
inventory.totalBytes + Buffer.byteLength(content) > MODULE_CACHE_LIMITS.totalBytes ||
inventory.entries.length + 1 > MODULE_CACHE_LIMITS.entries
) {
fail('sealed cache exceeds limits')
}
const file = await open(
join(options.path, MODULE_CACHE_MANIFEST),
constants.O_WRONLY |
constants.O_CREAT |
(constants.O_NOFOLLOW ?? 0) |
(constants.O_NONBLOCK ?? 0),
0o600
)
try {
if (!(await file.stat()).isFile()) {
fail('manifest is not a regular file')
}
await file.truncate(0)
await file.writeFile(content)
} finally {
await file.close()
}
return manifest
}
export async function validateModuleCache(options) {
checkIdentity(options)
const manifest = JSON.parse(
(await readRegularFile(join(options.path, MODULE_CACHE_MANIFEST))).toString('utf8')
)
if (!isRecord(manifest)) {
fail('invalid cache manifest')
}
checkIdentity(manifest)
if (
!manifest.sourceSha ||
manifest.version !== 1 ||
manifest.key !== options.key ||
manifest.format !== options.format ||
manifest.lockfileHash !== options.lockfileHash ||
(options.sourceSha !== undefined && manifest.sourceSha !== options.sourceSha) ||
!['node', 'bun', 'vitest'].every((name) => manifest.versions[name] === options.versions[name])
) {
fail('cache manifest identity mismatch')
}
const inventory = await inventoryModuleCache(options)
if (
manifest.totalBytes !== inventory.totalBytes ||
!Array.isArray(manifest.entries) ||
manifest.entries.length !== inventory.entries.length
) {
fail('cache inventory mismatch')
}
for (let index = 0; index < inventory.entries.length; index++) {
const expected = manifest.entries[index]
const actual = inventory.entries[index]
if (
!isRecord(expected) ||
expected.name !== actual.name ||
expected.bytes !== actual.bytes ||
expected.sha256 !== actual.sha256
) {
fail(`cache content mismatch: ${actual.name}`)
}
}
return manifest
}
@@ -0,0 +1,313 @@
import {
mkdtemp,
mkdir,
readFile,
rename,
rm,
symlink,
truncate,
writeFile
} from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, describe, expect, it } from 'vitest'
import {
inventoryModuleCache,
MODULE_CACHE_LIMITS,
MODULE_CACHE_MANIFEST,
sealModuleCache,
validateModuleCache
} from './ci-unit-module-cache-contents.mjs'
const roots = []
const filename = 'a'.repeat(40)
const identity = {
key: 'unit-module-v1-Linux-ARM64-runtime-config-lock',
format: 'vitest-fs-1.0.0-beta.8',
versions: { node: '24.21.0', bun: '1.4.3', vitest: '5.0.3' },
sourceSha: 'b'.repeat(40),
lockfileHash: '0028fbb2'
}
function moduleTable() {
return [
{
file: '1',
id: '1',
url: '2',
imports: '3',
mappings: false,
deps: '4',
dynamicDeps: '5',
staticMocks: '6',
moduleType: '7'
},
'/workspace/src/main.ts',
'/src/main.ts',
{ urls: '8', ids: '9' },
['10'],
[],
['11'],
'esm',
['10'],
{ '/dependency.ts': '12' },
'/dependency.ts',
{ method: '13', specifier: '10', hasFactory: true, factoryLoadsOriginal: false },
'/workspace/dependency.ts',
'mock'
]
}
const moduleCode = (table = moduleTable()) =>
`export const value = 1\n//# vitestCache=${Buffer.from(JSON.stringify(table)).toString('base64')}`
async function fixture() {
const path = await mkdtemp(join(tmpdir(), 'orca-unit-module-cache-'))
roots.push(path)
await writeFile(join(path, filename), moduleCode())
await writeFile(
join(path, '_metadata.json'),
JSON.stringify({ lockfileHash: identity.lockfileHash })
)
return { path, ...identity }
}
async function changeManifest(options, change) {
const path = join(options.path, MODULE_CACHE_MANIFEST)
const manifest = JSON.parse(await readFile(path, 'utf8'))
change(manifest)
await writeFile(path, JSON.stringify(manifest))
}
afterEach(async () => {
await Promise.all(roots.splice(0).map((path) => rm(path, { recursive: true, force: true })))
})
describe('unit module cache contents', () => {
it('seals all transformed files and Vitest metadata with exact content hashes', async () => {
const options = await fixture()
const inventory = await inventoryModuleCache(options)
expect(inventory.entries.map((entry) => entry.name)).toEqual(['_metadata.json', filename])
expect(inventory.entries.every((entry) => /^[a-f0-9]{64}$/.test(entry.sha256))).toBe(true)
expect(inventory.totalBytes).toBe(
inventory.entries.reduce((sum, entry) => sum + entry.bytes, 0)
)
const manifest = await sealModuleCache(options)
expect(manifest).toMatchObject({ version: 1, ...identity, ...inventory })
expect(await validateModuleCache(options)).toEqual(manifest)
const restoreOptions = { ...options, sourceSha: undefined }
expect(await validateModuleCache(restoreOptions)).toEqual(manifest)
})
it('accepts absent optional module type, dependency lists and mocks', async () => {
const options = await fixture()
await writeFile(
join(options.path, filename),
moduleCode([
{ id: '1', url: '2', file: null, imports: '3', mappings: false },
'\u0000virtual-module',
'/@id/virtual-module',
{ urls: '4', ids: '5' },
[],
{}
])
)
await sealModuleCache(options)
await expect(validateModuleCache(options)).resolves.toMatchObject({ key: identity.key })
})
it.each(['missing', 'modified', 'extra', 'renamed', 'metadata'])(
'rejects %s sealed contents',
async (change) => {
const options = await fixture()
await sealModuleCache(options)
const path = join(options.path, filename)
if (change === 'missing') {
await rm(path)
}
if (change === 'modified') {
await writeFile(path, moduleCode().replace('value = 1', 'value = 2'))
}
if (change === 'extra') {
await writeFile(join(options.path, 'c'.repeat(40)), moduleCode())
}
if (change === 'renamed') {
await rename(path, join(options.path, 'c'.repeat(40)))
}
if (change === 'metadata') {
await writeFile(
join(options.path, '_metadata.json'),
JSON.stringify({ lockfileHash: identity.lockfileHash, changed: true })
)
}
await expect(validateModuleCache(options)).rejects.toThrow(
/cache (inventory|content) mismatch/
)
}
)
it.each([
['key', 'different-key'],
['format', 'different-format'],
['sourceSha', 'd'.repeat(40)],
['lockfileHash', '12345678'],
['version', 2],
['versions', { ...identity.versions, bun: '1.4.4' }]
])('rejects mismatched manifest %s', async (field, value) => {
const options = await fixture()
await sealModuleCache(options)
await changeManifest(options, (manifest) => {
manifest[field] = value
})
await expect(validateModuleCache(options)).rejects.toThrow(/identity mismatch/)
})
it.each(['missing', 'malformed', 'mismatched'])(
'rejects %s Vitest metadata before sealing',
async (kind) => {
const options = await fixture()
const path = join(options.path, '_metadata.json')
if (kind === 'missing') {
await rm(path)
}
if (kind === 'malformed') {
await writeFile(path, '{')
}
if (kind === 'mismatched') {
await writeFile(path, '{"lockfileHash":"12345678"}')
}
await expect(sealModuleCache(options)).rejects.toThrow()
}
)
it.each(['.tmp-partial', 'invalid-module.js', 'B'.repeat(40)])(
'rejects invalid filename %s',
async (name) => {
const options = await fixture()
await writeFile(join(options.path, name), moduleCode())
await expect(inventoryModuleCache(options)).rejects.toThrow('invalid cache entry name')
}
)
it('rejects a directory masquerading as a transformed file', async () => {
const options = await fixture()
await mkdir(join(options.path, 'c'.repeat(40)))
await expect(inventoryModuleCache(options)).rejects.toThrow('not a regular file')
})
it.skipIf(process.platform === 'win32')(
'rejects symlinked modules, manifests and cache roots',
async () => {
const options = await fixture()
const outside = await fixture()
await symlink(join(outside.path, filename), join(options.path, 'c'.repeat(40)))
await expect(inventoryModuleCache(options)).rejects.toThrow('not a regular file')
await rm(join(options.path, 'c'.repeat(40)))
await symlink(join(outside.path, filename), join(options.path, MODULE_CACHE_MANIFEST))
await expect(sealModuleCache(options)).rejects.toThrow('not a regular file')
await symlink(options.path, join(outside.path, 'linked-root'))
await expect(
inventoryModuleCache({ ...options, path: join(outside.path, 'linked-root') })
).rejects.toThrow('cache root')
}
)
it('rejects oversized entries before allocating their contents', async () => {
const options = await fixture()
await truncate(join(options.path, filename), MODULE_CACHE_LIMITS.entryBytes + 1)
await expect(inventoryModuleCache(options)).rejects.toThrow('invalid file size')
})
it.each([
['missing trailer', 'export const value = 1'],
['invalid base64', 'x\n//# vitestCache=***'],
['noncanonical base64', 'x\n//# vitestCache=W10'],
['invalid JSON', 'x\n//# vitestCache=ew=='],
['plain JSON object', `x\n//# vitestCache=${Buffer.from('{}').toString('base64')}`]
])('rejects %s', async (_label, code) => {
const options = await fixture()
await writeFile(join(options.path, filename), code)
await expect(sealModuleCache(options)).rejects.toThrow()
})
it.each([
[
'out-of-range reference',
(table) => {
table[0].id = '999'
}
],
[
'noncanonical reference',
(table) => {
table[0].id = '01'
}
],
[
'numeric id',
(table) => {
table[0].id = 1
}
],
[
'cyclic imports',
(table) => {
table[0].imports = '0'
}
],
[
'invalid mappings',
(table) => {
table[0].mappings = null
}
],
[
'invalid module type',
(table) => {
table[7] = 'unknown'
}
],
[
'invalid import URL',
(table) => {
table[8][0] = false
}
],
[
'invalid import ids',
(table) => {
table[9]['/dependency.ts'] = 4
}
],
[
'invalid dependencies',
(table) => {
table[4][0] = false
}
],
[
'invalid static mocks',
(table) => {
table[11].hasFactory = 1
}
],
[
'inline object',
(table) => {
table[0].imports = {}
}
],
[
'unsafe property',
(table) => {
table[0] = JSON.parse('{"__proto__":"0"}')
}
]
])('rejects %s in module metadata', async (_label, mutate) => {
const options = await fixture()
const table = moduleTable()
mutate(table)
await writeFile(join(options.path, filename), moduleCode(table))
await expect(sealModuleCache(options)).rejects.toThrow()
})
})
@@ -0,0 +1,118 @@
const TRAILER = '\n//# vitestCache='
const isRecord = (value) => value !== null && typeof value === 'object' && !Array.isArray(value)
const fail = (message) => {
throw new Error(`Unit module cache: ${message}`)
}
export function checkModuleTrailer(buffer) {
const text = buffer.toString('utf8')
if (!Buffer.from(text).equals(buffer)) {
fail('module is not valid UTF-8')
}
const marker = text.lastIndexOf(TRAILER)
if (marker === -1) {
fail('module trailer is missing')
}
const encoded = text.slice(marker + TRAILER.length)
const decoded = Buffer.from(encoded, 'base64')
if (!encoded || decoded.toString('base64') !== encoded) {
fail('module trailer is not canonical base64')
}
const json = decoded.toString('utf8')
if (!Buffer.from(json).equals(decoded)) {
fail('module metadata is not valid UTF-8')
}
const table = JSON.parse(json)
if (!Array.isArray(table) || !isRecord(table[0])) {
fail('module metadata is not a flatted object table')
}
const resolve = (value) => {
if (typeof value !== 'string') {
return value
}
if (
!/^(0|[1-9][0-9]*)$/.test(value) ||
!Number.isSafeInteger(Number(value)) ||
Number(value) >= table.length
) {
fail('invalid flatted reference')
}
return table[Number(value)]
}
// Check table references directly; the cache only needs the documented metadata fields.
for (const slot of table) {
if (typeof slot === 'string') {
continue
}
if (!slot || typeof slot !== 'object') {
fail('invalid flatted table slot')
}
if (Object.hasOwn(slot, '__proto__')) {
fail('unsafe flatted property')
}
for (const value of Object.values(slot)) {
if (value !== null && typeof value === 'object') {
fail('inline object in flatted table')
}
if (typeof value === 'number' && !Number.isFinite(value)) {
fail('invalid flatted number')
}
resolve(value)
}
}
const fields = Object.fromEntries(
Object.entries(table[0]).map(([name, value]) => [name, resolve(value)])
)
const strings = (value) =>
Array.isArray(value) && value.every((item) => typeof resolve(item) === 'string')
if (typeof fields.id !== 'string' || typeof fields.url !== 'string') {
fail('invalid module id or url')
}
if (fields.file != null && typeof fields.file !== 'string') {
fail('invalid module file')
}
if (typeof fields.mappings !== 'boolean') {
fail('invalid module mappings')
}
if (
fields.moduleType !== undefined &&
fields.moduleType !== 'cjs' &&
fields.moduleType !== 'esm'
) {
fail('invalid module type')
}
if (!isRecord(fields.imports)) {
fail('invalid module imports')
}
const urls = resolve(fields.imports.urls)
const ids = resolve(fields.imports.ids)
if (
!strings(urls) ||
!isRecord(ids) ||
!Object.values(ids).every((id) => typeof resolve(id) === 'string')
) {
fail('invalid module import references')
}
for (const name of ['deps', 'dynamicDeps']) {
if (fields[name] !== undefined && !strings(fields[name])) {
fail(`invalid module ${name}`)
}
}
if (fields.staticMocks != null) {
if (!Array.isArray(fields.staticMocks)) {
fail('invalid module static mocks')
}
for (const reference of fields.staticMocks) {
const mock = resolve(reference)
if (
!isRecord(mock) ||
typeof resolve(mock.method) !== 'string' ||
typeof resolve(mock.specifier) !== 'string' ||
typeof mock.hasFactory !== 'boolean' ||
typeof mock.factoryLoadsOriginal !== 'boolean'
) {
fail('invalid module static mock')
}
}
}
}
+239
View File
@@ -0,0 +1,239 @@
import { createHash } from 'node:crypto'
import {
appendFileSync,
existsSync,
globSync,
readFileSync,
realpathSync,
rmSync,
statSync
} from 'node:fs'
import { createRequire } from 'node:module'
import { join, resolve } from 'node:path'
import { performance } from 'node:perf_hooks'
import { pathToFileURL } from 'node:url'
import { runProcessSync } from '@orca/process-host'
import { collectNodeServerInputs } from './node-server-change-scope.mjs'
import { workspacePackageManifests } from './workspace-source-exports.mjs'
import { writeAssignment } from './ci-shard-assignment.mjs'
import { sealModuleCache, validateModuleCache } from './ci-unit-module-cache-contents.mjs'
const ROOT = resolve(import.meta.dirname, '../..')
const IDENTITY_REPORT = 'ci-shards/unit-module-cache-identity.json'
const CACHE_REPORT = 'ci-shards/unit-module-cache.json'
const CACHE_FORMAT = '1.0.0-beta.8'
function digestInputs(root, files) {
const hash = createHash('sha256')
for (const file of [...new Set(files)].sort()) {
if (!existsSync(join(root, file))) {
hash.update(`${file}\0missing\0`)
continue
}
const bytes = readFileSync(join(root, file))
hash.update(`${file}\0${bytes.length}\0`).update(bytes)
}
return hash.digest('hex')
}
export async function moduleCachePolicyHash(root = ROOT) {
const runnerScripts = globSync('config/scripts/vitest*.{mjs,ts}', { cwd: root }).filter(
(file) => !file.includes('.test.')
)
const configInputs = await collectNodeServerInputs({
root,
entryPoints: [
'config/vitest.config.ts',
'config/scripts/run-vitest.mjs',
'config/scripts/ci-unit-timing-reporter.mjs',
...runnerScripts
]
})
return digestInputs(root, [
...configInputs,
...workspacePackageManifests(root),
...globSync(['config/patches/**', '**/tsconfig*.json'], {
cwd: root,
exclude: ['**/node_modules/**', '**/.git/**', '**/dist/**', '**/out/**', 'mobile/vendor/**']
}).filter((file) => statSync(join(root, file)).isFile()),
'package.json',
'pnpm-lock.yaml',
'pnpm-workspace.yaml',
'.npmrc',
'.pnpmfile.cjs',
'config/.bun-version',
'config/scripts/ci-unit-module-cache.mjs',
'config/scripts/ci-unit-module-cache-contents.mjs',
'config/scripts/ci-unit-module-cache-trailer.mjs'
])
}
export function moduleCacheIdentity({
root = ROOT,
cacheRoot = process.env.RUNNER_TEMP,
policyHash,
installedLockHash,
versions,
shard,
platform = process.platform,
arch = process.arch,
nodeEnv = process.env.NODE_ENV ?? 'test',
namespace = ''
}) {
if (
!cacheRoot ||
!/^[a-f0-9]{64}$/.test(policyHash ?? '') ||
!/^[a-f0-9]{64}$/.test(installedLockHash ?? '') ||
![versions?.node, versions?.bun, versions?.vitest].every((version) =>
/^\d+\.\d+\.\d+(?:[-+][\w.-]+)?$/.test(version ?? '')
) ||
!Number.isInteger(shard?.index) ||
!Number.isInteger(shard?.count) ||
shard.index < 1 ||
shard.index > shard.count
) {
throw new Error('Unit module cache identity is incomplete')
}
const identity = {
format: CACHE_FORMAT,
root: realpathSync(root),
platform,
arch,
versions,
policyHash,
installedLockHash,
nodeEnv,
shard,
namespace
}
const digest = createHash('sha256').update(JSON.stringify(identity)).digest('hex')
return {
...identity,
key: `unit-module-v1-${platform}-${arch}-n${versions.node}-b${versions.bun}-v${versions.vitest}-s${shard.index}of${shard.count}-${digest}`,
path: join(resolve(cacheRoot), 'orca-vitest-module-cache')
}
}
export async function resolveModuleCacheIdentity({
root = ROOT,
cacheRoot = process.env.RUNNER_TEMP,
shard,
namespace = ''
} = {}) {
const require = createRequire(join(root, 'package.json'))
const bun = runProcessSync({ program: 'bun', args: ['--version'], cwd: root, timeoutMs: 10_000 })
if (bun.code !== 0) {
throw new Error('Bun version is unavailable')
}
return moduleCacheIdentity({
root,
cacheRoot,
shard,
namespace,
policyHash: await moduleCachePolicyHash(root),
installedLockHash: createHash('sha256')
.update(readFileSync(join(root, 'node_modules', '.pnpm', 'lock.yaml')))
.digest('hex'),
versions: {
node: process.versions.node,
bun: bun.stdout.trim(),
vitest: require('vitest/package.json').version
}
})
}
export async function prepareModuleCache(identity, { restoreHit = false } = {}) {
const started = performance.now()
try {
if (!restoreHit) {
throw new Error('No exact cache was restored')
}
const manifest = await validateModuleCache({
path: identity.path,
key: identity.key,
format: identity.format,
versions: identity.versions,
lockfileHash: identity.installedLockHash.slice(0, 8)
})
return {
available: true,
entries: manifest.entries.length,
bytes: manifest.totalBytes,
producerSourceSha: manifest.sourceSha,
validationMs: performance.now() - started
}
} catch (error) {
rmSync(identity.path, { recursive: true, force: true })
return {
available: false,
reason: String(error),
validationMs: performance.now() - started
}
}
}
export async function finishModuleCache(identity, { sourceSha } = {}) {
const started = performance.now()
const manifest = await sealModuleCache({
...identity,
sourceSha,
lockfileHash: identity.installedLockHash.slice(0, 8)
})
return {
entries: manifest.entries.length,
bytes: manifest.totalBytes,
sealMs: performance.now() - started
}
}
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
const phase = process.argv[2]
const output = (values) => {
if (process.env.GITHUB_OUTPUT) {
for (const [key, value] of Object.entries(values)) {
appendFileSync(process.env.GITHUB_OUTPUT, `${key}=${value}\n`)
}
}
}
if (phase === 'identity') {
try {
const identity = await resolveModuleCacheIdentity({
shard: {
index: Number(process.env.ORCA_UNIT_SHARD_INDEX),
count: Number(process.env.ORCA_UNIT_SHARD_COUNT)
},
namespace: process.env.ORCA_UNIT_CACHE_NAMESPACE ?? ''
})
writeAssignment(IDENTITY_REPORT, identity)
output({ key: identity.key, path: identity.path })
if (process.env.GITHUB_ENV) {
appendFileSync(process.env.GITHUB_ENV, `ORCA_UNIT_MODULE_CACHE_PATH=${identity.path}\n`)
}
} catch (error) {
console.warn(`Unit module cache unavailable: ${String(error)}`)
}
} else {
const identity = JSON.parse(readFileSync(IDENTITY_REPORT, 'utf8'))
if (phase === 'prepare') {
const result = await prepareModuleCache(identity, {
restoreHit: process.env.ORCA_UNIT_CACHE_RESTORE_HIT === 'true'
})
writeAssignment(CACHE_REPORT, { key: identity.key, mode: 'restore', ...result })
console.log(JSON.stringify(result))
} else if (phase === 'seal') {
try {
const result = await finishModuleCache(identity, {
sourceSha: process.env.ORCA_SHARD_SOURCE_SHA ?? process.env.GITHUB_SHA
})
const previous = JSON.parse(readFileSync(CACHE_REPORT, 'utf8'))
writeAssignment(CACHE_REPORT, { ...previous, produced: result })
output({ ready: true })
console.log(JSON.stringify(result))
} catch (error) {
console.warn(`Unit module cache was not saved: ${String(error)}`)
}
} else {
throw new Error(`Unknown unit module cache phase: ${phase}`)
}
}
}
@@ -0,0 +1,167 @@
import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, describe, expect, it } from 'vitest'
import { parse } from 'yaml'
import {
finishModuleCache,
moduleCacheIdentity,
prepareModuleCache
} from './ci-unit-module-cache.mjs'
const temporary = []
afterEach(() => {
for (const directory of temporary.splice(0)) {
rmSync(directory, { recursive: true, force: true })
}
})
function options() {
const directory = mkdtempSync(join(tmpdir(), 'orca-unit-cache-identity-'))
temporary.push(directory)
return {
root: directory,
cacheRoot: directory,
policyHash: 'a'.repeat(64),
installedLockHash: 'b'.repeat(64),
versions: { node: '24.21.0', bun: '1.4.3', vitest: '5.0.3' },
shard: { index: 1, count: 5 },
platform: 'linux',
arch: 'arm64',
nodeEnv: 'test',
namespace: ''
}
}
describe('persistent unit module cache boundary', () => {
it('separates hosts, runtimes, config, lockfiles, roots, shards and experiments', () => {
const original = options()
const identity = moduleCacheIdentity(original)
const anotherRoot = options().root
for (const update of [
{ root: anotherRoot },
{ policyHash: 'c'.repeat(64) },
{ installedLockHash: 'd'.repeat(64) },
{ platform: 'win32' },
{ arch: 'x64' },
{ nodeEnv: 'production' },
{ namespace: 'comparison' },
{ shard: { index: 2, count: 5 } },
{ shard: { index: 1, count: 10 } },
{ versions: { ...original.versions, node: '26.6.0' } },
{ versions: { ...original.versions, bun: '1.4.4' } },
{ versions: { ...original.versions, vitest: '5.0.4' } }
]) {
const next = moduleCacheIdentity({ ...original, ...update })
expect(next.key).not.toBe(identity.key)
expect(next.path).toBe(identity.path)
}
expect(moduleCacheIdentity(original)).toEqual(identity)
})
it.each([
{ cacheRoot: '' },
{ policyHash: '' },
{ installedLockHash: '' },
{ versions: { node: '24', bun: '1.4.3', vitest: '5.0.3' } },
{ shard: { index: 0, count: 5 } },
{ shard: { index: 6, count: 5 } }
])('refuses an incomplete identity %j', (update) => {
expect(() => moduleCacheIdentity({ ...options(), ...update })).toThrow('incomplete')
})
it.each([false, true])(
'starts cold and discards unusable restored bytes (hit %s)',
async (hit) => {
const identity = moduleCacheIdentity(options())
mkdirSync(identity.path)
writeFileSync(join(identity.path, 'untrusted'), 'throw Error("must not execute")')
const prepared = await prepareModuleCache(identity, { restoreHit: hit })
expect(prepared.available).toBe(false)
expect(prepared.validationMs).toBeGreaterThanOrEqual(0)
expect(() => readFileSync(join(identity.path, 'untrusted'))).toThrow()
}
)
it('restores a matching cache from an earlier source without copying evaluated state', async () => {
const identity = moduleCacheIdentity(options())
mkdirSync(identity.path)
writeFileSync(
join(identity.path, '_metadata.json'),
JSON.stringify({ lockfileHash: identity.installedLockHash.slice(0, 8) })
)
const metadata = [
{ id: '1', url: '2', file: '1', imports: '3', mappings: false },
join(identity.root, 'source.ts'),
'/source.ts',
{ urls: '4', ids: '5' },
[],
{}
]
writeFileSync(
join(identity.path, 'a'.repeat(40)),
`export const value = 1\n//# vitestCache=${Buffer.from(JSON.stringify(metadata)).toString('base64')}`
)
const produced = await finishModuleCache(identity, { sourceSha: 'a'.repeat(40) })
expect(produced.entries).toBe(2)
const prepared = await prepareModuleCache(
{ ...identity, sourceSha: 'b'.repeat(40) },
{ restoreHit: true }
)
expect(prepared).toMatchObject({
available: true,
entries: produced.entries,
bytes: produced.bytes,
producerSourceSha: 'a'.repeat(40)
})
})
})
it('uses an optional exact restore after install and saves successful scoped producers', () => {
const workflow = parse(readFileSync('.github/workflows/unit-tests.yml', 'utf8'))
const steps = workflow.jobs.test.steps
const dependencies = steps.findIndex(
(step) => step.uses === './.github/actions/install-node-dependencies'
)
const identity = steps.findIndex((step) => step.id === 'module-cache')
const restore = steps.findIndex((step) => step.id === 'module-cache-restore')
const prepare = steps.findIndex((step) => step.run?.endsWith('ci-unit-module-cache.mjs prepare'))
const tests = steps.findIndex((step) => step.name === 'Test shard')
const seal = steps.find((step) => step.id === 'module-cache-seal')
const save = steps.find((step) => step.uses === 'actions/cache/save@v5')
expect(identity).toBeGreaterThan(dependencies)
expect(restore).toBeGreaterThan(identity)
expect(prepare).toBeGreaterThan(restore)
expect(tests).toBeGreaterThan(prepare)
expect(steps[restore].uses).toBe('actions/cache/restore@v5')
expect(steps[restore].with['restore-keys']).toBeUndefined()
expect(steps[restore]['continue-on-error']).toBe(true)
expect(seal.if).toContain('success()')
expect(seal.if).toContain("github.event_name == 'pull_request'")
expect(seal.if).toContain("github.ref == 'refs/heads/main'")
expect(seal.if).toContain("github.event_name != 'pull_request'")
expect(seal.if).toContain("inputs.module_cache_seed && github.event_name == 'workflow_dispatch'")
expect(save.if).toBe("steps.module-cache-seal.outputs.ready == 'true'")
expect(save.with.key).toBe(steps[restore].with.key)
expect(workflow.on.workflow_call.inputs.module_cache.default).toBe('off')
expect(workflow.on.workflow_call.inputs.run_relay_integration.default).toBe(true)
})
it('runs full same-source controls serially with five four-worker shards and included producer costs', () => {
const workflow = parse(readFileSync('.github/workflows/unit-module-cache-benchmark.yml', 'utf8'))
const { baseline, producer, warm } = workflow.jobs
expect(producer.needs).toBe('baseline')
expect(warm.needs).toBe('producer')
for (const job of [baseline, producer, warm]) {
expect(job.uses).toBe('./.github/workflows/unit-tests.yml')
expect(job.with.node_versions).toBe('["24.21.0"]')
expect(job.with.runner).toBe('ubuntu-24.04-arm')
expect(JSON.parse(job.with.shards)).toEqual(
Array.from({ length: 5 }, (_, index) => ({ index: index + 1, count: 5 }))
)
expect(job.with.phase_diagnostics).toBe(true)
}
expect(producer.with.module_cache).toBe('cold')
expect(producer.with.module_cache_seed).toBe(true)
expect(warm.with.module_cache).toBe('warm')
expect(warm.with.module_cache_namespace).toBe(producer.with.module_cache_namespace)
})
+125 -1
View File
@@ -1,8 +1,114 @@
import { relative } from 'node:path'
import { relative, resolve } from 'node:path'
import { appendFileSync, readFileSync } from 'node:fs'
import { writeAssignment } from './ci-shard-assignment.mjs'
import { auditUnitSelection } from './ci-unit-selection.mjs'
export const UNIT_PHASE_FILE_LIMIT = 50_000
const phaseNames = [
'environmentSetupDuration',
'prepareDuration',
'collectDuration',
'setupDuration',
'duration'
]
const nonnegativeNumber = (value) => (Number.isFinite(value) && value >= 0 ? value : null)
const emptyPhases = () => Object.fromEntries(phaseNames.map((name) => [name, 0]))
const emptyImportSample = () => ({ entries: 0, selfTimeMs: 0, totalTimeMs: 0 })
const workerCounters = (state) => ({
workersSpawned: Number.isInteger(state?.workersSpawned)
? nonnegativeNumber(state.workersSpawned)
: null,
startupTimeMs: nonnegativeNumber(state?.startupTime)
})
export function buildUnitPhaseReport(ctx, modules, errors, reason, baseline) {
const files = []
const projects = new Map()
const totals = emptyPhases()
const phaseSamples = emptyPhases()
for (const module of modules) {
const diagnostic = module.diagnostic()
const project = module.project?.name ?? 'unknown'
if (!projects.has(project)) {
projects.set(project, {
project,
files: 0,
phases: emptyPhases(),
phaseSamples: emptyPhases(),
sampledImports: emptyImportSample()
})
}
const aggregate = projects.get(project)
aggregate.files += 1
const phases = {}
for (const name of phaseNames) {
const value = nonnegativeNumber(diagnostic?.[name])
phases[name] = value
if (value !== null) {
aggregate.phases[name] += value
aggregate.phaseSamples[name] += 1
totals[name] += value
phaseSamples[name] += 1
}
}
const imports = diagnostic?.importDurations
if (imports && typeof imports === 'object') {
for (const id in imports) {
if (!Object.hasOwn(imports, id)) {
continue
}
const selfTime = nonnegativeNumber(imports[id]?.selfTime)
const totalTime = nonnegativeNumber(imports[id]?.totalTime)
if (selfTime !== null && totalTime !== null) {
aggregate.sampledImports.entries += 1
aggregate.sampledImports.selfTimeMs += selfTime
aggregate.sampledImports.totalTimeMs += totalTime
}
}
}
if (files.length < UNIT_PHASE_FILE_LIMIT) {
files.push({
file: relative(ctx.config.root, module.moduleId).replaceAll('\\', '/'),
project,
workerId: nonnegativeNumber(diagnostic?.workerId),
concurrencyId: nonnegativeNumber(diagnostic?.concurrencyId),
phases
})
}
}
const counters = workerCounters(ctx.state)
return {
metric: 'module-phase-diagnostics-v1',
nodeVersion: process.env.ORCA_TEST_NODE_VERSION ?? process.versions.node,
...(process.versions.bun ? { bunVersion: process.versions.bun } : {}),
vitestVersion: ctx.version ?? null,
shard: ctx.config.shard ?? { index: 1, count: 1 },
status: reason,
unhandledErrors: errors.length,
units: 'milliseconds',
phaseTime: 'summed-worker-time',
fileLimit: UNIT_PHASE_FILE_LIMIT,
filesSeen: modules.length,
filesOmitted: modules.length - files.length,
workerTotals: {
scope: baseline ? 'test-run' : 'vitest-session',
...Object.fromEntries(
Object.entries(counters).map(([name, value]) => [
name,
value === null || (baseline && baseline[name] === null)
? null
: nonnegativeNumber(value - (baseline?.[name] ?? 0))
])
)
},
phases: totals,
phaseSamples,
projects: [...projects.values()],
files
}
}
export function moduleDuration(diagnostic) {
return Math.max(
1,
@@ -21,6 +127,12 @@ export default class UnitTimingReporter {
this.ctx = ctx
}
onTestRunStart() {
if (process.env.ORCA_UNIT_PHASE_REPORT) {
this.workerBaseline = workerCounters(this.ctx.state)
}
}
onTestRunEnd(modules, errors, reason) {
const results = Object.fromEntries(
modules.map((module) => [
@@ -61,5 +173,17 @@ export default class UnitTimingReporter {
} catch {
// Timing evidence remains useful when selection planning was unavailable.
}
const phaseReport = process.env.ORCA_UNIT_PHASE_REPORT
const timingReport = process.env.ORCA_UNIT_TIMING_REPORT ?? 'ci-shards/unit-timings.json'
if (phaseReport && resolve(phaseReport) !== resolve(timingReport)) {
try {
writeAssignment(
phaseReport,
buildUnitPhaseReport(this.ctx, modules, errors, reason, this.workerBaseline)
)
} catch {
// Optional diagnostics must not change the test verdict.
}
}
}
}
+221 -2
View File
@@ -1,9 +1,13 @@
import { mkdtempSync, readFileSync, rmSync } from 'node:fs'
import { existsSync, mkdtempSync, readFileSync, rmSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join, resolve } from 'node:path'
import { describe, expect, it, vi } from 'vitest'
import { importUnitTimingReports } from './ci-unit-timing-import.mjs'
import UnitTimingReporter, { moduleDuration } from './ci-unit-timing-reporter.mjs'
import UnitTimingReporter, {
buildUnitPhaseReport,
moduleDuration,
UNIT_PHASE_FILE_LIMIT
} from './ci-unit-timing-reporter.mjs'
const report = (index, timings) => ({
metric: 'module-duration-v1',
@@ -38,6 +42,7 @@ describe('unit worker timing evidence', () => {
const previous = process.env.ORCA_UNIT_TIMING_REPORT
process.env.ORCA_UNIT_TIMING_REPORT = join(directory, 'unit-timings.json')
vi.stubEnv('ORCA_TEST_NODE_VERSION', nodeVersion)
vi.stubEnv('ORCA_UNIT_PHASE_REPORT', undefined)
try {
const reporter = new UnitTimingReporter()
reporter.onInit({ config: { root: process.cwd(), shard: { index: 1, count: 8 } } })
@@ -121,3 +126,217 @@ describe('unit worker timing evidence', () => {
expect(() => importUnitTimingReports([first, report(2, { b: -1 })])).toThrow('timing')
})
})
const phaseDiagnostic = (overrides = {}) => ({
environmentSetupDuration: 1.5,
prepareDuration: 2.5,
collectDuration: 30.25,
setupDuration: 4.5,
duration: 5.25,
workerId: 7,
concurrencyId: 2,
importDurations: {},
...overrides
})
const phaseModule = (file, diagnostic = phaseDiagnostic(), project = 'bun') => ({
moduleId: resolve(file),
project: { name: project },
state: () => 'passed',
diagnostic: () => diagnostic
})
const phaseContext = (state) => ({
config: { root: process.cwd(), shard: { index: 1, count: 5 } },
version: '5.0.3',
state
})
function withReporterOutputs(run) {
const directory = mkdtempSync(join(tmpdir(), 'orca-unit-phases-'))
const timingPath = join(directory, 'unit-timings.json')
const phasePath = join(directory, 'unit-phase-diagnostics.json')
vi.stubEnv('ORCA_UNIT_TIMING_REPORT', timingPath)
vi.stubEnv('ORCA_UNIT_PHASE_REPORT', undefined)
vi.stubEnv('ORCA_UNIT_SELECTION_PLAN', join(directory, 'missing-selection.json'))
vi.stubEnv('ORCA_SHARD_SOURCE_SHA', 'phase-source')
vi.stubEnv('GITHUB_RUN_ID', 'phase-run')
vi.stubEnv('GITHUB_RUN_ATTEMPT', '3')
try {
run({ timingPath, phasePath })
} finally {
vi.unstubAllEnvs()
rmSync(directory, { recursive: true, force: true })
}
}
describe('optional unit phase diagnostics', () => {
it('leaves the timing report identical and writes nothing extra when disabled', () => {
withReporterOutputs(({ timingPath, phasePath }) => {
const modules = [
phaseModule(
'src/example.test.ts',
phaseDiagnostic({ importDurations: { dependency: { selfTime: 100, totalTime: 200 } } })
)
]
const reporter = new UnitTimingReporter()
reporter.onInit(phaseContext({ workersSpawned: 3, startupTime: 500 }))
reporter.onTestRunEnd(modules, [], 'passed')
const timing = readFileSync(timingPath, 'utf8')
expect(existsSync(phasePath)).toBe(false)
vi.stubEnv('ORCA_UNIT_PHASE_REPORT', phasePath)
reporter.onTestRunEnd(modules, [], 'passed')
expect(readFileSync(timingPath, 'utf8')).toBe(timing)
expect(JSON.parse(timing).timings).toEqual({ 'src/example.test.ts': 44 })
expect(JSON.parse(readFileSync(phasePath, 'utf8'))).toMatchObject({
sourceSha: 'phase-source',
runId: 'phase-run',
runAttempt: '3',
metric: 'module-phase-diagnostics-v1',
vitestVersion: '5.0.3',
shard: { index: 1, count: 5 },
status: 'passed',
unhandledErrors: 0,
workerTotals: { scope: 'vitest-session', workersSpawned: 3, startupTimeMs: 500 }
})
})
})
it('records fractions and project totals without retaining sampled import records', () => {
const report = buildUnitPhaseReport(
phaseContext(),
[
phaseModule(
'src/first.test.ts',
phaseDiagnostic({
importDurations: {
first: { selfTime: 2, totalTime: 9, importer: 'src/private-account.ts' },
second: { selfTime: 3.5, totalTime: 5.5 },
invalid: { selfTime: Number.NaN, totalTime: 4 }
}
})
),
phaseModule('src/second.test.ts'),
phaseModule('src/node.test.ts', phaseDiagnostic({ workerId: 9 }), 'node-runtime')
],
[{ message: 'test error' }],
'failed'
)
expect(report.files[0]).toEqual({
file: 'src/first.test.ts',
project: 'bun',
workerId: 7,
concurrencyId: 2,
phases: {
environmentSetupDuration: 1.5,
prepareDuration: 2.5,
collectDuration: 30.25,
setupDuration: 4.5,
duration: 5.25
}
})
expect(report.projects).toMatchObject([
{
project: 'bun',
files: 2,
phases: { collectDuration: 60.5 },
phaseSamples: { collectDuration: 2 },
sampledImports: { entries: 2, selfTimeMs: 5.5, totalTimeMs: 14.5 }
},
{ project: 'node-runtime', files: 1, sampledImports: { entries: 0 } }
])
expect(report.phases.collectDuration).toBe(90.75)
expect(report).toMatchObject({
phaseTime: 'summed-worker-time',
status: 'failed',
unhandledErrors: 1
})
expect(JSON.stringify(report)).not.toContain('private-account')
expect(JSON.stringify(report)).not.toContain('importDurations')
})
it('reports successful worker startup counters for the current run separately', () => {
withReporterOutputs(({ phasePath }) => {
vi.stubEnv('ORCA_UNIT_PHASE_REPORT', phasePath)
const state = { workersSpawned: 2, startupTime: 10 }
const reporter = new UnitTimingReporter()
reporter.onInit(phaseContext(state))
reporter.onTestRunStart()
state.workersSpawned = 5
state.startupTime = 60.25
reporter.onTestRunEnd([phaseModule('src/example.test.ts')], [], 'passed')
expect(JSON.parse(readFileSync(phasePath, 'utf8')).workerTotals).toEqual({
scope: 'test-run',
workersSpawned: 3,
startupTimeMs: 50.25
})
})
})
it('caps file detail while keeping aggregates from omitted files', () => {
const module = phaseModule('src/repeated.test.ts')
const modules = Array.from({ length: UNIT_PHASE_FILE_LIMIT }, () => module)
modules.push(
phaseModule(
'src/omitted.test.ts',
phaseDiagnostic({
collectDuration: 11,
importDurations: { dependency: { selfTime: 2, totalTime: 3 } }
})
)
)
const report = buildUnitPhaseReport(phaseContext(), modules, [], 'passed')
expect(report.files).toHaveLength(UNIT_PHASE_FILE_LIMIT)
expect(report).toMatchObject({
fileLimit: UNIT_PHASE_FILE_LIMIT,
filesSeen: UNIT_PHASE_FILE_LIMIT + 1,
filesOmitted: 1,
phases: { collectDuration: UNIT_PHASE_FILE_LIMIT * 30.25 + 11 },
projects: [
{
files: UNIT_PHASE_FILE_LIMIT + 1,
sampledImports: { entries: 1, selfTimeMs: 2, totalTimeMs: 3 }
}
]
})
})
it('marks unavailable counters, phases and worker IDs without inventing measurements', () => {
const report = buildUnitPhaseReport(
phaseContext({ workersSpawned: -1, startupTime: Number.NaN }),
[phaseModule('src/unavailable.test.ts', {})],
[],
'interrupted'
)
expect(report.workerTotals).toEqual({
scope: 'vitest-session',
workersSpawned: null,
startupTimeMs: null
})
expect(report.files[0]).toMatchObject({
workerId: null,
concurrencyId: null,
phases: { collectDuration: null }
})
expect(report.phaseSamples.collectDuration).toBe(0)
expect(report.status).toBe('interrupted')
})
it.each(['unwritable', 'same as timing'])(
'preserves timing evidence when the optional output is %s',
(target) => {
withReporterOutputs(({ timingPath }) => {
const reporter = new UnitTimingReporter()
reporter.onInit(phaseContext())
const modules = [phaseModule('src/example.test.ts')]
reporter.onTestRunEnd(modules, [], 'passed')
const timing = readFileSync(timingPath, 'utf8')
vi.stubEnv(
'ORCA_UNIT_PHASE_REPORT',
target === 'unwritable' ? `${timingPath}/unwritable.json` : timingPath
)
expect(() => reporter.onTestRunEnd(modules, [], 'passed')).not.toThrow()
expect(readFileSync(timingPath, 'utf8')).toBe(timing)
})
}
)
})
+1 -1
View File
@@ -90,7 +90,7 @@ it('builds the e2e app when only a build-dependent orcad spec is requested', ()
e2e_needs_build: true
})
}
expect(jobs['orcad-ssh-docker'].needs).toEqual(['build', 'prepare-native-cache'])
expect(jobs['orcad-ssh-docker'].needs).toEqual(['build'])
})
it('runs every managed-SSH spec in one sharded lane, only when one is routed', () => {
@@ -251,7 +251,15 @@ describe('Mobile CI tool preflight routing', () => {
it('keeps root shard execution enabled for callers that do not opt out', () => {
expect(unit.on.workflow_call.inputs.run_shards.default).toBe(true)
expect(evaluate(unit.jobs.test.if, { inputs: { run_shards: true } })).toBe(true)
expect(unit.jobs.relay_integration.if).toBeUndefined()
const relayDefault = unit.on.workflow_call.inputs.run_relay_integration.default
expect(relayDefault).toBe(true)
expect(
evaluate(unit.jobs.relay_integration.if, { inputs: { run_relay_integration: relayDefault } })
).toBe(true)
expect(
evaluate(unit.jobs.relay_integration.if, { inputs: { run_relay_integration: false } })
).toBe(false)
expect(workflow.jobs.test.with.run_relay_integration).toBeUndefined()
expect(unit.jobs.relay_integration.needs).toBeUndefined()
})
@@ -14,7 +14,6 @@ const EXPECTED_MATRIX = {
'.github/workflows/e2e.yml#orcad-serve-mode-switch': { contents: 'read' },
'.github/workflows/e2e.yml#orcad-serve-mode-switch-windows': { contents: 'read' },
'.github/workflows/e2e.yml#orcad-ssh-docker': { contents: 'read' },
'.github/workflows/e2e.yml#prepare-native-cache': { contents: 'read' },
'.github/workflows/e2e.yml#ssh-browser-network-route': { contents: 'read' },
'.github/workflows/homebrew-bump.yml#bump-cask': { contents: 'read' },
'.github/workflows/node-server-tests.yml#changes': { contents: 'read' },
+3
View File
@@ -30,6 +30,9 @@ const testOptions = {
environment: 'node',
clearMocks: false,
fsModuleCache: true,
...(process.env.ORCA_UNIT_MODULE_CACHE_PATH
? { fsModuleCachePath: process.env.ORCA_UNIT_MODULE_CACHE_PATH }
: {}),
env: { ORCA_VITEST_RUNTIME: 'node' },
server: { deps: { inline: ['zod'] } },
// Node's storage globals and V8 retention checks require the existing child flags.
@@ -6,10 +6,12 @@ import { createRequire } from 'node:module'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { chromium, type Browser, type Page } from 'playwright'
import { afterAll, afterEach, beforeAll, expect, vi } from 'vitest'
import { afterAll, afterEach, beforeAll, beforeEach, expect, vi } from 'vitest'
import { build as buildVite } from 'vite'
import { waitForBrowserCdpEndpoint } from './browser-session-ua-cdp-collector'
import { popupFixtureHtml } from './browser-popup-compatibility-page-fixture'
import { resolveElectronProbeLaunch } from './electron-probe-display-launch'
import { startHiddenRendererFrameCapture } from './hidden-renderer-frame-capture-fixture'
// Hidden renderer startup and CDP events can exceed the default one-second poll on CI.
vi.setConfig({ expect: { poll: { timeout: 10_000 } } })
@@ -35,6 +37,7 @@ export let browser: Browser | null = null
export let origin = ''
let stderr = ''
const pages = new Map<string, Page>()
const stopFrameCaptures = new Map<Page, () => Promise<void>>()
const statusPath = join(root, 'status.json')
export const artifactDir = process.env.ORCA_POPUP_FIXTURE_ARTIFACT_DIR
export const snapshots: ReturnType<typeof status>[] = []
@@ -77,25 +80,12 @@ export function status(): {
return JSON.parse(readFileSync(statusPath, 'utf8'))
}
function fixtureHtml(): string {
return `<!doctype html><meta name="referrer" content="strict-origin-when-cross-origin"><body>
<button id="oauth" onclick="window.popup=window.open('/authorize?client_id=synthetic&amp;redirect_uri=${origin}/callback&amp;response_type=code');document.body.dataset.handle=String(!!window.popup)">OAuth</button>
<button id="blank" onclick="window.popup=window.open('about:blank#state');document.body.dataset.handle=String(!!window.popup);if(window.popup)window.popup.eval(&quot;window.opener.postMessage('blank:'+document.cookie,'*')&quot;)">Blank</button>
<button id="invalid" onclick="window.popup=window.open('about:blank?state=synthetic');document.body.dataset.handle=String(!!window.popup)">Invalid</button>
<button id="burst" onclick="window.handles=Array.from({length:5},()=>window.open('about:blank'));document.body.dataset.handles=JSON.stringify(window.handles.map(Boolean))">Burst</button>
<button id="stale" onclick="document.body.dataset.handle='waiting';setTimeout(()=>{window.popup=window.open('about:blank');document.body.dataset.handle=String(!!window.popup)},1100)">Stale</button>
<form method="post" target="_blank" action="/post"><input name="code" value="synthetic+value"><button id="post">POST</button></form>
<form method="post" target="_blank" action="/multipart" enctype="multipart/form-data"><input name="code" value="synthetic-multipart"><button id="multipart">Multipart</button></form>
<form method="post" target="_blank" action="/empty"><button id="empty">Empty POST</button></form>
<a id="link" href="/docs" target="_blank">Docs</a>
<a id="oauth-link" href="/authorize-link?client_id=synthetic&amp;redirect_uri=${origin}/callback&amp;response_type=code" target="_blank">OAuth link</a>
<a id="background" href="/background">Background</a>
<a id="shift" href="/shift">Popup link</a>
<script>window.addEventListener('message',event=>document.body.dataset.message=event.data)</script>
</body>`
}
const cdpConnected = Promise.withResolvers<void>()
const server = createServer((request, response) => {
if (request.url === '/fixture-cdp-connected') {
void cdpConnected.promise.then(() => response.writeHead(204).end())
return
}
const chunks: Buffer[] = []
request.on('data', (chunk: Buffer) => chunks.push(chunk))
request.on('end', () => {
@@ -127,7 +117,7 @@ const server = createServer((request, response) => {
response.setHeader('Content-Type', 'text/html')
response.end(
['/local', '/route', '/local-control', '/route-control'].includes(path)
? fixtureHtml()
? popupFixtureHtml(origin)
: `<title>Hostile synthetic title</title><body>${path}<script>if(window.opener)window.opener.postMessage('callback:'+document.cookie,'*')</script></body>`
)
})
@@ -213,6 +203,7 @@ beforeAll(async () => {
})
await waitForBrowserCdpEndpoint(cdpPort)
browser = await chromium.connectOverCDP(`http://127.0.0.1:${cdpPort}`)
cdpConnected.resolve()
await waitForFixture(() => {
if (!existsSync(statusPath)) {
return false
@@ -246,6 +237,17 @@ beforeAll(async () => {
}
}, 60_000)
beforeEach(async () => {
for (const page of pages.values()) {
if (!stopFrameCaptures.has(page)) {
stopFrameCaptures.set(
page,
await startHiddenRendererFrameCapture(page, (hasFrame) => expect.poll(hasFrame).toBe(true))
)
}
}
})
afterEach(async () => {
const route = pages.get('route')
if (route && !route.isClosed()) {
@@ -258,6 +260,8 @@ afterEach(async () => {
})
afterAll(async () => {
cdpConnected.resolve()
await Promise.allSettled([...stopFrameCaptures.values()].map((stop) => stop()))
if (artifactDir && existsSync(statusPath)) {
mkdirSync(artifactDir, { recursive: true })
writeFileSync(
@@ -286,6 +286,8 @@ async function run(): Promise<void> {
writeStatus()
}
})
// Playwright must attach before Electron changes guest targets from pages to webviews.
await fetch(`${fixtureOrigin}/fixture-cdp-connected`)
await createHost('local', localPartition)
phase = 'local'
writeStatus()
@@ -0,0 +1,17 @@
export function popupFixtureHtml(origin: string): string {
return `<!doctype html><meta name="referrer" content="strict-origin-when-cross-origin"><body>
<button id="oauth" onclick="window.popup=window.open('/authorize?client_id=synthetic&amp;redirect_uri=${origin}/callback&amp;response_type=code');document.body.dataset.handle=String(!!window.popup)">OAuth</button>
<button id="blank" onclick="window.popup=window.open('about:blank#state');document.body.dataset.handle=String(!!window.popup);if(window.popup)window.popup.eval(&quot;window.opener.postMessage('blank:'+document.cookie,'*')&quot;)">Blank</button>
<button id="invalid" onclick="window.popup=window.open('about:blank?state=synthetic');document.body.dataset.handle=String(!!window.popup)">Invalid</button>
<button id="burst" onclick="window.handles=Array.from({length:5},()=>window.open('about:blank'));document.body.dataset.handles=JSON.stringify(window.handles.map(Boolean))">Burst</button>
<button id="stale" onclick="document.body.dataset.handle='waiting';setTimeout(()=>{window.popup=window.open('about:blank');document.body.dataset.handle=String(!!window.popup)},1100)">Stale</button>
<form method="post" target="_blank" action="/post"><input name="code" value="synthetic+value"><button id="post">POST</button></form>
<form method="post" target="_blank" action="/multipart" enctype="multipart/form-data"><input name="code" value="synthetic-multipart"><button id="multipart">Multipart</button></form>
<form method="post" target="_blank" action="/empty"><button id="empty">Empty POST</button></form>
<a id="link" href="/docs" target="_blank">Docs</a>
<a id="oauth-link" href="/authorize-link?client_id=synthetic&amp;redirect_uri=${origin}/callback&amp;response_type=code" target="_blank">OAuth link</a>
<a id="background" href="/background">Background</a>
<a id="shift" href="/shift">Popup link</a>
<script>window.addEventListener('message',event=>document.body.dataset.message=event.data)</script>
</body>`
}
@@ -0,0 +1,47 @@
import type { Page } from 'playwright'
export async function startHiddenRendererFrameCapture(
page: Page,
waitForFirstFrame: (hasFrame: () => boolean) => Promise<void>
): Promise<() => Promise<void>> {
const cdp = await page.context().newCDPSession(page)
let stopped = false
const handlePageClose = (): void => {
void stop().catch(() => {})
}
const stop = async (): Promise<void> => {
if (stopped) {
return
}
stopped = true
try {
await cdp.send('Page.stopScreencast')
} finally {
page.off('close', handlePageClose)
cdp.off('Page.screencastFrame', acknowledgeFrame)
await cdp.detach()
}
}
let hasFrame = false
const acknowledgeFrame = (frame: { sessionId: number }): void => {
hasFrame = true
void cdp.send('Page.screencastFrameAck', { sessionId: frame.sessionId }).catch(() => {})
}
cdp.on('Page.screencastFrame', acknowledgeFrame)
page.once('close', handlePageClose)
try {
// Keep hidden renderers painting without revealing their native windows.
await cdp.send('Page.enable')
await cdp.send('Page.startScreencast', {
format: 'jpeg',
quality: 20,
maxWidth: 640,
maxHeight: 480
})
await waitForFirstFrame(() => hasFrame)
return stop
} catch (error) {
await stop().catch(() => {})
throw error
}
}
@@ -1,3 +1,4 @@
import './rpc/unused-default-rpc-methods.test-fixture'
/**
* A phone subscribing to an idle reattached PTY must get its first snapshot on the phone grid.
*
@@ -1,3 +1,4 @@
import './rpc/unused-default-rpc-methods.test-fixture'
import { afterEach, beforeEach, expect, it, vi } from 'vitest'
import { OrcaRuntimeService } from './orca-runtime'
import { RpcDispatcher } from './rpc/dispatcher'
@@ -1,3 +1,4 @@
import './unused-default-rpc-methods.test-fixture'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { z } from 'zod'
import { ORCHESTRATION_CONTRACT_VERSION } from '../../../shared/protocol-version'
@@ -1,3 +1,4 @@
import '../unused-default-rpc-methods.test-fixture'
/**
* A launch's record across a host restart, against the real durable ledger.
*
@@ -1,3 +1,4 @@
import '../unused-default-rpc-methods.test-fixture'
import { afterEach, describe, expect, it, vi } from 'vitest'
import type { OrcaRuntimeService } from '../../orca-runtime'
import {
@@ -1,3 +1,4 @@
import '../unused-default-rpc-methods.test-fixture'
/**
* The behaviours that only exist while a read is genuinely in flight or genuinely failing: the
* per-connection cap, an abort that arrives mid-read, and a verify whose open throws. All three go
@@ -1,3 +1,4 @@
import '../unused-default-rpc-methods.test-fixture'
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
@@ -1,3 +1,4 @@
import '../unused-default-rpc-methods.test-fixture'
import { mkdirSync, mkdtempSync, rmSync, truncateSync, unlinkSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
@@ -1,3 +1,4 @@
import '../unused-default-rpc-methods.test-fixture'
import { mkdtemp, readFile, rm } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
@@ -1,3 +1,4 @@
import './unused-default-rpc-methods.test-fixture'
import { createHash } from 'node:crypto'
import { afterEach, describe, expect, it, vi } from 'vitest'
import type Database from '../../sqlite/sync-database'
@@ -32,26 +32,49 @@ vi.mock('./main-process-runtime-service', () => ({
}),
configureRuntimeServices: vi.fn()
}))
vi.mock('../star-nag/service')
vi.mock('../browser/agent-browser-bridge')
vi.mock('../emulator/emulator-bridge')
vi.mock('../runtime/rpc/dispatcher')
vi.mock('../star-nag/service', () => ({
StarNagService: class {
start = vi.fn()
registerIpcHandlers = vi.fn()
}
}))
vi.mock('../browser/agent-browser-bridge', () => ({
AgentBrowserBridge: class {
sweepOrphanedSessions = vi.fn()
}
}))
vi.mock('../emulator/emulator-bridge', () => ({ EmulatorBridge: class {} }))
vi.mock('../runtime/rpc/dispatcher', () => ({ RpcDispatcher: class {} }))
vi.mock('../browser/browser-manager', () => ({ browserManager: {} }))
vi.mock('../browser/browser-client-page-automation-runtime')
vi.mock('../crash-reporting/process-gone-diagnostics')
vi.mock('./main-window-lifecycle-flags')
vi.mock('./gpu-lifecycle')
vi.mock('../browser/browser-client-page-automation-runtime', () => ({
configureBrowserClientPageAutomationRuntime: vi.fn()
}))
vi.mock('../crash-reporting/process-gone-diagnostics', () => ({
startPreGoneCrashSampling: vi.fn()
}))
vi.mock('./main-window-lifecycle-flags', () => ({ recordProcessGoneCrash: vi.fn() }))
vi.mock('./gpu-lifecycle', () => ({ handleGpuChildCrash: vi.fn() }))
vi.mock('./configure-process', () => ({ shouldInstallManagedHooks: () => false }))
vi.mock('../agent-hooks/install-telemetry')
vi.mock('./main-process-observers')
vi.mock('./main-process-account-services')
vi.mock('./main-process-automations')
vi.mock('./main-process-plugins')
vi.mock('../worktree-trash')
vi.mock('./worktree-removal-records-load')
vi.mock('./first-window-deferral')
vi.mock('./startup-diagnostics')
vi.mock('../opencode/opencode-status-plugin-startup-refresh')
vi.mock('../agent-hooks/install-telemetry', () => ({ recordManagedHookInstallFailure: vi.fn() }))
vi.mock('./main-process-observers', () => ({ initializeMainProcessObservers: vi.fn() }))
vi.mock('./main-process-account-services', () => ({
initializeMainProcessAccountServices: vi.fn()
}))
vi.mock('./main-process-automations', () => ({ initializeMainProcessAutomations: vi.fn() }))
vi.mock('./main-process-plugins', () => ({ initializeMainProcessPlugins: vi.fn() }))
vi.mock('../worktree-trash', () => ({
collectWorktreeTrashSweepRoots: vi.fn(),
sweepStaleWorktreeTrash: vi.fn()
}))
vi.mock('./worktree-removal-records-load', () => ({ loadWorktreeRemovalRecordsForStore: vi.fn() }))
vi.mock('./first-window-deferral', () => ({ runAfterFirstWindowShown: vi.fn() }))
vi.mock('./startup-diagnostics', () => ({ logStartupMilestone: vi.fn() }))
vi.mock('../opencode/opencode-status-plugin-startup-refresh', () => ({
refreshInstalledOpenCodeStatusPlugins: vi.fn()
}))
vi.mock('../orcad/orcad-artifact-cache-retention', () => ({
pruneDesktopOrcadArtifactCache: vi.fn()
}))
import { initializeReadyRuntimeServices } from './main-process-ready-runtime'
@@ -102,6 +102,14 @@ vi.mock('../window/foreground-activation-policy', () => ({
isWindowlessLaunch: () => false,
showWindowWithoutStealingFocus
}))
vi.mock('../tray/system-tray', () => ({
createSystemTray: vi.fn(),
setMacMenuBarIconVisible: vi.fn()
}))
vi.mock('../window/attach-main-window-services', () => ({ ensureAutoUpdaterConfigured: vi.fn() }))
vi.mock('../window/dashboard-popout-window', () => ({ getDashboardPopoutWindow: () => null }))
vi.mock('../window/createMainWindow', () => ({ loadMainWindow: vi.fn() }))
vi.mock('../window/renderer-recovery-prompt', () => ({ presentRendererRecoveryPrompt: vi.fn() }))
vi.mock('./main-process-ready-foundation', () => ({
initializeReadyFoundation: vi.fn(async () => {})
@@ -19,6 +19,7 @@ vi.mock('@monaco-editor/react', () => ({
},
loader: { config: vi.fn() }
}))
vi.mock('@/lib/monaco-setup', () => ({}))
vi.mock('@/store', () => ({
useAppStore: (selector: (state: Record<string, unknown>) => unknown) =>
selector({
@@ -17,6 +17,7 @@ vi.mock('@monaco-editor/react', () => ({
},
loader: { config: vi.fn() }
}))
vi.mock('@/lib/monaco-setup', () => ({}))
vi.mock('@/store', () => ({
useAppStore: (selector: (state: Record<string, unknown>) => unknown) =>
selector({
@@ -1,141 +0,0 @@
import { describe, expect, it, vi } from 'vitest'
import { renderToStaticMarkup } from 'react-dom/server'
import { TooltipProvider } from '@/components/ui/tooltip'
import { CommitArea } from './source-control/commit/commit-area'
import { resolvePrimaryAction, type PrimaryActionInputs } from './source-control-primary-action'
import { resolveDropdownItems } from './source-control-dropdown-items'
import type { DropdownActionKind } from './source-control-dropdown-item-types'
// Why: split out from CommitArea.test.tsx so each file stays under the
// project's max-lines budget. These tests cover the directional-icon
// mapping for primary action kinds (push / pull / sync / publish); the
// commit-checkmark and core CommitArea behaviour live in the sibling file.
function buildInputs(overrides: Partial<PrimaryActionInputs> = {}): PrimaryActionInputs {
return {
stagedCount: 1,
hasUnstagedChanges: false,
hasStageableChanges: false,
hasPartiallyStagedChanges: false,
hasMessage: true,
hasUnresolvedConflicts: false,
isCommitting: false,
isRemoteOperationActive: false,
upstreamStatus: { hasUpstream: true, ahead: 0, behind: 0 },
...overrides
}
}
function baseProps(overrides: Partial<PrimaryActionInputs> = {}) {
const inputs = buildInputs(overrides)
return {
worktreeId: 'wt-1',
groupId: 'group-1',
commitMessage: 'feat: add commit area',
commitError: null as string | null,
commitFailureRecoveryPrompt: null as string | null,
pushRecovery: null,
remoteActionError: null as string | null,
isCommitting: inputs.isCommitting,
isFixingCommitFailureWithAI: false,
isFixingPushFailureWithAI: false,
sourceControlAiActionsVisible: true,
aiAgentConfigured: false,
isGenerating: false,
generateError: null as string | null,
stagedCount: inputs.stagedCount,
hasPartiallyStagedChanges: inputs.hasPartiallyStagedChanges,
hasUnresolvedConflicts: inputs.hasUnresolvedConflicts,
isRemoteOperationActive: inputs.isRemoteOperationActive,
inFlightRemoteOpKind: inputs.inFlightRemoteOpKind ?? null,
primaryAction: resolvePrimaryAction(inputs),
dropdownItems: resolveDropdownItems(inputs),
onCommitMessageChange: vi.fn(),
onGenerate: vi.fn(),
onCancelGenerate: vi.fn(),
onFixCommitFailureWithAI: vi.fn(),
onFixPushFailureWithAI: vi.fn(),
onPrimaryAction: vi.fn(),
onDropdownAction: vi.fn() as (kind: DropdownActionKind) => void
}
}
function primaryButton(props: ReturnType<typeof baseProps>): string {
const markup = renderToStaticMarkup(
<TooltipProvider>
<CommitArea {...props} />
</TooltipProvider>
)
const button = [...markup.matchAll(/<button\b[\s\S]*?<\/button>/g)]
.map((match) => match[0])
.find((entry) => entry.includes('data-slot="button"'))
if (!button) {
throw new Error('primary button not found')
}
return button
}
describe('CommitArea primary action icons', () => {
it('renders an up-arrow on a Push primary', () => {
expect(
primaryButton(
baseProps({
stagedCount: 0,
hasMessage: false,
upstreamStatus: { hasUpstream: true, ahead: 1, behind: 0 }
})
)
).toContain('lucide-arrow-up')
})
it('renders no directional icon on a Pull primary', () => {
const button = primaryButton(
baseProps({
stagedCount: 0,
hasMessage: false,
upstreamStatus: { hasUpstream: true, ahead: 0, behind: 1 }
})
)
expect(button).not.toContain('lucide-arrow-up')
expect(button).not.toContain('lucide-arrow-down-up')
expect(button).not.toContain('lucide-cloud-upload')
})
it('renders a bidirectional arrow on a Sync primary', () => {
expect(
primaryButton(
baseProps({
stagedCount: 0,
hasMessage: false,
upstreamStatus: { hasUpstream: true, ahead: 1, behind: 1 }
})
)
).toContain('lucide-arrow-down-up')
})
it('renders a cloud-up icon on a Publish primary', () => {
expect(
primaryButton(
baseProps({
stagedCount: 0,
hasMessage: false,
upstreamStatus: { hasUpstream: false, ahead: 0, behind: 0 }
})
)
).toContain('lucide-cloud-upload')
})
it('renders a plus icon on a Stage All primary', () => {
expect(
primaryButton(
baseProps({
stagedCount: 0,
hasUnstagedChanges: true,
hasStageableChanges: true,
hasMessage: false,
upstreamStatus: { hasUpstream: true, ahead: 0, behind: 0 }
})
)
).toContain('lucide-plus')
})
})
@@ -1,45 +0,0 @@
// @vitest-environment happy-dom
import { cleanup, render, screen } from '@testing-library/react'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { getKeybindingDefinition } from '../../../../shared/keybindings'
import { TooltipProvider } from '@/components/ui/tooltip'
import { ShortcutCommandBlock } from './ShortcutCommandBlock'
afterEach(cleanup)
describe('ShortcutCommandBlock', () => {
it('wraps persistent conflict remediation instead of truncating it', () => {
const warning = 'Blocked by Mission Control. Remap here or change it in System Settings.'
const item = getKeybindingDefinition('tab.selectByIndex')
expect(item).not.toBeNull()
render(
<TooltipProvider>
<ShortcutCommandBlock
item={item!}
groupTitle="Tab Navigation"
platform="darwin"
effective={['Ctrl+1']}
modified={false}
warnings={[warning]}
previousBindings={[]}
recordingBindingIndex={null}
onStartRecordingAt={vi.fn()}
onAppendBinding={vi.fn()}
onCancelRecording={vi.fn()}
onCapture={vi.fn()}
onClearError={vi.fn()}
onRemoveBindingAt={vi.fn()}
onResetAction={vi.fn()}
onDisableAction={vi.fn()}
onEnableAction={vi.fn()}
/>
</TooltipProvider>
)
const helper = screen.getByText(warning)
expect(helper.classList.contains('whitespace-normal')).toBe(true)
expect(helper.classList.contains('truncate')).toBe(false)
})
})
@@ -1,19 +0,0 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`PREVIEW_BUFFER > matches the locked snapshot 1`] = `
"~/orca main * $ npm test
 PASS  src/preview.test.ts
✓ renders sample output (3ms)
✗ ligatures: => != >= <= ===
def total(xs: list[int]) -> int:
"""Sum the values."""
return sum(x for x in xs)
~/orca main * $ git diff
@@ -1,2 +1,3 @@
-const size = 13
+const size = 14
~/orca main * $ "
`;
@@ -1,27 +0,0 @@
import { describe, expect, it } from 'vitest'
import { getDefaultSettings } from '../../../../shared/constants'
import { resolveInterfaceSectionSummary } from './appearance-interface-summary'
describe('resolveInterfaceSectionSummary', () => {
it('includes theme, language, and font when the language setting is shown', () => {
const settings = {
...getDefaultSettings('/tmp'),
theme: 'dark' as const,
uiLanguage: 'zh' as const,
appFontFamily: 'Inter'
}
expect(resolveInterfaceSectionSummary(settings)).toBe('Dark · 中文(简体) · Inter')
})
it('falls back to the default font label when app font is empty', () => {
const settings = {
...getDefaultSettings('/tmp'),
theme: 'light' as const,
uiLanguage: 'system' as const,
appFontFamily: ''
}
expect(resolveInterfaceSectionSummary(settings)).toBe('Light · System · Default font')
})
})
@@ -1,26 +0,0 @@
import { describe, expect, it } from 'vitest'
import { PREVIEW_BUFFER } from './terminal-preview-content'
describe('PREVIEW_BUFFER', () => {
it('contains the canonical content markers', () => {
// Why: strip ANSI before matching so re-coloring a marker (e.g. splitting
// "def render" into separate keyword/identifier spans) doesn't break this
// test — what matters is the visible text the user reads.
// eslint-disable-next-line no-control-regex
const stripped = PREVIEW_BUFFER.replace(/\x1b\[[0-9;]*m/g, '')
expect(stripped).toContain('npm test')
expect(stripped).toContain('PASS')
expect(stripped).toContain('ligatures: => != >= <= ===')
expect(stripped).toContain('git diff')
expect(stripped).toContain('def total')
})
it('ends without a trailing newline so the cursor parks on the prompt', () => {
expect(PREVIEW_BUFFER.endsWith('\n')).toBe(false)
expect(PREVIEW_BUFFER).toMatch(/\$ $/)
})
it('matches the locked snapshot', () => {
expect(PREVIEW_BUFFER).toMatchSnapshot()
})
})
+47 -5
View File
@@ -3,6 +3,7 @@ import { execFileSync } from 'node:child_process'
import { existsSync, readFileSync, readdirSync } from 'node:fs'
import path from 'node:path'
import { cleanupE2ECrashpad } from './electron-crashpad-cleanup'
import { recordElectronShutdownTiming } from './electron-shutdown-timing'
import type { ElectronApplication } from '@stablyai/playwright-test'
const GRACEFUL_CLOSE_TIMEOUT_MS = 10_000
@@ -214,10 +215,35 @@ export async function forceQuitElectronAppForE2E(
).catch(() => undefined)
}
export async function closeElectronAppForE2E(app: ElectronApplication): Promise<void> {
export async function closeElectronAppForE2E(
app: ElectronApplication,
options: { cleanupDaemonsForUserDataDir?: string } = {}
): Promise<void> {
const proc = app.process()
const releasePipes = (): void => releaseExitedProcessPipes(proc)
const started = performance.now()
let rootExitMs: number | null = hasExited(proc) ? 0 : null
let pipesClosedMs: number | null = null
let outcome = 'closed'
let daemonCleanup: Promise<void> | undefined
const startDaemonCleanup = (): void => {
if (options.cleanupDaemonsForUserDataDir && !daemonCleanup) {
daemonCleanup = cleanupE2EDaemons(options.cleanupDaemonsForUserDataDir)
void daemonCleanup.catch(() => undefined)
}
}
const releasePipes = (): void => {
if (hasExited(proc)) {
rootExitMs ??= performance.now() - started
// Linux daemon trees retain Chromium sockets that block Playwright's public close.
startDaemonCleanup()
}
releaseExitedProcessPipes(proc)
}
const pipesClosed = (): void => {
pipesClosedMs = performance.now() - started
}
proc.once('exit', releasePipes)
proc.once('close', pipesClosed)
releasePipes()
try {
await withTimeout(app.close(), GRACEFUL_CLOSE_TIMEOUT_MS, 'Timed out closing Electron app')
@@ -228,13 +254,23 @@ export async function closeElectronAppForE2E(app: ElectronApplication): Promise<
}
}
} catch {
outcome = 'fallback'
if (proc) {
await forceKillProcessTree(proc)
}
} finally {
proc.off('exit', releasePipes)
proc.off('close', pipesClosed)
releasePipes()
recordElectronShutdownTiming('close', started, {
pid: proc.pid,
outcome,
rootExitMs,
pipesClosedMs
})
}
startDaemonCleanup()
await daemonCleanup
}
function readDaemonPidFiles(userDataDir: string): number[] {
@@ -268,8 +304,14 @@ export async function cleanupE2EDaemons(userDataDir: string): Promise<void> {
// Why: app quit intentionally leaves daemon PTYs alive for warm reattach.
// E2E temp profiles are deleted after each test, so their detached daemons
// must be stopped explicitly or CI accumulates orphan Electron/shell trees.
for (const pid of readDaemonPidFiles(userDataDir)) {
await forceKillPidTree(pid)
const started = performance.now()
const pids = readDaemonPidFiles(userDataDir)
try {
for (const pid of pids) {
await forceKillPidTree(pid)
}
cleanupE2ECrashpad(userDataDir)
} finally {
recordElectronShutdownTiming('daemon-cleanup', started, { daemonCount: pids.length })
}
cleanupE2ECrashpad(userDataDir)
}
@@ -158,6 +158,52 @@ describe('Electron shutdown with inherited pipes', () => {
})
describe.skipIf(process.platform === 'win32')('owned POSIX daemon cleanup', () => {
it('preserves a running daemon when final cleanup is not requested', async () => {
const daemon = await ownedDaemon('exit')
const { proc, app } = exitedAppFixture()
proc.exitCode = 0
await closeElectronAppForE2E(app)
expect(daemon.pids.every((pid) => !pidIsGone(pid))).toBe(true)
})
it('starts final daemon cleanup after app exit and awaits public close and all descendants', async () => {
const daemon = await ownedDaemon('descendant')
const { proc, app, close } = exitedAppFixture()
let finishPublicClose: () => void = () => {}
const publicClose = new Promise<void>((resolve) => {
finishPublicClose = resolve
})
let publicCloseFinished = false
close.mockImplementation(async () => {
await daemon.exited
await publicClose
publicCloseFinished = true
return []
})
let shutdownFinished = false
const closing = closeElectronAppForE2E(app, {
cleanupDaemonsForUserDataDir: daemon.profile
}).then(() => {
shutdownFinished = true
})
expect(close).toHaveBeenCalledOnce()
expect(daemon.pids.every((pid) => !pidIsGone(pid))).toBe(true)
proc.exitCode = 0
proc.emit('exit', 0, null)
const [, signal] = await daemon.exited
expect(signal).toBe('SIGTERM')
expect(pidIsGone(daemon.pids[1])).toBe(false)
expect(publicCloseFinished).toBe(false)
expect(shutdownFinished).toBe(false)
finishPublicClose()
await expect.poll(() => publicCloseFinished).toBe(true)
expect(shutdownFinished).toBe(false)
await closing
await expect.poll(() => daemon.pids.every(pidIsGone), { timeout: 5_000 }).toBe(true)
expect(close).toHaveBeenCalledOnce()
expect(proc.listenerCount('exit')).toBe(0)
}, 10_000)
it('returns when every captured process exits on SIGTERM', async () => {
const daemon = await ownedDaemon('exit')
const started = performance.now()
@@ -0,0 +1,21 @@
type ShutdownPhase = 'close' | 'daemon-cleanup' | 'profile-removal'
export function recordElectronShutdownTiming(
phase: ShutdownPhase,
started: number,
details: Record<string, number | string | boolean | null | undefined> = {}
): void {
if (
process.env.ORCA_E2E_SHUTDOWN_TIMING !== '1' &&
process.env.ORCA_E2E_FORWARD_APP_LOGS !== '1'
) {
return
}
console.log(
`[e2e:shutdown] ${JSON.stringify({
phase,
elapsedMs: Number((performance.now() - started).toFixed(3)),
...details
})}`
)
}
@@ -1,45 +1,9 @@
import type { Page } from '@playwright/test'
import { startHiddenRendererFrameCapture as startCapture } from '../../../src/main/browser/hidden-renderer-frame-capture-fixture'
import { expect } from './orca-app'
export async function startHiddenRendererFrameCapture(page: Page): Promise<() => Promise<void>> {
const cdp = await page.context().newCDPSession(page)
let stopped = false
const handlePageClose = (): void => {
void stop().catch(() => {})
}
const stop = async (): Promise<void> => {
if (stopped) {
return
}
stopped = true
try {
await cdp.send('Page.stopScreencast')
} finally {
page.off('close', handlePageClose)
cdp.off('Page.screencastFrame', acknowledgeFrame)
await cdp.detach()
}
}
let hasFrame = false
const acknowledgeFrame = (frame: { sessionId: number }): void => {
hasFrame = true
void cdp.send('Page.screencastFrameAck', { sessionId: frame.sessionId }).catch(() => {})
}
cdp.on('Page.screencastFrame', acknowledgeFrame)
page.once('close', handlePageClose)
try {
// Keep hidden renderers painting without revealing their native windows.
await cdp.send('Page.enable')
await cdp.send('Page.startScreencast', {
format: 'jpeg',
quality: 20,
maxWidth: 640,
maxHeight: 480
})
await expect.poll(() => hasFrame).toBe(true)
return stop
} catch (error) {
await stop().catch(() => {})
throw error
}
return startCapture(page, async (hasFrame) => {
await expect.poll(hasFrame).toBe(true)
})
}
+6 -5
View File
@@ -24,7 +24,8 @@ import {
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs'
import os from 'node:os'
import path from 'node:path'
import { cleanupE2EDaemons, closeElectronAppForE2E } from './electron-process-shutdown'
import { closeElectronAppForE2E } from './electron-process-shutdown'
import { recordElectronShutdownTiming } from './electron-shutdown-timing'
import { getOrcaElectronLaunchArgs } from './electron-launch-args'
import { retryTransientMainEvaluate } from './electron-main-evaluate-retry'
import { getE2ECompletedOnboardingProfile } from './e2e-completed-onboarding-profile'
@@ -91,9 +92,11 @@ const ORCA_E2E_SLOWMO_MS = ((): number => {
})()
async function removeUserDataDirAfterShutdown(userDataDir: string): Promise<void> {
const started = performance.now()
for (let attempt = 0; attempt < 5; attempt += 1) {
try {
rmSync(userDataDir, { recursive: true, force: true })
recordElectronShutdownTiming('profile-removal', started)
return
} catch (error) {
if (attempt === 4) {
@@ -251,16 +254,14 @@ export const test = base.extend<OrcaTestFixtures, OrcaWorkerFixtures>({
)
assertElectronResolvedIsolatedHome(resolvedHome, homeIsolation)
} catch (error) {
await closeElectronAppForE2E(app)
await cleanupE2EDaemons(userDataDir)
await closeElectronAppForE2E(app, { cleanupDaemonsForUserDataDir: userDataDir })
await removeUserDataDirAfterShutdown(userDataDir)
throw error
}
await provideFixture(app)
// Why: the Playwright close promise can settle before all Electron and PTY
// descendants are gone in CI; worker teardown then hangs on open handles.
await closeElectronAppForE2E(app)
await cleanupE2EDaemons(userDataDir)
await closeElectronAppForE2E(app, { cleanupDaemonsForUserDataDir: userDataDir })
await removeUserDataDirAfterShutdown(userDataDir)
},
@@ -82,7 +82,7 @@ function attachModelPane(
ReturnType<typeof createRemoteRuntimePtyTransport>['attach']
>[0]['callbacks']
): ReturnType<typeof createRemoteRuntimePtyTransport> {
const call = vi.fn(async (request: { method: string }) =>
const call = async (request: { method: string }) =>
request.method === 'terminal.resolvePane'
? {
ok: true,
@@ -96,7 +96,7 @@ function attachModelPane(
}
}
: { ok: true, result: { terminal: { handle: MODEL_TERMINAL_HANDLE } } }
)
vi.stubGlobal('window', {
api: { runtimeEnvironments: { call, subscribe: link.subscribe } },
location: { search: '' }
@@ -193,6 +193,8 @@ async function updateWindowsDefaultShellSetting(
}
test.describe('Windows terminal shell paste ownership', () => {
test.skip(process.platform !== 'win32', 'Windows terminal shell paste coverage is Windows-only')
async function skipWhenGitBashUnavailable(page: Page): Promise<void> {
const isAvailable = await page.evaluate(() => window.api.gitBash.isAvailable())
test.skip(!isAvailable, 'Git Bash is not available on this Windows host')
@@ -203,8 +205,6 @@ test.describe('Windows terminal shell paste ownership', () => {
orcaPage,
testRepoPath
}) => {
test.skip(process.platform !== 'win32', 'PowerShell paste coverage is Windows-only')
await waitForSessionReady(orcaPage)
await waitForActiveWorktree(orcaPage)
await ensureTerminalVisible(orcaPage)
@@ -256,8 +256,6 @@ test.describe('Windows terminal shell paste ownership', () => {
orcaPage,
testRepoPath
}) => {
test.skip(process.platform !== 'win32', 'cmd.exe paste coverage is Windows-only')
await waitForSessionReady(orcaPage)
await waitForActiveWorktree(orcaPage)
await ensureTerminalVisible(orcaPage)
@@ -307,7 +305,6 @@ test.describe('Windows terminal shell paste ownership', () => {
orcaPage,
testRepoPath
}) => {
test.skip(process.platform !== 'win32', 'Git Bash paste coverage is Windows-only')
await skipWhenGitBashUnavailable(orcaPage)
await waitForSessionReady(orcaPage)
@@ -359,7 +356,6 @@ test.describe('Windows terminal shell paste ownership', () => {
orcaPage,
testRepoPath
}) => {
test.skip(process.platform !== 'win32', 'WSL paste coverage is Windows-only')
test.skip(!hasWslNodeRuntime(), 'WSL with node is not available on this Windows host')
await waitForSessionReady(orcaPage)
@@ -418,7 +414,6 @@ test.describe('Windows terminal shell paste ownership', () => {
orcaPage,
testRepoPath
}) => {
test.skip(process.platform !== 'win32', 'WSL paste runtime retention is Windows-only')
test.skip(!hasWslNodeRuntime(), 'WSL with node is not available on this Windows host')
await waitForSessionReady(orcaPage)
+4
View File
@@ -1,4 +1,5 @@
import { defineConfig } from '@stablyai/playwright-test'
import { resolveBuildSourceCommit } from '../config/scripts/build-source-commit.mjs'
/**
* Playwright config for Orca E2E tests.
@@ -14,6 +15,9 @@ import { defineConfig } from '@stablyai/playwright-test'
*/
export default defineConfig({
testDir: './e2e',
// Keep checkout provenance without fetching the PR base during test setup.
captureGitInfo: { commit: false, diff: false },
metadata: { sourceCommit: resolveBuildSourceCommit() },
globalSetup: './e2e/global-setup.ts',
globalTeardown: './e2e/global-teardown.ts',
// Why: this suite launches a fresh Electron app and isolated userData dir per