manifest.py (7462B)
1 import argparse 2 import json 3 import logging 4 import os 5 6 from dataclasses import dataclass 7 from pathlib import Path 8 from typing import Any, List, Optional 9 10 from ..manifest.item import SupportFile 11 from ..manifest.sourcefile import SourceFile 12 from ..metadata.yaml.load import load_data_to_dict 13 from ..web_features.web_feature_map import WebFeatureToTestsDirMapper, WebFeaturesMap 14 from .. import localpaths 15 from ..metadata.webfeatures.schema import WEB_FEATURES_YML_FILENAME, WebFeaturesFile 16 17 """ 18 This command generates a manifest file containing a mapping of web-feature 19 identifiers to test paths. 20 21 The web-feature identifiers are sourced from https://github.com/web-platform-dx/web-features. 22 They are used in WEB_FEATURES.yml files located throughout the WPT source code. 23 Each file defines which test files correspond to a specific identifier. 24 Refer to RFC 163 (https://github.com/web-platform-tests/rfcs/pull/163) for more 25 file details. 26 27 This command processes all WEB_FEATURES.yml files, extracts the list of test 28 paths from the test files, and writes them to a manifest file. The manifest 29 file maps web-feature identifiers to their corresponding test paths. 30 31 The file written is a JSON file. An example file looks like: 32 33 { 34 "version": 1, 35 "data": { 36 "async-clipboard": [ 37 "/clipboard-apis/async-custom-formats-write-fail.tentative.https.html", 38 "/clipboard-apis/async-custom-formats-write-read-web-prefix.tentative.https.html" 39 ], 40 "idle-detection": [ 41 "/idle-detection/basics.tentative.https.window.html", 42 "/idle-detection/idle-detection-allowed-by-permissions-policy-attribute-redirect-on-load.https.sub.html" 43 ] 44 } 45 } 46 47 48 The JSON Schema for the file format can be found at MANIFEST_SCHEMA.json 49 50 This file does not follow the same format as the original manifest file, 51 MANIFEST.json. 52 """ 53 54 logger = logging.getLogger(__name__) 55 56 MANIFEST_FILE_NAME = "WEB_FEATURES_MANIFEST.json" 57 58 59 def abs_path(path: str) -> str: 60 return os.path.abspath(os.path.expanduser(path)) 61 62 def create_parser() -> argparse.ArgumentParser: 63 """ 64 Creates an argument parser for the script. 65 66 Returns: 67 argparse.ArgumentParser: The configured argument parser. 68 """ 69 parser = argparse.ArgumentParser( 70 description="Maps tests to web-features within a repo root." 71 ) 72 parser.add_argument( 73 "-p", "--path", type=abs_path, help="Path to manifest file.") 74 return parser 75 76 77 def find_all_test_files_in_dir(root_dir: str, rel_dir_path: str, url_base: str) -> List[SourceFile]: 78 """ 79 Finds all test files within a given directory. 80 81 Ignores any SourceFiles that are marked as non_test or the type 82 is SupportFile.item_type 83 84 Args: 85 root_dir (str): The root directory of the repository. 86 rel_dir_path (str): The relative path of the directory to search. 87 url_base (str): Base url to use as the mount point for tests in this manifest. 88 89 Returns: 90 List[SourceFile]: A list of SourceFile objects representing the found test files. 91 """ 92 rv: List[SourceFile] = [] 93 full_dir_path = os.path.join(root_dir, rel_dir_path) 94 for file in os.listdir(full_dir_path): 95 full_path = os.path.join(full_dir_path, file) 96 rel_file_path = os.path.relpath(full_path, root_dir) 97 source_file = SourceFile(root_dir, rel_file_path, url_base) 98 if not source_file.name_is_non_test and source_file.type != SupportFile.item_type: 99 rv.append(source_file) 100 return rv 101 102 @dataclass 103 class CmdConfig(): 104 """ 105 Configuration for the command-line options. 106 """ 107 108 repo_root: str # The root directory of the WPT repository 109 url_base: str # Base URL used when converting file paths to urls 110 111 112 def map_tests_to_web_features( 113 cmd_cfg: CmdConfig, 114 rel_dir_path: str, 115 result: WebFeaturesMap, 116 prev_inherited_features: List[str] = []) -> None: 117 """ 118 Recursively maps tests to web-features within a directory structure. 119 120 Args: 121 cmd_cfg (CmdConfig): The configuration for the command-line options. 122 rel_dir_path (str): The relative path of the directory to process. 123 result (WebFeaturesMap): The object to store the mapping results. 124 prev_inherited_features (List[str], optional): A list of inherited web-features from parent directories. Defaults to []. 125 """ 126 # Sometimes it will add a . at the beginning. Let's resolve the absolute path to disambiguate. 127 # current_path = Path(os.path.join(cmd_cfg.repo_root, rel_dir_path)).resolve() 128 current_dir = str(Path(os.path.join(cmd_cfg.repo_root, rel_dir_path)).resolve()) 129 130 # Create a copy that may be built upon or cleared during this iteration. 131 inherited_features = prev_inherited_features.copy() 132 133 rel_dir_path = os.path.relpath(current_dir, cmd_cfg.repo_root) 134 135 web_feature_yml_full_path = os.path.join(current_dir, WEB_FEATURES_YML_FILENAME) 136 web_feature_file: Optional[WebFeaturesFile] = None 137 if os.path.isfile(web_feature_yml_full_path): 138 try: 139 web_feature_file = WebFeaturesFile(load_data_to_dict( 140 open(web_feature_yml_full_path, "rb"))) 141 except Exception as e: 142 raise e 143 144 WebFeatureToTestsDirMapper( 145 find_all_test_files_in_dir(cmd_cfg.repo_root, rel_dir_path, cmd_cfg.url_base), 146 web_feature_file 147 ).run(result, inherited_features) 148 149 sub_dirs = [f for f in os.listdir(current_dir) if os.path.isdir(os.path.join(current_dir, f))] 150 for sub_dir in sub_dirs: 151 map_tests_to_web_features( 152 cmd_cfg, 153 os.path.join(rel_dir_path, sub_dir), 154 result, 155 inherited_features 156 ) 157 158 class WebFeatureManifestEncoder(json.JSONEncoder): 159 """ 160 Custom JSON encoder. 161 162 WebFeaturesMap contains a dictionary where the value is of type set. 163 Sets cannot serialize to JSON by default. This encoder handles that by 164 calling WebFeaturesMap's to_dict() method. 165 """ 166 def default(self, obj: Any) -> Any: 167 if isinstance(obj, WebFeaturesMap): 168 return obj.to_dict() 169 return super().default(obj) 170 171 172 def write_manifest_file(path: str, web_features_map: WebFeaturesMap) -> None: 173 """ 174 Serializes the WebFeaturesMap to a JSON manifest file at the specified path. 175 176 The generated JSON file adheres to the schema defined in the "MANIFEST_SCHEMA.json" file. The 177 serialization process uses the custom `WebFeatureManifestEncoder` to ensure correct formatting. 178 179 Args: 180 path (str): The file path where the manifest file will be created or overwritten. 181 web_features_map (WebFeaturesMap): The object containing the mapping between 182 web-features and their corresponding test paths. 183 """ 184 with open(path, "w") as outfile: 185 outfile.write( 186 json.dumps( 187 { 188 "version": 1, 189 "data": web_features_map 190 }, cls=WebFeatureManifestEncoder, sort_keys=True)) 191 192 193 def main(venv: Any = None, **kwargs: Any) -> int: 194 195 assert logger is not None 196 197 repo_root = localpaths.repo_root 198 url_base = "/" 199 path = kwargs.get("path") or os.path.join(repo_root, MANIFEST_FILE_NAME) 200 201 cmd_cfg = CmdConfig(repo_root, url_base) 202 feature_map = WebFeaturesMap() 203 map_tests_to_web_features(cmd_cfg, "", feature_map) 204 write_manifest_file(path, feature_map) 205 206 return 0