JAL-1705 align CDS and peptide products to transcripts
[jalview.git] / test / jalview / datamodel / SequenceTest.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.datamodel;
22
23 import static org.testng.AssertJUnit.assertEquals;
24 import static org.testng.AssertJUnit.assertFalse;
25 import static org.testng.AssertJUnit.assertNotNull;
26 import static org.testng.AssertJUnit.assertNull;
27 import static org.testng.AssertJUnit.assertSame;
28 import static org.testng.AssertJUnit.assertTrue;
29
30 import jalview.datamodel.PDBEntry.Type;
31
32 import java.util.Arrays;
33 import java.util.List;
34 import java.util.Vector;
35
36 import org.testng.annotations.BeforeMethod;
37 import org.testng.annotations.Test;
38
39 public class SequenceTest
40 {
41   SequenceI seq;
42
43   @BeforeMethod(alwaysRun = true)
44   public void setUp()
45   {
46     seq = new Sequence("FER1", "AKPNGVL");
47   }
48
49   @Test(groups = { "Functional" })
50   public void testInsertGapsAndGapmaps()
51   {
52     SequenceI aseq = seq.deriveSequence();
53     aseq.insertCharAt(2, 3, '-');
54     aseq.insertCharAt(6, 3, '-');
55     assertEquals("Gap insertions not correct", "AK---P---NGVL",
56             aseq.getSequenceAsString());
57     List<int[]> gapInt = aseq.getInsertions();
58     assertEquals("Gap interval 1 start wrong", 2, gapInt.get(0)[0]);
59     assertEquals("Gap interval 1 end wrong", 4, gapInt.get(0)[1]);
60     assertEquals("Gap interval 2 start wrong", 6, gapInt.get(1)[0]);
61     assertEquals("Gap interval 2 end wrong", 8, gapInt.get(1)[1]);
62   }
63
64   @Test(groups = { "Functional" })
65   public void testGetAnnotation()
66   {
67     // initial state returns null not an empty array
68     assertNull(seq.getAnnotation());
69     AlignmentAnnotation ann = addAnnotation("label1", "desc1", "calcId1",
70             1f);
71     AlignmentAnnotation[] anns = seq.getAnnotation();
72     assertEquals(1, anns.length);
73     assertSame(ann, anns[0]);
74
75     // removing all annotations reverts array to null
76     seq.removeAlignmentAnnotation(ann);
77     assertNull(seq.getAnnotation());
78   }
79
80   @Test(groups = { "Functional" })
81   public void testGetAnnotation_forLabel()
82   {
83     AlignmentAnnotation ann1 = addAnnotation("label1", "desc1", "calcId1",
84             1f);
85     AlignmentAnnotation ann2 = addAnnotation("label2", "desc2", "calcId2",
86             1f);
87     AlignmentAnnotation ann3 = addAnnotation("label1", "desc3", "calcId3",
88             1f);
89     AlignmentAnnotation[] anns = seq.getAnnotation("label1");
90     assertEquals(2, anns.length);
91     assertSame(ann1, anns[0]);
92     assertSame(ann3, anns[1]);
93   }
94
95   private AlignmentAnnotation addAnnotation(String label,
96           String description, String calcId, float value)
97   {
98     final AlignmentAnnotation annotation = new AlignmentAnnotation(label,
99             description, value);
100     annotation.setCalcId(calcId);
101     seq.addAlignmentAnnotation(annotation);
102     return annotation;
103   }
104
105   @Test(groups = { "Functional" })
106   public void testGetAlignmentAnnotations_forCalcIdAndLabel()
107   {
108     AlignmentAnnotation ann1 = addAnnotation("label1", "desc1", "calcId1",
109             1f);
110     AlignmentAnnotation ann2 = addAnnotation("label2", "desc2", "calcId2",
111             1f);
112     AlignmentAnnotation ann3 = addAnnotation("label2", "desc3", "calcId3",
113             1f);
114     AlignmentAnnotation ann4 = addAnnotation("label2", "desc3", "calcId2",
115             1f);
116     AlignmentAnnotation ann5 = addAnnotation("label5", "desc3", null, 1f);
117     AlignmentAnnotation ann6 = addAnnotation(null, "desc3", "calcId3", 1f);
118     List<AlignmentAnnotation> anns = seq.getAlignmentAnnotations("calcId2",
119             "label2");
120     assertEquals(2, anns.size());
121     assertSame(ann2, anns.get(0));
122     assertSame(ann4, anns.get(1));
123
124     assertTrue(seq.getAlignmentAnnotations("calcId2", "label3").isEmpty());
125     assertTrue(seq.getAlignmentAnnotations("calcId3", "label5").isEmpty());
126     assertTrue(seq.getAlignmentAnnotations("calcId2", null).isEmpty());
127     assertTrue(seq.getAlignmentAnnotations(null, "label3").isEmpty());
128     assertTrue(seq.getAlignmentAnnotations(null, null).isEmpty());
129   }
130
131   /**
132    * Tests for addAlignmentAnnotation. Note this method has the side-effect of
133    * setting the sequenceRef on the annotation. Adding the same annotation twice
134    * should be ignored.
135    */
136   @Test(groups = { "Functional" })
137   public void testAddAlignmentAnnotation()
138   {
139     assertNull(seq.getAnnotation());
140     final AlignmentAnnotation annotation = new AlignmentAnnotation("a",
141             "b", 2d);
142     assertNull(annotation.sequenceRef);
143     seq.addAlignmentAnnotation(annotation);
144     assertSame(seq, annotation.sequenceRef);
145     AlignmentAnnotation[] anns = seq.getAnnotation();
146     assertEquals(1, anns.length);
147     assertSame(annotation, anns[0]);
148
149     // re-adding does nothing
150     seq.addAlignmentAnnotation(annotation);
151     anns = seq.getAnnotation();
152     assertEquals(1, anns.length);
153     assertSame(annotation, anns[0]);
154
155     // an identical but different annotation can be added
156     final AlignmentAnnotation annotation2 = new AlignmentAnnotation("a",
157             "b", 2d);
158     seq.addAlignmentAnnotation(annotation2);
159     anns = seq.getAnnotation();
160     assertEquals(2, anns.length);
161     assertSame(annotation, anns[0]);
162     assertSame(annotation2, anns[1]);
163   }
164
165   @Test(groups = { "Functional" })
166   public void testGetStartGetEnd()
167   {
168     SequenceI sq = new Sequence("test", "ABCDEF");
169     assertEquals(1, sq.getStart());
170     assertEquals(6, sq.getEnd());
171
172     sq = new Sequence("test", "--AB-C-DEF--");
173     assertEquals(1, sq.getStart());
174     assertEquals(6, sq.getEnd());
175
176     sq = new Sequence("test", "----");
177     assertEquals(1, sq.getStart());
178     assertEquals(0, sq.getEnd()); // ??
179   }
180
181   /**
182    * Tests for the method that returns an alignment column position (base 1) for
183    * a given sequence position (base 1).
184    */
185   @Test(groups = { "Functional" })
186   public void testFindIndex()
187   {
188     SequenceI sq = new Sequence("test", "ABCDEF");
189     assertEquals(0, sq.findIndex(0));
190     assertEquals(1, sq.findIndex(1));
191     assertEquals(5, sq.findIndex(5));
192     assertEquals(6, sq.findIndex(6));
193     assertEquals(6, sq.findIndex(9));
194
195     sq = new Sequence("test", "-A--B-C-D-E-F--");
196     assertEquals(2, sq.findIndex(1));
197     assertEquals(5, sq.findIndex(2));
198     assertEquals(7, sq.findIndex(3));
199
200     // before start returns 0
201     assertEquals(0, sq.findIndex(0));
202     assertEquals(0, sq.findIndex(-1));
203
204     // beyond end returns last residue column
205     assertEquals(13, sq.findIndex(99));
206
207   }
208
209   /**
210    * Tests for the method that returns a dataset sequence position (base 1) for
211    * an aligned column position (base 0).
212    */
213   @Test(groups = { "Functional" })
214   public void testFindPosition()
215   {
216     SequenceI sq = new Sequence("test", "ABCDEF");
217     assertEquals(1, sq.findPosition(0));
218     assertEquals(6, sq.findPosition(5));
219     // assertEquals(-1, seq.findPosition(6)); // fails
220
221     sq = new Sequence("test", "AB-C-D--");
222     assertEquals(1, sq.findPosition(0));
223     assertEquals(2, sq.findPosition(1));
224     // gap position 'finds' residue to the right (not the left as per javadoc)
225     assertEquals(3, sq.findPosition(2));
226     assertEquals(3, sq.findPosition(3));
227     assertEquals(4, sq.findPosition(4));
228     assertEquals(4, sq.findPosition(5));
229     // returns 1 more than sequence length if off the end ?!?
230     assertEquals(5, sq.findPosition(6));
231     assertEquals(5, sq.findPosition(7));
232
233     sq = new Sequence("test", "--AB-C-DEF--");
234     assertEquals(1, sq.findPosition(0));
235     assertEquals(1, sq.findPosition(1));
236     assertEquals(1, sq.findPosition(2));
237     assertEquals(2, sq.findPosition(3));
238     assertEquals(3, sq.findPosition(4));
239     assertEquals(3, sq.findPosition(5));
240     assertEquals(4, sq.findPosition(6));
241     assertEquals(4, sq.findPosition(7));
242     assertEquals(5, sq.findPosition(8));
243     assertEquals(6, sq.findPosition(9));
244     assertEquals(7, sq.findPosition(10));
245     assertEquals(7, sq.findPosition(11));
246   }
247
248   @Test(groups = { "Functional" })
249   public void testDeleteChars()
250   {
251     SequenceI sq = new Sequence("test", "ABCDEF");
252     assertEquals(1, sq.getStart());
253     assertEquals(6, sq.getEnd());
254     sq.deleteChars(2, 3);
255     assertEquals("ABDEF", sq.getSequenceAsString());
256     assertEquals(1, sq.getStart());
257     assertEquals(5, sq.getEnd());
258
259     sq = new Sequence("test", "ABCDEF");
260     sq.deleteChars(0, 2);
261     assertEquals("CDEF", sq.getSequenceAsString());
262     assertEquals(3, sq.getStart());
263     assertEquals(6, sq.getEnd());
264   }
265
266   @Test(groups = { "Functional" })
267   public void testInsertCharAt()
268   {
269     // non-static methods:
270     SequenceI sq = new Sequence("test", "ABCDEF");
271     sq.insertCharAt(0, 'z');
272     assertEquals("zABCDEF", sq.getSequenceAsString());
273     sq.insertCharAt(2, 2, 'x');
274     assertEquals("zAxxBCDEF", sq.getSequenceAsString());
275
276     // for static method see StringUtilsTest
277   }
278
279   /**
280    * Test the method that returns an array of aligned sequence positions where
281    * the array index is the data sequence position (both base 0).
282    */
283   @Test(groups = { "Functional" })
284   public void testGapMap()
285   {
286     SequenceI sq = new Sequence("test", "-A--B-CD-E--F-");
287     sq.createDatasetSequence();
288     assertEquals("[1, 4, 6, 7, 9, 12]", Arrays.toString(sq.gapMap()));
289   }
290
291   /**
292    * Test the method that gets sequence features, either from the sequence or
293    * its dataset.
294    */
295   @Test(groups = { "Functional" })
296   public void testGetSequenceFeatures()
297   {
298     SequenceI sq = new Sequence("test", "GATCAT");
299     sq.createDatasetSequence();
300
301     assertNull(sq.getSequenceFeatures());
302
303     /*
304      * SequenceFeature on sequence
305      */
306     SequenceFeature sf = new SequenceFeature();
307     sq.addSequenceFeature(sf);
308     SequenceFeature[] sfs = sq.getSequenceFeatures();
309     assertEquals(1, sfs.length);
310     assertSame(sf, sfs[0]);
311
312     /*
313      * SequenceFeature on sequence and dataset sequence; returns that on
314      * sequence
315      */
316     SequenceFeature sf2 = new SequenceFeature();
317     sq.getDatasetSequence().addSequenceFeature(sf2);
318     sfs = sq.getSequenceFeatures();
319     assertEquals(1, sfs.length);
320     assertSame(sf, sfs[0]);
321
322     /*
323      * SequenceFeature on dataset sequence only
324      */
325     sq.setSequenceFeatures(null);
326     sfs = sq.getSequenceFeatures();
327     assertEquals(1, sfs.length);
328     assertSame(sf2, sfs[0]);
329
330     /*
331      * Corrupt case - no SequenceFeature, dataset's dataset is the original
332      * sequence. Test shows no infinite loop results.
333      */
334     sq.getDatasetSequence().setSequenceFeatures(null);
335     sq.getDatasetSequence().setDatasetSequence(sq); // loop!
336     assertNull(sq.getSequenceFeatures());
337   }
338
339   /**
340    * Test the method that returns an array, indexed by sequence position, whose
341    * entries are the residue positions at the sequence position (or to the right
342    * if a gap)
343    */
344   @Test(groups = { "Functional" })
345   public void testFindPositionMap()
346   {
347     /*
348      * Note: Javadoc for findPosition says it returns the residue position to
349      * the left of a gapped position; in fact it returns the position to the
350      * right. Also it returns a non-existent residue position for a gap beyond
351      * the sequence.
352      */
353     Sequence sq = new Sequence("TestSeq", "AB.C-D E.");
354     int[] map = sq.findPositionMap();
355     assertEquals(Arrays.toString(new int[] { 1, 2, 3, 3, 4, 4, 5, 5, 6 }),
356             Arrays.toString(map));
357   }
358
359   /**
360    * Test for getSubsequence
361    */
362   @Test(groups = { "Functional" })
363   public void testGetSubsequence()
364   {
365     SequenceI sq = new Sequence("TestSeq", "ABCDEFG");
366     sq.createDatasetSequence();
367
368     // positions are base 0, end position is exclusive
369     SequenceI subseq = sq.getSubSequence(2, 4);
370
371     assertEquals("CD", subseq.getSequenceAsString());
372     // start/end are base 1 positions
373     assertEquals(3, subseq.getStart());
374     assertEquals(4, subseq.getEnd());
375     // subsequence shares the full dataset sequence
376     assertSame(sq.getDatasetSequence(), subseq.getDatasetSequence());
377   }
378
379   /**
380    * Test for deriveSequence applied to a sequence with a dataset
381    */
382   @Test(groups = { "Functional" })
383   public void testDeriveSequence_existingDataset()
384   {
385     SequenceI sq = new Sequence("Seq1", "CD");
386     sq.setDatasetSequence(new Sequence("Seq1", "ABCDEF"));
387     sq.getDatasetSequence().addSequenceFeature(
388             new SequenceFeature("", "", 1, 2, 0f, null));
389     sq.setStart(3);
390     sq.setEnd(4);
391     SequenceI derived = sq.deriveSequence();
392     assertEquals("CD", derived.getSequenceAsString());
393     assertSame(sq.getDatasetSequence(), derived.getDatasetSequence());
394
395     assertNull(((Sequence) seq).sequenceFeatures);
396     assertNull(((Sequence) derived).sequenceFeatures);
397     assertNotNull(seq.getSequenceFeatures());
398     assertSame(seq.getSequenceFeatures(), derived.getSequenceFeatures());
399   }
400
401   /**
402    * Test for deriveSequence applied to an ungapped sequence with no dataset
403    */
404   @Test(groups = { "Functional" })
405   public void testDeriveSequence_noDatasetUngapped()
406   {
407     SequenceI sq = new Sequence("Seq1", "ABCDEF");
408     assertEquals(1, sq.getStart());
409     assertEquals(6, sq.getEnd());
410     SequenceI derived = sq.deriveSequence();
411     assertEquals("ABCDEF", derived.getSequenceAsString());
412     assertEquals("ABCDEF", derived.getDatasetSequence()
413             .getSequenceAsString());
414   }
415
416   /**
417    * Test for deriveSequence applied to a gapped sequence with no dataset
418    */
419   @Test(groups = { "Functional" })
420   public void testDeriveSequence_noDatasetGapped()
421   {
422     SequenceI sq = new Sequence("Seq1", "AB-C.D EF");
423     assertEquals(1, sq.getStart());
424     assertEquals(6, sq.getEnd());
425     assertNull(sq.getDatasetSequence());
426     SequenceI derived = sq.deriveSequence();
427     assertEquals("AB-C.D EF", derived.getSequenceAsString());
428     assertEquals("ABCDEF", derived.getDatasetSequence()
429             .getSequenceAsString());
430   }
431
432   @Test(groups = { "Functional" })
433   public void testCopyConstructor_noDataset()
434   {
435     SequenceI seq1 = new Sequence("Seq1", "AB-C.D EF");
436     seq1.setDescription("description");
437     seq1.addAlignmentAnnotation(new AlignmentAnnotation("label", "desc",
438             1.3d));
439     seq1.addSequenceFeature(new SequenceFeature("type", "desc", 22, 33,
440             12.4f, "group"));
441     seq1.addPDBId(new PDBEntry("1A70", "B", Type.PDB, "File"));
442     seq1.addDBRef(new DBRefEntry("EMBL", "1.2", "AZ12345"));
443     
444     SequenceI copy = new Sequence(seq1);
445
446     assertNull(copy.getDatasetSequence());
447
448     verifyCopiedSequence(seq1, copy);
449
450     // copy has a copy of the DBRefEntry
451     // this is murky - DBrefs are only copied for dataset sequences
452     // where the test for 'dataset sequence' is 'dataset is null'
453     // but that doesn't distinguish it from an aligned sequence
454     // which has not yet generated a dataset sequence
455     // NB getDBRef looks inside dataset sequence if not null
456     DBRefEntry[] dbrefs = copy.getDBRefs();
457     assertEquals(1, dbrefs.length);
458     assertFalse(dbrefs[0] == seq1.getDBRefs()[0]);
459     assertTrue(dbrefs[0].equals(seq1.getDBRefs()[0]));
460   }
461
462   @Test(groups = { "Functional" })
463   public void testCopyConstructor_withDataset()
464   {
465     SequenceI seq1 = new Sequence("Seq1", "AB-C.D EF");
466     seq1.createDatasetSequence();
467     seq1.setDescription("description");
468     seq1.addAlignmentAnnotation(new AlignmentAnnotation("label", "desc",
469             1.3d));
470     seq1.addSequenceFeature(new SequenceFeature("type", "desc", 22, 33,
471             12.4f, "group"));
472     seq1.addPDBId(new PDBEntry("1A70", "B", Type.PDB, "File"));
473     // here we add DBRef to the dataset sequence:
474     seq1.getDatasetSequence().addDBRef(
475             new DBRefEntry("EMBL", "1.2", "AZ12345"));
476
477     SequenceI copy = new Sequence(seq1);
478
479     assertNotNull(copy.getDatasetSequence());
480     assertSame(copy.getDatasetSequence(), seq1.getDatasetSequence());
481
482     verifyCopiedSequence(seq1, copy);
483
484     // getDBRef looks inside dataset sequence and this is shared,
485     // so holds the same dbref objects
486     DBRefEntry[] dbrefs = copy.getDBRefs();
487     assertEquals(1, dbrefs.length);
488     assertSame(dbrefs[0], seq1.getDBRefs()[0]);
489   }
490
491   /**
492    * Helper to make assertions about a copied sequence
493    * 
494    * @param seq1
495    * @param copy
496    */
497   protected void verifyCopiedSequence(SequenceI seq1, SequenceI copy)
498   {
499     // verify basic properties:
500     assertEquals(copy.getName(), seq1.getName());
501     assertEquals(copy.getDescription(), seq1.getDescription());
502     assertEquals(copy.getStart(), seq1.getStart());
503     assertEquals(copy.getEnd(), seq1.getEnd());
504     assertEquals(copy.getSequenceAsString(), seq1.getSequenceAsString());
505
506     // copy has a copy of the annotation:
507     AlignmentAnnotation[] anns = copy.getAnnotation();
508     assertEquals(1, anns.length);
509     assertFalse(anns[0] == seq1.getAnnotation()[0]);
510     assertEquals(anns[0].label, seq1.getAnnotation()[0].label);
511     assertEquals(anns[0].description, seq1.getAnnotation()[0].description);
512     assertEquals(anns[0].score, seq1.getAnnotation()[0].score);
513
514     // copy has a copy of the sequence feature:
515     SequenceFeature[] sfs = copy.getSequenceFeatures();
516     assertEquals(1, sfs.length);
517     assertFalse(sfs[0] == seq1.getSequenceFeatures()[0]);
518     assertTrue(sfs[0].equals(seq1.getSequenceFeatures()[0]));
519
520     // copy has a copy of the PDB entry
521     Vector<PDBEntry> pdbs = copy.getAllPDBEntries();
522     assertEquals(1, pdbs.size());
523     assertFalse(pdbs.get(0) == seq1.getAllPDBEntries().get(0));
524     assertTrue(pdbs.get(0).equals(seq1.getAllPDBEntries().get(0)));
525   }
526
527   @Test(groups = "Functional")
528   public void testGetCharAt()
529   {
530     SequenceI sq = new Sequence("", "abcde");
531     assertEquals('a', sq.getCharAt(0));
532     assertEquals('e', sq.getCharAt(4));
533     assertEquals(' ', sq.getCharAt(5));
534     assertEquals(' ', sq.getCharAt(-1));
535   }
536 }