test_debugger.py•6.74 kB
"""
Unit tests for debugger controller.
Tests breakpoint functionality and variable capture.
"""
import tempfile
from pathlib import Path
from mcp_debug_tool.debugger import DebugController
class TestDebugController:
"""Tests for DebugController breakpoint functionality."""
def test_run_to_single_breakpoint(self):
"""Test that debugger stops at a single breakpoint and captures locals."""
# Create a temporary test script
with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
f.write("""
def test_function():
x = 42
y = "hello"
z = [1, 2, 3]
return x + len(z) # Line 6 - breakpoint here
test_function()
""")
script_path = Path(f.name)
try:
debugger = DebugController()
response = debugger.run_to_breakpoint(script_path, str(script_path), 6)
# Verify breakpoint was hit
assert response.hit is True
assert response.completed is False
assert response.error is None
# Verify frame info
assert response.frameInfo is not None
assert response.frameInfo.line == 6
# Verify locals were captured
assert response.locals is not None
assert "x" in response.locals
assert response.locals["x"]["type"] == "int"
assert "42" in response.locals["x"]["repr"]
assert "y" in response.locals
assert response.locals["y"]["type"] == "str"
assert "hello" in response.locals["y"]["repr"]
assert "z" in response.locals
assert response.locals["z"]["type"] == "list"
finally:
script_path.unlink()
def test_breakpoint_not_reached(self):
"""Test when execution completes before breakpoint."""
# Create a script that finishes before the breakpoint line
with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
f.write("""
x = 1
y = 2
# Line 4 is empty, execution ends before we reach line 10
""")
script_path = Path(f.name)
try:
debugger = DebugController()
response = debugger.run_to_breakpoint(script_path, str(script_path), 10)
# Verify breakpoint was not hit
assert response.hit is False
assert response.completed is True
assert response.locals is None
finally:
script_path.unlink()
def test_captures_various_types(self):
"""Test that debugger captures various Python types correctly."""
with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
f.write("""
integer_var = 42
float_var = 3.14
string_var = "test"
list_var = [1, 2, 3]
dict_var = {"a": 1, "b": 2}
tuple_var = (1, 2)
set_var = {1, 2, 3}
bool_var = True
breakpoint_here = True # Line 10
""")
script_path = Path(f.name)
try:
debugger = DebugController()
response = debugger.run_to_breakpoint(script_path, str(script_path), 10)
assert response.hit is True
assert response.locals is not None
# Check various types are captured
assert "integer_var" in response.locals
assert response.locals["integer_var"]["type"] == "int"
assert "float_var" in response.locals
assert response.locals["float_var"]["type"] == "float"
assert "string_var" in response.locals
assert response.locals["string_var"]["type"] == "str"
assert "list_var" in response.locals
assert response.locals["list_var"]["type"] == "list"
assert "dict_var" in response.locals
assert response.locals["dict_var"]["type"] == "dict"
assert "bool_var" in response.locals
assert response.locals["bool_var"]["type"] == "bool"
# Note: None variables are not captured at module level
# since they might not be in scope yet
finally:
script_path.unlink()
def test_truncation_flag_for_large_collections(self):
"""Test that large collections are marked as truncated."""
with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
f.write("""
large_list = list(range(1000)) # Much larger than MAX_CONTAINER_ITEMS
breakpoint_here = True # Line 3
""")
script_path = Path(f.name)
try:
debugger = DebugController()
response = debugger.run_to_breakpoint(script_path, str(script_path), 3)
assert response.hit is True
assert response.locals is not None
assert "large_list" in response.locals
# Should be marked as truncated due to size
var_info = response.locals["large_list"]
assert var_info["length"] == 1000
assert var_info["isTruncated"] is True
finally:
script_path.unlink()
def test_exception_during_execution(self):
"""Test handling of exceptions before breakpoint."""
with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
f.write("""
x = 1
y = 0
z = x / y # This will raise ZeroDivisionError
breakpoint_here = True # Line 5 - never reached
""")
script_path = Path(f.name)
try:
debugger = DebugController()
response = debugger.run_to_breakpoint(script_path, str(script_path), 5)
# Breakpoint should not be hit due to exception
assert response.hit is False
assert response.error is not None
assert response.error.type == "ZeroDivisionError"
finally:
script_path.unlink()
def test_skips_private_variables(self):
"""Test that private variables (starting with __) are skipped."""
with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
f.write("""
public_var = 42
_private_var = "should appear"
__dunder_var__ = "should not appear"
breakpoint_here = True # Line 5
""")
script_path = Path(f.name)
try:
debugger = DebugController()
response = debugger.run_to_breakpoint(script_path, str(script_path), 5)
assert response.hit is True
assert response.locals is not None
# Public and single underscore should be captured
assert "public_var" in response.locals
assert "_private_var" in response.locals
# Dunder variables should be skipped
assert "__dunder_var__" not in response.locals
finally:
script_path.unlink()