JAL-3253 jalview.bin.Instance handles all singleton instances -
[jalview.git] / test / jalview / gui / FreeUpMemoryTest.java
1 package jalview.gui;
2
3 import static org.testng.Assert.assertEquals;
4 import static org.testng.Assert.assertNotNull;
5 import static org.testng.Assert.assertTrue;
6
7 import jalview.analysis.AlignmentGenerator;
8 import jalview.bin.Cache;
9 import jalview.bin.Instance;
10 import jalview.bin.Jalview;
11 import jalview.datamodel.AlignmentI;
12 import jalview.datamodel.SequenceGroup;
13 import jalview.io.DataSourceType;
14 import jalview.io.FileLoader;
15
16 import java.awt.event.MouseEvent;
17 import java.io.File;
18 import java.io.IOException;
19 import java.io.PrintStream;
20
21 import org.testng.annotations.BeforeClass;
22 import org.testng.annotations.Test;
23
24 import junit.extensions.PA;
25
26 public class FreeUpMemoryTest
27 {
28   private static final int ONE_MB = 1000 * 1000;
29
30   /**
31    * Configure (read-only) Jalview property settings for test
32    */
33   @BeforeClass(alwaysRun = true)
34   public void setUp()
35   {
36     Jalview.main(new String[] { "-nonews", "-props",
37         "test/jalview/testProps.jvprops" });
38     String True = Boolean.TRUE.toString();
39     Cache.setPropertyNoSave("SHOW_ANNOTATIONS", True);
40     Cache.setPropertyNoSave("SHOW_QUALITY", True);
41     Cache.setPropertyNoSave("SHOW_CONSERVATION", True);
42     Cache.setPropertyNoSave("SHOW_OCCUPANCY", True);
43     Cache.setPropertyNoSave("SHOW_IDENTITY", True);
44   }
45
46   /**
47    * A simple test that memory is released when all windows are closed.
48    * <ul>
49    * <li>generates a reasonably large alignment and loads it</li>
50    * <li>performs various operations on the alignment</li>
51    * <li>closes all windows</li>
52    * <li>requests garbage collection</li>
53    * <li>asserts that the remaining memory footprint (heap usage) is 'not large'
54    * </li>
55    * </ul>
56    * If the test fails, this suggests that a reference to some large object
57    * (perhaps the alignment data, or some annotation / Tree / PCA data) has
58    * failed to be garbage collected. If this is the case, the heap will need to
59    * be inspected manually (suggest using jvisualvm) in order to track down
60    * where large objects are still referenced. The code (for example
61    * AlignmentViewport.dispose()) should then be updated to ensure references to
62    * large objects are set to null when they are no longer required.
63    * 
64    * @throws IOException
65    */
66   @Test(groups = "Memory")
67   public void testFreeMemoryOnClose() throws IOException
68   {
69     File f = generateAlignment();
70     f.deleteOnExit();
71
72     doStuffInJalview(f);
73
74     Instance.getDesktop().closeAll_actionPerformed(null);
75
76     checkUsedMemory(35L);
77   }
78
79   /**
80    * Requests garbage collection and then checks whether remaining memory in use
81    * is less than the expected value (in Megabytes)
82    * 
83    * @param expectedMax
84    */
85   protected void checkUsedMemory(long expectedMax)
86   {
87     /*
88      * request garbage collection and wait for it to run;
89      * NB there is no guarantee when, or whether, it will do so
90      * wait time depends on JRE/processor, generous allowance here  
91      */
92     System.gc();
93     waitFor(1500);
94
95     /*
96      * a second gc() call should not be necessary - but it is!
97      * the test passes with it, and fails without it
98      */
99     System.gc();
100     waitFor(1500);
101
102     /*
103      * check used memory is 'reasonably low'
104      */
105     long availableMemory = Runtime.getRuntime().totalMemory() / ONE_MB;
106     long freeMemory = Runtime.getRuntime().freeMemory() / ONE_MB;
107     long usedMemory = availableMemory - freeMemory;
108
109     /*
110      * sanity check - fails if any frame was added after
111      * closeAll_actionPerformed
112      */
113     assertEquals(Instance.getDesktop().getAllFrames().length, 0);
114
115     /*
116      * if this assertion fails
117      * - set a breakpoint here
118      * - run jvisualvm to inspect a heap dump of Jalview
119      * - identify large objects in the heap and their referers
120      * - fix code as necessary to null the references on close
121      */
122     System.out.println("Used memory after gc = " + usedMemory + "MB");
123     assertTrue(usedMemory < expectedMax, String.format(
124             "Used memory %d should be less than %d (Recommend running test manually to verify)",
125             usedMemory,
126             expectedMax));
127   }
128
129   /**
130    * Loads an alignment from file and exercises various operations in Jalview
131    * 
132    * @param f
133    */
134   protected void doStuffInJalview(File f)
135   {
136     /*
137      * load alignment, wait for consensus and other threads to complete
138      */
139     AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(f.getPath(),
140             DataSourceType.FILE);
141     while (af.getViewport().isCalcInProgress())
142     {
143       waitFor(200);
144     }
145
146     /*
147      * open an Overview window
148      */
149     af.overviewMenuItem_actionPerformed(null);
150     assertNotNull(af.alignPanel.overviewPanel);
151
152     /*
153      * exercise the pop-up menu in the Overview Panel (JAL-2864)
154      */
155     Object[] args = new Object[] {
156         new MouseEvent(af, 0, 0, 0, 0, 0, 1, true) };
157     PA.invokeMethod(af.alignPanel.overviewPanel,
158             "showPopupMenu(java.awt.event.MouseEvent)", args);
159
160     /*
161      * set a selection group - potential memory leak if it retains
162      * a reference to the alignment
163      */
164     SequenceGroup sg = new SequenceGroup();
165     sg.setStartRes(0);
166     sg.setEndRes(100);
167     AlignmentI al = af.viewport.getAlignment();
168     for (int i = 0; i < al.getHeight(); i++)
169     {
170       sg.addSequence(al.getSequenceAt(i), false);
171     }
172     af.viewport.setSelectionGroup(sg);
173
174     /*
175      * compute Tree and PCA (on all sequences, 100 columns)
176      */
177     af.openTreePcaDialog();
178     CalculationChooser dialog = af.alignPanel.getCalculationDialog();
179     dialog.openPcaPanel("BLOSUM62", dialog.getSimilarityParameters(true));
180     dialog.openTreePanel("BLOSUM62", dialog.getSimilarityParameters(false));
181
182     /*
183      * wait until Tree and PCA have been computed
184      */
185     while (af.viewport.getCurrentTree() == null
186             && dialog.getPcaPanel().isWorking())
187     {
188       waitFor(10);
189     }
190
191     /*
192      * give Swing time to add the PCA panel (?!?)
193      */
194     waitFor(100);
195   }
196
197   /**
198    * Wait for waitMs miliseconds
199    * 
200    * @param waitMs
201    */
202   protected void waitFor(int waitMs)
203   {
204     try
205     {
206       Thread.sleep(waitMs);
207     } catch (InterruptedException e)
208     {
209     }
210   }
211
212   /**
213    * Generates an alignment and saves it in a temporary file, to be loaded by
214    * Jalview. We use a peptide alignment (so Conservation and Quality are
215    * calculated), which is wide enough to ensure Consensus, Conservation and
216    * Occupancy have a significant memory footprint (if not removed from the
217    * heap).
218    * 
219    * @return
220    * @throws IOException
221    */
222   private File generateAlignment() throws IOException
223   {
224     File f = File.createTempFile("MemoryTest", "fa");
225     PrintStream ps = new PrintStream(f);
226     AlignmentGenerator ag = new AlignmentGenerator(false, ps);
227     int width = 100000;
228     int height = 100;
229     ag.generate(width, height, 0, 10, 15);
230     return f;
231   }
232 }