Skip to content

Commit fff8477

Browse files
hnrklssnSterling-Augustine
authored andcommitted
[Utils] Add new --update-tests flag to llvm-lit (llvm#108425)
This adds a flag to lit for detecting and updating failing tests when possible to do so automatically. The flag uses a plugin architecture where config files can add additional auto-updaters for the types of tests in the test suite. When a test fails with `--update-tests` enabled lit passes the test RUN invocation and output to each registered test updater until one of them signals that it updated the test (or all test updaters have been run). As such it is the responsibility of the test updater to only update tests where it is reasonably certain that it will actually fix the test, or come close to doing so. Initially adds support for UpdateVerifyTests and UpdateTestChecks. The flag is currently only implemented for lit's internal shell, so `--update-tests` implies `LIT_USE_INTERNAL_SHELL=1`. Builds on work in llvm#97369 Fixes llvm#81320
1 parent 2ed88eb commit fff8477

File tree

9 files changed

+103
-3
lines changed

9 files changed

+103
-3
lines changed

clang/test/lit.cfg.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,3 +362,13 @@ def calculate_arch_features(arch_string):
362362
# possibly be present in system and user configuration files, so disable
363363
# default configs for the test runs.
364364
config.environment["CLANG_NO_DEFAULT_CONFIG"] = "1"
365+
366+
if lit_config.update_tests:
367+
import sys
368+
import os
369+
370+
utilspath = os.path.join(config.llvm_src_root, "utils")
371+
sys.path.append(utilspath)
372+
from update_any_test_checks import utc_lit_plugin
373+
374+
lit_config.test_updaters.append(utc_lit_plugin)

llvm/docs/CommandGuide/lit.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,11 @@ ADDITIONAL OPTIONS
313313

314314
List all of the discovered tests and exit.
315315

316+
.. option:: --update-tests
317+
318+
Pass failing tests to functions in the ``lit_config.update_tests`` list to
319+
check whether any of them know how to update the test to make it pass.
320+
316321
EXIT STATUS
317322
-----------
318323

llvm/test/lit.cfg.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -630,3 +630,13 @@ def have_ld64_plugin_support():
630630

631631
if config.has_logf128:
632632
config.available_features.add("has_logf128")
633+
634+
if lit_config.update_tests:
635+
import sys
636+
import os
637+
638+
utilspath = os.path.join(config.llvm_src_root, "utils")
639+
sys.path.append(utilspath)
640+
from update_any_test_checks import utc_lit_plugin
641+
642+
lit_config.test_updaters.append(utc_lit_plugin)

llvm/utils/lit/lit/LitConfig.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ def __init__(
3838
parallelism_groups={},
3939
per_test_coverage=False,
4040
gtest_sharding=True,
41+
update_tests=False,
4142
):
4243
# The name of the test runner.
4344
self.progname = progname
@@ -89,6 +90,8 @@ def __init__(
8990
self.parallelism_groups = parallelism_groups
9091
self.per_test_coverage = per_test_coverage
9192
self.gtest_sharding = bool(gtest_sharding)
93+
self.update_tests = update_tests
94+
self.test_updaters = []
9295

9396
@property
9497
def maxIndividualTestTime(self):

llvm/utils/lit/lit/TestRunner.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1190,6 +1190,18 @@ def executeScriptInternal(
11901190
str(result.timeoutReached),
11911191
)
11921192

1193+
if litConfig.update_tests:
1194+
for test_updater in litConfig.test_updaters:
1195+
try:
1196+
update_output = test_updater(result, test)
1197+
except Exception as e:
1198+
out += f"Exception occurred in test updater: {e}"
1199+
continue
1200+
if update_output:
1201+
for line in update_output.splitlines():
1202+
out += f"# {line}\n"
1203+
break
1204+
11931205
return out, err, exitCode, timeoutInfo
11941206

11951207

llvm/utils/lit/lit/cl_arguments.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,12 @@ def parse_args():
204204
action="store_true",
205205
help="Exit with status zero even if some tests fail",
206206
)
207+
execution_group.add_argument(
208+
"--update-tests",
209+
dest="update_tests",
210+
action="store_true",
211+
help="Try to update regression tests to reflect current behavior, if possible",
212+
)
207213
execution_test_time_group = execution_group.add_mutually_exclusive_group()
208214
execution_test_time_group.add_argument(
209215
"--skip-test-time-recording",

llvm/utils/lit/lit/llvm/config.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,17 @@ def __init__(self, lit_config, config):
6464
self.with_environment("_TAG_REDIR_ERR", "TXT")
6565
self.with_environment("_CEE_RUNOPTS", "FILETAG(AUTOCVT,AUTOTAG) POSIX(ON)")
6666

67+
if lit_config.update_tests:
68+
self.use_lit_shell = True
69+
6770
# Choose between lit's internal shell pipeline runner and a real shell.
6871
# If LIT_USE_INTERNAL_SHELL is in the environment, we use that as an
6972
# override.
7073
lit_shell_env = os.environ.get("LIT_USE_INTERNAL_SHELL")
7174
if lit_shell_env:
7275
self.use_lit_shell = lit.util.pythonize_bool(lit_shell_env)
76+
if not self.use_lit_shell and lit_config.update_tests:
77+
print("note: --update-tests is not supported when using external shell")
7378

7479
if not self.use_lit_shell:
7580
features.add("shell")

llvm/utils/lit/lit/main.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ def main(builtin_params={}):
4242
config_prefix=opts.configPrefix,
4343
per_test_coverage=opts.per_test_coverage,
4444
gtest_sharding=opts.gtest_sharding,
45+
update_tests=opts.update_tests,
4546
)
4647

4748
discovered_tests = lit.discovery.find_tests_for_inputs(

llvm/utils/update_any_test_checks.py

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,12 @@ def find_utc_tool(search_path, utc_name):
3434
return None
3535

3636

37-
def run_utc_tool(utc_name, utc_tool, testname):
37+
def run_utc_tool(utc_name, utc_tool, testname, environment):
3838
result = subprocess.run(
39-
[utc_tool, testname], stdout=subprocess.PIPE, stderr=subprocess.PIPE
39+
[utc_tool, testname],
40+
stdout=subprocess.PIPE,
41+
stderr=subprocess.PIPE,
42+
env=environment,
4043
)
4144
return (result.returncode, result.stdout, result.stderr)
4245

@@ -60,6 +63,42 @@ def expand_listfile_args(arg_list):
6063
return exp_arg_list
6164

6265

66+
def utc_lit_plugin(result, test):
67+
testname = test.getFilePath()
68+
if not testname:
69+
return None
70+
71+
script_name = os.path.abspath(__file__)
72+
utc_search_path = os.path.join(os.path.dirname(script_name), os.path.pardir)
73+
74+
with open(testname, "r") as f:
75+
header = f.readline().strip()
76+
77+
m = RE_ASSERTIONS.search(header)
78+
if m is None:
79+
return None
80+
81+
utc_name = m.group(1)
82+
utc_tool = find_utc_tool([utc_search_path], utc_name)
83+
if not utc_tool:
84+
return f"update-utc-tests: {utc_name} not found"
85+
86+
return_code, stdout, stderr = run_utc_tool(
87+
utc_name, utc_tool, testname, test.config.environment
88+
)
89+
90+
stderr = stderr.decode(errors="replace")
91+
if return_code != 0:
92+
if stderr:
93+
return f"update-utc-tests: {utc_name} exited with return code {return_code}\n{stderr.rstrip()}"
94+
return f"update-utc-tests: {utc_name} exited with return code {return_code}"
95+
96+
stdout = stdout.decode(errors="replace")
97+
if stdout:
98+
return f"update-utc-tests: updated {testname}\n{stdout.rstrip()}"
99+
return f"update-utc-tests: updated {testname}"
100+
101+
63102
def main():
64103
from argparse import RawTextHelpFormatter
65104

@@ -78,6 +117,11 @@ def main():
78117
nargs="*",
79118
help="Additional directories to scan for update_*_test_checks scripts",
80119
)
120+
parser.add_argument(
121+
"--path",
122+
help="""Additional directories to scan for executables invoked by the update_*_test_checks scripts,
123+
separated by the platform path separator""",
124+
)
81125
parser.add_argument("tests", nargs="+")
82126
config = parser.parse_args()
83127

@@ -88,6 +132,10 @@ def main():
88132
script_name = os.path.abspath(__file__)
89133
utc_search_path.append(os.path.join(os.path.dirname(script_name), os.path.pardir))
90134

135+
local_env = os.environ.copy()
136+
if config.path:
137+
local_env["PATH"] = config.path + os.pathsep + local_env["PATH"]
138+
91139
not_autogenerated = []
92140
utc_tools = {}
93141
have_error = False
@@ -117,7 +165,7 @@ def main():
117165
continue
118166

119167
future = executor.submit(
120-
run_utc_tool, utc_name, utc_tools[utc_name], testname
168+
run_utc_tool, utc_name, utc_tools[utc_name], testname, local_env
121169
)
122170
jobs.append((testname, future))
123171

0 commit comments

Comments
 (0)