Skip to content

[lit] add --max-retries-per-test execution option #141851

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 31, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions llvm/docs/CommandGuide/lit.rst
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,19 @@ EXECUTION OPTIONS

Stop execution after the given number of failures.

.. option:: --max-retries-per-test N

Retry running failed tests at most ``N`` times.
Out of the following options to rerun failed tests the
:option:`--max-retries-per-test` is the only one that doesn't
require a change in the test scripts or the test config:

* :option:`--max-retries-per-test` lit option
* ``config.test_retry_attempts`` test suite option
* ``ALLOW_RETRIES:`` annotation in test script

Any option in the list above overrules its predecessor.

.. option:: --allow-empty-runs

Do not fail the run if all tests are filtered out.
Expand Down
2 changes: 2 additions & 0 deletions llvm/utils/lit/lit/LitConfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ def __init__(
params,
config_prefix=None,
maxIndividualTestTime=0,
maxRetriesPerTest=None,
parallelism_groups={},
per_test_coverage=False,
gtest_sharding=True,
Expand Down Expand Up @@ -86,6 +87,7 @@ def __init__(
self.valgrindArgs.extend(self.valgrindUserArgs)

self.maxIndividualTestTime = maxIndividualTestTime
self.maxRetriesPerTest = maxRetriesPerTest
self.parallelism_groups = parallelism_groups
self.per_test_coverage = per_test_coverage
self.gtest_sharding = bool(gtest_sharding)
Expand Down
5 changes: 5 additions & 0 deletions llvm/utils/lit/lit/TestingConfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,11 @@ def finish(self, litConfig):
# files. Should we distinguish them?
self.test_source_root = str(self.test_source_root)
self.excludes = set(self.excludes)
if (
litConfig.maxRetriesPerTest is not None
and getattr(self, "test_retry_attempts", None) is None
):
self.test_retry_attempts = litConfig.maxRetriesPerTest

@property
def root(self):
Expand Down
9 changes: 9 additions & 0 deletions llvm/utils/lit/lit/cl_arguments.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,15 @@ def parse_args():
"0 means no time limit. [Default: 0]",
type=_non_negative_int,
)
execution_group.add_argument(
"--max-retries-per-test",
dest="maxRetriesPerTest",
metavar="N",
help="Maximum number of allowed retry attempts per test "
"(NOTE: The config.test_retry_attempts test suite option and "
"ALLOWED_RETRIES keyword always take precedence)",
type=_positive_int,
)
execution_group.add_argument(
"--max-failures",
help="Stop execution after the given number of failures.",
Expand Down
1 change: 1 addition & 0 deletions llvm/utils/lit/lit/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ def main(builtin_params={}):
config_prefix=opts.configPrefix,
per_test_coverage=opts.per_test_coverage,
gtest_sharding=opts.gtest_sharding,
maxRetriesPerTest=opts.maxRetriesPerTest,
)

discovered_tests = lit.discovery.find_tests_for_inputs(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import lit.formats

config.name = "allow-retries-no-test_retry_attempts"
config.suffixes = [".py"]
config.test_format = lit.formats.ShTest()
config.test_source_root = None
config.test_exec_root = None

config.substitutions.append(("%python", lit_config.params.get("python", "")))
config.substitutions.append(("%counter", lit_config.params.get("counter", "")))
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# ALLOW_RETRIES: 3
# RUN: "%python" "%s" "%counter"

import sys
import os

counter_file = sys.argv[1]

# The first time the test is run, initialize the counter to 1.
if not os.path.exists(counter_file):
with open(counter_file, "w") as counter:
counter.write("1")

# Succeed if this is the fourth time we're being run.
with open(counter_file, "r") as counter:
num = int(counter.read())
if num == 4:
sys.exit(0)

# Otherwise, increment the counter and fail
with open(counter_file, "w") as counter:
counter.write(str(num + 1))
sys.exit(1)
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import lit.formats

config.name = "allow-retries-test_retry_attempts"
config.suffixes = [".py"]
config.test_format = lit.formats.ShTest()
config.test_source_root = None
config.test_exec_root = None

config.substitutions.append(("%python", lit_config.params.get("python", "")))
config.substitutions.append(("%counter", lit_config.params.get("counter", "")))

config.test_retry_attempts = 2
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# ALLOW_RETRIES: 3
# RUN: "%python" "%s" "%counter"

import sys
import os

counter_file = sys.argv[1]

# The first time the test is run, initialize the counter to 1.
if not os.path.exists(counter_file):
with open(counter_file, "w") as counter:
counter.write("1")

# Succeed if this is the fourth time we're being run.
with open(counter_file, "r") as counter:
num = int(counter.read())
if num == 4:
sys.exit(0)

# Otherwise, increment the counter and fail
with open(counter_file, "w") as counter:
counter.write(str(num + 1))
sys.exit(1)
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import lit.formats

config.name = "no-allow-retries-no-test_retry_attempts"
config.suffixes = [".py"]
config.test_format = lit.formats.ShTest()
config.test_source_root = None
config.test_exec_root = None

config.substitutions.append(("%python", lit_config.params.get("python", "")))
config.substitutions.append(("%counter", lit_config.params.get("counter", "")))
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# RUN: "%python" "%s" "%counter"

import sys
import os

counter_file = sys.argv[1]

# The first time the test is run, initialize the counter to 1.
if not os.path.exists(counter_file):
with open(counter_file, "w") as counter:
counter.write("1")

# Succeed if this is the fourth time we're being run.
with open(counter_file, "r") as counter:
num = int(counter.read())
if num == 4:
sys.exit(0)

# Otherwise, increment the counter and fail
with open(counter_file, "w") as counter:
counter.write(str(num + 1))
sys.exit(1)
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import lit.formats

config.name = "no-allow-retries-test_retry_attempts"
config.suffixes = [".py"]
config.test_format = lit.formats.ShTest()
config.test_source_root = None
config.test_exec_root = None

config.substitutions.append(("%python", lit_config.params.get("python", "")))
config.substitutions.append(("%counter", lit_config.params.get("counter", "")))

config.test_retry_attempts = 3
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# RUN: "%python" "%s" "%counter"

import sys
import os

counter_file = sys.argv[1]

# The first time the test is run, initialize the counter to 1.
if not os.path.exists(counter_file):
with open(counter_file, "w") as counter:
counter.write("1")

# Succeed if this is the fourth time we're being run.
with open(counter_file, "r") as counter:
num = int(counter.read())
if num == 4:
sys.exit(0)

# Otherwise, increment the counter and fail
with open(counter_file, "w") as counter:
counter.write(str(num + 1))
sys.exit(1)
48 changes: 48 additions & 0 deletions llvm/utils/lit/tests/allow-retries.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,51 @@
# CHECK-TEST7: # executed command: export LLVM_PROFILE_FILE=
# CHECK-TEST7-NOT: # executed command: export LLVM_PROFILE_FILE=
# CHECK-TEST7: Passed With Retry: 1

# This test only passes on the 4th try. Here we check that a test can be re-run when:
# * The "--max-retries-per-test" is specified high enough (3).
# * No ALLOW_RETRIES keyword is used in the test script.
# * No config.test_retry_attempts is adjusted in the test suite config file.
# RUN: rm -f %t.counter
# RUN: %{lit} %{inputs}/max-retries-per-test/no-allow-retries-no-test_retry_attempts/test.py \
# RUN: --max-retries-per-test=3 \
# RUN: -Dcounter=%t.counter \
# RUN: -Dpython=%{python} \
# RUN: | FileCheck --check-prefix=CHECK-TEST8 %s
# CHECK-TEST8: Passed With Retry: 1

# This test only passes on the 4th try. Here we check that a test can be re-run when:
# * The "--max-retries-per-test" is specified too low (2).
# * ALLOW_RETRIES is specified high enough (3)
# * No config.test_retry_attempts is adjusted in the test suite config file.
# RUN: rm -f %t.counter
# RUN: %{lit} %{inputs}/max-retries-per-test/allow-retries-no-test_retry_attempts/test.py \
# RUN: --max-retries-per-test=2 \
# RUN: -Dcounter=%t.counter \
# RUN: -Dpython=%{python} \
# RUN: | FileCheck --check-prefix=CHECK-TEST9 %s
# CHECK-TEST9: Passed With Retry: 1

# This test only passes on the 4th try. Here we check that a test can be re-run when:
# * The "--max-retries-per-test" is specified too low (2).
# * No ALLOW_RETRIES keyword is used in the test script.
# * config.test_retry_attempts is set high enough (3).
# RUN: rm -f %t.counter
# RUN: %{lit} %{inputs}/max-retries-per-test/no-allow-retries-test_retry_attempts/test.py \
# RUN: --max-retries-per-test=2 \
# RUN: -Dcounter=%t.counter \
# RUN: -Dpython=%{python} \
# RUN: | FileCheck --check-prefix=CHECK-TEST10 %s
# CHECK-TEST10: Passed With Retry: 1

# This test only passes on the 4th try. Here we check that a test can be re-run when:
# * The "--max-retries-per-test" is specified too low (1).
# * ALLOW_RETRIES keyword set high enough (3).
# * config.test_retry_attempts is set too low enough (2).
# RUN: rm -f %t.counter
# RUN: %{lit} %{inputs}/max-retries-per-test/no-allow-retries-test_retry_attempts/test.py \
# RUN: --max-retries-per-test=1 \
# RUN: -Dcounter=%t.counter \
# RUN: -Dpython=%{python} \
# RUN: | FileCheck --check-prefix=CHECK-TEST11 %s
# CHECK-TEST11: Passed With Retry: 1