Skip to content

Commit 521c374

Browse files
committed
adding tests
1 parent b604c6e commit 521c374

4 files changed

Lines changed: 606 additions & 0 deletions

File tree

tests/__init__.py

Whitespace-only changes.

tests/test_parser.py

Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
"""Tests for AlphaBetaParser class (using mocks to avoid loading models)."""
2+
3+
import pytest
4+
from unittest.mock import patch, MagicMock
5+
6+
from hyperbase.hyperedge import hedge, UniqueAtom, build_atom
7+
8+
from hyperbase_parser_ab.parser import AlphaBetaParser
9+
10+
11+
class TestParserInitErrors:
12+
def test_unsupported_language_raises(self):
13+
with pytest.raises(RuntimeError, match="not recognized"):
14+
AlphaBetaParser('xx')
15+
16+
def test_unknown_beta_stage_raises(self):
17+
with patch('hyperbase_parser_ab.parser.get_spacy_models', return_value=['en_core_web_sm']), \
18+
patch('spacy.util.is_package', return_value=True), \
19+
patch('spacy.load', return_value=MagicMock()), \
20+
patch('hyperbase_parser_ab.parser.Alpha'):
21+
with pytest.raises(RuntimeError, match='unkown beta stage'):
22+
AlphaBetaParser('en', beta='invalid')
23+
24+
def test_no_spacy_model_installed_raises(self):
25+
with patch('hyperbase_parser_ab.parser.get_spacy_models', return_value=['en_core_web_trf']), \
26+
patch('spacy.util.is_package', return_value=False), \
27+
patch('hyperbase_parser_ab.parser.Alpha'):
28+
with pytest.raises(RuntimeError, match="requires one of the following"):
29+
AlphaBetaParser('en')
30+
31+
32+
def _make_parser(beta='repair'):
33+
"""Create a parser with mocked dependencies."""
34+
with patch('hyperbase_parser_ab.parser.get_spacy_models', return_value=['en_core_web_sm']), \
35+
patch('spacy.util.is_package', return_value=True), \
36+
patch('spacy.load', return_value=MagicMock()), \
37+
patch('hyperbase_parser_ab.parser.Alpha'):
38+
parser = AlphaBetaParser('en', beta=beta, normalize=True, post_process=True, debug=False)
39+
return parser
40+
41+
42+
class TestParserConfig:
43+
def test_default_config(self):
44+
parser = _make_parser()
45+
assert parser.lang == 'en'
46+
assert parser.normalize is True
47+
assert parser.post_process is True
48+
assert parser.debug is False
49+
assert parser.beta == 'repair'
50+
51+
def test_strict_mode(self):
52+
parser = _make_parser(beta='strict')
53+
assert parser.beta == 'strict'
54+
# strict rules are different from repair rules
55+
from hyperbase_parser_ab.rules import strict_rules
56+
assert parser.rules is strict_rules
57+
58+
59+
class TestParserNormalize:
60+
def test_normalize_modifier_on_predicate(self):
61+
"""When a modifier wraps a predicate relation, move modifier to inner connector."""
62+
parser = _make_parser()
63+
# (M (P C C)) should become ((M P) C C)
64+
edge = hedge('(quickly/M (runs/Pd/en cat/Cc/en dog/Cc/en))')
65+
result = parser._normalize(edge)
66+
assert result
67+
# The modifier should be merged with the inner predicate connector
68+
assert result[0].not_atom # connector should be (quickly/M runs/Pd/en)
69+
assert str(result[0][0]) == 'quickly/M'
70+
assert str(result[0][1]) == 'runs/Pd/en'
71+
72+
def test_normalize_atom_unchanged(self):
73+
parser = _make_parser()
74+
atom = hedge('cat/Cc/en')
75+
assert parser._normalize(atom) == atom
76+
77+
def test_normalize_non_modifier_unchanged(self):
78+
parser = _make_parser()
79+
edge = hedge('(runs/Pd/en cat/Cc/en dog/Cc/en)')
80+
result = parser._normalize(edge)
81+
assert result == edge
82+
83+
84+
class TestParserRepair:
85+
def test_repair_atom_unchanged(self):
86+
parser = _make_parser()
87+
atom = hedge('cat/Cc/en')
88+
assert parser._repair(atom) == atom
89+
90+
def test_repair_normal_edge_unchanged(self):
91+
parser = _make_parser()
92+
edge = hedge('(runs/Pd/en cat/Cc/en)')
93+
result = parser._repair(edge)
94+
assert result == edge
95+
96+
97+
class TestParserRelationArgRole:
98+
def _make_token(self, dep):
99+
token = MagicMock()
100+
token.dep_ = dep
101+
return token
102+
103+
def test_subject(self):
104+
parser = _make_parser()
105+
edge = hedge('cat/Cc/en')
106+
assert edge
107+
uatom = UniqueAtom(edge)
108+
token = self._make_token('nsubj')
109+
parser.atom2token = {uatom: token}
110+
parser.orig_atom = {uatom: uatom}
111+
parser.depths = {uatom: 1}
112+
assert parser._relation_arg_role(edge) == 's'
113+
114+
def test_object(self):
115+
parser = _make_parser()
116+
edge = hedge('cat/Cc/en')
117+
assert edge
118+
uatom = UniqueAtom(edge)
119+
token = self._make_token('dobj')
120+
parser.atom2token = {uatom: token}
121+
parser.orig_atom = {uatom: uatom}
122+
parser.depths = {uatom: 1}
123+
assert parser._relation_arg_role(edge) == 'o'
124+
125+
def test_passive_subject(self):
126+
parser = _make_parser()
127+
edge = hedge('cat/Cc/en')
128+
assert edge
129+
uatom = UniqueAtom(edge)
130+
token = self._make_token('nsubjpass')
131+
parser.atom2token = {uatom: token}
132+
parser.orig_atom = {uatom: uatom}
133+
parser.depths = {uatom: 1}
134+
assert parser._relation_arg_role(edge) == 'p'
135+
136+
def test_indirect_object(self):
137+
parser = _make_parser()
138+
edge = hedge('cat/Cc/en')
139+
assert edge
140+
uatom = UniqueAtom(edge)
141+
token = self._make_token('iobj')
142+
parser.atom2token = {uatom: token}
143+
parser.orig_atom = {uatom: uatom}
144+
parser.depths = {uatom: 1}
145+
assert parser._relation_arg_role(edge) == 'i'
146+
147+
def test_specifier(self):
148+
parser = _make_parser()
149+
edge = hedge('cat/Cc/en')
150+
assert edge
151+
uatom = UniqueAtom(edge)
152+
token = self._make_token('prep')
153+
parser.atom2token = {uatom: token}
154+
parser.orig_atom = {uatom: uatom}
155+
parser.depths = {uatom: 1}
156+
assert parser._relation_arg_role(edge) == 'x'
157+
158+
def test_unknown_dep(self):
159+
parser = _make_parser()
160+
edge = hedge('cat/Cc/en')
161+
assert edge
162+
uatom = UniqueAtom(edge)
163+
token = self._make_token('unknown_dep')
164+
parser.atom2token = {uatom: token}
165+
parser.orig_atom = {uatom: uatom}
166+
parser.depths = {uatom: 1}
167+
assert parser._relation_arg_role(edge) == '?'
168+
169+
def test_clausal_complement(self):
170+
parser = _make_parser()
171+
edge = hedge('go/P/en')
172+
assert edge
173+
uatom = UniqueAtom(edge)
174+
token = self._make_token('xcomp')
175+
parser.atom2token = {uatom: token}
176+
parser.orig_atom = {uatom: uatom}
177+
parser.depths = {uatom: 1}
178+
assert parser._relation_arg_role(edge) == 'r'
179+
180+
181+
class TestParserBuilderArgRoles:
182+
@staticmethod
183+
def _setup_edge_with_depths(parser, edge, depth_map):
184+
"""Set up parser state using the actual atom objects from the edge."""
185+
from hyperbase.hyperedge import unique
186+
atoms = edge.all_atoms()
187+
for atom in atoms:
188+
uatom = unique(atom)
189+
label = str(atom).split('/')[0]
190+
if label in depth_map:
191+
parser.atom2token[uatom] = MagicMock()
192+
parser.orig_atom[uatom] = uatom
193+
parser.depths[uatom] = depth_map[label]
194+
195+
def test_main_before_arg(self):
196+
"""Lower depth first → 'ma'."""
197+
parser = _make_parser()
198+
edge = hedge('(of/Br/en paris/Cp/en france/Cp/en)')
199+
self._setup_edge_with_depths(parser, edge, {'of': 2, 'paris': 1, 'france': 3})
200+
assert parser._builder_arg_roles(edge) == 'ma'
201+
202+
def test_arg_before_main(self):
203+
"""Lower depth second → 'am'."""
204+
parser = _make_parser()
205+
edge = hedge('(of/Br/en paris/Cp/en france/Cp/en)')
206+
self._setup_edge_with_depths(parser, edge, {'of': 2, 'paris': 3, 'france': 1})
207+
assert parser._builder_arg_roles(edge) == 'am'
208+
209+
def test_equal_depth(self):
210+
"""Equal depths → 'mm'."""
211+
parser = _make_parser()
212+
edge = hedge('(of/Br/en paris/Cp/en france/Cp/en)')
213+
self._setup_edge_with_depths(parser, edge, {'of': 1, 'paris': 2, 'france': 2})
214+
assert parser._builder_arg_roles(edge) == 'mm'
215+
216+
217+
class TestParserDebug:
218+
def test_debug_msg_when_enabled(self, capsys):
219+
parser = _make_parser()
220+
parser.debug = True
221+
parser.debug_msg('test message')
222+
assert 'test message' in capsys.readouterr().out
223+
224+
def test_debug_msg_when_disabled(self, capsys):
225+
parser = _make_parser()
226+
parser.debug = False
227+
# Clear any output from parser construction
228+
capsys.readouterr()
229+
parser.debug_msg('test message')
230+
assert capsys.readouterr().out == ''
231+
232+
233+
class TestParserReset:
234+
def test_reset_clears_state(self):
235+
parser = _make_parser()
236+
parser.temp_atoms.add('dummy')
237+
parser.orig_atom['dummy'] = 'dummy'
238+
parser.reset('new text')
239+
assert parser.cur_text == 'new text'
240+
assert len(parser.temp_atoms) == 0
241+
assert len(parser.orig_atom) == 0
242+
assert len(parser.edge2toks) == 0
243+
assert len(parser.toks2edge) == 0

0 commit comments

Comments
 (0)