1# This code is part of the Biopython distribution and governed by its
2# license.  Please see the LICENSE file that should have been included
3# as part of this package.
4#
5
6"""Testing code for Restriction enzyme classes of Biopython."""
7import unittest
8
9from Bio.Restriction import Analysis, Restriction, RestrictionBatch
10from Bio.Restriction import CommOnly, NonComm, AllEnzymes
11from Bio.Restriction import (
12    Acc65I,
13    Asp718I,
14    BamHI,
15    EcoRI,
16    EcoRV,
17    KpnI,
18    SmaI,
19    MluCI,
20    McrI,
21    NdeI,
22    BsmBI,
23    AanI,
24    EarI,
25    SnaI,
26    SphI,
27)
28from Bio.Restriction import FormattedSeq
29from Bio.Seq import Seq, MutableSeq
30from Bio import BiopythonWarning
31
32
33class SequenceTesting(unittest.TestCase):
34    """Tests for dealing with input."""
35
36    def test_sequence_object(self):
37        """Test if sequence must be a Seq or MutableSeq object."""
38        with self.assertRaises(TypeError):
39            seq = FormattedSeq("GATC")
40        seq = FormattedSeq(Seq("TAGC"))
41        seq = FormattedSeq(MutableSeq("AGTC"))
42        seq = FormattedSeq(seq)
43        with self.assertRaises(TypeError):
44            EcoRI.search("GATC")
45        EcoRI.search(Seq("ATGC"))
46        EcoRI.search(MutableSeq("TCAG"))
47
48    def test_non_iupac_letters(self):
49        """Test if non-IUPAC letters raise a TypeError."""
50        with self.assertRaises(TypeError):
51            seq = FormattedSeq(Seq("GATCZ"))
52
53    def test_formatted_seq(self):
54        """Test several methods of FormattedSeq."""
55        self.assertEqual(
56            str(FormattedSeq(Seq("GATC"))), "FormattedSeq(Seq('GATC'), linear=True)"
57        )
58        self.assertNotEqual(FormattedSeq(Seq("GATC")), FormattedSeq(Seq("TAGC")))
59        self.assertNotEqual(FormattedSeq(Seq("TAGC")), Seq("TAGC"))
60        self.assertEqual(FormattedSeq(Seq("ATGC")), FormattedSeq(Seq("ATGC")))
61        linear_seq = FormattedSeq(Seq("T"))
62        self.assertTrue(linear_seq.is_linear())
63        linear_seq.circularise()
64        self.assertFalse(linear_seq.is_linear())
65        linear_seq.linearise()
66        circular_seq = linear_seq.to_circular()
67        self.assertFalse(circular_seq.is_linear())
68        linear_seq = circular_seq.to_linear()
69        self.assertTrue(linear_seq.is_linear())
70
71
72class SimpleEnzyme(unittest.TestCase):
73    """Tests for dealing with basic enzymes using the Restriction package."""
74
75    def test_init(self):
76        """Check for error during __init__."""
77        with self.assertRaises(ValueError) as ve:
78            Restriction.OneCut("bla-me", (Restriction.RestrictionType,), {})
79            self.assertIn("hyphen", str(ve.exception))
80
81    def setUp(self):
82        """Set up some sequences for later use."""
83        base_seq = Seq("AAAA")
84        self.ecosite_seq = base_seq + Seq(EcoRI.site) + base_seq
85        self.smasite_seq = base_seq + Seq(SmaI.site) + base_seq
86        self.kpnsite_seq = base_seq + Seq(KpnI.site) + base_seq
87
88    def test_eco_cutting(self):
89        """Test basic cutting with EcoRI (5'overhang)."""
90        self.assertEqual(EcoRI.site, "GAATTC")
91        self.assertTrue(EcoRI.cut_once())
92        self.assertFalse(EcoRI.is_blunt())
93        self.assertTrue(EcoRI.is_5overhang())
94        self.assertFalse(EcoRI.is_3overhang())
95        self.assertEqual(EcoRI.overhang(), "5' overhang")
96        self.assertTrue(EcoRI.is_defined())
97        self.assertFalse(EcoRI.is_ambiguous())
98        self.assertFalse(EcoRI.is_unknown())
99        self.assertTrue(EcoRI.is_palindromic())
100        self.assertTrue(EcoRI.is_comm())
101        self.assertIn("Life Technologies", EcoRI.supplier_list())
102        self.assertEqual(EcoRI.elucidate(), "G^AATT_C")
103        self.assertEqual(EcoRI.search(self.ecosite_seq), [6])
104        self.assertEqual(EcoRI.characteristic(), (1, -1, None, None, "GAATTC"))
105
106        parts = EcoRI.catalyse(self.ecosite_seq)
107        self.assertEqual(len(parts), 2)
108        self.assertEqual(str(parts[1]), "AATTCAAAA")
109        parts = EcoRI.catalyze(self.ecosite_seq)
110        self.assertEqual(len(parts), 2)
111
112    def test_kpn_cutting(self):
113        """Test basic cutting with KpnI (3'overhang)."""
114        self.assertTrue(KpnI.is_3overhang())
115        self.assertFalse(KpnI.is_5overhang())
116        self.assertFalse(KpnI.is_blunt())
117        self.assertEqual(KpnI.overhang(), "3' overhang")
118        parts = KpnI.catalyse(self.kpnsite_seq)
119        self.assertEqual(len(parts), 2)
120        self.assertEqual(
121            KpnI.catalyse(self.kpnsite_seq), KpnI.catalyze(self.kpnsite_seq)
122        )
123
124    def test_sma_cutting(self):
125        """Test basic cutting with SmaI (blunt cutter)."""
126        self.assertTrue(SmaI.is_blunt())
127        self.assertFalse(SmaI.is_3overhang())
128        self.assertFalse(SmaI.is_5overhang())
129        self.assertEqual(SmaI.overhang(), "blunt")
130        parts = SmaI.catalyse(self.smasite_seq)
131        self.assertEqual(len(parts), 2)
132        self.assertEqual(str(parts[1]), "GGGAAAA")
133        parts = SmaI.catalyze(self.smasite_seq)
134        self.assertEqual(len(parts), 2)
135
136    def test_ear_cutting(self):
137        """Test basic cutting with EarI (ambiguous overhang)."""
138        self.assertFalse(EarI.is_palindromic())
139        self.assertFalse(EarI.is_defined())
140        self.assertTrue(EarI.is_ambiguous())
141        self.assertFalse(EarI.is_unknown())
142        self.assertEqual(EarI.elucidate(), "CTCTTCN^NNN_N")
143
144    def test_sna_cutting(self):
145        """Test basic cutting with SnaI (unknown)."""
146        self.assertEqual(SnaI.elucidate(), "? GTATAC ?")
147        self.assertFalse(SnaI.is_defined())
148        self.assertFalse(SnaI.is_ambiguous())
149        self.assertTrue(SnaI.is_unknown())
150        self.assertFalse(SnaI.is_comm())
151        self.assertIsNone(SnaI.suppliers())
152        self.assertEqual(SnaI.supplier_list(), [])
153        with self.assertRaises(TypeError):
154            SnaI.buffers("no company")
155
156    def test_circular_sequences(self):
157        """Deal with cutting circular sequences."""
158        parts = EcoRI.catalyse(self.ecosite_seq, linear=False)
159        self.assertEqual(len(parts), 1)
160        locations = EcoRI.search(parts[0], linear=False)
161        self.assertEqual(locations, [1])
162
163        parts = KpnI.catalyse(self.kpnsite_seq, linear=False)
164        self.assertEqual(len(parts), 1)
165        locations = KpnI.search(parts[0], linear=False)
166        self.assertEqual(locations, [1])
167
168        parts = SmaI.catalyse(self.smasite_seq, linear=False)
169        self.assertEqual(len(parts), 1)
170        locations = SmaI.search(parts[0], linear=False)
171        self.assertEqual(locations, [1])
172
173        self.assertEqual(
174            EarI.search(FormattedSeq(Seq("CTCTTCAAAAA")), linear=False), [8]
175        )
176        self.assertEqual(
177            SnaI.search(FormattedSeq(Seq("GTATACAAAAA")), linear=False), [1]
178        )
179
180    def test_shortcuts(self):
181        """Check if '/' and '//' work as '.search' and '.catalyse'."""
182        self.assertEqual(EcoRI / self.ecosite_seq, [6])
183        self.assertEqual(self.ecosite_seq / EcoRI, [6])
184        self.assertEqual(len(EcoRI // self.ecosite_seq), 2)
185        self.assertEqual(len(self.ecosite_seq // EcoRI), 2)
186
187    def test_cutting_border_positions(self):
188        """Check if cutting after first and penultimate position works."""
189        # Use EarI, cuts as follows: CTCTTCN^NNN_N
190        seq = Seq("CTCTTCA")
191        self.assertEqual(EarI.search(seq), [])
192        seq += "A"
193        self.assertEqual(EarI.search(seq), [8])
194        # Recognition site on reverse-complement strand
195        seq = Seq("AAAAGAAGAG")
196        self.assertEqual(EarI.search(seq), [])
197        seq = "A" + seq
198        self.assertEqual(EarI.search(seq), [2])
199
200    def test_recognition_site_on_both_strands(self):
201        """Check if recognition sites on both strands are properly handled."""
202        seq = Seq("CTCTTCGAAGAG")
203        self.assertEqual(EarI.search(seq), [3, 8])
204
205    def test_overlapping_cut_sites(self):
206        """Check if overlapping recognition sites are properly handled."""
207        seq = Seq("CATGCACGCATGCATGCACGC")
208        self.assertEqual(SphI.search(seq), [13, 17])
209
210
211class EnzymeComparison(unittest.TestCase):
212    """Tests for comparing various enzymes."""
213
214    def test_basic_isochizomers(self):
215        """Test to be sure isochizomer and neoschizomers are as expected."""
216        self.assertEqual(Acc65I.isoschizomers(), [Asp718I, KpnI])
217        self.assertEqual(Acc65I.elucidate(), "G^GTAC_C")
218        self.assertEqual(Asp718I.elucidate(), "G^GTAC_C")
219        self.assertEqual(KpnI.elucidate(), "G_GTAC^C")
220        self.assertTrue(Acc65I.is_isoschizomer(KpnI))
221        self.assertFalse(Acc65I.is_equischizomer(KpnI))
222        self.assertTrue(Acc65I.is_neoschizomer(KpnI))
223        self.assertIn(Acc65I, Asp718I.equischizomers())
224        self.assertIn(KpnI, Asp718I.neoschizomers())
225        self.assertIn(KpnI, Acc65I.isoschizomers())
226
227    def test_comparisons(self):
228        """Test comparison operators between different enzymes."""
229        # Comparison of iso- and neoschizomers
230        self.assertEqual(Acc65I, Acc65I)
231        self.assertNotEqual(Acc65I, KpnI)
232        self.assertFalse(Acc65I == Asp718I)  # noqa: A500
233        # self.assertNotEqual(Acc65I, Asp718I) it doesn't work as expected
234        self.assertFalse(Acc65I != Asp718I)  # noqa: A500
235        self.assertNotEqual(Acc65I, EcoRI)
236        self.assertTrue(Acc65I >> KpnI)
237        self.assertFalse(Acc65I >> Asp718I)
238
239        # Compare length of recognition sites
240        self.assertFalse(EcoRI >= EcoRV)
241        self.assertGreaterEqual(EcoRV, EcoRI)
242        with self.assertRaises(NotImplementedError):
243            EcoRV >= 3
244        self.assertFalse(EcoRI > EcoRV)
245        self.assertGreater(EcoRV, EcoRI)
246        with self.assertRaises(NotImplementedError):
247            EcoRV > 3
248        self.assertLessEqual(EcoRI, EcoRV)
249        self.assertFalse(EcoRV <= EcoRI)
250        with self.assertRaises(NotImplementedError):
251            EcoRV <= 3
252        self.assertLess(EcoRI, EcoRV)
253        self.assertFalse(EcoRV < EcoRI)
254        with self.assertRaises(NotImplementedError):
255            EcoRV < 3
256
257        # Compare compatible overhangs
258        self.assertTrue(Acc65I % Asp718I)
259        self.assertTrue(Acc65I % Acc65I)
260        self.assertFalse(Acc65I % KpnI)
261        with self.assertRaises(TypeError):
262            Acc65I % "KpnI"
263        self.assertTrue(SmaI % EcoRV)
264        self.assertTrue(EarI % EarI)
265        self.assertIn(EcoRV, SmaI.compatible_end())
266        self.assertIn(Acc65I, Asp718I.compatible_end())
267
268
269class RestrictionBatchPrintTest(unittest.TestCase):
270    """Tests Restriction.Analysis printing functionality."""
271
272    def createAnalysis(self, seq_str, batch_ary):
273        """Restriction.Analysis creation helper method."""
274        rb = Restriction.RestrictionBatch(batch_ary)
275        seq = Seq(seq_str)
276        return Restriction.Analysis(rb, seq)
277
278    def assertAnalysisFormat(self, analysis, expected):
279        """Test make_format.
280
281        Test that the Restriction.Analysis make_format(print_that) matches
282        some string.
283        """
284        dct = analysis.mapping
285        ls, nc = [], []
286        for k, v in dct.items():
287            if v:
288                ls.append((k, v))
289            else:
290                nc.append(k)
291        result = analysis.make_format(ls, "", [], "")
292        self.assertEqual(result.replace(" ", ""), expected.replace(" ", ""))
293
294    def test_make_format_map1(self):
295        """Test that print_as('map'); print_that() correctly wraps round.
296
297        1. With no marker.
298        """
299        analysis = self.createAnalysis(
300            "CCAGTCTATAATTCG"
301            + Restriction.BamHI.site
302            + "GCGGCATCATACTCGAATATCGCGTGATGATACGTAGTAATTACGCATG",
303            ["BamHI"],
304        )
305        analysis.print_as("map")
306        expected = [
307            "                17 BamHI",
308            "                |                                           ",
309            "CCAGTCTATAATTCGGGATCCGCGGCATCATACTCGAATATCGCGTGATGATACGTAGTA",
310            "||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||",
311            "GGTCAGATATTAAGCCCTAGGCGCCGTAGTATGAGCTTATAGCGCACTACTATGCATCAT",
312            "1                                                         60",
313            "",
314            "ATTACGCATG",
315            "||||||||||",
316            "TAATGCGTAC",
317            "61                          70",
318            "",
319            "",
320        ]
321        self.assertAnalysisFormat(analysis, "\n".join(expected))
322
323    def test_make_format_map2(self):
324        """Test that print_as('map'); print_that() correctly wraps round.
325
326        2. With marker.
327        """
328        analysis = self.createAnalysis(
329            "CCAGTCTATAATTCG"
330            + Restriction.BamHI.site
331            + "GCGGCATCATACTCGA"
332            + Restriction.BamHI.site
333            + "ATATCGCGTGATGATA"
334            + Restriction.NdeI.site
335            + "CGTAGTAATTACGCATG",
336            ["NdeI", "EcoRI", "BamHI", "BsmBI"],
337        )
338        analysis.print_as("map")
339        expected = [
340            "                17 BamHI",
341            "                |                                           ",
342            "                |                     39 BamHI",
343            "                |                     |                     ",
344            "CCAGTCTATAATTCGGGATCCGCGGCATCATACTCGAGGATCCATATCGCGTGATGATAC",
345            "||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||",
346            "GGTCAGATATTAAGCCCTAGGCGCCGTAGTATGAGCTCCTAGGTATAGCGCACTACTATG",
347            "1                                                         60",
348            "",
349            " 62 NdeI",
350            " |                                                          ",
351            "ATATGCGTAGTAATTACGCATG",
352            "||||||||||||||||||||||",
353            "TATACGCATCATTAATGCGTAC",
354            "61                          82",
355            "",
356            "",
357        ]
358        self.assertAnalysisFormat(analysis, "\n".join(expected))
359
360    def test_make_format_map3(self):
361        """Test that print_as('map'); print_that() correctly wraps round.
362
363        3. With marker restricted.
364        """
365        analysis = self.createAnalysis(
366            "CCAGTCTATAATTCG"
367            + Restriction.BamHI.site
368            + "GCGGCATCATACTCGA"
369            + Restriction.BamHI.site
370            + "ATATCGCGTGATGATA"
371            + Restriction.EcoRV.site
372            + "CGTAGTAATTACGCATG",
373            ["NdeI", "EcoRI", "BamHI", "BsmBI"],
374        )
375        analysis.print_as("map")
376        expected = [
377            "                17 BamHI",
378            "                |                                           ",
379            "                |                     39 BamHI",
380            "                |                     |                     ",
381            "CCAGTCTATAATTCGGGATCCGCGGCATCATACTCGAGGATCCATATCGCGTGATGATAG",
382            "||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||",
383            "GGTCAGATATTAAGCCCTAGGCGCCGTAGTATGAGCTCCTAGGTATAGCGCACTACTATC",
384            "1                                                         60",
385            "",
386            "ATATCCGTAGTAATTACGCATG",
387            "||||||||||||||||||||||",
388            "TATAGGCATCATTAATGCGTAC",
389            "61                          82",
390            "",
391            "",
392        ]
393        self.assertAnalysisFormat(analysis, "\n".join(expected))
394
395    def test_change(self):
396        """Test that change() changes something."""
397        seq = Seq(
398            "CCAGTCTATAATTCG"
399            + BamHI.site
400            + "GCGGCATCATACTCGA"
401            + BamHI.site
402            + "ATATCGCGTGATGATA"
403            + EcoRV.site
404            + "CGTAGTAATTACGCATG"
405        )
406        batch = NdeI + EcoRI + BamHI + BsmBI
407        analysis = Analysis(batch, seq)
408        self.assertEqual(analysis.full()[BamHI], [17, 39])
409        batch = NdeI + EcoRI + BsmBI
410        seq += NdeI.site
411        analysis.change(sequence=seq)
412        analysis.change(rb=batch)
413        self.assertEqual(len(analysis.full()), 3)
414        self.assertEqual(analysis.full()[NdeI], [85])
415        with self.assertRaises(AttributeError):
416            analysis.change(**{"NameWidth": 3, "KonsoleWidth": 40})  # Console
417
418
419class RestrictionBatches(unittest.TestCase):
420    """Tests for dealing with batches of restriction enzymes."""
421
422    def test_creating_batch(self):
423        """Creating and modifying a restriction batch."""
424        batch = RestrictionBatch()
425        self.assertEqual(batch.suppl_codes()["N"], "New England Biolabs")
426        self.assertTrue(batch.is_restriction(EcoRI))
427        batch = RestrictionBatch([EcoRI])
428        batch.add(KpnI)
429        batch += EcoRV
430        self.assertEqual(len(batch), 3)
431        self.assertEqual(batch.elements(), ["EcoRI", "EcoRV", "KpnI"])
432        # Problem with Python 3, as sequence of list may be different:
433        # self.assertEqual(batch.as_string(), ['EcoRI', 'KpnI', 'EcoRV'])
434        self.assertIn("EcoRI", batch.as_string())
435
436        # The usual way to test batch membership
437        self.assertIn(EcoRV, batch)
438        self.assertIn(EcoRI, batch)
439        self.assertIn(KpnI, batch)
440        self.assertNotIn(SmaI, batch)
441        # Syntax sugar for the above
442        self.assertIn("EcoRV", batch)
443        self.assertNotIn("SmaI", batch)
444
445        batch.get(EcoRV)
446        self.assertRaises(ValueError, batch.get, SmaI)
447        batch.get(SmaI, add=True)
448        self.assertEqual(len(batch), 4)
449        batch.remove(SmaI)
450        batch.remove(EcoRV)
451        self.assertEqual(len(batch), 2)
452
453        self.assertNotIn(EcoRV, batch)
454        self.assertNotIn("EcoRV", batch)
455
456        # Creating a batch by addition of restriction enzymes
457        new_batch = EcoRI + KpnI
458        self.assertEqual(batch, new_batch)
459        # or by addition of a batch with an enzyme
460        another_new_batch = new_batch + EcoRV
461        new_batch += EcoRV
462        self.assertEqual(another_new_batch, new_batch)
463        self.assertRaises(TypeError, EcoRI.__add__, 1)
464
465        # Create a batch with suppliers and other supplier related methods
466        # These tests may be 'update sensitive' since company names and
467        # products may change often...
468        batch = RestrictionBatch((), ("S"))  # Sigma
469        self.assertEqual(batch.current_suppliers(), ["Sigma Chemical Corporation"])
470        self.assertIn(EcoRI, batch)
471        self.assertNotIn(AanI, batch)
472        batch.add_supplier("B")  # Life Technologies
473        self.assertIn(AanI, batch)
474
475    def test_batch_analysis(self):
476        """Sequence analysis with a restriction batch."""
477        seq = Seq("AAAA" + EcoRV.site + "AAAA" + EcoRI.site + "AAAA")
478        batch = RestrictionBatch([EcoRV, EcoRI])
479
480        hits = batch.search(seq)
481        self.assertEqual(hits[EcoRV], [8])
482        self.assertEqual(hits[EcoRI], [16])
483
484    def test_premade_batches(self):
485        """Test content of premade batches CommOnly, NoComm, AllEnzymes."""
486        self.assertEqual(len(AllEnzymes), (len(CommOnly) + len(NonComm)))
487        self.assertTrue(len(AllEnzymes) > len(CommOnly) > len(NonComm))
488
489    def test_search_premade_batches(self):
490        """Test search with pre-made batches CommOnly, NoComm, AllEnzymes."""
491        seq = Seq("ACCCGAATTCAAAACTGACTGATCGATCGTCGACTG")
492        search = AllEnzymes.search(seq)
493        self.assertEqual(search[MluCI], [6])
494        # Check if '/' operator works as 'search':
495        search = CommOnly / seq
496        self.assertEqual(search[MluCI], [6])
497        # Also in reverse order:
498        search = seq / NonComm
499        self.assertEqual(search[McrI], [28])
500
501    def test_analysis_restrictions(self):
502        """Test Fancier restriction analysis."""
503        new_seq = Seq("TTCAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAA")
504        rb = RestrictionBatch([EcoRI, KpnI, EcoRV])
505        ana = Analysis(rb, new_seq, linear=False)
506        # Output only the result for enzymes which cut blunt:
507        self.assertEqual(ana.blunt(), {EcoRV: []})
508        self.assertEqual(ana.full(), {KpnI: [], EcoRV: [], EcoRI: [33]})
509        # Output only the result for enzymes which have a site:
510        self.assertEqual(ana.with_sites(), {EcoRI: [33]})
511        # Output only the enzymes which have no site:
512        self.assertEqual(ana.without_site(), {KpnI: [], EcoRV: []})
513        self.assertEqual(ana.with_site_size([32]), {})
514        # Output only enzymes which produce 5' overhangs
515        self.assertEqual(ana.overhang5(), {EcoRI: [33]})
516        # Output only enzymes which produce 3' overhangs
517        self.assertEqual(ana.overhang3(), {KpnI: []})
518        # Output only enzymes which produce defined ends
519        self.assertEqual(ana.defined(), {KpnI: [], EcoRV: [], EcoRI: [33]})
520        # Output only enzymes hich cut N times
521        self.assertEqual(ana.with_N_sites(2), {})
522        # The enzymes which cut between position x and y:
523        with self.assertRaises(TypeError):
524            ana.only_between("t", 20)
525        with self.assertRaises(TypeError):
526            ana.only_between(1, "t")
527        self.assertEqual(ana.only_between(1, 20), {})
528        self.assertEqual(ana.only_between(20, 34), {EcoRI: [33]})
529        # Mix start/end order:
530        self.assertEqual(ana.only_between(34, 20), {EcoRI: [33]})
531        self.assertEqual(ana.only_outside(20, 34), {})
532        with self.assertWarns(BiopythonWarning):
533            ana.with_name(["fake"])
534        self.assertEqual(ana.with_name([EcoRI]), {EcoRI: [33]})
535        self.assertEqual((ana._boundaries(1, 20)[:2]), (1, 20))
536        # Reverse order:
537        self.assertEqual((ana._boundaries(20, 1)[:2]), (1, 20))
538        # Fix negative start:
539        self.assertEqual((ana._boundaries(-1, 20)[:2]), (20, 33))
540        # Fix negative end:
541        self.assertEqual((ana._boundaries(1, -1)[:2]), (1, 33))
542        # Sites in- and outside of boundaries
543        new_seq = Seq("GAATTCAAAAAAGAATTC")
544        rb = RestrictionBatch([EcoRI])
545        ana = Analysis(rb, new_seq)
546        # Cut at least inside
547        self.assertEqual(ana.between(1, 7), {EcoRI: [2, 14]})
548        # Cut at least inside and report only inside site
549        self.assertEqual(ana.show_only_between(1, 7), {EcoRI: [2]})
550        # Cut at least outside
551        self.assertEqual(ana.outside(1, 7), {EcoRI: [2, 14]})
552        # Don't cut within
553        self.assertEqual(ana.do_not_cut(7, 12), {EcoRI: [2, 14]})
554
555
556class TestPrintOutputs(unittest.TestCase):
557    """Class to test various print outputs."""
558
559    import sys
560    from io import StringIO
561
562    def test_supplier(self):
563        """Test output of supplier list for different enzyme types."""
564        out = self.StringIO()
565        self.sys.stdout = out
566        EcoRI.suppliers()
567        self.assertIn("Life Technologies", out.getvalue())
568        self.assertIsNone(SnaI.suppliers())
569        EcoRI.all_suppliers()  # Independent of enzyme, list of all suppliers
570        self.assertIn("Agilent Technologies", out.getvalue())
571        batch = EcoRI + SnaI
572        batch.show_codes()
573        self.assertIn("N = New England Biolabs", out.getvalue())
574        self.sys.stdout = self.sys.__stdout__
575
576    def test_print_that(self):
577        """Test print_that function."""
578        out = self.StringIO()
579        self.sys.stdout = out
580        my_batch = EcoRI + SmaI + KpnI
581        my_seq = Seq("GAATTCCCGGGATATA")  # EcoRI and SmaI sites
582        analysis = Analysis(my_batch, my_seq)
583        analysis.print_that(None, title="My sequence\n\n", s1="Non Cutters\n\n")
584        self.assertIn("My sequence", out.getvalue())
585        self.assertIn("Non Cutters", out.getvalue())
586        self.assertIn("2.", out.getvalue())
587        self.sys.stdout = self.sys.__stdout__
588
589    def test_str_method(self):
590        """Test __str__ and __repr__ outputs."""
591        batch = EcoRI + SmaI + KpnI
592        self.assertEqual(str(batch), "EcoRI+KpnI+SmaI")
593        batch += Asp718I
594        batch += SnaI
595        self.assertEqual(str(batch), "Asp718I+EcoRI...SmaI+SnaI")
596        self.assertEqual(
597            repr(batch),
598            "RestrictionBatch(['Asp718I', 'EcoRI', 'KpnI', 'SmaI', 'SnaI'])",
599        )
600
601
602if __name__ == "__main__":
603    runner = unittest.TextTestRunner(verbosity=2)
604    unittest.main(testRunner=runner)
605