Skip to content

Commit 006e7aa

Browse files
authored
test: allow skipping individual WPT subtests
Signed-off-by: Filip Skokan <panva.ip@gmail.com> PR-URL: #62517 Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
1 parent 68e5f87 commit 006e7aa

File tree

3 files changed

+112
-4
lines changed

3 files changed

+112
-4
lines changed

test/common/wpt.js

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,7 @@ class StatusRule {
236236
this.requires = value.requires || [];
237237
this.fail = value.fail;
238238
this.skip = value.skip;
239+
this.skipTests = value.skipTests;
239240
if (pattern) {
240241
this.pattern = this.transformPattern(pattern);
241242
}
@@ -312,6 +313,7 @@ class WPTTestSpec {
312313
this.failedTests = [];
313314
this.flakyTests = [];
314315
this.skipReasons = [];
316+
this.skippedTests = [];
315317
for (const item of rules) {
316318
if (item.requires.length) {
317319
for (const req of item.requires) {
@@ -328,6 +330,9 @@ class WPTTestSpec {
328330
if (item.skip) {
329331
this.skipReasons.push(item.skip);
330332
}
333+
if (Array.isArray(item.skipTests)) {
334+
this.skippedTests.push(...item.skipTests);
335+
}
331336
}
332337

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

355+
/**
356+
* Check if a subtest should be skipped by name.
357+
* @param {string} name
358+
* @returns {boolean}
359+
*/
360+
isSkippedTest(name) {
361+
for (const matcher of this.skippedTests) {
362+
if (typeof matcher === 'string') {
363+
if (name === matcher) return true;
364+
} else if (matcher.test(name)) {
365+
return true;
366+
}
367+
}
368+
return false;
369+
}
370+
350371
getRelativePath() {
351372
return path.join(this.module, this.filename);
352373
}
@@ -684,6 +705,7 @@ class WPTRunner {
684705
},
685706
scriptsToRun,
686707
needsGc: !!meta.script?.find((script) => script === '/common/gc.js'),
708+
skippedTests: spec.skippedTests,
687709
},
688710
});
689711
this.inProgress.add(spec);
@@ -694,6 +716,8 @@ class WPTRunner {
694716
switch (message.type) {
695717
case 'result':
696718
return this.resultCallback(spec, message.result, reportResult);
719+
case 'skip':
720+
return this.skipTest(spec, { name: message.name }, reportResult);
697721
case 'completion':
698722
return this.completionCallback(spec, message.status, reportResult);
699723
default:
@@ -751,6 +775,7 @@ class WPTRunner {
751775
const failures = [];
752776
let expectedFailures = 0;
753777
let skipped = 0;
778+
let skippedTests = 0;
754779
for (const [key, item] of Object.entries(this.results)) {
755780
if (item.fail?.unexpected) {
756781
failures.push(key);
@@ -761,6 +786,9 @@ class WPTRunner {
761786
if (item.skip) {
762787
skipped++;
763788
}
789+
if (item.skipTests) {
790+
skippedTests += item.skipTests.length;
791+
}
764792
}
765793

766794
const unexpectedPasses = [];
@@ -801,7 +829,8 @@ class WPTRunner {
801829
console.log(`Ran ${ran}/${total} tests, ${skipped} skipped,`,
802830
`${passed} passed, ${expectedFailures} expected failures,`,
803831
`${failures.length} unexpected failures,`,
804-
`${unexpectedPasses.length} unexpected passes`);
832+
`${unexpectedPasses.length} unexpected passes` +
833+
(skippedTests ? `, ${skippedTests} subtests skipped` : ''));
805834
if (failures.length > 0) {
806835
const file = path.join('test', 'wpt', 'status', `${this.path}.json`);
807836
throw new Error(
@@ -890,8 +919,16 @@ class WPTRunner {
890919
let result = this.results[spec.filename];
891920
result ||= this.results[spec.filename] = {};
892921
if (item.status === kSkip) {
893-
// { filename: { skip: 'reason' } }
894-
result[kSkip] = item.reason;
922+
if (item.name) {
923+
// Subtest-level skip: { filename: { skipTests: [ ... ] } }
924+
result.skipTests ||= [];
925+
if (!result.skipTests.includes(item.name)) {
926+
result.skipTests.push(item.name);
927+
}
928+
} else {
929+
// File-level skip: { filename: { skip: 'reason' } }
930+
result[kSkip] = item.reason;
931+
}
895932
} else {
896933
// { filename: { fail: { expected: [ ... ],
897934
// unexpected: [ ... ] } }}
@@ -910,6 +947,15 @@ class WPTRunner {
910947
reportResult?.addSubtest(test.name, 'PASS');
911948
}
912949

950+
skipTest(spec, test, reportResult) {
951+
console.log(`[SKIP] ${test.name}`);
952+
reportResult?.addSubtest(test.name, 'NOTRUN');
953+
this.addTestResult(spec, {
954+
name: test.name,
955+
status: kSkip,
956+
});
957+
}
958+
913959
fail(spec, test, status, reportResult) {
914960
const expected = spec.failedTests.includes(test.name);
915961
if (expected) {

test/common/wpt/worker.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,32 @@ runInThisContext(workerData.harness.code, {
4242
importModuleDynamically: USE_MAIN_CONTEXT_DEFAULT_LOADER,
4343
});
4444

45+
// If there are skip patterns, wrap test functions to prevent execution of
46+
// matching tests. This must happen after testharness.js is loaded but before
47+
// the test scripts run.
48+
if (workerData.skippedTests?.length) {
49+
function isSkipped(name) {
50+
for (const matcher of workerData.skippedTests) {
51+
if (typeof matcher === 'string') {
52+
if (name === matcher) return true;
53+
} else if (matcher.test(name)) {
54+
return true;
55+
}
56+
}
57+
return false;
58+
}
59+
for (const fn of ['test', 'async_test', 'promise_test']) {
60+
const original = globalThis[fn];
61+
globalThis[fn] = function(func, name, ...rest) {
62+
if (typeof name === 'string' && isSkipped(name)) {
63+
parentPort.postMessage({ type: 'skip', name });
64+
return;
65+
}
66+
return original.call(this, func, name, ...rest);
67+
};
68+
}
69+
}
70+
4571
// eslint-disable-next-line no-undef
4672
add_result_callback((result) => {
4773
parentPort.postMessage({

test/wpt/README.md

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ expected failures.
155155
// Optional: If the requirement is not met, this test will be skipped
156156
"requires": ["small-icu"], // supports: "small-icu", "full-icu", "crypto"
157157

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

161161
// Optional: failing tests
@@ -173,6 +173,42 @@ expected failures.
173173
}
174174
```
175175

176+
### Skipping individual subtests
177+
178+
To skip specific subtests within a file (rather than skipping the entire file),
179+
use `skipTests` with an array of exact test names:
180+
181+
```json
182+
{
183+
"something.scope.js": {
184+
"skipTests": [
185+
"exact test name to skip"
186+
]
187+
}
188+
}
189+
```
190+
191+
When the status file is a CJS module, regular expressions can also be used:
192+
193+
```js
194+
module.exports = {
195+
'something.scope.js': {
196+
'skipTests': [
197+
'exact test name to skip',
198+
/regexp pattern to match/,
199+
],
200+
},
201+
};
202+
```
203+
204+
Skipped subtests are reported as `[SKIP]` in the output, recorded as `NOTRUN`
205+
in the WPT report, and counted separately in the summary line.
206+
207+
This is useful for skipping a particular subtest that crashes the runner,
208+
which would otherwise prevent the rest of the file from being run. When using
209+
CJS status files, this also enables conditionally skipping slow or
210+
resource-heavy subtests in CI on specific architectures.
211+
176212
A test may have to be skipped because it depends on another irrelevant
177213
Web API, or certain harness has not been ported in our test runner yet.
178214
In that case it needs to be marked with `skip` instead of `fail`.

0 commit comments

Comments
 (0)