Skip to content

Commit 84fd907

Browse files
authored
[lit] add --max-retries-per-test execution option (llvm#141851)
When packaging LLVM we've seen arbitrary tests fail. It happened sporadically and most of the times the test do work if they are run a second time on the next day. The tests themselves were always different and we didn't know ahead of time which ones we wanted to re-run. That's we filter-out a lot of `libomp` and `libarcher` tests [1]. This change allows us to set `LIT_OPTS="--max-retries-per-test=12"` when running any "check-XXX" build target. Then any lit test will at most be re-run 12 times, unless there's an `ALLOW_RETRIES:` in one of the test scripts that's specifying a different value than `12`. `12` is just an example here, any positive integer will work. Please note, that this only adds the possibility to re-run lit tests. It does not actually do it until the caller specifies `--max-retries-per-test=<POSITIVE_INT>` either on a call to `lit` or in `LIT_OPTS`. Also note, that one can still use `ALLOW_RETRIES:` in test scripts and it will always rule over `--max-retries-per-test`. When `--max-retries-per-test` is set too low, but the `config.test_retry_attempts` is high enough, it works as well. Any option in the list below overrules its predecessor: * `--max-retries-per-test` * `config.test_retry_attempts` * `ALLOW_RETRIES` keyword From the above options to re-run tests, `--max-retries-per-test` is the only one that doesn't require a change in the test scripts or the test config. [1]: https://src.fedoraproject.org/rpms/llvm/blob/rawhide/f/llvm.spec#_2326 Downstream PR to make use of the `--max-retries-per-test` option: https://src.fedoraproject.org/rpms/llvm/pull-request/434 Downstream ticket: https://issues.redhat.com/browse/LLVM-145
1 parent 278ef84 commit 84fd907

File tree

14 files changed

+212
-0
lines changed

14 files changed

+212
-0
lines changed

llvm/docs/CommandGuide/lit.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,19 @@ EXECUTION OPTIONS
218218

219219
Stop execution after the given number of failures.
220220

221+
.. option:: --max-retries-per-test N
222+
223+
Retry running failed tests at most ``N`` times.
224+
Out of the following options to rerun failed tests the
225+
:option:`--max-retries-per-test` is the only one that doesn't
226+
require a change in the test scripts or the test config:
227+
228+
* :option:`--max-retries-per-test` lit option
229+
* ``config.test_retry_attempts`` test suite option
230+
* ``ALLOW_RETRIES:`` annotation in test script
231+
232+
Any option in the list above overrules its predecessor.
233+
221234
.. option:: --allow-empty-runs
222235

223236
Do not fail the run if all tests are filtered out.

llvm/utils/lit/lit/LitConfig.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ def __init__(
3535
params,
3636
config_prefix=None,
3737
maxIndividualTestTime=0,
38+
maxRetriesPerTest=None,
3839
parallelism_groups={},
3940
per_test_coverage=False,
4041
gtest_sharding=True,
@@ -86,6 +87,7 @@ def __init__(
8687
self.valgrindArgs.extend(self.valgrindUserArgs)
8788

8889
self.maxIndividualTestTime = maxIndividualTestTime
90+
self.maxRetriesPerTest = maxRetriesPerTest
8991
self.parallelism_groups = parallelism_groups
9092
self.per_test_coverage = per_test_coverage
9193
self.gtest_sharding = bool(gtest_sharding)

llvm/utils/lit/lit/TestingConfig.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,11 @@ def finish(self, litConfig):
235235
# files. Should we distinguish them?
236236
self.test_source_root = str(self.test_source_root)
237237
self.excludes = set(self.excludes)
238+
if (
239+
litConfig.maxRetriesPerTest is not None
240+
and getattr(self, "test_retry_attempts", None) is None
241+
):
242+
self.test_retry_attempts = litConfig.maxRetriesPerTest
238243

239244
@property
240245
def root(self):

llvm/utils/lit/lit/cl_arguments.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,15 @@ def parse_args():
199199
"0 means no time limit. [Default: 0]",
200200
type=_non_negative_int,
201201
)
202+
execution_group.add_argument(
203+
"--max-retries-per-test",
204+
dest="maxRetriesPerTest",
205+
metavar="N",
206+
help="Maximum number of allowed retry attempts per test "
207+
"(NOTE: The config.test_retry_attempts test suite option and "
208+
"ALLOWED_RETRIES keyword always take precedence)",
209+
type=_positive_int,
210+
)
202211
execution_group.add_argument(
203212
"--max-failures",
204213
help="Stop execution after the given number of failures.",

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+
maxRetriesPerTest=opts.maxRetriesPerTest,
4546
)
4647

4748
discovered_tests = lit.discovery.find_tests_for_inputs(
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import lit.formats
2+
3+
config.name = "allow-retries-no-test_retry_attempts"
4+
config.suffixes = [".py"]
5+
config.test_format = lit.formats.ShTest()
6+
config.test_source_root = None
7+
config.test_exec_root = None
8+
9+
config.substitutions.append(("%python", lit_config.params.get("python", "")))
10+
config.substitutions.append(("%counter", lit_config.params.get("counter", "")))
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# ALLOW_RETRIES: 3
2+
# RUN: "%python" "%s" "%counter"
3+
4+
import sys
5+
import os
6+
7+
counter_file = sys.argv[1]
8+
9+
# The first time the test is run, initialize the counter to 1.
10+
if not os.path.exists(counter_file):
11+
with open(counter_file, "w") as counter:
12+
counter.write("1")
13+
14+
# Succeed if this is the fourth time we're being run.
15+
with open(counter_file, "r") as counter:
16+
num = int(counter.read())
17+
if num == 4:
18+
sys.exit(0)
19+
20+
# Otherwise, increment the counter and fail
21+
with open(counter_file, "w") as counter:
22+
counter.write(str(num + 1))
23+
sys.exit(1)
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import lit.formats
2+
3+
config.name = "allow-retries-test_retry_attempts"
4+
config.suffixes = [".py"]
5+
config.test_format = lit.formats.ShTest()
6+
config.test_source_root = None
7+
config.test_exec_root = None
8+
9+
config.substitutions.append(("%python", lit_config.params.get("python", "")))
10+
config.substitutions.append(("%counter", lit_config.params.get("counter", "")))
11+
12+
config.test_retry_attempts = 2
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# ALLOW_RETRIES: 3
2+
# RUN: "%python" "%s" "%counter"
3+
4+
import sys
5+
import os
6+
7+
counter_file = sys.argv[1]
8+
9+
# The first time the test is run, initialize the counter to 1.
10+
if not os.path.exists(counter_file):
11+
with open(counter_file, "w") as counter:
12+
counter.write("1")
13+
14+
# Succeed if this is the fourth time we're being run.
15+
with open(counter_file, "r") as counter:
16+
num = int(counter.read())
17+
if num == 4:
18+
sys.exit(0)
19+
20+
# Otherwise, increment the counter and fail
21+
with open(counter_file, "w") as counter:
22+
counter.write(str(num + 1))
23+
sys.exit(1)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import lit.formats
2+
3+
config.name = "no-allow-retries-no-test_retry_attempts"
4+
config.suffixes = [".py"]
5+
config.test_format = lit.formats.ShTest()
6+
config.test_source_root = None
7+
config.test_exec_root = None
8+
9+
config.substitutions.append(("%python", lit_config.params.get("python", "")))
10+
config.substitutions.append(("%counter", lit_config.params.get("counter", "")))
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# RUN: "%python" "%s" "%counter"
2+
3+
import sys
4+
import os
5+
6+
counter_file = sys.argv[1]
7+
8+
# The first time the test is run, initialize the counter to 1.
9+
if not os.path.exists(counter_file):
10+
with open(counter_file, "w") as counter:
11+
counter.write("1")
12+
13+
# Succeed if this is the fourth time we're being run.
14+
with open(counter_file, "r") as counter:
15+
num = int(counter.read())
16+
if num == 4:
17+
sys.exit(0)
18+
19+
# Otherwise, increment the counter and fail
20+
with open(counter_file, "w") as counter:
21+
counter.write(str(num + 1))
22+
sys.exit(1)
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import lit.formats
2+
3+
config.name = "no-allow-retries-test_retry_attempts"
4+
config.suffixes = [".py"]
5+
config.test_format = lit.formats.ShTest()
6+
config.test_source_root = None
7+
config.test_exec_root = None
8+
9+
config.substitutions.append(("%python", lit_config.params.get("python", "")))
10+
config.substitutions.append(("%counter", lit_config.params.get("counter", "")))
11+
12+
config.test_retry_attempts = 3
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# RUN: "%python" "%s" "%counter"
2+
3+
import sys
4+
import os
5+
6+
counter_file = sys.argv[1]
7+
8+
# The first time the test is run, initialize the counter to 1.
9+
if not os.path.exists(counter_file):
10+
with open(counter_file, "w") as counter:
11+
counter.write("1")
12+
13+
# Succeed if this is the fourth time we're being run.
14+
with open(counter_file, "r") as counter:
15+
num = int(counter.read())
16+
if num == 4:
17+
sys.exit(0)
18+
19+
# Otherwise, increment the counter and fail
20+
with open(counter_file, "w") as counter:
21+
counter.write(str(num + 1))
22+
sys.exit(1)

llvm/utils/lit/tests/allow-retries.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,51 @@
7070
# CHECK-TEST7: # executed command: export LLVM_PROFILE_FILE=
7171
# CHECK-TEST7-NOT: # executed command: export LLVM_PROFILE_FILE=
7272
# CHECK-TEST7: Passed With Retry: 1
73+
74+
# This test only passes on the 4th try. Here we check that a test can be re-run when:
75+
# * The "--max-retries-per-test" is specified high enough (3).
76+
# * No ALLOW_RETRIES keyword is used in the test script.
77+
# * No config.test_retry_attempts is adjusted in the test suite config file.
78+
# RUN: rm -f %t.counter
79+
# RUN: %{lit} %{inputs}/max-retries-per-test/no-allow-retries-no-test_retry_attempts/test.py \
80+
# RUN: --max-retries-per-test=3 \
81+
# RUN: -Dcounter=%t.counter \
82+
# RUN: -Dpython=%{python} \
83+
# RUN: | FileCheck --check-prefix=CHECK-TEST8 %s
84+
# CHECK-TEST8: Passed With Retry: 1
85+
86+
# This test only passes on the 4th try. Here we check that a test can be re-run when:
87+
# * The "--max-retries-per-test" is specified too low (2).
88+
# * ALLOW_RETRIES is specified high enough (3)
89+
# * No config.test_retry_attempts is adjusted in the test suite config file.
90+
# RUN: rm -f %t.counter
91+
# RUN: %{lit} %{inputs}/max-retries-per-test/allow-retries-no-test_retry_attempts/test.py \
92+
# RUN: --max-retries-per-test=2 \
93+
# RUN: -Dcounter=%t.counter \
94+
# RUN: -Dpython=%{python} \
95+
# RUN: | FileCheck --check-prefix=CHECK-TEST9 %s
96+
# CHECK-TEST9: Passed With Retry: 1
97+
98+
# This test only passes on the 4th try. Here we check that a test can be re-run when:
99+
# * The "--max-retries-per-test" is specified too low (2).
100+
# * No ALLOW_RETRIES keyword is used in the test script.
101+
# * config.test_retry_attempts is set high enough (3).
102+
# RUN: rm -f %t.counter
103+
# RUN: %{lit} %{inputs}/max-retries-per-test/no-allow-retries-test_retry_attempts/test.py \
104+
# RUN: --max-retries-per-test=2 \
105+
# RUN: -Dcounter=%t.counter \
106+
# RUN: -Dpython=%{python} \
107+
# RUN: | FileCheck --check-prefix=CHECK-TEST10 %s
108+
# CHECK-TEST10: Passed With Retry: 1
109+
110+
# This test only passes on the 4th try. Here we check that a test can be re-run when:
111+
# * The "--max-retries-per-test" is specified too low (1).
112+
# * ALLOW_RETRIES keyword set high enough (3).
113+
# * config.test_retry_attempts is set too low enough (2).
114+
# RUN: rm -f %t.counter
115+
# RUN: %{lit} %{inputs}/max-retries-per-test/no-allow-retries-test_retry_attempts/test.py \
116+
# RUN: --max-retries-per-test=1 \
117+
# RUN: -Dcounter=%t.counter \
118+
# RUN: -Dpython=%{python} \
119+
# RUN: | FileCheck --check-prefix=CHECK-TEST11 %s
120+
# CHECK-TEST11: Passed With Retry: 1

0 commit comments

Comments
 (0)