Spaces:
Running
Running
File size: 7,189 Bytes
67acd34 38c2566 180f0c3 67acd34 180f0c3 38c2566 67acd34 180f0c3 67acd34 38c2566 180f0c3 38c2566 180f0c3 f227b96 b266f7c 30865cf b266f7c | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 | from __future__ import annotations
import subprocess
import sys
import unittest
from pathlib import Path
from unittest.mock import patch
from space_app.verification import VerificationService
RLP_PATH = Path(__file__).resolve().parents[1] / "submodules" / "rlp"
if str(RLP_PATH) not in sys.path:
sys.path.insert(0, str(RLP_PATH))
from rlp.ascii_parser import parse_ascii_pattern # type: ignore
class VerificationTests(unittest.TestCase):
def test_flow_free_example_if_solver_available(self) -> None:
verifier = VerificationService()
problem = "BA...A\n..E...\n..D.F.\n..F..D\n..C.CE\nB....."
solution = "BAAAAA\nBEEDDD\nBEDDFD\nBEFFFD\nBECCCE\nBEEEEE"
try:
result = verifier.verify(
puzzle_type="flow_free",
problem_ascii=problem,
board_ascii=solution,
args="6x6",
)
except BaseException as caught: # pragma: no cover - best effort native test
self.skipTest(f"Native verifier unavailable locally: {caught}")
return
self.assertTrue(result["correct"])
def test_undead_verification_timeout_returns_clean_error(self) -> None:
verifier = VerificationService()
board = "\n".join(
[
"G: 1 V: 1 Z: 1",
"",
" 1 1 1 1 ",
" 1 . . . . 1",
" 1 . . . . 1",
" 1 . . . . 1",
" 1 . . . . 1",
" 1 1 1 1 ",
]
)
with patch("space_app.verification.subprocess.run", side_effect=subprocess.TimeoutExpired("cmd", 15)):
result = verifier.verify(
puzzle_type="undead",
problem_ascii=board,
board_ascii=board,
args="4x4de",
)
self.assertFalse(result["correct"])
self.assertEqual(result.get("error"), "Undead verification timed out.")
def test_pattern_parser_keeps_single_digit_top_clue_rows(self) -> None:
board = "\n".join(
[
" 1 ",
" 1 2 3 3 3 ",
" +--+--+--+--+--+",
"2 1|##|##|..|##|..|",
" +--+--+--+--+--+",
" 1|..|##|..|..|..|",
" +--+--+--+--+--+",
" 3|..|..|##|##|##|",
" +--+--+--+--+--+",
" 3|..|..|##|##|##|",
" +--+--+--+--+--+",
" 3|..|..|##|##|##|",
" +--+--+--+--+--+",
]
)
state = parse_ascii_pattern(board)
self.assertEqual(state["common"]["rowlen"][:5], [1, 1, 1, 2, 1])
self.assertEqual(state["common"]["rowdata"][:6], [1, 2, 3, 1, 3, 3])
def test_loopy_runs_in_isolated_subprocess(self) -> None:
# Loopy verification must go through the subprocess path so a native
# crash/hang in the RLP C library cannot take down the web worker.
verifier = VerificationService()
problem = "\n".join(
[
"+++++++++++++",
"+ +",
"+ 3 3 +",
"+ +",
"+ 2 1 0 1 2 +",
"+ +",
"+ 2 1 0 +",
"+ +",
"+ 3 +",
"+ +",
"+ 2 2 +",
"+ +",
"+++++++++++++",
]
)
solution = "\n".join(
[
" - x - x x ",
"|3| |3| x x",
" x - x - - ",
"|2x1x0x1x2|",
" - x x x x ",
"x2| x1x0x |",
" x x - x - ",
"x | | | |3x",
" - x x x - ",
"| x2| |2x |",
" - - x - -",
]
)
with patch(
"space_app.verification.subprocess.run", wraps=subprocess.run
) as run_spy:
try:
result = verifier.verify(
puzzle_type="loopy",
problem_ascii=problem,
board_ascii=solution,
args="5x5de",
)
except BaseException as caught: # pragma: no cover - native best effort
self.skipTest(f"Native verifier unavailable locally: {caught}")
return
run_spy.assert_called_once()
self.assertTrue(result["correct"])
def test_loopy_verification_timeout_returns_clean_error(self) -> None:
verifier = VerificationService()
solution = " - x - x x \n|3| |3| x x"
with patch(
"space_app.verification.subprocess.run",
side_effect=subprocess.TimeoutExpired("cmd", 20),
):
result = verifier.verify(
puzzle_type="loopy",
problem_ascii=solution,
board_ascii=solution,
args="5x5de",
)
self.assertFalse(result["correct"])
self.assertEqual(result.get("error"), "Loopy verification timed out.")
def test_flow_free_runs_in_isolated_subprocess(self) -> None:
verifier = VerificationService()
problem = "BA...A\n..E...\n..D.F.\n..F..D\n..C.CE\nB....."
solution = "BAAAAA\nBEEDDD\nBEDDFD\nBEFFFD\nBECCCE\nBEEEEE"
with patch(
"space_app.verification.subprocess.run", wraps=subprocess.run
) as run_spy:
try:
result = verifier.verify(
puzzle_type="flow_free",
problem_ascii=problem,
board_ascii=solution,
args="6x6",
)
except BaseException as caught: # pragma: no cover - native best effort
self.skipTest(f"FlowFree solver unavailable locally: {caught}")
return
run_spy.assert_called_once()
self.assertTrue(result["correct"])
def test_flow_free_verification_timeout_returns_clean_error(self) -> None:
verifier = VerificationService()
with patch(
"space_app.verification.subprocess.run",
side_effect=subprocess.TimeoutExpired("cmd", 30),
):
result = verifier.verify(
puzzle_type="flow_free",
problem_ascii="A..A",
board_ascii="AAAA",
args="2x2",
)
self.assertFalse(result["correct"])
self.assertEqual(result.get("error"), "Flow_free verification timed out.")
def test_all_native_rlp_families_are_subprocess_isolated(self) -> None:
# Every puzzle family verified via the in-process RLP C library must be
# routed through the crash-isolating subprocess path. flow_free is
# excluded (it shells out to its own compiled solver binary).
from space_app.verification import SUBPROCESS_RLP_PUZZLES
for family in ("loopy", "bridges", "galaxies", "pattern", "undead"):
self.assertIn(family, SUBPROCESS_RLP_PUZZLES)
|