summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNed Batchelder <ned@nedbatchelder.com>2019-07-07 13:47:25 -0400
committerGitHub <noreply@github.com>2019-07-07 13:47:25 -0400
commita9c49fbbd4fe7429936a8cc2cb50ec834c9aeb8b (patch)
treec08f34073cc3c768a366f1887e89f20778fd7428
parent48f996ff8dcccfefe0922fafc070b36f980c231e (diff)
parentdade9db58673d4f712ba5c50ed66d1c67a7e1d5d (diff)
downloadpython-coveragepy-git-a9c49fbbd4fe7429936a8cc2cb50ec834c9aeb8b.tar.gz
Merge branch 'master' into Fix-typo
-rw-r--r--CHANGES.rst2
-rw-r--r--coverage/cmdline.py19
-rw-r--r--coverage/control.py5
-rw-r--r--coverage/sqldata.py32
-rw-r--r--tests/test_api.py15
-rw-r--r--tests/test_debug.py10
6 files changed, 54 insertions, 29 deletions
diff --git a/CHANGES.rst b/CHANGES.rst
index 52ec0761..ec5fea2d 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -32,6 +32,8 @@ Unreleased
information to each covered line. Hovering over the "ctx" marker at the
end of the line reveals a list of the contexts that covered the line.
+- Dynamic contexts with no data are no longer written to the database.
+
- Added the classmethod :meth:`Coverage.current` to get the latest started
`Coverage` instance.
diff --git a/coverage/cmdline.py b/coverage/cmdline.py
index 354ae8c2..fdab7d93 100644
--- a/coverage/cmdline.py
+++ b/coverage/cmdline.py
@@ -799,3 +799,22 @@ def main(argv=None):
else:
status = None
return status
+
+# Profiling using ox_profile. Install it from GitHub:
+# pip install git+https://github.com/emin63/ox_profile.git
+#
+# $set_env.py: COVERAGE_PROFILE - Set to use ox_profile.
+_profile = os.environ.get("COVERAGE_PROFILE", "")
+if _profile: # pragma: debugging
+ from ox_profile.core.launchers import SimpleLauncher # pylint: disable=import-error
+ original_main = main
+
+ def main(argv=None): # pylint: disable=function-redefined
+ """A wrapper around main that profiles."""
+ try:
+ profiler = SimpleLauncher.launch()
+ return original_main(argv)
+ finally:
+ data, _ = profiler.query(re_filter='coverage', max_records=100)
+ print(profiler.show(query=data, limit=100, sep='', col=''))
+ profiler.cancel()
diff --git a/coverage/control.py b/coverage/control.py
index 823169d1..250bc137 100644
--- a/coverage/control.py
+++ b/coverage/control.py
@@ -648,8 +648,9 @@ class Coverage(object):
self._warn("No data was collected.", slug="no-data-collected")
# Find files that were never executed at all.
- for file_path, plugin_name in self._inorout.find_unexecuted_files():
- self._data.touch_file(file_path, plugin_name)
+ if self._data:
+ for file_path, plugin_name in self._inorout.find_unexecuted_files():
+ self._data.touch_file(file_path, plugin_name)
if self.config.note:
self._data.add_run_info(note=self.config.note)
diff --git a/coverage/sqldata.py b/coverage/sqldata.py
index cc871950..a182d829 100644
--- a/coverage/sqldata.py
+++ b/coverage/sqldata.py
@@ -92,6 +92,7 @@ class CoverageSqliteData(SimpleReprMixin):
self._choose_filename()
self._file_map = {}
+ # Maps thread ids to SqliteDb objects.
self._dbs = {}
self._pid = os.getpid()
@@ -124,7 +125,7 @@ class CoverageSqliteData(SimpleReprMixin):
def _create_db(self):
if self._debug.should('dataio'):
self._debug.write("Creating data file {!r}".format(self._filename))
- self._dbs[get_thread_id()] = Sqlite(self._filename, self._debug)
+ self._dbs[get_thread_id()] = SqliteDb(self._filename, self._debug)
with self._dbs[get_thread_id()] as db:
for stmt in SCHEMA.split(';'):
stmt = stmt.strip()
@@ -139,7 +140,7 @@ class CoverageSqliteData(SimpleReprMixin):
def _open_db(self):
if self._debug.should('dataio'):
self._debug.write("Opening data file {!r}".format(self._filename))
- self._dbs[get_thread_id()] = Sqlite(self._filename, self._debug)
+ self._dbs[get_thread_id()] = SqliteDb(self._filename, self._debug)
with self._dbs[get_thread_id()] as db:
try:
schema_version, = db.execute("select version from coverage_schema").fetchone()
@@ -164,6 +165,7 @@ class CoverageSqliteData(SimpleReprMixin):
self._file_map[path] = id
def _connect(self):
+ """Get the SqliteDb object to use."""
if get_thread_id() not in self._dbs:
if os.path.exists(self._filename):
self._open_db()
@@ -253,8 +255,10 @@ class CoverageSqliteData(SimpleReprMixin):
))
self._start_using()
self._choose_lines_or_arcs(lines=True)
- self._set_context_id()
+ if not line_data:
+ return
with self._connect() as con:
+ self._set_context_id()
for filename, linenos in iitems(line_data):
file_id = self._file_id(filename, add=True)
data = [(file_id, self._current_context_id, lineno) for lineno in linenos]
@@ -277,8 +281,10 @@ class CoverageSqliteData(SimpleReprMixin):
))
self._start_using()
self._choose_lines_or_arcs(arcs=True)
- self._set_context_id()
+ if not arc_data:
+ return
with self._connect() as con:
+ self._set_context_id()
for filename, arcs in iitems(arc_data):
file_id = self._file_id(filename, add=True)
data = [(file_id, self._current_context_id, fromno, tono) for fromno, tono in arcs]
@@ -305,6 +311,10 @@ class CoverageSqliteData(SimpleReprMixin):
`file_tracers` is { filename: plugin_name, ... }
"""
+ if self._debug.should('dataop'):
+ self._debug.write("Adding file tracers: %d files" % (len(file_tracers),))
+ if not file_tracers:
+ return
self._start_using()
with self._connect() as con:
for filename, plugin_name in iitems(file_tracers):
@@ -664,13 +674,17 @@ class CoverageSqliteData(SimpleReprMixin):
return [] # TODO
-class Sqlite(SimpleReprMixin):
+class SqliteDb(SimpleReprMixin):
+ """A simple abstraction over a SQLite database.
+
+ Use as a context manager to get an object you can call
+ execute or executemany on.
+
+ """
def __init__(self, filename, debug):
self.debug = debug if debug.should('sql') else None
self.filename = filename
self.nest = 0
- if self.debug:
- self.debug.write("Connecting to {!r}".format(filename))
def connect(self):
# SQLite on Windows on py2 won't open a file if the filename argument
@@ -679,9 +693,11 @@ class Sqlite(SimpleReprMixin):
filename = os.path.relpath(self.filename)
# It can happen that Python switches threads while the tracer writes
# data. The second thread will also try to write to the data,
- # effectively causing a nested context. However, given the indempotent
+ # effectively causing a nested context. However, given the idempotent
# nature of the tracer operations, sharing a connection among threads
# is not a problem.
+ if self.debug:
+ self.debug.write("Connecting to {!r}".format(self.filename))
self.con = sqlite3.connect(filename, check_same_thread=False)
# This pragma makes writing faster. It disables rollbacks, but we never need them.
diff --git a/tests/test_api.py b/tests/test_api.py
index 920cd9ad..301257dc 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -326,21 +326,6 @@ class ApiTest(CoverageTest):
with self.assert_warnings(cov, []):
cov.get_data()
- def test_two_getdata_only_warn_once_nostop(self):
- self.make_code1_code2()
- cov = coverage.Coverage(source=["."], omit=["code1.py"])
- cov.start()
- import_local_file("code1") # pragma: nested
- # We didn't collect any data, so we should get a warning.
- with self.assert_warnings(cov, ["No data was collected"]): # pragma: nested
- cov.get_data() # pragma: nested
- # But calling get_data a second time with no intervening activity
- # won't make another warning.
- with self.assert_warnings(cov, []): # pragma: nested
- cov.get_data() # pragma: nested
- # Then stop it, or the test suite gets out of whack.
- cov.stop() # pragma: nested
-
def test_two_getdata_warn_twice(self):
self.make_code1_code2()
cov = coverage.Coverage(source=["."], omit=["code1.py", "code2.py"])
diff --git a/tests/test_debug.py b/tests/test_debug.py
index 4eaba92e..351ef919 100644
--- a/tests/test_debug.py
+++ b/tests/test_debug.py
@@ -136,16 +136,18 @@ class DebugTraceTest(CoverageTest):
frames = re_lines(out_lines, frame_pattern).splitlines()
self.assertEqual(len(real_messages), len(frames))
- # The last message should be "Writing data", and the last frame should
- # be _write_file in data.py.
last_line = out_lines.splitlines()[-1]
+
+ # The details of what to expect on the stack are empirical, and can change
+ # as the code changes. This test is here to ensure that the debug code
+ # continues working. It's ok to adjust these details over time.
from coverage.data import STORAGE
if STORAGE == "json":
self.assertRegex(real_messages[-1], r"^\s*\d+\.\w{4}: Writing data")
self.assertRegex(last_line, r"\s+_write_file : .*coverage[/\\]data.py @\d+$")
else:
- self.assertRegex(real_messages[-1], r"^\s*\d+\.\w{4}: Creating data file")
- self.assertRegex(last_line, r"\s+_create_db : .*coverage[/\\]sqldata.py @\d+$")
+ self.assertRegex(real_messages[-1], r"^\s*\d+\.\w{4}: Adding file tracers: 0 files")
+ self.assertRegex(last_line, r"\s+add_file_tracers : .*coverage[/\\]sqldata.py @\d+$")
def test_debug_config(self):
out_lines = self.f1_debug_output(["config"])