Merge branch 'bug/JAL-1988_JAL-3772_improved_quit_handling' into ben-big-merge
authorBen Soares <b.soares@dundee.ac.uk>
Tue, 8 Nov 2022 17:47:25 +0000 (17:47 +0000)
committerBen Soares <b.soares@dundee.ac.uk>
Tue, 8 Nov 2022 17:47:25 +0000 (17:47 +0000)
src/jalview/gui/JvOptionPane.java
src/jalview/gui/QuitHandler.java
src/jalview/project/Jalview2XML.java
test/jalview/gui/QuitHandlerTest.java [new file with mode: 0644]
test/jalview/gui/quitProps.jvprops [new file with mode: 0644]

index 364d4c1..5702aa0 100644 (file)
@@ -803,6 +803,7 @@ public class JvOptionPane extends JOptionPane
     if (!isInteractiveMode())
     {
       handleResponse(getMockResponse());
+      return;
     }
     // two uses:
     //
@@ -1149,6 +1150,10 @@ public class JvOptionPane extends JOptionPane
   public static int showDialogOnTop(String label, String actionString,
           int JOPTIONPANE_OPTION, int JOPTIONPANE_MESSAGETYPE)
   {
+    if (!isInteractiveMode())
+    {
+      return (int) getMockResponse();
+    }
     // Ensure Jalview window is brought to front (primarily for Quit
     // confirmation window to be visible)
 
@@ -1196,6 +1201,11 @@ public class JvOptionPane extends JOptionPane
           int JOPTIONPANE_MESSAGETYPE, Icon icon, Object[] options,
           Object initialValue, boolean modal, JButton[] buttons)
   {
+    if (!isInteractiveMode())
+    {
+      handleResponse(getMockResponse());
+      return;
+    }
     // Ensure Jalview window is brought to front (primarily for Quit
     // confirmation window to be visible)
 
@@ -1280,6 +1290,11 @@ public class JvOptionPane extends JOptionPane
           Object[] options, Object initialValue, boolean modal,
           JButton[] buttons)
   {
+    if (!isInteractiveMode())
+    {
+      handleResponse(getMockResponse());
+      return null;
+    }
     JButton[] optionsButtons = null;
     Object initialValueButton = null;
     JOptionPane joptionpane = new JOptionPane();
index e90a2d5..77eed81 100644 (file)
@@ -29,10 +29,12 @@ import jalview.util.Platform;
 
 public class QuitHandler
 {
-  private static final int MIN_WAIT_FOR_SAVE = 3000;
+  private static final int MIN_WAIT_FOR_SAVE = 1000;
 
   private static final int MAX_WAIT_FOR_SAVE = 20000;
 
+  private static boolean interactive = true;
+
   public static enum QResponse
   {
     NULL, QUIT, CANCEL_QUIT, FORCE_QUIT
@@ -67,7 +69,7 @@ public class QuitHandler
 
   private static QResponse gotQuitResponse = QResponse.NULL;
 
-  private static QResponse setResponse(QResponse qresponse)
+  protected static QResponse setResponse(QResponse qresponse)
   {
     gotQuitResponse = qresponse;
     return qresponse;
@@ -81,7 +83,7 @@ public class QuitHandler
   public static final Callable<Void> defaultCancelQuit = () -> {
     Console.debug("QuitHandler: (default) Quit action CANCELLED by user");
     // reset
-    setResponse(QResponse.NULL);
+    setResponse(QResponse.CANCEL_QUIT);
     return null;
   };
 
@@ -105,8 +107,6 @@ public class QuitHandler
             defaultCancelQuit);
   }
 
-  private static boolean interactive = true;
-
   public static QResponse getQuitResponse(boolean ui, Callable<Void> okQuit,
           Callable<Void> forceQuit, Callable<Void> cancelQuit)
   {
@@ -147,14 +147,14 @@ public class QuitHandler
     {
       JvOptionPane.newOptionDialog()
               .setResponseHandler(JOptionPane.YES_OPTION, defaultOkQuit)
-              .setResponseHandler(JOptionPane.NO_OPTION, defaultCancelQuit)
+              .setResponseHandler(JOptionPane.NO_OPTION, cancelQuit)
               .showDialogOnTopAsync(
                       new StringBuilder(MessageManager
                               .getString("label.quit_jalview"))
-                                      .append("\n")
-                                      .append(MessageManager.getString(
-                                              "label.unsaved_changes"))
-                                      .toString(),
+                              .append("\n")
+                              .append(MessageManager
+                                      .getString("label.unsaved_changes"))
+                              .toString(),
                       MessageManager.getString("action.quit"),
                       JOptionPane.YES_NO_OPTION,
                       JOptionPane.QUESTION_MESSAGE, null, new Object[]
@@ -168,6 +168,7 @@ public class QuitHandler
     if (got == QResponse.CANCEL_QUIT)
     {
       // reset
+      Console.debug("Cancelling quit.  Resetting response to NULL");
       setResponse(QResponse.NULL);
       // but return cancel
       return QResponse.CANCEL_QUIT;
index 9cec063..c8f9be6 100644 (file)
@@ -625,7 +625,7 @@ public class Jalview2XML
 
     if (Cache.getDefault("DEBUG_DELAY_SAVE", false))
     {
-      int n = 20;
+      int n = debugDelaySave;
       int i = 0;
       while (i < n)
       {
@@ -791,7 +791,7 @@ public class Jalview2XML
 
       if (Cache.getDefault("DEBUG_DELAY_SAVE", false))
       {
-        int n = 20;
+        int n = debugDelaySave;
         int i = 0;
         while (i < n)
         {
@@ -6614,4 +6614,12 @@ public class Jalview2XML
     }
     return true;
   }
+
+  // used for debugging and tests
+  private static int debugDelaySave = 20;
+
+  public static void setDebugDelaySave(int n)
+  {
+    debugDelaySave = n;
+  }
 }
diff --git a/test/jalview/gui/QuitHandlerTest.java b/test/jalview/gui/QuitHandlerTest.java
new file mode 100644 (file)
index 0000000..2caa8ec
--- /dev/null
@@ -0,0 +1,268 @@
+package jalview.gui;
+
+import static org.testng.Assert.assertNotNull;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import jalview.bin.Cache;
+import jalview.bin.Jalview;
+import jalview.gui.QuitHandler.QResponse;
+import jalview.io.DataSourceType;
+import jalview.io.FileFormat;
+import jalview.io.FileLoader;
+import jalview.project.Jalview2XML;
+
+@Test(singleThreaded = true)
+public class QuitHandlerTest
+{
+  private static String saveProjectFile = "test-output/tempSaveFile.jvp";
+
+  private static String saveFastaFile = "test-output/tempSaveFile.fa";
+
+  @BeforeClass(alwaysRun = true)
+  public void setUpJvOptionPane()
+  {
+    JvOptionPane.setInteractiveMode(false);
+    JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
+    Jalview2XML.setDebugDelaySave(3);
+  }
+
+  /**
+   * @throws java.lang.Exception
+   */
+  @BeforeClass(alwaysRun = true)
+  public static void setUpBeforeClass() throws Exception
+  {
+    Cache.loadProperties("test/jalview/gui/quitProps.jvprops");
+
+    /*
+     * set news feed last read to a future time to ensure no
+     * 'unread' news item is displayed
+     */
+    Date oneHourFromNow = new Date(
+            System.currentTimeMillis() + 3600 * 1000);
+    Cache.setDateProperty("JALVIEW_NEWS_RSS_LASTMODIFIED", oneHourFromNow);
+
+    Jalview.main(
+            new String[]
+            { "-nowebservicediscovery", "-nosplash", "-nonews" });
+  }
+
+  @AfterClass(alwaysRun = true)
+  public static void resetProps()
+  {
+    // reset quit response
+    QuitHandler.setResponse(QResponse.NULL);
+    // reset mock response
+    JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
+    // close desktop windows/frames
+    jalview.gui.Desktop.instance.closeAll_actionPerformed(null);
+    // reset debug delay
+    Jalview2XML.setDebugDelaySave(20);
+  }
+
+  @BeforeMethod(alwaysRun = true)
+  public static void tearDownAfterClass() throws Exception
+  {
+    // reset quit response
+    QuitHandler.setResponse(QResponse.NULL);
+    // reset mock response
+    JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
+    // close desktop windows/frames
+    jalview.gui.Desktop.instance.closeAll_actionPerformed(null);
+    // reset debug delay
+    Cache.setProperty("DEBUG_DELAY_SAVE", "false");
+    Jalview2XML.setDebugDelaySave(3);
+  }
+
+  @AfterMethod(alwaysRun = true)
+  public static void cleanup()
+  {
+    // delete save files
+    List<String> files = new ArrayList<>();
+    files.add(saveProjectFile);
+    files.add(saveFastaFile);
+    for (String filename : files)
+    {
+      File file = new File(filename);
+      if (file.exists())
+      {
+        file.delete();
+      }
+    }
+  }
+
+  @Test(groups = { "Functional" }, singleThreaded = true, priority = 1)
+  public void testInstantQuit() throws Exception
+  {
+    String inFile = "examples/uniref50.fa";
+    AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(inFile,
+            DataSourceType.FILE);
+    assertNotNull(af, "Didn't read input file " + inFile);
+
+    long start = System.currentTimeMillis();
+
+    // if a save is attempted it will delay 3s
+    Cache.setProperty("DEBUG_DELAY_SAVE", "true");
+
+    // loaded file but haven't done anything, should just quit
+    QResponse response = QuitHandler.getQuitResponse(true);
+    long end = System.currentTimeMillis();
+
+    Assert.assertEquals(response, QResponse.QUIT);
+    Assert.assertTrue(end - start < 1000,
+            "Quit-with-no-save-needed took too long (" + (end - start)
+                    + "ms)");
+    Desktop.instance.closeAll_actionPerformed(null);
+  }
+
+  @Test(groups = { "Functional" }, singleThreaded = true, priority = 10)
+  public void testWaitForSaveQuit() throws Exception
+  {
+    String inFile = "examples/uniref50.fa";
+    AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(inFile,
+            DataSourceType.FILE);
+    assertNotNull(af, "Didn't read input file " + inFile);
+
+    long start = System.currentTimeMillis();
+
+    // start a long save (3s)
+    Cache.setProperty("DEBUG_DELAY_SAVE", "true");
+    af.saveAlignment(saveProjectFile, FileFormat.Jalview);
+
+    QResponse response = QuitHandler.getQuitResponse(true,
+            QuitHandler.defaultOkQuit, () -> {
+              // set FORCE_QUIT without the force quit
+              QuitHandler.setResponse(QResponse.FORCE_QUIT);
+              return null;
+            }, QuitHandler.defaultCancelQuit);
+    long end = System.currentTimeMillis();
+
+    Assert.assertEquals(response, QResponse.QUIT);
+    Assert.assertTrue(end - start > 2900,
+            "Quit-whilst-saving was too short (" + (end - start) + "ms)");
+
+    Desktop.instance.closeAll_actionPerformed(null);
+  }
+
+  @Test(groups = { "Functional" }, singleThreaded = true, priority = 9)
+  public void testSavedProjectChanges() throws Exception
+  {
+    String inFile = "examples/uniref50.fa";
+    AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(inFile,
+            DataSourceType.FILE);
+    assertNotNull(af, "Didn't read input file " + inFile);
+    AlignViewport viewport = af.getViewport();
+    // pretend something has happened
+    viewport.setSavedUpToDate(false);
+    Jalview2XML.setStateSavedUpToDate(false);
+
+    // no hanging around needed here
+    Cache.setProperty("DEBUG_DELAY_SAVE", "false");
+    af.saveAlignment(saveProjectFile, FileFormat.Jalview);
+
+    // this is only a two button dialog [Quit] [Cancel] so use NO_OPTION
+    JvOptionPane.setMockResponse(JvOptionPane.NO_OPTION);
+    QResponse response = QuitHandler.getQuitResponse(true,
+            QuitHandler.defaultOkQuit, () -> {
+              // set FORCE_QUIT without the force quit
+              QuitHandler.setResponse(QResponse.FORCE_QUIT);
+              return null;
+            }, QuitHandler.defaultCancelQuit);
+
+    // if not saved this would be CANCEL_QUIT
+    Assert.assertEquals(response, QResponse.QUIT);
+    Desktop.instance.closeAll_actionPerformed(null);
+  }
+
+  @Test(groups = { "Functional" }, singleThreaded = true, priority = 9)
+  public void testSavedAlignmentChanges() throws Exception
+  {
+    String inFile = "examples/uniref50.fa";
+    AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(inFile,
+            DataSourceType.FILE);
+    assertNotNull(af, "Didn't read input file " + inFile);
+    AlignViewport viewport = af.getViewport();
+    // pretend something has happened
+    viewport.setSavedUpToDate(false);
+    Jalview2XML.setStateSavedUpToDate(false);
+
+    // no hanging around needed here
+    Cache.setProperty("DEBUG_DELAY_SAVE", "false");
+    af.saveAlignment(saveFastaFile, FileFormat.Fasta);
+
+    // this is only a two button dialog [Quit] [Cancel] so use NO_OPTION
+    JvOptionPane.setMockResponse(JvOptionPane.NO_OPTION);
+    QResponse response = QuitHandler.getQuitResponse(true,
+            QuitHandler.defaultOkQuit, () -> {
+              // set FORCE_QUIT without the force quit
+              QuitHandler.setResponse(QResponse.FORCE_QUIT);
+              return null;
+            }, QuitHandler.defaultCancelQuit);
+
+    // if not saved this would be CANCEL_QUIT
+    Assert.assertEquals(response, QResponse.QUIT);
+    Desktop.instance.closeAll_actionPerformed(null);
+  }
+
+  @Test(groups = { "Functional" }, singleThreaded = true, priority = 1)
+  public void testUnsavedChanges() throws Exception
+  {
+    String inFile = "examples/uniref50.fa";
+    AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(inFile,
+            DataSourceType.FILE);
+    assertNotNull(af, "Didn't read input file " + inFile);
+    AlignViewport viewport = af.getViewport();
+    // pretend something has happened
+    viewport.setSavedUpToDate(false);
+    Jalview2XML.setStateSavedUpToDate(false);
+
+    // this is only a two button dialog [Quit] [Cancel] so use NO_OPTION
+    JvOptionPane.setMockResponse(JvOptionPane.NO_OPTION);
+    QResponse response = QuitHandler.getQuitResponse(true,
+            QuitHandler.defaultOkQuit, () -> {
+              // set FORCE_QUIT without the force quit
+              QuitHandler.setResponse(QResponse.FORCE_QUIT);
+              return null;
+            }, QuitHandler.defaultCancelQuit);
+
+    Assert.assertEquals(response, QResponse.CANCEL_QUIT);
+    Desktop.instance.closeAll_actionPerformed(null);
+  }
+
+  @Test(groups = { "Functional" }, singleThreaded = true, priority = 1)
+  public void testNoGUIUnsavedChanges() throws Exception
+  {
+    String inFile = "examples/uniref50.fa";
+    AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(inFile,
+            DataSourceType.FILE);
+    assertNotNull(af, "Didn't read input file " + inFile);
+    AlignViewport viewport = af.getViewport();
+    // pretend something has happened
+    viewport.setSavedUpToDate(false);
+    Jalview2XML.setStateSavedUpToDate(false);
+
+    // this is only a two button dialog [Quit] [Cancel] so use NO_OPTION
+    JvOptionPane.setMockResponse(JvOptionPane.NO_OPTION);
+    QResponse response = QuitHandler.getQuitResponse(false,
+            QuitHandler.defaultOkQuit, () -> {
+              // set FORCE_QUIT without the force quit
+              QuitHandler.setResponse(QResponse.FORCE_QUIT);
+              return null;
+            }, QuitHandler.defaultCancelQuit);
+
+    Assert.assertEquals(response, QResponse.QUIT);
+    Desktop.instance.closeAll_actionPerformed(null);
+  }
+
+}
diff --git a/test/jalview/gui/quitProps.jvprops b/test/jalview/gui/quitProps.jvprops
new file mode 100644 (file)
index 0000000..d806eac
--- /dev/null
@@ -0,0 +1,7 @@
+#DEBUG_DELAY_SAVE=true
+BACKUPFILES_ENABLED=true
+BACKUPFILES_FC_INCLUDE=false
+BACKUPFILES_PRESET=1
+CONFIRM_OVERWRITE_FILE=true
+SHOW_STARTUP_FILE=false
+logs.Jalview.level=DEBUG