Skip to content

Commit c32f177

Browse files
committed
gh-150429: Fix sampling profiler generator stack test
1 parent e6b17d1 commit c32f177

1 file changed

Lines changed: 45 additions & 26 deletions

File tree

Lib/test/test_profiling/test_sampling_profiler/test_blocking.py

Lines changed: 45 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,9 @@ class TestBlockingModeStackAccuracy(unittest.TestCase):
3939
@classmethod
4040
def setUpClass(cls):
4141
# Test script that uses a generator consumed in a loop.
42-
# When consume_generator is on the arithmetic lines (temp1, temp2, etc.),
43-
# fibonacci_generator should NOT be in the stack at all.
42+
# When consume_generator is the executing leaf frame on the arithmetic
43+
# lines (temp1, temp2, etc.), fibonacci_generator should NOT be in the
44+
# stack at all.
4445
# Line numbers are important here - see ARITHMETIC_LINES below.
4546
cls.generator_script = textwrap.dedent('''
4647
def fibonacci_generator(n):
@@ -65,29 +66,32 @@ def main():
6566
main()
6667
''')
6768
# Line numbers of the arithmetic operations in consume_generator.
68-
# These are the lines where fibonacci_generator should NOT be in the stack.
69-
# The socket injection code adds 7 lines before our script.
70-
# temp1 = value + 1 -> line 17
71-
# temp2 = value * 2 -> line 18
72-
# temp3 = value - 1 -> line 19
73-
# result = ... -> line 20
74-
cls.ARITHMETIC_LINES = {17, 18, 19, 20}
69+
# These are the lines where fibonacci_generator should NOT be in the
70+
# stack when consume_generator is the executing leaf frame. They account
71+
# for the socket prelude added by test_subprocess().
72+
# temp1 = value + 1 -> line 16
73+
# temp2 = value * 2 -> line 17
74+
# temp3 = value - 1 -> line 18
75+
# result = ... -> line 19
76+
cls.ARITHMETIC_LINES = {16, 17, 18, 19}
7577

7678
def test_generator_not_under_consumer_arithmetic(self):
7779
"""Test that fibonacci_generator doesn't appear when consume_generator does arithmetic.
7880
79-
When consume_generator is executing arithmetic lines (temp1, temp2, etc.),
80-
fibonacci_generator should NOT be anywhere in the stack - it's not being
81-
called at that point.
81+
When consume_generator is the leaf frame on arithmetic lines (temp1,
82+
temp2, etc.), fibonacci_generator should NOT be anywhere in the stack -
83+
it's not being called at that point. Non-leaf frame line numbers are
84+
caller/resume metadata, not proof that the frame is executing.
8285
8386
Valid stacks:
84-
- consume_generator at 'for value in gen:' line WITH fibonacci_generator
85-
at the top (generator is yielding)
87+
- fibonacci_generator at the top (generator is executing), with
88+
consume_generator below it
8689
- consume_generator at arithmetic lines WITHOUT fibonacci_generator
8790
(we're just doing math, not calling the generator)
8891
8992
Invalid stacks (indicate torn/inconsistent reads):
90-
- consume_generator at arithmetic lines WITH fibonacci_generator
93+
- consume_generator leaf frame at arithmetic lines WITH
94+
fibonacci_generator
9195
anywhere in the stack
9296
9397
Note: call_tree is ordered from bottom (index 0) to top (index -1).
@@ -110,22 +114,30 @@ def test_generator_not_under_consumer_arithmetic(self):
110114
total_samples = 0
111115
invalid_stacks = 0
112116
arithmetic_samples = 0
117+
generator_samples = 0
118+
generator_not_leaf_samples = 0
113119

114120
for (call_tree, _thread_id), count in collector.stack_counter.items():
115121
total_samples += count
116122

117123
if not call_tree:
118124
continue
119125

120-
# Find consume_generator in the stack and check its line number
121-
for i, (filename, lineno, funcname) in enumerate(call_tree):
122-
if funcname == "consume_generator" and lineno in self.ARITHMETIC_LINES:
123-
arithmetic_samples += count
124-
# Check if fibonacci_generator appears anywhere in this stack
125-
func_names = [frame[2] for frame in call_tree]
126-
if "fibonacci_generator" in func_names:
127-
invalid_stacks += count
128-
break
126+
# Non-leaf frame line numbers can point at resume locations while
127+
# a callee is the executing leaf frame.
128+
_, lineno, funcname = call_tree[-1]
129+
func_names = [frame[2] for frame in call_tree]
130+
131+
if "fibonacci_generator" in func_names:
132+
generator_samples += count
133+
if funcname != "fibonacci_generator":
134+
generator_not_leaf_samples += count
135+
136+
if funcname == "consume_generator" and lineno in self.ARITHMETIC_LINES:
137+
arithmetic_samples += count
138+
# Check if fibonacci_generator appears anywhere in this stack.
139+
if "fibonacci_generator" in func_names:
140+
invalid_stacks += count
129141

130142
self.assertGreater(total_samples, 10,
131143
f"Expected at least 10 samples, got {total_samples}")
@@ -134,8 +146,15 @@ def test_generator_not_under_consumer_arithmetic(self):
134146
self.assertGreater(arithmetic_samples, 0,
135147
f"Expected some samples on arithmetic lines, got {arithmetic_samples}")
136148

149+
self.assertGreater(generator_samples, 0,
150+
f"Expected some samples in fibonacci_generator, got {generator_samples}")
151+
152+
self.assertEqual(generator_not_leaf_samples, 0,
153+
f"Found {generator_not_leaf_samples}/{generator_samples} stacks where "
154+
f"fibonacci_generator appears but is not the leaf frame.")
155+
137156
self.assertEqual(invalid_stacks, 0,
138157
f"Found {invalid_stacks}/{arithmetic_samples} invalid stacks where "
139158
f"fibonacci_generator appears in the stack when consume_generator "
140-
f"is on an arithmetic line. This indicates torn/inconsistent stack "
141-
f"traces are being captured.")
159+
f"is the leaf frame on an arithmetic line. This indicates "
160+
f"torn/inconsistent stack traces are being captured.")

0 commit comments

Comments
 (0)