llvm/llvm/utils/update_test_body.py

#!/usr/bin/env python3
"""Generate test body using split-file and a custom script.

The script will prepare extra files with `split-file`, invoke `gen`, and then
rewrite the part after `gen` with its stdout.

https://llvm.org/docs/TestingGuide.html#elaborated-tests

Example:
PATH=/path/to/clang_build/bin:$PATH llvm/utils/update_test_body.py path/to/test.s
"""
import argparse
import contextlib
import os
import re
import subprocess
import sys
import tempfile


@contextlib.contextmanager
def cd(directory):
    cwd = os.getcwd()
    os.chdir(directory)
    try:
        yield
    finally:
        os.chdir(cwd)


def process(args, path):
    prolog = []
    seen_gen = False
    with open(path) as f:
        for line in f.readlines():
            line = line.rstrip()
            prolog.append(line)
            if (seen_gen and re.match(r"(.|//)---", line)) or line.startswith(".endif"):
                break
            if re.match(r"(.|//)--- gen", line):
                seen_gen = True
        else:
            print(
                "'gen' should be followed by another part (---) or .endif",
                file=sys.stderr,
            )
            return 1

    if not seen_gen:
        print("'gen' does not exist", file=sys.stderr)
        return 1
    with tempfile.TemporaryDirectory(prefix="update_test_body_") as dir:
        try:
            # If the last line starts with ".endif", remove it.
            sub = subprocess.run(
                ["split-file", "-", dir],
                input="\n".join(
                    prolog[:-1] if prolog[-1].startswith(".endif") else prolog
                ).encode(),
                capture_output=True,
                check=True,
            )
        except subprocess.CalledProcessError as ex:
            sys.stderr.write(ex.stderr.decode())
            return 1
        with cd(dir):
            if args.shell:
                print(f"invoke shell in the temporary directory '{dir}'")
                subprocess.run([os.environ.get("SHELL", "sh")])
                return 0

            sub = subprocess.run(
                ["sh", "-eu", "gen"],
                capture_output=True,
                # Don't encode the directory information to the Clang output.
                # Remove unneeded details (.ident) as well.
                env=dict(
                    os.environ,
                    CCC_OVERRIDE_OPTIONS="#^-fno-ident",
                    PWD="/proc/self/cwd",
                ),
            )
            sys.stderr.write(sub.stderr.decode())
            if sub.returncode != 0:
                print("'gen' failed", file=sys.stderr)
                return sub.returncode
            if not sub.stdout:
                print("stdout is empty; forgot -o - ?", file=sys.stderr)
                return 1
            content = sub.stdout.decode()

    with open(path, "w") as f:
        # Print lines up to '.endif'.
        print("\n".join(prolog), file=f)
        # Then print the stdout of 'gen'.
        f.write(content)


parser = argparse.ArgumentParser(
    description="Generate test body using split-file and a custom script"
)
parser.add_argument("files", nargs="+")
parser.add_argument(
    "--shell", action="store_true", help="invoke shell instead of 'gen'"
)
args = parser.parse_args()
for path in args.files:
    retcode = process(args, path)
    if retcode != 0:
        sys.exit(retcode)