tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

checklist.ts (5172B)


      1 import * as fs from 'fs';
      2 import * as process from 'process';
      3 
      4 import { DefaultTestFileLoader } from '../internal/file_loader.js';
      5 import { Ordering, compareQueries } from '../internal/query/compare.js';
      6 import { parseQuery } from '../internal/query/parseQuery.js';
      7 import { TestQuery, TestQueryMultiFile } from '../internal/query/query.js';
      8 import { loadTreeForQuery, TestTree } from '../internal/tree.js';
      9 import { StacklessError } from '../internal/util.js';
     10 import { assert } from '../util/util.js';
     11 
     12 function usage(rc: number): void {
     13  console.error('Usage:');
     14  console.error('  tools/checklist FILE');
     15  console.error('  tools/checklist my/list.txt');
     16  process.exit(rc);
     17 }
     18 
     19 if (process.argv.length === 2) usage(0);
     20 if (process.argv.length !== 3) usage(1);
     21 
     22 type QueryInSuite = { readonly query: TestQuery; readonly done: boolean };
     23 type QueriesInSuite = QueryInSuite[];
     24 type QueriesBySuite = Map<string, QueriesInSuite>;
     25 async function loadQueryListFromTextFile(filename: string): Promise<QueriesBySuite> {
     26  const lines = (await fs.promises.readFile(filename, 'utf8')).split(/\r?\n/);
     27  const allQueries = lines
     28    .filter(l => l)
     29    .map(l => {
     30      const [doneStr, q] = l.split(/\s+/);
     31      assert(doneStr === 'DONE' || doneStr === 'TODO', 'first column must be DONE or TODO');
     32      return { query: parseQuery(q), done: doneStr === 'DONE' } as const;
     33    });
     34 
     35  const queriesBySuite: QueriesBySuite = new Map();
     36  for (const q of allQueries) {
     37    let suiteQueries = queriesBySuite.get(q.query.suite);
     38    if (suiteQueries === undefined) {
     39      suiteQueries = [];
     40      queriesBySuite.set(q.query.suite, suiteQueries);
     41    }
     42 
     43    suiteQueries.push(q);
     44  }
     45 
     46  return queriesBySuite;
     47 }
     48 
     49 function checkForOverlappingQueries(queries: QueriesInSuite): void {
     50  for (let i1 = 0; i1 < queries.length; ++i1) {
     51    for (let i2 = i1 + 1; i2 < queries.length; ++i2) {
     52      const q1 = queries[i1].query;
     53      const q2 = queries[i2].query;
     54      if (compareQueries(q1, q2) !== Ordering.Unordered) {
     55        console.log(`    FYI, the following checklist items overlap:\n      ${q1}\n      ${q2}`);
     56      }
     57    }
     58  }
     59 }
     60 
     61 function checkForUnmatchedSubtreesAndDoneness(
     62  tree: TestTree,
     63  matchQueries: QueriesInSuite
     64 ): number {
     65  let subtreeCount = 0;
     66  const unmatchedSubtrees: TestQuery[] = [];
     67  const overbroadMatches: [TestQuery, TestQuery][] = [];
     68  const donenessMismatches: QueryInSuite[] = [];
     69  const alwaysExpandThroughLevel = 1; // expand to, at minimum, every file.
     70  for (const subtree of tree.iterateCollapsedNodes({
     71    includeIntermediateNodes: true,
     72    includeEmptySubtrees: true,
     73    alwaysExpandThroughLevel,
     74  })) {
     75    subtreeCount++;
     76    const subtreeDone = !subtree.subtreeCounts?.nodesWithTODO;
     77 
     78    let subtreeMatched = false;
     79    for (const q of matchQueries) {
     80      const comparison = compareQueries(q.query, subtree.query);
     81      if (comparison !== Ordering.Unordered) subtreeMatched = true;
     82      if (comparison === Ordering.StrictSubset) continue;
     83      if (comparison === Ordering.StrictSuperset) overbroadMatches.push([q.query, subtree.query]);
     84      if (comparison === Ordering.Equal && q.done !== subtreeDone) donenessMismatches.push(q);
     85    }
     86    if (!subtreeMatched) unmatchedSubtrees.push(subtree.query);
     87  }
     88 
     89  if (overbroadMatches.length) {
     90    // (note, this doesn't show ALL multi-test queries - just ones that actually match any .spec.ts)
     91    console.log(`  FYI, the following checklist items were broader than one file:`);
     92    for (const [q, collapsedSubtree] of overbroadMatches) {
     93      console.log(`    ${q}  >  ${collapsedSubtree}`);
     94    }
     95  }
     96 
     97  if (unmatchedSubtrees.length) {
     98    throw new StacklessError(`Found unmatched tests:\n  ${unmatchedSubtrees.join('\n  ')}`);
     99  }
    100 
    101  if (donenessMismatches.length) {
    102    throw new StacklessError(
    103      'Found done/todo mismatches:\n  ' +
    104        donenessMismatches
    105          .map(q => `marked ${q.done ? 'DONE, but is TODO' : 'TODO, but is DONE'}: ${q.query}`)
    106          .join('\n  ')
    107    );
    108  }
    109 
    110  return subtreeCount;
    111 }
    112 
    113 (async () => {
    114  console.log('Loading queries...');
    115  const queriesBySuite = await loadQueryListFromTextFile(process.argv[2]);
    116  console.log('  Found suites: ' + Array.from(queriesBySuite.keys()).join(' '));
    117 
    118  const loader = new DefaultTestFileLoader();
    119  for (const [suite, queriesInSuite] of queriesBySuite.entries()) {
    120    console.log(`Suite "${suite}":`);
    121    console.log(`  Checking overlaps between ${queriesInSuite.length} checklist items...`);
    122    checkForOverlappingQueries(queriesInSuite);
    123    const suiteQuery = new TestQueryMultiFile(suite, []);
    124    console.log(`  Loading tree ${suiteQuery}...`);
    125    const tree = await loadTreeForQuery(loader, suiteQuery, {
    126      subqueriesToExpand: queriesInSuite.map(q => q.query),
    127    });
    128    console.log('  Found no invalid queries in the checklist. Checking for unmatched tests...');
    129    const subtreeCount = checkForUnmatchedSubtreesAndDoneness(tree, queriesInSuite);
    130    console.log(`  No unmatched tests or done/todo mismatches among ${subtreeCount} subtrees!`);
    131  }
    132  console.log(`Checklist looks good!`);
    133 })().catch(ex => {
    134  console.log(ex.stack ?? ex.toString());
    135  process.exit(1);
    136 });