integration/run.py (127 lines of code) (raw):

#!/usr/bin/env 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 http://mozilla.org/MPL/2.0/. import argparse import json import os import re import shutil import subprocess import structlog import yaml from taskcluster.helper import TaskclusterConfig taskcluster = TaskclusterConfig("https://community-tc.services.mozilla.com") logger = structlog.getLogger(__name__) ROOT = os.path.realpath(os.path.dirname(__file__)) def configure(): """ Load configuration from CLI args and Taskcluster secrets """ parser = argparse.ArgumentParser(description="Run code-review integration tests") parser.add_argument( "-c", "--configuration", help="Local configuration file replacing Taskcluster secrets", type=open, ) parser.add_argument( "--clone-dir", help="Directory where to clone repositories", default=os.environ.get("CLONE_DIR", os.path.join(ROOT, "clone")), ) parser.add_argument( "--taskcluster-secret", help="Taskcluster Secret path", default=os.environ.get("TASKCLUSTER_SECRET"), ) args = parser.parse_args() taskcluster.auth() taskcluster.load_secrets( args.taskcluster_secret, required=("phabricator", "admins"), existing={"admins": ["babadie@mozilla.com"]}, local_secrets=yaml.safe_load(args.configuration) if args.configuration else None, ) # Make sure the clone dir is available os.makedirs(args.clone_dir, exist_ok=True) # Check the url is correctly formatted assert taskcluster.secrets["phabricator"]["url"].endswith( "/api/" ), "Phabricator url must end in /api/" return args def clone(url, directory, branch="tip"): """ Mercurial clone with robustcheckout """ logger.info("Cloning repository", url=url, dir=directory) # Parent should exist, not current dir assert os.path.exists(os.path.dirname(directory)), "Missing parent of clone dir" # Cleanup existing target if os.path.exists(directory): logger.info("Removing previous clone") shutil.rmtree(directory) # Now let's clone cmd = [ "hg", "robustcheckout", "--purge", f"--sharebase={directory}-shared", f"--branch={branch}", url, directory, ] subprocess.check_output(cmd) def tip(repo_dir): """ Get the tip of the repo """ cmd = ["hg", "tip", "--template={rev}"] rev = subprocess.check_output(cmd, cwd=repo_dir) return int(rev) def patch(filename, repo_dir, message): """ Apply a locally stored patch on the repository and commit the difference """ assert os.path.isdir(repo_dir), f"Not a directory {repo_dir}" path = os.path.join(ROOT, "patches", filename) assert os.path.exists(path), f"Missing patch {path}" logger.info("Applying patch", name=filename, dir=repo_dir) cmd = [ "hg", "import", "--user=code-review-integration", f"--message={message}", path, ] subprocess.check_output(cmd, cwd=repo_dir) # Load revision created rev = tip(repo_dir) logger.info("Committed a new revision", id=rev) return rev def publish(repo_dir, repo_callsign, revision): """ Publish diff on Phabricator from the base of the repository """ def _dump(path, payload): if os.path.exists(path): logger.info("Skip overriding arc config", path=path) return with open(path, "w") as f: json.dump(payload, f, indent=4, sort_keys=True) logger.info("Setup arc configuration", path=path) # Write arcrc config files phab_url = taskcluster.secrets["phabricator"]["url"] base_url = phab_url.replace("/api/", "/") phab_token = taskcluster.secrets["phabricator"]["token"] _dump(os.path.expanduser("~/.arcrc"), {"hosts": {phab_url: {"token": phab_token}}}) _dump( os.path.join(repo_dir, ".hg", ".arcconfig"), {"repository.callsign": repo_callsign, "phabricator.uri": base_url}, ) logger.info( "Publishing a revision on phabricator", url=phab_url, local_revision=revision ) cmd = ["moz-phab", "submit", "--yes", "--no-lint", "--no-bug", f"{revision}"] output = subprocess.check_output(cmd, cwd=repo_dir) # Parse output to get the revision url on the last line last_line = output.splitlines()[-1] match = re.search(rf"^-> ({base_url}D\d+)$", last_line.decode("utf-8")) assert match is not None, f"No revision found in moz-phab output:\n{output}" return match.group(1) def notify(message): """ Notify admins through email """ notify = taskcluster.get_service("notify") for email in taskcluster.secrets["admins"]: logger.info("Sending email", to=email) notify.email( { "address": email, "subject": "Code review integration test", "content": message, } ) if __name__ == "__main__": logger.info("Running integration test") args = configure() # Clone NSS for a shorter time than MC nss = os.path.join(args.clone_dir, "nss") clone("https://hg.mozilla.org/projects/nss", nss) base = tip(nss) # Apply a specific patch on the NSS clone revision = patch("nss.diff", nss, "Bug XXYYZZ - Code review integration test") # Submit commit on Phabricator instance url = publish(nss, "NSS", revision) # Send notification to admins notify(f"New code-review integration test: {url}") logger.info("All done !")