Skip to content

Commit

Permalink
Bug 1872547 [wpt PR 43828] - Add WEB_FEATURES_MANIFEST.json generatio…
Browse files Browse the repository at this point in the history
…n, a=testonly

Automatic update from web-platform-tests
Add WEB_FEATURES_MANIFEST.json generation (#43828)

* Add WEB_FEATURES_MANIFEST.json generation

This will allow mapping of web features to web platform tests

Creates a new command: ./wpt web-features-manifest

It will create a file called WEB_FEATURES_MANIFEST.json inside of the
repo_root.

The format of the file is a simple dictionary.

The key is the web feature key from https://github.com/web-platform-dx/web-features/tree/main/feature-group-definitions

The value is a list of test paths that apply to the web feature key

fix relative import

add path argument

improve manifest file

Update form of the web_features manifest file

wpt.fyi needs the url instead of path for test comparison

simplify the manifest file

change 'web features' to 'web-features'

resolve web-platform-tests/wpt#43828 (comment)

resolve web-platform-tests/wpt#43828 (comment)

resolve web-platform-tests/wpt#43828 (comment)

resolve web-platform-tests/wpt#43828 (comment)

resolve https://github.com/web-platform-tests/wpt/pull/43828/files/8f520a2d9e815973e53598d582ed455391be98f1#r1455453034

validate schema

use os.listdir instead of os.walk

Use mypy ignore-errors

Similar to tools/ci/tc/tests/test_valid.py that also uses jsonschema

* Apply suggestions from code review

Co-authored-by: Philip Jägenstedt <philip@foolip.org>

* resolve web-platform-tests/wpt#43828 (comment)

* Resolve web-platform-tests/wpt#43828 (comment)

- Ensure feature identifiers are sorted, so that the output is more stable
- Drop the object wrapping and just have an array of strings instead
- Adjust the schema file and tests to reflect that

---------

Co-authored-by: Philip Jägenstedt <philip@foolip.org>
--

wpt-commits: 7b1bc84001f2bb2aba9fcf298d4e62679781f278
wpt-pr: 43828
  • Loading branch information
jcscottiii authored and moz-wptsync-bot committed Jan 22, 2024
1 parent a6b99cd commit ff297f3
Show file tree
Hide file tree
Showing 11 changed files with 799 additions and 0 deletions.
5 changes: 5 additions & 0 deletions testing/web-platform/tests/tools/manifest/sourcefile.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@
WebDriverSpecTest)
from .utils import cached_property

# Cannot do `from ..metadata.webfeatures.schema import WEB_FEATURES_YML_FILENAME`
# because relative import beyond toplevel throws *ImportError*!
from metadata.webfeatures.schema import WEB_FEATURES_YML_FILENAME # type: ignore

wd_pattern = "*.py"
js_meta_re = re.compile(br"//\s*META:\s*(\w*)=(.*)$")
python_meta_re = re.compile(br"#\s*META:\s*(\w*)=(.*)$")
Expand Down Expand Up @@ -302,6 +306,7 @@ def name_is_non_test(self) -> bool:
return (self.is_dir() or
self.name_prefix("MANIFEST") or
self.filename == "META.yml" or
self.filename == WEB_FEATURES_YML_FILENAME or
self.filename.startswith(".") or
self.filename.endswith(".headers") or
self.filename.endswith(".ini") or
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ def items(s):
"crashtests/foo.html.ini",
"css/common/test.html",
"css/CSS2/archive/test.html",
"css/WEB_FEATURES.yml",
"css/META.yml",
])
def test_name_is_non_test(rel_path):
s = create(rel_path)
Expand Down
35 changes: 35 additions & 0 deletions testing/web-platform/tests/tools/web_features/MANIFEST_SCHEMA.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"$schema":"http://json-schema.org/draft-06/schema#",
"$ref":"#/definitions/File",
"definitions":{
"File":{
"type":"object",
"additionalProperties":false,
"properties":{
"version":{
"type":"integer",
"description":"Schema version of the file.",
"enum":[
1
]
},
"data":{
"type":"object",
"description":"High level container for the data. Object key is the web-features identifier.",
"additionalProperties":{
"type":"array",
"items":{
"type":"string",
"description":"The url field in tools.manifest.item.URLManifestItem"
}
}
}
},
"required":[
"data",
"version"
],
"title":"File"
}
}
}
Empty file.
12 changes: 12 additions & 0 deletions testing/web-platform/tests/tools/web_features/commands.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"web-features-manifest": {
"path": "manifest.py",
"script": "main",
"parser": "create_parser",
"help": "Create the WEB_FEATURES_MANIFEST.json",
"virtualenv": true,
"requirements": [
"../metadata/yaml/requirements.txt"
]
}
}
208 changes: 208 additions & 0 deletions testing/web-platform/tests/tools/web_features/manifest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
#!/usr/bin/env python3

