diff --git a/test/wpt/runner/runner/runner.mjs b/test/wpt/runner/runner/runner.mjs index 1a17ca6f7a0..ba8c75e6893 100644 --- a/test/wpt/runner/runner/runner.mjs +++ b/test/wpt/runner/runner/runner.mjs @@ -1,15 +1,34 @@ import { deepStrictEqual } from 'node:assert' import { EventEmitter, once } from 'node:events' -import { readdirSync, readFileSync, statSync } from 'node:fs' +import { readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs' import { basename, isAbsolute, join, resolve } from 'node:path' import { fileURLToPath } from 'node:url' import { Worker } from 'node:worker_threads' import { parseMeta, handlePipes, normalizeName, colors } from './util.mjs' +const { WPT_REPORT } = process.env const basePath = fileURLToPath(join(import.meta.url, '../../..')) const testPath = join(basePath, 'tests') const statusPath = join(basePath, 'status') +// https://github.com/web-platform-tests/wpt/blob/b24eedd/resources/testharness.js#L3705 +function sanitizeUnpairedSurrogates (str) { + return str.replace( + /([\ud800-\udbff]+)(?![\udc00-\udfff])|(^|[^\ud800-\udbff])([\udc00-\udfff]+)/g, + function (_, low, prefix, high) { + let output = prefix || '' // Prefix may be undefined + const string = low || high // Only one of these alternates can match + for (let i = 0; i < string.length; i++) { + output += codeUnitStr(string[i]) + } + return output + }) +} + +function codeUnitStr (char) { + return 'U+' + char.charCodeAt(0).toString(16) +} + export class WPTRunner extends EventEmitter { /** @type {string} */ #folderName @@ -134,13 +153,29 @@ export class WPTRunner extends EventEmitter { } }) + let result, report + if (WPT_REPORT) { + report = JSON.parse(readFileSync(WPT_REPORT)) + + const fileUrl = new URL(`/${this.#folderName}${test.slice(this.#folderPath.length)}`, 'http://wpt') + fileUrl.pathname = fileUrl.pathname.replace(/\.js$/, '.html') + fileUrl.search = variant + + result = { + test: fileUrl.href.slice(fileUrl.origin.length), + subtests: [], + status: 'OK' + } + report.results.push(result) + } + activeWorkers.add(worker) // These values come directly from the web-platform-tests const timeout = meta.timeout === 'long' ? 60_000 : 10_000 worker.on('message', (message) => { if (message.type === 'result') { - this.handleIndividualTestCompletion(message, basename(test)) + this.handleIndividualTestCompletion(message, basename(test), result) } else if (message.type === 'completion') { this.handleTestCompletion(worker) } @@ -159,6 +194,10 @@ export class WPTRunner extends EventEmitter { console.log(`Test took ${(performance.now() - start).toFixed(2)}ms`) console.log('='.repeat(96)) + if (result?.subtests.length > 0) { + writeFileSync(WPT_REPORT, JSON.stringify(report)) + } + finishedFiles++ } catch (e) { console.log(`${test} timed out after ${timeout}ms`) @@ -173,7 +212,7 @@ export class WPTRunner extends EventEmitter { /** * Called after a test has succeeded or failed. */ - handleIndividualTestCompletion (message, fileName) { + handleIndividualTestCompletion (message, fileName, wptResult) { const { fail, allowUnexpectedFailures, flaky } = this.#status[fileName] ?? this.#status if (message.type === 'result') { @@ -182,6 +221,12 @@ export class WPTRunner extends EventEmitter { if (message.result.status === 1) { this.#stats.failed += 1 + wptResult?.subtests.push({ + status: 'FAIL', + name: sanitizeUnpairedSurrogates(message.result.name), + message: sanitizeUnpairedSurrogates(message.result.message) + }) + const name = normalizeName(message.result.name) if (flaky?.includes(name)) { @@ -197,6 +242,10 @@ export class WPTRunner extends EventEmitter { console.error(message.result) } } else { + wptResult?.subtests.push({ + status: 'PASS', + name: sanitizeUnpairedSurrogates(message.result.name) + }) this.#stats.success += 1 } }