Skip to main content
Glama
test_dap_step_operations.py10.8 kB
""" Integration tests for DAP step operations (step_in, step_over, step_out). Tests Phase 3 of the DAP integration proposal: - Step into function calls - Step over lines - Step out of functions """ import sys import tempfile from pathlib import Path import pytest from mcp_debug_tool.dap_wrapper import DAPSyncWrapper @pytest.fixture def step_test_script(): """Create a Python script for testing step operations.""" code = '''def add(a, b): result = a + b return result def multiply(x, y): product = x * y return product x = 5 y = 10 sum_result = add(x, y) mul_result = multiply(x, y) print(f"Sum: {sum_result}, Product: {mul_result}") ''' with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f: f.write(code) script_path = Path(f.name) yield script_path # Cleanup script_path.unlink(missing_ok=True) @pytest.fixture def nested_function_script(): """Create a script with nested functions for testing step_out.""" code = '''def outer(): x = 1 y = inner() return x + y def inner(): a = 10 b = 20 return a + b result = outer() print(result) ''' with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f: f.write(code) script_path = Path(f.name) yield script_path # Cleanup script_path.unlink(missing_ok=True) def test_step_over_basic(step_test_script): """ Test step_over: execute current line and stop at next line. Steps: 1. Set breakpoint at line 9 (x = 5) 2. Hit breakpoint 3. Step over to line 10 (y = 10) 4. Verify we're at line 10 with x = 5 """ python_path = sys.executable with DAPSyncWrapper() as wrapper: # Initialize and launch wrapper.initialize_and_launch( python_path=python_path, script_path=step_test_script, args=[], env=None, cwd=step_test_script.parent, ) # Run to first breakpoint at line 9 (x = 5) response = wrapper.run_to_breakpoint( file=str(step_test_script), line=9, timeout=10.0 ) assert response.hit, f"Breakpoint not hit: {response.error}" assert response.frameInfo.line == 9 # Step over to line 10 (y = 10) step_response = wrapper.step_over(timeout=10.0) assert step_response.hit, f"Step over failed: {step_response.error}" assert step_response.frameInfo.line == 10, f"Expected line 10, got {step_response.frameInfo.line}" # Verify x variable exists and equals 5 assert step_response.locals is not None assert 'x' in step_response.locals assert step_response.locals['x']['repr'] == '5' def test_step_in_to_function(step_test_script): """ Test step_in: step into a function call. Steps: 1. Set breakpoint at line 11 (sum_result = add(x, y)) 2. Hit breakpoint 3. Step in - should enter add() function at line 2 4. Verify we're inside add() with parameters a=5, b=10 """ python_path = sys.executable with DAPSyncWrapper() as wrapper: # Initialize and launch wrapper.initialize_and_launch( python_path=python_path, script_path=step_test_script, args=[], env=None, cwd=step_test_script.parent, ) # Run to breakpoint at line 11 (sum_result = add(x, y)) response = wrapper.run_to_breakpoint( file=str(step_test_script), line=11, timeout=10.0 ) assert response.hit, f"Breakpoint not hit: {response.error}" assert response.frameInfo.line == 11 # Step in to add() function step_response = wrapper.step_in(timeout=10.0) assert step_response.hit, f"Step in failed: {step_response.error}" assert step_response.frameInfo.line == 2, f"Expected line 2 (inside add), got {step_response.frameInfo.line}" # Verify we're inside add() with correct parameters assert step_response.locals is not None assert 'a' in step_response.locals assert 'b' in step_response.locals assert step_response.locals['a']['repr'] == '5' assert step_response.locals['b']['repr'] == '10' def test_step_out_from_function(nested_function_script): """ Test step_out: step out of current function to caller. Steps: 1. Set breakpoint at line 10 (result = outer()) 2. Step in to outer() (line 2) 3. Step in to inner() (line 7) 4. Step out back to outer() (should be at line 3 after inner() returns) 5. Verify we're back in outer() with y set to inner's return value (30) """ python_path = sys.executable with DAPSyncWrapper() as wrapper: # Initialize and launch wrapper.initialize_and_launch( python_path=python_path, script_path=nested_function_script, args=[], env=None, cwd=nested_function_script.parent, ) # Run to breakpoint at line 10 (result = outer()) response = wrapper.run_to_breakpoint( file=str(nested_function_script), line=10, timeout=10.0 ) assert response.hit, f"Breakpoint not hit: {response.error}" # Step in to outer() step_response1 = wrapper.step_in(timeout=10.0) assert step_response1.hit, f"Step in to outer failed: {step_response1.error}" # debugpy may stop at line 2 (x = 1) or line 3 (y = inner()) depending on optimization assert step_response1.frameInfo.line in [2, 3], f"Expected line 2 or 3, got {step_response1.frameInfo.line}" # If we're at line 2, step over to line 3 if step_response1.frameInfo.line == 2: step_response2 = wrapper.step_over(timeout=10.0) assert step_response2.hit assert step_response2.frameInfo.line == 3 # y = inner() else: # Already at line 3 step_response2 = step_response1 # Step in to inner() - or it may already be executed step_response3 = wrapper.step_in(timeout=10.0) assert step_response3.hit, f"Step in to inner failed: {step_response3.error}" # Check if we're inside inner() or back in outer() # debugpy behavior varies: may enter inner() at line 7, or may complete inner() and return if step_response3.frameInfo.line == 7: # Inside inner() - step out back to outer() step_response4 = wrapper.step_out(timeout=10.0) assert step_response4.hit, f"Step out failed: {step_response4.error}" # Should be back in outer() assert step_response4.frameInfo.line in [3, 4], f"Expected line 3 or 4, got {step_response4.frameInfo.line}" final_response = step_response4 else: # Already back in outer() (inner() executed completely) assert step_response3.frameInfo.line in [3, 4], f"Expected line 3 or 4, got {step_response3.frameInfo.line}" final_response = step_response3 # Verify we're in outer() scope (should have x and y variables) assert final_response.locals is not None assert 'x' in final_response.locals # y should be set to 30 (return value of inner()) assert 'y' in final_response.locals assert final_response.locals['y']['repr'] == '30' def test_step_over_without_function_call(step_test_script): """ Test step_over on a line without function call (simple statement). Should behave the same as step_in in this case. """ python_path = sys.executable with DAPSyncWrapper() as wrapper: # Initialize and launch wrapper.initialize_and_launch( python_path=python_path, script_path=step_test_script, args=[], env=None, cwd=step_test_script.parent, ) # Run to breakpoint at line 9 (x = 5) response = wrapper.run_to_breakpoint( file=str(step_test_script), line=9, timeout=10.0 ) assert response.hit # Step over line 9 (x = 5) to line 10 (y = 10) step_response1 = wrapper.step_over(timeout=10.0) assert step_response1.hit assert step_response1.frameInfo.line == 10 # Step over line 10 (y = 10) to line 11 (sum_result = add(x, y)) step_response2 = wrapper.step_over(timeout=10.0) assert step_response2.hit assert step_response2.frameInfo.line == 11 def test_multiple_steps_sequence(step_test_script): """ Test a sequence of mixed step operations. Demonstrates realistic debugging workflow: 1. Break at line 9 2. Step over x = 5 (to line 10) 3. Step over y = 10 (to line 11) 4. Step in to add() (to line 2) 5. Step over result = a + b (to line 3) 6. Step out back to main (to line 12) """ python_path = sys.executable with DAPSyncWrapper() as wrapper: # Initialize and launch wrapper.initialize_and_launch( python_path=python_path, script_path=step_test_script, args=[], env=None, cwd=step_test_script.parent, ) # 1. Break at line 9 (x = 5) response = wrapper.run_to_breakpoint( file=str(step_test_script), line=9, timeout=10.0 ) assert response.hit assert response.frameInfo.line == 9 # 2. Step over to line 10 (y = 10) r1 = wrapper.step_over(timeout=10.0) assert r1.hit and r1.frameInfo.line == 10 # 3. Step over to line 11 (sum_result = add(x, y)) r2 = wrapper.step_over(timeout=10.0) assert r2.hit and r2.frameInfo.line == 11 # 4. Step in to add() (should enter at line 2) r3 = wrapper.step_in(timeout=10.0) assert r3.hit and r3.frameInfo.line == 2 # 5. Step over inside add() to line 3 (return result) r4 = wrapper.step_over(timeout=10.0) assert r4.hit and r4.frameInfo.line == 3 # 6. Step out back to main (should be at line 11 or 12) r5 = wrapper.step_out(timeout=10.0) assert r5.hit # Should be at line 12 (after add() call returns) or line 11 assert r5.frameInfo.line in [11, 12], f"Expected line 11 or 12, got {r5.frameInfo.line}" if __name__ == "__main__": # Run tests with pytest pytest.main([__file__, "-v", "-s"])

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Kaina3/Debug-MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server