Files
core2026/tools/check-conditional-integration-files.mjs
T

179 lines
7.2 KiB
JavaScript

import fs from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const scriptDirectory = path.dirname(fileURLToPath(import.meta.url));
const defaultWorkspaceRoot = path.resolve(scriptDirectory, '..');
const defaultRegistryPath = path.join(scriptDirectory, 'conditional-integration-file-registry.tsv');
export const supportedRequirements = new Set([
'reference_command',
'reference_full_lifecycle',
'reference_instant_diplomacy',
'reference_monthly',
'reference_snapshot',
'saved_trace_pair',
]);
const databaseMarkerPattern = /process\.env\.[A-Z0-9_]+_DATABASE_URL/u;
const redisMarkerPattern = /process\.env\.REDIS_URL/u;
const integrationFilePattern = /^test\/[A-Za-z0-9._/-]+\.integration\.test\.ts$/u;
export const parseConditionalIntegrationFileRegistry = (source) => {
const entries = [];
const seen = new Set();
for (const [index, rawLine] of source.split(/\r?\n/u).entries()) {
const line = rawLine.trim();
if (line === '' || line.startsWith('#')) continue;
const fields = rawLine.split('\t');
if (fields.length !== 2) {
throw new Error(`line ${index + 1} must contain exactly one tab-separated file and requirement`);
}
const [file, requirement] = fields.map((field) => field.trim());
if (!integrationFilePattern.test(file)) {
throw new Error(`line ${index + 1} has an invalid integration test path: ${file}`);
}
if (!supportedRequirements.has(requirement)) {
throw new Error(`line ${index + 1} has an unsupported environment requirement: ${requirement}`);
}
if (seen.has(file)) {
throw new Error(`line ${index + 1} duplicates integration test path: ${file}`);
}
seen.add(file);
entries.push({ file, requirement });
}
return entries;
};
const listIntegrationSources = async (testDirectory) => {
const sources = [];
const visit = async (directory) => {
for (const entry of await fs.readdir(directory, { withFileTypes: true })) {
const absolute = path.join(directory, entry.name);
if (entry.isDirectory()) {
await visit(absolute);
} else if (entry.isFile() && entry.name.endsWith('.integration.test.ts')) {
sources.push({
file: path.relative(path.dirname(testDirectory), absolute).split(path.sep).join('/'),
source: await fs.readFile(absolute, 'utf8'),
});
}
}
};
await visit(testDirectory);
return sources.sort((left, right) => left.file.localeCompare(right.file));
};
const isNonDatabaseConditionalReference = (source) => {
if (!source.includes('describe.skipIf')) return false;
if (databaseMarkerPattern.test(source) || redisMarkerPattern.test(source)) return false;
return (
source.includes('TURN_DIFFERENTIAL_REFERENCE') ||
(source.includes('TURN_REFERENCE_TRACE') && source.includes('TURN_CORE_TRACE'))
);
};
const validateRequirementEvidence = ({ file, requirement, source }) => {
const errors = [];
const requireToken = (token) => {
if (!source.includes(token)) errors.push(`${file}: ${requirement} requires source token ${token}`);
};
if (requirement.startsWith('reference_')) {
requireToken('TURN_DIFFERENTIAL_REFERENCE');
}
switch (requirement) {
case 'reference_command':
requireToken('runReferenceTurnCommandTrace');
break;
case 'reference_full_lifecycle':
requireToken('turn_full_lifecycle_trace.php');
break;
case 'reference_instant_diplomacy':
requireToken('instant_diplomacy_response_trace.php');
break;
case 'reference_monthly':
requireToken('monthly_event_trace.php');
break;
case 'reference_snapshot':
requireToken('readReferenceDatabaseSnapshot');
break;
case 'saved_trace_pair':
requireToken('TURN_REFERENCE_TRACE');
requireToken('TURN_CORE_TRACE');
break;
}
return errors;
};
export const validateConditionalIntegrationFileRegistry = async ({
workspaceRoot = defaultWorkspaceRoot,
registryPath = defaultRegistryPath,
} = {}) => {
const packageRoot = path.join(workspaceRoot, 'tools/integration-tests');
const testDirectory = path.join(packageRoot, 'test');
const registrySource = await fs.readFile(registryPath, 'utf8');
const entries = parseConditionalIntegrationFileRegistry(registrySource);
const sources = await listIntegrationSources(testDirectory);
const sourceByFile = new Map(sources.map((entry) => [entry.file, entry.source]));
const discovered = sources
.filter(({ source }) => isNonDatabaseConditionalReference(source))
.map(({ file }) => file);
const registered = entries.map(({ file }) => file);
const errors = [];
const missing = discovered.filter((file) => !registered.includes(file));
const stale = registered.filter((file) => !discovered.includes(file));
if (missing.length > 0) errors.push(`unregistered non-database conditional suite(s): ${missing.join(', ')}`);
if (stale.length > 0) errors.push(`stale non-database conditional suite(s): ${stale.join(', ')}`);
for (const entry of entries) {
const source = sourceByFile.get(entry.file);
if (source === undefined) continue;
if (databaseMarkerPattern.test(source) || redisMarkerPattern.test(source)) {
errors.push(`${entry.file}: file registry overlaps a database/Redis marker suite`);
}
errors.push(...validateRequirementEvidence({ ...entry, source }));
}
if (errors.length > 0) {
throw new Error(
`invalid conditional integration file registry:\n${errors.map((error) => ` ${error}`).join('\n')}`
);
}
return entries;
};
export const selectEnabledConditionalIntegrationFiles = (entries, environment = process.env) => {
const hasReferenceTrace = Boolean(environment.TURN_REFERENCE_TRACE);
const hasCoreTrace = Boolean(environment.TURN_CORE_TRACE);
if (hasReferenceTrace !== hasCoreTrace) {
throw new Error('TURN_REFERENCE_TRACE and TURN_CORE_TRACE must be provided together');
}
return {
referenceFiles:
environment.TURN_DIFFERENTIAL_REFERENCE === '1'
? entries.filter(({ requirement }) => requirement.startsWith('reference_')).map(({ file }) => file)
: [],
savedTraceFiles:
hasReferenceTrace && hasCoreTrace
? entries.filter(({ requirement }) => requirement === 'saved_trace_pair').map(({ file }) => file)
: [],
};
};
const isMain = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
if (isMain) {
try {
const entries = await validateConditionalIntegrationFileRegistry();
selectEnabledConditionalIntegrationFiles(entries);
process.stdout.write(`conditional integration file registry is valid (${entries.length} files)\n`);
} catch (error) {
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
process.exitCode = 1;
}
}