tor-browser

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

loaders_and_trees.spec.ts (26454B)


      1 export const description = `
      2 Tests for queries/filtering, loading, and running.
      3 `;
      4 
      5 import { Fixture } from '../common/framework/fixture.js';
      6 import { makeTestGroup } from '../common/framework/test_group.js';
      7 import { TestFileLoader, SpecFile } from '../common/internal/file_loader.js';
      8 import { Logger } from '../common/internal/logging/logger.js';
      9 import { Status } from '../common/internal/logging/result.js';
     10 import { parseQuery } from '../common/internal/query/parseQuery.js';
     11 import {
     12  TestQuery,
     13  TestQuerySingleCase,
     14  TestQueryMultiCase,
     15  TestQueryMultiTest,
     16  TestQueryMultiFile,
     17  TestQueryWithExpectation,
     18 } from '../common/internal/query/query.js';
     19 import { makeTestGroupForUnitTesting } from '../common/internal/test_group.js';
     20 import { TestSuiteListing, TestSuiteListingEntry } from '../common/internal/test_suite_listing.js';
     21 import { ExpandThroughLevel, TestTreeLeaf } from '../common/internal/tree.js';
     22 import { assert, objectEquals } from '../common/util/util.js';
     23 
     24 import { UnitTest } from './unit_test.js';
     25 
     26 const listingData: { [k: string]: TestSuiteListingEntry[] } = {
     27  suite1: [
     28    { file: [], readme: 'desc 1a' },
     29    { file: ['foo'] },
     30    { file: ['bar'], readme: 'desc 1h' },
     31    { file: ['bar', 'biz'] },
     32    { file: ['bar', 'buzz', 'buzz'] },
     33    { file: ['baz'] },
     34    { file: ['empty'], readme: 'desc 1z' }, // directory with no files
     35  ],
     36  suite2: [{ file: [], readme: 'desc 2a' }, { file: ['foof'] }],
     37 };
     38 
     39 const specsData: { [k: string]: SpecFile } = {
     40  'suite1/foo.spec.js': {
     41    description: 'desc 1b',
     42    g: (() => {
     43      const g = makeTestGroupForUnitTesting(UnitTest);
     44      g.test('hello').fn(() => {});
     45      g.test('bonjour').fn(() => {});
     46      g.test('hola')
     47        .desc('TODO TODO')
     48        .fn(() => {});
     49      return g;
     50    })(),
     51  },
     52  'suite1/bar/biz.spec.js': {
     53    description: 'desc 1f TODO TODO',
     54    g: makeTestGroupForUnitTesting(UnitTest), // file with no tests
     55  },
     56  'suite1/bar/buzz/buzz.spec.js': {
     57    description: 'desc 1d TODO',
     58    g: (() => {
     59      const g = makeTestGroupForUnitTesting(UnitTest);
     60      g.test('zap').fn(() => {});
     61      return g;
     62    })(),
     63  },
     64  'suite1/baz.spec.js': {
     65    description: 'desc 1e',
     66    g: (() => {
     67      const g = makeTestGroupForUnitTesting(UnitTest);
     68      g.test('wye')
     69        .paramsSimple([{}, { x: 1 }])
     70        .fn(() => {});
     71      g.test('zed')
     72        .paramsSimple([
     73          { a: 1, b: 2, _c: 0 },
     74          { b: 3, a: 1, _c: 0 },
     75        ])
     76        .fn(() => {});
     77      g.test('batched')
     78        // creates two cases: one for subcases 1,2 and one for subcase 3
     79        .paramsSubcasesOnly(u => u.combine('x', [1, 2, 3]))
     80        .batch(2)
     81        .fn(() => {});
     82      return g;
     83    })(),
     84  },
     85  'suite2/foof.spec.js': {
     86    description: 'desc 2b',
     87    g: (() => {
     88      const g = makeTestGroupForUnitTesting(UnitTest);
     89      g.test('blah').fn(t => {
     90        t.debug('OK');
     91      });
     92      g.test('bleh')
     93        .paramsSimple([{ a: 1 }])
     94        .fn(t => {
     95          t.debug('OK');
     96          t.debug('OK');
     97        });
     98      g.test('bluh,a').fn(t => {
     99        t.fail('goodbye');
    100      });
    101      return g;
    102    })(),
    103  },
    104 };
    105 
    106 class FakeTestFileLoader extends TestFileLoader {
    107  listing(suite: string): Promise<TestSuiteListing> {
    108    return Promise.resolve(listingData[suite]);
    109  }
    110 
    111  import(path: string): Promise<SpecFile> {
    112    assert(path in specsData, '[test] mock file ' + path + ' does not exist');
    113    return Promise.resolve(specsData[path]);
    114  }
    115 }
    116 
    117 class LoadingTest extends UnitTest {
    118  loader: FakeTestFileLoader = new FakeTestFileLoader();
    119  events: (string | null)[] = [];
    120  private isListenersAdded = false;
    121 
    122  collectEvents(): void {
    123    this.events = [];
    124    if (!this.isListenersAdded) {
    125      this.isListenersAdded = true;
    126      this.loader.addEventListener('import', ev => this.events.push(ev.data.url));
    127      this.loader.addEventListener('finish', _ev => this.events.push(null));
    128    }
    129  }
    130 
    131  async load(query: string): Promise<TestTreeLeaf[]> {
    132    return Array.from(await this.loader.loadCases(parseQuery(query)));
    133  }
    134 
    135  async loadNames(query: string): Promise<string[]> {
    136    return (await this.load(query)).map(c => c.query.toString());
    137  }
    138 }
    139 
    140 export const g = makeTestGroup(LoadingTest);
    141 
    142 g.test('suite').fn(t => {
    143  t.shouldReject('Error', t.load('suite1'));
    144  t.shouldReject('Error', t.load('suite1:'));
    145 });
    146 
    147 g.test('group').fn(async t => {
    148  t.collectEvents();
    149  t.expect((await t.load('suite1:*')).length === 10);
    150  t.expect(
    151    objectEquals(t.events, [
    152      'suite1/foo.spec.js',
    153      'suite1/bar/biz.spec.js',
    154      'suite1/bar/buzz/buzz.spec.js',
    155      'suite1/baz.spec.js',
    156      null,
    157    ])
    158  );
    159 
    160  t.collectEvents();
    161  t.expect((await t.load('suite1:foo,*')).length === 3); // x:foo,* matches x:foo:
    162  t.expect(objectEquals(t.events, ['suite1/foo.spec.js', null]));
    163 
    164  t.collectEvents();
    165  t.expect((await t.load('suite1:bar,*')).length === 1);
    166  t.expect(
    167    objectEquals(t.events, ['suite1/bar/biz.spec.js', 'suite1/bar/buzz/buzz.spec.js', null])
    168  );
    169 
    170  t.collectEvents();
    171  t.expect((await t.load('suite1:bar,buzz,buzz,*')).length === 1);
    172  t.expect(objectEquals(t.events, ['suite1/bar/buzz/buzz.spec.js', null]));
    173 
    174  t.shouldReject('Error', t.load('suite1:f*'));
    175 
    176  {
    177    const s = new TestQueryMultiFile('suite1', ['bar', 'buzz']).toString();
    178    t.collectEvents();
    179    t.expect((await t.load(s)).length === 1);
    180    t.expect(objectEquals(t.events, ['suite1/bar/buzz/buzz.spec.js', null]));
    181  }
    182 });
    183 
    184 g.test('test').fn(async t => {
    185  t.shouldReject('Error', t.load('suite1::'));
    186  t.shouldReject('Error', t.load('suite1:bar:'));
    187  t.shouldReject('Error', t.load('suite1:bar,:'));
    188 
    189  t.shouldReject('Error', t.load('suite1::*'));
    190  t.shouldReject('Error', t.load('suite1:bar,:*'));
    191  t.shouldReject('Error', t.load('suite1:bar:*'));
    192 
    193  t.expect((await t.load('suite1:foo:*')).length === 3);
    194  t.expect((await t.load('suite1:bar,buzz,buzz:*')).length === 1);
    195  t.expect((await t.load('suite1:baz:*')).length === 6);
    196 
    197  t.expect((await t.load('suite2:foof:bluh,*')).length === 1);
    198  t.expect((await t.load('suite2:foof:bluh,a,*')).length === 1);
    199 
    200  {
    201    const s = new TestQueryMultiTest('suite2', ['foof'], ['bluh']).toString();
    202    t.expect((await t.load(s)).length === 1);
    203  }
    204 });
    205 
    206 g.test('case').fn(async t => {
    207  t.shouldReject('Error', t.load('suite1:foo::'));
    208  t.shouldReject('Error', t.load('suite1:bar:zed,:'));
    209 
    210  t.shouldReject('Error', t.load('suite1:foo:h*'));
    211 
    212  t.shouldReject('Error', t.load('suite1:foo::*'));
    213  t.shouldReject('Error', t.load('suite1:baz::*'));
    214  t.shouldReject('Error', t.load('suite1:baz:zed,:*'));
    215 
    216  t.shouldReject('Error', t.load('suite1:baz:zed:'));
    217  t.shouldReject('Error', t.load('suite1:baz:zed:a=1'));
    218  t.shouldReject('Error', t.load('suite1:baz:zed:a=1;b=2*'));
    219  t.shouldReject('Error', t.load('suite1:baz:zed:a=1;b=2;'));
    220  t.shouldReject('SyntaxError', t.load('suite1:baz:zed:a=1;b=2,')); // tries to parse '2,' as JSON
    221  t.shouldReject('Error', t.load('suite1:baz:zed:a=1,b=2')); // '=' not allowed in value '1,b=2'
    222  t.shouldReject('Error', t.load('suite1:baz:zed:b=2*'));
    223  t.shouldReject('Error', t.load('suite1:baz:zed:b=2;a=1;_c=0'));
    224  t.shouldReject('Error', t.load('suite1:baz:zed:a=1,*'));
    225 
    226  t.expect((await t.load('suite1:baz:zed:*')).length === 2);
    227  t.expect((await t.load('suite1:baz:zed:a=1;*')).length === 2);
    228  t.expect((await t.load('suite1:baz:zed:a=1;b=2')).length === 1);
    229  t.expect((await t.load('suite1:baz:zed:a=1;b=2;*')).length === 1);
    230  t.expect((await t.load('suite1:baz:zed:b=2;*')).length === 1);
    231  t.expect((await t.load('suite1:baz:zed:b=2;a=1')).length === 1);
    232  t.expect((await t.load('suite1:baz:zed:b=2;a=1;*')).length === 1);
    233  t.expect((await t.load('suite1:baz:zed:b=3;a=1')).length === 1);
    234  t.expect((await t.load('suite1:baz:zed:a=1;b=3')).length === 1);
    235  t.expect((await t.load('suite1:foo:hello:')).length === 1);
    236 
    237  {
    238    const s = new TestQueryMultiCase('suite1', ['baz'], ['zed'], { a: 1, b: 2 }).toString();
    239    t.expect((await t.load(s)).length === 1);
    240  }
    241  {
    242    const s = new TestQuerySingleCase('suite1', ['baz'], ['zed'], { a: 1, b: 2 }).toString();
    243    t.expect((await t.load(s)).length === 1);
    244  }
    245 });
    246 
    247 g.test('batching').fn(async t => {
    248  t.expect((await t.load('suite1:baz:batched,*')).length === 2);
    249  t.expect((await t.load('suite1:baz:batched:*')).length === 2);
    250  t.expect((await t.load('suite1:baz:batched:batch__=1;*')).length === 1);
    251  t.expect((await t.load('suite1:baz:batched:batch__=1')).length === 1);
    252 });
    253 
    254 async function runTestcase(
    255  t: Fixture,
    256  log: Logger,
    257  testcases: TestTreeLeaf[],
    258  i: number,
    259  query: TestQuery,
    260  expectations: TestQueryWithExpectation[],
    261  status: Status,
    262  logs: (s: string[]) => boolean
    263 ) {
    264  t.expect(objectEquals(testcases[i].query, query));
    265  const name = testcases[i].query.toString();
    266  const [rec, res] = log.record(name);
    267  await testcases[i].run(rec, expectations);
    268 
    269  t.expect(log.results.get(name) === res);
    270  t.expect(res.status === status);
    271  t.expect(res.timems >= 0);
    272  assert(res.logs !== undefined); // only undefined while pending
    273  t.expect(logs(res.logs.map(l => JSON.stringify(l))));
    274 }
    275 
    276 g.test('end2end').fn(async t => {
    277  const l = await t.load('suite2:foof:*');
    278  assert(l.length === 3, 'listing length');
    279 
    280  const log = new Logger({ overrideDebugMode: true });
    281 
    282  await runTestcase(
    283    t,
    284    log,
    285    l,
    286    0,
    287    new TestQuerySingleCase('suite2', ['foof'], ['blah'], {}),
    288    [],
    289    'pass',
    290    logs => objectEquals(logs, ['"DEBUG: OK"'])
    291  );
    292  await runTestcase(
    293    t,
    294    log,
    295    l,
    296    1,
    297    new TestQuerySingleCase('suite2', ['foof'], ['bleh'], { a: 1 }),
    298    [],
    299    'pass',
    300    logs => objectEquals(logs, ['"DEBUG: OK"', '"DEBUG: OK"'])
    301  );
    302  await runTestcase(
    303    t,
    304    log,
    305    l,
    306    2,
    307    new TestQuerySingleCase('suite2', ['foof'], ['bluh', 'a'], {}),
    308    [],
    309    'fail',
    310    logs =>
    311      logs.length === 1 &&
    312      logs[0].startsWith('"EXPECTATION FAILED: goodbye\\n') &&
    313      logs[0].indexOf('loaders_and_trees.spec.') !== -1
    314  );
    315 });
    316 
    317 g.test('expectations,single_case').fn(async t => {
    318  const log = new Logger({ overrideDebugMode: true });
    319  const zedCases = await t.load('suite1:baz:zed:*');
    320 
    321  // Single-case. Covers one case.
    322  const zedExpectationsSkipA1B2 = [
    323    {
    324      query: new TestQuerySingleCase('suite1', ['baz'], ['zed'], { a: 1, b: 2 }),
    325      expectation: 'skip' as const,
    326    },
    327  ];
    328 
    329  await runTestcase(
    330    t,
    331    log,
    332    zedCases,
    333    0,
    334    new TestQuerySingleCase('suite1', ['baz'], ['zed'], { a: 1, b: 2 }),
    335    zedExpectationsSkipA1B2,
    336    'skip',
    337    logs => logs.length === 1 && logs[0].startsWith('"SKIP: Skipped by expectations"')
    338  );
    339 
    340  await runTestcase(
    341    t,
    342    log,
    343    zedCases,
    344    1,
    345    new TestQuerySingleCase('suite1', ['baz'], ['zed'], { a: 1, b: 3 }),
    346    zedExpectationsSkipA1B2,
    347    'pass',
    348    logs => logs.length === 0
    349  );
    350 });
    351 
    352 g.test('expectations,single_case,none').fn(async t => {
    353  const log = new Logger({ overrideDebugMode: true });
    354  const zedCases = await t.load('suite1:baz:zed:*');
    355  // Single-case. Doesn't cover any cases.
    356  const zedExpectationsSkipA1B0 = [
    357    {
    358      query: new TestQuerySingleCase('suite1', ['baz'], ['zed'], { a: 1, b: 0 }),
    359      expectation: 'skip' as const,
    360    },
    361  ];
    362 
    363  await runTestcase(
    364    t,
    365    log,
    366    zedCases,
    367    0,
    368    new TestQuerySingleCase('suite1', ['baz'], ['zed'], { a: 1, b: 2 }),
    369    zedExpectationsSkipA1B0,
    370    'pass',
    371    logs => logs.length === 0
    372  );
    373 
    374  await runTestcase(
    375    t,
    376    log,
    377    zedCases,
    378    1,
    379    new TestQuerySingleCase('suite1', ['baz'], ['zed'], { a: 1, b: 3 }),
    380    zedExpectationsSkipA1B0,
    381    'pass',
    382    logs => logs.length === 0
    383  );
    384 });
    385 
    386 g.test('expectations,multi_case').fn(async t => {
    387  const log = new Logger({ overrideDebugMode: true });
    388  const zedCases = await t.load('suite1:baz:zed:*');
    389  // Multi-case, not all cases covered.
    390  const zedExpectationsSkipB3 = [
    391    {
    392      query: new TestQueryMultiCase('suite1', ['baz'], ['zed'], { b: 3 }),
    393      expectation: 'skip' as const,
    394    },
    395  ];
    396 
    397  await runTestcase(
    398    t,
    399    log,
    400    zedCases,
    401    0,
    402    new TestQuerySingleCase('suite1', ['baz'], ['zed'], { a: 1, b: 2 }),
    403    zedExpectationsSkipB3,
    404    'pass',
    405    logs => logs.length === 0
    406  );
    407 
    408  await runTestcase(
    409    t,
    410    log,
    411    zedCases,
    412    1,
    413    new TestQuerySingleCase('suite1', ['baz'], ['zed'], { a: 1, b: 3 }),
    414    zedExpectationsSkipB3,
    415    'skip',
    416    logs => logs.length === 1 && logs[0].startsWith('"SKIP: Skipped by expectations"')
    417  );
    418 });
    419 
    420 g.test('expectations,multi_case_all').fn(async t => {
    421  const log = new Logger({ overrideDebugMode: true });
    422  const zedCases = await t.load('suite1:baz:zed:*');
    423  // Multi-case, all cases covered.
    424  const zedExpectationsSkipA1 = [
    425    {
    426      query: new TestQueryMultiCase('suite1', ['baz'], ['zed'], { a: 1 }),
    427      expectation: 'skip' as const,
    428    },
    429  ];
    430 
    431  await runTestcase(
    432    t,
    433    log,
    434    zedCases,
    435    0,
    436    new TestQuerySingleCase('suite1', ['baz'], ['zed'], { a: 1, b: 2 }),
    437    zedExpectationsSkipA1,
    438    'skip',
    439    logs => logs.length === 1 && logs[0].startsWith('"SKIP: Skipped by expectations"')
    440  );
    441 
    442  await runTestcase(
    443    t,
    444    log,
    445    zedCases,
    446    1,
    447    new TestQuerySingleCase('suite1', ['baz'], ['zed'], { a: 1, b: 3 }),
    448    zedExpectationsSkipA1,
    449    'skip',
    450    logs => logs.length === 1 && logs[0].startsWith('"SKIP: Skipped by expectations"')
    451  );
    452 });
    453 
    454 g.test('expectations,multi_case_none').fn(async t => {
    455  const log = new Logger({ overrideDebugMode: true });
    456  const zedCases = await t.load('suite1:baz:zed:*');
    457  // Multi-case, no params, all cases covered.
    458  const zedExpectationsSkipZed = [
    459    {
    460      query: new TestQueryMultiCase('suite1', ['baz'], ['zed'], {}),
    461      expectation: 'skip' as const,
    462    },
    463  ];
    464 
    465  await runTestcase(
    466    t,
    467    log,
    468    zedCases,
    469    0,
    470    new TestQuerySingleCase('suite1', ['baz'], ['zed'], { a: 1, b: 2 }),
    471    zedExpectationsSkipZed,
    472    'skip',
    473    logs => logs.length === 1 && logs[0].startsWith('"SKIP: Skipped by expectations"')
    474  );
    475 
    476  await runTestcase(
    477    t,
    478    log,
    479    zedCases,
    480    1,
    481    new TestQuerySingleCase('suite1', ['baz'], ['zed'], { a: 1, b: 3 }),
    482    zedExpectationsSkipZed,
    483    'skip',
    484    logs => logs.length === 1 && logs[0].startsWith('"SKIP: Skipped by expectations"')
    485  );
    486 });
    487 
    488 g.test('expectations,multi_test').fn(async t => {
    489  const log = new Logger({ overrideDebugMode: true });
    490  const suite1Cases = await t.load('suite1:*');
    491 
    492  // Multi-test, all cases covered.
    493  const expectationsSkipAllInBaz = [
    494    {
    495      query: new TestQueryMultiTest('suite1', ['baz'], []),
    496      expectation: 'skip' as const,
    497    },
    498  ];
    499 
    500  await runTestcase(
    501    t,
    502    log,
    503    suite1Cases,
    504    4,
    505    new TestQuerySingleCase('suite1', ['baz'], ['wye'], {}),
    506    expectationsSkipAllInBaz,
    507    'skip',
    508    logs => logs.length === 1 && logs[0].startsWith('"SKIP: Skipped by expectations"')
    509  );
    510 
    511  await runTestcase(
    512    t,
    513    log,
    514    suite1Cases,
    515    6,
    516    new TestQuerySingleCase('suite1', ['baz'], ['zed'], { a: 1, b: 2 }),
    517    expectationsSkipAllInBaz,
    518    'skip',
    519    logs => logs.length === 1 && logs[0].startsWith('"SKIP: Skipped by expectations"')
    520  );
    521 });
    522 
    523 g.test('expectations,multi_test,none').fn(async t => {
    524  const log = new Logger({ overrideDebugMode: true });
    525  const suite1Cases = await t.load('suite1:*');
    526 
    527  // Multi-test, no cases covered.
    528  const expectationsSkipAllInFoo = [
    529    {
    530      query: new TestQueryMultiTest('suite1', ['foo'], []),
    531      expectation: 'skip' as const,
    532    },
    533  ];
    534 
    535  await runTestcase(
    536    t,
    537    log,
    538    suite1Cases,
    539    4,
    540    new TestQuerySingleCase('suite1', ['baz'], ['wye'], {}),
    541    expectationsSkipAllInFoo,
    542    'pass',
    543    logs => logs.length === 0
    544  );
    545 
    546  await runTestcase(
    547    t,
    548    log,
    549    suite1Cases,
    550    6,
    551    new TestQuerySingleCase('suite1', ['baz'], ['zed'], { a: 1, b: 2 }),
    552    expectationsSkipAllInFoo,
    553    'pass',
    554    logs => logs.length === 0
    555  );
    556 });
    557 
    558 g.test('expectations,multi_file').fn(async t => {
    559  const log = new Logger({ overrideDebugMode: true });
    560  const suite1Cases = await t.load('suite1:*');
    561 
    562  // Multi-file
    563  const expectationsSkipAll = [
    564    {
    565      query: new TestQueryMultiFile('suite1', []),
    566      expectation: 'skip' as const,
    567    },
    568  ];
    569 
    570  await runTestcase(
    571    t,
    572    log,
    573    suite1Cases,
    574    0,
    575    new TestQuerySingleCase('suite1', ['foo'], ['hello'], {}),
    576    expectationsSkipAll,
    577    'skip',
    578    logs => logs.length === 1 && logs[0].startsWith('"SKIP: Skipped by expectations"')
    579  );
    580 
    581  await runTestcase(
    582    t,
    583    log,
    584    suite1Cases,
    585    3,
    586    new TestQuerySingleCase('suite1', ['bar', 'buzz', 'buzz'], ['zap'], {}),
    587    expectationsSkipAll,
    588    'skip',
    589    logs => logs.length === 1 && logs[0].startsWith('"SKIP: Skipped by expectations"')
    590  );
    591 });
    592 
    593 g.test('expectations,catches_failure').fn(async t => {
    594  const log = new Logger({ overrideDebugMode: true });
    595  const suite2Cases = await t.load('suite2:*');
    596 
    597  // Catches failure
    598  const expectedFailures = [
    599    {
    600      query: new TestQueryMultiCase('suite2', ['foof'], ['bluh', 'a'], {}),
    601      expectation: 'fail' as const,
    602    },
    603  ];
    604 
    605  await runTestcase(
    606    t,
    607    log,
    608    suite2Cases,
    609    0,
    610    new TestQuerySingleCase('suite2', ['foof'], ['blah'], {}),
    611    expectedFailures,
    612    'pass',
    613    logs => objectEquals(logs, ['"DEBUG: OK"'])
    614  );
    615 
    616  // Status is passed, but failure is logged.
    617  await runTestcase(
    618    t,
    619    log,
    620    suite2Cases,
    621    2,
    622    new TestQuerySingleCase('suite2', ['foof'], ['bluh', 'a'], {}),
    623    expectedFailures,
    624    'pass',
    625    logs => logs.length === 1 && logs[0].startsWith('"EXPECTATION FAILED: goodbye\\n')
    626  );
    627 });
    628 
    629 g.test('expectations,skip_dominates_failure').fn(async t => {
    630  const log = new Logger({ overrideDebugMode: true });
    631  const suite2Cases = await t.load('suite2:*');
    632 
    633  const expectedFailures = [
    634    {
    635      query: new TestQueryMultiCase('suite2', ['foof'], ['bluh', 'a'], {}),
    636      expectation: 'fail' as const,
    637    },
    638    {
    639      query: new TestQueryMultiCase('suite2', ['foof'], ['bluh', 'a'], {}),
    640      expectation: 'skip' as const,
    641    },
    642  ];
    643 
    644  await runTestcase(
    645    t,
    646    log,
    647    suite2Cases,
    648    2,
    649    new TestQuerySingleCase('suite2', ['foof'], ['bluh', 'a'], {}),
    650    expectedFailures,
    651    'skip',
    652    logs => logs.length === 1 && logs[0].startsWith('"SKIP: Skipped by expectations"')
    653  );
    654 });
    655 
    656 g.test('expectations,skip_inside_failure').fn(async t => {
    657  const log = new Logger({ overrideDebugMode: true });
    658  const suite2Cases = await t.load('suite2:*');
    659 
    660  const expectedFailures = [
    661    {
    662      query: new TestQueryMultiFile('suite2', []),
    663      expectation: 'fail' as const,
    664    },
    665    {
    666      query: new TestQueryMultiCase('suite2', ['foof'], ['blah'], {}),
    667      expectation: 'skip' as const,
    668    },
    669  ];
    670 
    671  await runTestcase(
    672    t,
    673    log,
    674    suite2Cases,
    675    0,
    676    new TestQuerySingleCase('suite2', ['foof'], ['blah'], {}),
    677    expectedFailures,
    678    'skip',
    679    logs => logs.length === 1 && logs[0].startsWith('"SKIP: Skipped by expectations"')
    680  );
    681 
    682  await runTestcase(
    683    t,
    684    log,
    685    suite2Cases,
    686    2,
    687    new TestQuerySingleCase('suite2', ['foof'], ['bluh', 'a'], {}),
    688    expectedFailures,
    689    'pass',
    690    logs => logs.length === 1 && logs[0].startsWith('"EXPECTATION FAILED: goodbye\\n')
    691  );
    692 });
    693 
    694 async function testIterateCollapsed(
    695  t: LoadingTest,
    696  alwaysExpandThroughLevel: ExpandThroughLevel,
    697  expectations: string[],
    698  expectedResult: 'throws' | string[] | [string, number | undefined][],
    699  includeEmptySubtrees = false
    700 ) {
    701  t.debug(`expandThrough=${alwaysExpandThroughLevel} expectations=${expectations}`);
    702  const treePromise = t.loader.loadTree(new TestQueryMultiFile('suite1', []), {
    703    subqueriesToExpand: expectations,
    704  });
    705  if (expectedResult === 'throws') {
    706    t.shouldReject('Error', treePromise, {
    707      // Some errors here use StacklessError to print nicer command line outputs.
    708      allowMissingStack: true,
    709    });
    710    return;
    711  }
    712  const tree = await treePromise;
    713  const actualIter = tree.iterateCollapsedNodes({
    714    includeEmptySubtrees,
    715    alwaysExpandThroughLevel,
    716  });
    717  const testingTODOs = expectedResult.length > 0 && expectedResult[0] instanceof Array;
    718  const actual = Array.from(actualIter, ({ query, subtreeCounts }) =>
    719    testingTODOs ? [query.toString(), subtreeCounts?.nodesWithTODO] : query.toString()
    720  );
    721  if (!objectEquals(actual, expectedResult)) {
    722    t.fail(
    723      `iterateCollapsed failed:
    724  got ${JSON.stringify(actual)}
    725  exp ${JSON.stringify(expectedResult)}
    726 ${tree.toString()}`
    727    );
    728  }
    729 }
    730 
    731 g.test('print').fn(async t => {
    732  const tree = await t.loader.loadTree(new TestQueryMultiFile('suite1', []));
    733  tree.toString();
    734 });
    735 
    736 g.test('iterateCollapsed').fn(async t => {
    737  await testIterateCollapsed(
    738    t,
    739    1,
    740    [],
    741    [
    742      ['suite1:foo:*', 1], // to-do propagated up from foo:hola
    743      ['suite1:bar,buzz,buzz:*', 1], // to-do in file description
    744      ['suite1:baz:*', 0],
    745    ]
    746  );
    747  await testIterateCollapsed(
    748    t,
    749    2,
    750    [],
    751    [
    752      ['suite1:foo:hello:*', 0],
    753      ['suite1:foo:bonjour:*', 0],
    754      ['suite1:foo:hola:*', 1], // to-do in test description
    755      ['suite1:bar,buzz,buzz:zap:*', 0],
    756      ['suite1:baz:wye:*', 0],
    757      ['suite1:baz:zed:*', 0],
    758      ['suite1:baz:batched:*', 0],
    759    ]
    760  );
    761  await testIterateCollapsed(
    762    t,
    763    3,
    764    [],
    765    [
    766      ['suite1:foo:hello:', undefined],
    767      ['suite1:foo:bonjour:', undefined],
    768      ['suite1:foo:hola:', undefined],
    769      ['suite1:bar,buzz,buzz:zap:', undefined],
    770      ['suite1:baz:wye:', undefined],
    771      ['suite1:baz:wye:x=1', undefined],
    772      ['suite1:baz:zed:a=1;b=2', undefined],
    773      ['suite1:baz:zed:b=3;a=1', undefined],
    774      ['suite1:baz:batched:batch__=0', undefined],
    775      ['suite1:baz:batched:batch__=1', undefined],
    776    ]
    777  );
    778 
    779  // Expectations lists that have no effect
    780  await testIterateCollapsed(
    781    t,
    782    1,
    783    ['suite1:foo:*'],
    784    ['suite1:foo:*', 'suite1:bar,buzz,buzz:*', 'suite1:baz:*']
    785  );
    786  await testIterateCollapsed(
    787    t,
    788    1,
    789    ['suite1:bar,buzz,buzz:*'],
    790    ['suite1:foo:*', 'suite1:bar,buzz,buzz:*', 'suite1:baz:*']
    791  );
    792  await testIterateCollapsed(
    793    t,
    794    2,
    795    ['suite1:baz:wye:*'],
    796    [
    797      'suite1:foo:hello:*',
    798      'suite1:foo:bonjour:*',
    799      'suite1:foo:hola:*',
    800      'suite1:bar,buzz,buzz:zap:*',
    801      'suite1:baz:wye:*',
    802      'suite1:baz:zed:*',
    803      'suite1:baz:batched:*',
    804    ]
    805  );
    806  // Test with includeEmptySubtrees=true
    807  await testIterateCollapsed(
    808    t,
    809    1,
    810    [],
    811    [
    812      'suite1:foo:*',
    813      'suite1:bar,biz:*',
    814      'suite1:bar,buzz,buzz:*',
    815      'suite1:baz:*',
    816      'suite1:empty,*',
    817    ],
    818    true
    819  );
    820  await testIterateCollapsed(
    821    t,
    822    2,
    823    [],
    824    [
    825      'suite1:foo:hello:*',
    826      'suite1:foo:bonjour:*',
    827      'suite1:foo:hola:*',
    828      'suite1:bar,biz:*',
    829      'suite1:bar,buzz,buzz:zap:*',
    830      'suite1:baz:wye:*',
    831      'suite1:baz:zed:*',
    832      'suite1:baz:batched:*',
    833      'suite1:empty,*',
    834    ],
    835    true
    836  );
    837 
    838  // Expectations lists that have some effect
    839  await testIterateCollapsed(
    840    t,
    841    1,
    842    ['suite1:baz:wye:*'],
    843    [
    844      'suite1:foo:*',
    845      'suite1:bar,buzz,buzz:*',
    846      'suite1:baz:wye:*',
    847      'suite1:baz:zed,*',
    848      'suite1:baz:batched,*',
    849    ]
    850  );
    851  await testIterateCollapsed(
    852    t,
    853    1,
    854    ['suite1:baz:zed:*'],
    855    [
    856      'suite1:foo:*',
    857      'suite1:bar,buzz,buzz:*',
    858      'suite1:baz:wye,*',
    859      'suite1:baz:zed:*',
    860      'suite1:baz:batched,*',
    861    ]
    862  );
    863  await testIterateCollapsed(
    864    t,
    865    1,
    866    ['suite1:baz:wye:*', 'suite1:baz:zed:*'],
    867    [
    868      'suite1:foo:*',
    869      'suite1:bar,buzz,buzz:*',
    870      'suite1:baz:wye:*',
    871      'suite1:baz:zed:*',
    872      'suite1:baz:batched,*',
    873    ]
    874  );
    875  await testIterateCollapsed(
    876    t,
    877    1,
    878    ['suite1:baz:wye:'],
    879    [
    880      'suite1:foo:*',
    881      'suite1:bar,buzz,buzz:*',
    882      'suite1:baz:wye:',
    883      'suite1:baz:wye:x=1;*',
    884      'suite1:baz:zed,*',
    885      'suite1:baz:batched,*',
    886    ]
    887  );
    888  await testIterateCollapsed(
    889    t,
    890    1,
    891    ['suite1:baz:wye:x=1'],
    892    [
    893      'suite1:foo:*',
    894      'suite1:bar,buzz,buzz:*',
    895      'suite1:baz:wye:',
    896      'suite1:baz:wye:x=1',
    897      'suite1:baz:zed,*',
    898      'suite1:baz:batched,*',
    899    ]
    900  );
    901  await testIterateCollapsed(
    902    t,
    903    1,
    904    ['suite1:foo:*', 'suite1:baz:wye:'],
    905    [
    906      'suite1:foo:*',
    907      'suite1:bar,buzz,buzz:*',
    908      'suite1:baz:wye:',
    909      'suite1:baz:wye:x=1;*',
    910      'suite1:baz:zed,*',
    911      'suite1:baz:batched,*',
    912    ]
    913  );
    914  await testIterateCollapsed(
    915    t,
    916    2,
    917    ['suite1:baz:wye:'],
    918    [
    919      'suite1:foo:hello:*',
    920      'suite1:foo:bonjour:*',
    921      'suite1:foo:hola:*',
    922      'suite1:bar,buzz,buzz:zap:*',
    923      'suite1:baz:wye:',
    924      'suite1:baz:wye:x=1;*',
    925      'suite1:baz:zed:*',
    926      'suite1:baz:batched:*',
    927    ]
    928  );
    929  await testIterateCollapsed(
    930    t,
    931    2,
    932    ['suite1:baz:wye:x=1'],
    933    [
    934      'suite1:foo:hello:*',
    935      'suite1:foo:bonjour:*',
    936      'suite1:foo:hola:*',
    937      'suite1:bar,buzz,buzz:zap:*',
    938      'suite1:baz:wye:',
    939      'suite1:baz:wye:x=1',
    940      'suite1:baz:zed:*',
    941      'suite1:baz:batched:*',
    942    ]
    943  );
    944  await testIterateCollapsed(
    945    t,
    946    2,
    947    ['suite1:foo:hello:*', 'suite1:baz:wye:'],
    948    [
    949      'suite1:foo:hello:*',
    950      'suite1:foo:bonjour:*',
    951      'suite1:foo:hola:*',
    952      'suite1:bar,buzz,buzz:zap:*',
    953      'suite1:baz:wye:',
    954      'suite1:baz:wye:x=1;*',
    955      'suite1:baz:zed:*',
    956      'suite1:baz:batched:*',
    957    ]
    958  );
    959 
    960  // Invalid expectation queries
    961  await testIterateCollapsed(t, 1, ['*'], 'throws');
    962  await testIterateCollapsed(t, 1, ['garbage'], 'throws');
    963  await testIterateCollapsed(t, 1, ['garbage*'], 'throws');
    964  await testIterateCollapsed(t, 1, ['suite1*'], 'throws');
    965  await testIterateCollapsed(t, 1, ['suite1:foo*'], 'throws');
    966  await testIterateCollapsed(t, 1, ['suite1:foo:he*'], 'throws');
    967 
    968  // Valid expectation queries but they don't match anything
    969  await testIterateCollapsed(t, 1, ['garbage:*'], 'throws');
    970  await testIterateCollapsed(t, 1, ['suite1:doesntexist:*'], 'throws');
    971  await testIterateCollapsed(t, 1, ['suite2:foo:*'], 'throws');
    972  // Can't expand subqueries bigger than one file.
    973  await testIterateCollapsed(t, 1, ['suite1:*'], 'throws');
    974  await testIterateCollapsed(t, 1, ['suite1:bar,*'], 'throws');
    975  await testIterateCollapsed(t, 1, ['suite1:*'], 'throws');
    976  await testIterateCollapsed(t, 1, ['suite1:bar:hello,*'], 'throws');
    977  await testIterateCollapsed(t, 1, ['suite1:baz,*'], 'throws');
    978 });