summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--coverage/data.py6
-rw-r--r--coverage/sqldata.py71
-rw-r--r--tests/test_data.py19
3 files changed, 71 insertions, 25 deletions
diff --git a/coverage/data.py b/coverage/data.py
index 44b75439..4b8b7eb2 100644
--- a/coverage/data.py
+++ b/coverage/data.py
@@ -635,10 +635,10 @@ class CoverageJsonData(object):
return self._arcs is not None
-which = os.environ.get("COVERAGE_STORAGE", "json")
-if which == "json":
+STORAGE = os.environ.get("COVERAGE_STORAGE", "json")
+if STORAGE == "json":
CoverageData = CoverageJsonData
-elif which == "sql":
+elif STORAGE == "sql":
from coverage.sqldata import CoverageSqliteData
CoverageData = CoverageSqliteData
diff --git a/coverage/sqldata.py b/coverage/sqldata.py
index 25a6d62d..9d25d92c 100644
--- a/coverage/sqldata.py
+++ b/coverage/sqldata.py
@@ -6,6 +6,7 @@
import glob
import os
import sqlite3
+import struct
from coverage.backward import iitems
from coverage.debug import SimpleRepr
@@ -45,9 +46,13 @@ create table arc (
);
"""
-# >>> struct.unpack(">i", b"\xc0\x7e\x8a\x6e") # "coverage", kind of.
-# (-1065448850,)
-APP_ID = -1065448850
+APP_ID = 0xc07e8a6e # "coverage", kind of.
+
+def unsigned_to_signed(val):
+ return struct.unpack('>i', struct.pack('>I', val))[0]
+
+def signed_to_unsigned(val):
+ return struct.unpack('>I', struct.pack('>i', val))[0]
class CoverageSqliteData(SimpleRepr):
def __init__(self, basename=None, warn=None, debug=None):
@@ -75,7 +80,7 @@ class CoverageSqliteData(SimpleRepr):
self._debug.write("Creating data file {!r}".format(self.filename))
self._db = Sqlite(self.filename, self._debug)
with self._db:
- self._db.execute("pragma application_id = {}".format(APP_ID))
+ self._db.execute("pragma application_id = {}".format(unsigned_to_signed(APP_ID)))
for stmt in SCHEMA.split(';'):
stmt = stmt.strip()
if stmt:
@@ -91,10 +96,10 @@ class CoverageSqliteData(SimpleRepr):
self._db = Sqlite(self.filename, self._debug)
with self._db:
for app_id, in self._db.execute("pragma application_id"):
- app_id = int(app_id)
+ app_id = signed_to_unsigned(int(app_id))
if app_id != APP_ID:
raise CoverageException(
- "File {!r} doesn't look like a coverage data file: "
+ "Couldn't use {!r}: wrong application_id: "
"0x{:08x} != 0x{:08x}".format(self.filename, app_id, APP_ID)
)
for row in self._db.execute("select has_lines, has_arcs from meta"):
@@ -111,6 +116,19 @@ class CoverageSqliteData(SimpleRepr):
self._create_db()
return self._db
+ def __nonzero__(self):
+ try:
+ with self._connect() as con:
+ if self.has_arcs():
+ rows = con.execute("select * from arc limit 1")
+ else:
+ rows = con.execute("select * from line limit 1")
+ return bool(list(rows))
+ except CoverageException:
+ return False
+
+ __bool__ = __nonzero__
+
def _file_id(self, filename):
self._start_writing()
if filename not in self._file_map:
@@ -184,13 +202,28 @@ class CoverageSqliteData(SimpleRepr):
"""
self._start_writing()
with self._connect() as con:
- data = list(iitems(file_tracers))
- if data:
- con.executemany(
- "insert into file (path, tracer) values (?, ?) on duplicate key update",
- data,
+ for filename, plugin_name in iitems(file_tracers):
+ con.execute(
+ "update file set tracer = ? where path = ?",
+ (plugin_name, filename)
)
+ def touch_file(self, filename, plugin_name=""):
+ """Ensure that `filename` appears in the data, empty if needed.
+
+ `plugin_name` is the name of the plugin resposible for this file. It is used
+ to associate the right filereporter, etc.
+ """
+ if self._debug and self._debug.should('dataop'):
+ self._debug.write("Touching %r" % (filename,))
+ if not self._has_arcs and not self._has_lines:
+ raise CoverageException("Can't touch files in an empty CoverageSqliteData")
+
+ file_id = self._file_id(filename)
+ if plugin_name:
+ # Set the tracer for this file
+ self.add_file_tracers({filename: plugin_name})
+
def erase(self, parallel=False):
"""Erase the data in this object.
@@ -239,7 +272,10 @@ class CoverageSqliteData(SimpleRepr):
was not measured, then None is returned.
"""
- return "" # TODO
+ with self._connect() as con:
+ for tracer, in con.execute("select tracer from file where path = ?", (filename,)):
+ return tracer or ""
+ return None
def lines(self, filename):
if self.has_arcs():
@@ -258,13 +294,17 @@ class CoverageSqliteData(SimpleRepr):
file_id = self._file_id(filename)
return [pair for pair in con.execute("select fromno, tono from arc where file_id = ?", (file_id,))]
+ def run_infos(self):
+ return [] # TODO
+
class Sqlite(SimpleRepr):
def __init__(self, filename, debug):
self.debug = debug if (debug and debug.should('sql')) else None
if self.debug:
self.debug.write("Connecting to {!r}".format(filename))
- self.con = sqlite3.connect(filename)
+ self.filename = filename
+ self.con = sqlite3.connect(self.filename)
# This pragma makes writing faster. It disables rollbacks, but we never need them.
self.execute("pragma journal_mode=off")
@@ -285,7 +325,10 @@ class Sqlite(SimpleRepr):
if self.debug:
tail = " with {!r}".format(parameters) if parameters else ""
self.debug.write("Executing {!r}{}".format(sql, tail))
- return self.con.execute(sql, parameters)
+ try:
+ return self.con.execute(sql, parameters)
+ except sqlite3.Error as exc:
+ raise CoverageException("Couldn't use data file {!r}: {}".format(self.filename, exc))
def executemany(self, sql, data):
if self.debug:
diff --git a/tests/test_data.py b/tests/test_data.py
index 7ca6f655..5e75b012 100644
--- a/tests/test_data.py
+++ b/tests/test_data.py
@@ -12,7 +12,7 @@ import re
import mock
from coverage.data import CoverageData, debug_main, canonicalize_json_data, combine_parallel_data
-from coverage.data import add_data_to_hash, line_counts
+from coverage.data import add_data_to_hash, line_counts, STORAGE
from coverage.debug import DebugControlString
from coverage.files import PathAliases, canonical_filename
from coverage.misc import CoverageException
@@ -105,7 +105,7 @@ class DataTestHelpers(CoverageTest):
class CoverageDataTest(DataTestHelpers, CoverageTest):
"""Test cases for CoverageData."""
- run_in_temp_dir = False
+ run_in_temp_dir = STORAGE == "sql"
def test_empty_data_is_false(self):
covdata = CoverageData()
@@ -449,7 +449,7 @@ class CoverageDataTestInTempDir(DataTestHelpers, CoverageTest):
self.assert_arcs3_data(covdata2)
def test_read_errors(self):
- msg = r"Couldn't read data from '.*[/\\]{0}': \S+"
+ msg = r"Couldn't .* '.*[/\\]{0}': \S+"
self.make_file("xyzzy.dat", "xyzzy")
with self.assertRaisesRegex(CoverageException, msg.format("xyzzy.dat")):
@@ -463,11 +463,12 @@ class CoverageDataTestInTempDir(DataTestHelpers, CoverageTest):
covdata.read()
self.assertFalse(covdata)
- self.make_file("misleading.dat", CoverageData._GO_AWAY + " this isn't JSON")
- with self.assertRaisesRegex(CoverageException, msg.format("misleading.dat")):
- covdata = CoverageData("misleading.dat")
- covdata.read()
- self.assertFalse(covdata)
+ if STORAGE == "json":
+ self.make_file("misleading.dat", CoverageData._GO_AWAY + " this isn't JSON")
+ with self.assertRaisesRegex(CoverageException, msg.format("misleading.dat")):
+ covdata = CoverageData("misleading.dat")
+ covdata.read()
+ self.assertFalse(covdata)
def test_debug_main(self):
covdata1 = CoverageData(".coverage")
@@ -640,6 +641,8 @@ class CoverageDataFilesTest(DataTestHelpers, CoverageTest):
def read_json_data_file(self, fname):
"""Read a JSON data file for testing the JSON directly."""
+ if STORAGE != "json":
+ self.skipTest("Not using JSON for data storage")
with open(fname, 'r') as fdata:
go_away = fdata.read(len(CoverageData._GO_AWAY))
self.assertEqual(go_away, CoverageData._GO_AWAY)