Skip to content

Commit b41be3f

Browse files
committed
fix: create the db as needed when accessed
1 parent f4ee9a2 commit b41be3f

File tree

3 files changed

+33
-45
lines changed

3 files changed

+33
-45
lines changed

CHANGES.rst

+6
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,20 @@ Unreleased
2626

2727
- Updated Python 3.11 support to 3.11.0a4, fixing `issue 1294`_.
2828

29+
- Fix: the coverage data file is now created in a more robust way, to avoid
30+
problems when multiple processes are trying to write data at once. Fixes
31+
`issue 1303`_ and `issue 883`_.
32+
2933
- Fix: a .gitignore file will only be written into the HTML report output
3034
directory if the directory is empty. This should prevent certain unfortunate
3135
accidents of writing the file where it is not wanted.
3236

3337
- Releases now have MacOS arm64 wheels for Apple Silicon (fixes `issue 1288`_).
3438

39+
.. _issue 883: https://github.com/nedbat/coveragepy/issues/883
3540
.. _issue 1288: https://github.com/nedbat/coveragepy/issues/1288
3641
.. _issue 1294: https://github.com/nedbat/coveragepy/issues/1294
42+
.. _issue 1303: https://github.com/nedbat/coveragepy/issues/1303
3743

3844

3945
.. _changes_62:

coverage/sqldata.py

+25-30
Original file line numberDiff line numberDiff line change
@@ -256,26 +256,6 @@ def _reset(self):
256256
self._have_used = False
257257
self._current_context_id = None
258258

259-
def _create_db(self):
260-
"""Create a db file that doesn't exist yet.
261-
262-
Initializes the schema and certain metadata.
263-
"""
264-
if self._debug.should("dataio"):
265-
self._debug.write(f"Creating data file {self._filename!r}")
266-
self._dbs[threading.get_ident()] = db = SqliteDb(self._filename, self._debug)
267-
with db:
268-
db.executescript(SCHEMA)
269-
db.execute("insert into coverage_schema (version) values (?)", (SCHEMA_VERSION,))
270-
db.executemany(
271-
"insert into meta (key, value) values (?, ?)",
272-
[
273-
("sys_argv", str(getattr(sys, "argv", None))),
274-
("version", __version__),
275-
("when", datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")),
276-
]
277-
)
278-
279259
def _open_db(self):
280260
"""Open an existing db file, and read its metadata."""
281261
if self._debug.should("dataio"):
@@ -289,11 +269,14 @@ def _read_db(self):
289269
try:
290270
schema_version, = db.execute_one("select version from coverage_schema")
291271
except Exception as exc:
292-
raise DataError(
293-
"Data file {!r} doesn't seem to be a coverage data file: {}".format(
294-
self._filename, exc
295-
)
296-
) from exc
272+
if "no such table: coverage_schema" in str(exc):
273+
self._init_db(db)
274+
else:
275+
raise DataError(
276+
"Data file {!r} doesn't seem to be a coverage data file: {}".format(
277+
self._filename, exc
278+
)
279+
) from exc
297280
else:
298281
if schema_version != SCHEMA_VERSION:
299282
raise DataError(
@@ -309,13 +292,25 @@ def _read_db(self):
309292
for path, file_id in db.execute("select path, id from file"):
310293
self._file_map[path] = file_id
311294

295+
def _init_db(self, db):
296+
"""Write the initial contents of the database."""
297+
if self._debug.should("dataio"):
298+
self._debug.write(f"Initing data file {self._filename!r}")
299+
db.executescript(SCHEMA)
300+
db.execute("insert into coverage_schema (version) values (?)", (SCHEMA_VERSION,))
301+
db.executemany(
302+
"insert or ignore into meta (key, value) values (?, ?)",
303+
[
304+
("sys_argv", str(getattr(sys, "argv", None))),
305+
("version", __version__),
306+
("when", datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")),
307+
]
308+
)
309+
312310
def _connect(self):
313311
"""Get the SqliteDb object to use."""
314312
if threading.get_ident() not in self._dbs:
315-
if os.path.exists(self._filename):
316-
self._open_db()
317-
else:
318-
self._create_db()
313+
self._open_db()
319314
return self._dbs[threading.get_ident()]
320315

321316
def __bool__(self):
@@ -522,7 +517,7 @@ def _choose_lines_or_arcs(self, lines=False, arcs=False):
522517
self._has_arcs = arcs
523518
with self._connect() as con:
524519
con.execute(
525-
"insert into meta (key, value) values (?, ?)",
520+
"insert or ignore into meta (key, value) values (?, ?)",
526521
("has_arcs", str(int(arcs)))
527522
)
528523

tests/test_data.py

+2-15
Original file line numberDiff line numberDiff line change
@@ -592,12 +592,6 @@ def test_read_errors(self):
592592
covdata.read()
593593
assert not covdata
594594

595-
self.make_file("empty.dat", "")
596-
with pytest.raises(DataError, match=msg.format("empty.dat")):
597-
covdata = DebugCoverageData("empty.dat")
598-
covdata.read()
599-
assert not covdata
600-
601595
def test_hard_read_error(self):
602596
self.make_file("noperms.dat", "go away")
603597
os.chmod("noperms.dat", 0)
@@ -626,14 +620,6 @@ def test_read_sql_errors(self):
626620
covdata.read()
627621
assert not covdata
628622

629-
with sqlite3.connect("no_schema.db") as con:
630-
con.execute("create table foobar (baz text)")
631-
msg = r"Couldn't .* '.*[/\\]no_schema.db': \S+"
632-
with pytest.raises(DataError, match=msg):
633-
covdata = DebugCoverageData("no_schema.db")
634-
covdata.read()
635-
assert not covdata
636-
637623

638624
class CoverageDataFilesTest(CoverageTest):
639625
"""Tests of CoverageData file handling."""
@@ -667,7 +653,8 @@ def test_debug_output_with_debug_option(self):
667653

668654
assert re.search(
669655
r"^Erasing data file '.*\.coverage'\n" +
670-
r"Creating data file '.*\.coverage'\n" +
656+
r"Opening data file '.*\.coverage'\n" +
657+
r"Initing data file '.*\.coverage'\n" +
671658
r"Opening data file '.*\.coverage'\n$",
672659
debug.get_output()
673660
)

0 commit comments

Comments
 (0)