From a49ca095f5e7232a625f3c4ef438d9970d66902c Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sun, 22 May 2022 16:45:40 -0400 Subject: [PATCH] feat: a new debug option `sqldata` shows all the data being written to the db. --- CHANGES.rst | 3 +++ coverage/sqldata.py | 33 +++++++++++++++++++++++---------- doc/cmd.rst | 3 +++ tests/test_data.py | 17 ++++++++++++----- 4 files changed, 41 insertions(+), 15 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index c483ed391..7d34fc12c 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -36,6 +36,9 @@ Unreleased - The timestamp and version are displayed at the top of the report. Thanks, `Ammar Askar `_. Closes `issue 1351`_. +- A new debug option ``debug=sqldata`` adds more detail to ``debug=sql``, + logging all the data being written to the database. + - On Python 3.11, the ``[toml]`` extra no longer installs tomli, instead using tomllib from the standard library. Thanks `Shantanu `_. diff --git a/coverage/sqldata.py b/coverage/sqldata.py index 0081d8a50..1d6f626ea 100644 --- a/coverage/sqldata.py +++ b/coverage/sqldata.py @@ -1,10 +1,7 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt -"""Sqlite coverage data.""" - -# TODO: factor out dataop debugging to a wrapper class? -# TODO: make sure all dataop debugging is in place somehow +"""SQLite coverage data.""" import collections import datetime @@ -31,7 +28,7 @@ os = isolate_module(os) # If you change the schema, increment the SCHEMA_VERSION, and update the -# docs in docs/dbschema.rst also. +# docs in docs/dbschema.rst by running "make cogdoc". SCHEMA_VERSION = 7 @@ -390,8 +387,10 @@ def _file_id(self, filename, add=False): if filename not in self._file_map: if add: with self._connect() as con: - cur = con.execute("insert or replace into file (path) values (?)", (filename,)) - self._file_map[filename] = cur.lastrowid + self._file_map[filename] = con.execute_for_rowid( + "insert or replace into file (path) values (?)", + (filename,) + ) return self._file_map.get(filename) def _context_id(self, context): @@ -428,8 +427,10 @@ def _set_context_id(self): self._current_context_id = context_id else: with self._connect() as con: - cur = con.execute("insert into context (context) values (?)", (context,)) - self._current_context_id = cur.lastrowid + self._current_context_id = con.execute_for_rowid( + "insert into context (context) values (?)", + (context,) + ) def base_filename(self): """The base filename for storing data. @@ -1126,6 +1127,14 @@ def execute(self, sql, parameters=()): self.debug.write(f"EXCEPTION from execute: {msg}") raise DataError(f"Couldn't use data file {self.filename!r}: {msg}") from exc + def execute_for_rowid(self, sql, parameters=()): + """Like execute, but returns the lastrowid.""" + con = self.execute(sql, parameters) + rowid = con.lastrowid + if self.debug.should("sqldata"): + self.debug.write(f"Row id result: {rowid!r}") + return rowid + def execute_one(self, sql, parameters=()): """Execute a statement and return the one row that results. @@ -1147,7 +1156,11 @@ def executemany(self, sql, data): """Same as :meth:`python:sqlite3.Connection.executemany`.""" if self.debug.should("sql"): data = list(data) - self.debug.write(f"Executing many {sql!r} with {len(data)} rows") + final = ":" if self.debug.should("sqldata") else "" + self.debug.write(f"Executing many {sql!r} with {len(data)} rows{final}") + if self.debug.should("sqldata"): + for i, row in enumerate(data): + self.debug.write(f"{i:4d}: {row!r}") try: return self.con.executemany(sql, data) except Exception: # pragma: cant happen diff --git a/doc/cmd.rst b/doc/cmd.rst index 6f8563849..cb784ca10 100644 --- a/doc/cmd.rst +++ b/doc/cmd.rst @@ -1014,6 +1014,9 @@ of operation to log: * ``sql``: log the SQL statements used for recording data. +* ``sqldata``: when used with ``debug=sql``, also log the full data being used + in SQL statements. + * ``sys``: before starting, dump all the system and environment information, as with :ref:`coverage debug sys `. diff --git a/tests/test_data.py b/tests/test_data.py index d20e3ff04..9f9a92bd5 100644 --- a/tests/test_data.py +++ b/tests/test_data.py @@ -67,7 +67,12 @@ def DebugCoverageData(*args, **kwargs): lines in our coverage reports. """ assert "debug" not in kwargs - debug = DebugControlString(options=["dataio", "dataop", "sql"]) + options = ["dataio", "dataop", "sql"] + if kwargs: + # There's no reason kwargs should imply sqldata debugging. + # This is a way to get a mix of debug options across the tests. + options.extend(["sqldata"]) + debug = DebugControlString(options=options) return CoverageData(*args, debug=debug, **kwargs) @@ -160,15 +165,17 @@ def test_ok_to_add_arcs_twice(self): assert_line_counts(covdata, SUMMARY_3_4) assert_measured_files(covdata, MEASURED_FILES_3_4) - def test_cant_add_arcs_with_lines(self): - covdata = DebugCoverageData() + @pytest.mark.parametrize("klass", [CoverageData, DebugCoverageData]) + def test_cant_add_arcs_with_lines(self, klass): + covdata = klass() covdata.add_lines(LINES_1) msg = "Can't add branch measurements to existing line data" with pytest.raises(DataError, match=msg): covdata.add_arcs(ARCS_3) - def test_cant_add_lines_with_arcs(self): - covdata = DebugCoverageData() + @pytest.mark.parametrize("klass", [CoverageData, DebugCoverageData]) + def test_cant_add_lines_with_arcs(self, klass): + covdata = klass() covdata.add_arcs(ARCS_3) msg = "Can't add line measurements to existing branch data" with pytest.raises(DataError, match=msg):