diff --git a/config/scripts/ci-unit-sequencer.mjs b/config/scripts/ci-unit-sequencer.mjs index 6b580bdb002..94dcd933f45 100644 --- a/config/scripts/ci-unit-sequencer.mjs +++ b/config/scripts/ci-unit-sequencer.mjs @@ -16,13 +16,14 @@ export default class TimingSequencer extends BaseSequencer { 'utf8' ) ) + const discovered = new Set(plan.files) if ( plan.version !== 1 || !plan.sourceSha || plan.sourceSha !== process.env.ORCA_SHARD_SOURCE_SHA || JSON.stringify([...plan.files].sort()) !== JSON.stringify(specs.map(key).sort()) || !Array.isArray(plan.executionFiles) || - plan.executionFiles.some((file) => !plan.files.includes(file)) + plan.executionFiles.some((file) => !discovered.has(file)) ) { throw new Error('Selection provenance or discovery differs') } diff --git a/config/scripts/ci-unit-sequencer.test.mjs b/config/scripts/ci-unit-sequencer.test.mjs index 931a3ccd158..a14ab60b497 100644 --- a/config/scripts/ci-unit-sequencer.test.mjs +++ b/config/scripts/ci-unit-sequencer.test.mjs @@ -6,39 +6,86 @@ import TimingSequencer from './ci-unit-sequencer.mjs' let root afterEach(() => { + vi.restoreAllMocks() vi.unstubAllEnvs() if (root) { rmSync(root, { recursive: true, force: true }) } }) -it.each(['valid', 'stale', 'missing-file', 'missing-artifact'])( - 'preserves complete shard coverage with %s planning evidence', - async (kind) => { - root = mkdtempSync(join(tmpdir(), 'unit-sequencer-')) - const files = ['src/a.test.ts', 'src/b.test.ts', 'src/c.test.ts', 'src/d.test.ts'] - const plan = { - version: 1, - sourceSha: kind === 'stale' ? 'old' : 'current', - files: kind === 'missing-file' ? files.slice(1) : files, - executionFiles: files.slice(0, 2) - } - const planPath = join(root, 'selection.json') - if (kind !== 'missing-artifact') { - writeFileSync(planPath, JSON.stringify(plan)) - } - vi.stubEnv('ORCA_UNIT_SELECTION_PLAN', planPath) - vi.stubEnv('ORCA_SHARD_SOURCE_SHA', 'current') - vi.stubEnv('ORCA_SHARD_MANIFEST', join(root, 'assignment.json')) - const assigned = [] - for (const index of [1, 2]) { - const sequencer = new TimingSequencer({ config: { root, shard: { index, count: 2 } } }) - const specs = files.map((file) => ({ moduleId: join(root, file) })) - assigned.push(...(await sequencer.shard(specs)).map((spec) => spec.moduleId)) - } - expect(assigned.sort()).toEqual( - (kind === 'valid' ? files.slice(0, 2) : files).map((file) => join(root, file)).sort() - ) - expect(new Set(assigned).size).toBe(assigned.length) +it.each([ + 'valid', + 'stale', + 'missing-file', + 'missing-artifact', + 'outside-selection', + 'empty-selection' +])('preserves complete shard coverage with %s planning evidence', async (kind) => { + root = mkdtempSync(join(tmpdir(), 'unit-sequencer-')) + const files = ['src/a.test.ts', 'src/b.test.ts', 'src/c.test.ts', 'src/d.test.ts'] + const plan = { + version: 1, + sourceSha: kind === 'stale' ? 'old' : 'current', + files: kind === 'missing-file' ? files.slice(1) : files, + executionFiles: + kind === 'outside-selection' + ? ['src/unknown.test.ts'] + : kind === 'empty-selection' + ? [] + : files.slice(0, 2) } -) + const planPath = join(root, 'selection.json') + if (kind !== 'missing-artifact') { + writeFileSync(planPath, JSON.stringify(plan)) + } + vi.stubEnv('ORCA_UNIT_SELECTION_PLAN', planPath) + vi.stubEnv('ORCA_SHARD_SOURCE_SHA', 'current') + vi.stubEnv('ORCA_SHARD_MANIFEST', join(root, 'assignment.json')) + const assigned = [] + for (const index of [1, 2]) { + const sequencer = new TimingSequencer({ config: { root, shard: { index, count: 2 } } }) + const specs = files.map((file) => ({ moduleId: join(root, file) })) + assigned.push(...(await sequencer.shard(specs)).map((spec) => spec.moduleId)) + } + expect(assigned.sort()).toEqual( + (kind === 'valid' ? files.slice(0, 2) : files).map((file) => join(root, file)).sort() + ) + expect(new Set(assigned).size).toBe(assigned.length) +}) + +it('validates a large selection without scanning the discovered array for each file', async () => { + root = mkdtempSync(join(tmpdir(), 'unit-sequencer-scale-')) + const files = Array.from({ length: 1600 }, (_, index) => `src/scale-${index}.test.ts`) + const executionFiles = files.slice(800) + const planPath = join(root, 'selection.json') + writeFileSync( + planPath, + JSON.stringify({ version: 1, sourceSha: 'current', files, executionFiles }) + ) + vi.stubEnv('ORCA_UNIT_SELECTION_PLAN', planPath) + vi.stubEnv('ORCA_SHARD_SOURCE_SHA', 'current') + vi.stubEnv('ORCA_SHARD_MANIFEST', join(root, 'assignment.json')) + const sequencer = new TimingSequencer({ config: { root, shard: { index: 1, count: 1 } } }) + const specs = files.map((file) => ({ moduleId: join(root, file) })) + const includes = Array.prototype.includes + let discoveredArrayScans = 0 + const scan = vi + .spyOn(Array.prototype, 'includes') + .mockImplementation(function (value, fromIndex) { + if ( + this.length === files.length && + this[0] === files[0] && + typeof value === 'string' && + value.startsWith('src/scale-') + ) { + discoveredArrayScans += 1 + } + return includes.call(this, value, fromIndex) + }) + const selected = await sequencer.shard(specs) + scan.mockRestore() + expect(selected.map((spec) => spec.moduleId).sort()).toEqual( + executionFiles.map((file) => join(root, file)).sort() + ) + expect(discoveredArrayScans).toBe(0) +})