JAL-2727 first pass of test of memory recovery on close all
[jalview.git] / test / jalview / gui / FreeUpMemoryTest.java
diff --git a/test/jalview/gui/FreeUpMemoryTest.java b/test/jalview/gui/FreeUpMemoryTest.java
new file mode 100644 (file)
index 0000000..2a9a8c9
--- /dev/null
@@ -0,0 +1,146 @@
+package jalview.gui;
+
+import static org.testng.Assert.assertTrue;
+
+import jalview.analysis.AlignmentGenerator;
+import jalview.bin.Cache;
+import jalview.bin.Jalview;
+import jalview.io.DataSourceType;
+import jalview.io.FileLoader;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintStream;
+
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+public class FreeUpMemoryTest
+{
+  private static final int ONE_MB = 1024 * 1024;
+
+  /**
+   * Configure (read-only) Jalview property settings for test
+   */
+  @BeforeClass(alwaysRun = true)
+  public void setUp()
+  {
+    Jalview.main(new String[] { "-nonews", "-props",
+        "test/jalview/testProps.jvprops" });
+    Cache.applicationProperties.setProperty("SHOW_ANNOTATIONS",
+            Boolean.TRUE.toString());
+    Cache.applicationProperties.setProperty("SHOW_QUALITY",
+            Boolean.TRUE.toString());
+    Cache.applicationProperties.setProperty("SHOW_CONSERVATION",
+            Boolean.TRUE.toString());
+    Cache.applicationProperties.setProperty("SHOW_OCCUPANCY",
+            Boolean.TRUE.toString());
+    Cache.applicationProperties.setProperty("SHOW_IDENTITY",
+            Boolean.TRUE.toString());
+  }
+
+  /**
+   * A simple test that memory is released when all windows are closed.
+   * <ul>
+   * <li>generates a reasonably large alignment and loads it</li>
+   * <li>performs various operations on the alignment</li>
+   * <li>closes all windows</li>
+   * <li>requests garbage collection</li>
+   * <li>asserts that the remaining memory footprint (heap usage) is 'not large'
+   * </li>
+   * </ul>
+   * If the test fails, this suggests that a reference to some large object
+   * (perhaps the alignment data, or consensus profile) has failed to be garbage
+   * collected. If this is the case, the heap will need to be inspected manually
+   * (suggest using jvisualvm) in order to track down where large objects are
+   * still referenced. The code (for example AlignmentViewport.dispose()) should
+   * then be updated to ensure references to large objects are set to null when
+   * they are no longer required.
+   * 
+   * @throws IOException
+   */
+  @Test(groups = "Memory")
+  public void testFreeMemoryOnClose() throws IOException
+  {
+    File f = generateAlignment();
+    f.deleteOnExit();
+
+    /*
+     * load alignment, wait for consensus and other threads to complete
+     */
+    AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(f.getPath(),
+            DataSourceType.FILE);
+    waitForThreads(af.getViewport());
+
+    af.closeMenuItem_actionPerformed(true);
+
+    /*
+     * request garbage collection and allow 1 second for it to complete;
+     * NB there is no guarantee when, or whether, it will run!
+     */
+    System.gc();
+    synchronized (this)
+    {
+      try
+      {
+        wait(1000);
+      } catch (InterruptedException e)
+      {
+      }
+    }
+
+    /*
+     * check used memory is 'reasonably low'
+     */
+    long availableMemory = Runtime.getRuntime().totalMemory() / ONE_MB;
+    long freeMemory = Runtime.getRuntime().freeMemory() / ONE_MB;
+    long usedMemory = availableMemory - freeMemory;
+    System.out.println("Memory in use after close all windows: "
+            + usedMemory + "MB");
+
+    /*
+     * if this assertion fails
+     * - set a breakpoint here
+     * - run jvisualvm to inspect a heap dump of Jalview
+     * - identify large objects in the heap and their referers
+     * - fix code as necessary to null the references on close
+     */
+    long expectedMax = 100L;
+    assertTrue(usedMemory < expectedMax,
+            String.format("Used memory %d > %d", usedMemory, expectedMax));
+  }
+
+  /**
+   * wait for consensus etc thread to complete
+   * 
+   * @param av
+   */
+  protected void waitForThreads(AlignViewport av)
+  {
+    while (av.isCalcInProgress())
+    {
+      try
+      {
+        Thread.sleep(200);
+      } catch (Exception x)
+      {
+      }
+    }
+  }
+
+  /**
+   * Generates an alignment (large enough for this test but not so large it is
+   * too slow or runs out of memory) and saves it in a temporary file.
+   * 
+   * @return
+   * @throws IOException
+   */
+  private File generateAlignment() throws IOException
+  {
+    File f = File.createTempFile("MemoryTest", "fa");
+    PrintStream ps = new PrintStream(f);
+    AlignmentGenerator ag = new AlignmentGenerator(false, ps);
+    ag.generate(1000, 20000, 0, 10, 15);
+    return f;
+  }
+}