servo/python/wpt/manifestupdate.py
Jerens Lensun 797db25c4e
mach: Enable ANN rules (type annotations) for ruff Python linter (#38531)
This changes will introduce [flake8-annotations
(ANN)](https://docs.astral.sh/ruff/rules/#flake8-annotations-ann) for
python type annotation, this will make all thing related to function
strictly typed in python

This rule will start to affected this directory from now:
- /python -> Root directory
- /python/tidy
- /python/wpt

Testing: `./mach test-tidy`
Fixes: Not related to any issues

---------

Signed-off-by: Jerens Lensun <jerensslensun@gmail.com>
2025-08-14 10:36:17 +00:00

179 lines
6.4 KiB
Python

# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
from wptrunner.wptcommandline import TestRoot
from typing import Mapping, Any
import argparse
from argparse import ArgumentParser
import os
import sys
import tempfile
from collections import defaultdict
from six import iterkeys, iteritems
from . import SERVO_ROOT, WPT_PATH
from mozlog import commandline
# This must happen after importing from "." since it adds WPT
# tools to the Python system path.
import manifest as wptmanifest
from wptrunner.wptcommandline import get_test_paths, set_from_config
from wptrunner import wptlogging
def create_parser() -> ArgumentParser:
p = argparse.ArgumentParser()
p.add_argument(
"--check-clean", action="store_true", help="Check that updating the manifest doesn't lead to any changes"
)
p.add_argument("--rebuild", action="store_true", help="Rebuild the manifest from scratch")
commandline.add_logging_group(p)
return p
def update(
check_clean: bool = True,
rebuild: bool = False,
logger: Any = None,
**kwargs: Any,
) -> int:
if not logger:
logger = wptlogging.setup(kwargs, {"mach": sys.stdout})
kwargs = {
"config": os.path.join(WPT_PATH, "config.ini"),
"product": "servo",
"manifest_path": os.path.join(WPT_PATH, "meta"),
"tests_root": None,
"metadata_root": None,
}
set_from_config(kwargs)
config = kwargs["config"]
test_paths = get_test_paths(config)
if check_clean:
return _check_clean(logger, test_paths)
return _update(logger, test_paths, rebuild)
def _update(logger: Any, test_paths: Mapping[str, TestRoot], rebuild: bool) -> int:
for url_base, paths in iteritems(test_paths):
manifest_path = os.path.join(paths.metadata_path, "MANIFEST.json")
cache_subdir = os.path.relpath(os.path.dirname(manifest_path), os.path.dirname(__file__))
wptmanifest.manifest.load_and_update(
paths.tests_path,
manifest_path,
url_base,
working_copy=True,
rebuild=rebuild,
cache_root=os.path.join(SERVO_ROOT, ".wpt", cache_subdir),
)
return 0
def _check_clean(logger: Any, test_paths: Mapping[str, TestRoot]) -> int:
manifests_by_path = {}
rv = 0
for url_base, paths in iteritems(test_paths):
tests_path = paths.tests_path
manifest_path = os.path.join(paths.metadata_path, "MANIFEST.json")
old_manifest = wptmanifest.manifest.load_and_update(
tests_path, manifest_path, url_base, working_copy=False, update=False, write_manifest=False
)
# Even if no cache is specified, one will be used automatically by the
# VCS integration. Create a brand new cache every time to ensure that
# the VCS integration always thinks that any file modifications in the
# working directory are new and interesting.
cache_root = tempfile.mkdtemp()
new_manifest = wptmanifest.manifest.load_and_update(
tests_path,
manifest_path,
url_base,
working_copy=True,
update=True,
cache_root=cache_root,
write_manifest=False,
allow_cached=False,
)
manifests_by_path[manifest_path] = (old_manifest, new_manifest)
for manifest_path, (old_manifest, new_manifest) in iteritems(manifests_by_path):
if not diff_manifests(logger, manifest_path, old_manifest, new_manifest):
logger.error("Manifest %s is outdated, use |./mach update-manifest| to fix." % manifest_path)
rv = 1
return rv
def diff_manifests(logger: Any, manifest_path: Any, old_manifest: Any, new_manifest: Any) -> bool:
"""Lint the differences between old and new versions of a
manifest. Differences are considered significant (and so produce
lint errors) if they produce a meaningful difference in the actual
tests run.
:param logger: mozlog logger to use for output
:param manifest_path: Path to the manifest being linted
:param old_manifest: Manifest object representing the initial manifest
:param new_manifest: Manifest object representing the updated manifest
"""
logger.info("Diffing old and new manifests %s" % manifest_path)
old_items, new_items = defaultdict(set), defaultdict(set)
for manifest, items in [(old_manifest, old_items), (new_manifest, new_items)]:
for test_type, path, tests in manifest:
for test in tests:
test_id = [test.id]
if hasattr(test, "script_metadata"):
if test.script_metadata is not None:
test_id.extend(tuple(item) for item in test.script_metadata)
if hasattr(test, "references"):
test_id.extend(tuple(item) for item in test.references)
test_id = tuple(test_id)
items[path].add((test_type, test_id))
old_paths = set(iterkeys(old_items))
new_paths = set(iterkeys(new_items))
added_paths = new_paths - old_paths
deleted_paths = old_paths - new_paths
common_paths = new_paths & old_paths
clean = True
for path in added_paths:
clean = False
log_error(logger, manifest_path, "%s in source but not in manifest." % path)
for path in deleted_paths:
clean = False
log_error(logger, manifest_path, "%s in manifest but removed from source." % path)
for path in common_paths:
old_tests = old_items[path]
new_tests = new_items[path]
added_tests = new_tests - old_tests
removed_tests = old_tests - new_tests
if added_tests or removed_tests:
clean = False
log_error(logger, manifest_path, "%s changed test types or metadata" % path)
if clean:
# Manifest currently has some list vs tuple inconsistencies that break
# a simple equality comparison.
old_paths = old_manifest.to_json()["items"]
new_paths = new_manifest.to_json()["items"]
if old_paths != new_paths:
logger.warning("Manifest %s contains correct tests but file hashes changed." % manifest_path)
clean = False
return clean
def log_error(logger: Any, manifest_path: Any, msg: str) -> None:
logger.lint_error(path=manifest_path, message=msg, lineno=0, source="", linter="wpt-manifest")