import argparse
import json
import logging
import os

from dataclasses import dataclass
from pathlib import Path
from typing import Any, List, Optional

from ..manifest.item import SupportFile
from ..manifest.sourcefile import SourceFile
from ..metadata.yaml.load import load_data_to_dict
from ..web_features.web_feature_map import WebFeatureToTestsDirMapper, WebFeaturesMap
from .. import localpaths
from ..metadata.webfeatures.schema import WEB_FEATURES_YML_FILENAME, WebFeaturesFile

"""
This command generates a manifest file containing a mapping of web-feature
identifiers to test paths.
The web-feature identifiers are sourced from https://github.com/web-platform-dx/web-features.
They are used in WEB_FEATURES.yml files located throughout the WPT source code.
Each file defines which test files correspond to a specific identifier.
Refer to RFC 163 (https://github.com/web-platform-tests/rfcs/pull/163) for more
file details.
This command processes all WEB_FEATURES.yml files, extracts the list of test
paths from the test files, and writes them to a manifest file. The manifest
file maps web-feature identifiers to their corresponding test paths.
The file written is a JSON file. An example file looks like:
{
"version": 1,
"data": {
"async-clipboard": [
"/clipboard-apis/async-custom-formats-write-fail.tentative.https.html",
"/clipboard-apis/async-custom-formats-write-read-web-prefix.tentative.https.html"
],
"idle-detection": [
"/idle-detection/basics.tentative.https.window.html",
"/idle-detection/idle-detection-allowed-by-permissions-policy-attribute-redirect-on-load.https.sub.html"
]
}
}
The JSON Schema for the file format can be found at MANIFEST_SCHEMA.json
This file does not follow the same format as the original manifest file,
MANIFEST.json.
"""

logger = logging.getLogger(__name__)

MANIFEST_FILE_NAME = "WEB_FEATURES_MANIFEST.json"


def abs_path(path: str) -> str:
return os.path.abspath(os.path.expanduser(path))

def create_parser() -> argparse.ArgumentParser:
"""
Creates an argument parser for the script.
Returns:
argparse.ArgumentParser: The configured argument parser.
"""
parser = argparse.ArgumentParser(
description="Maps tests to web-features within a repo root."
)
parser.add_argument(
"-p", "--path", type=abs_path, help="Path to manifest file.")
return parser


def find_all_test_files_in_dir(root_dir: str, rel_dir_path: str, url_base: str) -> List[SourceFile]:
"""
Finds all test files within a given directory.
Ignores any SourceFiles that are marked as non_test or the type
is SupportFile.item_type
Args:
root_dir (str): The root directory of the repository.
rel_dir_path (str): The relative path of the directory to search.
url_base (str): Base url to use as the mount point for tests in this manifest.
Returns:
List[SourceFile]: A list of SourceFile objects representing the found test files.
"""
rv: List[SourceFile] = []
full_dir_path = os.path.join(root_dir, rel_dir_path)
for file in os.listdir(full_dir_path):
full_path = os.path.join(full_dir_path, file)
rel_file_path = os.path.relpath(full_path, root_dir)
source_file = SourceFile(root_dir, rel_file_path, url_base)
if not source_file.name_is_non_test and source_file.type != SupportFile.item_type:
rv.append(source_file)
return rv

