diff options
Diffstat (limited to 'testing/web-platform/tests/tools/web_features/manifest.py')
-rw-r--r-- | testing/web-platform/tests/tools/web_features/manifest.py | 206 |
1 files changed, 206 insertions, 0 deletions
diff --git a/testing/web-platform/tests/tools/web_features/manifest.py b/testing/web-platform/tests/tools/web_features/manifest.py new file mode 100644 index 0000000000..0f442fe7f6 --- /dev/null +++ b/testing/web-platform/tests/tools/web_features/manifest.py @@ -0,0 +1,206 @@ +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 |