Source code
Revision control
Copy as Markdown
Other Tools
import * as fs from 'fs';
import * as process from 'process';
import { DefaultTestFileLoader } from '../internal/file_loader.js';
import { Ordering, compareQueries } from '../internal/query/compare.js';
import { parseQuery } from '../internal/query/parseQuery.js';
import { TestQuery, TestQueryMultiFile } from '../internal/query/query.js';
import { loadTreeForQuery, TestTree } from '../internal/tree.js';
import { StacklessError } from '../internal/util.js';
import { assert } from '../util/util.js';
function usage(rc: number): void {
console.error('Usage:');
console.error(' tools/checklist FILE');
console.error(' tools/checklist my/list.txt');
process.exit(rc);
}
if (process.argv.length === 2) usage(0);
if (process.argv.length !== 3) usage(1);
type QueryInSuite = { readonly query: TestQuery; readonly done: boolean };
type QueriesInSuite = QueryInSuite[];
type QueriesBySuite = Map<string, QueriesInSuite>;
async function loadQueryListFromTextFile(filename: string): Promise<QueriesBySuite> {
const lines = (await fs.promises.readFile(filename, 'utf8')).split(/\r?\n/);
const allQueries = lines
.filter(l => l)
.map(l => {
const [doneStr, q] = l.split(/\s+/);
assert(doneStr === 'DONE' || doneStr === 'TODO', 'first column must be DONE or TODO');
return { query: parseQuery(q), done: doneStr === 'DONE' } as const;
});
const queriesBySuite: QueriesBySuite = new Map();
for (const q of allQueries) {
let suiteQueries = queriesBySuite.get(q.query.suite);
if (suiteQueries === undefined) {
suiteQueries = [];
queriesBySuite.set(q.query.suite, suiteQueries);
}
suiteQueries.push(q);
}
return queriesBySuite;
}
function checkForOverlappingQueries(queries: QueriesInSuite): void {
for (let i1 = 0; i1 < queries.length; ++i1) {
for (let i2 = i1 + 1; i2 < queries.length; ++i2) {
const q1 = queries[i1].query;
const q2 = queries[i2].query;
if (compareQueries(q1, q2) !== Ordering.Unordered) {
console.log(` FYI, the following checklist items overlap:\n ${q1}\n ${q2}`);
}
}
}
}
function checkForUnmatchedSubtreesAndDoneness(
tree: TestTree,
matchQueries: QueriesInSuite
): number {
let subtreeCount = 0;
const unmatchedSubtrees: TestQuery[] = [];
const overbroadMatches: [TestQuery, TestQuery][] = [];
const donenessMismatches: QueryInSuite[] = [];
const alwaysExpandThroughLevel = 1; // expand to, at minimum, every file.
for (const subtree of tree.iterateCollapsedNodes({
includeIntermediateNodes: true,
includeEmptySubtrees: true,
alwaysExpandThroughLevel,
})) {
subtreeCount++;
const subtreeDone = !subtree.subtreeCounts?.nodesWithTODO;
let subtreeMatched = false;
for (const q of matchQueries) {
const comparison = compareQueries(q.query, subtree.query);
if (comparison !== Ordering.Unordered) subtreeMatched = true;
if (comparison === Ordering.StrictSubset) continue;
if (comparison === Ordering.StrictSuperset) overbroadMatches.push([q.query, subtree.query]);
if (comparison === Ordering.Equal && q.done !== subtreeDone) donenessMismatches.push(q);
}
if (!subtreeMatched) unmatchedSubtrees.push(subtree.query);
}
if (overbroadMatches.length) {
// (note, this doesn't show ALL multi-test queries - just ones that actually match any .spec.ts)
console.log(` FYI, the following checklist items were broader than one file:`);
for (const [q, collapsedSubtree] of overbroadMatches) {
console.log(` ${q} > ${collapsedSubtree}`);
}
}
if (unmatchedSubtrees.length) {
throw new StacklessError(`Found unmatched tests:\n ${unmatchedSubtrees.join('\n ')}`);
}
if (donenessMismatches.length) {
throw new StacklessError(
'Found done/todo mismatches:\n ' +
donenessMismatches
.map(q => `marked ${q.done ? 'DONE, but is TODO' : 'TODO, but is DONE'}: ${q.query}`)
.join('\n ')
);
}
return subtreeCount;
}
(async () => {
console.log('Loading queries...');
const queriesBySuite = await loadQueryListFromTextFile(process.argv[2]);
console.log(' Found suites: ' + Array.from(queriesBySuite.keys()).join(' '));
const loader = new DefaultTestFileLoader();
for (const [suite, queriesInSuite] of queriesBySuite.entries()) {
console.log(`Suite "${suite}":`);
console.log(` Checking overlaps between ${queriesInSuite.length} checklist items...`);
checkForOverlappingQueries(queriesInSuite);
const suiteQuery = new TestQueryMultiFile(suite, []);
console.log(` Loading tree ${suiteQuery}...`);
const tree = await loadTreeForQuery(loader, suiteQuery, {
subqueriesToExpand: queriesInSuite.map(q => q.query),
});
console.log(' Found no invalid queries in the checklist. Checking for unmatched tests...');
const subtreeCount = checkForUnmatchedSubtreesAndDoneness(tree, queriesInSuite);
console.log(` No unmatched tests or done/todo mismatches among ${subtreeCount} subtrees!`);
}
console.log(`Checklist looks good!`);
})().catch(ex => {
console.log(ex.stack ?? ex.toString());
process.exit(1);
});