diff --git a/CHANGELOG.md b/CHANGELOG.md index 1391dff..6c077a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added +- Add `--no-split` to disable splitting even when `--splits` and `--group` are configured in pytest's `addopts`. + ### Fixed - Fix malformed bullet points rendering in GitHub Pages documentation diff --git a/README.md b/README.md index 03d30e2..75e93b0 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,15 @@ pytest --splits 3 --group 2 pytest --splits 3 --group 3 ``` +To disable splitting for a single run, use `--no-split`. This overrides `--splits` +and `--group`, including values configured in pytest's `addopts`: +```sh +pytest --no-split +``` + +Other pytest test selection options still apply. You can also combine `--no-split` +with `--store-durations` to refresh duration information without splitting the suite. + Time goes by, new tests are added and old ones are removed/renamed during development. No worries! `pytest-split` assumes average test execution time (calculated based on the stored information) for every test which does not have duration information stored. Thus, there's no need to store durations after changing the test suite. diff --git a/src/pytest_split/plugin.py b/src/pytest_split/plugin.py index 9140936..4b66d69 100644 --- a/src/pytest_split/plugin.py +++ b/src/pytest_split/plugin.py @@ -55,6 +55,11 @@ def pytest_addoption(parser: "Parser") -> None: type=int, help="The group of tests that should be executed (first one is 1)", ) + group.addoption( + "--no-split", + action="store_true", + help="Disable test splitting, overriding --splits and --group.", + ) group.addoption( "--splitting-algorithm", dest="splitting_algorithm", @@ -79,6 +84,9 @@ def pytest_cmdline_main(config: "Config") -> "int | ExitCode | None": """ Validate options. """ + if config.option.no_split: + return None + group = config.getoption("group") splits = config.getoption("splits") @@ -104,7 +112,7 @@ def pytest_configure(config: "Config") -> None: """ Enable the plugins we need. """ - if config.option.splits and config.option.group: + if not config.option.no_split and config.option.splits and config.option.group: config.pluginmanager.register(PytestSplitPlugin(config), "pytestsplitplugin") if config.option.store_durations: diff --git a/tests/test_plugin.py b/tests/test_plugin.py index 51b8577..fe65e68 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -313,6 +313,61 @@ def test_it_splits_with_other_collect_hooks(self, testdir, durations_path): assert _passed_test_names(result) == expected_tests +class TestNoSplit: + @pytest.mark.parametrize("algorithm", Algorithms.names()) + def test_overrides_configured_splitting(self, example_suite, algorithm): + example_suite.makeini( + f"[pytest]\naddopts = --splits 2 --group 1 --splitting-algorithm {algorithm}" + ) + + result = example_suite.runpytest("--no-split") + + result.assert_outcomes(passed=EXAMPLE_SUITE_TEST_COUNT) + assert "[pytest-split]" not in result.stdout.str() + + @pytest.mark.parametrize( + "addopts", + [ + "", + "--splits 2", + "--group 1", + "--splits 0 --group 1", + "--splits 2 --group 3", + ], + ) + def test_ignores_unused_split_configuration(self, example_suite, addopts): + example_suite.makeini(f"[pytest]\naddopts = {addopts}") + + result = example_suite.runpytest("--no-split") + + result.assert_outcomes(passed=EXAMPLE_SUITE_TEST_COUNT) + + def test_preserves_other_test_selection(self, example_suite): + example_suite.makeini("[pytest]\naddopts = --splits 2 --group 2") + + result = example_suite.runpytest("--no-split", "-k", "test_1") + + result.assert_outcomes(passed=2) + result.stdout.fnmatch_lines( + ["*collected 10 items / 8 deselected / 2 selected*"] + ) + + def test_still_stores_durations(self, example_suite, durations_path): + example_suite.makeini("[pytest]\naddopts = --splits 2 --group 1") + + result = example_suite.runpytest( + "--no-split", "--store-durations", "--durations-path", durations_path + ) + + result.assert_outcomes(passed=EXAMPLE_SUITE_TEST_COUNT) + with open(durations_path) as f: + durations = json.load(f) + assert set(durations) == { + f"test_still_stores_durations.py::test_{num}" + for num in range(1, EXAMPLE_SUITE_TEST_COUNT + 1) + } + + class TestRaisesUsageErrors: def test_returns_nonzero_when_group_but_not_splits(self, example_suite, capsys): result = example_suite.inline_run("--group", "1")