Skip to content

Commit 5034375

Browse files
committed
Add unit test for pystats.
1 parent bef1bcb commit 5034375

File tree

1 file changed

+215
-0
lines changed

1 file changed

+215
-0
lines changed

Lib/test/test_pystats.py

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
import sys
2+
import textwrap
3+
import unittest
4+
from test.support import script_helper
5+
6+
# This function is availible for the --enable-pystats config.
7+
HAVE_PYSTATS = hasattr(sys, '_stats_on')
8+
9+
TEST_TEMPLATE = """
10+
import sys
11+
import threading
12+
import time
13+
14+
THREADS = 2
15+
16+
class A:
17+
pass
18+
19+
class B:
20+
pass
21+
22+
def modify_class():
23+
# This is used as a rare event we can assume doesn't happen unless we do it.
24+
# It increments the "Rare event (set_class)" count.
25+
a = A()
26+
a.__class__ = B
27+
28+
TURNED_ON = False
29+
def stats_on():
30+
global TURNED_ON
31+
sys._stats_on()
32+
TURNED_ON = True
33+
34+
TURNED_OFF = False
35+
def stats_off():
36+
global TURNED_OFF
37+
sys._stats_off()
38+
TURNED_OFF = True
39+
40+
CLEARED = False
41+
def stats_clear():
42+
global CLEARED
43+
sys._stats_clear()
44+
CLEARED = True
45+
46+
def func_start():
47+
pass
48+
49+
def func_end():
50+
pass
51+
52+
def func_test(thread_id):
53+
pass
54+
55+
_TEST_CODE_
56+
57+
func_start()
58+
threads = []
59+
for i in range(THREADS):
60+
t = threading.Thread(target=func_test, args=(i,))
61+
threads.append(t)
62+
t.start()
63+
for t in threads:
64+
t.join()
65+
func_end()
66+
"""
67+
68+
69+
def run_test_code(
70+
test_code,
71+
args=[],
72+
env_vars=None,
73+
):
74+
"""Run test code and return the value of the "set_class" stats counter.
75+
"""
76+
code = textwrap.dedent(TEST_TEMPLATE)
77+
code = code.replace('_TEST_CODE_', textwrap.dedent(test_code))
78+
script_args = args + ['-c', code]
79+
env_vars = env_vars or {}
80+
res, _ = script_helper.run_python_until_end(*script_args, **env_vars)
81+
stderr = res.err.decode("ascii", "backslashreplace")
82+
for line in stderr.split('\n'):
83+
if 'Rare event (set_class)' in line:
84+
label, _, value = line.partition(':')
85+
return value.strip()
86+
return ''
87+
88+
89+
@unittest.skipUnless(HAVE_PYSTATS, "requires pystats build option")
90+
class TestPyStats(unittest.TestCase):
91+
"""Tests for pystats functionality (requires --enable-pystats build
92+
option).
93+
"""
94+
95+
def test_stats_toggle_on(self):
96+
"""Check the toggle on functionality.
97+
"""
98+
code = """
99+
def func_start():
100+
modify_class()
101+
"""
102+
103+
# If turned on with command line flag, should get one count.
104+
stat_count = run_test_code(code, args=['-X', 'pystats'])
105+
self.assertEqual(stat_count, '1')
106+
107+
# If turned on with env var, should get one count.
108+
stat_count = run_test_code(code, env_vars={'PYTHONSTATS': '1'})
109+
self.assertEqual(stat_count, '1')
110+
111+
# If not turned on, should be no counts.
112+
stat_count = run_test_code(code)
113+
self.assertEqual(stat_count, '')
114+
115+
code = """
116+
def func_start():
117+
modify_class()
118+
sys._stats_on()
119+
modify_class()
120+
"""
121+
# Not initially turned on but enabled by sys._stats_on(), should get
122+
# one count.
123+
stat_count = run_test_code(code)
124+
self.assertEqual(stat_count, '1')
125+
126+
def test_stats_toggle_on_thread(self):
127+
"""Check the toggle on functionality when threads are used.
128+
"""
129+
code = """
130+
def func_test(thread_id):
131+
if thread_id == 0:
132+
modify_class()
133+
stats_on()
134+
modify_class()
135+
else:
136+
while not TURNED_ON:
137+
pass
138+
modify_class()
139+
"""
140+
# Turning on in one thread will count in other thread.
141+
stat_count = run_test_code(code)
142+
self.assertEqual(stat_count, '2')
143+
144+
code = """
145+
def func_test(thread_id):
146+
if thread_id == 0:
147+
modify_class()
148+
stats_off()
149+
modify_class()
150+
else:
151+
while not TURNED_OFF:
152+
pass
153+
modify_class()
154+
"""
155+
# Turning off in one thread will not count in other threads.
156+
stat_count = run_test_code(code, args=['-X', 'pystats'])
157+
self.assertEqual(stat_count, '1')
158+
159+
def test_thread_exit_merge(self):
160+
"""Check that per-thread stats (when free-threading enabled) are merged.
161+
"""
162+
code = """
163+
def func_test(thread_id):
164+
modify_class()
165+
if thread_id == 0:
166+
raise SystemExit
167+
"""
168+
# Stats from a thread exiting early should still be counted.
169+
stat_count = run_test_code(code, args=['-X', 'pystats'])
170+
self.assertEqual(stat_count, '2')
171+
172+
def test_stats_dump(self):
173+
"""Check that sys._stats_dump() works.
174+
"""
175+
code = """
176+
def func_test(thread_id):
177+
if thread_id == 0:
178+
stats_on()
179+
else:
180+
while not TURNED_ON:
181+
pass
182+
modify_class()
183+
sys._stats_dump()
184+
stats_off()
185+
"""
186+
# Stats from a thread exiting early should still be counted.
187+
stat_count = run_test_code(code)
188+
self.assertEqual(stat_count, '1')
189+
190+
def test_stats_clear(self):
191+
"""Check that sys._stats_clear() works.
192+
"""
193+
code = """
194+
ready = False
195+
def func_test(thread_id):
196+
global ready
197+
if thread_id == 0:
198+
stats_on()
199+
modify_class()
200+
while not ready:
201+
pass # wait until other thread has called modify_class()
202+
stats_clear() # clears stats for all threads
203+
else:
204+
while not TURNED_ON:
205+
pass
206+
modify_class()
207+
ready = True
208+
"""
209+
# Clearing stats will clear for all threads
210+
stat_count = run_test_code(code)
211+
self.assertEqual(stat_count, '0')
212+
213+
214+
if __name__ == "__main__":
215+
unittest.main()

0 commit comments

Comments
 (0)