test_tracing.py 8.1 KB

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