Skip to content
Closed
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
2 changes: 2 additions & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ Changelog
Unreleased
~~~~~~~~~~

* Fix obsolete test names never being removed from ``mutants/mutmut-stats.json``: the counters guarding the save iterated dict keys instead of the test sets, so the cleanup was applied in memory but never persisted (`#564`)

* Fix ``# pragma: no mutate block`` being silently ignored when placed on an ``else``, ``except``, ``except*`` or ``finally`` header, and on the ``try`` line of a ``try``/``except*``

* Fix mutants being reported as survived when a test uses ``patch.dict(os.environ, ..., clear=True)`` (`#511`)
Expand Down
4 changes: 2 additions & 2 deletions src/mutmut/runners/harness.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,15 +60,15 @@ def __init__(self, *, ids: set[str]) -> None:
self.ids = ids

def clear_out_obsolete_test_names(self) -> None:
count_before = sum(len(x) for x in state().tests_by_mangled_function_name)
count_before = sum(len(x) for x in state().tests_by_mangled_function_name.values())
state().tests_by_mangled_function_name = defaultdict(
set,
**{
k: {test_name for test_name in test_names if test_name in self.ids}
for k, test_names in state().tests_by_mangled_function_name.items()
},
)
count_after = sum(len(x) for x in state().tests_by_mangled_function_name)
count_after = sum(len(x) for x in state().tests_by_mangled_function_name.values())
if count_before != count_after:
print(f"Removed {count_before - count_after} obsolete test names")
save_stats()
Expand Down
Empty file added tests/runners/__init__.py
Empty file.
56 changes: 56 additions & 0 deletions tests/runners/test_harness.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
"""Tests for the incremental-stats bookkeeping in ``ListAllTestsResult``."""

import json
from collections import defaultdict
from pathlib import Path

import pytest

from mutmut.runners.harness import ListAllTestsResult
from mutmut.state import state

STATS_FILE = Path("mutants/mutmut-stats.json")


@pytest.fixture(autouse=True)
def isolated_stats(tmp_path, monkeypatch):
# save_stats() writes mutants/mutmut-stats.json relative to the cwd, and it
# loads the config on the way, which needs a guessable source dir to exist.
monkeypatch.chdir(tmp_path)
(tmp_path / "mutants").mkdir()
(tmp_path / "src").mkdir()
saved = state().tests_by_mangled_function_name
state().tests_by_mangled_function_name = defaultdict(set)
yield
state().tests_by_mangled_function_name = saved


class TestClearOutObsoleteTestNames:
def test_obsolete_test_names_are_dropped_and_persisted(self, capsys):
state().tests_by_mangled_function_name["pkg.foo.x_add"] |= {
"tests/test_foo.py::test_add",
"tests/test_gone.py::test_add",
}
state().tests_by_mangled_function_name["pkg.foo.x_sub"].add("tests/test_gone.py::test_sub")

ListAllTestsResult(ids={"tests/test_foo.py::test_add"}).clear_out_obsolete_test_names()

assert dict(state().tests_by_mangled_function_name) == {
"pkg.foo.x_add": {"tests/test_foo.py::test_add"},
"pkg.foo.x_sub": set(),
}
assert "Removed 2 obsolete test names" in capsys.readouterr().out
on_disk = json.loads(STATS_FILE.read_text())
assert on_disk["tests_by_mangled_function_name"] == {
"pkg.foo.x_add": ["tests/test_foo.py::test_add"],
"pkg.foo.x_sub": [],
}

def test_nothing_obsolete_leaves_stats_file_untouched(self, capsys):
state().tests_by_mangled_function_name["pkg.foo.x_add"].add("tests/test_foo.py::test_add")

ListAllTestsResult(ids={"tests/test_foo.py::test_add"}).clear_out_obsolete_test_names()

assert dict(state().tests_by_mangled_function_name) == {"pkg.foo.x_add": {"tests/test_foo.py::test_add"}}
assert capsys.readouterr().out == ""
assert not STATS_FILE.exists()