xref: /third_party/node/test/common/wpt.js (revision 1cb0ef41)
11cb0ef41Sopenharmony_ci'use strict';
21cb0ef41Sopenharmony_ci
31cb0ef41Sopenharmony_ciconst assert = require('assert');
41cb0ef41Sopenharmony_ciconst fixtures = require('../common/fixtures');
51cb0ef41Sopenharmony_ciconst fs = require('fs');
61cb0ef41Sopenharmony_ciconst fsPromises = fs.promises;
71cb0ef41Sopenharmony_ciconst path = require('path');
81cb0ef41Sopenharmony_ciconst events = require('events');
91cb0ef41Sopenharmony_ciconst os = require('os');
101cb0ef41Sopenharmony_ciconst { inspect } = require('util');
111cb0ef41Sopenharmony_ciconst { Worker } = require('worker_threads');
121cb0ef41Sopenharmony_ci
131cb0ef41Sopenharmony_cifunction getBrowserProperties() {
141cb0ef41Sopenharmony_ci  const { node: version } = process.versions; // e.g. 18.13.0, 20.0.0-nightly202302078e6e215481
151cb0ef41Sopenharmony_ci  const release = /^\d+\.\d+\.\d+$/.test(version);
161cb0ef41Sopenharmony_ci  const browser = {
171cb0ef41Sopenharmony_ci    browser_channel: release ? 'stable' : 'experimental',
181cb0ef41Sopenharmony_ci    browser_version: version,
191cb0ef41Sopenharmony_ci  };
201cb0ef41Sopenharmony_ci
211cb0ef41Sopenharmony_ci  return browser;
221cb0ef41Sopenharmony_ci}
231cb0ef41Sopenharmony_ci
241cb0ef41Sopenharmony_ci/**
251cb0ef41Sopenharmony_ci * Return one of three expected values
261cb0ef41Sopenharmony_ci * https://github.com/web-platform-tests/wpt/blob/1c6ff12/tools/wptrunner/wptrunner/tests/test_update.py#L953-L958
271cb0ef41Sopenharmony_ci */
281cb0ef41Sopenharmony_cifunction getOs() {
291cb0ef41Sopenharmony_ci  switch (os.type()) {
301cb0ef41Sopenharmony_ci    case 'Linux':
311cb0ef41Sopenharmony_ci      return 'linux';
321cb0ef41Sopenharmony_ci    case 'Darwin':
331cb0ef41Sopenharmony_ci      return 'mac';
341cb0ef41Sopenharmony_ci    case 'Windows_NT':
351cb0ef41Sopenharmony_ci      return 'win';
361cb0ef41Sopenharmony_ci    default:
371cb0ef41Sopenharmony_ci      throw new Error('Unsupported os.type()');
381cb0ef41Sopenharmony_ci  }
391cb0ef41Sopenharmony_ci}
401cb0ef41Sopenharmony_ci
411cb0ef41Sopenharmony_ci// https://github.com/web-platform-tests/wpt/blob/b24eedd/resources/testharness.js#L3705
421cb0ef41Sopenharmony_cifunction sanitizeUnpairedSurrogates(str) {
431cb0ef41Sopenharmony_ci  return str.replace(
441cb0ef41Sopenharmony_ci    /([\ud800-\udbff]+)(?![\udc00-\udfff])|(^|[^\ud800-\udbff])([\udc00-\udfff]+)/g,
451cb0ef41Sopenharmony_ci    function(_, low, prefix, high) {
461cb0ef41Sopenharmony_ci      let output = prefix || '';  // Prefix may be undefined
471cb0ef41Sopenharmony_ci      const string = low || high;  // Only one of these alternates can match
481cb0ef41Sopenharmony_ci      for (let i = 0; i < string.length; i++) {
491cb0ef41Sopenharmony_ci        output += codeUnitStr(string[i]);
501cb0ef41Sopenharmony_ci      }
511cb0ef41Sopenharmony_ci      return output;
521cb0ef41Sopenharmony_ci    });
531cb0ef41Sopenharmony_ci}
541cb0ef41Sopenharmony_ci
551cb0ef41Sopenharmony_cifunction codeUnitStr(char) {
561cb0ef41Sopenharmony_ci  return 'U+' + char.charCodeAt(0).toString(16);
571cb0ef41Sopenharmony_ci}
581cb0ef41Sopenharmony_ci
591cb0ef41Sopenharmony_ciclass WPTReport {
601cb0ef41Sopenharmony_ci  constructor() {
611cb0ef41Sopenharmony_ci    this.results = [];
621cb0ef41Sopenharmony_ci    this.time_start = Date.now();
631cb0ef41Sopenharmony_ci  }
641cb0ef41Sopenharmony_ci
651cb0ef41Sopenharmony_ci  addResult(name, status) {
661cb0ef41Sopenharmony_ci    const result = {
671cb0ef41Sopenharmony_ci      test: name,
681cb0ef41Sopenharmony_ci      status,
691cb0ef41Sopenharmony_ci      subtests: [],
701cb0ef41Sopenharmony_ci      addSubtest(name, status, message) {
711cb0ef41Sopenharmony_ci        const subtest = {
721cb0ef41Sopenharmony_ci          status,
731cb0ef41Sopenharmony_ci          // https://github.com/web-platform-tests/wpt/blob/b24eedd/resources/testharness.js#L3722
741cb0ef41Sopenharmony_ci          name: sanitizeUnpairedSurrogates(name),
751cb0ef41Sopenharmony_ci        };
761cb0ef41Sopenharmony_ci        if (message) {
771cb0ef41Sopenharmony_ci          // https://github.com/web-platform-tests/wpt/blob/b24eedd/resources/testharness.js#L4506
781cb0ef41Sopenharmony_ci          subtest.message = sanitizeUnpairedSurrogates(message);
791cb0ef41Sopenharmony_ci        }
801cb0ef41Sopenharmony_ci        this.subtests.push(subtest);
811cb0ef41Sopenharmony_ci        return subtest;
821cb0ef41Sopenharmony_ci      },
831cb0ef41Sopenharmony_ci    };
841cb0ef41Sopenharmony_ci    this.results.push(result);
851cb0ef41Sopenharmony_ci    return result;
861cb0ef41Sopenharmony_ci  }
871cb0ef41Sopenharmony_ci
881cb0ef41Sopenharmony_ci  write() {
891cb0ef41Sopenharmony_ci    this.time_end = Date.now();
901cb0ef41Sopenharmony_ci    this.results = this.results.filter((result) => {
911cb0ef41Sopenharmony_ci      return result.status === 'SKIP' || result.subtests.length !== 0;
921cb0ef41Sopenharmony_ci    }).map((result) => {
931cb0ef41Sopenharmony_ci      const url = new URL(result.test, 'http://wpt');
941cb0ef41Sopenharmony_ci      url.pathname = url.pathname.replace(/\.js$/, '.html');
951cb0ef41Sopenharmony_ci      result.test = url.href.slice(url.origin.length);
961cb0ef41Sopenharmony_ci      return result;
971cb0ef41Sopenharmony_ci    });
981cb0ef41Sopenharmony_ci
991cb0ef41Sopenharmony_ci    if (fs.existsSync('out/wpt/wptreport.json')) {
1001cb0ef41Sopenharmony_ci      const prev = JSON.parse(fs.readFileSync('out/wpt/wptreport.json'));
1011cb0ef41Sopenharmony_ci      this.results = [...prev.results, ...this.results];
1021cb0ef41Sopenharmony_ci      this.time_start = prev.time_start;
1031cb0ef41Sopenharmony_ci      this.time_end = Math.max(this.time_end, prev.time_end);
1041cb0ef41Sopenharmony_ci      this.run_info = prev.run_info;
1051cb0ef41Sopenharmony_ci    } else {
1061cb0ef41Sopenharmony_ci      /**
1071cb0ef41Sopenharmony_ci       * Return required and some optional properties
1081cb0ef41Sopenharmony_ci       * https://github.com/web-platform-tests/wpt.fyi/blob/60da175/api/README.md?plain=1#L331-L335
1091cb0ef41Sopenharmony_ci       */
1101cb0ef41Sopenharmony_ci      this.run_info = {
1111cb0ef41Sopenharmony_ci        product: 'node.js',
1121cb0ef41Sopenharmony_ci        ...getBrowserProperties(),
1131cb0ef41Sopenharmony_ci        revision: process.env.WPT_REVISION || 'unknown',
1141cb0ef41Sopenharmony_ci        os: getOs(),
1151cb0ef41Sopenharmony_ci      };
1161cb0ef41Sopenharmony_ci    }
1171cb0ef41Sopenharmony_ci
1181cb0ef41Sopenharmony_ci    fs.writeFileSync('out/wpt/wptreport.json', JSON.stringify(this));
1191cb0ef41Sopenharmony_ci  }
1201cb0ef41Sopenharmony_ci}
1211cb0ef41Sopenharmony_ci
1221cb0ef41Sopenharmony_ci// https://github.com/web-platform-tests/wpt/blob/HEAD/resources/testharness.js
1231cb0ef41Sopenharmony_ci// TODO: get rid of this half-baked harness in favor of the one
1241cb0ef41Sopenharmony_ci// pulled from WPT
1251cb0ef41Sopenharmony_ciconst harnessMock = {
1261cb0ef41Sopenharmony_ci  test: (fn, desc) => {
1271cb0ef41Sopenharmony_ci    try {
1281cb0ef41Sopenharmony_ci      fn();
1291cb0ef41Sopenharmony_ci    } catch (err) {
1301cb0ef41Sopenharmony_ci      console.error(`In ${desc}:`);
1311cb0ef41Sopenharmony_ci      throw err;
1321cb0ef41Sopenharmony_ci    }
1331cb0ef41Sopenharmony_ci  },
1341cb0ef41Sopenharmony_ci  assert_equals: assert.strictEqual,
1351cb0ef41Sopenharmony_ci  assert_true: (value, message) => assert.strictEqual(value, true, message),
1361cb0ef41Sopenharmony_ci  assert_false: (value, message) => assert.strictEqual(value, false, message),
1371cb0ef41Sopenharmony_ci  assert_throws: (code, func, desc) => {
1381cb0ef41Sopenharmony_ci    assert.throws(func, function(err) {
1391cb0ef41Sopenharmony_ci      return typeof err === 'object' &&
1401cb0ef41Sopenharmony_ci             'name' in err &&
1411cb0ef41Sopenharmony_ci             err.name.startsWith(code.name);
1421cb0ef41Sopenharmony_ci    }, desc);
1431cb0ef41Sopenharmony_ci  },
1441cb0ef41Sopenharmony_ci  assert_array_equals: assert.deepStrictEqual,
1451cb0ef41Sopenharmony_ci  assert_unreached(desc) {
1461cb0ef41Sopenharmony_ci    assert.fail(`Reached unreachable code: ${desc}`);
1471cb0ef41Sopenharmony_ci  },
1481cb0ef41Sopenharmony_ci};
1491cb0ef41Sopenharmony_ci
1501cb0ef41Sopenharmony_ciclass ResourceLoader {
1511cb0ef41Sopenharmony_ci  constructor(path) {
1521cb0ef41Sopenharmony_ci    this.path = path;
1531cb0ef41Sopenharmony_ci  }
1541cb0ef41Sopenharmony_ci
1551cb0ef41Sopenharmony_ci  toRealFilePath(from, url) {
1561cb0ef41Sopenharmony_ci    // We need to patch this to load the WebIDL parser
1571cb0ef41Sopenharmony_ci    url = url.replace(
1581cb0ef41Sopenharmony_ci      '/resources/WebIDLParser.js',
1591cb0ef41Sopenharmony_ci      '/resources/webidl2/lib/webidl2.js',
1601cb0ef41Sopenharmony_ci    );
1611cb0ef41Sopenharmony_ci    const base = path.dirname(from);
1621cb0ef41Sopenharmony_ci    return url.startsWith('/') ?
1631cb0ef41Sopenharmony_ci      fixtures.path('wpt', url) :
1641cb0ef41Sopenharmony_ci      fixtures.path('wpt', base, url);
1651cb0ef41Sopenharmony_ci  }
1661cb0ef41Sopenharmony_ci
1671cb0ef41Sopenharmony_ci  /**
1681cb0ef41Sopenharmony_ci   * Load a resource in test/fixtures/wpt specified with a URL
1691cb0ef41Sopenharmony_ci   * @param {string} from the path of the file loading this resource,
1701cb0ef41Sopenharmony_ci   *                      relative to the WPT folder.
1711cb0ef41Sopenharmony_ci   * @param {string} url the url of the resource being loaded.
1721cb0ef41Sopenharmony_ci   * @param {boolean} asFetch if true, return the resource in a
1731cb0ef41Sopenharmony_ci   *                          pseudo-Response object.
1741cb0ef41Sopenharmony_ci   */
1751cb0ef41Sopenharmony_ci  read(from, url, asFetch = true) {
1761cb0ef41Sopenharmony_ci    const file = this.toRealFilePath(from, url);
1771cb0ef41Sopenharmony_ci    if (asFetch) {
1781cb0ef41Sopenharmony_ci      return fsPromises.readFile(file)
1791cb0ef41Sopenharmony_ci        .then((data) => {
1801cb0ef41Sopenharmony_ci          return {
1811cb0ef41Sopenharmony_ci            ok: true,
1821cb0ef41Sopenharmony_ci            json() { return JSON.parse(data.toString()); },
1831cb0ef41Sopenharmony_ci            text() { return data.toString(); },
1841cb0ef41Sopenharmony_ci          };
1851cb0ef41Sopenharmony_ci        });
1861cb0ef41Sopenharmony_ci    }
1871cb0ef41Sopenharmony_ci    return fs.readFileSync(file, 'utf8');
1881cb0ef41Sopenharmony_ci  }
1891cb0ef41Sopenharmony_ci}
1901cb0ef41Sopenharmony_ci
1911cb0ef41Sopenharmony_ciclass StatusRule {
1921cb0ef41Sopenharmony_ci  constructor(key, value, pattern) {
1931cb0ef41Sopenharmony_ci    this.key = key;
1941cb0ef41Sopenharmony_ci    this.requires = value.requires || [];
1951cb0ef41Sopenharmony_ci    this.fail = value.fail;
1961cb0ef41Sopenharmony_ci    this.skip = value.skip;
1971cb0ef41Sopenharmony_ci    if (pattern) {
1981cb0ef41Sopenharmony_ci      this.pattern = this.transformPattern(pattern);
1991cb0ef41Sopenharmony_ci    }
2001cb0ef41Sopenharmony_ci    // TODO(joyeecheung): implement this
2011cb0ef41Sopenharmony_ci    this.scope = value.scope;
2021cb0ef41Sopenharmony_ci    this.comment = value.comment;
2031cb0ef41Sopenharmony_ci  }
2041cb0ef41Sopenharmony_ci
2051cb0ef41Sopenharmony_ci  /**
2061cb0ef41Sopenharmony_ci   * Transform a filename pattern into a RegExp
2071cb0ef41Sopenharmony_ci   * @param {string} pattern
2081cb0ef41Sopenharmony_ci   * @returns {RegExp}
2091cb0ef41Sopenharmony_ci   */
2101cb0ef41Sopenharmony_ci  transformPattern(pattern) {
2111cb0ef41Sopenharmony_ci    const result = path.normalize(pattern).replace(/[-/\\^$+?.()|[\]{}]/g, '\\$&');
2121cb0ef41Sopenharmony_ci    return new RegExp(result.replace('*', '.*'));
2131cb0ef41Sopenharmony_ci  }
2141cb0ef41Sopenharmony_ci}
2151cb0ef41Sopenharmony_ci
2161cb0ef41Sopenharmony_ciclass StatusRuleSet {
2171cb0ef41Sopenharmony_ci  constructor() {
2181cb0ef41Sopenharmony_ci    // We use two sets of rules to speed up matching
2191cb0ef41Sopenharmony_ci    this.exactMatch = {};
2201cb0ef41Sopenharmony_ci    this.patternMatch = [];
2211cb0ef41Sopenharmony_ci  }
2221cb0ef41Sopenharmony_ci
2231cb0ef41Sopenharmony_ci  /**
2241cb0ef41Sopenharmony_ci   * @param {object} rules
2251cb0ef41Sopenharmony_ci   */
2261cb0ef41Sopenharmony_ci  addRules(rules) {
2271cb0ef41Sopenharmony_ci    for (const key of Object.keys(rules)) {
2281cb0ef41Sopenharmony_ci      if (key.includes('*')) {
2291cb0ef41Sopenharmony_ci        this.patternMatch.push(new StatusRule(key, rules[key], key));
2301cb0ef41Sopenharmony_ci      } else {
2311cb0ef41Sopenharmony_ci        const normalizedPath = path.normalize(key);
2321cb0ef41Sopenharmony_ci        this.exactMatch[normalizedPath] = new StatusRule(key, rules[key]);
2331cb0ef41Sopenharmony_ci      }
2341cb0ef41Sopenharmony_ci    }
2351cb0ef41Sopenharmony_ci  }
2361cb0ef41Sopenharmony_ci
2371cb0ef41Sopenharmony_ci  match(file) {
2381cb0ef41Sopenharmony_ci    const result = [];
2391cb0ef41Sopenharmony_ci    const exact = this.exactMatch[file];
2401cb0ef41Sopenharmony_ci    if (exact) {
2411cb0ef41Sopenharmony_ci      result.push(exact);
2421cb0ef41Sopenharmony_ci    }
2431cb0ef41Sopenharmony_ci    for (const item of this.patternMatch) {
2441cb0ef41Sopenharmony_ci      if (item.pattern.test(file)) {
2451cb0ef41Sopenharmony_ci        result.push(item);
2461cb0ef41Sopenharmony_ci      }
2471cb0ef41Sopenharmony_ci    }
2481cb0ef41Sopenharmony_ci    return result;
2491cb0ef41Sopenharmony_ci  }
2501cb0ef41Sopenharmony_ci}
2511cb0ef41Sopenharmony_ci
2521cb0ef41Sopenharmony_ci// A specification of WPT test
2531cb0ef41Sopenharmony_ciclass WPTTestSpec {
2541cb0ef41Sopenharmony_ci  /**
2551cb0ef41Sopenharmony_ci   * @param {string} mod name of the WPT module, e.g.
2561cb0ef41Sopenharmony_ci   *                     'html/webappapis/microtask-queuing'
2571cb0ef41Sopenharmony_ci   * @param {string} filename path of the test, relative to mod, e.g.
2581cb0ef41Sopenharmony_ci   *                          'test.any.js'
2591cb0ef41Sopenharmony_ci   * @param {StatusRule[]} rules
2601cb0ef41Sopenharmony_ci   */
2611cb0ef41Sopenharmony_ci  constructor(mod, filename, rules) {
2621cb0ef41Sopenharmony_ci    this.module = mod;
2631cb0ef41Sopenharmony_ci    this.filename = filename;
2641cb0ef41Sopenharmony_ci
2651cb0ef41Sopenharmony_ci    this.requires = new Set();
2661cb0ef41Sopenharmony_ci    this.failedTests = [];
2671cb0ef41Sopenharmony_ci    this.flakyTests = [];
2681cb0ef41Sopenharmony_ci    this.skipReasons = [];
2691cb0ef41Sopenharmony_ci    for (const item of rules) {
2701cb0ef41Sopenharmony_ci      if (item.requires.length) {
2711cb0ef41Sopenharmony_ci        for (const req of item.requires) {
2721cb0ef41Sopenharmony_ci          this.requires.add(req);
2731cb0ef41Sopenharmony_ci        }
2741cb0ef41Sopenharmony_ci      }
2751cb0ef41Sopenharmony_ci      if (Array.isArray(item.fail?.expected)) {
2761cb0ef41Sopenharmony_ci        this.failedTests.push(...item.fail.expected);
2771cb0ef41Sopenharmony_ci      }
2781cb0ef41Sopenharmony_ci      if (Array.isArray(item.fail?.flaky)) {
2791cb0ef41Sopenharmony_ci        this.failedTests.push(...item.fail.flaky);
2801cb0ef41Sopenharmony_ci        this.flakyTests.push(...item.fail.flaky);
2811cb0ef41Sopenharmony_ci      }
2821cb0ef41Sopenharmony_ci      if (item.skip) {
2831cb0ef41Sopenharmony_ci        this.skipReasons.push(item.skip);
2841cb0ef41Sopenharmony_ci      }
2851cb0ef41Sopenharmony_ci    }
2861cb0ef41Sopenharmony_ci
2871cb0ef41Sopenharmony_ci    this.failedTests = [...new Set(this.failedTests)];
2881cb0ef41Sopenharmony_ci    this.flakyTests = [...new Set(this.flakyTests)];
2891cb0ef41Sopenharmony_ci    this.skipReasons = [...new Set(this.skipReasons)];
2901cb0ef41Sopenharmony_ci  }
2911cb0ef41Sopenharmony_ci
2921cb0ef41Sopenharmony_ci  getRelativePath() {
2931cb0ef41Sopenharmony_ci    return path.join(this.module, this.filename);
2941cb0ef41Sopenharmony_ci  }
2951cb0ef41Sopenharmony_ci
2961cb0ef41Sopenharmony_ci  getAbsolutePath() {
2971cb0ef41Sopenharmony_ci    return fixtures.path('wpt', this.getRelativePath());
2981cb0ef41Sopenharmony_ci  }
2991cb0ef41Sopenharmony_ci
3001cb0ef41Sopenharmony_ci  getContent() {
3011cb0ef41Sopenharmony_ci    return fs.readFileSync(this.getAbsolutePath(), 'utf8');
3021cb0ef41Sopenharmony_ci  }
3031cb0ef41Sopenharmony_ci}
3041cb0ef41Sopenharmony_ci
3051cb0ef41Sopenharmony_ciconst kIntlRequirement = {
3061cb0ef41Sopenharmony_ci  none: 0,
3071cb0ef41Sopenharmony_ci  small: 1,
3081cb0ef41Sopenharmony_ci  full: 2,
3091cb0ef41Sopenharmony_ci  // TODO(joyeecheung): we may need to deal with --with-intl=system-icu
3101cb0ef41Sopenharmony_ci};
3111cb0ef41Sopenharmony_ci
3121cb0ef41Sopenharmony_ciclass IntlRequirement {
3131cb0ef41Sopenharmony_ci  constructor() {
3141cb0ef41Sopenharmony_ci    this.currentIntl = kIntlRequirement.none;
3151cb0ef41Sopenharmony_ci    if (process.config.variables.v8_enable_i18n_support === 0) {
3161cb0ef41Sopenharmony_ci      this.currentIntl = kIntlRequirement.none;
3171cb0ef41Sopenharmony_ci      return;
3181cb0ef41Sopenharmony_ci    }
3191cb0ef41Sopenharmony_ci    // i18n enabled
3201cb0ef41Sopenharmony_ci    if (process.config.variables.icu_small) {
3211cb0ef41Sopenharmony_ci      this.currentIntl = kIntlRequirement.small;
3221cb0ef41Sopenharmony_ci    } else {
3231cb0ef41Sopenharmony_ci      this.currentIntl = kIntlRequirement.full;
3241cb0ef41Sopenharmony_ci    }
3251cb0ef41Sopenharmony_ci  }
3261cb0ef41Sopenharmony_ci
3271cb0ef41Sopenharmony_ci  /**
3281cb0ef41Sopenharmony_ci   * @param {Set} requires
3291cb0ef41Sopenharmony_ci   * @returns {string|false} The config that the build is lacking, or false
3301cb0ef41Sopenharmony_ci   */
3311cb0ef41Sopenharmony_ci  isLacking(requires) {
3321cb0ef41Sopenharmony_ci    const current = this.currentIntl;
3331cb0ef41Sopenharmony_ci    if (requires.has('full-icu') && current !== kIntlRequirement.full) {
3341cb0ef41Sopenharmony_ci      return 'full-icu';
3351cb0ef41Sopenharmony_ci    }
3361cb0ef41Sopenharmony_ci    if (requires.has('small-icu') && current < kIntlRequirement.small) {
3371cb0ef41Sopenharmony_ci      return 'small-icu';
3381cb0ef41Sopenharmony_ci    }
3391cb0ef41Sopenharmony_ci    return false;
3401cb0ef41Sopenharmony_ci  }
3411cb0ef41Sopenharmony_ci}
3421cb0ef41Sopenharmony_ci
3431cb0ef41Sopenharmony_ciconst intlRequirements = new IntlRequirement();
3441cb0ef41Sopenharmony_ci
3451cb0ef41Sopenharmony_ciclass StatusLoader {
3461cb0ef41Sopenharmony_ci  /**
3471cb0ef41Sopenharmony_ci   * @param {string} path relative path of the WPT subset
3481cb0ef41Sopenharmony_ci   */
3491cb0ef41Sopenharmony_ci  constructor(path) {
3501cb0ef41Sopenharmony_ci    this.path = path;
3511cb0ef41Sopenharmony_ci    this.loaded = false;
3521cb0ef41Sopenharmony_ci    this.rules = new StatusRuleSet();
3531cb0ef41Sopenharmony_ci    /** @type {WPTTestSpec[]} */
3541cb0ef41Sopenharmony_ci    this.specs = [];
3551cb0ef41Sopenharmony_ci  }
3561cb0ef41Sopenharmony_ci
3571cb0ef41Sopenharmony_ci  /**
3581cb0ef41Sopenharmony_ci   * Grep for all .*.js file recursively in a directory.
3591cb0ef41Sopenharmony_ci   * @param {string} dir
3601cb0ef41Sopenharmony_ci   */
3611cb0ef41Sopenharmony_ci  grep(dir) {
3621cb0ef41Sopenharmony_ci    let result = [];
3631cb0ef41Sopenharmony_ci    const list = fs.readdirSync(dir);
3641cb0ef41Sopenharmony_ci    for (const file of list) {
3651cb0ef41Sopenharmony_ci      const filepath = path.join(dir, file);
3661cb0ef41Sopenharmony_ci      const stat = fs.statSync(filepath);
3671cb0ef41Sopenharmony_ci      if (stat.isDirectory()) {
3681cb0ef41Sopenharmony_ci        const list = this.grep(filepath);
3691cb0ef41Sopenharmony_ci        result = result.concat(list);
3701cb0ef41Sopenharmony_ci      } else {
3711cb0ef41Sopenharmony_ci        if (!(/\.\w+\.js$/.test(filepath)) || filepath.endsWith('.helper.js')) {
3721cb0ef41Sopenharmony_ci          continue;
3731cb0ef41Sopenharmony_ci        }
3741cb0ef41Sopenharmony_ci        result.push(filepath);
3751cb0ef41Sopenharmony_ci      }
3761cb0ef41Sopenharmony_ci    }
3771cb0ef41Sopenharmony_ci    return result;
3781cb0ef41Sopenharmony_ci  }
3791cb0ef41Sopenharmony_ci
3801cb0ef41Sopenharmony_ci  load() {
3811cb0ef41Sopenharmony_ci    const dir = path.join(__dirname, '..', 'wpt');
3821cb0ef41Sopenharmony_ci    const statusFile = path.join(dir, 'status', `${this.path}.json`);
3831cb0ef41Sopenharmony_ci    const result = JSON.parse(fs.readFileSync(statusFile, 'utf8'));
3841cb0ef41Sopenharmony_ci    this.rules.addRules(result);
3851cb0ef41Sopenharmony_ci
3861cb0ef41Sopenharmony_ci    const subDir = fixtures.path('wpt', this.path);
3871cb0ef41Sopenharmony_ci    const list = this.grep(subDir);
3881cb0ef41Sopenharmony_ci    for (const file of list) {
3891cb0ef41Sopenharmony_ci      const relativePath = path.relative(subDir, file);
3901cb0ef41Sopenharmony_ci      const match = this.rules.match(relativePath);
3911cb0ef41Sopenharmony_ci      this.specs.push(new WPTTestSpec(this.path, relativePath, match));
3921cb0ef41Sopenharmony_ci    }
3931cb0ef41Sopenharmony_ci    this.loaded = true;
3941cb0ef41Sopenharmony_ci  }
3951cb0ef41Sopenharmony_ci}
3961cb0ef41Sopenharmony_ci
3971cb0ef41Sopenharmony_ciconst kPass = 'pass';
3981cb0ef41Sopenharmony_ciconst kFail = 'fail';
3991cb0ef41Sopenharmony_ciconst kSkip = 'skip';
4001cb0ef41Sopenharmony_ciconst kTimeout = 'timeout';
4011cb0ef41Sopenharmony_ciconst kIncomplete = 'incomplete';
4021cb0ef41Sopenharmony_ciconst kUncaught = 'uncaught';
4031cb0ef41Sopenharmony_ciconst NODE_UNCAUGHT = 100;
4041cb0ef41Sopenharmony_ci
4051cb0ef41Sopenharmony_ciclass WPTRunner {
4061cb0ef41Sopenharmony_ci  constructor(path) {
4071cb0ef41Sopenharmony_ci    this.path = path;
4081cb0ef41Sopenharmony_ci    this.resource = new ResourceLoader(path);
4091cb0ef41Sopenharmony_ci
4101cb0ef41Sopenharmony_ci    this.flags = [];
4111cb0ef41Sopenharmony_ci    this.globalThisInitScripts = [];
4121cb0ef41Sopenharmony_ci    this.initScript = null;
4131cb0ef41Sopenharmony_ci
4141cb0ef41Sopenharmony_ci    this.status = new StatusLoader(path);
4151cb0ef41Sopenharmony_ci    this.status.load();
4161cb0ef41Sopenharmony_ci    this.specMap = new Map(
4171cb0ef41Sopenharmony_ci      this.status.specs.map((item) => [item.filename, item]),
4181cb0ef41Sopenharmony_ci    );
4191cb0ef41Sopenharmony_ci
4201cb0ef41Sopenharmony_ci    this.results = {};
4211cb0ef41Sopenharmony_ci    this.inProgress = new Set();
4221cb0ef41Sopenharmony_ci    this.workers = new Map();
4231cb0ef41Sopenharmony_ci    this.unexpectedFailures = [];
4241cb0ef41Sopenharmony_ci
4251cb0ef41Sopenharmony_ci    this.scriptsModifier = null;
4261cb0ef41Sopenharmony_ci
4271cb0ef41Sopenharmony_ci    if (process.env.WPT_REPORT != null) {
4281cb0ef41Sopenharmony_ci      this.report = new WPTReport();
4291cb0ef41Sopenharmony_ci    }
4301cb0ef41Sopenharmony_ci  }
4311cb0ef41Sopenharmony_ci
4321cb0ef41Sopenharmony_ci  /**
4331cb0ef41Sopenharmony_ci   * Sets the Node.js flags passed to the worker.
4341cb0ef41Sopenharmony_ci   * @param {Array<string>} flags
4351cb0ef41Sopenharmony_ci   */
4361cb0ef41Sopenharmony_ci  setFlags(flags) {
4371cb0ef41Sopenharmony_ci    this.flags = flags;
4381cb0ef41Sopenharmony_ci  }
4391cb0ef41Sopenharmony_ci
4401cb0ef41Sopenharmony_ci  /**
4411cb0ef41Sopenharmony_ci   * Sets a script to be run in the worker before executing the tests.
4421cb0ef41Sopenharmony_ci   * @param {string} script
4431cb0ef41Sopenharmony_ci   */
4441cb0ef41Sopenharmony_ci  setInitScript(script) {
4451cb0ef41Sopenharmony_ci    this.initScript = script;
4461cb0ef41Sopenharmony_ci  }
4471cb0ef41Sopenharmony_ci
4481cb0ef41Sopenharmony_ci  /**
4491cb0ef41Sopenharmony_ci   * Set the scripts modifier for each script.
4501cb0ef41Sopenharmony_ci   * @param {(meta: { code: string, filename: string }) => void} modifier
4511cb0ef41Sopenharmony_ci   */
4521cb0ef41Sopenharmony_ci  setScriptModifier(modifier) {
4531cb0ef41Sopenharmony_ci    this.scriptsModifier = modifier;
4541cb0ef41Sopenharmony_ci  }
4551cb0ef41Sopenharmony_ci
4561cb0ef41Sopenharmony_ci  fullInitScript(hasSubsetScript, locationSearchString) {
4571cb0ef41Sopenharmony_ci    let { initScript } = this;
4581cb0ef41Sopenharmony_ci    if (hasSubsetScript || locationSearchString) {
4591cb0ef41Sopenharmony_ci      initScript = `${initScript}\n\n//===\nglobalThis.location ||= {};`;
4601cb0ef41Sopenharmony_ci    }
4611cb0ef41Sopenharmony_ci
4621cb0ef41Sopenharmony_ci    if (locationSearchString) {
4631cb0ef41Sopenharmony_ci      initScript = `${initScript}\n\n//===\nglobalThis.location.search = "${locationSearchString}";`;
4641cb0ef41Sopenharmony_ci    }
4651cb0ef41Sopenharmony_ci
4661cb0ef41Sopenharmony_ci    if (this.globalThisInitScripts.length === null) {
4671cb0ef41Sopenharmony_ci      return initScript;
4681cb0ef41Sopenharmony_ci    }
4691cb0ef41Sopenharmony_ci
4701cb0ef41Sopenharmony_ci    const globalThisInitScript = this.globalThisInitScripts.join('\n\n//===\n');
4711cb0ef41Sopenharmony_ci
4721cb0ef41Sopenharmony_ci    if (initScript === null) {
4731cb0ef41Sopenharmony_ci      return globalThisInitScript;
4741cb0ef41Sopenharmony_ci    }
4751cb0ef41Sopenharmony_ci
4761cb0ef41Sopenharmony_ci    return `${globalThisInitScript}\n\n//===\n${initScript}`;
4771cb0ef41Sopenharmony_ci  }
4781cb0ef41Sopenharmony_ci
4791cb0ef41Sopenharmony_ci  /**
4801cb0ef41Sopenharmony_ci   * Pretend the runner is run in `name`'s environment (globalThis).
4811cb0ef41Sopenharmony_ci   * @param {'Window'} name
4821cb0ef41Sopenharmony_ci   * @see {@link https://github.com/nodejs/node/blob/24673ace8ae196bd1c6d4676507d6e8c94cf0b90/test/fixtures/wpt/resources/idlharness.js#L654-L671}
4831cb0ef41Sopenharmony_ci   */
4841cb0ef41Sopenharmony_ci  pretendGlobalThisAs(name) {
4851cb0ef41Sopenharmony_ci    switch (name) {
4861cb0ef41Sopenharmony_ci      case 'Window': {
4871cb0ef41Sopenharmony_ci        this.globalThisInitScripts.push(
4881cb0ef41Sopenharmony_ci          `global.Window = Object.getPrototypeOf(globalThis).constructor;
4891cb0ef41Sopenharmony_ci          self.GLOBAL.isWorker = () => false;`);
4901cb0ef41Sopenharmony_ci        this.loadLazyGlobals();
4911cb0ef41Sopenharmony_ci        break;
4921cb0ef41Sopenharmony_ci      }
4931cb0ef41Sopenharmony_ci
4941cb0ef41Sopenharmony_ci      // TODO(XadillaX): implement `ServiceWorkerGlobalScope`,
4951cb0ef41Sopenharmony_ci      // `DedicateWorkerGlobalScope`, etc.
4961cb0ef41Sopenharmony_ci      //
4971cb0ef41Sopenharmony_ci      // e.g. `ServiceWorkerGlobalScope` should implement dummy
4981cb0ef41Sopenharmony_ci      // `addEventListener` and so on.
4991cb0ef41Sopenharmony_ci
5001cb0ef41Sopenharmony_ci      default: throw new Error(`Invalid globalThis type ${name}.`);
5011cb0ef41Sopenharmony_ci    }
5021cb0ef41Sopenharmony_ci  }
5031cb0ef41Sopenharmony_ci
5041cb0ef41Sopenharmony_ci  loadLazyGlobals() {
5051cb0ef41Sopenharmony_ci    const lazyProperties = [
5061cb0ef41Sopenharmony_ci      'DOMException',
5071cb0ef41Sopenharmony_ci      'Performance', 'PerformanceEntry', 'PerformanceMark', 'PerformanceMeasure',
5081cb0ef41Sopenharmony_ci      'PerformanceObserver', 'PerformanceObserverEntryList', 'PerformanceResourceTiming',
5091cb0ef41Sopenharmony_ci      'Blob', 'atob', 'btoa',
5101cb0ef41Sopenharmony_ci      'MessageChannel', 'MessagePort', 'MessageEvent',
5111cb0ef41Sopenharmony_ci      'EventTarget', 'Event',
5121cb0ef41Sopenharmony_ci      'AbortController', 'AbortSignal',
5131cb0ef41Sopenharmony_ci      'performance',
5141cb0ef41Sopenharmony_ci      'TransformStream', 'TransformStreamDefaultController',
5151cb0ef41Sopenharmony_ci      'WritableStream', 'WritableStreamDefaultController', 'WritableStreamDefaultWriter',
5161cb0ef41Sopenharmony_ci      'ReadableStream', 'ReadableStreamDefaultReader',
5171cb0ef41Sopenharmony_ci      'ReadableStreamBYOBReader', 'ReadableStreamBYOBRequest',
5181cb0ef41Sopenharmony_ci      'ReadableByteStreamController', 'ReadableStreamDefaultController',
5191cb0ef41Sopenharmony_ci      'ByteLengthQueuingStrategy', 'CountQueuingStrategy',
5201cb0ef41Sopenharmony_ci      'TextEncoderStream', 'TextDecoderStream',
5211cb0ef41Sopenharmony_ci      'CompressionStream', 'DecompressionStream',
5221cb0ef41Sopenharmony_ci    ];
5231cb0ef41Sopenharmony_ci    if (Boolean(process.versions.openssl) && !process.env.NODE_SKIP_CRYPTO) {
5241cb0ef41Sopenharmony_ci      lazyProperties.push('crypto');
5251cb0ef41Sopenharmony_ci    }
5261cb0ef41Sopenharmony_ci    const script = lazyProperties.map((name) => `globalThis.${name};`).join('\n');
5271cb0ef41Sopenharmony_ci    this.globalThisInitScripts.push(script);
5281cb0ef41Sopenharmony_ci  }
5291cb0ef41Sopenharmony_ci
5301cb0ef41Sopenharmony_ci  // TODO(joyeecheung): work with the upstream to port more tests in .html
5311cb0ef41Sopenharmony_ci  // to .js.
5321cb0ef41Sopenharmony_ci  async runJsTests() {
5331cb0ef41Sopenharmony_ci    let queue = [];
5341cb0ef41Sopenharmony_ci
5351cb0ef41Sopenharmony_ci    // If the tests are run as `node test/wpt/test-something.js subset.any.js`,
5361cb0ef41Sopenharmony_ci    // only `subset.any.js` will be run by the runner.
5371cb0ef41Sopenharmony_ci    if (process.argv[2]) {
5381cb0ef41Sopenharmony_ci      const filename = process.argv[2];
5391cb0ef41Sopenharmony_ci      if (!this.specMap.has(filename)) {
5401cb0ef41Sopenharmony_ci        throw new Error(`${filename} not found!`);
5411cb0ef41Sopenharmony_ci      }
5421cb0ef41Sopenharmony_ci      queue.push(this.specMap.get(filename));
5431cb0ef41Sopenharmony_ci    } else {
5441cb0ef41Sopenharmony_ci      queue = this.buildQueue();
5451cb0ef41Sopenharmony_ci    }
5461cb0ef41Sopenharmony_ci
5471cb0ef41Sopenharmony_ci    this.inProgress = new Set(queue.map((spec) => spec.filename));
5481cb0ef41Sopenharmony_ci
5491cb0ef41Sopenharmony_ci    for (const spec of queue) {
5501cb0ef41Sopenharmony_ci      const testFileName = spec.filename;
5511cb0ef41Sopenharmony_ci      const content = spec.getContent();
5521cb0ef41Sopenharmony_ci      const meta = spec.meta = this.getMeta(content);
5531cb0ef41Sopenharmony_ci
5541cb0ef41Sopenharmony_ci      const absolutePath = spec.getAbsolutePath();
5551cb0ef41Sopenharmony_ci      const relativePath = spec.getRelativePath();
5561cb0ef41Sopenharmony_ci      const harnessPath = fixtures.path('wpt', 'resources', 'testharness.js');
5571cb0ef41Sopenharmony_ci      const scriptsToRun = [];
5581cb0ef41Sopenharmony_ci      let hasSubsetScript = false;
5591cb0ef41Sopenharmony_ci
5601cb0ef41Sopenharmony_ci      // Scripts specified with the `// META: script=` header
5611cb0ef41Sopenharmony_ci      if (meta.script) {
5621cb0ef41Sopenharmony_ci        for (const script of meta.script) {
5631cb0ef41Sopenharmony_ci          if (script === '/common/subset-tests.js' || script === '/common/subset-tests-by-key.js') {
5641cb0ef41Sopenharmony_ci            hasSubsetScript = true;
5651cb0ef41Sopenharmony_ci          }
5661cb0ef41Sopenharmony_ci          const obj = {
5671cb0ef41Sopenharmony_ci            filename: this.resource.toRealFilePath(relativePath, script),
5681cb0ef41Sopenharmony_ci            code: this.resource.read(relativePath, script, false),
5691cb0ef41Sopenharmony_ci          };
5701cb0ef41Sopenharmony_ci          this.scriptsModifier?.(obj);
5711cb0ef41Sopenharmony_ci          scriptsToRun.push(obj);
5721cb0ef41Sopenharmony_ci        }
5731cb0ef41Sopenharmony_ci      }
5741cb0ef41Sopenharmony_ci      // The actual test
5751cb0ef41Sopenharmony_ci      const obj = {
5761cb0ef41Sopenharmony_ci        code: content,
5771cb0ef41Sopenharmony_ci        filename: absolutePath,
5781cb0ef41Sopenharmony_ci      };
5791cb0ef41Sopenharmony_ci      this.scriptsModifier?.(obj);
5801cb0ef41Sopenharmony_ci      scriptsToRun.push(obj);
5811cb0ef41Sopenharmony_ci
5821cb0ef41Sopenharmony_ci      /**
5831cb0ef41Sopenharmony_ci       * Example test with no META variant
5841cb0ef41Sopenharmony_ci       * https://github.com/nodejs/node/blob/03854f6/test/fixtures/wpt/WebCryptoAPI/sign_verify/hmac.https.any.js#L1-L4
5851cb0ef41Sopenharmony_ci       *
5861cb0ef41Sopenharmony_ci       * Example test with multiple META variants
5871cb0ef41Sopenharmony_ci       * https://github.com/nodejs/node/blob/03854f6/test/fixtures/wpt/WebCryptoAPI/generateKey/successes_RSASSA-PKCS1-v1_5.https.any.js#L1-L9
5881cb0ef41Sopenharmony_ci       */
5891cb0ef41Sopenharmony_ci      for (const variant of meta.variant || ['']) {
5901cb0ef41Sopenharmony_ci        const workerPath = path.join(__dirname, 'wpt/worker.js');
5911cb0ef41Sopenharmony_ci        const worker = new Worker(workerPath, {
5921cb0ef41Sopenharmony_ci          execArgv: this.flags,
5931cb0ef41Sopenharmony_ci          workerData: {
5941cb0ef41Sopenharmony_ci            testRelativePath: relativePath,
5951cb0ef41Sopenharmony_ci            wptRunner: __filename,
5961cb0ef41Sopenharmony_ci            wptPath: this.path,
5971cb0ef41Sopenharmony_ci            initScript: this.fullInitScript(hasSubsetScript, variant),
5981cb0ef41Sopenharmony_ci            harness: {
5991cb0ef41Sopenharmony_ci              code: fs.readFileSync(harnessPath, 'utf8'),
6001cb0ef41Sopenharmony_ci              filename: harnessPath,
6011cb0ef41Sopenharmony_ci            },
6021cb0ef41Sopenharmony_ci            scriptsToRun,
6031cb0ef41Sopenharmony_ci          },
6041cb0ef41Sopenharmony_ci        });
6051cb0ef41Sopenharmony_ci        this.workers.set(testFileName, worker);
6061cb0ef41Sopenharmony_ci
6071cb0ef41Sopenharmony_ci        let reportResult;
6081cb0ef41Sopenharmony_ci        worker.on('message', (message) => {
6091cb0ef41Sopenharmony_ci          switch (message.type) {
6101cb0ef41Sopenharmony_ci            case 'result':
6111cb0ef41Sopenharmony_ci              reportResult ||= this.report?.addResult(`/${relativePath}${variant}`, 'OK');
6121cb0ef41Sopenharmony_ci              return this.resultCallback(testFileName, message.result, reportResult);
6131cb0ef41Sopenharmony_ci            case 'completion':
6141cb0ef41Sopenharmony_ci              return this.completionCallback(testFileName, message.status);
6151cb0ef41Sopenharmony_ci            default:
6161cb0ef41Sopenharmony_ci              throw new Error(`Unexpected message from worker: ${message.type}`);
6171cb0ef41Sopenharmony_ci          }
6181cb0ef41Sopenharmony_ci        });
6191cb0ef41Sopenharmony_ci
6201cb0ef41Sopenharmony_ci        worker.on('error', (err) => {
6211cb0ef41Sopenharmony_ci          if (!this.inProgress.has(testFileName)) {
6221cb0ef41Sopenharmony_ci            // The test is already finished. Ignore errors that occur after it.
6231cb0ef41Sopenharmony_ci            // This can happen normally, for example in timers tests.
6241cb0ef41Sopenharmony_ci            return;
6251cb0ef41Sopenharmony_ci          }
6261cb0ef41Sopenharmony_ci          this.fail(
6271cb0ef41Sopenharmony_ci            testFileName,
6281cb0ef41Sopenharmony_ci            {
6291cb0ef41Sopenharmony_ci              status: NODE_UNCAUGHT,
6301cb0ef41Sopenharmony_ci              name: 'evaluation in WPTRunner.runJsTests()',
6311cb0ef41Sopenharmony_ci              message: err.message,
6321cb0ef41Sopenharmony_ci              stack: inspect(err),
6331cb0ef41Sopenharmony_ci            },
6341cb0ef41Sopenharmony_ci            kUncaught,
6351cb0ef41Sopenharmony_ci          );
6361cb0ef41Sopenharmony_ci          this.inProgress.delete(testFileName);
6371cb0ef41Sopenharmony_ci        });
6381cb0ef41Sopenharmony_ci
6391cb0ef41Sopenharmony_ci        await events.once(worker, 'exit').catch(() => {});
6401cb0ef41Sopenharmony_ci      }
6411cb0ef41Sopenharmony_ci    }
6421cb0ef41Sopenharmony_ci
6431cb0ef41Sopenharmony_ci    process.on('exit', () => {
6441cb0ef41Sopenharmony_ci      for (const spec of this.inProgress) {
6451cb0ef41Sopenharmony_ci        this.fail(spec, { name: 'Incomplete' }, kIncomplete);
6461cb0ef41Sopenharmony_ci      }
6471cb0ef41Sopenharmony_ci      inspect.defaultOptions.depth = Infinity;
6481cb0ef41Sopenharmony_ci      // Sorts the rules to have consistent output
6491cb0ef41Sopenharmony_ci      console.log(JSON.stringify(Object.keys(this.results).sort().reduce(
6501cb0ef41Sopenharmony_ci        (obj, key) => {
6511cb0ef41Sopenharmony_ci          obj[key] = this.results[key];
6521cb0ef41Sopenharmony_ci          return obj;
6531cb0ef41Sopenharmony_ci        },
6541cb0ef41Sopenharmony_ci        {},
6551cb0ef41Sopenharmony_ci      ), null, 2));
6561cb0ef41Sopenharmony_ci
6571cb0ef41Sopenharmony_ci      const failures = [];
6581cb0ef41Sopenharmony_ci      let expectedFailures = 0;
6591cb0ef41Sopenharmony_ci      let skipped = 0;
6601cb0ef41Sopenharmony_ci      for (const [key, item] of Object.entries(this.results)) {
6611cb0ef41Sopenharmony_ci        if (item.fail?.unexpected) {
6621cb0ef41Sopenharmony_ci          failures.push(key);
6631cb0ef41Sopenharmony_ci        }
6641cb0ef41Sopenharmony_ci        if (item.fail?.expected) {
6651cb0ef41Sopenharmony_ci          expectedFailures++;
6661cb0ef41Sopenharmony_ci        }
6671cb0ef41Sopenharmony_ci        if (item.skip) {
6681cb0ef41Sopenharmony_ci          skipped++;
6691cb0ef41Sopenharmony_ci        }
6701cb0ef41Sopenharmony_ci      }
6711cb0ef41Sopenharmony_ci
6721cb0ef41Sopenharmony_ci      const unexpectedPasses = [];
6731cb0ef41Sopenharmony_ci      for (const specMap of queue) {
6741cb0ef41Sopenharmony_ci        const key = specMap.filename;
6751cb0ef41Sopenharmony_ci
6761cb0ef41Sopenharmony_ci        // File has no expected failures
6771cb0ef41Sopenharmony_ci        if (!specMap.failedTests.length) {
6781cb0ef41Sopenharmony_ci          continue;
6791cb0ef41Sopenharmony_ci        }
6801cb0ef41Sopenharmony_ci
6811cb0ef41Sopenharmony_ci        // File was (maybe even conditionally) skipped
6821cb0ef41Sopenharmony_ci        if (this.results[key]?.skip) {
6831cb0ef41Sopenharmony_ci          continue;
6841cb0ef41Sopenharmony_ci        }
6851cb0ef41Sopenharmony_ci
6861cb0ef41Sopenharmony_ci        // Full check: every expected to fail test is present
6871cb0ef41Sopenharmony_ci        if (specMap.failedTests.some((expectedToFail) => {
6881cb0ef41Sopenharmony_ci          if (specMap.flakyTests.includes(expectedToFail)) {
6891cb0ef41Sopenharmony_ci            return false;
6901cb0ef41Sopenharmony_ci          }
6911cb0ef41Sopenharmony_ci          return this.results[key]?.fail?.expected?.includes(expectedToFail) !== true;
6921cb0ef41Sopenharmony_ci        })) {
6931cb0ef41Sopenharmony_ci          unexpectedPasses.push(key);
6941cb0ef41Sopenharmony_ci          continue;
6951cb0ef41Sopenharmony_ci        }
6961cb0ef41Sopenharmony_ci      }
6971cb0ef41Sopenharmony_ci
6981cb0ef41Sopenharmony_ci      this.report?.write();
6991cb0ef41Sopenharmony_ci
7001cb0ef41Sopenharmony_ci      const ran = queue.length;
7011cb0ef41Sopenharmony_ci      const total = ran + skipped;
7021cb0ef41Sopenharmony_ci      const passed = ran - expectedFailures - failures.length;
7031cb0ef41Sopenharmony_ci      console.log(`Ran ${ran}/${total} tests, ${skipped} skipped,`,
7041cb0ef41Sopenharmony_ci                  `${passed} passed, ${expectedFailures} expected failures,`,
7051cb0ef41Sopenharmony_ci                  `${failures.length} unexpected failures,`,
7061cb0ef41Sopenharmony_ci                  `${unexpectedPasses.length} unexpected passes`);
7071cb0ef41Sopenharmony_ci      if (failures.length > 0) {
7081cb0ef41Sopenharmony_ci        const file = path.join('test', 'wpt', 'status', `${this.path}.json`);
7091cb0ef41Sopenharmony_ci        throw new Error(
7101cb0ef41Sopenharmony_ci          `Found ${failures.length} unexpected failures. ` +
7111cb0ef41Sopenharmony_ci          `Consider updating ${file} for these files:\n${failures.join('\n')}`);
7121cb0ef41Sopenharmony_ci      }
7131cb0ef41Sopenharmony_ci      if (unexpectedPasses.length > 0) {
7141cb0ef41Sopenharmony_ci        const file = path.join('test', 'wpt', 'status', `${this.path}.json`);
7151cb0ef41Sopenharmony_ci        throw new Error(
7161cb0ef41Sopenharmony_ci          `Found ${unexpectedPasses.length} unexpected passes. ` +
7171cb0ef41Sopenharmony_ci          `Consider updating ${file} for these files:\n${unexpectedPasses.join('\n')}`);
7181cb0ef41Sopenharmony_ci      }
7191cb0ef41Sopenharmony_ci    });
7201cb0ef41Sopenharmony_ci  }
7211cb0ef41Sopenharmony_ci
7221cb0ef41Sopenharmony_ci  getTestTitle(filename) {
7231cb0ef41Sopenharmony_ci    const spec = this.specMap.get(filename);
7241cb0ef41Sopenharmony_ci    return spec.meta?.title || filename.split('.')[0];
7251cb0ef41Sopenharmony_ci  }
7261cb0ef41Sopenharmony_ci
7271cb0ef41Sopenharmony_ci  // Map WPT test status to strings
7281cb0ef41Sopenharmony_ci  getTestStatus(status) {
7291cb0ef41Sopenharmony_ci    switch (status) {
7301cb0ef41Sopenharmony_ci      case 1:
7311cb0ef41Sopenharmony_ci        return kFail;
7321cb0ef41Sopenharmony_ci      case 2:
7331cb0ef41Sopenharmony_ci        return kTimeout;
7341cb0ef41Sopenharmony_ci      case 3:
7351cb0ef41Sopenharmony_ci        return kIncomplete;
7361cb0ef41Sopenharmony_ci      case NODE_UNCAUGHT:
7371cb0ef41Sopenharmony_ci        return kUncaught;
7381cb0ef41Sopenharmony_ci      default:
7391cb0ef41Sopenharmony_ci        return kPass;
7401cb0ef41Sopenharmony_ci    }
7411cb0ef41Sopenharmony_ci  }
7421cb0ef41Sopenharmony_ci
7431cb0ef41Sopenharmony_ci  /**
7441cb0ef41Sopenharmony_ci   * Report the status of each specific test case (there could be multiple
7451cb0ef41Sopenharmony_ci   * in one test file).
7461cb0ef41Sopenharmony_ci   * @param {string} filename
7471cb0ef41Sopenharmony_ci   * @param {Test} test  The Test object returned by WPT harness
7481cb0ef41Sopenharmony_ci   */
7491cb0ef41Sopenharmony_ci  resultCallback(filename, test, reportResult) {
7501cb0ef41Sopenharmony_ci    const status = this.getTestStatus(test.status);
7511cb0ef41Sopenharmony_ci    const title = this.getTestTitle(filename);
7521cb0ef41Sopenharmony_ci    if (/^Untitled( \d+)?$/.test(test.name)) {
7531cb0ef41Sopenharmony_ci      test.name = `${title}${test.name.slice(8)}`;
7541cb0ef41Sopenharmony_ci    }
7551cb0ef41Sopenharmony_ci    console.log(`---- ${title} ----`);
7561cb0ef41Sopenharmony_ci    if (status !== kPass) {
7571cb0ef41Sopenharmony_ci      this.fail(filename, test, status, reportResult);
7581cb0ef41Sopenharmony_ci    } else {
7591cb0ef41Sopenharmony_ci      this.succeed(filename, test, status, reportResult);
7601cb0ef41Sopenharmony_ci    }
7611cb0ef41Sopenharmony_ci  }
7621cb0ef41Sopenharmony_ci
7631cb0ef41Sopenharmony_ci  /**
7641cb0ef41Sopenharmony_ci   * Report the status of each WPT test (one per file)
7651cb0ef41Sopenharmony_ci   * @param {string} filename
7661cb0ef41Sopenharmony_ci   * @param {object} harnessStatus - The status object returned by WPT harness.
7671cb0ef41Sopenharmony_ci   */
7681cb0ef41Sopenharmony_ci  completionCallback(filename, harnessStatus) {
7691cb0ef41Sopenharmony_ci    const status = this.getTestStatus(harnessStatus.status);
7701cb0ef41Sopenharmony_ci
7711cb0ef41Sopenharmony_ci    // Treat it like a test case failure
7721cb0ef41Sopenharmony_ci    if (status === kTimeout) {
7731cb0ef41Sopenharmony_ci      this.fail(filename, { name: 'WPT testharness timeout' }, kTimeout);
7741cb0ef41Sopenharmony_ci    }
7751cb0ef41Sopenharmony_ci    this.inProgress.delete(filename);
7761cb0ef41Sopenharmony_ci    // Always force termination of the worker. Some tests allocate resources
7771cb0ef41Sopenharmony_ci    // that would otherwise keep it alive.
7781cb0ef41Sopenharmony_ci    this.workers.get(filename).terminate();
7791cb0ef41Sopenharmony_ci  }
7801cb0ef41Sopenharmony_ci
7811cb0ef41Sopenharmony_ci  addTestResult(filename, item) {
7821cb0ef41Sopenharmony_ci    let result = this.results[filename];
7831cb0ef41Sopenharmony_ci    if (!result) {
7841cb0ef41Sopenharmony_ci      result = this.results[filename] = {};
7851cb0ef41Sopenharmony_ci    }
7861cb0ef41Sopenharmony_ci    if (item.status === kSkip) {
7871cb0ef41Sopenharmony_ci      // { filename: { skip: 'reason' } }
7881cb0ef41Sopenharmony_ci      result[kSkip] = item.reason;
7891cb0ef41Sopenharmony_ci    } else {
7901cb0ef41Sopenharmony_ci      // { filename: { fail: { expected: [ ... ],
7911cb0ef41Sopenharmony_ci      //                      unexpected: [ ... ] } }}
7921cb0ef41Sopenharmony_ci      if (!result[item.status]) {
7931cb0ef41Sopenharmony_ci        result[item.status] = {};
7941cb0ef41Sopenharmony_ci      }
7951cb0ef41Sopenharmony_ci      const key = item.expected ? 'expected' : 'unexpected';
7961cb0ef41Sopenharmony_ci      if (!result[item.status][key]) {
7971cb0ef41Sopenharmony_ci        result[item.status][key] = [];
7981cb0ef41Sopenharmony_ci      }
7991cb0ef41Sopenharmony_ci      const hasName = result[item.status][key].includes(item.name);
8001cb0ef41Sopenharmony_ci      if (!hasName) {
8011cb0ef41Sopenharmony_ci        result[item.status][key].push(item.name);
8021cb0ef41Sopenharmony_ci      }
8031cb0ef41Sopenharmony_ci    }
8041cb0ef41Sopenharmony_ci  }
8051cb0ef41Sopenharmony_ci
8061cb0ef41Sopenharmony_ci  succeed(filename, test, status, reportResult) {
8071cb0ef41Sopenharmony_ci    console.log(`[${status.toUpperCase()}] ${test.name}`);
8081cb0ef41Sopenharmony_ci    reportResult?.addSubtest(test.name, 'PASS');
8091cb0ef41Sopenharmony_ci  }
8101cb0ef41Sopenharmony_ci
8111cb0ef41Sopenharmony_ci  fail(filename, test, status, reportResult) {
8121cb0ef41Sopenharmony_ci    const spec = this.specMap.get(filename);
8131cb0ef41Sopenharmony_ci    const expected = spec.failedTests.includes(test.name);
8141cb0ef41Sopenharmony_ci    if (expected) {
8151cb0ef41Sopenharmony_ci      console.log(`[EXPECTED_FAILURE][${status.toUpperCase()}] ${test.name}`);
8161cb0ef41Sopenharmony_ci      console.log(test.message || status);
8171cb0ef41Sopenharmony_ci    } else {
8181cb0ef41Sopenharmony_ci      console.log(`[UNEXPECTED_FAILURE][${status.toUpperCase()}] ${test.name}`);
8191cb0ef41Sopenharmony_ci    }
8201cb0ef41Sopenharmony_ci    if (status === kFail || status === kUncaught) {
8211cb0ef41Sopenharmony_ci      console.log(test.message);
8221cb0ef41Sopenharmony_ci      console.log(test.stack);
8231cb0ef41Sopenharmony_ci    }
8241cb0ef41Sopenharmony_ci    const command = `${process.execPath} ${process.execArgv}` +
8251cb0ef41Sopenharmony_ci                    ` ${require.main.filename} ${filename}`;
8261cb0ef41Sopenharmony_ci    console.log(`Command: ${command}\n`);
8271cb0ef41Sopenharmony_ci
8281cb0ef41Sopenharmony_ci    reportResult?.addSubtest(test.name, 'FAIL', test.message);
8291cb0ef41Sopenharmony_ci
8301cb0ef41Sopenharmony_ci    this.addTestResult(filename, {
8311cb0ef41Sopenharmony_ci      name: test.name,
8321cb0ef41Sopenharmony_ci      expected,
8331cb0ef41Sopenharmony_ci      status: kFail,
8341cb0ef41Sopenharmony_ci      reason: test.message || status,
8351cb0ef41Sopenharmony_ci    });
8361cb0ef41Sopenharmony_ci  }
8371cb0ef41Sopenharmony_ci
8381cb0ef41Sopenharmony_ci  skip(filename, reasons) {
8391cb0ef41Sopenharmony_ci    const title = this.getTestTitle(filename);
8401cb0ef41Sopenharmony_ci    console.log(`---- ${title} ----`);
8411cb0ef41Sopenharmony_ci    const joinedReasons = reasons.join('; ');
8421cb0ef41Sopenharmony_ci    console.log(`[SKIPPED] ${joinedReasons}`);
8431cb0ef41Sopenharmony_ci    this.addTestResult(filename, {
8441cb0ef41Sopenharmony_ci      status: kSkip,
8451cb0ef41Sopenharmony_ci      reason: joinedReasons,
8461cb0ef41Sopenharmony_ci    });
8471cb0ef41Sopenharmony_ci  }
8481cb0ef41Sopenharmony_ci
8491cb0ef41Sopenharmony_ci  getMeta(code) {
8501cb0ef41Sopenharmony_ci    const matches = code.match(/\/\/ META: .+/g);
8511cb0ef41Sopenharmony_ci    if (!matches) {
8521cb0ef41Sopenharmony_ci      return {};
8531cb0ef41Sopenharmony_ci    }
8541cb0ef41Sopenharmony_ci    const result = {};
8551cb0ef41Sopenharmony_ci    for (const match of matches) {
8561cb0ef41Sopenharmony_ci      const parts = match.match(/\/\/ META: ([^=]+?)=(.+)/);
8571cb0ef41Sopenharmony_ci      const key = parts[1];
8581cb0ef41Sopenharmony_ci      const value = parts[2];
8591cb0ef41Sopenharmony_ci      if (key === 'script' || key === 'variant') {
8601cb0ef41Sopenharmony_ci        if (result[key]) {
8611cb0ef41Sopenharmony_ci          result[key].push(value);
8621cb0ef41Sopenharmony_ci        } else {
8631cb0ef41Sopenharmony_ci          result[key] = [value];
8641cb0ef41Sopenharmony_ci        }
8651cb0ef41Sopenharmony_ci      } else {
8661cb0ef41Sopenharmony_ci        result[key] = value;
8671cb0ef41Sopenharmony_ci      }
8681cb0ef41Sopenharmony_ci    }
8691cb0ef41Sopenharmony_ci    return result;
8701cb0ef41Sopenharmony_ci  }
8711cb0ef41Sopenharmony_ci
8721cb0ef41Sopenharmony_ci  buildQueue() {
8731cb0ef41Sopenharmony_ci    const queue = [];
8741cb0ef41Sopenharmony_ci    for (const spec of this.specMap.values()) {
8751cb0ef41Sopenharmony_ci      const filename = spec.filename;
8761cb0ef41Sopenharmony_ci      if (spec.skipReasons.length > 0) {
8771cb0ef41Sopenharmony_ci        this.skip(filename, spec.skipReasons);
8781cb0ef41Sopenharmony_ci        continue;
8791cb0ef41Sopenharmony_ci      }
8801cb0ef41Sopenharmony_ci
8811cb0ef41Sopenharmony_ci      const lackingIntl = intlRequirements.isLacking(spec.requires);
8821cb0ef41Sopenharmony_ci      if (lackingIntl) {
8831cb0ef41Sopenharmony_ci        this.skip(filename, [ `requires ${lackingIntl}` ]);
8841cb0ef41Sopenharmony_ci        continue;
8851cb0ef41Sopenharmony_ci      }
8861cb0ef41Sopenharmony_ci
8871cb0ef41Sopenharmony_ci      queue.push(spec);
8881cb0ef41Sopenharmony_ci    }
8891cb0ef41Sopenharmony_ci    return queue;
8901cb0ef41Sopenharmony_ci  }
8911cb0ef41Sopenharmony_ci}
8921cb0ef41Sopenharmony_ci
8931cb0ef41Sopenharmony_cimodule.exports = {
8941cb0ef41Sopenharmony_ci  harness: harnessMock,
8951cb0ef41Sopenharmony_ci  ResourceLoader,
8961cb0ef41Sopenharmony_ci  WPTRunner,
8971cb0ef41Sopenharmony_ci};
898