JAL-1499 replace identity symbols when parsing
[jalview.git] / test / jalview / io / MegaFileTest.java
1 package jalview.io;
2
3 import static org.testng.AssertJUnit.assertEquals;
4 import static org.testng.AssertJUnit.assertFalse;
5 import static org.testng.AssertJUnit.assertNull;
6 import static org.testng.AssertJUnit.assertTrue;
7 import static org.testng.AssertJUnit.fail;
8
9 import jalview.datamodel.AlignmentI;
10 import jalview.datamodel.Sequence;
11 import jalview.datamodel.SequenceI;
12
13 import java.io.IOException;
14 import java.util.Vector;
15
16 import org.testng.annotations.Test;
17
18 /*
19  * Unit tests for MegaFile - read and write in MEGA format(s).
20  */
21 public class MegaFileTest
22 {
23   private static final String TWENTY_CHARS = "9876543210abcdefghij";
24
25   private static final String THIRTY_CHARS = "0123456789klmnopqrstABCDEFGHIJ";
26
27   //@formatter:off
28   private static final String INTERLEAVED = 
29           "#MEGA\n"+ 
30           "TITLE: Interleaved sequence data\n\n" + 
31           "#U455   ABCDEF\n" + 
32           "#CPZANT  MNOPQR\n\n" + 
33           "#U455   KLMNOP\n" + 
34           "#CPZANT WXYZGC";
35
36   private static final String INTERLEAVED_NOHEADERS = 
37           "#U455   ABCDEF\n" 
38           + "#CPZANT MNOPQR\n\n" 
39           + "#U455   KLMNOP\n"
40           + "#CPZANT WXYZGC\n";
41
42   // interleaved sequences, with 50 residues
43   private static final String INTERLEAVED_50RESIDUES = 
44           "#MEGA\n"
45           + "!TITLE Interleaved sequence data\n\n"
46           + "#U455 " + THIRTY_CHARS + TWENTY_CHARS + "\n" 
47           + "#CPZANT " + TWENTY_CHARS + THIRTY_CHARS + "\n";
48
49   private static final String NONINTERLEAVED = 
50           "#MEGA\n"
51           + "!TITLE Noninterleaved sequence data\n\n" 
52           + "#U455  \n"
53           + "ABCFEDHIJ\n" 
54           + "MNOPQR\n\n" 
55           + "#CPZANT \n" 
56           + "KLMNOPWXYZ\n" 
57           + "CGATC\n";
58   
59   // this one starts interleaved then switches to non-interleaved
60   private static final String MIXED = 
61           "#MEGA\n"
62           + "!TITLE This is a mess\n\n" 
63           + "#CPZANT KLMNOPWXYZCGATC\n\n"
64           + "#U455\n  "
65           + "ABCFEDHIJ\n";
66
67   // interleaved with a new sequence appearing in the second block :-O
68   private static final String INTERLEAVED_SEQUENCE_ERROR = 
69           "#MEGA" + "\n"
70           + "!TITLE Interleaved sequence data\n\n"
71           + "#U455   ABCDEF\n" 
72           + "#CPZANT  MNOPQR\n\n"
73           + "#U456   KLMNOP\n";
74
75   // the 'fancy' format, different header format, bases in triplet groups
76   private static final String INTERLEAVED_WITH_DESCRIPTION = 
77           "#MEGA\n"
78           + "!Title Data with description;\n"
79           + "!Format DataType=DNA indel=- CodeTable=Standard Missing=? MatchChar=.;\n\n"
80           + "!Description\n" 
81           + "    Line one of description\n"
82           + "    Line two of description;\n\n"
83           + "#U455   CGC GTA\n" 
84           + "#CPZANT ATC GGG\n\n"
85           + "#U455   CGA TTT\n" 
86           + "#CPZANT CAA TGC\n";
87
88   //@formatter:on
89
90   /**
91    * Test paste of interleaved mega format data.
92    * 
93    * @throws IOException
94    */
95   @Test(groups = { "Functional" })
96   public void testParse_interleaved() throws IOException
97   {
98     MegaFile testee = new MegaFile(INTERLEAVED, AppletFormatAdapter.PASTE);
99     assertEquals("Title not as expected", "Interleaved sequence data",
100             testee.getAlignmentProperty(MegaFile.PROP_TITLE));
101     Vector<SequenceI> seqs = testee.getSeqs();
102     // should be 2 sequences
103     assertEquals("Expected two sequences", 2, seqs.size());
104     // check sequence names correct and order preserved
105     assertEquals("First sequence id wrong", "U455", seqs.get(0).getName());
106     assertEquals("Second sequence id wrong", "CPZANT", seqs.get(1)
107             .getName());
108     // check sequence data
109     assertEquals("First sequence data wrong", "ABCDEFKLMNOP", seqs.get(0)
110             .getSequenceAsString());
111     assertEquals("Second sequence data wrong", "MNOPQRWXYZGC", seqs.get(1)
112             .getSequenceAsString());
113     assertTrue("File format is not flagged as interleaved",
114             testee.isInterleaved());
115   }
116
117   /**
118    * Test paste of noninterleaved mega format data.
119    * 
120    * @throws IOException
121    */
122   @Test(groups = { "Functional" })
123   public void testParse_nonInterleaved() throws IOException
124   {
125     MegaFile testee = new MegaFile(NONINTERLEAVED,
126             AppletFormatAdapter.PASTE);
127     assertEquals("Title not as expected", "Noninterleaved sequence data",
128             testee.getAlignmentProperty(MegaFile.PROP_TITLE));
129     Vector<SequenceI> seqs = testee.getSeqs();
130     // should be 2 sequences
131     assertEquals("Expected two sequences", 2, seqs.size());
132     // check sequence names correct and order preserved
133     assertEquals("First sequence id wrong", "U455", seqs.get(0).getName());
134     assertEquals("Second sequence id wrong", "CPZANT", seqs.get(1)
135             .getName());
136     // check sequence data
137     assertEquals("First sequence data wrong", "ABCFEDHIJMNOPQR", seqs
138             .get(0).getSequenceAsString());
139     assertEquals("Second sequence data wrong", "KLMNOPWXYZCGATC",
140             seqs.get(1).getSequenceAsString());
141     assertFalse("File format is not flagged as noninterleaved",
142             testee.isInterleaved());
143   }
144
145   /**
146    * Test parsing an interleaved file with an extra sequence appearing after the
147    * first block - should fail.
148    */
149   @Test(groups = { "Functional" })
150   public void testParse_interleavedExtraSequenceError()
151   {
152     try
153     {
154       new MegaFile(INTERLEAVED_SEQUENCE_ERROR, AppletFormatAdapter.PASTE);
155       fail("Expected extra sequence IOException");
156     } catch (IOException e)
157     {
158       assertEquals(
159               "Unexpected exception message",
160               "Parse error: misplaced new sequence starting at #U456   KLMNOP",
161               e.getMessage());
162     }
163   }
164
165   /**
166    * Test a mixed up file.
167    */
168   @Test(groups = { "Functional" })
169   public void testParse_mixedInterleavedNonInterleaved()
170   {
171     try
172     {
173       new MegaFile(MIXED, AppletFormatAdapter.PASTE);
174       fail("Expected mixed content exception");
175     } catch (IOException e)
176     {
177       assertEquals(
178               "Unexpected exception message",
179               "Parse error: mix of interleaved and noninterleaved detected, at line: ABCFEDHIJ",
180               e.getMessage());
181     }
182
183   }
184
185   @Test(groups = { "Functional" })
186   public void testGetSequenceId()
187   {
188     assertEquals("AB123", MegaFile.getSequenceId("#AB123 CGATC"));
189     assertEquals("AB123", MegaFile.getSequenceId("#AB123    CGATC"));
190     assertEquals("AB123", MegaFile.getSequenceId("#AB123 CGC TAC"));
191     assertEquals("AB123", MegaFile.getSequenceId("#AB123"));
192     assertNull(MegaFile.getSequenceId("AB123 CTAG"));
193     assertNull(MegaFile.getSequenceId("AB123"));
194     assertNull(MegaFile.getSequenceId(""));
195     assertNull(MegaFile.getSequenceId(null));
196   }
197
198   @Test(groups = { "Functional" })
199   public void testGetMaxIdLength()
200   {
201     SequenceI[] seqs = new Sequence[2];
202     seqs[0] = new Sequence("Something", "GCATAC");
203     seqs[1] = new Sequence("SomethingElse", "GCATAC");
204     assertEquals(13, MegaFile.getMaxIdLength(seqs));
205     seqs[1] = new Sequence("DNA", "GCATAC");
206     assertEquals(9, MegaFile.getMaxIdLength(seqs));
207   }
208
209   @Test(groups = { "Functional" })
210   public void testGetMaxSequenceLength()
211   {
212     SequenceI[] seqs = new Sequence[2];
213     seqs[0] = new Sequence("Seq1", "GCATAC");
214     seqs[1] = new Sequence("Seq2", "GCATACTAG");
215     assertEquals(9, MegaFile.getMaxSequenceLength(seqs));
216     seqs[1] = new Sequence("Seq2", "GCA");
217     assertEquals(6, MegaFile.getMaxSequenceLength(seqs));
218   }
219
220   /**
221    * Test (parse and) print of interleaved mega format data.
222    * 
223    * @throws IOException
224    */
225   @Test(groups = { "Functional" })
226   public void testPrint_interleaved() throws IOException
227   {
228     MegaFile testee = new MegaFile(INTERLEAVED, AppletFormatAdapter.PASTE);
229     String printed = testee.print();
230     System.out.println(printed);
231     // normally output should match input
232     // we cheated here with a number of short input lines
233     // nb don't get Title in output if not calling print(AlignmentI)
234     String expected = "#MEGA\n\n" + "#U455   ABCDEF [6]\n"
235             + "#CPZANT MNOPQR [6]\n\n" + "#U455   KLMNOP [12]\n"
236             + "#CPZANT WXYZGC [12]"
237             + "\n";
238     assertEquals("Print format wrong", expected, printed);
239   }
240
241   /**
242    * Test (parse and) print of interleaved data with no headers (acceptable).
243    * 
244    * @throws IOException
245    */
246   @Test(groups = { "Functional" })
247   public void testPrint_interleavedNoHeaders() throws IOException
248   {
249     MegaFile testee = new MegaFile(INTERLEAVED_NOHEADERS,
250             AppletFormatAdapter.PASTE);
251     String printed = testee.print();
252     System.out.println(printed);
253
254     //@formatter:off
255     assertEquals("Print format wrong", 
256     "#MEGA\n\n" + "#U455   ABCDEF [6]\n" 
257     + "#CPZANT MNOPQR [6]\n\n" 
258     + "#U455   KLMNOP [12]\n"
259     + "#CPZANT WXYZGC [12]\n",
260             printed);
261     //@formatter:on
262   }
263
264   /**
265    * Test (parse and) print of noninterleaved mega format data.
266    * 
267    * @throws IOException
268    */
269   @Test(groups = { "Functional" })
270   public void testPrint_noninterleaved() throws IOException
271   {
272     MegaFile testee = new MegaFile(NONINTERLEAVED,
273             AppletFormatAdapter.PASTE);
274     assertEquals(10, testee.getPositionsPerLine());
275     String printed = testee.print();
276     System.out.println(printed);
277     // normally output should match input
278     // we cheated here with a number of short input lines
279     String expected = "#MEGA\n\n"
280  + "#U455\n" + "ABCFEDHIJM\nNOPQR\n\n"
281             + "#CPZANT\n" + "KLMNOPWXYZ\nCGATC\n";
282     assertEquals("Print format wrong", expected, printed);
283   }
284
285   /**
286    * Test (parse and) print of interleaved mega format data extending to more
287    * than one line of output.
288    * 
289    * @throws IOException
290    */
291   @Test(groups = { "Functional" })
292   public void testPrint_interleavedMultiLine() throws IOException
293   {
294     MegaFile testee = new MegaFile(INTERLEAVED_50RESIDUES,
295             AppletFormatAdapter.PASTE);
296     assertEquals(50, testee.getPositionsPerLine());
297     /*
298      * now simulate choosing 20 residues per line on output
299      */
300     testee.setPositionsPerLine(20);
301     String printed = testee.print();
302     System.out.println(printed);
303     //@formatter:off
304     //0123456789klmnopqrstABCDEFGHIJ9876543210abcdefghij
305     String expected = 
306             "#MEGA\n\n" + 
307             "#U455   0123456789 klmnopqrst [20]\n" + // first 20
308             "#CPZANT 9876543210 abcdefghij [20]\n\n" +
309             "#U455   ABCDEFGHIJ 9876543210 [40]\n" + // next 20
310             "#CPZANT 0123456789 klmnopqrst [40]\n\n" +
311             "#U455   abcdefghij [50]\n" + // last 10
312             "#CPZANT ABCDEFGHIJ [50]\n";
313     //@formatter:on
314     assertEquals("Print format wrong", expected, printed);
315   }
316
317   /**
318    * Test (parse and) print of noninterleaved mega format data extending to more
319    * than one line of output.
320    * 
321    * @throws IOException
322    */
323   @Test(groups = { "Functional" })
324   public void testPrint_noninterleavedMultiLine() throws IOException
325   {
326     final String NONINTERLEAVED_LONGERTHAN50 = "#SIXTY\n" + THIRTY_CHARS
327             + "\n" + TWENTY_CHARS + "9993332221\n";
328     MegaFile testee = new MegaFile(NONINTERLEAVED_LONGERTHAN50,
329             AppletFormatAdapter.PASTE);
330     assertEquals(30, testee.getPositionsPerLine());
331     testee.setPositionsPerLine(25);
332     String printed = testee.print();
333     // 60 character sequence should be output as 50 on first line then 10 more
334     String expected = "#MEGA\n\n" + "#SIXTY\n"
335             + "0123456789klmnopqrstABCDE\n" + "FGHIJ9876543210abcdefghij\n"
336             + "9993332221\n";
337     assertEquals("Print format wrong", expected, printed);
338   }
339
340   /**
341    * Test parse of data including description
342    * 
343    * @throws IOException
344    */
345   @Test(groups = { "Functional" })
346   public void testParse_withDescription() throws IOException
347   {
348     MegaFile testee = new MegaFile(INTERLEAVED_WITH_DESCRIPTION,
349             AppletFormatAdapter.PASTE);
350     assertEquals("Title not as expected", "Data with description",
351             testee.getAlignmentProperty(MegaFile.PROP_TITLE));
352
353     Vector<SequenceI> seqs = testee.getSeqs();
354     // should be 2 sequences
355     assertEquals("Expected two sequences", 2, seqs.size());
356     // check sequence names correct and order preserved
357     assertEquals("First sequence id wrong", "U455", seqs.get(0).getName());
358     assertEquals("Second sequence id wrong", "CPZANT", seqs.get(1)
359             .getName());
360     // check sequence data
361     assertEquals("First sequence data wrong", "CGCGTACGATTT", seqs.get(0)
362             .getSequenceAsString());
363     assertEquals("Second sequence data wrong", "ATCGGGCAATGC", seqs.get(1)
364             .getSequenceAsString());
365     assertTrue("File format is not flagged as interleaved",
366             testee.isInterleaved());
367
368     assertEquals(
369             "Description property not parsed",
370             "    Line one of description\n" + "    Line two of description",
371             testee.getAlignmentProperty(MegaFile.PROP_DESCRIPTION));
372   }
373
374   @Test(groups = { "Functional" })
375   public void testGetNonCommentContent() throws FileFormatException
376   {
377     assertEquals("abcde", MegaFile.getNonCommentContent("abcde", 0));
378     assertEquals("CGT ACG GAC ",
379             MegaFile.getNonCommentContent("CGT ACG GAC [9]", 0));
380     assertEquals("", MegaFile.getNonCommentContent("abcde", 1));
381     assertEquals(" abcde",
382             MegaFile.getNonCommentContent("and others ] abcde", 1));
383     assertEquals(" abcde", MegaFile.getNonCommentContent(
384             "and others [including refs] ] abcde", 1));
385     assertEquals(" x ] abcde",
386             MegaFile.getNonCommentContent("and others ] x ] abcde", 1));
387   }
388
389   @Test(groups = { "Functional" })
390   public void testCommentDepth() throws FileFormatException
391   {
392     assertEquals(0, MegaFile.commentDepth("abcde", 0));
393     assertEquals(1, MegaFile.commentDepth("abc[de", 0));
394     assertEquals(3, MegaFile.commentDepth("ab[c[de", 1));
395     assertEquals(1, MegaFile.commentDepth("ab]c[d]e[f", 1));
396     assertEquals(0, MegaFile.commentDepth("a]b[c]d]e", 1));
397   }
398
399   @Test(groups = { "Functional" })
400   public void testGetValue()
401   {
402     assertEquals("Mega", MegaFile.getValue("Name=Mega"));
403     assertEquals("Mega", MegaFile.getValue("Name =Mega"));
404     assertEquals("Mega", MegaFile.getValue(" Name = Mega "));
405     assertEquals("Mega", MegaFile.getValue("Name = Mega; "));
406     assertEquals("Mega", MegaFile.getValue(" Name = Mega ; "));
407     assertEquals("Mega", MegaFile.getValue("\t!Name \t= \tMega ; "));
408     assertEquals("Mega", MegaFile.getValue("!Name \t\t Mega; "));
409     assertEquals("", MegaFile.getValue("Name"));
410   }
411
412   /**
413    * Test reading a MEGA file to an alignment then writing it out in MEGA
414    * format. Verify the output is (functionally) the same as the input.
415    * 
416    * @throws IOException
417    */
418   @Test(groups = "Functional")
419   public void testRoundTrip_Interleaved() throws IOException
420   {
421     AppletFormatAdapter fa = new AppletFormatAdapter();
422     AlignmentI al = fa.readFile(INTERLEAVED_WITH_DESCRIPTION,
423             AppletFormatAdapter.PASTE, "MEGA");
424     MegaFile output = new MegaFile();
425     String formatted = output.print(al);
426     //@formatter:off
427     String expected = 
428          "#MEGA\n!Title Data with description;\n" +
429          "!Description\n" +
430          "    Line one of description\n" +
431          "    Line two of description;\n" +
432          "!Format\n" +
433          "    DataType=DNA CodeTable=Standard\n" +
434          "    NSeqs=2 NSites=12\n" +
435          "    Indel=- Identical=. Missing=?;\n\n" +
436          "#U455   CGC GTA [6]\n" +
437          "#CPZANT ATC GGG [6]\n\n" +
438          "#U455   CGA TTT [12]\n" +
439          "#CPZANT CAA TGC [12]\n";
440     //@formatter:on
441     assertEquals("Roundtrip didn't match", expected,
442             formatted);
443   }
444
445   /**
446    * Test reading a MEGA file to an alignment then writing it out in MEGA
447    * format. Verify the output is (functionally) the same as the input.
448    * 
449    * @throws IOException
450    */
451   @Test(groups = "Functional")
452   public void testRoundTrip_multilineFormatWithComments()
453           throws IOException
454   {
455     AppletFormatAdapter fa = new AppletFormatAdapter();
456     //@formatter:off
457     AlignmentI al = fa.readFile("#MEGA\n"
458     + "!Title Data with description;\n"
459     + "[ this comment should be ignored\n"
460     + "including [this nested comment]\n"
461     + "]\n"
462     + "!Format \n"
463     + "DataType=DNA CodeTable=Standard\n"
464     + "indel=- Missing=? MatchChar=.;\n\n"
465     + "!Description\n" 
466     + "    Line one of description\n"
467     + "    Line two of description;\n\n"
468     + "#U455   CGC GTA\n" 
469     + "#CPZANT ATC GGG\n\n"
470     + "#U455   CGA TTT\n" 
471     + "#CPZANT CAA TGC\n",
472             AppletFormatAdapter.PASTE, "MEGA");
473     //@formatter:on
474     MegaFile output = new MegaFile();
475     String formatted = output.print(al);
476     //@formatter:off
477     String expected = 
478          "#MEGA\n!Title Data with description;\n" +
479          "!Description\n" +
480          "    Line one of description\n" +
481          "    Line two of description;\n" +
482          "!Format\n" +
483          "    DataType=DNA CodeTable=Standard\n" +
484          "    NSeqs=2 NSites=12\n" +
485          "    Indel=- Identical=. Missing=?;\n\n" +
486          "#U455   CGC GTA [6]\n" +
487          "#CPZANT ATC GGG [6]\n\n" +
488          "#U455   CGA TTT [12]\n" +
489          "#CPZANT CAA TGC [12]\n";
490     //@formatter:on
491     assertEquals("Roundtrip didn't match", expected,
492             formatted);
493   }
494
495   //@formatter:on
496   
497   /**
498    * Test paste of interleaved mega format data where the identity character is
499    * used in sequences after the first
500    * 
501    * @throws IOException
502    */
503   @Test(groups = { "Functional" })
504   public void testParse_interleavedWithIdentity() throws IOException
505   {
506     //@formatter:off
507     MegaFile testee = new MegaFile("#MEGA\n"+ 
508     "!TITLE Interleaved sequence data;\n" +
509     "!Format Identical=.;\n\n" +
510     "#U455   ABCDEF\n" + 
511     "#CPZANT  M..P.R\n\n" + 
512     "#U455   KLMNOP\n" + 
513     "#CPZANT ..YZ..", AppletFormatAdapter.PASTE);
514     //@formatter:on
515     assertEquals("Title not as expected", "Interleaved sequence data",
516             testee.getAlignmentProperty(MegaFile.PROP_TITLE));
517     Vector<SequenceI> seqs = testee.getSeqs();
518     // should be 2 sequences
519     assertEquals("Expected two sequences", 2, seqs.size());
520     // check sequence names correct and order preserved
521     assertEquals("First sequence id wrong", "U455", seqs.get(0).getName());
522     assertEquals("Second sequence id wrong", "CPZANT", seqs.get(1)
523             .getName());
524     // check sequence data
525     assertEquals("First sequence data wrong", "ABCDEFKLMNOP", seqs.get(0)
526             .getSequenceAsString());
527     assertEquals("Second sequence data wrong", "MBCPERKLYZOP", seqs.get(1)
528             .getSequenceAsString());
529     assertTrue("File format is not flagged as interleaved",
530             testee.isInterleaved());
531   }
532
533   /**
534    * Test paste of noninterleaved format data including identity symbol
535    * 
536    * @throws IOException
537    */
538   @Test(groups = { "Functional" })
539   public void testParse_nonInterleavedWithIdentity() throws IOException
540   {
541     //@formatter:off
542     MegaFile testee = new MegaFile("#MEGA\n"
543     + "!TITLE Noninterleaved sequence data;\n"
544     + "!Format MatchChar=.;\n"
545     + "#U455  \n"
546     + "ABCFEDHIJ\n" 
547     + "MNOPQR\n\n" 
548     + "#CPZANT \n" 
549     + "KL..O..XYZ\n" 
550     + "CG..C\n",
551             AppletFormatAdapter.PASTE);
552     //@formatter:on
553     assertEquals("Title not as expected", "Noninterleaved sequence data",
554             testee.getAlignmentProperty(MegaFile.PROP_TITLE));
555     Vector<SequenceI> seqs = testee.getSeqs();
556     // should be 2 sequences
557     assertEquals("Expected two sequences", 2, seqs.size());
558     // check sequence names correct and order preserved
559     assertEquals("First sequence id wrong", "U455", seqs.get(0).getName());
560     assertEquals("Second sequence id wrong", "CPZANT", seqs.get(1)
561             .getName());
562     // check sequence data
563     assertEquals("First sequence data wrong", "ABCFEDHIJMNOPQR", seqs
564             .get(0).getSequenceAsString());
565     assertEquals("Second sequence data wrong", "KLCFODHXYZCGPQC",
566             seqs.get(1).getSequenceAsString());
567     assertFalse("File format is not flagged as noninterleaved",
568             testee.isInterleaved());
569   }
570 }