Skip to content

Commit 0b4eeed

Browse files
committed
Improvements to debugging while chasing #915
1 parent 70d527b commit 0b4eeed

File tree

2 files changed

+34
-14
lines changed

2 files changed

+34
-14
lines changed

coverage/debug.py

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
# debugging the configuration mechanisms you usually use to control debugging!
2626
# This is a list of forced debugging options.
2727
FORCED_DEBUG = []
28+
FORCED_DEBUG_FILE = None
2829

2930

3031
class DebugControl(object):
@@ -189,11 +190,14 @@ def add_pid_and_tid(text):
189190

190191
class SimpleReprMixin(object):
191192
"""A mixin implementing a simple __repr__."""
193+
simple_repr_ignore = ['simple_repr_ignore', '$coverage.object_id']
194+
192195
def __repr__(self):
193196
show_attrs = (
194197
(k, v) for k, v in self.__dict__.items()
195198
if getattr(v, "show_repr_attr", True)
196199
and not callable(v)
200+
and k not in self.simple_repr_ignore
197201
)
198202
return "<{klass} @0x{id:x} {attrs}>".format(
199203
klass=self.__class__.__name__,
@@ -301,7 +305,7 @@ def get_one(cls, fileobj=None, show_process=True, filters=(), interim=False):
301305
the_one, is_interim = sys.modules.get(cls.SYS_MOD_NAME, (None, True))
302306
if the_one is None or is_interim:
303307
if fileobj is None:
304-
debug_file_name = os.environ.get("COVERAGE_DEBUG_FILE")
308+
debug_file_name = os.environ.get("COVERAGE_DEBUG_FILE", FORCED_DEBUG_FILE)
305309
if debug_file_name:
306310
fileobj = open(debug_file_name, "a")
307311
else:
@@ -328,14 +332,18 @@ def log(msg, stack=False): # pragma: debugging
328332
dump_stack_frames(out=out, skip=1)
329333

330334

331-
def decorate_methods(decorator, butnot=()): # pragma: debugging
332-
"""A class decorator to apply a decorator to public methods."""
335+
def decorate_methods(decorator, butnot=(), private=False): # pragma: debugging
336+
"""A class decorator to apply a decorator to methods."""
333337
def _decorator(cls):
334338
for name, meth in inspect.getmembers(cls, inspect.isroutine):
335-
public = name == '__init__' or not name.startswith("_")
336-
decorate_it = public and name not in butnot
337-
if decorate_it:
338-
setattr(cls, name, decorator(meth))
339+
if name not in cls.__dict__:
340+
continue
341+
if name != "__init__":
342+
if not private and name.startswith("_"):
343+
continue
344+
if name in butnot:
345+
continue
346+
setattr(cls, name, decorator(meth))
339347
return cls
340348
return _decorator
341349

@@ -355,7 +363,7 @@ def _wrapper(*args, **kwargs):
355363
CALLS = itertools.count()
356364
OBJ_ID_ATTR = "$coverage.object_id"
357365

358-
def show_calls(show_args=True, show_stack=False): # pragma: debugging
366+
def show_calls(show_args=True, show_stack=False, show_return=False): # pragma: debugging
359367
"""A method decorator to debug-log each call to the function."""
360368
def _decorator(func):
361369
@functools.wraps(func)
@@ -377,9 +385,14 @@ def _wrapper(self, *args, **kwargs):
377385
if show_stack:
378386
extra += " @ "
379387
extra += "; ".join(_clean_stack_line(l) for l in short_stack().splitlines())
380-
msg = "{} {:04d} {}{}\n".format(oid, next(CALLS), func.__name__, extra)
388+
callid = next(CALLS)
389+
msg = "{} {:04d} {}{}\n".format(oid, callid, func.__name__, extra)
381390
DebugOutputFile.get_one(interim=True).write(msg)
382-
return func(self, *args, **kwargs)
391+
ret = func(self, *args, **kwargs)
392+
if show_return:
393+
msg = "{} {:04d} {} return {!r}\n".format(oid, callid, func.__name__, ret)
394+
DebugOutputFile.get_one(interim=True).write(msg)
395+
return ret
383396
return _wrapper
384397
return _decorator
385398

coverage/sqldata.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ def __init__(self, basename=None, suffix=None, no_disk=False, warn=None, debug=N
192192
write any disk file.
193193
warn: a warning callback function, accepting a warning message
194194
argument.
195-
debug: a debug callback function.
195+
debug: a `DebugControl` object (optional)
196196
197197
"""
198198
self._no_disk = no_disk
@@ -963,7 +963,7 @@ class SqliteDb(SimpleReprMixin):
963963
Use as a context manager, then you can use it like a
964964
:class:`python:sqlite3.Connection` object::
965965
966-
with SqliteDb(filename, debug=True) as db:
966+
with SqliteDb(filename, debug_control) as db:
967967
db.execute("insert into schema (version) values (?)", (SCHEMA_VERSION,))
968968
969969
"""
@@ -1025,8 +1025,13 @@ def __enter__(self):
10251025
def __exit__(self, exc_type, exc_value, traceback):
10261026
self.nest -= 1
10271027
if self.nest == 0:
1028-
self.con.__exit__(exc_type, exc_value, traceback)
1029-
self.close()
1028+
try:
1029+
self.con.__exit__(exc_type, exc_value, traceback)
1030+
self.close()
1031+
except Exception as exc:
1032+
if self.debug:
1033+
self.debug.write("EXCEPTION from __exit__: {}".format(exc))
1034+
raise
10301035

10311036
def execute(self, sql, parameters=()):
10321037
"""Same as :meth:`python:sqlite3.Connection.execute`."""
@@ -1049,6 +1054,8 @@ def execute(self, sql, parameters=()):
10491054
)
10501055
except Exception:
10511056
pass
1057+
if self.debug:
1058+
self.debug.write("EXCEPTION from execute: {}".format(msg))
10521059
raise CoverageException("Couldn't use data file {!r}: {}".format(self.filename, msg))
10531060

10541061
def executemany(self, sql, data):

0 commit comments

Comments
 (0)