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