Skip to content

Commit faa550f

Browse files
Add Python C extension tests for _tsinfermodule.c coverage (80% → 86%)
1 parent cbbde81 commit faa550f

1 file changed

Lines changed: 117 additions & 0 deletions

File tree

tests/test_python_c.py

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,106 @@ def test_mean_traceback_size(self):
195195
assert isinstance(tb, float)
196196
assert tb >= 0
197197

198+
def test_init_bad_recombination(self):
199+
ts, mi, _ = make_matcher_indexes_and_matcher()
200+
mismatch = np.array([0.0])
201+
with pytest.raises(ValueError):
202+
_tsinfer.AncestorMatcher(mi, recombination="bad", mismatch=mismatch)
203+
with pytest.raises(ValueError, match="Size of recombination"):
204+
_tsinfer.AncestorMatcher(
205+
mi, recombination=np.array([1e-9, 1e-9]), mismatch=mismatch
206+
)
207+
208+
def test_init_bad_mismatch(self):
209+
ts, mi, _ = make_matcher_indexes_and_matcher()
210+
recombination = np.array([1e-9])
211+
with pytest.raises(ValueError):
212+
_tsinfer.AncestorMatcher(mi, recombination=recombination, mismatch="bad")
213+
with pytest.raises(ValueError, match="Size of mismatch"):
214+
_tsinfer.AncestorMatcher(
215+
mi, recombination=recombination, mismatch=np.array([0.0, 0.0])
216+
)
217+
218+
def test_init_extended_checks(self):
219+
ts, mi, _ = make_matcher_indexes_and_matcher()
220+
am = _tsinfer.AncestorMatcher(
221+
mi,
222+
recombination=np.array([1e-9]),
223+
mismatch=np.array([0.0]),
224+
extended_checks=True,
225+
)
226+
assert am is not None
227+
228+
def test_init_weight_by_n_false(self):
229+
ts, mi, _ = make_matcher_indexes_and_matcher()
230+
am = _tsinfer.AncestorMatcher(
231+
mi,
232+
recombination=np.array([1e-9]),
233+
mismatch=np.array([0.0]),
234+
weight_by_n=False,
235+
)
236+
assert am is not None
237+
238+
def test_find_path_bad_haplotype(self):
239+
ts, _, am = make_matcher_indexes_and_matcher()
240+
with pytest.raises(ValueError):
241+
am.find_path("not an array", 0, ts.num_sites)
242+
with pytest.raises(ValueError, match="Dim != 1"):
243+
am.find_path(np.zeros((1, 1), dtype=np.int8), 0, ts.num_sites)
244+
with pytest.raises(ValueError, match="Incorrect size"):
245+
am.find_path(np.zeros(100, dtype=np.int8), 0, ts.num_sites)
246+
247+
def test_find_path_returns_values(self):
248+
ts, _, am = make_matcher_indexes_and_matcher()
249+
h = np.zeros(ts.num_sites, dtype=np.int8)
250+
result = am.find_path(h, 0, ts.num_sites)
251+
assert len(result) == 5
252+
num_edges, left, right, parent, match = result
253+
assert isinstance(num_edges, int)
254+
assert left.dtype == np.uint32
255+
assert right.dtype == np.uint32
256+
assert parent.dtype == np.int32
257+
assert match.dtype == np.int8
258+
259+
def test_find_path_match_impossible(self):
260+
# mu=0 with a mismatching haplotype makes p_e=0 for all nodes,
261+
# triggering TSI_ERR_MATCH_IMPOSSIBLE_EXTREME_MUTATION_PROBA.
262+
# Build a tree sequence where all nodes carry allele 0 at a site
263+
# (no mutations), then query h=[1] so nothing matches.
264+
tables = tskit.Tree.generate_balanced(4).tree_sequence.dump_tables()
265+
tables.sequence_length = 2
266+
tables.edges.right = np.full(len(tables.edges), 2, dtype=np.float64)
267+
tables.sites.add_row(position=1, ancestral_state="A")
268+
# No mutations: all nodes carry allele 0
269+
ts = tables.tree_sequence()
270+
ts = matching.add_vestigial_root(ts)
271+
ll_tables = _tsinfer.LightweightTableCollection(ts.sequence_length)
272+
ll_tables.fromdict(ts.dump_tables().asdict())
273+
mi = _tsinfer.MatcherIndexes(ll_tables)
274+
am = _tsinfer.AncestorMatcher(
275+
mi,
276+
recombination=np.array([1e-9]),
277+
mismatch=np.array([0.0]),
278+
)
279+
h = np.array([1], dtype=np.int8)
280+
with pytest.raises(_tsinfer.MatchImpossible):
281+
am.find_path(h, 0, ts.num_sites)
282+
283+
def test_get_traceback(self):
284+
ts, _, am = make_matcher_indexes_and_matcher()
285+
h = np.zeros(ts.num_sites, dtype=np.int8)
286+
am.find_path(h, 0, ts.num_sites)
287+
for site in range(ts.num_sites):
288+
tb = am.get_traceback(site)
289+
assert isinstance(tb, dict)
290+
291+
def test_get_traceback_bad_site(self):
292+
ts, _, am = make_matcher_indexes_and_matcher()
293+
h = np.zeros(ts.num_sites, dtype=np.int8)
294+
am.find_path(h, 0, ts.num_sites)
295+
with pytest.raises(ValueError, match="site out of range"):
296+
am.get_traceback(ts.num_sites)
297+
198298

199299
class TestMatcherIndexes:
200300
def test_single_tree(self):
@@ -204,6 +304,14 @@ def test_single_tree(self):
204304
ll_tables.fromdict(tables.asdict())
205305
_ = _tsinfer.MatcherIndexes(ll_tables)
206306

307+
def test_num_alleles(self):
308+
ts, mi, _ = make_matcher_indexes_and_matcher()
309+
num_alleles = np.array([2] * ts.num_sites, dtype=np.uint64)
310+
ll_tables = _tsinfer.LightweightTableCollection(ts.sequence_length)
311+
ll_tables.fromdict(ts.dump_tables().asdict())
312+
mi = _tsinfer.MatcherIndexes(ll_tables, num_alleles=num_alleles)
313+
assert mi is not None
314+
207315
def test_print_state(self, tmpdir):
208316
ts = tskit.Tree.generate_balanced(4).tree_sequence
209317
tables = ts.dump_tables()
@@ -220,3 +328,12 @@ def test_print_state(self, tmpdir):
220328
output = f.read()
221329
assert len(output) > 0
222330
assert "indexes" in output
331+
332+
def test_print_state_bad_file(self):
333+
ts = tskit.Tree.generate_balanced(4).tree_sequence
334+
tables = ts.dump_tables()
335+
ll_tables = _tsinfer.LightweightTableCollection(tables.sequence_length)
336+
ll_tables.fromdict(tables.asdict())
337+
mi = _tsinfer.MatcherIndexes(ll_tables)
338+
with pytest.raises(TypeError):
339+
mi.print_state("not a file")

0 commit comments

Comments
 (0)