Skip to content

Commit

Permalink
Add MockAligner class to test utils
Browse files Browse the repository at this point in the history
  • Loading branch information
Donaim committed Nov 7, 2023
1 parent a612a4c commit bf2e50e
Show file tree
Hide file tree
Showing 2 changed files with 131 additions and 0 deletions.
75 changes: 75 additions & 0 deletions micall/tests/test_tests_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@

import pytest
from micall.tests.utils import MockAligner, MockAlignment

def test_basic_mapping():

aligner = MockAligner('acgt' + 'a' * 20 + 'acgt')

alignment = list(aligner.map('a' * 10))

assert len(alignment) == 5

alignment = alignment[0]

assert isinstance(alignment, MockAlignment)
assert alignment.mapq == 60
assert alignment.is_rev == False
assert alignment.r_st == 4
assert alignment.r_en == 14
assert alignment.q_st == 0
assert alignment.q_en == 10


def test_exact_match():
aligner = MockAligner("abcdefg")
alignments = list(aligner.map("abc"))
assert len(alignments) == 1
assert alignments[0].r_st == 0
assert alignments[0].r_en == 3


def test_no_match():
aligner = MockAligner("abcdefg")
alignments = list(aligner.map("xyz"))
assert len(alignments) == 0


def test_partial_match():
aligner = MockAligner("abcdefg")
alignments = list(aligner.map("abxyabc"))
assert len(alignments) == 1
assert alignments[0].r_st == 0
assert alignments[0].r_en == 3


def test_multiple_matches():
aligner = MockAligner("A" * 40)
alignments = list(aligner.map("A" * 20))
assert len(alignments) == 5
assert alignments[0].r_st == 0
assert alignments[0].r_en == 20
assert alignments[1].r_st == 0
assert alignments[1].r_en == 19


def test_multiple_matches_bigger_query():
aligner = MockAligner("A" * 40)
alignments = list(aligner.map("A" * 50))
assert len(alignments) == 5
assert alignments[0].r_st == 0
assert alignments[0].r_en == 40
assert alignments[1].r_st == 0
assert alignments[1].r_en == 40


def test_empty_reference():
aligner = MockAligner("A" * 0)
alignments = list(aligner.map("A" * 20))
assert len(alignments) == 0


def test_empty_query():
aligner = MockAligner("A" * 40)
alignments = list(aligner.map("A" * 0))
assert len(alignments) == 0
56 changes: 56 additions & 0 deletions micall/tests/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
from dataclasses import dataclass
from math import floor, ceil

from micall.utils.consensus_aligner import CigarActions


@dataclass
class MockAlignment:
is_rev: bool
mapq: int
cigar: list
cigar_str: str
q_st: int
q_en: int
r_st: int
r_en: int


class MockAligner:
"""
Mock for the mappy's aligner class.
Only reports exact matches.
"""

def __init__(self, seq, *args, **kwargs):
self.seq = seq
self.max_matches = 5
self.min_length = 3


def map(self, seq):
max_matches = self.max_matches
returned = set()
for length in range(len(seq), self.min_length - 2, -1):
for start in range(len(seq) - length):
end = start + length
substring = seq[start:end+1]
if substring not in self.seq:
continue

mapq = 60
is_rev = False # Doesn't handle reverse complements in this mock.
r_st = self.seq.index(substring)
r_en = r_st + len(substring)
q_st = start
q_en = end + 1
cigar = [[q_en - q_st, CigarActions.MATCH]]
cigar_str = f'{(q_en - q_st)}M'
al = MockAlignment(is_rev, mapq, cigar, cigar_str, q_st, q_en, r_st, r_en)
if (q_st, q_en, r_st, r_en) not in returned:
returned.add((q_st, q_en, r_st, r_en))
yield MockAlignment(is_rev, mapq, cigar, cigar_str, q_st, q_en, r_st, r_en)

max_matches -= 1
if max_matches < 1:
return

0 comments on commit bf2e50e

Please sign in to comment.