Merge branch 'develop' into spike/JAL-4047/JAL-4048_columns_in_sequenceID
[jalview.git] / test / jalview / bin / CommandLineOperationsNG.java
diff --git a/test/jalview/bin/CommandLineOperationsNG.java b/test/jalview/bin/CommandLineOperationsNG.java
new file mode 100644 (file)
index 0000000..56d4300
--- /dev/null
@@ -0,0 +1,537 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.bin;
+
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.testng.Assert;
+import org.testng.FileAssert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import io.github.classgraph.ClassGraph;
+import io.github.classgraph.ModuleRef;
+import io.github.classgraph.ScanResult;
+import jalview.gui.JvOptionPane;
+
+public class CommandLineOperationsNG
+{
+
+  @BeforeClass(alwaysRun = true)
+  public void setUpJvOptionPane()
+  {
+    JvOptionPane.setInteractiveMode(false);
+    JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
+  }
+
+  // Note longer timeout needed on full test run than on individual tests
+  private static final int TEST_TIMEOUT = 13000;
+
+  private static final int SETUP_TIMEOUT = 9500;
+
+  private static final int MINFILESIZE_SMALL = 2096;
+
+  private static final int MINFILESIZE_BIG = 4096;
+
+  private List<String> successfulCMDs = new ArrayList<>();
+
+  /***
+   * from
+   * http://stackoverflow.com/questions/808276/how-to-add-a-timeout-value-when
+   * -using-javas-runtime-exec
+   * 
+   * @author jimp
+   * 
+   */
+  private static class Worker extends Thread
+  {
+    private final Process process;
+
+    private BufferedReader outputReader;
+
+    private BufferedReader errorReader;
+
+    private Integer exit;
+
+    private Worker(Process process)
+    {
+      this.process = process;
+    }
+
+    @Override
+    public void run()
+    {
+      try
+      {
+        exit = process.waitFor();
+      } catch (InterruptedException ignore)
+      {
+        return;
+      }
+    }
+
+    public BufferedReader getOutputReader()
+    {
+      return outputReader;
+    }
+
+    public void setOutputReader(BufferedReader outputReader)
+    {
+      this.outputReader = outputReader;
+    }
+
+    public BufferedReader getErrorReader()
+    {
+      return errorReader;
+    }
+
+    public void setErrorReader(BufferedReader errorReader)
+    {
+      this.errorReader = errorReader;
+    }
+  }
+
+  private static ClassGraph scanner = null;
+
+  private static String classpath = null;
+
+  private static String modules = null;
+
+  private static String java_exe = null;
+
+  public synchronized static String getClassPath()
+  {
+    if (scanner == null)
+    {
+      scanner = new ClassGraph();
+      ScanResult scan = scanner.scan();
+      classpath = scan.getClasspath();
+      modules = "";
+      for (ModuleRef mr : scan.getModules())
+      {
+        modules.concat(mr.getName());
+      }
+      java_exe = System.getProperty("java.home") + File.separator + "bin"
+              + File.separator + "java";
+
+    }
+
+    while (classpath == null)
+    {
+      try
+      {
+        Thread.sleep(10);
+      } catch (InterruptedException x)
+      {
+
+      }
+    }
+    return classpath;
+  }
+
+  private Worker getJalviewDesktopRunner(boolean withAwt, String cmd,
+          int timeout)
+  {
+    return getJalviewDesktopRunner(withAwt, cmd, timeout, true);
+  }
+
+  private Worker getJalviewDesktopRunner(boolean withAwt, String cmd,
+          int timeout, boolean testoutput)
+  {
+    /*
+    boolean win = System.getProperty("os.name").indexOf("Win") >= 0;
+    String pwd = "";
+    try
+    {
+      Path currentRelativePath = Paths.get("");
+      pwd = currentRelativePath.toAbsolutePath().toString();
+    } catch (Exception q)
+    {
+      q.printStackTrace();
+    }
+    if (pwd == null || pwd.length() == 0)
+      pwd = ".";
+    String[] classpaths = new String[] { pwd + "/bin/main",
+        pwd + "/j11lib/*", pwd + "/resources", pwd + "/help" };
+    String classpath = String.join(win ? ";" : ":", classpaths);
+    getClassPath();
+    */
+    // Note: JAL-3065 - don't include quotes for lib/* because the arguments are
+    // not expanded by the shell
+    String classpath = getClassPath();
+    String _cmd = java_exe + " "
+            + (withAwt ? "-Djava.awt.headless=true" : "") + " -classpath "
+            + classpath
+            + ((modules != null && modules.length() > 2)
+                    ? "--add-modules=\"" + modules + "\""
+                    : "")
+            + " jalview.bin.Jalview ";
+    Process ls2_proc = null;
+    Worker worker = null;
+    try
+    {
+      cmd = cmd + (testoutput ? " --testoutput " : "");
+      System.out.println("Running '" + _cmd + cmd + "'");
+      ls2_proc = Runtime.getRuntime().exec(_cmd + cmd);
+    } catch (Throwable e1)
+    {
+      e1.printStackTrace();
+    }
+    if (ls2_proc != null)
+    {
+      BufferedReader outputReader = new BufferedReader(
+              new InputStreamReader(ls2_proc.getInputStream()));
+      BufferedReader errorReader = new BufferedReader(
+              new InputStreamReader(ls2_proc.getErrorStream()));
+      worker = new Worker(ls2_proc);
+      worker.start();
+      try
+      {
+        worker.join(timeout);
+      } catch (InterruptedException e)
+      {
+        System.err.println("Thread interrupted");
+      }
+      worker.setOutputReader(outputReader);
+      worker.setErrorReader(errorReader);
+    }
+    return worker;
+  }
+
+  @Test(groups = { "Functional", "testTask1" })
+  public void reportCurrentWorkingDirectory()
+  {
+    try
+    {
+      Path currentRelativePath = Paths.get("");
+      String s = currentRelativePath.toAbsolutePath().toString();
+      System.out.println("Test CWD is " + s);
+    } catch (Exception q)
+    {
+      q.printStackTrace();
+    }
+  }
+
+  @BeforeClass(alwaysRun = true)
+  public void initialize()
+  {
+    new CommandLineOperationsNG();
+  }
+
+  @BeforeClass(alwaysRun = true)
+  public void setUpForHeadlessCommandLineInputOperations()
+          throws IOException
+  {
+    String cmds = "--headless " + "--open examples/uniref50.fa "
+            + "--sortbytree "
+            + "--props test/jalview/bin/testProps.jvprops "
+            + "--colour zappo "
+            + "--jabaws http://www.compbio.dundee.ac.uk/jabaws "
+            + "--features examples/testdata/plantfdx.features "
+            + "--annotations examples/testdata/plantfdx.annotations "
+            + "--tree examples/testdata/uniref50_test_tree "
+            + "--nousagestats ";
+    Worker worker = getJalviewDesktopRunner(true, cmds, SETUP_TIMEOUT);
+    String ln = null;
+    while ((ln = worker.getOutputReader().readLine()) != null)
+    {
+      System.out.println(ln);
+      successfulCMDs.add(ln);
+    }
+    while ((ln = worker.getErrorReader().readLine()) != null)
+    {
+      System.err.println(ln);
+    }
+  }
+
+  @BeforeClass(alwaysRun = true)
+  public void setUpForCommandLineInputOperations() throws IOException
+  {
+    String cmds = "--open examples/uniref50.fa --noquestionnaire --nousagestats";
+    final Worker worker = getJalviewDesktopRunner(false, cmds,
+            SETUP_TIMEOUT);
+
+    // number of lines expected on STDERR when Jalview starts up normally
+    // may need to adjust this if Jalview is excessively noisy ?
+    final int STDERR_SETUPLINES = 50;
+
+    // thread monitors stderr - bails after SETUP_TIMEOUT or when
+    // STDERR_SETUPLINES have been read
+    Thread runner = new Thread(new Runnable()
+    {
+      @Override
+      public void run()
+      {
+        String ln = null;
+        int count = 0;
+        try
+        {
+          while ((ln = worker.getOutputReader().readLine()) != null)
+          {
+            System.out.println(ln);
+            successfulCMDs.add(ln);
+            if (++count > STDERR_SETUPLINES)
+            {
+              break;
+            }
+          }
+        } catch (Exception e)
+        {
+          System.err.println(
+                  "Unexpected Exception reading stderr from the Jalview process");
+          e.printStackTrace();
+        }
+      }
+    });
+    long t = System.currentTimeMillis() + SETUP_TIMEOUT;
+    runner.start();
+    while (!runner.isInterrupted() && System.currentTimeMillis() < t)
+    {
+      try
+      {
+        Thread.sleep(500);
+      } catch (InterruptedException e)
+      {
+      }
+    }
+    runner.interrupt();
+    if (worker != null && worker.exit == null)
+    {
+      worker.interrupt();
+      Thread.currentThread().interrupt();
+      worker.process.destroy();
+    }
+  }
+
+  @Test(
+    groups =
+    { "Functional", "testTask1" },
+    dataProvider = "allInputOperationsData")
+  public void testAllInputOperations(String expectedString,
+          String failureMsg)
+  {
+    Assert.assertTrue(successfulCMDs.contains(expectedString),
+            failureMsg + "; was expecting '" + expectedString + "'");
+  }
+
+  @Test(
+    groups =
+    { "Functional", "testTask1" },
+    dataProvider = "headlessModeOutputOperationsData")
+  public void testHeadlessModeOutputOperations(String harg, String type,
+          String fileName, boolean withAWT, int expectedMinFileSize,
+          int timeout)
+  {
+    String cmd = harg + type + " " + fileName;
+    // System.out.println(">>>>>>>>>>>>>>>> Command : " + cmd);
+    File file = new File(fileName);
+    file.deleteOnExit();
+    Worker worker = getJalviewDesktopRunner(withAWT, cmd, timeout);
+    assertNotNull(worker, "worker is null");
+    String msg = "Didn't create an output" + type + " file '" + fileName
+            + "'. [" + cmd + "]";
+    assertTrue(file.exists(), msg);
+    FileAssert.assertFile(file, msg);
+    FileAssert.assertMinLength(file, expectedMinFileSize);
+    if (worker != null && worker.exit == null)
+    {
+      worker.interrupt();
+      Thread.currentThread().interrupt();
+      worker.process.destroy();
+      Assert.fail("Jalview did not exit after " + type
+              + " generation (try running test again to verify - timeout at "
+              + timeout + "ms). [" + harg + "]");
+    }
+    file.delete();
+  }
+
+  @Test(
+    groups =
+    { "Functional", "testTask1" },
+    dataProvider = "headlessModeOutputToStdout")
+  public void testHeadlessModeOutputToStdout(String args,
+          String comparisonFile, int timeout)
+  {
+    String cmd = args;
+    File file = new File(comparisonFile);
+    Worker worker = getJalviewDesktopRunner(true, cmd, timeout, false);
+    int b = -1;
+    StringBuilder sb = new StringBuilder();
+    try
+    {
+      while ((b = worker.getOutputReader().read()) != -1)
+      {
+        sb.append(Character.toChars(b));
+      }
+    } catch (IOException e)
+    {
+      Assert.fail("IOException whilst trying to read from jalview process");
+    }
+
+    String comparisonContent = null;
+    try
+    {
+      comparisonContent = new String(Files.readAllBytes(file.toPath()));
+    } catch (IOException e)
+    {
+      Assert.fail("IOException whilst trying to read comparison file");
+    }
+
+    Assert.assertEquals(sb.toString(), comparisonContent,
+            "STDOUT from jalview command did not match the comparison file");
+  }
+
+  @DataProvider(name = "allInputOperationsData")
+  public Object[][] getHeadlessModeInputParams()
+  {
+    return new Object[][] {
+        // headless mode input operations
+        { "[TESTOUTPUT] arg --colour='zappo' was set",
+            "Failed setting arg --colour" },
+        { "[TESTOUTPUT] arg --props='test/jalview/bin/testProps.jvprops' was set",
+            "Failed setting arg --props" },
+        { "[TESTOUTPUT] arg --sortbytree was set",
+            "Failed setting arg --sortbytree" },
+        { "[TESTOUTPUT] arg --jabaws='http://www.compbio.dundee.ac.uk/jabaws' was set",
+            "Failed setting arg --jabaws" },
+        { "[TESTOUTPUT] arg --open='examples/uniref50.fa' was set",
+            "Failed setting arg --open" },
+        { "[TESTOUTPUT] arg --features='examples/testdata/plantfdx.features' was set",
+            "Failed setting arg --features" },
+        { "[TESTOUTPUT] arg --annotations='examples/testdata/plantfdx.annotations' was set",
+            "Failed setting arg --annotations" },
+        { "[TESTOUTPUT] arg --tree='examples/testdata/uniref50_test_tree' was set",
+            "Failed setting arg --tree" },
+        // non headless mode input operations
+        { "[TESTOUTPUT] arg --nousagestats was set",
+            "Failed setting arg --nousagestats" },
+        { "[TESTOUTPUT] arg --noquestionnaire was set",
+            "Failed setting arg --noquestionnaire" }
+        //
+    };
+  }
+
+  @DataProvider(name = "headlessModeOutputOperationsData")
+  public static Object[][] getHeadlessModeOutputParams()
+  {
+    // JBPNote: I'm not clear why need to specify full path for output file
+    // when running tests on build server, but we will keep this patch for now
+    // since it works.
+    // https://issues.jalview.org/browse/JAL-1889?focusedCommentId=21609&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-21609
+    String workingDir = "test/jalview/bin/";
+    return new Object[][] {
+        //
+        { "--headless --open examples/uniref50.fa", " --image",
+            workingDir + "test_uniref50_out.eps", true, MINFILESIZE_BIG,
+            TEST_TIMEOUT },
+        { "--headless --open examples/uniref50.fa", " --image",
+            workingDir + "test_uniref50_out.eps", false, MINFILESIZE_BIG,
+            TEST_TIMEOUT },
+        { "--headless --open examples/uniref50.fa", " --image",
+            workingDir + "test_uniref50_out.eps", true, MINFILESIZE_BIG,
+            TEST_TIMEOUT },
+        { "--headless --open examples/uniref50.fa", " --image",
+            workingDir + "test_uniref50_out.eps", false, MINFILESIZE_BIG,
+            TEST_TIMEOUT },
+        { "--headless --open examples/uniref50.fa", " --image",
+            workingDir + "test_uniref50_out.eps", true, MINFILESIZE_BIG,
+            TEST_TIMEOUT },
+        { "--headless --open examples/uniref50.fa", " --image",
+            workingDir + "test_uniref50_out.svg", false, MINFILESIZE_BIG,
+            TEST_TIMEOUT },
+        { "--headless --open examples/uniref50.fa", " --image",
+            workingDir + "test_uniref50_out.png", true, MINFILESIZE_BIG,
+            TEST_TIMEOUT },
+        { "--headless --open examples/uniref50.fa", " --image",
+            workingDir + "test_uniref50_out.html", true, MINFILESIZE_BIG,
+            TEST_TIMEOUT },
+        { "--headless --open examples/uniref50.fa", " --output",
+            workingDir + "test_uniref50_out.mfa", true, MINFILESIZE_SMALL,
+            TEST_TIMEOUT },
+        { "--headless --open examples/uniref50.fa", " --output",
+            workingDir + "test_uniref50_out.aln", true, MINFILESIZE_SMALL,
+            TEST_TIMEOUT },
+        { "--headless --open examples/uniref50.fa", " --output",
+            workingDir + "test_uniref50_out.msf", true, MINFILESIZE_SMALL,
+            TEST_TIMEOUT },
+        { "--headless --open examples/uniref50.fa", " --output",
+            workingDir + "test_uniref50_out.aln", true, MINFILESIZE_SMALL,
+            TEST_TIMEOUT },
+        { "--headless --open examples/uniref50.fa", " --output",
+            workingDir + "test_uniref50_out.pir", true, MINFILESIZE_SMALL,
+            TEST_TIMEOUT },
+        { "--headless --open examples/uniref50.fa", " --output",
+            workingDir + "test_uniref50_out.pfam", true, MINFILESIZE_SMALL,
+            TEST_TIMEOUT },
+        { "--headless --open examples/uniref50.fa", " --output",
+            workingDir + "test_uniref50_out.blc", true, MINFILESIZE_SMALL,
+            TEST_TIMEOUT },
+        { "--headless --open examples/uniref50.fa", " --output",
+            workingDir + "test_uniref50_out.jvp", true, MINFILESIZE_SMALL,
+            TEST_TIMEOUT },
+        //
+    };
+  }
+
+  @DataProvider(name = "headlessModeOutputToStdout")
+  public static Object[][] getHeadlessModeOutputToStdout()
+  {
+    // JBPNote: I'm not clear why need to specify full path for output file
+    // when running tests on build server, but we will keep this patch for now
+    // since it works.
+    // https://issues.jalview.org/browse/JAL-1889?focusedCommentId=21609&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-21609
+    String workingDir = "test/jalview/bin";
+    return new Object[][] {
+        //
+        { "--open=examples/uniref50.fa --output=-",
+            workingDir + "/uniref50-output.fa", TEST_TIMEOUT },
+        { "--open examples/uniref50.fa --output -",
+            workingDir + "/uniref50-output.fa", TEST_TIMEOUT },
+        { "--open examples/uniref50.fa --output=[format=blc]-",
+            workingDir + "/uniref50-output.blc", TEST_TIMEOUT },
+        { "--open examples/uniref50.fa --output - --format blc",
+            workingDir + "/uniref50-output.blc", TEST_TIMEOUT },
+        { "./examples/uniref50.fa --output=-",
+            workingDir + "/uniref50-output.fa", TEST_TIMEOUT },
+        { "./examples/uniref50.fa --output - --format blc",
+            workingDir + "/uniref50-output.blc", TEST_TIMEOUT },
+        // remember you can't use shell wildcards for filenames in a test
+        { "./test/jalview/bin/argparser/testfiles/test1.fa ./test/jalview/bin/argparser/testfiles/test2.fa ./test/jalview/bin/argparser/testfiles/test3.fa --all --output -",
+            workingDir + "/test1-3.fa", TEST_TIMEOUT },
+        // but you can use java wildcards when using an equals sign
+        { "--open=./test/jalview/bin/argparser/testfiles/test*.fa --all --output -",
+            workingDir + "/test1-3.fa", TEST_TIMEOUT },
+        //
+    };
+  }
+}