통합 테스트를 실행 권위별로 분리한다

This commit is contained in:
2026-09-04 06:13:31 +00:00
parent 01fdb51b6e
commit 07897d9765
9 changed files with 193 additions and 20 deletions
+3 -1
View File
@@ -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": {
+89
View File
@@ -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);
+30
View File
@@ -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
1 # Test file Lane
2 test/auctionFlow.test.ts lifecycle
3 test/battleDifferential.test.ts reference
4 test/coreCommandTraceClock.test.ts core
5 test/initialization.test.ts lifecycle
6 test/instantDiplomacyCoreReference.integration.test.ts reference
7 test/instantDiplomacyReference.integration.test.ts reference
8 test/liveSortiePersistence.integration.test.ts conditional
9 test/monthlyDisasterCoreReference.integration.test.ts reference
10 test/monthlyNationBettingApiLifecycle.integration.test.ts conditional
11 test/npcPossessionSelectionReference.integration.test.ts conditional
12 test/orchestrator.e2e.test.ts lifecycle
13 test/reservedTurnDaemonApiRace.integration.test.ts conditional
14 test/scenario29NationBettingLifecycle.integration.test.ts conditional
15 test/tournamentLifecycle.test.ts lifecycle
16 test/troopStaticEvent.integration.test.ts conditional
17 test/turnCommandCoreReference.integration.test.ts reference
18 test/turnCommandFullLifecycle.integration.test.ts reference
19 test/turnCommandFullLifecyclePersistence.integration.test.ts conditional
20 test/turnCommandGeneralMatrix.integration.test.ts reference
21 test/turnCommandNationMatrix.integration.test.ts reference
22 test/turnCommandReference.integration.test.ts reference
23 test/turnCommandRiskDurabilityMatrix.integration.test.ts conditional
24 test/turnLogProjection.test.ts core
25 test/turnMessageProjection.test.ts core
26 test/turnSnapshotCanonicalCoverage.test.ts core
27 test/turnSnapshotComparator.test.ts core
28 test/turnSnapshotCoreDatabase.integration.test.ts conditional
29 test/turnSnapshotReference.integration.test.ts reference
30 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 () => {
+1
View File
@@ -571,6 +571,7 @@ run_redis_only_tests() {
export PATH
cd "$workspace_root"
node "$script_dir/check-conditional-integration-files.mjs"
node "$workspace_root/tools/integration-tests/run-test-lane.mjs" check
validate_marker_registry
pnpm install --frozen-lockfile