Skip to content

Commit 9f5274b

Browse files
committed
Validate WEB_FEATURES.yml
For every pull request, verify that the `WEB_FEATURES.yml` file is internally consistent and that the pull request does not reduce the number of tests associated with a given classifier. Failing for any reduction in the number of matched tests is intended to alert contributors during file renaming operations. This failure will need to be ignored when tests are simply deleted, and this could potentially become a distraction for maintainers. However, test deletion has historically been so rare that spurious failures are not expected to be common enough to substantively interfere with routine maintenance.
1 parent aa62607 commit 9f5274b

File tree

7 files changed

+387
-12
lines changed

7 files changed

+387
-12
lines changed

.github/workflows/checks.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,39 @@ jobs:
5454
printf "::error file=%s,line=1::%s\n", substr($0, 1, i-1), substr($0, i+2);
5555
}'
5656
57+
lint-web-features:
58+
name: Lint web-features
59+
runs-on: ubuntu-latest
60+
steps:
61+
- name: Checkout
62+
uses: actions/checkout@v4
63+
64+
- name: Install Python dependencies
65+
run: |
66+
python -m pip install --upgrade pip
67+
pip install -r tools/web-features/requirements.txt
68+
69+
- name: Validate WEB_FEATURES.yml
70+
run: |
71+
./tools/web-features/lint.py --manifest WEB_FEATURES-manifest-pr.json
72+
73+
- name: Checkout base branch
74+
uses: actions/checkout@v4
75+
with:
76+
ref: ${{ github.event_name == 'pull_request' && github.base_ref || github.ref_name }}
77+
# The `clean` option must be unset in order to retain the first
78+
# manifest file.
79+
clean: false
80+
81+
- name: Generate manifest on base branch
82+
run: |
83+
./tools/web-features/lint.py --manifest WEB_FEATURES-manifest-base.json
84+
85+
- name: Verify absence of regressions in classification coverage
86+
run: |
87+
./tools/web-features/check-coverage.py \
88+
--base WEB_FEATURES-manifest-base.json \
89+
--new WEB_FEATURES-manifest-pr.json
5790
5891
build:
5992
name: Build generated tests

.github/workflows/test-tools.yml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,24 @@ jobs:
5050
5151
- name: Test the generation tool
5252
run: ./tools/generation/test/run.py
53+
54+
web-features:
55+
name: Test the web-features tooling
56+
runs-on: ubuntu-latest
57+
steps:
58+
- name: Checkout
59+
uses: actions/checkout@v4
60+
61+
- name: Set up Python
62+
uses: actions/setup-python@v5
63+
with:
64+
python-version: '3.x'
65+
cache: pip
66+
67+
- name: Install dependencies for web-features tooling
68+
run: |
69+
python -m pip install --upgrade pip
70+
pip install -r tools/web-features/requirements.txt
71+
72+
- name: Test the web-features tooling
73+
run: ./tools/web-features/test/run.py

