test_executable_lines.py•7.12 kB
"""
Unit tests for executable line validation.
Tests the get_executable_lines and validate_file_and_line functions.
"""
import tempfile
from pathlib import Path
import pytest
from mcp_debug_tool.utils import (
find_nearest_executable_line,
get_executable_lines,
validate_file_and_line,
)
def test_get_executable_lines_simple():
"""Test get_executable_lines with a simple script."""
code = """# Comment line
x = 1
# Another comment
y = 2
print(x + y)
"""
with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
f.write(code)
f.flush()
path = Path(f.name)
try:
executable = get_executable_lines(path)
# Line 2 (x = 1), Line 5 (y = 2), Line 6 (print) should be executable
assert 2 in executable
assert 5 in executable
assert 6 in executable
# Line 1 (comment), Line 3 (comment), Line 4 (blank) should NOT be executable
assert 1 not in executable
assert 3 not in executable
assert 4 not in executable
finally:
path.unlink()
def test_get_executable_lines_with_function():
"""Test get_executable_lines with function definition."""
code = """def greet(name):
print(f"Hello, {name}")
return name
x = greet("World")
"""
with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
f.write(code)
f.flush()
path = Path(f.name)
try:
executable = get_executable_lines(path)
# Function definition and body
assert 1 in executable # def greet(name):
assert 2 in executable # print(...)
assert 3 in executable # return name
assert 5 in executable # x = greet(...)
finally:
path.unlink()
def test_get_executable_lines_with_control_flow():
"""Test get_executable_lines with if/for statements."""
code = """x = 5
if x > 3:
print("yes")
else:
print("no")
for i in range(3):
print(i)
"""
with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
f.write(code)
f.flush()
path = Path(f.name)
try:
executable = get_executable_lines(path)
assert 1 in executable # x = 5
assert 2 in executable # if x > 3:
assert 3 in executable # print("yes")
# Note: 'else:' line (4) doesn't generate bytecode, so it's not executable
assert 5 in executable # print("no")
assert 7 in executable # for i in range(3):
assert 8 in executable # print(i)
finally:
path.unlink()
def test_get_executable_lines_empty_file():
"""Test get_executable_lines with empty file."""
code = ""
with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
f.write(code)
f.flush()
path = Path(f.name)
try:
executable = get_executable_lines(path)
assert len(executable) == 0
finally:
path.unlink()
def test_get_executable_lines_syntax_error():
"""Test get_executable_lines with syntax error."""
code = """x = 1
def broken(
# Missing closing parenthesis
"""
with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
f.write(code)
f.flush()
path = Path(f.name)
try:
executable = get_executable_lines(path)
# Should return empty set on syntax error
assert isinstance(executable, set)
finally:
path.unlink()
def test_validate_file_and_line_executable():
"""Test validate_file_and_line with executable line."""
code = """x = 1
# Comment
y = 2
"""
with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
f.write(code)
f.flush()
path = Path(f.name)
try:
# Line 1 (x = 1) is executable - should pass
validate_file_and_line(path, 1, check_executable=True)
# Line 3 (y = 2) is executable - should pass
validate_file_and_line(path, 3, check_executable=True)
finally:
path.unlink()
def test_validate_file_and_line_not_executable():
"""Test validate_file_and_line with non-executable line."""
code = """x = 1
# Comment line
y = 2
"""
with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
f.write(code)
f.flush()
path = Path(f.name)
try:
# Line 2 (comment) is not executable - should raise ValueError
with pytest.raises(ValueError, match="does not contain executable code"):
validate_file_and_line(path, 2, check_executable=True)
finally:
path.unlink()
def test_validate_file_and_line_blank_line():
"""Test validate_file_and_line with blank line."""
code = """x = 1
y = 2
"""
with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
f.write(code)
f.flush()
path = Path(f.name)
try:
# Line 2 (blank) is not executable - should raise ValueError
with pytest.raises(ValueError, match="does not contain executable code"):
validate_file_and_line(path, 2, check_executable=True)
finally:
path.unlink()
def test_validate_file_and_line_skip_check():
"""Test validate_file_and_line with check_executable=False."""
code = """x = 1
# Comment
y = 2
"""
with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
f.write(code)
f.flush()
path = Path(f.name)
try:
# With check_executable=False, even comment line should pass
validate_file_and_line(path, 2, check_executable=False)
finally:
path.unlink()
def test_find_nearest_executable_line():
"""Test find_nearest_executable_line function."""
executable_lines = {1, 5, 10, 15, 20}
# Exact match
assert find_nearest_executable_line(5, executable_lines) == 5
# Between lines - should find closer one
assert find_nearest_executable_line(3, executable_lines) == 1
assert find_nearest_executable_line(7, executable_lines) == 5
assert find_nearest_executable_line(8, executable_lines) == 10
# Before first line
assert find_nearest_executable_line(0, executable_lines) == 1
# After last line
assert find_nearest_executable_line(25, executable_lines) == 20
def test_find_nearest_executable_line_empty():
"""Test find_nearest_executable_line with empty set."""
assert find_nearest_executable_line(5, set()) is None
def test_validate_file_and_line_with_nearest_suggestion():
"""Test that error message includes nearest executable line."""
code = """x = 1
# Comment
# Another comment
y = 2
"""
with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
f.write(code)
f.flush()
path = Path(f.name)
try:
# Line 2 (comment) - nearest should be line 1 or 4
with pytest.raises(ValueError, match="Nearest executable line"):
validate_file_and_line(path, 2, check_executable=True)
finally:
path.unlink()