통합 테스트를 실행 권위별로 분리한다
This commit is contained in:
@@ -4,7 +4,9 @@
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test:integration": "vitest run --config vitest.config.ts",
|
||||
"test:integration": "node run-test-lane.mjs core",
|
||||
"test:integration:reference": "node run-test-lane.mjs reference",
|
||||
"test:integration:lifecycle": "node run-test-lane.mjs lifecycle",
|
||||
"typecheck": "pnpm -w tsc7 -b tools/integration-tests/tsconfig.json"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const packageRoot = path.dirname(fileURLToPath(import.meta.url));
|
||||
const registryPath = path.join(packageRoot, 'test-lanes.tsv');
|
||||
const testRoot = path.join(packageRoot, 'test');
|
||||
const supportedLanes = new Set(['core', 'reference', 'lifecycle', 'conditional']);
|
||||
const supportedCommands = new Set(['core', 'reference', 'lifecycle', 'check']);
|
||||
|
||||
const usage = () => {
|
||||
process.stderr.write('usage: node run-test-lane.mjs <core|reference|lifecycle> [vitest arguments...]\n');
|
||||
process.exit(64);
|
||||
};
|
||||
|
||||
const [lane, ...rawVitestArguments] = process.argv.slice(2);
|
||||
const vitestArguments = rawVitestArguments[0] === '--' ? rawVitestArguments.slice(1) : rawVitestArguments;
|
||||
if (!lane || !supportedCommands.has(lane)) {
|
||||
usage();
|
||||
}
|
||||
|
||||
const rows = fs
|
||||
.readFileSync(registryPath, 'utf8')
|
||||
.split(/\r?\n/u)
|
||||
.map((line) => line.trim())
|
||||
.filter((line) => line !== '' && !line.startsWith('#'))
|
||||
.map((line, index) => {
|
||||
const fields = line.split('\t');
|
||||
if (fields.length !== 2) {
|
||||
throw new Error(`test-lanes.tsv row ${index + 1} must have exactly two tab-separated fields`);
|
||||
}
|
||||
const [file, registeredLane] = fields;
|
||||
if (!file || !registeredLane || !supportedLanes.has(registeredLane)) {
|
||||
throw new Error(`test-lanes.tsv row ${index + 1} is invalid`);
|
||||
}
|
||||
return { file, lane: registeredLane };
|
||||
});
|
||||
|
||||
const duplicateFiles = rows.map(({ file }) => file).filter((file, index, files) => files.indexOf(file) !== index);
|
||||
if (duplicateFiles.length > 0) {
|
||||
throw new Error(`duplicate integration test lane entries: ${[...new Set(duplicateFiles)].join(', ')}`);
|
||||
}
|
||||
|
||||
const discoveredFiles = fs
|
||||
.readdirSync(testRoot, { withFileTypes: true })
|
||||
.filter((entry) => entry.isFile() && entry.name.endsWith('.test.ts'))
|
||||
.map((entry) => `test/${entry.name}`)
|
||||
.sort();
|
||||
const registeredFiles = rows.map(({ file }) => file).sort();
|
||||
if (JSON.stringify(discoveredFiles) !== JSON.stringify(registeredFiles)) {
|
||||
const registered = new Set(registeredFiles);
|
||||
const discovered = new Set(discoveredFiles);
|
||||
const missing = discoveredFiles.filter((file) => !registered.has(file));
|
||||
const stale = registeredFiles.filter((file) => !discovered.has(file));
|
||||
throw new Error(
|
||||
`integration test lane registry mismatch; missing=[${missing.join(', ')}] stale=[${stale.join(', ')}]`
|
||||
);
|
||||
}
|
||||
|
||||
if (lane === 'check') {
|
||||
process.stdout.write(`integration test lane registry is valid (${registeredFiles.length} files)\n`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (lane === 'lifecycle' && process.env.INTEGRATION_LIFECYCLE_DISPOSABLE !== '1') {
|
||||
throw new Error(
|
||||
'lifecycle tests truncate public/che schemas and Redis; set INTEGRATION_LIFECYCLE_DISPOSABLE=1 only for a dedicated stack'
|
||||
);
|
||||
}
|
||||
|
||||
const files = rows.filter((row) => row.lane === lane).map(({ file }) => file);
|
||||
const environment = {
|
||||
...process.env,
|
||||
...(lane === 'reference' ? { TURN_DIFFERENTIAL_REFERENCE: '1' } : {}),
|
||||
};
|
||||
const result = spawnSync(
|
||||
'pnpm',
|
||||
['exec', 'vitest', 'run', '--config', 'vitest.config.ts', ...files, ...vitestArguments],
|
||||
{
|
||||
cwd: packageRoot,
|
||||
env: environment,
|
||||
stdio: 'inherit',
|
||||
}
|
||||
);
|
||||
if (result.error) {
|
||||
throw result.error;
|
||||
}
|
||||
process.exit(result.status ?? 1);
|
||||
@@ -0,0 +1,30 @@
|
||||
# Test file Lane
|
||||
test/auctionFlow.test.ts lifecycle
|
||||
test/battleDifferential.test.ts reference
|
||||
test/coreCommandTraceClock.test.ts core
|
||||
test/initialization.test.ts lifecycle
|
||||
test/instantDiplomacyCoreReference.integration.test.ts reference
|
||||
test/instantDiplomacyReference.integration.test.ts reference
|
||||
test/liveSortiePersistence.integration.test.ts conditional
|
||||
test/monthlyDisasterCoreReference.integration.test.ts reference
|
||||
test/monthlyNationBettingApiLifecycle.integration.test.ts conditional
|
||||
test/npcPossessionSelectionReference.integration.test.ts conditional
|
||||
test/orchestrator.e2e.test.ts lifecycle
|
||||
test/reservedTurnDaemonApiRace.integration.test.ts conditional
|
||||
test/scenario29NationBettingLifecycle.integration.test.ts conditional
|
||||
test/tournamentLifecycle.test.ts lifecycle
|
||||
test/troopStaticEvent.integration.test.ts conditional
|
||||
test/turnCommandCoreReference.integration.test.ts reference
|
||||
test/turnCommandFullLifecycle.integration.test.ts reference
|
||||
test/turnCommandFullLifecyclePersistence.integration.test.ts conditional
|
||||
test/turnCommandGeneralMatrix.integration.test.ts reference
|
||||
test/turnCommandNationMatrix.integration.test.ts reference
|
||||
test/turnCommandReference.integration.test.ts reference
|
||||
test/turnCommandRiskDurabilityMatrix.integration.test.ts conditional
|
||||
test/turnLogProjection.test.ts core
|
||||
test/turnMessageProjection.test.ts core
|
||||
test/turnSnapshotCanonicalCoverage.test.ts core
|
||||
test/turnSnapshotComparator.test.ts core
|
||||
test/turnSnapshotCoreDatabase.integration.test.ts conditional
|
||||
test/turnSnapshotReference.integration.test.ts reference
|
||||
test/turnTraceFiles.integration.test.ts conditional
|
||||
|
@@ -1402,6 +1402,13 @@ describeWithReference('ref ↔ core2026 battle differential', () => {
|
||||
},
|
||||
];
|
||||
|
||||
const executions: Array<{
|
||||
entry: (typeof cases)[number];
|
||||
base: BattleSimRequestPayload & { startYear: number };
|
||||
coreEvents: WarBattleTraceEvent[];
|
||||
coreRng: TracingRng | null;
|
||||
coreOutcome: WarBattleOutcome | null;
|
||||
}> = [];
|
||||
for (const entry of cases) {
|
||||
const base = readJson<BattleSimRequestPayload & { startYear: number }>(
|
||||
path.resolve(process.cwd(), 'fixtures/battle/basic-infantry.json')
|
||||
@@ -1441,13 +1448,16 @@ describeWithReference('ref ↔ core2026 battle differential', () => {
|
||||
},
|
||||
}
|
||||
);
|
||||
executions.push({ entry, base, coreEvents, coreRng, coreOutcome });
|
||||
}
|
||||
|
||||
const references = runReferenceTraceBatch(
|
||||
workspaceRoot!,
|
||||
executions.map(({ base }) => JSON.stringify(base))
|
||||
);
|
||||
executions.forEach(({ entry, coreEvents, coreRng, coreOutcome }, index) => {
|
||||
try {
|
||||
assertTraceParity(
|
||||
coreEvents,
|
||||
runReferenceTrace(workspaceRoot!, JSON.stringify(base)),
|
||||
coreRng,
|
||||
coreOutcome
|
||||
);
|
||||
assertTraceParity(coreEvents, references[index]!, coreRng, coreOutcome);
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`${entry.kind}/${entry.key}: ${error instanceof Error ? error.message : String(error)}`,
|
||||
@@ -1456,7 +1466,7 @@ describeWithReference('ref ↔ core2026 battle differential', () => {
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('loads every scenario item with the scenario slot', async () => {
|
||||
|
||||
Reference in New Issue
Block a user