Skip to content

Commit a4cd6a0

Browse files
feat: add purge_files method to CoverageData + unit tests for it (#1547)
* Add purge_files method to CoverageData, to allow for selective removal and update of coverage data. * Fix assert syntax so it's not true; this code isn't reached in the test unless it fails and then it would have failed to fail. * Remove trailing whitespace; did not expect this would matter on a blank line. * Add type annotations required by mypy --------- Co-authored-by: Stephan Deibel <[email protected]>
1 parent 441823f commit a4cd6a0

File tree

2 files changed

+142
-0
lines changed

2 files changed

+142
-0
lines changed

coverage/sqldata.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -615,6 +615,42 @@ def touch_files(self, filenames: Collection[str], plugin_name: Optional[str] = N
615615
# Set the tracer for this file
616616
self.add_file_tracers({filename: plugin_name})
617617

618+
def purge_files(self, filenames: Iterable[str], context: Optional[str] = None) -> None:
619+
"""Purge any existing coverage data for the given `filenames`.
620+
621+
If `context` is given, purge only data associated with that measurement context.
622+
"""
623+
624+
if self._debug.should("dataop"):
625+
self._debug.write(f"Purging {filenames!r} for context {context}")
626+
self._start_using()
627+
with self._connect() as con:
628+
629+
if context is not None:
630+
context_id = self._context_id(context)
631+
if context_id is None:
632+
raise DataError("Unknown context {context}")
633+
else:
634+
context_id = None
635+
636+
if self._has_lines:
637+
table = 'line_bits'
638+
elif self._has_arcs:
639+
table = 'arcs'
640+
else:
641+
return
642+
643+
for filename in filenames:
644+
file_id = self._file_id(filename, add=False)
645+
if file_id is None:
646+
continue
647+
self._file_map.pop(filename, None)
648+
if context_id is None:
649+
q = f'delete from {table} where file_id={file_id}'
650+
else:
651+
q = f'delete from {table} where file_id={file_id} and context_id={context_id}'
652+
con.execute(q)
653+
618654
def update(self, other_data: CoverageData, aliases: Optional[PathAliases] = None) -> None:
619655
"""Update this data with data from several other :class:`CoverageData` instances.
620656

tests/test_api.py

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -754,6 +754,112 @@ def test_run_debug_sys(self) -> None:
754754
cov.stop() # pragma: nested
755755
assert cast(str, d['data_file']).endswith(".coverage")
756756

757+
def test_purge_filenames(self) -> None:
758+
759+
fn1 = self.make_file("mymain.py", """\
760+
import mymod
761+
a = 1
762+
""")
763+
fn1 = os.path.join(self.temp_dir, fn1)
764+
765+
fn2 = self.make_file("mymod.py", """\
766+
fooey = 17
767+
""")
768+
fn2 = os.path.join(self.temp_dir, fn2)
769+
770+
cov = coverage.Coverage()
771+
self.start_import_stop(cov, "mymain")
772+
773+
data = cov.get_data()
774+
775+
# Initial measurement was for two files
776+
assert len(data.measured_files()) == 2
777+
assert [1, 2] == sorted_lines(data, fn1)
778+
assert [1,] == sorted_lines(data, fn2)
779+
780+
# Purge one file's data and one should remain
781+
data.purge_files([fn1])
782+
assert len(data.measured_files()) == 1
783+
assert [] == sorted_lines(data, fn1)
784+
assert [1,] == sorted_lines(data, fn2)
785+
786+
# Purge second file's data and none should remain
787+
data.purge_files([fn2])
788+
assert len(data.measured_files()) == 0
789+
assert [] == sorted_lines(data, fn1)
790+
assert [] == sorted_lines(data, fn2)
791+
792+
def test_purge_filenames_context(self) -> None:
793+
794+
fn1 = self.make_file("mymain.py", """\
795+
import mymod
796+
a = 1
797+
""")
798+
fn1 = os.path.join(self.temp_dir, fn1)
799+
800+
fn2 = self.make_file("mymod.py", """\
801+
fooey = 17
802+
""")
803+
fn2 = os.path.join(self.temp_dir, fn2)
804+
805+
def dummy_function() -> None:
806+
unused = 42
807+
808+
# Start/stop since otherwise cantext
809+
cov = coverage.Coverage()
810+
cov.start()
811+
cov.switch_context('initialcontext')
812+
dummy_function()
813+
cov.switch_context('testcontext')
814+
cov.stop()
815+
self.start_import_stop(cov, "mymain")
816+
817+
data = cov.get_data()
818+
819+
# Initial measurement was for three files and two contexts
820+
assert len(data.measured_files()) == 3
821+
assert [1, 2] == sorted_lines(data, fn1)
822+
assert [1,] == sorted_lines(data, fn2)
823+
assert len(sorted_lines(data, __file__)) == 1
824+
assert len(data.measured_contexts()) == 2
825+
826+
# Remove specifying wrong context should raise exception and not remove anything
827+
try:
828+
data.purge_files([fn1], 'wrongcontext')
829+
except coverage.sqldata.DataError:
830+
pass
831+
else:
832+
assert 0, "exception expected"
833+
assert len(data.measured_files()) == 3
834+
assert [1, 2] == sorted_lines(data, fn1)
835+
assert [1,] == sorted_lines(data, fn2)
836+
assert len(sorted_lines(data, __file__)) == 1
837+
assert len(data.measured_contexts()) == 2
838+
839+
# Remove one file specifying correct context
840+
data.purge_files([fn1], 'testcontext')
841+
assert len(data.measured_files()) == 2
842+
assert [] == sorted_lines(data, fn1)
843+
assert [1,] == sorted_lines(data, fn2)
844+
assert len(sorted_lines(data, __file__)) == 1
845+
assert len(data.measured_contexts()) == 2
846+
847+
# Remove second file with other correct context
848+
data.purge_files([__file__], 'initialcontext')
849+
assert len(data.measured_files()) == 1
850+
assert [] == sorted_lines(data, fn1)
851+
assert [1,] == sorted_lines(data, fn2)
852+
assert len(sorted_lines(data, __file__)) == 0
853+
assert len(data.measured_contexts()) == 2
854+
855+
# Remove last file specifying correct context
856+
data.purge_files([fn2], 'testcontext')
857+
assert len(data.measured_files()) == 0
858+
assert [] == sorted_lines(data, fn1)
859+
assert [] == sorted_lines(data, fn2)
860+
assert len(sorted_lines(data, __file__)) == 0
861+
assert len(data.measured_contexts()) == 2
862+
757863

758864
class CurrentInstanceTest(CoverageTest):
759865
"""Tests of Coverage.current()."""

0 commit comments

Comments
 (0)