llvm-project/llvm/utils/update_test_body.py
Fangrui Song aacea0d0f6
[utils] Add script to generate elaborated IR and assembly tests (#89026)
Generally, IR and assembly test files benefit from being cleaned to
remove unnecessary details. However, for tests requiring elaborate
IR or assembly files where cleanup is less practical (e.g., large amount
of debug information output from Clang), the current practice is to
include the C/C++ source file and the generation instructions as
comments.

This is inconvenient when regeneration is needed. This patch adds
`llvm/utils/update_test_body.py` to allow easier regeneration.

`ld.lld --debug-names` tests (#86508) utilize this script for
Clang-generated assembly tests.

Note: `-o pipefail` is standard (since
https://www.austingroupbugs.net/view.php?id=789) but not supported by
dash.

Link:
https://discourse.llvm.org/t/utility-to-generate-elaborated-assembly-ir-tests/78408
2024-05-08 23:58:55 -07:00

111 lines
3.3 KiB
Python
Executable File

#!/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)