Download tests/test_lean_runner_lean.py from NicolasT08/formal-mas: direct link, hf CLI and curl.
- Browser
- Download file 6.36 kB
-
https://huggingface.co/NicolasT08/formal-mas/resolve/main/tests/test_lean_runner_lean.py
- Command line
-
hf download hf://NicolasT08/formal-mas/tests/test_lean_runner_lean.py
-
curl -L -o test_lean_runner_lean.py https://huggingface.co/NicolasT08/formal-mas/resolve/main/tests/test_lean_runner_lean.py
6.36 kB
| """Integration test: LeanRunner with the real Lean REPL (`pytest -m slow`, ~1.5 min warm; | |
| the first `import Mathlib` after a reboot can take much longer on an HDD).""" | |
| from __future__ import annotations | |
| from pathlib import Path | |
| import pytest | |
| from formal_mas.lean.runner import LeanRunner, accepted | |
| ROOT = Path(__file__).resolve().parents[1] | |
| REPL_BIN = ROOT / "lean_repl" / ".lake" / "build" / "bin" | |
| pytestmark = [ | |
| pytest.mark.slow, | |
| pytest.mark.skipif(not REPL_BIN.exists(), reason="Lean REPL not built (bash scripts/build_repl.sh)"), | |
| ] | |
| HEADER = "import Mathlib\nimport Aesop\n\nset_option maxHeartbeats 400000\n\nopen Real\n\n" | |
| GOOD = HEADER + "theorem t (a b : β) : 2 * a * b β€ a ^ 2 + b ^ 2 := two_mul_le_add_sq a b\n" | |
| BAD = HEADER + "theorem t (x : β) : 0 < x ^ 2 := sq_nonneg x\n" | |
| HANG = HEADER + "partial def spin (n : Nat) : Nat := spin (n + 1)\n\n#eval spin 0\n" | |
| def runner(): | |
| r = LeanRunner() | |
| assert r.start(), r.warnings | |
| yield r | |
| r.close() | |
| def test_accepts_and_rejects(runner): | |
| good = runner.verify(GOOD, theorem="t") | |
| assert good.backend == "repl" and accepted(good) == (True, "accepted") | |
| bad = runner.verify(BAD, theorem="t") | |
| assert accepted(bad) == (False, "error") | |
| assert bad.errors[0].line == 8 and bad.errors[0].text.startswith("Type mismatch") | |
| def test_hang_times_out_and_repl_restarts(runner): | |
| starts = runner.starts | |
| res = runner.verify(HANG, timeout=10) | |
| assert res.status == "timeout" | |
| good = runner.verify(GOOD + "-- not in the cache\n", theorem="t") | |
| assert not good.cached and accepted(good) == (True, "accepted") and runner.starts == starts + 1 | |
| def test_statement_pipeline_end_to_end(runner): | |
| """skeleton (with sorry) compiles β each subgoal lemma is accepted β the assembled | |
| proof, with the same blocks pasted unchanged, is accepted; a false proof is not.""" | |
| from formal_mas.lean.runner import compiles | |
| from formal_mas.lean.statement import assemble, parse_theorem, proof_file, skeleton, subgoal_lemma | |
| thm = parse_theorem("theorem amgm_10 (a b : β) (hβ : 0 < a β§ 0 < b) (hβ : a + b = 10) :\n" | |
| " a * b β€ 25 := by sorry") | |
| subgoals = ["0 β€ (a - b) ^ 2", "(a + b) ^ 2 = 100"] | |
| blocks = ["positivity", "rw [hβ]\nnorm_num"] | |
| closing = "nlinarith [h1, h2]" | |
| sk = runner.verify(skeleton(thm, subgoals, closing), theorem=thm.name) | |
| assert compiles(sk) and sk.has_sorry | |
| for i, block in enumerate(blocks, start=1): | |
| name, code = subgoal_lemma(thm, subgoals, i, block) | |
| assert accepted(runner.verify(code, theorem=name)) == (True, "accepted"), i | |
| final = runner.verify(assemble(thm, subgoals, blocks, closing), theorem=thm.name) | |
| assert accepted(final) == (True, "accepted") and final.declarations == ["amgm_10"] | |
| wrong = runner.verify(proof_file(thm, "linarith"), theorem=thm.name) | |
| assert accepted(wrong) == (False, "error") | |
| REFUTE = ["omega", "linarith", "nlinarith", "norm_num at *", "simp_all"] | |
| AIME = ("theorem aime_1991_p1 (x y : β) (hβ : 0 < x β§ 0 < y) (hβ : x * y + (x + y) = 71)\n" | |
| " (hβ : x ^ 2 * y + x * y ^ 2 = 880) : x ^ 2 + y ^ 2 = 146 := by sorry") | |
| def _proved(runner, thm, code, ranges): | |
| from formal_mas.lean.runner import compiles_between | |
| res = runner.verify(code, None, timeout=60) | |
| return {name.removeprefix(thm.name + "_") for name, r in ranges.items() if compiles_between(res, *r)} | |
| def test_refutation_rejects_false_subgoals_only(runner): | |
| """The plan Flash-Lite proposed for aime_1991_p1 on 2026-10-28 (x*y = 10, x + y = 61) is | |
| refuted subgoal by subgoal; the true values (55, 16) are not; contradictory hypotheses | |
| prove `_consistent` (the Planner then skips the check).""" | |
| from formal_mas.lean.statement import parse_theorem, refutation_file | |
| thm = parse_theorem("import Mathlib\nimport Aesop\n\nset_option maxHeartbeats 0\n\n" + AIME) | |
| assert _proved(runner, thm, *refutation_file(thm, ["x * y = 10", "x + y = 61"], REFUTE, 200000)) == \ | |
| {"ref1", "ref2"} | |
| assert _proved(runner, thm, *refutation_file(thm, ["x * y = 55", "x + y = 16"], REFUTE, 200000)) == set() | |
| bad = parse_theorem("theorem c (x y : β) (hβ : x + y = 3) (hβ : 10 < x) : x = 5 := by sorry") | |
| assert _proved(runner, bad, *refutation_file(bad, ["x = 5"], REFUTE, 200000)) == {"consistent", "ref1"} | |
| # a subgoal naming an earlier subgoal's hypothesis is not auto-bound: it fails to elaborate, | |
| # so it is never "refuted" (without autoImplicit false, `h1` would become a new variable) | |
| assert _proved(runner, thm, *refutation_file(thm, ["x * y = 55", "h1 = h1 β x = 0"], REFUTE, 200000)) == set() | |
| def test_closing_candidates(runner): | |
| from formal_mas.lean.statement import closing_candidates_file, parse_theorem | |
| thm = parse_theorem("theorem s (a b : β) (hβ : a + b = 5) (hβ : a - b = 1) : a + 2 * b = 7 := by sorry") | |
| code, ranges = closing_candidates_file(thm, ["a = 3", "b = 2"], ["omega", "linarith", "nlinarith"], 200000) | |
| assert _proved(runner, thm, code, ranges) == {"close_auto2", "close_auto3"} # omega: not over β | |
| def test_probes_pass_on_the_real_repl_and_a_missing_name_is_caught(runner): | |
| # the module fixture started a REPL whose probes (Real.sqrt, Aesop.BuiltinRules.not_intro) answered | |
| assert runner.starts >= 1 and not any(w["reason"] == "repl_probe_failed" for w in runner.warnings) | |
| assert runner.ensure_available() and runner.available | |
| # a probe name that does not exist stands in for "the import did not bring Mathlib": the REPL is | |
| # tried twice, then the file back-end runs the same probe and fails too β Lean unavailable | |
| from formal_mas.config import load_pipeline | |
| lean = {**load_pipeline()["lean"], "probes": {"Mathlib": "Real.no_such_constant_formal_mas"}} | |
| broken = LeanRunner({"lean": lean}) | |
| try: | |
| assert broken.ensure_available() is False and broken.available is False | |
| reasons = [w["reason"] for w in broken.warnings] | |
| assert reasons == ["repl_probe_failed", "repl_probe_failed", "file_import_failed"], broken.warnings | |
| assert "Real.no_such_constant_formal_mas" in broken.warnings[0]["detail"] | |
| assert broken.verify(GOOD, theorem="t").status == "crash" # never accepts anything | |
| finally: | |
| broken.close() | |