Add tests for error handling paths in ranking, refinement, and state

- test_ranking: Update normalize_code test to expect fallback on invalid
  syntax instead of SyntaxError (matches new behavior)
- test_refinement: Add 7 tests for _parse_candidate markdown parsing
  (fenced blocks, file paths, multiple blocks, plain fallback)
- test_state: Add 6 tests for PythonState.module_ast and invalidate_module
  (valid parse, caching, SyntaxError→None, re-parse after fix)
This commit is contained in:
Kevin Turcios 2026-04-23 03:13:23 -05:00
parent 9e679f1c06
commit e1990092e0
3 changed files with 184 additions and 4 deletions

View file

@ -134,10 +134,12 @@ class TestNormalizeCode:
assert normalize_code(code_a) != normalize_code(code_b)
def test_invalid_code_raises_syntax_error(self) -> None:
"""Invalid Python code raises SyntaxError."""
with pytest.raises(SyntaxError):
normalize_code("def (broken:")
def test_invalid_code_returns_raw_string(self) -> None:
"""Invalid Python code falls back to the raw string."""
normalize_code.cache_clear()
result = normalize_code("def (broken:")
assert "def (broken:" == result
class TestDiffLength:

View file

@ -17,6 +17,7 @@ from codeflash_python.ai._refinement import (
CodeRepairRequest,
OptimizedCandidateSource,
RefinementRequest,
_parse_candidate,
adaptive_optimize,
code_repair,
optimize_code_refinement,
@ -469,3 +470,95 @@ class TestAdaptiveOptimize:
client, # type: ignore[arg-type]
make_adaptive_request(),
)
class TestParseCandidateMarkdown:
"""_parse_candidate markdown code-block parsing."""
def test_plain_code_passes_through(self) -> None:
"""Plain code without markdown fences is used as-is."""
data = {
"source_code": "def foo(): return 1",
"explanation": "Plain",
"optimization_id": "opt-1",
}
result = _parse_candidate(data, "REPAIR")
assert result is not None
assert "def foo(): return 1" == result.code
def test_markdown_fenced_code_is_extracted(self) -> None:
"""Markdown-fenced code block is parsed and extracted."""
data = {
"source_code": "```python\ndef foo(): return 2\n```",
"explanation": "Fenced",
"optimization_id": "opt-2",
}
result = _parse_candidate(data, "REFINE")
assert result is not None
assert "def foo(): return 2" == result.code
def test_markdown_with_file_path_is_extracted(self) -> None:
"""Markdown block with file path suffix is parsed."""
data = {
"source_code": ("```python:src/mod.py\ndef bar(): pass\n```"),
"explanation": "With path",
"optimization_id": "opt-3",
}
result = _parse_candidate(data, "REFINE")
assert result is not None
assert "def bar(): pass" == result.code
def test_multiple_code_blocks_joined(self) -> None:
"""Multiple markdown blocks are joined with double newlines."""
data = {
"source_code": (
"```python\ndef a(): pass\n```\n```python\ndef b(): pass\n```"
),
"explanation": "Multi",
"optimization_id": "opt-4",
}
result = _parse_candidate(data, "ADAPTIVE")
assert result is not None
assert "def a(): pass" in result.code
assert "def b(): pass" in result.code
assert "\n\n" in result.code
def test_empty_source_code_returns_none(self) -> None:
"""Empty source_code returns None."""
data = {
"source_code": "",
"explanation": "Empty",
"optimization_id": "opt-5",
}
assert _parse_candidate(data, "REPAIR") is None
def test_missing_source_code_returns_none(self) -> None:
"""Missing source_code key returns None."""
data: dict[str, Any] = {
"explanation": "No code",
"optimization_id": "opt-6",
}
assert _parse_candidate(data, "REPAIR") is None
def test_source_field_is_forwarded(self) -> None:
"""The source parameter is set on the Candidate."""
data = {
"source_code": "x = 1",
"explanation": "Test",
"optimization_id": "opt-7",
}
result = _parse_candidate(data, "ADAPTIVE")
assert result is not None
assert "ADAPTIVE" == result.source

View file

@ -0,0 +1,85 @@
"""Tests for PythonState lazy accessor methods."""
from __future__ import annotations
import ast
from pathlib import Path
from unittest.mock import MagicMock
import pytest
from codeflash_python._state import PythonState
@pytest.fixture(name="state")
def _state() -> PythonState:
"""Build a PythonState with a minimal mock configuration."""
cfg = MagicMock()
cfg.project_root = Path("/fake/project")
return PythonState(cfg=cfg)
class TestModuleAst:
"""Tests for PythonState.module_ast."""
def test_valid_file_returns_ast(self, state, tmp_path) -> None:
"""Valid Python source is parsed and cached."""
py_file = tmp_path / "mod.py"
py_file.write_text("x = 1\n", encoding="utf-8")
result = state.module_ast(py_file)
assert isinstance(result, ast.Module)
def test_cached_on_second_call(self, state, tmp_path) -> None:
"""Second call returns the same cached object."""
py_file = tmp_path / "mod.py"
py_file.write_text("x = 1\n", encoding="utf-8")
first = state.module_ast(py_file)
second = state.module_ast(py_file)
assert first is second
def test_syntax_error_returns_none(self, state, tmp_path) -> None:
"""Malformed source returns None instead of crashing."""
py_file = tmp_path / "bad.py"
py_file.write_text("def (broken:\n", encoding="utf-8")
result = state.module_ast(py_file)
assert result is None
def test_syntax_error_not_cached(self, state, tmp_path) -> None:
"""Failed parse is not cached — a corrected file can be re-read."""
py_file = tmp_path / "fix.py"
py_file.write_text("def (broken:\n", encoding="utf-8")
assert state.module_ast(py_file) is None
py_file.write_text("x = 1\n", encoding="utf-8")
result = state.module_ast(py_file)
assert isinstance(result, ast.Module)
class TestInvalidateModule:
"""Tests for PythonState.invalidate_module."""
def test_invalidate_clears_cached_ast(self, state, tmp_path) -> None:
"""After invalidation, the next call re-parses."""
py_file = tmp_path / "mod.py"
py_file.write_text("x = 1\n", encoding="utf-8")
first = state.module_ast(py_file)
state.invalidate_module(py_file)
py_file.write_text("y = 2\n", encoding="utf-8")
second = state.module_ast(py_file)
assert first is not second
assert "y" in ast.unparse(second)
def test_invalidate_nonexistent_path_is_noop(self, state) -> None:
"""Invalidating a path that was never cached does not raise."""
state.invalidate_module(Path("/nonexistent/mod.py"))