diff --git a/CHANGELOG.md b/CHANGELOG.md index f8a9845..cc4dadc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,4 +44,3 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), [0.6.0]: https://github.com/jerry-git/pytest-split/compare/0.5.0...0.6.0 [0.5.0]: https://github.com/jerry-git/pytest-split/compare/0.4.0...0.5.0 [0.4.0]: https://github.com/jerry-git/pytest-split/tree/0.4.0 - diff --git a/README.md b/README.md index fd04e61..9936f21 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,20 @@ The file path is configurable via `--durations-path` CLI option. pytest --store-durations ``` +With the `--store-durations` flag, you can also specify how test case runtime is managed: + +- `keep`: Only new test cases' runtime are added. The pre-existing durations in the .test_durations file are reused. This is useful when you've added new tests and want to keep the historical durations of older tests. + +```sh +pytest --store-durations=keep +``` + +- `replace` (Default): All test case runtimes in the .test_durations file are replaced with the new ones from the current run. + +```sh +pytest --store-durations=replace +``` + Then we can have as many splits as we want: ```sh pytest --splits 3 --group 1 diff --git a/src/pytest_split/plugin.py b/src/pytest_split/plugin.py index ef635f7..0d7bc70 100644 --- a/src/pytest_split/plugin.py +++ b/src/pytest_split/plugin.py @@ -33,8 +33,11 @@ def pytest_addoption(parser: "Parser") -> None: group.addoption( "--store-durations", dest="store_durations", - action="store_true", help="Store durations into '--durations-path'.", + type=str, + choices=["keep", "replace"], + const="replace", + nargs="?", ) group.addoption( "--durations-path", @@ -187,7 +190,7 @@ class PytestSplitCachePlugin(Base): The cache plugin writes durations to our durations file. """ - def pytest_sessionfinish(self) -> None: + def pytest_sessionfinish(self) -> None: # noqa: C901 """ Method is called by Pytest after the test-suite has run. https://github.com/pytest-dev/pytest/blob/main/src/_pytest/main.py#L308 @@ -216,6 +219,10 @@ def pytest_sessionfinish(self) -> None: if self.config.option.clean_durations: self.cached_durations = dict(test_durations) + elif self.config.option.store_durations == "keep": + for k, v in test_durations.items(): + if k not in self.cached_durations: + self.cached_durations[k] = v else: for k, v in test_durations.items(): self.cached_durations[k] = v diff --git a/tests/test_plugin.py b/tests/test_plugin.py index 869c6a6..dfd5345 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -27,28 +27,66 @@ def durations_path(tmpdir): class TestStoreDurations: - def test_it_stores(self, example_suite, durations_path): + def test_it_stores_replace(self, example_suite, durations_path): example_suite.runpytest("--store-durations", "--durations-path", durations_path) with open(durations_path) as f: durations = json.load(f) assert list(durations.keys()) == [ - "test_it_stores.py::test_1", - "test_it_stores.py::test_10", - "test_it_stores.py::test_2", - "test_it_stores.py::test_3", - "test_it_stores.py::test_4", - "test_it_stores.py::test_5", - "test_it_stores.py::test_6", - "test_it_stores.py::test_7", - "test_it_stores.py::test_8", - "test_it_stores.py::test_9", + "test_it_stores_replace.py::test_1", + "test_it_stores_replace.py::test_10", + "test_it_stores_replace.py::test_2", + "test_it_stores_replace.py::test_3", + "test_it_stores_replace.py::test_4", + "test_it_stores_replace.py::test_5", + "test_it_stores_replace.py::test_6", + "test_it_stores_replace.py::test_7", + "test_it_stores_replace.py::test_8", + "test_it_stores_replace.py::test_9", ] for duration in durations.values(): assert isinstance(duration, float) + def test_it_stores_keep(self, example_suite, durations_path): + example_suite.runpytest("--store-durations", "--durations-path", durations_path) + + with open(durations_path) as f: + durations = json.load(f) + + default_keys = [ + "test_it_stores_keep.py::test_1", + "test_it_stores_keep.py::test_10", + "test_it_stores_keep.py::test_2", + "test_it_stores_keep.py::test_3", + "test_it_stores_keep.py::test_4", + "test_it_stores_keep.py::test_5", + "test_it_stores_keep.py::test_6", + "test_it_stores_keep.py::test_7", + "test_it_stores_keep.py::test_8", + "test_it_stores_keep.py::test_9", + ] + + assert list(durations.keys()) == default_keys + + for duration in durations.values(): + assert isinstance(duration, float) + + example_suite.makepyfile("def test_11(): pass") + example_suite.runpytest( + "--store-durations", "keep", "--durations-path", durations_path + ) + + with open(durations_path) as f: + durations_keep = json.load(f) + assert list(durations_keep.keys()) == [ + *default_keys, + "test_it_stores_keep0/test_it_stores_keep.py::test_11", + ] + for k in default_keys: + assert durations_keep[k] == durations[k] + def test_it_overrides_existing_durations(self, example_suite, durations_path): existing_duration_test_name = "test_it_overrides_existing_durations0/test_it_overrides_existing_durations.py::test_1" old_value = 99