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 });