diff --git a/ducktape/command_line/main.py b/ducktape/command_line/main.py index 2237fba8c..560e89e5c 100644 --- a/ducktape/command_line/main.py +++ b/ducktape/command_line/main.py @@ -131,7 +131,7 @@ def main(): # Discover and load tests to be run loader = TestLoader(session_context, session_logger, repeat=args_dict["repeat"], injected_args=injected_args, - subset=args_dict["subset"], subsets=args_dict["subsets"]) + subset=args_dict["subset"], subsets=args_dict["subsets"], allow_empty_tests_list=args_dict["allow_empty_tests_list"]) try: tests = loader.load(args_dict["test_path"], excluded_test_symbols=args_dict['exclude']) except LoaderException as e: diff --git a/ducktape/command_line/parse_args.py b/ducktape/command_line/parse_args.py index d402f1627..aa96d7af8 100644 --- a/ducktape/command_line/parse_args.py +++ b/ducktape/command_line/parse_args.py @@ -100,6 +100,8 @@ def create_ducktape_parser(): "to determine flakyness. When not present, deflake will not be used, " "and a test will be marked as either passed or failed. " "When enabled tests will be marked as flaky if it passes on any of the reruns") + parser.add_argument("--allow-empty-tests-list", action="store_true", + help="Proceeds without failing when no tests are loaded ") return parser diff --git a/ducktape/tests/loader.py b/ducktape/tests/loader.py index df2d1314f..665b04d09 100644 --- a/ducktape/tests/loader.py +++ b/ducktape/tests/loader.py @@ -52,7 +52,7 @@ class TestLoader(object): """Class used to discover and load tests.""" def __init__(self, session_context, logger, repeat=1, injected_args=None, cluster=None, subset=0, subsets=1, - historical_report=None): + historical_report=None, allow_empty_tests_list=False): self.session_context = session_context self.cluster = cluster assert logger is not None @@ -74,6 +74,7 @@ def __init__(self, session_context, logger, repeat=1, injected_args=None, cluste # A non-None value here means the loader will override the injected_args # in any discovered test, whether or not it is parametrized self.injected_args = injected_args + self.allow_empty_tests_list = allow_empty_tests_list def load(self, symbols, excluded_test_symbols=None): """ @@ -146,7 +147,9 @@ def load(self, symbols, excluded_test_symbols=None): # Sort to make sure we get a consistent order for when we create subsets all_test_context_list = sorted(all_test_context_list, key=attrgetter("test_id")) if not all_test_context_list: - raise LoaderException("No tests to run!") + if not self.allow_empty_tests_list: + raise LoaderException("No tests to run!") + self.logger.warn("No tests to run!") self.logger.debug("Discovered these tests: " + str(all_test_context_list)) # Select the subset of tests. if self.historical_report: diff --git a/tests/loader/check_loader.py b/tests/loader/check_loader.py index aa5f66f9b..37766133a 100644 --- a/tests/loader/check_loader.py +++ b/tests/loader/check_loader.py @@ -403,6 +403,13 @@ def check_test_loader_raises_on_params_not_found(self): with pytest.raises(LoaderException, match='No tests to run'): loader.load(included) + def check_test_loader_allow_empty_tests_list(self): + loader = TestLoader(self.SESSION_CONTEXT, logger=Mock(), allow_empty_tests_list=True) + # parameter syntax is valid, but there is no such parameter defined in the test annotation in the code + included = [os.path.join(discover_dir(), 'test_decorated.py::TestMatrix.test_thing@{"x": 1,"y": "missing"}')] + with pytest.raises(LoaderException, match='No tests to run'): + loader.load(included) + @pytest.mark.parametrize("symbol", [ # no class 'test_decorated.py::.test_thing'