PySnooper/tests/test_ended_by_exception.py
2026-06-08 11:57:40 -07:00

62 lines
1.8 KiB
Python

"""Tests for call_ended_by_exception, the 'return'-via-exception detector.
Regression test for https://github.com/cool-RR/PySnooper/issues/260: a frame's
f_lasti can be -1, and co_code[-1] would read the wrong opcode and misreport a
normal return as "Call ended by exception".
"""
import io
import opcode
from unittest.mock import Mock
import pysnooper
from pysnooper.tracer import call_ended_by_exception
def _frame(co_code, f_lasti):
frame = Mock()
frame.f_lasti = f_lasti
frame.f_code.co_code = co_code
return frame
def test_negative_f_lasti_is_not_an_exception():
# Last byte is a non-return opcode, so reading co_code[-1] (the old bug)
# would misreport an exception. With f_lasti == -1 it must be a normal
# return.
co_code = bytes([0, opcode.opmap["NOP"]])
assert call_ended_by_exception(_frame(co_code, -1), "return", None) is False
def test_last_opcode_return_is_normal_return():
co_code = bytes([opcode.opmap["RETURN_VALUE"], 0])
assert call_ended_by_exception(_frame(co_code, 0), "return", None) is False
def test_last_opcode_not_return_is_exception():
co_code = bytes([opcode.opmap["NOP"], 0])
assert call_ended_by_exception(_frame(co_code, 0), "return", None) is True
def test_non_return_event_is_never_exception():
co_code = bytes([opcode.opmap["NOP"], 0])
assert call_ended_by_exception(_frame(co_code, 0), "call", None) is False
def test_unstarted_generator_close_is_not_an_exception():
string_io = io.StringIO()
def gen(n):
for i in range(n):
yield i
@pysnooper.snoop(string_io, depth=2, color=False)
def main():
g = gen(5)
g.close()
return 42
assert main() == 42
output = string_io.getvalue()
assert "Call ended by exception" not in output
assert "Return value:.. 42" in output