mirror of
https://github.com/stablyai/orca.git
synced 2026-11-13 09:27:29 +00:00
* refactor(tooling): organize test runtime and maintenance owners * test(tooling): preserve relocated runtime fixture dependency edges * refactor(tooling): preserve xterm routing owner checks * ci(tooling): include routing owner in sparse checkout * test(tooling): preserve copied workflow dependency closures * fix(tooling): normalize hashed JSON assets in release parity * fix(tooling): distinguish asset aliases from literal references * fix(tooling): frame normalized asset reference payloads * fix(tooling): normalize decoded JSON asset references * refactor(tooling): organize build, CI and release owners * fix(tooling): align moved generator and workflow path contracts * fix(tooling): update generated plugin bundle owner banners * test(tooling): create relocated mobile verifier fixture directories * test(tooling): copy headless detector dependency closure * test(tooling): align native batch policy fixtures with owner path * fix(tooling): link moved reliability command to historical evidence * fix(tooling): preserve nested sandbox owners and canonical guide source * fix(tooling): target release version owners in checkout fixture
101 lines
3.5 KiB
JavaScript
101 lines
3.5 KiB
JavaScript
import { unitConsumers } from './dependency-graph/ci-unit-dependency-graph.mjs'
|
|
import { balanceFiles } from '../sharding/ci-shard-assignment.mjs'
|
|
import { UNIT_SETUP_FILES } from './dependency-graph/ci-unit-files.mjs'
|
|
import { FULL_SHARD_COUNT } from './ci-unit-matrix.mjs'
|
|
|
|
const RUNTIME_ENTRY_POINTS = new Set([
|
|
'config/vitest.config.ts',
|
|
'config/scripts/test-runtime/execution-policy/run-vitest.mjs',
|
|
...UNIT_SETUP_FILES
|
|
])
|
|
|
|
function isUnitRuntimeEntryPoint(file) {
|
|
return (
|
|
RUNTIME_ENTRY_POINTS.has(file) ||
|
|
(/^config\/scripts\/(?:test-runtime\/environment\/|test-runtime\/execution-policy\/|test-runtime\/dom\/|test-runtime\/module-cache\/|test-runtime\/node-worker\/|test-runtime\/sqlite-admission\/)?vitest[^/]*\.(?:ts|mjs)$/.test(
|
|
file
|
|
) &&
|
|
!file.includes('.test.'))
|
|
)
|
|
}
|
|
|
|
export function selectUnitFiles(files, changed, graphInput) {
|
|
const full = (reason) => ({ files, reason, full: true })
|
|
if (!changed.length) {
|
|
return full('Missing changed-path evidence')
|
|
}
|
|
if (changed.some((file) => !file.startsWith('src/'))) {
|
|
return full('Global, deleted, renamed or unknown input')
|
|
}
|
|
const graph = typeof graphInput === 'function' ? graphInput() : graphInput
|
|
if (changed.some((file) => !graph.files.has(file))) {
|
|
return full('Global, deleted, renamed or unknown input')
|
|
}
|
|
for (const file of changed) {
|
|
const consumers = unitConsumers([file], graph.reverse)
|
|
if ([...consumers].some(isUnitRuntimeEntryPoint)) {
|
|
return full('Shared unit runtime input')
|
|
}
|
|
if (!files.some((test) => consumers.has(test))) {
|
|
return full('No proven test coverage for changed inputs')
|
|
}
|
|
}
|
|
const affected = unitConsumers(changed, graph.reverse)
|
|
for (const file of graph.opaqueConsumers ?? unitConsumers(graph.opaque, graph.reverse)) {
|
|
affected.add(file)
|
|
}
|
|
const selected = files.filter((file) => affected.has(file))
|
|
if (!selected.length) {
|
|
return full('No proven test coverage for changed inputs')
|
|
}
|
|
return {
|
|
files: selected,
|
|
reason: 'Transitive imports plus indirect-input consumers',
|
|
full: false,
|
|
checkedInputOwners: graph.checkedInputOwners ?? []
|
|
}
|
|
}
|
|
|
|
export function planUnitSelection({
|
|
files,
|
|
changed,
|
|
graph,
|
|
timings,
|
|
event,
|
|
mode = 'shadow',
|
|
fullShardCount = FULL_SHARD_COUNT
|
|
}) {
|
|
const candidate = selectUnitFiles(files, changed, graph)
|
|
const selected = mode === 'selected' && event?.pull_request?.draft === true && !candidate.full
|
|
const executionFiles = selected ? candidate.files : files
|
|
const totalMs = balanceFiles(executionFiles, 1, timings).shards[0].durationMs
|
|
const count = selected
|
|
? Math.max(1, Math.min(FULL_SHARD_COUNT, Math.ceil(totalMs / 900_000), executionFiles.length))
|
|
: fullShardCount
|
|
return {
|
|
version: 1,
|
|
mode: selected ? 'selected' : 'shadow',
|
|
selectionAvailable: !candidate.full,
|
|
reason: candidate.reason,
|
|
checkedInputOwners: candidate.checkedInputOwners ?? [],
|
|
files,
|
|
candidateFiles: candidate.files,
|
|
executionFiles,
|
|
shards: Array.from({ length: count }, (_, index) => ({ index: index + 1, count }))
|
|
}
|
|
}
|
|
|
|
export function auditUnitSelection(plan, results) {
|
|
const candidates = new Set(plan.candidateFiles)
|
|
const omittedFailures = Object.entries(results)
|
|
.filter(([file, state]) => state === 'failed' && !candidates.has(file))
|
|
.map(([file]) => file)
|
|
return {
|
|
mode: plan.mode,
|
|
discovered: plan.files.length,
|
|
candidate: candidates.size,
|
|
executed: Object.keys(results).length,
|
|
omittedFailures
|
|
}
|
|
}
|