Download tests/test_verify.py from CamiloS1111/ragsolver: direct link, hf CLI and curl.
- Browser
- Download file 5.45 kB
-
https://huggingface.co/CamiloS1111/ragsolver/resolve/main/tests/test_verify.py
- Command line
-
hf download hf://CamiloS1111/ragsolver/tests/test_verify.py
-
curl -L -o test_verify.py https://huggingface.co/CamiloS1111/ragsolver/resolve/main/tests/test_verify.py
5.45 kB
| """Fase 1 de docs/PLAN_CORRECCIONES.md — endurecimiento del verificador | |
| (SEC-1, VER-1, BUG-2). Sin Lean: `find_forbidden_tactic`, la auditoría de | |
| axiomas y que la fuente que se reporta sea la misma que se verifica. | |
| El test con Lean real vive en test_verify_slow.py (marcador `slow`).""" | |
| from __future__ import annotations | |
| import pytest | |
| from ragsolver.lean.verify import ( | |
| STANDARD_AXIOMS, | |
| build_file, | |
| find_forbidden_tactic, | |
| non_standard_axioms, | |
| parse_axioms, | |
| theorem_block, | |
| ) | |
| # -- SEC-1: validación de tácticas -------------------------------------------- | |
| DANGEROUS = [ | |
| 'simp\n #eval IO.println "pwned"', # comando inyectado tras una táctica legítima (el exploit real de la auditoría) | |
| "open Nat in #eval IO.println \"pwned\"", # mismo bypass, pero disfrazado con `open ... in` (no anclado a línea) | |
| "decide +native", | |
| "exact Lean.ofReduceBool _ _ rfl", | |
| "native_decide", | |
| 'run_tac (do IO.println "x")', | |
| "set_option maxHeartbeats 1 in simp", | |
| "sorry", | |
| "admit", | |
| "open Foo", # `open` SIN `in`: no es una táctica válida por sí sola | |
| "have h := 1\nimport Mathlib.Tactic", | |
| "theorem evil : True := trivial", | |
| "#print axioms foo", | |
| "#check 1", | |
| "axiom evil : False", | |
| "attribute [instance] foo", | |
| ] | |
| def test_find_forbidden_tactic_rejects_dangerous_patterns(tactics): | |
| assert find_forbidden_tactic(tactics) is not None | |
| SAFE = [ | |
| "simp", | |
| "nlinarith [sq_nonneg (a - b)]", | |
| "norm_num [Nat.Prime] at h", | |
| "decide", | |
| "omega", | |
| "interval_cases x <;> simp_all", | |
| "have h : 1 = 1 := by rfl\n exact h", | |
| "-- esto menciona axiom y sorry, pero es un comentario\nsimp", | |
| "open Nat in exact zero_le n", # `open X in`: alcance de nombres, no ejecuta código | |
| "ring_nf", | |
| "constructor <;> linarith", | |
| ] | |
| def test_find_forbidden_tactic_accepts_legitimate_tactics(tactics): | |
| assert find_forbidden_tactic(tactics) is None | |
| # -- VER-1: auditoría de axiomas ----------------------------------------------- | |
| def test_parse_axioms_extracts_the_bracketed_list(): | |
| assert parse_axioms("'foo' depends on axioms: [propext, Classical.choice, Quot.sound]") == ["propext", "Classical.choice", "Quot.sound"] | |
| assert parse_axioms("'foo' does not depend on any axioms") == [] | |
| def test_non_standard_axioms_flags_native_decide_and_sorry(): | |
| assert non_standard_axioms(["propext"]) == [] | |
| assert set(STANDARD_AXIOMS) == {"propext", "Classical.choice", "Quot.sound"} | |
| assert non_standard_axioms(["propext", "Lean.ofReduceBool", "sorryAx"]) == ["Lean.ofReduceBool", "sorryAx"] | |
| # -- BUG-2: fuente única REPL <-> reporte ------------------------------------- | |
| def test_theorem_block_includes_header_opens_and_axiom_audit(): | |
| code, name = theorem_block("theorem t1 (n : ℕ) : n + 0 = n := by sorry", "simp", header="open BigOperators Real") | |
| assert name == "t1" | |
| assert code.startswith("open BigOperators Real\n") | |
| assert "set_option maxHeartbeats 400000 in" in code | |
| assert "theorem t1 (n : ℕ) : n + 0 = n := by\n simp" in code | |
| assert code.rstrip().endswith("#print axioms t1") | |
| def test_theorem_block_ignores_import_lines_in_header(): | |
| """El header de miniF2F trae sólo `open ...` (sin imports, docs/PLAN_RAG.md | |
| §7.1 `Problem.header`); si alguna vez trajera un `import`, no debe duplicar | |
| el de `build_file`.""" | |
| code, _ = theorem_block("theorem t : True := by sorry", "trivial", header="import Mathlib\nopen Nat") | |
| assert "import Mathlib" not in code | |
| assert code.startswith("open Nat\n") | |
| def test_theorem_block_renames_anonymous_example_deterministically(): | |
| code, name = theorem_block("example : 1 + 1 = 2 := by sorry", "norm_num") | |
| assert name == "_ragsolver_goal" | |
| assert "theorem _ragsolver_goal : 1 + 1 = 2 := by\n norm_num" in code | |
| def test_theorem_block_rejects_unrecognized_statement_shape(): | |
| with pytest.raises(ValueError): | |
| theorem_block("def foo := 1", "simp") | |
| def test_build_file_lean_code_matches_what_the_repl_would_receive(): | |
| """BUG-2: el `lean_code` reportado debe ser exactamente el bloque que | |
| verificaría el REPL (imports + el mismo `theorem_block`), incluidos los | |
| `open` del header.""" | |
| block, _ = theorem_block("theorem t1 : True := by sorry", "trivial", header="open Nat") | |
| full = build_file("theorem t1 : True := by sorry", "trivial", ["Mathlib", "Aesop"], header="open Nat") | |
| assert full == "import Mathlib\nimport Aesop\n\n" + block + "\n" | |
| # -- verify(): el rechazo de tácticas ocurre sin invocar Lean ------------------ | |
| def test_verify_rejects_forbidden_tactics_without_running_lean(monkeypatch): | |
| from ragsolver.lean import verify as V | |
| def boom(*a, **kw): | |
| raise AssertionError("no debería llegar a ejecutar Lean") | |
| monkeypatch.setattr(V, "run_lean", boom) | |
| v = V.verify("theorem t : True := by sorry", "native_decide", "t", ["Mathlib"]) | |
| assert v.ok is False and "native_decide" in v.errors[0]["message"] | |
| def test_verify_rejects_injected_command_without_running_lean(monkeypatch): | |
| from ragsolver.lean import verify as V | |
| def boom(*a, **kw): | |
| raise AssertionError("no debería llegar a ejecutar Lean") | |
| monkeypatch.setattr(V, "run_lean", boom) | |
| v = V.verify("theorem t : True := by sorry", 'simp\n#eval IO.println "pwned"', "t", ["Mathlib"]) | |
| assert v.ok is False | |