tor-browser

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

query.ts (8128B)


      1 import { TestParams } from '../../framework/fixture.js';
      2 import { optionWorkerMode } from '../../runtime/helper/options.js';
      3 import { assert, unreachable } from '../../util/util.js';
      4 import { Expectation } from '../logging/result.js';
      5 
      6 import { compareQueries, Ordering } from './compare.js';
      7 import { encodeURIComponentSelectively } from './encode_selectively.js';
      8 import { parseQuery } from './parseQuery.js';
      9 import { kBigSeparator, kPathSeparator, kWildcard } from './separators.js';
     10 import { stringifyPublicParams } from './stringify_params.js';
     11 
     12 /**
     13 * Represents a test query of some level.
     14 *
     15 * TestQuery types are immutable.
     16 */
     17 export type TestQuery =
     18  | TestQuerySingleCase
     19  | TestQueryMultiCase
     20  | TestQueryMultiTest
     21  | TestQueryMultiFile;
     22 
     23 /**
     24 * - 1 = MultiFile.
     25 * - 2 = MultiTest.
     26 * - 3 = MultiCase.
     27 * - 4 = SingleCase.
     28 */
     29 export type TestQueryLevel = 1 | 2 | 3 | 4;
     30 
     31 export interface TestQueryWithExpectation {
     32  query: TestQuery;
     33  expectation: Expectation;
     34 }
     35 
     36 /**
     37 * A multi-file test query, like `s:*` or `s:a,b,*`.
     38 *
     39 * Immutable (makes copies of constructor args).
     40 */
     41 export class TestQueryMultiFile {
     42  readonly level: TestQueryLevel = 1;
     43  readonly isMultiFile: boolean = true;
     44  readonly suite: string;
     45  readonly filePathParts: readonly string[];
     46 
     47  constructor(suite: string, file: readonly string[]) {
     48    this.suite = suite;
     49    this.filePathParts = [...file];
     50  }
     51 
     52  get depthInLevel() {
     53    return this.filePathParts.length;
     54  }
     55 
     56  toString(): string {
     57    return encodeURIComponentSelectively(this.toStringHelper().join(kBigSeparator));
     58  }
     59 
     60  protected toStringHelper(): string[] {
     61    return [this.suite, [...this.filePathParts, kWildcard].join(kPathSeparator)];
     62  }
     63 }
     64 
     65 /**
     66 * A multi-test test query, like `s:f:*` or `s:f:a,b,*`.
     67 *
     68 * Immutable (makes copies of constructor args).
     69 */
     70 export class TestQueryMultiTest extends TestQueryMultiFile {
     71  override readonly level: TestQueryLevel = 2;
     72  override readonly isMultiFile = false as const;
     73  readonly isMultiTest: boolean = true;
     74  readonly testPathParts: readonly string[];
     75 
     76  constructor(suite: string, file: readonly string[], test: readonly string[]) {
     77    super(suite, file);
     78    assert(file.length > 0, 'multi-test (or finer) query must have file-path');
     79    this.testPathParts = [...test];
     80  }
     81 
     82  override get depthInLevel() {
     83    return this.testPathParts.length;
     84  }
     85 
     86  protected override toStringHelper(): string[] {
     87    return [
     88      this.suite,
     89      this.filePathParts.join(kPathSeparator),
     90      [...this.testPathParts, kWildcard].join(kPathSeparator),
     91    ];
     92  }
     93 }
     94 
     95 /**
     96 * A multi-case test query, like `s:f:t:*` or `s:f:t:a,b,*`.
     97 *
     98 * Immutable (makes copies of constructor args), except for param values
     99 * (which aren't normally supposed to change; they're marked readonly in TestParams).
    100 */
    101 export class TestQueryMultiCase extends TestQueryMultiTest {
    102  override readonly level: TestQueryLevel = 3;
    103  override readonly isMultiTest = false as const;
    104  readonly isMultiCase: boolean = true;
    105  readonly params: TestParams;
    106 
    107  constructor(suite: string, file: readonly string[], test: readonly string[], params: TestParams) {
    108    super(suite, file, test);
    109    assert(test.length > 0, 'multi-case (or finer) query must have test-path');
    110    this.params = { ...params };
    111  }
    112 
    113  override get depthInLevel() {
    114    return Object.keys(this.params).length;
    115  }
    116 
    117  protected override toStringHelper(): string[] {
    118    return [
    119      this.suite,
    120      this.filePathParts.join(kPathSeparator),
    121      this.testPathParts.join(kPathSeparator),
    122      stringifyPublicParams(this.params, true),
    123    ];
    124  }
    125 }
    126 
    127 /**
    128 * A multi-case test query, like `s:f:t:` or `s:f:t:a=1,b=1`.
    129 *
    130 * Immutable (makes copies of constructor args).
    131 */
    132 export class TestQuerySingleCase extends TestQueryMultiCase {
    133  override readonly level: TestQueryLevel = 4;
    134  override readonly isMultiCase = false as const;
    135 
    136  override get depthInLevel() {
    137    return 0;
    138  }
    139 
    140  protected override toStringHelper(): string[] {
    141    return [
    142      this.suite,
    143      this.filePathParts.join(kPathSeparator),
    144      this.testPathParts.join(kPathSeparator),
    145      stringifyPublicParams(this.params),
    146    ];
    147  }
    148 }
    149 
    150 /**
    151 * Parse raw expectations input into TestQueryWithExpectation[], filtering so that only
    152 * expectations that are relevant for the provided query and wptURL.
    153 *
    154 * `rawExpectations` should be @type {{ query: string, expectation: Expectation }[]}
    155 *
    156 * The `rawExpectations` are parsed and validated that they are in the correct format.
    157 * If `wptURL` is passed, the query string should be of the full path format such
    158 * as `path/to/cts.https.html?worker=0&q=suite:test_path:test_name:foo=1;bar=2;*`.
    159 * If `wptURL` is `undefined`, the query string should be only the query
    160 * `suite:test_path:test_name:foo=1;bar=2;*`.
    161 */
    162 export function parseExpectationsForTestQuery(
    163  rawExpectations:
    164    | unknown
    165    | {
    166        query: string;
    167        expectation: Expectation;
    168      }[],
    169  query: TestQuery,
    170  wptURL?: URL
    171 ) {
    172  if (!Array.isArray(rawExpectations)) {
    173    unreachable('Expectations should be an array');
    174  }
    175  const expectations: TestQueryWithExpectation[] = [];
    176  for (const entry of rawExpectations) {
    177    assert(typeof entry === 'object');
    178    const rawExpectation = entry as { query?: string; expectation?: string };
    179    assert(rawExpectation.query !== undefined, 'Expectation missing query string');
    180    assert(rawExpectation.expectation !== undefined, 'Expectation missing expectation string');
    181 
    182    let expectationQuery: TestQuery;
    183    if (wptURL !== undefined) {
    184      const expectationURL = new URL(`${wptURL.origin}/${entry.query}`);
    185      if (expectationURL.pathname !== wptURL.pathname) {
    186        continue;
    187      }
    188      assert(
    189        expectationURL.pathname === wptURL.pathname,
    190        `Invalid expectation path ${expectationURL.pathname}
    191 Expectation should be of the form path/to/cts.https.html?debug=0&q=suite:test_path:test_name:foo=1;bar=2;...
    192        `
    193      );
    194 
    195      const params = expectationURL.searchParams;
    196      if (optionWorkerMode('worker', params) !== optionWorkerMode('worker', wptURL.searchParams)) {
    197        continue;
    198      }
    199 
    200      const qs = params.getAll('q');
    201      assert(qs.length === 1, 'currently, there must be exactly one ?q= in the expectation string');
    202      expectationQuery = parseQuery(qs[0]);
    203    } else {
    204      expectationQuery = parseQuery(entry.query);
    205    }
    206 
    207    // Strip params from multicase expectations so that an expectation of foo=2;*
    208    // is stored if the test query is bar=3;*
    209    const queryForFilter =
    210      expectationQuery instanceof TestQueryMultiCase
    211        ? new TestQueryMultiCase(
    212            expectationQuery.suite,
    213            expectationQuery.filePathParts,
    214            expectationQuery.testPathParts,
    215            {}
    216          )
    217        : expectationQuery;
    218 
    219    if (compareQueries(query, queryForFilter) === Ordering.Unordered) {
    220      continue;
    221    }
    222 
    223    switch (entry.expectation) {
    224      case 'pass':
    225      case 'skip':
    226      case 'fail':
    227        break;
    228      default:
    229        unreachable(`Invalid expectation ${entry.expectation}`);
    230    }
    231 
    232    expectations.push({
    233      query: expectationQuery,
    234      expectation: entry.expectation,
    235    });
    236  }
    237  return expectations;
    238 }
    239 
    240 /**
    241 * For display purposes only, produces a "relative" query string from parent to child.
    242 * Used in the wpt runtime to reduce the verbosity of logs.
    243 */
    244 export function relativeQueryString(parent: TestQuery, child: TestQuery): string {
    245  const ordering = compareQueries(parent, child);
    246  if (ordering === Ordering.Equal) {
    247    return '';
    248  } else if (ordering === Ordering.StrictSuperset) {
    249    const parentString = parent.toString();
    250    assert(parentString.endsWith(kWildcard));
    251    const childString = child.toString();
    252    assert(
    253      childString.startsWith(parentString.substring(0, parentString.length - 2)),
    254      'impossible?: childString does not start with parentString[:-2]'
    255    );
    256    return childString.substring(parentString.length - 2);
    257  } else {
    258    unreachable(
    259      `relativeQueryString arguments have invalid ordering ${ordering}:\n${parent}\n${child}`
    260    );
    261  }
    262 }