@dataclass
class CmdConfig():
"""
Configuration for the command-line options.
"""

repo_root: str # The root directory of the WPT repository
url_base: str # Base URL used when converting file paths to urls


def map_tests_to_web_features(
cmd_cfg: CmdConfig,
rel_dir_path: str,
result: WebFeaturesMap,
prev_inherited_features: List[str] = []) -> None:
"""
Recursively maps tests to web-features within a directory structure.
Args:
cmd_cfg (CmdConfig): The configuration for the command-line options.
rel_dir_path (str): The relative path of the directory to process.
result (WebFeaturesMap): The object to store the mapping results.
prev_inherited_features (List[str], optional): A list of inherited web-features from parent directories. Defaults to [].
"""
# Sometimes it will add a . at the beginning. Let's resolve the absolute path to disambiguate.
# current_path = Path(os.path.join(cmd_cfg.repo_root, rel_dir_path)).resolve()
current_dir = str(Path(os.path.join(cmd_cfg.repo_root, rel_dir_path)).resolve())

# Create a copy that may be built upon or cleared during this iteration.
inherited_features = prev_inherited_features.copy()

rel_dir_path = os.path.relpath(current_dir, cmd_cfg.repo_root)

web_feature_yml_full_path = os.path.join(current_dir, WEB_FEATURES_YML_FILENAME)
web_feature_file: Optional[WebFeaturesFile] = None
if os.path.isfile(web_feature_yml_full_path):
try:
web_feature_file = WebFeaturesFile(load_data_to_dict(
open(web_feature_yml_full_path, "rb")))
except Exception as e:
raise e

WebFeatureToTestsDirMapper(
find_all_test_files_in_dir(cmd_cfg.repo_root, rel_dir_path, cmd_cfg.url_base),
web_feature_file
).run(result, inherited_features)

sub_dirs = [f for f in os.listdir(current_dir) if os.path.isdir(os.path.join(current_dir, f))]
for sub_dir in sub_dirs:
map_tests_to_web_features(
cmd_cfg,
os.path.join(rel_dir_path, sub_dir),
result,
inherited_features
)

class WebFeatureManifestEncoder(json.JSONEncoder):
"""
Custom JSON encoder.
WebFeaturesMap contains a dictionary where the value is of type set.
Sets cannot serialize to JSON by default. This encoder handles that by
calling WebFeaturesMap's to_dict() method.
"""
def default(self, obj: Any) -> Any:
if isinstance(obj, WebFeaturesMap):
return obj.to_dict()
return super().default(obj)


def write_manifest_file(path: str, web_features_map: WebFeaturesMap) -> None:
"""
Serializes the WebFeaturesMap to a JSON manifest file at the specified path.
The generated JSON file adheres to the schema defined in the "MANIFEST_SCHEMA.json" file. The
serialization process uses the custom `WebFeatureManifestEncoder` to ensure correct formatting.
Args:
path (str): The file path where the manifest file will be created or overwritten.
web_features_map (WebFeaturesMap): The object containing the mapping between
web-features and their corresponding test paths.
"""
with open(path, "w") as outfile:
outfile.write(
json.dumps(
{
"version": 1,
"data": web_features_map
}, cls=WebFeatureManifestEncoder))


def main(venv: Any = None, **kwargs: Any) -> int:

assert logger is not None

repo_root = localpaths.repo_root
url_base = "/"
path = kwargs.get("path") or os.path.join(repo_root, MANIFEST_FILE_NAME)

cmd_cfg = CmdConfig(repo_root, url_base)
feature_map = WebFeaturesMap()
map_tests_to_web_features(cmd_cfg, "", feature_map)
write_manifest_file(path, feature_map)

return 0
Empty file.
Loading

0 comments on commit ff297f3

Please sign in to comment.