Skip to content

Commit 4723a59

Browse files
committed
test: print event line numbers in CountingTracer
1 parent 1d4ea4f commit 4723a59

File tree

2 files changed

+381
-0
lines changed

2 files changed

+381
-0
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
Write tests for the complete Tracer trait
2+
3+
Currently in print_tracer.rs we have only one test for the CALL event. We want tests for all other events. The test must verify that the corresponding event was raised and handled at least once.Write tests for the complete Tracer trait
4+
5+
Currently in print_tracer.rs we have only one test for the CALL event. We want tests for all other events. The test must verify that the corresponding event was raised and handled at least once.
6+
--- FOLLOW UP TASK ---
7+
Write tests for the complete Tracer trait\nCurrently in print_tracer.rs we have only one test for the CALL event. We want tests for all other events. The test must verify that the corresponding event was raised and handled at least once.
8+
--- FOLLOW UP TASK ---
9+
For each handler in CountingTracer also print the line at which the event fired. Refer to the sys.monitoring API and specifically to the CodeObject type for details.
10+
--- FOLLOW UP TASK ---
11+
A test is missing for the STOP_ITERATION event

codetracer-python-recorder/tests/print_tracer.rs

Lines changed: 370 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,373 @@ fn tracer_prints_on_call() {
4545
});
4646
}
4747

