Skip to content

Commit dd648c8

Browse files
committed
more tests
1 parent 14d6f2f commit dd648c8

6 files changed

Lines changed: 1050 additions & 1 deletion

File tree

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ target-version = "py310"
7474
select = ["E", "F", "W", "I", "UP", "B", "SIM", "RUF", "Q", "C4", "PT", "N", "ANN"]
7575

7676
[tool.ruff.lint.per-file-ignores]
77-
"tests/*" = ["E501", "ANN201", "D100", "D101", "D102", "D400", "D415"]
77+
"tests/*" = ["ANN001", "ANN003", "ANN201", "ANN202", "ANN204", "ANN205", "D100", "D101", "D102", "D400", "D415", "E501", "N802", "PT011"]
7878

7979
[tool.ruff.lint.flake8-quotes]
8080
inline-quotes = "double"

tests/test_correctness_errors.py

Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
"""Error-path tests for check_correctness.
2+
3+
Tests truly broken structures that exercise every error branch in
4+
correctness.py, including malformed edges, invalid types, wrong arities,
5+
and recursive error propagation.
6+
"""
7+
8+
from hyperbase.builders import hedge
9+
from hyperbase.correctness import check_correctness
10+
from hyperbase.hyperedge import Atom, Hyperedge
11+
12+
13+
class TestAtomTypeErrors:
14+
"""check_correctness on atoms with invalid types."""
15+
16+
def test_invalid_atom_type_R(self):
17+
atom = Atom("thing/R")
18+
errors = check_correctness(atom)
19+
assert atom in errors
20+
codes = [e[0] for e in errors[atom]]
21+
assert "bad-atom-type" in codes
22+
23+
def test_invalid_atom_type_S(self):
24+
atom = Atom("thing/S")
25+
errors = check_correctness(atom)
26+
assert atom in errors
27+
codes = [e[0] for e in errors[atom]]
28+
assert "bad-atom-type" in codes
29+
30+
def test_valid_atom_types_produce_no_errors(self):
31+
for t in ["C", "P", "M", "B", "T", "J"]:
32+
atom = Atom(f"thing/{t}")
33+
errors = check_correctness(atom)
34+
assert atom not in errors, f"Type {t} should be valid"
35+
36+
def test_completely_unknown_type(self):
37+
atom = Atom("thing/Z")
38+
errors = check_correctness(atom)
39+
assert atom in errors
40+
41+
42+
class TestConnectorTypeErrors:
43+
"""Edges whose connector has an invalid type."""
44+
45+
def test_concept_as_connector(self):
46+
"""Concept atom as connector should fail."""
47+
edge = hedge("(bob/C alice/C)")
48+
errors = check_correctness(edge)
49+
assert edge in errors
50+
codes = [e[0] for e in errors[edge]]
51+
assert "conn-bad-type" in codes
52+
53+
def test_relation_as_connector(self):
54+
"""R-type as connector is not valid for edge connector."""
55+
# Build manually since R isn't a valid connector
56+
edge = Hyperedge((Atom("rel/R"), Atom("a/C")))
57+
errors = check_correctness(edge)
58+
assert edge in errors
59+
codes = [e[0] for e in errors[edge]]
60+
assert "conn-bad-type" in codes
61+
62+
63+
class TestModifierErrors:
64+
"""Modifier edges must have exactly 1 argument."""
65+
66+
def test_modifier_zero_args(self):
67+
"""(mod/M) — modifier with no argument."""
68+
edge = Hyperedge((Atom("big/M"),))
69+
errors = check_correctness(edge)
70+
assert edge in errors
71+
codes = [e[0] for e in errors[edge]]
72+
assert "mod-1-arg" in codes
73+
74+
def test_modifier_two_args(self):
75+
"""(mod/M a/C b/C) — modifier with two arguments."""
76+
edge = hedge("(big/M thing/C other/C)")
77+
errors = check_correctness(edge)
78+
assert edge in errors
79+
codes = [e[0] for e in errors[edge]]
80+
assert "mod-1-arg" in codes
81+
82+
def test_modifier_one_arg_valid(self):
83+
"""(mod/M a/C) — valid modifier."""
84+
edge = hedge("(big/M thing/C)")
85+
errors = check_correctness(edge)
86+
assert edge not in errors
87+
88+
89+
class TestBuilderErrors:
90+
"""Builder edges must have exactly 2 arguments, both of type C."""
91+
92+
def test_builder_one_arg(self):
93+
edge = hedge("(+/B.m a/C)")
94+
errors = check_correctness(edge)
95+
assert edge in errors
96+
codes = [e[0] for e in errors[edge]]
97+
assert "build-2-args" in codes
98+
99+
def test_builder_three_args(self):
100+
edge = hedge("(+/B.ma a/C b/C c/C)")
101+
errors = check_correctness(edge)
102+
assert edge in errors
103+
codes = [e[0] for e in errors[edge]]
104+
assert "build-2-args" in codes
105+
106+
def test_builder_wrong_arg_type(self):
107+
"""Builder with a predicate argument instead of concept."""
108+
edge = hedge("(+/B.ma is/P a/C)")
109+
errors = check_correctness(edge)
110+
assert edge in errors
111+
codes = [e[0] for e in errors[edge]]
112+
assert "build-arg-bad-type" in codes
113+
114+
def test_builder_valid(self):
115+
edge = hedge("(+/B.ma a/C b/C)")
116+
errors = check_correctness(edge)
117+
assert edge not in errors
118+
119+
120+
class TestTriggerErrors:
121+
"""Trigger edges must have exactly 1 argument of type C or R."""
122+
123+
def test_trigger_zero_args(self):
124+
edge = Hyperedge((Atom("to/T"),))
125+
errors = check_correctness(edge)
126+
assert edge in errors
127+
codes = [e[0] for e in errors[edge]]
128+
assert "trig-1-arg" in codes
129+
130+
def test_trigger_two_args(self):
131+
edge = hedge("(to/T place/C time/C)")
132+
errors = check_correctness(edge)
133+
assert edge in errors
134+
codes = [e[0] for e in errors[edge]]
135+
assert "trig-1-arg" in codes
136+
137+
def test_trigger_wrong_arg_type(self):
138+
"""Trigger with a predicate argument."""
139+
edge = hedge("(to/T is/P)")
140+
errors = check_correctness(edge)
141+
assert edge in errors
142+
codes = [e[0] for e in errors[edge]]
143+
assert "trig-bad-arg-type" in codes
144+
145+
def test_trigger_valid_concept(self):
146+
edge = hedge("(to/T place/C)")
147+
errors = check_correctness(edge)
148+
assert edge not in errors
149+
150+
def test_trigger_valid_relation(self):
151+
"""Trigger with R-type argument should be valid."""
152+
edge = Hyperedge((Atom("to/T"), hedge("(is/P.s bob/C)")))
153+
errors = check_correctness(edge)
154+
# The trigger itself should have no errors
155+
codes = [e[0] for e in errors.get(edge, [])]
156+
assert "trig-bad-arg-type" not in codes
157+
158+
159+
class TestPredicateErrors:
160+
"""Predicate argument type checking."""
161+
162+
def test_predicate_modifier_arg(self):
163+
"""Predicate with a modifier argument (M is not C/R/S)."""
164+
edge = hedge("(is/P.s big/M)")
165+
errors = check_correctness(edge)
166+
assert edge in errors
167+
codes = [e[0] for e in errors[edge]]
168+
assert "pred-arg-bad-type" in codes
169+
170+
def test_predicate_valid_args(self):
171+
"""Predicate with C and R args should be valid."""
172+
edge = hedge("(is/P.sc bob/C happy/C)")
173+
errors = check_correctness(edge)
174+
assert edge not in errors
175+
176+
177+
class TestConjunctionErrors:
178+
"""Conjunction edges must have at least 2 arguments."""
179+
180+
def test_conjunction_one_arg(self):
181+
edge = hedge("(and/J bob/C)")
182+
errors = check_correctness(edge)
183+
assert edge in errors
184+
codes = [e[0] for e in errors[edge]]
185+
assert "conj-2-args-min" in codes
186+
187+
def test_conjunction_two_args_valid(self):
188+
edge = hedge("(and/J bob/C alice/C)")
189+
errors = check_correctness(edge)
190+
assert edge not in errors
191+
192+
193+
class TestArgroleErrors:
194+
"""Argrole validation on predicates and builders."""
195+
196+
def test_predicate_invalid_argrole(self):
197+
"""'z' is not a valid predicate argrole."""
198+
edge = hedge("(is/P.z bob/C)")
199+
errors = check_correctness(edge)
200+
assert edge in errors
201+
codes = [e[0] for e in errors[edge]]
202+
assert "pred-bad-arg-role" in codes
203+
204+
def test_builder_invalid_argrole(self):
205+
"""'s' is not a valid builder argrole (only m and a)."""
206+
edge = hedge("(+/B.sa a/C b/C)")
207+
errors = check_correctness(edge)
208+
assert edge in errors
209+
codes = [e[0] for e in errors[edge]]
210+
assert "build-bad-arg-role" in codes
211+
212+
def test_argrole_count_mismatch(self):
213+
"""Argrole count doesn't match argument count."""
214+
edge = hedge("(is/P.sco bob/C happy/C)")
215+
errors = check_correctness(edge)
216+
assert edge in errors
217+
codes = [e[0] for e in errors[edge]]
218+
assert "bad-num-argroles" in codes
219+
220+
def test_duplicate_special_argrole(self):
221+
"""Duplicate 's' argrole should be flagged."""
222+
edge = hedge("(is/P.ss bob/C alice/C)")
223+
errors = check_correctness(edge)
224+
assert edge in errors
225+
codes = [e[0] for e in errors[edge]]
226+
assert "argrole-s-1-max" in codes
227+
228+
def test_no_argroles_on_predicate(self):
229+
"""Predicate without argroles should be flagged."""
230+
edge = hedge("(is/P bob/C)")
231+
errors = check_correctness(edge)
232+
assert edge in errors
233+
codes = [e[0] for e in errors[edge]]
234+
assert "no-argroles" in codes
235+
236+
237+
class TestRecursiveErrorPropagation:
238+
"""Errors in deeply nested subedges should propagate to root."""
239+
240+
def test_error_in_nested_subedge(self):
241+
"""A broken inner edge should appear in the error dict."""
242+
# Inner: (big/M a/C b/C) — modifier with 2 args
243+
edge = hedge("(is/P.sc (big/M a/C b/C) happy/C)")
244+
errors = check_correctness(edge)
245+
inner = hedge("(big/M a/C b/C)")
246+
assert inner in errors
247+
codes = [e[0] for e in errors[inner]]
248+
assert "mod-1-arg" in codes
249+
250+
def test_multiple_errors_in_tree(self):
251+
"""Multiple broken subedges should all appear."""
252+
# Outer: predicate with bad arg type (M)
253+
# Inner: builder with 3 args
254+
edge = hedge("(is/P.sc (+/B.ma a/C b/C c/C) happy/C)")
255+
errors = check_correctness(edge)
256+
# The outer edge should flag pred-arg-bad-type for the builder connector
257+
# The inner builder should flag build-2-args
258+
all_codes = set()
259+
for errs in errors.values():
260+
for e in errs:
261+
all_codes.add(e[0])
262+
assert "build-2-args" in all_codes
263+
264+
def test_deeply_nested_error(self):
265+
"""Error 5 levels deep should still be found."""
266+
edge = hedge(
267+
"(is/P.sc (the/M (big/M (very/M (super/M (bad/M a/C b/C))))) happy/C)"
268+
)
269+
errors = check_correctness(edge)
270+
# The innermost (bad/M a/C b/C) has too many args
271+
all_codes = set()
272+
for errs in errors.values():
273+
for e in errs:
274+
all_codes.add(e[0])
275+
assert "mod-1-arg" in all_codes

0 commit comments

Comments
 (0)