Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 49 additions & 3 deletions test/common/wpt.js
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ class StatusRule {
this.requires = value.requires || [];
this.fail = value.fail;
this.skip = value.skip;
this.skipTests = value.skipTests;
if (pattern) {
this.pattern = this.transformPattern(pattern);
}
Expand Down Expand Up @@ -312,6 +313,7 @@ class WPTTestSpec {
this.failedTests = [];
this.flakyTests = [];
this.skipReasons = [];
this.skippedTests = [];
for (const item of rules) {
if (item.requires.length) {
for (const req of item.requires) {
Expand All @@ -328,6 +330,9 @@ class WPTTestSpec {
if (item.skip) {
this.skipReasons.push(item.skip);
}
if (Array.isArray(item.skipTests)) {
this.skippedTests.push(...item.skipTests);
}
}

this.failedTests = [...new Set(this.failedTests)];
Expand All @@ -347,6 +352,22 @@ class WPTTestSpec {
return meta.variant?.map((variant) => new WPTTestSpec(mod, filename, rules, variant)) || [spec];
}

/**
* Check if a subtest should be skipped by name.
* @param {string} name
* @returns {boolean}
*/
isSkippedTest(name) {
for (const matcher of this.skippedTests) {
if (typeof matcher === 'string') {
if (name === matcher) return true;
} else if (matcher.test(name)) {
return true;
}
}
return false;
}

getRelativePath() {
return path.join(this.module, this.filename);
}
Expand Down Expand Up @@ -684,6 +705,7 @@ class WPTRunner {
},
scriptsToRun,
needsGc: !!meta.script?.find((script) => script === '/common/gc.js'),
skippedTests: spec.skippedTests,
},
});
this.inProgress.add(spec);
Expand All @@ -694,6 +716,8 @@ class WPTRunner {
switch (message.type) {
case 'result':
return this.resultCallback(spec, message.result, reportResult);
case 'skip':
return this.skipTest(spec, { name: message.name }, reportResult);
case 'completion':
return this.completionCallback(spec, message.status, reportResult);
default:
Expand Down Expand Up @@ -751,6 +775,7 @@ class WPTRunner {
const failures = [];
let expectedFailures = 0;
let skipped = 0;
let skippedTests = 0;
for (const [key, item] of Object.entries(this.results)) {
if (item.fail?.unexpected) {
failures.push(key);
Expand All @@ -761,6 +786,9 @@ class WPTRunner {
if (item.skip) {
skipped++;
}
if (item.skipTests) {
skippedTests += item.skipTests.length;
}
}

const unexpectedPasses = [];
Expand Down Expand Up @@ -801,7 +829,8 @@ class WPTRunner {
console.log(`Ran ${ran}/${total} tests, ${skipped} skipped,`,
`${passed} passed, ${expectedFailures} expected failures,`,
`${failures.length} unexpected failures,`,
`${unexpectedPasses.length} unexpected passes`);
`${unexpectedPasses.length} unexpected passes` +
(skippedTests ? `, ${skippedTests} subtests skipped` : ''));
if (failures.length > 0) {
const file = path.join('test', 'wpt', 'status', `${this.path}.json`);
throw new Error(
Expand Down Expand Up @@ -890,8 +919,16 @@ class WPTRunner {
let result = this.results[spec.filename];
result ||= this.results[spec.filename] = {};
if (item.status === kSkip) {
// { filename: { skip: 'reason' } }
result[kSkip] = item.reason;
if (item.name) {
// Subtest-level skip: { filename: { skipTests: [ ... ] } }
result.skipTests ||= [];
if (!result.skipTests.includes(item.name)) {
result.skipTests.push(item.name);
}
Comment on lines +924 to +927
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we use a Set here?

Suggested change
result.skipTests ||= [];
if (!result.skipTests.includes(item.name)) {
result.skipTests.push(item.name);
}
(result.skipTests ??= new Set()).add(item.name);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not easily, i don't think so.

} else {
// File-level skip: { filename: { skip: 'reason' } }
result[kSkip] = item.reason;
}
} else {
// { filename: { fail: { expected: [ ... ],
// unexpected: [ ... ] } }}
Expand All @@ -910,6 +947,15 @@ class WPTRunner {
reportResult?.addSubtest(test.name, 'PASS');
}

skipTest(spec, test, reportResult) {
console.log(`[SKIP] ${test.name}`);
reportResult?.addSubtest(test.name, 'NOTRUN');
this.addTestResult(spec, {
name: test.name,
status: kSkip,
});
}

fail(spec, test, status, reportResult) {
const expected = spec.failedTests.includes(test.name);
if (expected) {
Expand Down
26 changes: 26 additions & 0 deletions test/common/wpt/worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,32 @@ runInThisContext(workerData.harness.code, {
filename: workerData.harness.filename,
});

// If there are skip patterns, wrap test functions to prevent execution of
// matching tests. This must happen after testharness.js is loaded but before
// the test scripts run.
if (workerData.skippedTests?.length) {
function isSkipped(name) {
for (const matcher of workerData.skippedTests) {
if (typeof matcher === 'string') {
if (name === matcher) return true;
} else if (matcher.test(name)) {
return true;
}
}
return false;
}
for (const fn of ['test', 'async_test', 'promise_test']) {
const original = globalThis[fn];
globalThis[fn] = function(func, name, ...rest) {
if (typeof name === 'string' && isSkipped(name)) {
parentPort.postMessage({ type: 'skip', name });
return;
}
return original.call(this, func, name, ...rest);
};
}
}

// eslint-disable-next-line no-undef
add_result_callback((result) => {
parentPort.postMessage({
Expand Down
38 changes: 37 additions & 1 deletion test/wpt/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ expected failures.
// Optional: If the requirement is not met, this test will be skipped
"requires": ["small-icu"], // supports: "small-icu", "full-icu", "crypto"

// Optional: the test will be skipped with the reason printed
// Optional: the entire file will be skipped with the reason printed
"skip": "explain why we cannot run a test that's supposed to pass",

// Optional: failing tests
Expand All @@ -173,6 +173,42 @@ expected failures.
}
```

### Skipping individual subtests

To skip specific subtests within a file (rather than skipping the entire file),
use `skipTests` with an array of exact test names:

```json
{
"something.scope.js": {
"skipTests": [
"exact test name to skip"
]
}
}
```

When the status file is a CJS module, regular expressions can also be used:

```js
module.exports = {
'something.scope.js': {
'skipTests': [
'exact test name to skip',
/regexp pattern to match/,
],
},
};
```

Skipped subtests are reported as `[SKIP]` in the output, recorded as `NOTRUN`
in the WPT report, and counted separately in the summary line.

This is useful for skipping a particular subtest that crashes the runner,
which would otherwise prevent the rest of the file from being run. When using
CJS status files, this also enables conditionally skipping slow or
resource-heavy subtests in CI on specific architectures.

A test may have to be skipped because it depends on another irrelevant
Web API, or certain harness has not been ported in our test runner yet.
In that case it needs to be marked with `skip` instead of `fail`.
Expand Down
Loading