JAL-3840 failing test - show that addGaps overflows
[jalview.git] / test / jalview / datamodel / ResidueCountTest.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.Assert.assertEquals;
24 import static org.testng.Assert.assertFalse;
25 import static org.testng.Assert.assertTrue;
26
27 import jalview.datamodel.ResidueCount.SymbolCounts;
28 import jalview.gui.JvOptionPane;
29
30 import org.junit.Assert;
31 import org.testng.annotations.BeforeClass;
32 import org.testng.annotations.Test;
33
34 public class ResidueCountTest
35 {
36
37   @BeforeClass(alwaysRun = true)
38   public void setUpJvOptionPane()
39   {
40     JvOptionPane.setInteractiveMode(false);
41     JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
42   }
43
44   /**
45    * Test a mix of add and put for nucleotide counting
46    */
47   @Test(groups = "Functional")
48   public void test_countNucleotide()
49   {
50     ResidueCount rc = new ResidueCount(true);
51     assertEquals(rc.getCount('A'), 0);
52     assertEquals(rc.getGapCount(), 0);
53     // add then add
54     assertEquals(rc.add('A'), 1);
55     assertEquals(rc.add('a'), 2);
56     // put then add
57     rc.put('g', 3);
58     assertEquals(rc.add('G'), 4);
59     // add then put
60     assertEquals(rc.add('c'), 1);
61     rc.put('C', 4);
62     assertEquals(rc.add('N'), 1);
63
64     assertEquals(rc.getCount('a'), 2);
65     assertEquals(rc.getCount('A'), 2);
66     assertEquals(rc.getCount('G'), 4);
67     assertEquals(rc.getCount('c'), 4);
68     assertEquals(rc.getCount('T'), 0); // never seen
69     assertEquals(rc.getCount('N'), 1);
70     assertEquals(rc.getCount('?'), 0);
71     assertEquals(rc.getCount('-'), 0);
72
73     assertFalse(rc.isCountingInts());
74     assertFalse(rc.isUsingOtherData());
75   }
76
77   /**
78    * Test adding to gap count (either using addGap or add)
79    */
80   @Test(groups = "Functional")
81   public void testAddGap()
82   {
83     ResidueCount rc = new ResidueCount(true);
84     rc.addGap();
85     rc.add('-');
86     rc.add('.');
87     rc.add(' ');
88     
89     assertEquals(rc.getGapCount(), 4);
90     assertEquals(rc.getCount(' '), 4);
91     assertEquals(rc.getCount('-'), 4);
92     assertEquals(rc.getCount('.'), 4);
93     assertFalse(rc.isUsingOtherData());
94     assertFalse(rc.isCountingInts());
95     
96     rc.set(ResidueCount.GAP_COUNT, Short.MAX_VALUE-2);
97     assertEquals(rc.getGapCount(), Short.MAX_VALUE-2);
98     assertFalse(rc.isCountingInts());
99     rc.addGap();
100     assertEquals(rc.getGapCount(), Short.MAX_VALUE-1);
101     assertFalse(rc.isCountingInts());
102     rc.addGap();
103     assertEquals(rc.getGapCount(), Short.MAX_VALUE);
104     rc.addGap();
105     assertTrue(rc.isCountingInts());
106     assertEquals(rc.getGapCount(), Short.MAX_VALUE+1);
107   }
108
109   @Test(groups = "Functional")
110   public void testOverflow()
111   {
112     /*
113      * overflow from add
114      */
115     ResidueCount rc = new ResidueCount(true);
116     rc.addGap();
117     rc.put('A', Short.MAX_VALUE - 1);
118     assertFalse(rc.isCountingInts());
119     rc.add('A');
120     assertFalse(rc.isCountingInts());
121     rc.add('A');
122     assertTrue(rc.isCountingInts());
123     assertEquals(rc.getCount('a'), Short.MAX_VALUE + 1);
124     rc.add('A');
125     assertTrue(rc.isCountingInts());
126     assertEquals(rc.getCount('a'), Short.MAX_VALUE + 2);
127     assertEquals(rc.getGapCount(), 1);
128     rc.addGap();
129     assertEquals(rc.getGapCount(), 2);
130
131     /*
132      * overflow from put
133      */
134     rc = new ResidueCount(true);
135     rc.put('G', Short.MAX_VALUE + 1);
136     assertTrue(rc.isCountingInts());
137     assertEquals(rc.getCount('g'), Short.MAX_VALUE + 1);
138     rc.put('G', 1);
139     assertTrue(rc.isCountingInts());
140     assertEquals(rc.getCount('g'), 1);
141
142     /*
143      * underflow from put
144      */
145     rc = new ResidueCount(true);
146     rc.put('G', Short.MIN_VALUE - 1);
147     assertTrue(rc.isCountingInts());
148     assertEquals(rc.getCount('g'), Short.MIN_VALUE - 1);
149   }
150
151   /**
152    * Test a mix of add and put for peptide counting
153    */
154   @Test(groups = "Functional")
155   public void test_countPeptide()
156   {
157     ResidueCount rc = new ResidueCount(false);
158     rc.put('q', 4);
159     rc.add('Q');
160     rc.add('X');
161     rc.add('x');
162     rc.add('W');
163     rc.put('w', 7);
164     rc.put('m', 12);
165     rc.put('M', 13);
166
167     assertEquals(rc.getCount('q'), 5);
168     assertEquals(rc.getCount('X'), 2);
169     assertEquals(rc.getCount('W'), 7);
170     assertEquals(rc.getCount('m'), 13);
171     assertEquals(rc.getCount('G'), 0);
172     assertEquals(rc.getCount('-'), 0);
173
174     assertFalse(rc.isCountingInts());
175     assertFalse(rc.isUsingOtherData());
176   }
177
178   @Test(groups = "Functional")
179   public void test_unexpectedPeptide()
180   {
181     ResidueCount rc = new ResidueCount(false);
182     // expected characters (upper or lower case):
183     String aas = "ACDEFGHIKLMNPQRSTVWXY";
184     String lower = aas.toLowerCase();
185     for (int i = 0; i < aas.length(); i++)
186     {
187       rc.put(aas.charAt(i), i);
188       rc.add(lower.charAt(i));
189     }
190     for (int i = 0; i < aas.length(); i++)
191     {
192       assertEquals(rc.getCount(aas.charAt(i)), i + 1);
193     }
194     assertFalse(rc.isUsingOtherData());
195
196     rc.put('J', 4);
197     assertTrue(rc.isUsingOtherData());
198     assertEquals(rc.getCount('J'), 4);
199     rc.add('j');
200     assertEquals(rc.getCount('J'), 5);
201   }
202
203   @Test(groups = "Functional")
204   public void test_unexpectedNucleotide()
205   {
206     ResidueCount rc = new ResidueCount(true);
207     // expected characters (upper or lower case):
208     String nucs = "ACGTUN";
209     String lower = nucs.toLowerCase();
210     for (int i = 0; i < nucs.length(); i++)
211     {
212       rc.put(nucs.charAt(i), i);
213       rc.add(lower.charAt(i));
214     }
215     for (int i = 0; i < nucs.length(); i++)
216     {
217       assertEquals(rc.getCount(nucs.charAt(i)), i + 1);
218     }
219     assertFalse(rc.isUsingOtherData());
220
221     rc.add('J');
222     assertTrue(rc.isUsingOtherData());
223   }
224
225   @Test(groups = "Functional")
226   public void testGetModalCount()
227   {
228     ResidueCount rc = new ResidueCount(true);
229     rc.add('c');
230     rc.add('g');
231     rc.add('c');
232     assertEquals(rc.getModalCount(), 2);
233
234     // modal count is in the 'short overflow' counts
235     rc = new ResidueCount();
236     rc.add('c');
237     rc.put('g', Short.MAX_VALUE);
238     rc.add('G');
239     assertEquals(rc.getModalCount(), Short.MAX_VALUE + 1);
240
241     // modal count is in the 'other data' counts
242     rc = new ResidueCount(false);
243     rc.add('Q');
244     rc.add('{');
245     rc.add('{');
246     assertEquals(rc.getModalCount(), 2);
247
248     // verify modal count excludes gap
249     rc = new ResidueCount();
250     rc.add('Q');
251     rc.add('P');
252     rc.add('Q');
253     rc.addGap();
254     rc.addGap();
255     rc.addGap();
256     assertEquals(rc.getModalCount(), 2);
257   }
258
259   @Test(groups = "Functional")
260   public void testGetResiduesForCount()
261   {
262     ResidueCount rc = new ResidueCount(true);
263     rc.add('c');
264     rc.add('g');
265     rc.add('c');
266     assertEquals(rc.getResiduesForCount(2), "C");
267     assertEquals(rc.getResiduesForCount(1), "G");
268     assertEquals(rc.getResiduesForCount(3), "");
269     assertEquals(rc.getResiduesForCount(0), "");
270     assertEquals(rc.getResiduesForCount(-1), "");
271
272     // modal count is in the 'short overflow' counts
273     rc = new ResidueCount(true);
274     rc.add('c');
275     rc.put('g', Short.MAX_VALUE);
276     rc.add('G');
277     assertEquals(rc.getResiduesForCount(Short.MAX_VALUE + 1), "G");
278     assertEquals(rc.getResiduesForCount(1), "C");
279
280     // peptide modal count is in the 'short overflow' counts
281     rc = new ResidueCount(false);
282     rc.add('c');
283     rc.put('p', Short.MAX_VALUE);
284     rc.add('P');
285     assertEquals(rc.getResiduesForCount(Short.MAX_VALUE + 1), "P");
286     assertEquals(rc.getResiduesForCount(1), "C");
287   
288     // modal count is in the 'other data' counts
289     rc = new ResidueCount();
290     rc.add('Q');
291     rc.add('{');
292     rc.add('{');
293     assertEquals(rc.getResiduesForCount(1), "Q");
294     assertEquals(rc.getResiduesForCount(2), "{");
295
296     // residues share modal count
297     rc = new ResidueCount();
298     rc.add('G');
299     rc.add('G');
300     rc.add('c');
301     rc.add('C');
302     rc.add('U');
303     assertEquals(rc.getResiduesForCount(1), "U");
304     assertEquals(rc.getResiduesForCount(2), "CG");
305
306     // expected and unexpected symbols share modal count
307     rc = new ResidueCount();
308     rc.add('G');
309     rc.add('t');
310     rc.add('[');
311     rc.add('[');
312     rc.add('t');
313     rc.add('G');
314     rc.add('c');
315     rc.add('C');
316     rc.add('U');
317     assertEquals(rc.getResiduesForCount(1), "U");
318     assertEquals(rc.getResiduesForCount(2), "CGT[");
319   }
320
321   @Test(groups = "Functional")
322   public void testGetSymbolCounts_nucleotide()
323   {
324     ResidueCount rc = new ResidueCount(true);
325     rc.add('g');
326     rc.add('c');
327     rc.add('G');
328     rc.add('J'); // 'otherData'
329     rc.add('g');
330     rc.add('N');
331     rc.put('[', 0); // 'otherdata'
332
333     SymbolCounts sc = rc.getSymbolCounts();
334     Assert.assertArrayEquals(new char[] { 'C', 'G', 'N', 'J', '[' },
335             sc.symbols);
336     Assert.assertArrayEquals(new int[] { 1, 3, 1, 1, 0 }, sc.values);
337
338     // now with overflow to int counts
339     rc.put('U', Short.MAX_VALUE);
340     rc.add('u');
341     sc = rc.getSymbolCounts();
342     Assert.assertArrayEquals(new char[] { 'C', 'G', 'N', 'U', 'J', '[' },
343             sc.symbols);
344     Assert.assertArrayEquals(new int[] { 1, 3, 1, 32768, 1, 0 }, sc.values);
345   }
346
347   @Test(groups = "Functional")
348   public void testGetSymbolCounts_peptide()
349   {
350     ResidueCount rc = new ResidueCount(false);
351     rc.add('W');
352     rc.add('q');
353     rc.add('W');
354     rc.add('Z'); // 'otherData'
355     rc.add('w');
356     rc.add('L');
357
358     SymbolCounts sc = rc.getSymbolCounts();
359     Assert.assertArrayEquals(new char[] { 'L', 'Q', 'W', 'Z' }, sc.symbols);
360     Assert.assertArrayEquals(new int[] { 1, 1, 3, 1 }, sc.values);
361
362     // now with overflow to int counts
363     rc.put('W', Short.MAX_VALUE);
364     rc.add('W');
365     sc = rc.getSymbolCounts();
366     Assert.assertArrayEquals(new char[] { 'L', 'Q', 'W', 'Z' }, sc.symbols);
367     Assert.assertArrayEquals(new int[] { 1, 1, 32768, 1 }, sc.values);
368   }
369
370   @Test(groups = "Functional")
371   public void testToString()
372   {
373     ResidueCount rc = new ResidueCount();
374     rc.add('q');
375     rc.add('c');
376     rc.add('Q');
377     assertEquals(rc.toString(), "[ C:1 Q:2 ]");
378
379     // add 'other data'
380     rc.add('{');
381     assertEquals(rc.toString(), "[ C:1 Q:2 {:1 ]");
382
383     // switch from short to int counting:
384     rc.put('G', Short.MAX_VALUE);
385     rc.add('g');
386     assertEquals(rc.toString(), "[ C:1 G:32768 Q:2 {:1 ]");
387   }
388
389   @Test(groups = "Functional")
390   public void testGetTooltip()
391   {
392     ResidueCount rc = new ResidueCount();
393
394     // no counts!
395     assertEquals(rc.getTooltip(20, 1), "");
396
397     /*
398      * count 7 C, 6 K, 7 Q, 10 P, 9 W, 1 F (total 40)
399      */
400     for (int i = 0; i < 7; i++)
401     {
402       rc.add('c');
403       rc.add('q');
404     }
405     for (int i = 0; i < 10; i++)
406     {
407       rc.add('p');
408     }
409     for (int i = 0; i < 9; i++)
410     {
411       rc.add('W');
412     }
413     for (int i = 0; i < 6; i++)
414     {
415       rc.add('K');
416     }
417     rc.add('F');
418     
419     /*
420      * percentages are rounded (0.5 rounded up)
421      * 10/40 9/40 7/40 6/40 1/40
422      */
423     assertEquals(rc.getTooltip(40, 0),
424             "P 25%; W 23%; C 18%; Q 18%; K 15%; F 3%");
425
426     rc.add('Q');
427     /*
428      * 10/30 9/30 8/30 7/30 6/30 1/30
429      */
430     assertEquals(rc.getTooltip(30, 1),
431             "P 33.3%; W 30.0%; Q 26.7%; C 23.3%; K 20.0%; F 3.3%");
432   }
433
434   @Test(groups = "Functional")
435   public void testPut()
436   {
437     ResidueCount rc = new ResidueCount();
438     rc.put('q', 3);
439     assertEquals(rc.getCount('Q'), 3);
440     rc.put(' ', 4);
441     assertEquals(rc.getGapCount(), 4);
442     rc.put('.', 5);
443     assertEquals(rc.getGapCount(), 5);
444     rc.put('-', 6);
445     assertEquals(rc.getGapCount(), 6);
446
447     rc.put('?', 5);
448     assertEquals(rc.getCount('?'), 5);
449     rc.put('?', 6);
450     rc.put('!', 7);
451     assertEquals(rc.getCount('?'), 6);
452     assertEquals(rc.getCount('!'), 7);
453   }
454 }