diff --git a/tests/test_syntax_validation.py b/tests/test_syntax_validation.py new file mode 100644 index 000000000..e33fd8707 --- /dev/null +++ b/tests/test_syntax_validation.py @@ -0,0 +1,82 @@ +"""Tests for Python syntax validation in execute_code.""" + +import json +import sys +import os +from pathlib import Path + +import pytest + +# Import the validation function directly +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) +from tools.code_execution_tool import _validate_python_syntax + + +class TestValidatePythonSyntax: + """Test _validate_python_syntax catches errors before subprocess spawn.""" + + def test_valid_code_returns_none(self): + assert _validate_python_syntax("print('hello')") is None + + def test_valid_multiline_returns_none(self): + code = """ +import os +def foo(): + return 42 +result = foo() +""" + assert _validate_python_syntax(code) is None + + def test_syntax_error_detected(self): + result = _validate_python_syntax("def foo( +") + assert result is not None + data = json.loads(result) + assert data["syntax_error"] is True + assert "line" in data + assert "message" in data + + def test_missing_colon(self): + result = _validate_python_syntax("def foo() + pass") + data = json.loads(result) + assert data["syntax_error"] is True + assert data["line"] == 1 + + def test_unmatched_paren(self): + result = _validate_python_syntax("print('hello'") + data = json.loads(result) + assert data["syntax_error"] is True + + def test_indentation_error(self): + result = _validate_python_syntax("def foo(): +pass") + data = json.loads(result) + assert data["syntax_error"] is True + assert data["line"] == 2 + + def test_invalid_character(self): + result = _validate_python_syntax("x = 5 √ 2") + data = json.loads(result) + assert data["syntax_error"] is True + + def test_error_format_has_required_fields(self): + result = _validate_python_syntax("def( +") + data = json.loads(result) + assert "error" in data + assert "syntax_error" in data + assert "line" in data + assert "offset" in data + assert "message" in data + + def test_empty_string_returns_none(self): + # Empty code is caught by the guard before validation + # But if called directly, ast.parse("") is valid + assert _validate_python_syntax("") is None + + def test_comment_only_returns_none(self): + assert _validate_python_syntax("# just a comment") is None + + def test_complex_valid_code(self): + code = \ No newline at end of file