Skip to content

Commit 5ff8428

Browse files
panvajuanarbol
authored andcommitted
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 5e40c7d commit 5ff8428

3 files changed

Lines changed: 112 additions & 4 deletions

File tree

test/common/wpt.js

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,7 @@ class StatusRule {
224224
this.requires = value.requires || [];
225225
this.fail = value.fail;
226226
this.skip = value.skip;
227+
this.skipTests = value.skipTests;
227228
if (pattern) {
228229
this.pattern = this.transformPattern(pattern);
229230
}
@@ -300,6 +301,7 @@ class WPTTestSpec {
300301
this.failedTests = [];
301302
this.flakyTests = [];
302303
this.skipReasons = [];
304+
this.skippedTests = [];
303305
for (const item of rules) {
304306
if (item.requires.length) {
305307
for (const req of item.requires) {
@@ -316,6 +318,9 @@ class WPTTestSpec {
316318
if (item.skip) {
317319
this.skipReasons.push(item.skip);
318320
}
321+
if (Array.isArray(item.skipTests)) {
322+
this.skippedTests.push(...item.skipTests);
323+
}
319324
}
320325

321326
this.failedTests = [...new Set(this.failedTests)];
@@ -334,6 +339,22 @@ class WPTTestSpec {
334339
return meta.variant?.map((variant) => new WPTTestSpec(mod, filename, rules, variant)) || [spec];
335340
}
336341

342+
/**
343+
* Check if a subtest should be skipped by name.
344+
* @param {string} name
345+
* @returns {boolean}
346+
*/
347+
isSkippedTest(name) {
348+
for (const matcher of this.skippedTests) {
349+
if (typeof matcher === 'string') {
350+
if (name === matcher) return true;
351+
} else if (matcher.test(name)) {
352+
return true;
353+
}
354+
}
355+
return false;
356+
}
357+
337358
getRelativePath() {
338359
return path.join(this.module, this.filename);
339360
}
@@ -669,6 +690,7 @@ class WPTRunner {
669690
},
670691
scriptsToRun,
671692
needsGc: !!meta.script?.find((script) => script === '/common/gc.js'),
693+
skippedTests: spec.skippedTests,
672694
},
673695
});
674696
this.inProgress.add(spec);
@@ -679,6 +701,8 @@ class WPTRunner {
679701
switch (message.type) {
680702
case 'result':
681703
return this.resultCallback(spec, message.result, reportResult);
704+
case 'skip':
705+
return this.skipTest(spec, { name: message.name }, reportResult);
682706
case 'completion':
683707
return this.completionCallback(spec, message.status, reportResult);
684708
default:
@@ -736,6 +760,7 @@ class WPTRunner {
736760
const failures = [];
737761
let expectedFailures = 0;
738762
let skipped = 0;
763+
let skippedTests = 0;
739764
for (const [key, item] of Object.entries(this.results)) {
740765
if (item.fail?.unexpected) {
741766
failures.push(key);
@@ -746,6 +771,9 @@ class WPTRunner {
746771
if (item.skip) {
747772
skipped++;
748773
}
774+
if (item.skipTests) {
775+
skippedTests += item.skipTests.length;
776+
}
749777
}
750778

751779
const unexpectedPasses = [];
@@ -786,7 +814,8 @@ class WPTRunner {
786814
console.log(`Ran ${ran}/${total} tests, ${skipped} skipped,`,
787815
`${passed} passed, ${expectedFailures} expected failures,`,
788816
`${failures.length} unexpected failures,`,
789-
`${unexpectedPasses.length} unexpected passes`);
817+
`${unexpectedPasses.length} unexpected passes` +
818+
(skippedTests ? `, ${skippedTests} subtests skipped` : ''));
790819
if (failures.length > 0) {
791820
const file = path.join('test', 'wpt', 'status', `${this.path}.json`);
792821
throw new Error(
@@ -875,8 +904,16 @@ class WPTRunner {
875904
let result = this.results[spec.filename];
876905
result ||= this.results[spec.filename] = {};
877906
if (item.status === kSkip) {
878-
// { filename: { skip: 'reason' } }
879-
result[kSkip] = item.reason;
907+
if (item.name) {
908+
// Subtest-level skip: { filename: { skipTests: [ ... ] } }
909+
result.skipTests ||= [];
910+
if (!result.skipTests.includes(item.name)) {
911+
result.skipTests.push(item.name);
912+
}
913+
} else {
914+
// File-level skip: { filename: { skip: 'reason' } }
915+
result[kSkip] = item.reason;
916+
}
880917
} else {
881918
// { filename: { fail: { expected: [ ... ],
882919
// unexpected: [ ... ] } }}
@@ -895,6 +932,15 @@ class WPTRunner {
895932
reportResult?.addSubtest(test.name, 'PASS');
896933
}
897934

935+
skipTest(spec, test, reportResult) {
936+
console.log(`[SKIP] ${test.name}`);
937+
reportResult?.addSubtest(test.name, 'NOTRUN');
938+
this.addTestResult(spec, {
939+
name: test.name,
940+
status: kSkip,
941+
});
942+
}
943+
898944
fail(spec, test, status, reportResult) {
899945
const expected = spec.failedTests.includes(test.name);
900946
if (expected) {

test/common/wpt/worker.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,32 @@ runInThisContext(workerData.harness.code, {
3535
filename: workerData.harness.filename,
3636
});
3737

38+
// If there are skip patterns, wrap test functions to prevent execution of
39+
// matching tests. This must happen after testharness.js is loaded but before
40+
// the test scripts run.
41+
if (workerData.skippedTests?.length) {
42+
function isSkipped(name) {
43+
for (const matcher of workerData.skippedTests) {
44+
if (typeof matcher === 'string') {
45+
if (name === matcher) return true;
46+
} else if (matcher.test(name)) {
47+
return true;
48+
}
49+
}
50+
return false;
51+
}
52+
for (const fn of ['test', 'async_test', 'promise_test']) {
53+
const original = globalThis[fn];
54+
globalThis[fn] = function(func, name, ...rest) {
55+
if (typeof name === 'string' && isSkipped(name)) {
56+
parentPort.postMessage({ type: 'skip', name });
57+
return;
58+
}
59+
return original.call(this, func, name, ...rest);
60+
};
61+
}
62+
}
63+
3864
// eslint-disable-next-line no-undef
3965
add_result_callback((result) => {
4066
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)