From ecb9039daac71f6cf7f83ea3046659ac924188d4 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sun, 26 Aug 2018 10:37:01 -0400 Subject: Formalize some more debugging tools I've been keeping to the side --- coverage/debug.py | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 73 insertions(+), 1 deletion(-) (limited to 'coverage/debug.py') diff --git a/coverage/debug.py b/coverage/debug.py index f491a0f7..67a45ce1 100644 --- a/coverage/debug.py +++ b/coverage/debug.py @@ -4,7 +4,9 @@ """Control of and utilities for debugging.""" import contextlib +import functools import inspect +import itertools import os import re import sys @@ -173,7 +175,10 @@ def add_pid_and_tid(text): class SimpleRepr(object): """A mixin implementing a simple __repr__.""" def __repr__(self): - show_attrs = ((k, v) for k, v in self.__dict__.items() if getattr(v, "show_repr_attr", True)) + show_attrs = ( + (k, v) for k, v in self.__dict__.items() + if getattr(v, "show_repr_attr", True) + ) return "<{klass} @0x{id:x} {attrs}>".format( klass=self.__class__.__name__, id=id(self), @@ -248,6 +253,8 @@ class DebugOutputFile(object): # pragma: debugging # on a class attribute. Yes, this is aggressively gross. the_one = sys.modules.get(cls.SYS_MOD_NAME) if the_one is None: + with open("/tmp/where.txt", "a") as f: + f.write("Starting with {}\n".format(fileobj)) if fileobj is None: fileobj = open("/tmp/debug_log.txt", "a") sys.modules[cls.SYS_MOD_NAME] = the_one = cls(fileobj, show_process, filters) @@ -271,6 +278,71 @@ def log(msg, stack=False): # pragma: debugging dump_stack_frames(out=out, skip=1) +def decorate_methods(decorator, butnot=()): # pragma: debugging + """A class decorator to apply a decorator to public methods.""" + def _decorator(cls): + for name, meth in inspect.getmembers(cls, inspect.isroutine): + public = name == '__init__' or not name.startswith("_") + decorate_it = public and name not in butnot + if decorate_it: + setattr(cls, name, decorator(meth)) + return cls + return _decorator + + +def break_in_pudb(func): # pragma: debugging + """A function decorator to stop in the debugger for each call.""" + @functools.wraps(func) + def wrapper(*args, **kwargs): + import pudb # pylint: disable=import-error + sys.stdout = sys.__stdout__ + pudb.set_trace() + return func(*args, **kwargs) + return wrapper + + +OBJ_IDS = itertools.count() +CALLS = itertools.count() +OBJ_ID_ATTR = "$coverage.object_id" + +def show_calls(show_args=True, show_stack=False): # pragma: debugging + """A method decorator to debug-log each call to the function.""" + def _decorator(func): + @functools.wraps(func) + def wrapper(self, *args, **kwargs): + oid = getattr(self, OBJ_ID_ATTR, None) + if oid is None: + oid = "{:08d} {:04d}".format(os.getpid(), next(OBJ_IDS)) + setattr(self, OBJ_ID_ATTR, oid) + extra = "" + if show_args: + eargs = ", ".join(map(repr, args)) + ekwargs = ", ".join("{}={!r}".format(*item) for item in kwargs.items()) + extra += "(" + extra += eargs + if eargs and ekwargs: + extra += ", " + extra += ekwargs + extra += ")" + if show_stack: + extra += " @ " + extra += "; ".join(_clean_stack_line(l) for l in short_stack().splitlines()) + msg = "{} {:04d} {}{}\n".format(oid, next(CALLS), func.__name__, extra) + DebugOutputFile.the_one().write(msg) + return func(self, *args, **kwargs) + return wrapper + return _decorator + + +def _clean_stack_line(s): # pragma: debugging + """Simplify some paths in a stack trace, for compactness.""" + s = s.strip() + s = s.replace(os.path.dirname(__file__) + '/', '') + s = s.replace(os.path.dirname(os.__file__) + '/', '') + s = s.replace(sys.prefix + '/', '') + return s + + def filter_aspectlib_frames(text): # pragma: debugging """Aspectlib prints stack traces, but includes its own frames. Scrub those out.""" # <<< aspectlib/__init__.py:257:function_wrapper < igor.py:143:run_tests < ... -- cgit v1.2.1