Automatic update from web-platform-tests feat(webfeatures): Improve validation of WEB_FEATURES.yml (#54526) This commit enhances the validation of `WEB_FEATURES.yml` files by: - Adding more specific checks for invalid structures within the file. - Ensuring that if the `**` wildcard is used in the `files` field, it is the only value and not part of a list. - Expanding the test suite to cover these new validation scenarios. These changes will help to prevent common errors in `WEB_FEATURES.yml` files. -- wpt-commits: 24234ae57b11711d1883c84defd412bc739bf5de wpt-pr: 54526
110 lines
4.2 KiB
Python
110 lines
4.2 KiB
Python
from enum import Enum
|
|
from dataclasses import dataclass
|
|
from fnmatch import fnmatchcase
|
|
from functools import cached_property
|
|
from typing import Any, Dict, Sequence, Union
|
|
|
|
from ..schema import SchemaValue, validate_dict
|
|
|
|
"""
|
|
YAML filename for meta files
|
|
"""
|
|
WEB_FEATURES_YML_FILENAME = "WEB_FEATURES.yml"
|
|
|
|
# File prefix to indicate that this FeatureFile should run in EXCLUDE mode.
|
|
EXCLUSION_PREFIX = "!"
|
|
|
|
|
|
class SpecialFileEnum(Enum):
|
|
"""All files recursively"""
|
|
RECURSIVE = "**"
|
|
|
|
|
|
class FileMatchingMode(Enum):
|
|
"""Defines how a FeatureFile pattern is used for matching."""
|
|
INCLUDE = 1 # Include files that match the pattern
|
|
EXCLUDE = 2 # Exclude files that match the pattern
|
|
|
|
class FeatureFile(str):
|
|
@cached_property
|
|
def matching_mode(self) -> FileMatchingMode:
|
|
"""Determines if the pattern should include or exclude matches."""
|
|
return FileMatchingMode.EXCLUDE if self.startswith(EXCLUSION_PREFIX) else FileMatchingMode.INCLUDE
|
|
|
|
@cached_property
|
|
def processed_filename(self) -> str:
|
|
"""Removes the exclusion prefix "!" from the pattern."""
|
|
# TODO. After moving to Python3.9, use: return self.removeprefix(EXCLUSION_PREFIX)
|
|
return self[len(EXCLUSION_PREFIX):] if self.startswith(EXCLUSION_PREFIX) else self
|
|
|
|
def match_files(self, base_filenames: Sequence[str]) -> Sequence[str]:
|
|
"""
|
|
Given the input base file names, returns the subset of base file names
|
|
that match the given FeatureFile based on matching_mode.
|
|
If the FeatureFile contains any number of "*" characters, fnmatch is
|
|
used check each file name.
|
|
If the FeatureFile does not contain any "*" characters, the base file name
|
|
must match the FeatureFile exactly
|
|
:param base_filenames: The list of filenames to check against the FeatureFile
|
|
:return: List of matching file names that match FeatureFile
|
|
"""
|
|
result = []
|
|
# If our file name contains a wildcard, use fnmatch
|
|
if "*" in self:
|
|
for base_filename in base_filenames:
|
|
if fnmatchcase(base_filename, self.processed_filename):
|
|
result.append(base_filename)
|
|
elif self.processed_filename in base_filenames:
|
|
result.append(self.processed_filename)
|
|
return result
|
|
|
|
|
|
@dataclass
|
|
class FeatureEntry:
|
|
files: Union[Sequence[FeatureFile], SpecialFileEnum]
|
|
"""The web-features key"""
|
|
name: str
|
|
|
|
_required_keys = {"files", "name"}
|
|
|
|
def __init__(self, obj: Dict[str, Any]):
|
|
"""
|
|
Converts the provided dictionary to an instance of FeatureEntry
|
|
:param obj: The object that will be converted to a FeatureEntry.
|
|
:return: An instance of FeatureEntry
|
|
:raises ValueError: If there are unexpected keys or missing required keys.
|
|
"""
|
|
validate_dict(obj, FeatureEntry._required_keys)
|
|
self.files = SchemaValue.from_union([
|
|
lambda x: SchemaValue.from_list(SchemaValue.from_class(FeatureFile), x),
|
|
SpecialFileEnum], obj.get("files"))
|
|
self.name = SchemaValue.from_str(obj.get("name"))
|
|
# If "**" is used, it should be the only item. Not in a list.
|
|
if isinstance(self.files, list) and SpecialFileEnum.RECURSIVE.value in self.files:
|
|
raise ValueError(f'Feature {self.name} contains "**" in a list. It should be `files: "**"`')
|
|
|
|
|
|
def does_feature_apply_recursively(self) -> bool:
|
|
if isinstance(self.files, SpecialFileEnum) and self.files == SpecialFileEnum.RECURSIVE:
|
|
return True
|
|
return False
|
|
|
|
|
|
@dataclass
|
|
class WebFeaturesFile:
|
|
"""List of features"""
|
|
features: Sequence[FeatureEntry]
|
|
|
|
_required_keys = {"features"}
|
|
|
|
def __init__(self, obj: Dict[str, Any]):
|
|
"""
|
|
Converts the provided dictionary to an instance of WebFeaturesFile
|
|
:param obj: The object that will be converted to a WebFeaturesFile.
|
|
:return: An instance of WebFeaturesFile
|
|
:raises ValueError: If there are unexpected keys or missing required keys.
|
|
"""
|
|
validate_dict(obj, WebFeaturesFile._required_keys)
|
|
self.features = SchemaValue.from_list(
|
|
lambda raw_feature: FeatureEntry(SchemaValue.from_dict(raw_feature)), obj.get("features"))
|