tools/web-features/README.md

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,22 @@ For details on the design considerations, please see [GitHub issue
8484
8585
## Tooling
8686
87-
This directory defines a command-line utility in the form of an executable
88-
Python script named `lint.py`. Upon execution, the script will validate some
89-
basic expectations about the contents of the `WEB_FEATURES.yml` (e.g. that the
90-
file conforms to the schema and that every pattern impacts the set of matched
91-
test files).
87+
This directory defines two command-line utilities in the form of executable
88+
Python scripts. Their dependencies can be installed with the following command
89+
(executed from the root of this repository):
90+
91+
pip install -r tools/web-features/requirements.txt
92+
93+
### `lint.py`
94+
95+
This script validates basic expectations about the contents of the
96+
`WEB_FEATURES.yml` (e.g. that the file conforms to the schema and that every
97+
pattern impacts the set of matched test files). If invoked with the argument
98+
`--manifest`, this utility will write a "manifest" of all web-features and
99+
their associated test file names.
100+
101+
### `check-coverage.py'
102+
103+
This script compares two web-features manifests to identify changes that likely
104+
indicate an unintentional regression. Specifically, any reduction in the number
105+
of tests associated with a web-feature will produce an error.
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#!/usr/bin/env python3
2+
# Copyright (C) 2025 the V8 project authors. All rights reserved.
3+
# This code is governed by the BSD license found in the LICENSE file.
4+
5+
import argparse
6+
import json
7+
import sys
8+
9+
def main(base_filename, new_filename):
10+
with open(base_filename, 'r') as base_handle:
11+
base = json.loads(base_handle.read())
12+
13+
assert base['version'] == 1, 'Unsupported base manifest version'
14+
15+
with open(new_filename, 'r') as new_handle:
16+
new = json.loads(new_handle.read())
17+
18+
assert new['version'] == 1, 'Unsupported new manifest version'
19+
20+
errors = []
21+
for name, tests in base['data'].items():
22+
assert name in new['data']
23+
24+
base_count = len(tests)
25+
new_count = len(new['data'][name])
26+
27+
if base_count > new_count:
28+
errors.append(
29+
f'Web feature "{name}" appears to have regressed ' \
30+
f'(from {base_count} matching tests to {new_count} matching ' \
31+
f'tests). Ensure the classifiers in WEB_FEATURES.yml have ' \
32+
f'been updated to accommodate the changes on this branch.'
33+
)
34+
35+
return errors
36+
37+
if __name__ == '__main__':
38+
parser = argparse.ArgumentParser(
39+
description='Test262 web-features manifest regression checker'
40+
)
41+
42+
parser.add_argument('--base', help='''A web-features manifest to use as the
43+
basis for validation''')
44+
parser.add_argument('--new', help='''A web-features manifest to check
45+
for regressions''')
46+
47+
args = parser.parse_args()
48+
errors = main(args.base, args.new)
49+
50+
if len(errors) == 0:
51+
sys.exit(0)
52+
else:
53+
for error in errors:
54+
print(error, file=sys.stderr)
55+
sys.exit(1)

tools/web-features/lint.py

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
#!/usr/bin/env python3
22

3+
import argparse
4+
import json
35
import os
46
import re
7+
import sys
58
import yaml
69

710
def read_features(filename):
@@ -25,7 +28,7 @@ def file_is_test(filename):
2528
return not (filename.endswith('FIXTURE.js') or filename.endswith('.json'))
2629

2730
def pattern_from_path_spec(path_spec):
28-
return re.compile(re.sub('\*', '.*', path_spec) + '(/|$)')
31+
return re.compile(re.sub('\\*', '.*', path_spec) + '(/|$)')
2932

3033
def get_filenames(path_spec):
3134
pattern = pattern_from_path_spec(path_spec)
@@ -92,23 +95,35 @@ def match(file_path, tag_specs):
9295
return False
9396
return True
9497

95-
def main(web_features_filename):
98+
def main(web_features_filename, manifest_filename):
9699
with open(web_features_filename, 'r') as handle:
97100
features = yaml.safe_load(handle)['features']
101+
manifest = {'data': dict(), 'version': 1}
98102

99103
for feature in features:
100104
name = feature['name']
101105
path_specs = feature['files']
102106
tag_specs = feature.get('tags', [])
103107

104-
tests = [*filter(
108+
manifest['data'][name] = [*filter(
105109
lambda candidate: match(candidate, tag_specs),
106110
get_filenames_from_path_specs(path_specs)
107111
)]
108112

109-
print(f'{name},{len(tests)}')
110-
111-
print(f'{web_features_filename} is conformant')
113+
if manifest_filename:
114+
with open(manifest_filename, 'w') as manifest_handle:
115+
manifest_handle.write(json.dumps(manifest))
112116

113117
if __name__ == '__main__':
114-
main('./WEB_FEATURES.yml')
118+
parser = argparse.ArgumentParser(
119+
description='Test262 web-features manifest regression checker'
120+
)
121+
122+
parser.add_argument('--manifest', help='''The name of a JSON-formatted test
123+
manifest file to create''')
124+
125+
args = parser.parse_args()
126+
127+
main('./WEB_FEATURES.yml', args.manifest)
128+
129+
print('./WEB_FEATURES.yml is conformant')
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
PyYAML==6.0.3

0 commit comments

Comments
 (0)