test_tracing.py 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. from __future__ import print_function
  2. import sys
  3. import sysconfig
  4. import greenlet
  5. import unittest
  6. from . import TestCase
  7. from . import PY312
  8. # https://discuss.python.org/t/cpython-3-12-greenlet-and-tracing-profiling-how-to-not-crash-and-get-correct-results/33144/2
  9. # When build variables are available, OPT is the best way of detecting
  10. # the build with assertions enabled. Otherwise, fallback to detecting PyDEBUG
  11. # build.
  12. ASSERTION_BUILD_PY312 = (
  13. PY312 and (
  14. "-DNDEBUG" not in sysconfig.get_config_var("OPT").split()
  15. if sysconfig.get_config_var("OPT") is not None
  16. else hasattr(sys, 'gettotalrefcount')
  17. ),
  18. "Broken on assertion-enabled builds of Python 3.12"
  19. )
  20. class SomeError(Exception):
  21. pass
  22. class GreenletTracer(object):
  23. oldtrace = None
  24. def __init__(self, error_on_trace=False):
  25. self.actions = []
  26. self.error_on_trace = error_on_trace
  27. def __call__(self, *args):
  28. self.actions.append(args)
  29. if self.error_on_trace:
  30. raise SomeError
  31. def __enter__(self):
  32. self.oldtrace = greenlet.settrace(self)
  33. return self.actions
  34. def __exit__(self, *args):
  35. greenlet.settrace(self.oldtrace)
  36. class TestGreenletTracing(TestCase):
  37. """
  38. Tests of ``greenlet.settrace()``
  39. """
  40. def test_a_greenlet_tracing(self):
  41. main = greenlet.getcurrent()
  42. def dummy():
  43. pass
  44. def dummyexc():
  45. raise SomeError()
  46. with GreenletTracer() as actions:
  47. g1 = greenlet.greenlet(dummy)
  48. g1.switch()
  49. g2 = greenlet.greenlet(dummyexc)
  50. self.assertRaises(SomeError, g2.switch)
  51. self.assertEqual(actions, [
  52. ('switch', (main, g1)),
  53. ('switch', (g1, main)),
  54. ('switch', (main, g2)),
  55. ('throw', (g2, main)),
  56. ])
  57. def test_b_exception_disables_tracing(self):
  58. main = greenlet.getcurrent()
  59. def dummy():
  60. main.switch()
  61. g = greenlet.greenlet(dummy)
  62. g.switch()
  63. with GreenletTracer(error_on_trace=True) as actions:
  64. self.assertRaises(SomeError, g.switch)
  65. self.assertEqual(greenlet.gettrace(), None)
  66. self.assertEqual(actions, [
  67. ('switch', (main, g)),
  68. ])
  69. def test_set_same_tracer_twice(self):
  70. # https://github.com/python-greenlet/greenlet/issues/332
  71. # Our logic in asserting that the tracefunction should
  72. # gain a reference was incorrect if the same tracefunction was set
  73. # twice.
  74. tracer = GreenletTracer()
  75. with tracer:
  76. greenlet.settrace(tracer)
  77. class PythonTracer(object):
  78. oldtrace = None
  79. def __init__(self):
  80. self.actions = []
  81. def __call__(self, frame, event, arg):
  82. # Record the co_name so we have an idea what function we're in.
  83. self.actions.append((event, frame.f_code.co_name))
  84. def __enter__(self):
  85. self.oldtrace = sys.setprofile(self)
  86. return self.actions
  87. def __exit__(self, *args):
  88. sys.setprofile(self.oldtrace)
  89. def tpt_callback():
  90. return 42
  91. class TestPythonTracing(TestCase):
  92. """
  93. Tests of the interaction of ``sys.settrace()``
  94. with greenlet facilities.
  95. NOTE: Most of this is probably CPython specific.
  96. """
  97. maxDiff = None
  98. def test_trace_events_trivial(self):
  99. with PythonTracer() as actions:
  100. tpt_callback()
  101. # If we use the sys.settrace instead of setprofile, we get
  102. # this:
  103. # self.assertEqual(actions, [
  104. # ('call', 'tpt_callback'),
  105. # ('call', '__exit__'),
  106. # ])
  107. self.assertEqual(actions, [
  108. ('return', '__enter__'),
  109. ('call', 'tpt_callback'),
  110. ('return', 'tpt_callback'),
  111. ('call', '__exit__'),
  112. ('c_call', '__exit__'),
  113. ])
  114. def _trace_switch(self, glet):
  115. with PythonTracer() as actions:
  116. glet.switch()
  117. return actions
  118. def _check_trace_events_func_already_set(self, glet):
  119. actions = self._trace_switch(glet)
  120. self.assertEqual(actions, [
  121. ('return', '__enter__'),
  122. ('c_call', '_trace_switch'),
  123. ('call', 'run'),
  124. ('call', 'tpt_callback'),
  125. ('return', 'tpt_callback'),
  126. ('return', 'run'),
  127. ('c_return', '_trace_switch'),
  128. ('call', '__exit__'),
  129. ('c_call', '__exit__'),
  130. ])
  131. def test_trace_events_into_greenlet_func_already_set(self):
  132. def run():
  133. return tpt_callback()
  134. self._check_trace_events_func_already_set(greenlet.greenlet(run))
  135. def test_trace_events_into_greenlet_subclass_already_set(self):
  136. class X(greenlet.greenlet):
  137. def run(self):
  138. return tpt_callback()
  139. self._check_trace_events_func_already_set(X())
  140. def _check_trace_events_from_greenlet_sets_profiler(self, g, tracer):
  141. g.switch()
  142. tpt_callback()
  143. tracer.__exit__()
  144. self.assertEqual(tracer.actions, [
  145. ('return', '__enter__'),
  146. ('call', 'tpt_callback'),
  147. ('return', 'tpt_callback'),
  148. ('return', 'run'),
  149. ('call', 'tpt_callback'),
  150. ('return', 'tpt_callback'),
  151. ('call', '__exit__'),
  152. ('c_call', '__exit__'),
  153. ])
  154. def test_trace_events_from_greenlet_func_sets_profiler(self):
  155. tracer = PythonTracer()
  156. def run():
  157. tracer.__enter__()
  158. return tpt_callback()
  159. self._check_trace_events_from_greenlet_sets_profiler(greenlet.greenlet(run),
  160. tracer)
  161. def test_trace_events_from_greenlet_subclass_sets_profiler(self):
  162. tracer = PythonTracer()
  163. class X(greenlet.greenlet):
  164. def run(self):
  165. tracer.__enter__()
  166. return tpt_callback()
  167. self._check_trace_events_from_greenlet_sets_profiler(X(), tracer)
  168. @unittest.skipIf(*ASSERTION_BUILD_PY312)
  169. def test_trace_events_multiple_greenlets_switching(self):
  170. tracer = PythonTracer()
  171. g1 = None
  172. g2 = None
  173. def g1_run():
  174. tracer.__enter__()
  175. tpt_callback()
  176. g2.switch()
  177. tpt_callback()
  178. return 42
  179. def g2_run():
  180. tpt_callback()
  181. tracer.__exit__()
  182. tpt_callback()
  183. g1.switch()
  184. g1 = greenlet.greenlet(g1_run)
  185. g2 = greenlet.greenlet(g2_run)
  186. x = g1.switch()
  187. self.assertEqual(x, 42)
  188. tpt_callback() # ensure not in the trace
  189. self.assertEqual(tracer.actions, [
  190. ('return', '__enter__'),
  191. ('call', 'tpt_callback'),
  192. ('return', 'tpt_callback'),
  193. ('c_call', 'g1_run'),
  194. ('call', 'g2_run'),
  195. ('call', 'tpt_callback'),
  196. ('return', 'tpt_callback'),
  197. ('call', '__exit__'),
  198. ('c_call', '__exit__'),
  199. ])
  200. @unittest.skipIf(*ASSERTION_BUILD_PY312)
  201. def test_trace_events_multiple_greenlets_switching_siblings(self):
  202. # Like the first version, but get both greenlets running first
  203. # as "siblings" and then establish the tracing.
  204. tracer = PythonTracer()
  205. g1 = None
  206. g2 = None
  207. def g1_run():
  208. greenlet.getcurrent().parent.switch()
  209. tracer.__enter__()
  210. tpt_callback()
  211. g2.switch()
  212. tpt_callback()
  213. return 42
  214. def g2_run():
  215. greenlet.getcurrent().parent.switch()
  216. tpt_callback()
  217. tracer.__exit__()
  218. tpt_callback()
  219. g1.switch()
  220. g1 = greenlet.greenlet(g1_run)
  221. g2 = greenlet.greenlet(g2_run)
  222. # Start g1
  223. g1.switch()
  224. # And it immediately returns control to us.
  225. # Start g2
  226. g2.switch()
  227. # Which also returns. Now kick of the real part of the
  228. # test.
  229. x = g1.switch()
  230. self.assertEqual(x, 42)
  231. tpt_callback() # ensure not in the trace
  232. self.assertEqual(tracer.actions, [
  233. ('return', '__enter__'),
  234. ('call', 'tpt_callback'),
  235. ('return', 'tpt_callback'),
  236. ('c_call', 'g1_run'),
  237. ('call', 'tpt_callback'),
  238. ('return', 'tpt_callback'),
  239. ('call', '__exit__'),
  240. ('c_call', '__exit__'),
  241. ])
  242. if __name__ == '__main__':
  243. unittest.main()