import { spawnSync } from 'child_process'; import { describe, it, expect } from 'vitest'; interface AssertPythonIsValidInput { files: { [filename: string]: string; }; } /** * Asserts that the provided Python code is syntactically valid. */ export function assertPythonIsValid({ files }: AssertPythonIsValidInput) { for (const [filename, code] of Object.entries(files)) { try { // Use spawnSync to avoid shell quote escaping issues by passing code via stdin const { status: exitCode, stderr, stdout, } = spawnSync( 'python3', ['-c', 'import sys; compile(sys.stdin.read(), "", "exec")'], { input: code, encoding: 'utf8', } ); if (exitCode !== 0) { throw new Error(stderr || stdout || 'Python compilation failed'); } } catch (error) { if (error instanceof Error) { throw new Error(`Invalid Python code in ${filename}:\n${error.message}`); } throw error; } } } if (import.meta.vitest) { describe('assertPythonIsValid', () => { it('[Given] valid Python code [Then] no errors are found', () => { const code = /* python */ ` def greet(name): return f"Hello, {name}!" result = greet("World") print(result) `; assertPythonIsValid({ files: { 'main.py': code } }); }); it('[Given] valid Python code with imports [Then] no errors are found', () => { const mainSource = /* python */ ` import math def calculate_area(radius): return math.pi * radius ** 2 area = calculate_area(5) print(f"Area: {area}") `; assertPythonIsValid({ files: { 'main.py': mainSource }, }); }); it('[Given] valid Python code with class definition [Then] no errors are found', () => { const code = /* python */ ` class Calculator: def __init__(self): self.result = 0 def add(self, x, y): self.result = x + y return self.result calc = Calculator() calc.add(2, 3) `; assertPythonIsValid({ files: { 'calculator.py': code } }); }); it('[Given] valid Python code with string escaping [Then] no errors are found', () => { const code = /* python */ ` message = "Hello \\"World\\" with 'quotes'" template = '''This is a multiline string with "double" and 'single' quotes''' formatted = f"Template: {template}" print(formatted) `; assertPythonIsValid({ files: { 'strings.py': code } }); }); it('[Given] invalid Python code [Then] errors are found', () => { const code = /* python */ ` def broken_function( print("This is missing a closing parenthesis" `; expect(() => { assertPythonIsValid({ files: { 'broken.py': code } }); }).toThrowError(); }); it('[Given] Python code with syntax error [Then] errors are found', () => { const code = /* python */ ` def invalid_syntax(): if True print("Missing colon") `; expect(() => { assertPythonIsValid({ files: { 'syntax_error.py': code } }); }).toThrowError(); }); it('[Given] Python code with indentation error [Then] errors are found', () => { const code = /* python */ ` def bad_indentation(): x = 1 y = 2 # Wrong indentation return x + y `; expect(() => { assertPythonIsValid({ files: { 'indentation_error.py': code } }); }).toThrowError(); }); it('[Given] multiple valid Python files [Then] no errors are found', () => { const mainSource = /* python */ ` from math_utils import add, multiply def main(): result = multiply(add(2, 3), 4) print(f"Result: {result}") if __name__ == "__main__": main() `; const mathUtilsSource = /* python */ ` def add(x, y): return x + y def multiply(x, y): return x * y `; assertPythonIsValid({ files: { 'main.py': mainSource, 'math_utils.py': mathUtilsSource }, }); }); it('[Given] valid Python code with decorators [Then] no errors are found', () => { const code = /* python */ ` def my_decorator(func): def wrapper(*args, **kwargs): print(f"Calling {func.__name__}") return func(*args, **kwargs) return wrapper @my_decorator def greet(name): return f"Hello, {name}!" result = greet("World") print(result) `; assertPythonIsValid({ files: { 'decorators.py': code } }); }); it('[Given] valid Python code with async/await [Then] no errors are found', () => { const code = /* python */ ` import asyncio async def fetch_data(): await asyncio.sleep(1) return "data" async def main(): result = await fetch_data() print(result) if __name__ == "__main__": asyncio.run(main()) `; assertPythonIsValid({ files: { 'async_example.py': code } }); }); it('[Given] valid Python code with type hints [Then] no errors are found', () => { const code = /* python */ ` from typing import List, Optional, Dict def process_items(items: List[str], config: Optional[Dict[str, bool]] = None) -> str: if config is None: config = {} processed = [item.upper() for item in items if config.get("uppercase", True)] return ", ".join(processed) result: str = process_items(["hello", "world"]) print(result) `; assertPythonIsValid({ files: { 'type_hints.py': code } }); }); it('[Given] Python code with complex string literals [Then] no errors are found', () => { const code = /* python */ ` # Test various string literal formats single_quotes = 'This is a single-quoted string' double_quotes = "This is a double-quoted string" triple_single = '''This is a multi-line string with single quotes''' triple_double = """This is a multi-line string with double quotes""" # Raw strings raw_string = r"This is a raw string with \\n and \\t" f_string = f"Hello, {single_quotes}" # Mixed quotes mixed = "She said, 'Hello there!' to him" escaped = "This has \\"escaped\\" quotes" print(f_string) `; assertPythonIsValid({ files: { 'string_literals.py': code } }); }); it('[Given] invalid Python code with undefined variable [Then] errors are found', () => { const code = /* python */ ` def test_function(): return undefined_variable `; // Note: Python syntax validation won't catch undefined variables - only syntax errors // This should pass syntax validation but would fail at runtime assertPythonIsValid({ files: { 'undefined_var.py': code } }); }); it('[Given] Python code with syntax error in function call [Then] errors are found', () => { const code = /* python */ ` def test_function(): print("Hello",) # This trailing comma is valid print("Hello",,) # This double comma is invalid `; expect(() => { assertPythonIsValid({ files: { 'function_call_error.py': code } }); }).toThrowError(); }); }); }