tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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