@@ -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