| 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, sort_keys=True)) |
| |
| |
| 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 |