aboutsummaryrefslogtreecommitdiffstats
path: root/python/wpt/manifestupdate.py
diff options
context:
space:
mode:
Diffstat (limited to 'python/wpt/manifestupdate.py')
-rw-r--r--python/wpt/manifestupdate.py173
1 files changed, 173 insertions, 0 deletions
diff --git a/python/wpt/manifestupdate.py b/python/wpt/manifestupdate.py
new file mode 100644
index 00000000000..05d380139db
--- /dev/null
+++ b/python/wpt/manifestupdate.py
@@ -0,0 +1,173 @@
+# 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/.
+
+import argparse
+import os
+import sys
+import tempfile
+from collections import defaultdict
+from six import iterkeys, iteritems
+
+from . import SERVO_ROOT, WPT_PATH
+from mozlog.structured 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():
+ 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=True, rebuild=False, **kwargs):
+ logger = wptlogging.setup(kwargs, {"mach": sys.stdout})
+ kwargs = {"config": os.path.join(WPT_PATH, "config.ini"),
+ "manifest_path": os.path.join(WPT_PATH, "metadata"),
+ "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, test_paths, rebuild):
+ 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, test_paths):
+ 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, manifest_path, old_manifest, new_manifest):
+ """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) # noqa
+ clean = False
+
+ return clean
+
+
+def log_error(logger, manifest_path, msg):
+ logger.lint_error(path=manifest_path,
+ message=msg,
+ lineno=0,
+ source="",
+ linter="wpt-manifest")