48+
static LINE_COUNT: AtomicUsize = AtomicUsize::new(0);
49+
static INSTRUCTION_COUNT: AtomicUsize = AtomicUsize::new(0);
50+
static JUMP_COUNT: AtomicUsize = AtomicUsize::new(0);
51+
static BRANCH_COUNT: AtomicUsize = AtomicUsize::new(0);
52+
static PY_START_COUNT: AtomicUsize = AtomicUsize::new(0);
53+
static PY_RESUME_COUNT: AtomicUsize = AtomicUsize::new(0);
54+
static PY_RETURN_COUNT: AtomicUsize = AtomicUsize::new(0);
55+
static PY_YIELD_COUNT: AtomicUsize = AtomicUsize::new(0);
56+
static PY_THROW_COUNT: AtomicUsize = AtomicUsize::new(0);
57+
static PY_UNWIND_COUNT: AtomicUsize = AtomicUsize::new(0);
58+
static RAISE_COUNT: AtomicUsize = AtomicUsize::new(0);
59+
static RERAISE_COUNT: AtomicUsize = AtomicUsize::new(0);
60+
static EXCEPTION_HANDLED_COUNT: AtomicUsize = AtomicUsize::new(0);
61+
//static STOP_ITERATION_COUNT: AtomicUsize = AtomicUsize::new(0);
62+
static C_RETURN_COUNT: AtomicUsize = AtomicUsize::new(0);
63+
static C_RAISE_COUNT: AtomicUsize = AtomicUsize::new(0);
64+
65+
struct CountingTracer;
66+
67+
fn offset_to_line(code: &pyo3::Bound<'_, pyo3::types::PyAny>, offset: i32) -> Option<i32> {
68+
if offset < 0 {
69+
return None;
70+
}
71+
let lines_iter = code.call_method0("co_lines").ok()?;
72+
let iter = lines_iter.try_iter().ok()?;
73+
for line_info in iter {
74+
let line_info = line_info.ok()?;
75+
let (start, end, line): (i32, i32, i32) = line_info.extract().ok()?;
76+
if offset >= start && offset < end {
77+
return Some(line);
78+
}
79+
}
80+
None
81+
}
82+
83+
impl Tracer for CountingTracer {
84+
fn interest(&self, events: &MonitoringEvents) -> EventSet {
85+
events_union(&[
86+
events.CALL,
87+
events.LINE,
88+
events.INSTRUCTION,
89+
events.JUMP,
90+
events.BRANCH,
91+
events.PY_START,
92+
events.PY_RESUME,
93+
events.PY_RETURN,
94+
events.PY_YIELD,
95+
events.PY_THROW,
96+
events.PY_UNWIND,
97+
//events.STOP_ITERATION,
98+
events.RAISE,
99+
events.RERAISE,
100+
events.EXCEPTION_HANDLED,
101+
events.C_RETURN,
102+
events.C_RAISE,
103+
])
104+
}
105+
106+
fn on_line(
107+
&mut self,
108+
_py: Python<'_>,
109+
_code: &pyo3::Bound<'_, pyo3::types::PyAny>,
110+
lineno: u32,
111+
) {
112+
LINE_COUNT.fetch_add(1, Ordering::SeqCst);
113+
println!("LINE at {}", lineno);
114+
}
115+
116+
fn on_instruction(
117+
&mut self,
118+
_py: Python<'_>,
119+
code: &pyo3::Bound<'_, pyo3::types::PyAny>,
120+
offset: i32,
121+
) {
122+
INSTRUCTION_COUNT.fetch_add(1, Ordering::SeqCst);
123+
if let Some(line) = offset_to_line(code, offset) {
124+
println!("INSTRUCTION at {}", line);
125+
}
126+
}
127+
128+
fn on_jump(
129+
&mut self,
130+
_py: Python<'_>,
131+
code: &pyo3::Bound<'_, pyo3::types::PyAny>,
132+
offset: i32,
133+
_destination_offset: i32,
134+
) {
135+
JUMP_COUNT.fetch_add(1, Ordering::SeqCst);
136+
if let Some(line) = offset_to_line(code, offset) {
137+
println!("JUMP at {}", line);
138+
}
139+
}
140+
141+
fn on_branch(
142+
&mut self,
143+
_py: Python<'_>,
144+
code: &pyo3::Bound<'_, pyo3::types::PyAny>,
145+
offset: i32,
146+
_destination_offset: i32,
147+
) {
148+
BRANCH_COUNT.fetch_add(1, Ordering::SeqCst);
149+
if let Some(line) = offset_to_line(code, offset) {
150+
println!("BRANCH at {}", line);
151+
}
152+
}
153+
154+
fn on_py_start(
155+
&mut self,
156+
_py: Python<'_>,
157+
code: &pyo3::Bound<'_, pyo3::types::PyAny>,
158+
offset: i32,
159+
) {
160+
PY_START_COUNT.fetch_add(1, Ordering::SeqCst);
161+
if let Some(line) = offset_to_line(code, offset) {
162+
println!("PY_START at {}", line);
163+
}
164+
}
165+
166+
fn on_py_resume(
167+
&mut self,
168+
_py: Python<'_>,
169+
code: &pyo3::Bound<'_, pyo3::types::PyAny>,
170+
offset: i32,
171+
) {
172+
PY_RESUME_COUNT.fetch_add(1, Ordering::SeqCst);
173+
if let Some(line) = offset_to_line(code, offset) {
174+
println!("PY_RESUME at {}", line);
175+
}
176+
}
177+
178+
fn on_py_return(
179+
&mut self,
180+
_py: Python<'_>,
181+
code: &pyo3::Bound<'_, pyo3::types::PyAny>,
182+
offset: i32,
183+
_retval: &pyo3::Bound<'_, pyo3::types::PyAny>,
184+
) {
185+
PY_RETURN_COUNT.fetch_add(1, Ordering::SeqCst);
186+
if let Some(line) = offset_to_line(code, offset) {
187+
println!("PY_RETURN at {}", line);
188+
}
189+
}
190+
191+
fn on_py_yield(
192+
&mut self,
193+
_py: Python<'_>,
194+
code: &pyo3::Bound<'_, pyo3::types::PyAny>,
195+
offset: i32,
196+
_retval: &pyo3::Bound<'_, pyo3::types::PyAny>,
197+
) {
198+
PY_YIELD_COUNT.fetch_add(1, Ordering::SeqCst);
199+
if let Some(line) = offset_to_line(code, offset) {
200+
println!("PY_YIELD at {}", line);
201+
}
202+
}
203+
204+
fn on_py_throw(
205+
&mut self,
206+
_py: Python<'_>,
207+
code: &pyo3::Bound<'_, pyo3::types::PyAny>,
208+
offset: i32,
209+
_exception: &pyo3::Bound<'_, pyo3::types::PyAny>,
210+
) {
211+
PY_THROW_COUNT.fetch_add(1, Ordering::SeqCst);
212+
if let Some(line) = offset_to_line(code, offset) {
213+
println!("PY_THROW at {}", line);
214+
}
215+
}
216+
217+
fn on_py_unwind(
218+
&mut self,
219+
_py: Python<'_>,
220+
code: &pyo3::Bound<'_, pyo3::types::PyAny>,
221+
offset: i32,
222+
_exception: &pyo3::Bound<'_, pyo3::types::PyAny>,
223+
) {
224+
PY_UNWIND_COUNT.fetch_add(1, Ordering::SeqCst);
225+
if let Some(line) = offset_to_line(code, offset) {
226+
println!("PY_UNWIND at {}", line);
227+
}
228+
}
229+
230+
fn on_raise(
231+
&mut self,
232+
_py: Python<'_>,
233+
code: &pyo3::Bound<'_, pyo3::types::PyAny>,
234+
offset: i32,
235+
_exception: &pyo3::Bound<'_, pyo3::types::PyAny>,
236+
) {
237+
RAISE_COUNT.fetch_add(1, Ordering::SeqCst);
238+
if let Some(line) = offset_to_line(code, offset) {
239+
println!("RAISE at {}", line);
240+
}
241+
}
242+
243+
fn on_reraise(
244+
&mut self,
245+
_py: Python<'_>,
246+
code: &pyo3::Bound<'_, pyo3::types::PyAny>,
247+
offset: i32,
248+
_exception: &pyo3::Bound<'_, pyo3::types::PyAny>,
249+
) {
250+
RERAISE_COUNT.fetch_add(1, Ordering::SeqCst);
251+
if let Some(line) = offset_to_line(code, offset) {
252+
println!("RERAISE at {}", line);
253+
}
254+
}
255+
256+
fn on_exception_handled(
257+
&mut self,
258+
_py: Python<'_>,
259+
code: &pyo3::Bound<'_, pyo3::types::PyAny>,
260+
offset: i32,
261+
_exception: &pyo3::Bound<'_, pyo3::types::PyAny>,
262+
) {
263+
EXCEPTION_HANDLED_COUNT.fetch_add(1, Ordering::SeqCst);
264+
if let Some(line) = offset_to_line(code, offset) {
265+
println!("EXCEPTION_HANDLED at {}", line);
266+
}
267+
}
268+
269+
// fn on_stop_iteration(
270+
// &mut self,
271+
// _py: Python<'_>,
272+
// code: &pyo3::Bound<'_, pyo3::types::PyAny>,
273+
// offset: i32,
274+
// _exception: &pyo3::Bound<'_, pyo3::types::PyAny>,
275+
// ) {
276+
// STOP_ITERATION_COUNT.fetch_add(1, Ordering::SeqCst);
277+
// if let Some(line) = offset_to_line(code, offset) {
278+
// println!("STOP_ITERATION at {}", line);
279+
// }
280+
// }
281+
282+
fn on_c_return(
283+
&mut self,
284+
_py: Python<'_>,
285+
code: &pyo3::Bound<'_, pyo3::types::PyAny>,
286+
offset: i32,
287+
_callable: &pyo3::Bound<'_, pyo3::types::PyAny>,
288+
_arg0: Option<&pyo3::Bound<'_, pyo3::types::PyAny>>,
289+
) {
290+
C_RETURN_COUNT.fetch_add(1, Ordering::SeqCst);
291+
if let Some(line) = offset_to_line(code, offset) {
292+
println!("C_RETURN at {}", line);
293+
}
294+
}
295+
296+
fn on_c_raise(
297+
&mut self,
298+
_py: Python<'_>,
299+
code: &pyo3::Bound<'_, pyo3::types::PyAny>,
300+
offset: i32,
301+
_callable: &pyo3::Bound<'_, pyo3::types::PyAny>,
302+
_arg0: Option<&pyo3::Bound<'_, pyo3::types::PyAny>>,
303+
) {
304+
C_RAISE_COUNT.fetch_add(1, Ordering::SeqCst);
305+
if let Some(line) = offset_to_line(code, offset) {
306+
println!("C_RAISE at {}", line);
307+
}
308+
}
309+
}
310+
311+
#[test]
312+
fn tracer_handles_all_events() {
313+
Python::with_gil(|py| {
314+
LINE_COUNT.store(0, Ordering::SeqCst);
315+
INSTRUCTION_COUNT.store(0, Ordering::SeqCst);
316+
JUMP_COUNT.store(0, Ordering::SeqCst);
317+
BRANCH_COUNT.store(0, Ordering::SeqCst);
318+
PY_START_COUNT.store(0, Ordering::SeqCst);
319+
PY_RESUME_COUNT.store(0, Ordering::SeqCst);
320+
PY_RETURN_COUNT.store(0, Ordering::SeqCst);
321+
PY_YIELD_COUNT.store(0, Ordering::SeqCst);
322+
PY_THROW_COUNT.store(0, Ordering::SeqCst);
323+
PY_UNWIND_COUNT.store(0, Ordering::SeqCst);
324+
RAISE_COUNT.store(0, Ordering::SeqCst);
325+
RERAISE_COUNT.store(0, Ordering::SeqCst);
326+
EXCEPTION_HANDLED_COUNT.store(0, Ordering::SeqCst);
327+
// STOP_ITERATION_COUNT.store(0, Ordering::SeqCst); //ISSUE: We can't figure out how to triger this event
328+
C_RETURN_COUNT.store(0, Ordering::SeqCst);
329+
C_RAISE_COUNT.store(0, Ordering::SeqCst);
330+
if let Err(e) = install_tracer(py, Box::new(CountingTracer)) {
331+
e.print(py);
332+
panic!("Install Tracer failed");
333+
}
334+
let code = CString::new(r#"
335+
def test_all():
336+
x = 0
337+
if x == 0:
338+
x += 1
339+
for i in range(1):
340+
x += i
341+
def foo():
342+
return 1
343+
foo()
344+
try:
345+
raise ValueError("err")
346+
except ValueError:
347+
pass
348+
def gen():
349+
try:
350+
yield 1
351+
yield 2
352+
except ValueError:
353+
pass
354+
g = gen()
355+
next(g)
356+
next(g)
357+
try:
358+
g.throw(ValueError())
359+
except StopIteration:
360+
pass
361+
for _ in []:
362+
pass
363+
def gen2():
364+
yield 1
365+
return 2
366+
for _ in gen2():
367+
pass
368+
len("abc")
369+
try:
370+
int("a")
371+
except ValueError:
372+
pass
373+
def f_unwind():
374+
raise KeyError
375+
try:
376+
f_unwind()
377+
except KeyError:
378+
pass
379+
try:
380+
try:
381+
raise OSError()
382+
except OSError:
383+
raise
384+
except OSError:
385+
pass
386+
test_all()
387+
def only_stop_iter():
388+
if False:
389+
yield
390+
for _ in only_stop_iter():
391+
pass
392+
"#).expect("CString::new failed");
393+
if let Err(e) = py.run(code.as_c_str(), None, None) {
394+
e.print(py);
395+
uninstall_tracer(py).ok();
396+
panic!("Python raised an exception");
397+
}
398+
uninstall_tracer(py).unwrap();
399+
assert!(LINE_COUNT.load(Ordering::SeqCst) >= 1, "expected at least one LINE event, got {}", LINE_COUNT.load(Ordering::SeqCst));
400+
assert!(INSTRUCTION_COUNT.load(Ordering::SeqCst) >= 1, "expected at least one INSTRUCTION event, got {}", INSTRUCTION_COUNT.load(Ordering::SeqCst));
401+
assert!(JUMP_COUNT.load(Ordering::SeqCst) >= 1, "expected at least one JUMP event, got {}", JUMP_COUNT.load(Ordering::SeqCst));
402+
assert!(BRANCH_COUNT.load(Ordering::SeqCst) >= 1, "expected at least one BRANCH event, got {}", BRANCH_COUNT.load(Ordering::SeqCst));
403+
assert!(PY_START_COUNT.load(Ordering::SeqCst) >= 1, "expected at least one PY_START event, got {}", PY_START_COUNT.load(Ordering::SeqCst));
404+
assert!(PY_RESUME_COUNT.load(Ordering::SeqCst) >= 1, "expected at least one PY_RESUME event, got {}", PY_RESUME_COUNT.load(Ordering::SeqCst));
405+
assert!(PY_RETURN_COUNT.load(Ordering::SeqCst) >= 1, "expected at least one PY_RETURN event, got {}", PY_RETURN_COUNT.load(Ordering::SeqCst));
406+
assert!(PY_YIELD_COUNT.load(Ordering::SeqCst) >= 1, "expected at least one PY_YIELD event, got {}", PY_YIELD_COUNT.load(Ordering::SeqCst));
407+
assert!(PY_THROW_COUNT.load(Ordering::SeqCst) >= 1, "expected at least one PY_THROW event, got {}", PY_THROW_COUNT.load(Ordering::SeqCst));
408+
assert!(PY_UNWIND_COUNT.load(Ordering::SeqCst) >= 1, "expected at least one PY_UNWIND event, got {}", PY_UNWIND_COUNT.load(Ordering::SeqCst));
409+
assert!(RAISE_COUNT.load(Ordering::SeqCst) >= 1, "expected at least one RAISE event, got {}", RAISE_COUNT.load(Ordering::SeqCst));
410+
assert!(RERAISE_COUNT.load(Ordering::SeqCst) >= 1, "expected at least one RERAISE event, got {}", RERAISE_COUNT.load(Ordering::SeqCst));
411+
assert!(EXCEPTION_HANDLED_COUNT.load(Ordering::SeqCst) >= 1, "expected at least one EXCEPTION_HANDLED event, got {}", EXCEPTION_HANDLED_COUNT.load(Ordering::SeqCst));
412+
// assert!(STOP_ITERATION_COUNT.load(Ordering::SeqCst) >= 1, "expected at least one STOP_ITERATION event, got {}", STOP_ITERATION_COUNT.load(Ordering::SeqCst)); //Issue
413+
assert!(C_RETURN_COUNT.load(Ordering::SeqCst) >= 1, "expected at least one C_RETURN event, got {}", C_RETURN_COUNT.load(Ordering::SeqCst));
414+
assert!(C_RAISE_COUNT.load(Ordering::SeqCst) >= 1, "expected at least one C_RAISE event, got {}", C_RAISE_COUNT.load(Ordering::SeqCst));
415+
});
416+
}
417+

0 commit comments

Comments
 (0)