JAL-4290 new CommandsTest.headlessOrGuiImageOutputTest - uses CommandLineOperations...
[jalview.git] / test / jalview / bin / CommandLineOperations.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.bin;
22
23 import static org.testng.Assert.assertNotNull;
24 import static org.testng.Assert.assertTrue;
25
26 import java.io.BufferedReader;
27 import java.io.File;
28 import java.io.IOException;
29 import java.io.InputStreamReader;
30 import java.nio.file.Path;
31 import java.nio.file.Paths;
32 import java.util.ArrayList;
33
34 import org.testng.Assert;
35 import org.testng.FileAssert;
36 import org.testng.annotations.BeforeClass;
37 import org.testng.annotations.DataProvider;
38 import org.testng.annotations.Test;
39
40 import io.github.classgraph.ClassGraph;
41 import io.github.classgraph.ModuleRef;
42 import io.github.classgraph.ScanResult;
43 import jalview.gui.JvOptionPane;
44
45 public class CommandLineOperations
46 {
47
48   @BeforeClass(alwaysRun = true)
49   public void setUpJvOptionPane()
50   {
51     JvOptionPane.setInteractiveMode(false);
52     JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
53   }
54
55   // Note longer timeout needed on full test run than on individual tests
56   private static final int TEST_TIMEOUT = 15000;
57
58   private static final int SETUP_TIMEOUT = 9500;
59
60   private static final int MINFILESIZE_SMALL = 2096;
61
62   private static final int MINFILESIZE_BIG = 4096;
63
64   private ArrayList<String> successfulCMDs = new ArrayList<>();
65
66   /***
67    * from
68    * http://stackoverflow.com/questions/808276/how-to-add-a-timeout-value-when
69    * -using-javas-runtime-exec
70    * 
71    * @author jimp
72    * 
73    */
74   public static class Worker extends Thread
75   {
76     private final Process process;
77
78     private BufferedReader outputReader;
79
80     private BufferedReader errorReader;
81
82     private Integer exit;
83
84     private Worker(Process process)
85     {
86       this.process = process;
87     }
88
89     @Override
90     public void run()
91     {
92       try
93       {
94         exit = process.waitFor();
95       } catch (InterruptedException ignore)
96       {
97         return;
98       }
99     }
100
101     public BufferedReader getOutputReader()
102     {
103       return outputReader;
104     }
105
106     public void setOutputReader(BufferedReader outputReader)
107     {
108       this.outputReader = outputReader;
109     }
110
111     public BufferedReader getErrorReader()
112     {
113       return errorReader;
114     }
115
116     public void setErrorReader(BufferedReader errorReader)
117     {
118       this.errorReader = errorReader;
119     }
120   }
121
122   private static ClassGraph scanner = null;
123
124   private static String classpath = null;
125
126   private static String modules = null;
127
128   private static String java_exe = null;
129
130   public synchronized static String getClassPath()
131   {
132     if (scanner == null)
133     {
134       scanner = new ClassGraph();
135       ScanResult scan = scanner.scan();
136       classpath = scan.getClasspath();
137       modules = "";
138       for (ModuleRef mr : scan.getModules())
139       {
140         modules.concat(mr.getName());
141       }
142       java_exe = System.getProperty("java.home") + File.separator + "bin"
143               + File.separator + "java";
144
145     }
146     while (classpath == null)
147     {
148       try
149       {
150         Thread.sleep(10);
151       } catch (InterruptedException x)
152       {
153
154       }
155     }
156     return classpath;
157   }
158
159   public static Worker getJalviewDesktopRunner(boolean withAwt, String cmd,
160           int timeout)
161   {
162     // Note: JAL-3065 - don't include quotes for lib/* because the arguments are
163     // not expanded by the shell
164     String classpath = getClassPath();
165     String _cmd = java_exe + " "
166             + (withAwt ? "-Djava.awt.headless=true" : "") + " -classpath "
167             + classpath
168             + (modules.length() > 2 ? "--add-modules=\"" + modules + "\""
169                     : "")
170             + " jalview.bin.Jalview ";
171     Process ls2_proc = null;
172     Worker worker = null;
173     try
174     {
175       ls2_proc = Runtime.getRuntime().exec(_cmd + cmd);
176     } catch (Throwable e1)
177     {
178       e1.printStackTrace();
179     }
180     if (ls2_proc != null)
181     {
182       BufferedReader outputReader = new BufferedReader(
183               new InputStreamReader(ls2_proc.getInputStream()));
184       BufferedReader errorReader = new BufferedReader(
185               new InputStreamReader(ls2_proc.getErrorStream()));
186       worker = new CommandLineOperations.Worker(ls2_proc);
187       worker.start();
188       try
189       {
190         worker.join(timeout);
191       } catch (InterruptedException e)
192       {
193         System.err.println("Thread interrupted");
194       }
195       worker.setOutputReader(outputReader);
196       worker.setErrorReader(errorReader);
197     }
198     return worker;
199   }
200
201   @Test(groups = { "Functional" })
202   public void reportCurrentWorkingDirectory()
203   {
204     try
205     {
206       Path currentRelativePath = Paths.get("");
207       String s = currentRelativePath.toAbsolutePath().toString();
208       System.out.println("Test CWD is " + s);
209     } catch (Exception q)
210     {
211       q.printStackTrace();
212     }
213   }
214
215   @BeforeClass(alwaysRun = true)
216   public void initialize()
217   {
218     new CommandLineOperations();
219   }
220
221   @BeforeClass(alwaysRun = true)
222   public void setUpForHeadlessCommandLineInputOperations()
223           throws IOException
224   {
225     String cmds = "nodisplay -open examples/uniref50.fa -sortbytree -props test/jalview/bin/testProps.jvprops -colour zappo "
226             + "-jabaws http://www.compbio.dundee.ac.uk/jabaws -nosortbytree "
227             + "-features examples/testdata/plantfdx.features -annotations examples/testdata/plantfdx.annotations -tree examples/testdata/uniref50_test_tree";
228     Worker worker = getJalviewDesktopRunner(true, cmds, SETUP_TIMEOUT);
229     String ln = null;
230     while ((ln = worker.getOutputReader().readLine()) != null)
231     {
232       System.out.println("STDOUT: " + ln);
233       successfulCMDs.add(ln);
234     }
235     while ((ln = worker.getErrorReader().readLine()) != null)
236     {
237       System.err.println("STDERR: " + ln);
238       successfulCMDs.add(ln);
239     }
240   }
241
242   @BeforeClass(alwaysRun = true)
243   public void setUpForCommandLineInputOperations() throws IOException
244   {
245     String cmds = "-open examples/uniref50.fa -noquestionnaire -nousagestats";
246     final Worker worker = getJalviewDesktopRunner(false, cmds,
247             SETUP_TIMEOUT);
248
249     // number of lines expected on STDERR when Jalview starts up normally
250     // may need to adjust this if Jalview is excessively noisy ?
251     final int STDOUT_SETUPLINES = 50;
252     final int STDERR_SETUPLINES = 50;
253
254     // thread monitors stderr - bails after SETUP_TIMEOUT or when
255     // STDERR_SETUPLINES have been read
256     Thread runner = new Thread(new Runnable()
257     {
258       @Override
259       public void run()
260       {
261         String ln = null;
262         int stdoutcount = 0;
263         int stderrcount = 0;
264         try
265         {
266           while ((ln = worker.getOutputReader().readLine()) != null)
267           {
268             System.out.println(ln);
269             successfulCMDs.add(ln);
270             if (++stdoutcount > STDOUT_SETUPLINES)
271             {
272               break;
273             }
274           }
275           while ((ln = worker.getErrorReader().readLine()) != null)
276           {
277             System.err.println(ln);
278             successfulCMDs.add(ln);
279             if (++stderrcount > STDERR_SETUPLINES)
280             {
281               break;
282             }
283           }
284         } catch (Exception e)
285         {
286           System.err.println(
287                   "Unexpected Exception reading stderr from the Jalview process");
288           e.printStackTrace();
289         }
290       }
291     });
292     long t = System.currentTimeMillis() + SETUP_TIMEOUT;
293     runner.start();
294     while (!runner.isInterrupted() && System.currentTimeMillis() < t)
295     {
296       try
297       {
298         Thread.sleep(500);
299       } catch (InterruptedException e)
300       {
301       }
302     }
303     runner.interrupt();
304     if (worker != null && worker.exit == null)
305     {
306       worker.interrupt();
307       Thread.currentThread().interrupt();
308       worker.process.destroy();
309     }
310   }
311
312   @Test(groups = { "Functional" }, dataProvider = "allInputOperationsData")
313   public void testAllInputOperations(String expectedString,
314           String failureMsg)
315   {
316     Assert.assertTrue(successfulCMDs.contains(expectedString), failureMsg);
317   }
318
319   @Test(
320     groups =
321     { "Functional" },
322     dataProvider = "headlessModeOutputOperationsData")
323   public void testHeadlessModeOutputOperations(String harg, String type,
324           String fileName, boolean withAWT, int expectedMinFileSize,
325           int timeout)
326   {
327     String cmd = harg + type + " " + fileName;
328     // System.out.println(">>>>>>>>>>>>>>>> Command : " + cmd);
329     File file = new File(fileName);
330     file.deleteOnExit();
331     Worker worker = getJalviewDesktopRunner(withAWT, cmd, timeout);
332     assertNotNull(worker, "worker is null");
333     String msg = "Didn't create an output" + type + " file.[" + harg + "]";
334     assertTrue(file.exists(), msg);
335     FileAssert.assertFile(file, msg);
336     FileAssert.assertMinLength(file, expectedMinFileSize);
337     if (worker != null && worker.exit == null)
338     {
339       worker.interrupt();
340       Thread.currentThread().interrupt();
341       worker.process.destroy();
342       Assert.fail("Jalview did not exit after " + type
343               + " generation (try running test again to verify - timeout at "
344               + timeout + "ms). [" + harg + "]");
345     }
346     file.delete();
347   }
348
349   @DataProvider(name = "allInputOperationsData")
350   public Object[][] getHeadlessModeInputParams()
351   {
352     return new Object[][] {
353         // headless mode input operations
354         { "CMD [-colour zappo] executed successfully!",
355             "Failed command : -colour zappo" },
356         { "CMD [-props test/jalview/bin/testProps.jvprops] executed successfully!",
357             "Failed command : -props File" },
358         { "CMD [-sortbytree] executed successfully!",
359             "Failed command : -sortbytree" },
360         { "CMD [-jabaws http://www.compbio.dundee.ac.uk/jabaws] executed successfully!",
361             "Failed command : -jabaws http://www.compbio.dundee.ac.uk/jabaws" },
362         { "CMD [-open examples/uniref50.fa] executed successfully!",
363             "Failed command : -open examples/uniref50.fa" },
364         { "CMD [-nosortbytree] executed successfully!",
365             "Failed command : -nosortbytree" },
366         { "CMD [-features examples/testdata/plantfdx.features]  executed successfully!",
367             "Failed command : -features examples/testdata/plantfdx.features" },
368         { "CMD [-annotations examples/testdata/plantfdx.annotations] executed successfully!",
369             "Failed command : -annotations examples/testdata/plantfdx.annotations" },
370         { "CMD [-tree examples/testdata/uniref50_test_tree] executed successfully!",
371             "Failed command : -tree examples/testdata/uniref50_test_tree" },
372         // non headless mode input operations
373         { "CMD [-nousagestats] executed successfully!",
374             "Failed command : -nousagestats" },
375         { "CMD [-noquestionnaire] executed successfully!",
376             "Failed command : -noquestionnaire" } };
377   }
378
379   @DataProvider(name = "headlessModeOutputOperationsData")
380   public static Object[][] getHeadlessModeOutputParams()
381   {
382     // JBPNote: I'm not clear why need to specify full path for output file
383     // when running tests on build server, but we will keep this patch for now
384     // since it works.
385     // https://issues.jalview.org/browse/JAL-1889?focusedCommentId=21609&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-21609
386     String workingDir = "test/jalview/bin/";
387     return new Object[][] {
388         //
389         { "nodisplay -open examples/uniref50.fa", " -eps",
390             workingDir + "test_uniref50_out.eps", true, MINFILESIZE_BIG,
391             TEST_TIMEOUT },
392         { "nodisplay -open examples/uniref50.fa", " -eps",
393             workingDir + "test_uniref50_out.eps", false, MINFILESIZE_BIG,
394             TEST_TIMEOUT },
395         { "nogui -open examples/uniref50.fa", " -eps",
396             workingDir + "test_uniref50_out.eps", true, MINFILESIZE_BIG,
397             TEST_TIMEOUT },
398         { "nogui -open examples/uniref50.fa", " -eps",
399             workingDir + "test_uniref50_out.eps", false, MINFILESIZE_BIG,
400             TEST_TIMEOUT },
401         { "headless -open examples/uniref50.fa", " -eps",
402             workingDir + "test_uniref50_out.eps", true, MINFILESIZE_BIG,
403             TEST_TIMEOUT },
404         { "headless -open examples/uniref50.fa", " -svg",
405             workingDir + "test_uniref50_out.svg", false, MINFILESIZE_BIG,
406             TEST_TIMEOUT },
407         { "headless -open examples/uniref50.fa", " -png",
408             workingDir + "test_uniref50_out.png", true, MINFILESIZE_BIG,
409             TEST_TIMEOUT },
410         { "headless -open examples/uniref50.fa", " -html",
411             workingDir + "test_uniref50_out.html", true, MINFILESIZE_BIG,
412             TEST_TIMEOUT },
413         { "headless -open examples/uniref50.fa", " -fasta",
414             workingDir + "test_uniref50_out.mfa", true, MINFILESIZE_SMALL,
415             TEST_TIMEOUT },
416         { "headless -open examples/uniref50.fa", " -clustal",
417             workingDir + "test_uniref50_out.aln", true, MINFILESIZE_SMALL,
418             TEST_TIMEOUT },
419         { "headless -open examples/uniref50.fa", " -msf",
420             workingDir + "test_uniref50_out.msf", true, MINFILESIZE_SMALL,
421             TEST_TIMEOUT },
422         { "headless -open examples/uniref50.fa", " -pileup",
423             workingDir + "test_uniref50_out.aln", true, MINFILESIZE_SMALL,
424             TEST_TIMEOUT },
425         { "headless -open examples/uniref50.fa", " -pir",
426             workingDir + "test_uniref50_out.pir", true, MINFILESIZE_SMALL,
427             TEST_TIMEOUT },
428         { "headless -open examples/uniref50.fa", " -pfam",
429             workingDir + "test_uniref50_out.pfam", true, MINFILESIZE_SMALL,
430             TEST_TIMEOUT },
431         { "headless -open examples/uniref50.fa", " -blc",
432             workingDir + "test_uniref50_out.blc", true, MINFILESIZE_SMALL,
433             TEST_TIMEOUT },
434         { "headless -open examples/uniref50.fa", " -jalview",
435             workingDir + "test_uniref50_out.jvp", true, MINFILESIZE_SMALL,
436             TEST_TIMEOUT },
437         //
438     };
439   }
440 }