Skip to content

Commit

Permalink
test: report error wpt test results
Browse files Browse the repository at this point in the history
When a wpt test file is exited for uncaught error, its result should be
recorded in the `wptreport.json` and uploaded to wpt.fyi.

For instance, `html/webappapis/timers/evil-spec-example.any.js` is
exited for uncaught error in Node.js but it shows as "MISSING" at
https://wpt.fyi/results/html/webappapis/timers?label=master&label=experimental&product=chrome&product=node.js&aligned.
  • Loading branch information
legendecas committed Oct 27, 2023
1 parent 6431c65 commit 780117a
Showing 1 changed file with 65 additions and 28 deletions.
93 changes: 65 additions & 28 deletions test/common/wpt.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,39 +58,60 @@ function codeUnitStr(char) {
return 'U+' + char.charCodeAt(0).toString(16);
}

class ReportResult {
constructor(name) {
this.test = name;
this.status = 'OK';
this.subtests = [];
}

addSubtest(name, status, message) {
const subtest = {
status,
// https://github.com/web-platform-tests/wpt/blob/b24eedd/resources/testharness.js#L3722
name: sanitizeUnpairedSurrogates(name),
};
if (message) {
// https://github.com/web-platform-tests/wpt/blob/b24eedd/resources/testharness.js#L4506
subtest.message = sanitizeUnpairedSurrogates(message);
}
this.subtests.push(subtest);
return subtest;
}

finish(status) {
this.status = status ?? 'OK';
}
}

// Generates a report that can be uploaded to wpt.fyi.
// Checkout https://github.com/web-platform-tests/wpt.fyi/tree/main/api#results-creation
// for more details.
class WPTReport {
constructor(path) {
this.filename = `report-${path.replaceAll('/', '-')}.json`;
this.results = [];
/** @type {Map<string, ReportResult>} */
this.results = new Map();
this.time_start = Date.now();
}

addResult(name, status) {
const result = {
test: name,
status,
subtests: [],
addSubtest(name, status, message) {
const subtest = {
status,
// https://github.com/web-platform-tests/wpt/blob/b24eedd/resources/testharness.js#L3722
name: sanitizeUnpairedSurrogates(name),
};
if (message) {
// https://github.com/web-platform-tests/wpt/blob/b24eedd/resources/testharness.js#L4506
subtest.message = sanitizeUnpairedSurrogates(message);
}
this.subtests.push(subtest);
return subtest;
},
};
this.results.push(result);
/**
* Get or create a ReportResult for a test spec.
* @param {WPTTestSpec} spec
*/
getResult(spec) {
const name = `/${spec.getRelativePath()}${spec.variant}`;
if (this.results.has(name)) {
return this.results.get(name);
}
const result = new ReportResult(name);
this.results.set(name, result);
return result;
}

write() {
this.time_end = Date.now();
this.results = this.results.filter((result) => {
const results = Array.from(this.results.values()).filter((result) => {
return result.status === 'SKIP' || result.subtests.length !== 0;
}).map((result) => {
const url = new URL(result.test, 'http://wpt');
Expand All @@ -110,7 +131,12 @@ class WPTReport {
os: getOs(),
};

fs.writeFileSync(`out/wpt/${this.filename}`, JSON.stringify(this));
fs.writeFileSync(`out/wpt/${this.filename}`, JSON.stringify({
time_start: this.time_start,
time_end: this.time_end,
run_info: this.run_info,
results: results,
}));
}
}

Expand Down Expand Up @@ -642,14 +668,13 @@ class WPTRunner {
this.inProgress.add(spec);
this.workers.set(spec, worker);

let reportResult;
const reportResult = this.report?.getResult(spec);
worker.on('message', (message) => {
switch (message.type) {
case 'result':
reportResult ||= this.report?.addResult(`/${relativePath}${spec.variant}`, 'OK');
return this.resultCallback(spec, message.result, reportResult);
case 'completion':
return this.completionCallback(spec, message.status);
return this.completionCallback(spec, message.status, reportResult);
default:
throw new Error(`Unexpected message from worker: ${message.type}`);
}
Expand All @@ -661,6 +686,7 @@ class WPTRunner {
// This can happen normally, for example in timers tests.
return;
}
// Generate a subtest failure for visibility.
this.fail(
spec,
{
Expand All @@ -670,7 +696,10 @@ class WPTRunner {
stack: inspect(err),
},
kUncaught,
reportResult,
);
// Mark the whole test as failed in wpt.fyi report.
reportResult?.finish('ERROR');
this.inProgress.delete(spec);
});

Expand All @@ -681,6 +710,9 @@ class WPTRunner {
process.on('exit', () => {
for (const spec of this.inProgress) {
this.fail(spec, { name: 'Incomplete' }, kIncomplete);
// Mark the whole test as failed in wpt.fyi report.
const reportResult = this.report?.getResult(spec);
reportResult.finish('ERROR');
}
inspect.defaultOptions.depth = Infinity;
// Sorts the rules to have consistent output
Expand Down Expand Up @@ -780,6 +812,7 @@ class WPTRunner {
* in one test file).
* @param {WPTTestSpec} spec
* @param {Test} test The Test object returned by WPT harness
* @param {ReportResult} reportResult The report result object
*/
resultCallback(spec, test, reportResult) {
const status = this.getTestStatus(test.status);
Expand All @@ -794,13 +827,17 @@ class WPTRunner {
* Report the status of each WPT test (one per file)
* @param {WPTTestSpec} spec
* @param {object} harnessStatus - The status object returned by WPT harness.
* @param {ReportResult} reportResult The report result object
*/
completionCallback(spec, harnessStatus) {
completionCallback(spec, harnessStatus, reportResult) {
const status = this.getTestStatus(harnessStatus.status);

// Treat it like a test case failure
if (status === kTimeout) {
this.fail(spec, { name: 'WPT testharness timeout' }, kTimeout);
this.fail(spec, { name: 'WPT testharness timeout' }, kTimeout, reportResult);
reportResult?.finish('TIMEOUT');
} else {
reportResult?.finish();
}
this.inProgress.delete(spec);
// Always force termination of the worker. Some tests allocate resources
Expand Down

0 comments on commit 780117a

Please sign in to comment.