# HG changeset patch
# User Christophe de Vienne <christophe@unlish.com>
# Date 1417084944 -3600
# Thu Nov 27 11:42:24 2014 +0100
# Node ID 31725b8fa3f58b94742899a20d939a781a65be42
# Parent d7b9bf86588969721df23d9d19b2832e1a6e197d
[coverage] Provides better tools to pause tracing
The former implementation was not restoring properly the trace function on python 2.7 at least.
This cleaner implementation uses context-manager, and deprecates the pause_tracing/resume_tracing couple.
Closes #280757
# User Christophe de Vienne <christophe@unlish.com>
# Date 1417084944 -3600
# Thu Nov 27 11:42:24 2014 +0100
# Node ID 31725b8fa3f58b94742899a20d939a781a65be42
# Parent d7b9bf86588969721df23d9d19b2832e1a6e197d
[coverage] Provides better tools to pause tracing
The former implementation was not restoring properly the trace function on python 2.7 at least.
This cleaner implementation uses context-manager, and deprecates the pause_tracing/resume_tracing couple.
Closes #280757
@@ -117,16 +117,18 @@
1 import os.path as osp 2 from time import time, clock 3 import warnings 4 import types 5 from inspect import isgeneratorfunction, isclass 6 +from contextlib import contextmanager 7 8 from logilab.common.fileutils import abspath_listdir 9 from logilab.common import textutils 10 from logilab.common import testlib, STD_BLACKLIST 11 # use the same unittest module as testlib 12 from logilab.common.testlib import unittest, start_interactive_mode 13 +from logilab.common.deprecation import deprecated 14 import doctest 15 16 import unittest as unittest_legacy 17 if not getattr(unittest_legacy, "__package__", None): 18 try:
@@ -143,53 +145,59 @@
19 except ImportError: 20 DJANGO_FOUND = False 21 22 CONF_FILE = 'pytestconf.py' 23 24 -## coverage hacks, do not read this, do not read this, do not read this 25 +## coverage pausing tools 26 27 -# hey, but this is an aspect, right ?!!! 28 +@contextmanager 29 +def replace_trace(trace=None): 30 + """A context manager that temporary replaces the trace function""" 31 + oldtrace = sys.gettrace() 32 + sys.settrace(trace) 33 + try: 34 + yield 35 + finally: 36 + sys.settrace(oldtrace) 37 + 38 + 39 +def pause_trace(): 40 + """A context manager that temporary pauses any tracing""" 41 + return replace_trace() 42 + 43 class TraceController(object): 44 - nesting = 0 45 + ctx_stack = [] 46 47 + @classmethod 48 + @deprecated('[lgc 0.63.1] Use the pause_trace() context manager') 49 def pause_tracing(cls): 50 - if not cls.nesting: 51 - cls.tracefunc = staticmethod(getattr(sys, '__settrace__', sys.settrace)) 52 - cls.oldtracer = getattr(sys, '__tracer__', None) 53 - sys.__notrace__ = True 54 - cls.tracefunc(None) 55 - cls.nesting += 1 56 - pause_tracing = classmethod(pause_tracing) 57 + cls.ctx_stack.append(pause_trace()) 58 + cls.ctx_stack[-1].__enter__() 59 60 + @classmethod 61 + @deprecated('[lgc 0.63.1] Use the pause_trace() context manager') 62 def resume_tracing(cls): 63 - cls.nesting -= 1 64 - assert cls.nesting >= 0 65 - if not cls.nesting: 66 - cls.tracefunc(cls.oldtracer) 67 - delattr(sys, '__notrace__') 68 - resume_tracing = classmethod(resume_tracing) 69 + cls.ctx_stack.pop().__exit__(None, None, None) 70 71 72 pause_tracing = TraceController.pause_tracing 73 resume_tracing = TraceController.resume_tracing 74 75 76 def nocoverage(func): 77 + """Function decorator that pauses tracing functions""" 78 if hasattr(func, 'uncovered'): 79 return func 80 func.uncovered = True 81 + 82 def not_covered(*args, **kwargs): 83 - pause_tracing() 84 - try: 85 + with pause_trace(): 86 return func(*args, **kwargs) 87 - finally: 88 - resume_tracing() 89 not_covered.uncovered = True 90 return not_covered 91 92 - 93 -## end of coverage hacks 94 +## end of coverage pausing tools 95 96 97 TESTFILE_RE = re.compile("^((unit)?test.*|smoketest)\.py$") 98 def this_is_a_testfile(filename): 99 """returns True if `filename` seems to be a test file"""
@@ -44,7 +44,58 @@
100 self.assertFalse(this_is_a_testfile("smoketest.pl")) 101 self.assertFalse(this_is_a_testfile("unittest")) 102 self.assertTrue(this_is_a_testfile(join("coincoin", "unittest_bibi.py"))) 103 self.assertFalse(this_is_a_testfile(join("unittest", "spongebob.py"))) 104 105 + def test_replace_trace(self): 106 + def tracefn(frame, event, arg): 107 + pass 108 + 109 + oldtrace = sys.gettrace() 110 + with replace_trace(tracefn): 111 + self.assertIs(sys.gettrace(), tracefn) 112 + 113 + self.assertIs(sys.gettrace(), oldtrace) 114 + 115 + def test_pause_trace(self): 116 + def tracefn(frame, event, arg): 117 + pass 118 + 119 + oldtrace = sys.gettrace() 120 + sys.settrace(tracefn) 121 + try: 122 + self.assertIs(sys.gettrace(), tracefn) 123 + with pause_trace(): 124 + self.assertIs(sys.gettrace(), None) 125 + self.assertIs(sys.gettrace(), tracefn) 126 + finally: 127 + sys.settrace(oldtrace) 128 + 129 + def test_nocoverage(self): 130 + def tracefn(frame, event, arg): 131 + pass 132 + 133 + @nocoverage 134 + def myfn(): 135 + self.assertIs(sys.gettrace(), None) 136 + 137 + with replace_trace(tracefn): 138 + myfn() 139 + 140 + def test_legacy_pause_resume_tracing(self): 141 + def tracefn(frame, event, arg): 142 + pass 143 + 144 + with replace_trace(tracefn): 145 + pause_tracing() 146 + self.assertIs(sys.gettrace(), None) 147 + with replace_trace(tracefn): 148 + pause_tracing() 149 + self.assertIs(sys.gettrace(), None) 150 + resume_tracing() 151 + self.assertIs(sys.gettrace(), tracefn) 152 + self.assertIs(sys.gettrace(), None) 153 + resume_tracing() 154 + self.assertIs(sys.gettrace(), tracefn) 155 + 156 if __name__ == '__main__': 157 unittest_main()