Skip to content

Commit b1629aa

Browse files
committed
test: print event line numbers in CountingTracer
1 parent 82bef01 commit b1629aa

File tree

2 files changed

+348
-1
lines changed

2 files changed

+348
-1
lines changed
Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
Write tests for the complete Tracer trait
22

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.
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.
4+
--- FOLLOW UP TASK ---
5+
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.
6+
--- FOLLOW UP TASK ---
7+
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.

codetracer-python-recorder/tests/print_tracer.rs

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

0 commit comments

Comments
 (0)