View Javadoc
1   package org.codehaus.plexus.util.cli;
2   
3   /*
4    * Copyright The Codehaus Foundation.
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  import java.io.File;
20  import java.io.IOException;
21  import java.io.Writer;
22  import java.nio.file.Files;
23  
24  import org.codehaus.plexus.util.IOUtil;
25  import org.codehaus.plexus.util.Os;
26  import org.codehaus.plexus.util.StringUtils;
27  import org.codehaus.plexus.util.cli.shell.BourneShell;
28  import org.codehaus.plexus.util.cli.shell.CmdShell;
29  import org.codehaus.plexus.util.cli.shell.Shell;
30  import org.junit.jupiter.api.BeforeEach;
31  import org.junit.jupiter.api.Test;
32  
33  import static org.junit.jupiter.api.Assertions.assertEquals;
34  import static org.junit.jupiter.api.Assertions.assertTrue;
35  import static org.junit.jupiter.api.Assertions.fail;
36  
37  /**
38   * <p>CommandlineTest class.</p>
39   *
40   * @author herve
41   * @version $Id: $Id
42   * @since 3.4.0
43   */
44  public class CommandlineTest {
45      private String baseDir;
46  
47      /**
48       * <p>setUp.</p>
49       *
50       * @throws java.lang.Exception if any.
51       */
52      @BeforeEach
53      public void setUp() throws Exception {
54          baseDir = System.getProperty("basedir");
55  
56          if (baseDir == null) {
57              baseDir = new File(".").getCanonicalPath();
58          }
59      }
60  
61      /**
62       * <p>testCommandlineWithoutCommandInConstructor.</p>
63       */
64      @Test
65      public void testCommandlineWithoutCommandInConstructor() {
66          Commandline cmd = new Commandline(new Shell());
67          cmd.setWorkingDirectory(baseDir);
68          cmd.createArgument().setValue("cd");
69          cmd.createArgument().setValue(".");
70  
71          // NOTE: cmd.toString() uses CommandLineUtils.toString( String[] ), which *quotes* the result.
72          assertEquals("cd .", cmd.toString());
73      }
74  
75      /**
76       * <p>testCommandlineWithCommandInConstructor.</p>
77       */
78      @Test
79      public void testCommandlineWithCommandInConstructor() {
80          Commandline cmd = new Commandline("cd .", new Shell());
81          cmd.setWorkingDirectory(baseDir);
82  
83          // NOTE: cmd.toString() uses CommandLineUtils.toString( String[] ), which *quotes* the result.
84          assertEquals("cd .", cmd.toString());
85      }
86  
87      /**
88       * <p>testExecuteBinaryOnPath.</p>
89       *
90       * @throws java.lang.Exception if any.
91       */
92      @Test
93      public void testExecuteBinaryOnPath() throws Exception {
94          // Maven startup script on PATH is required for this test
95          Commandline cmd = new Commandline();
96          cmd.setWorkingDirectory(baseDir);
97          cmd.setExecutable("mvn");
98          assertEquals("mvn", cmd.getShell().getOriginalExecutable());
99          cmd.createArg().setValue("-version");
100         Process process = cmd.execute();
101         String out = IOUtil.toString(process.getInputStream());
102         assertTrue(out.contains("Apache Maven"));
103         assertTrue(out.contains("Maven home:"));
104         assertTrue(out.contains("Java version:"));
105     }
106 
107     /**
108      * <p>testExecute.</p>
109      *
110      * @throws java.lang.Exception if any.
111      */
112     @org.junit.jupiter.api.Test
113     public void testExecute() throws Exception {
114         // allow it to detect the proper shell here.
115         Commandline cmd = new Commandline();
116         cmd.setWorkingDirectory(baseDir);
117         cmd.setExecutable("echo");
118         assertEquals("echo", cmd.getShell().getOriginalExecutable());
119         cmd.createArgument().setValue("Hello");
120 
121         Process process = cmd.execute();
122         assertEquals("Hello", IOUtil.toString(process.getInputStream()).trim());
123     }
124 
125     /**
126      * <p>testSetLine.</p>
127      */
128     @Test
129     public void testSetLine() {
130         Commandline cmd = new Commandline(new Shell());
131         cmd.setWorkingDirectory(baseDir);
132         cmd.setExecutable("echo");
133         cmd.createArgument().setLine(null);
134         cmd.createArgument().setLine("Hello");
135 
136         // NOTE: cmd.toString() uses CommandLineUtils.toString( String[] ), which *quotes* the result.
137         assertEquals("echo Hello", cmd.toString());
138     }
139 
140     /**
141      * <p>testCreateCommandInReverseOrder.</p>
142      */
143     @Test
144     public void testCreateCommandInReverseOrder() {
145         Commandline cmd = new Commandline(new Shell());
146         cmd.setWorkingDirectory(baseDir);
147         cmd.createArgument().setValue(".");
148         cmd.createArgument(true).setValue("cd");
149 
150         // NOTE: cmd.toString() uses CommandLineUtils.toString( String[] ), which *quotes* the result.
151         assertEquals("cd .", cmd.toString());
152     }
153 
154     /**
155      * <p>testSetFile.</p>
156      */
157     @Test
158     public void testSetFile() {
159         Commandline cmd = new Commandline(new Shell());
160         cmd.setWorkingDirectory(baseDir);
161         cmd.createArgument().setValue("more");
162         File f = new File("test.txt");
163         cmd.createArgument().setFile(f);
164         String fileName = f.getAbsolutePath();
165         if (fileName.contains(" ")) {
166             fileName = "\"" + fileName + "\"";
167         }
168 
169         // NOTE: cmd.toString() uses CommandLineUtils.toString( String[] ), which *quotes* the result.
170         assertEquals("more " + fileName, cmd.toString());
171     }
172 
173     /**
174      * <p>testGetShellCommandLineWindows.</p>
175      *
176      * @throws java.lang.Exception if any.
177      */
178     @Test
179     public void testGetShellCommandLineWindows() throws Exception {
180         Commandline cmd = new Commandline(new CmdShell());
181         cmd.setExecutable("c:\\Program Files\\xxx");
182         cmd.addArguments(new String[] {"a", "b"});
183         String[] shellCommandline = cmd.getShellCommandline();
184 
185         assertEquals(5, shellCommandline.length, "Command line size");
186 
187         assertEquals("cmd.exe", shellCommandline[0]);
188         assertEquals("/X", shellCommandline[1]);
189         assertEquals("/D", shellCommandline[2]);
190         assertEquals("/C", shellCommandline[3]);
191         String expectedShellCmd = "\"c:" + File.separator + "Program Files" + File.separator + "xxx\" a b";
192         expectedShellCmd = "\"" + expectedShellCmd + "\"";
193         assertEquals(expectedShellCmd, shellCommandline[4]);
194     }
195 
196     /**
197      * <p>testGetShellCommandLineWindowsWithSeveralQuotes.</p>
198      *
199      * @throws java.lang.Exception if any.
200      */
201     @Test
202     public void testGetShellCommandLineWindowsWithSeveralQuotes() throws Exception {
203         Commandline cmd = new Commandline(new CmdShell());
204         cmd.setExecutable("c:\\Program Files\\xxx");
205         cmd.addArguments(new String[] {"c:\\Documents and Settings\\whatever", "b"});
206         String[] shellCommandline = cmd.getShellCommandline();
207 
208         assertEquals(5, shellCommandline.length, "Command line size");
209 
210         assertEquals("cmd.exe", shellCommandline[0]);
211         assertEquals("/X", shellCommandline[1]);
212         assertEquals("/D", shellCommandline[2]);
213         assertEquals("/C", shellCommandline[3]);
214         String expectedShellCmd = "\"c:" + File.separator + "Program Files" + File.separator
215                 + "xxx\" \"c:\\Documents and Settings\\whatever\" b";
216         expectedShellCmd = "\"" + expectedShellCmd + "\"";
217         assertEquals(expectedShellCmd, shellCommandline[4]);
218     }
219 
220     /**
221      * Test the command line generated for the bash shell
222      *
223      * @throws java.lang.Exception
224      */
225     @Test
226     public void testGetShellCommandLineBash() throws Exception {
227         Commandline cmd = new Commandline(new BourneShell());
228         cmd.setExecutable("/bin/echo");
229         cmd.addArguments(new String[] {"hello world"});
230 
231         String[] shellCommandline = cmd.getShellCommandline();
232 
233         assertEquals(3, shellCommandline.length, "Command line size");
234 
235         assertEquals("/bin/sh", shellCommandline[0]);
236         assertEquals("-c", shellCommandline[1]);
237         String expectedShellCmd = "'/bin/echo' 'hello world'";
238         if (Os.isFamily(Os.FAMILY_WINDOWS)) {
239             expectedShellCmd = "'\\bin\\echo' \'hello world\'";
240         }
241         assertEquals(expectedShellCmd, shellCommandline[2]);
242     }
243 
244     /**
245      * Test the command line generated for the bash shell
246      *
247      * @throws java.lang.Exception
248      */
249     @Test
250     public void testGetShellCommandLineBash_WithWorkingDirectory() throws Exception {
251         Commandline cmd = new Commandline(new BourneShell());
252         cmd.setExecutable("/bin/echo");
253         cmd.addArguments(new String[] {"hello world"});
254         File root = File.listRoots()[0];
255         File workingDirectory = new File(root, "path with spaces");
256         cmd.setWorkingDirectory(workingDirectory);
257 
258         String[] shellCommandline = cmd.getShellCommandline();
259 
260         assertEquals(3, shellCommandline.length, "Command line size");
261 
262         assertEquals("/bin/sh", shellCommandline[0]);
263         assertEquals("-c", shellCommandline[1]);
264         String expectedShellCmd = "cd '" + root.getAbsolutePath() + "path with spaces' && '/bin/echo' 'hello world'";
265         if (Os.isFamily(Os.FAMILY_WINDOWS)) {
266             expectedShellCmd = "cd '" + root.getAbsolutePath() + "path with spaces' && '\\bin\\echo' 'hello world'";
267         }
268         assertEquals(expectedShellCmd, shellCommandline[2]);
269     }
270 
271     /**
272      * Test the command line generated for the bash shell
273      *
274      * @throws java.lang.Exception
275      */
276     @Test
277     public void testGetShellCommandLineBash_WithSingleQuotedArg() throws Exception {
278         Commandline cmd = new Commandline(new BourneShell());
279         cmd.setExecutable("/bin/echo");
280         cmd.addArguments(new String[] {"\'hello world\'"});
281 
282         String[] shellCommandline = cmd.getShellCommandline();
283 
284         assertEquals(3, shellCommandline.length, "Command line size");
285 
286         assertEquals("/bin/sh", shellCommandline[0]);
287         assertEquals("-c", shellCommandline[1]);
288         String expectedShellCmd = "'/bin/echo' ''\"'\"'hello world'\"'\"''";
289         if (Os.isFamily(Os.FAMILY_WINDOWS)) {
290             expectedShellCmd = expectedShellCmd.replace("/bin/echo", "\\bin\\echo");
291         }
292         assertEquals(expectedShellCmd, shellCommandline[2]);
293     }
294 
295     /**
296      * <p>testGetShellCommandLineNonWindows.</p>
297      *
298      * @throws java.lang.Exception if any.
299      */
300     @Test
301     public void testGetShellCommandLineNonWindows() throws Exception {
302         Commandline cmd = new Commandline(new BourneShell());
303         cmd.setExecutable("/usr/bin");
304         cmd.addArguments(new String[] {"a", "b"});
305         String[] shellCommandline = cmd.getShellCommandline();
306 
307         assertEquals(3, shellCommandline.length, "Command line size");
308 
309         assertEquals("/bin/sh", shellCommandline[0]);
310         assertEquals("-c", shellCommandline[1]);
311 
312         if (Os.isFamily(Os.FAMILY_WINDOWS)) {
313             assertEquals("'\\usr\\bin' 'a' 'b'", shellCommandline[2]);
314         } else {
315             assertEquals("'/usr/bin' 'a' 'b'", shellCommandline[2]);
316         }
317     }
318 
319     /**
320      * <p>testEnvironment.</p>
321      *
322      * @throws java.lang.Exception if any.
323      */
324     @Test
325     public void testEnvironment() throws Exception {
326         Commandline cmd = new Commandline();
327         cmd.addEnvironment("name", "value");
328         assertEquals("name=value", cmd.getEnvironmentVariables()[0]);
329     }
330 
331     /**
332      * <p>testEnvironmentWitOverrideSystemEnvironment.</p>
333      *
334      * @throws java.lang.Exception if any.
335      */
336     @Test
337     public void testEnvironmentWitOverrideSystemEnvironment() throws Exception {
338         Commandline cmd = new Commandline();
339         cmd.addSystemEnvironment();
340         cmd.addEnvironment("JAVA_HOME", "/usr/jdk1.5");
341         String[] environmentVariables = cmd.getEnvironmentVariables();
342 
343         for (String environmentVariable : environmentVariables) {
344             if ("JAVA_HOME=/usr/jdk1.5".equals(environmentVariable)) {
345                 return;
346             }
347         }
348 
349         fail("can't find JAVA_HOME=/usr/jdk1.5");
350     }
351 
352     /**
353      * Test an executable with a single apostrophe <code>'</code> in its path
354      *
355      * @throws java.lang.Exception
356      */
357     @Test
358     public void testQuotedPathWithSingleApostrophe() throws Exception {
359         File dir = new File(System.getProperty("basedir"), "target/test/quotedpath'test");
360         createAndCallScript(dir, "echo Quoted");
361 
362         dir = new File(System.getProperty("basedir"), "target/test/quoted path'test");
363         createAndCallScript(dir, "echo Quoted");
364     }
365 
366     /**
367      * Test an executable with shell-expandable content in its path.
368      *
369      * @throws java.lang.Exception
370      */
371     @Test
372     public void testPathWithShellExpansionStrings() throws Exception {
373         File dir = new File(System.getProperty("basedir"), "target/test/dollar$test");
374         createAndCallScript(dir, "echo Quoted");
375     }
376 
377     /**
378      * Test an executable with a single quotation mark <code>\"</code> in its path only for non Windows box.
379      *
380      * @throws java.lang.Exception
381      */
382     @Test
383     public void testQuotedPathWithQuotationMark() throws Exception {
384         if (Os.isFamily(Os.FAMILY_WINDOWS)) {
385             System.out.println("testQuotedPathWithQuotationMark() skipped on Windows");
386             return;
387         }
388 
389         File dir = new File(System.getProperty("basedir"), "target/test/quotedpath\"test");
390         createAndCallScript(dir, "echo Quoted");
391 
392         dir = new File(System.getProperty("basedir"), "target/test/quoted path\"test");
393         createAndCallScript(dir, "echo Quoted");
394     }
395 
396     /**
397      * Test an executable with a single quotation mark <code>\"</code> and <code>'</code> in its path only for non
398      * Windows box.
399      *
400      * @throws java.lang.Exception
401      */
402     @Test
403     public void testQuotedPathWithQuotationMarkAndApostrophe() throws Exception {
404         if (Os.isFamily(Os.FAMILY_WINDOWS)) {
405             System.out.println("testQuotedPathWithQuotationMarkAndApostrophe() skipped on Windows");
406             return;
407         }
408 
409         File dir = new File(System.getProperty("basedir"), "target/test/quotedpath\"'test");
410         createAndCallScript(dir, "echo Quoted");
411 
412         dir = new File(System.getProperty("basedir"), "target/test/quoted path\"'test");
413         createAndCallScript(dir, "echo Quoted");
414     }
415 
416     /**
417      * Test an executable with a quote in its path and no space
418      *
419      * @throws java.lang.Exception
420      */
421     @Test
422     public void testOnlyQuotedPath() throws Exception {
423         File dir = new File(System.getProperty("basedir"), "target/test/quotedpath\'test");
424 
425         File javaHome = new File(System.getProperty("java.home"));
426         File java;
427         if (Os.isFamily(Os.FAMILY_WINDOWS)) {
428             java = new File(javaHome, "/bin/java.exe");
429         } else {
430             java = new File(javaHome, "/bin/java");
431         }
432 
433         if (!java.exists()) {
434             throw new IOException(java.getAbsolutePath() + " doesn't exist");
435         }
436 
437         String javaBinStr = java.getAbsolutePath();
438         if (Os.isFamily(Os.FAMILY_WINDOWS) && javaBinStr.contains(" ")) {
439             javaBinStr = "\"" + javaBinStr + "\"";
440         }
441 
442         createAndCallScript(dir, javaBinStr + " -version");
443     }
444 
445     /**
446      * <p>testDollarSignInArgumentPath.</p>
447      *
448      * @throws java.lang.Exception if any.
449      */
450     @Test
451     public void testDollarSignInArgumentPath() throws Exception {
452         File dir = new File(System.getProperty("basedir"), "target/test");
453         if (!dir.exists()) {
454             assertTrue(dir.mkdirs(), "Can't create dir:" + dir.getAbsolutePath());
455         }
456 
457         Writer writer = null;
458         try {
459             writer = Files.newBufferedWriter(dir.toPath().resolve("test$1.txt"));
460             IOUtil.copy("Success", writer);
461         } finally {
462             IOUtil.close(writer);
463         }
464 
465         Commandline cmd = new Commandline();
466         // cmd.getShell().setShellCommand( "/bin/sh" );
467         cmd.getShell().setQuotedArgumentsEnabled(true);
468         cmd.setExecutable("cat");
469         if (Os.isFamily(Os.FAMILY_WINDOWS)) {
470             cmd.setExecutable("dir");
471         }
472         cmd.setWorkingDirectory(dir);
473         cmd.createArg().setLine("test$1.txt");
474 
475         executeCommandLine(cmd);
476     }
477 
478     /**
479      * <p>testTimeOutException.</p>
480      *
481      * @throws java.lang.Exception if any.
482      */
483     @Test
484     public void testTimeOutException() throws Exception {
485         File javaHome = new File(System.getProperty("java.home"));
486         File java;
487         if (Os.isFamily(Os.FAMILY_WINDOWS)) {
488             java = new File(javaHome, "/bin/java.exe");
489         } else {
490             java = new File(javaHome, "/bin/java");
491         }
492 
493         if (!java.exists()) {
494             throw new IOException(java.getAbsolutePath() + " doesn't exist");
495         }
496 
497         Commandline cli = new Commandline();
498         cli.setExecutable(java.getAbsolutePath());
499         cli.createArg().setLine("-version");
500         CommandLineUtils.StringStreamConsumer err = new CommandLineUtils.StringStreamConsumer();
501         try {
502             // if the os is faster than 1s to execute java -version the unit will fail :-)
503             CommandLineUtils.executeCommandLine(cli, new DefaultConsumer(), err, 1);
504         } catch (CommandLineTimeOutException e) {
505             // it works
506         }
507     }
508 
509     /**
510      * Make the file executable for Unix box.
511      *
512      * @param path not null
513      * @throws IOException if any
514      */
515     private static void makeExecutable(File path) throws IOException {
516         if (path == null) {
517             throw new IllegalArgumentException("The file is null");
518         }
519 
520         if (!path.isFile()) {
521             throw new IllegalArgumentException("The file '" + path.getAbsolutePath() + "' should be a file");
522         }
523 
524         if (!Os.isFamily(Os.FAMILY_WINDOWS)) {
525             Process proc = Runtime.getRuntime().exec(new String[] {"chmod", "a+x", path.getAbsolutePath()});
526             while (true) {
527                 try {
528                     proc.waitFor();
529                     break;
530                 } catch (InterruptedException e) {
531                     // ignore
532                 }
533             }
534         }
535     }
536 
537     /**
538      * Create and execute a script file in the given dir with the given content. The script file will be called
539      * <code>echo.bat</code> for Windows box, otherwise <code>echo</code>.
540      *
541      * @param dir the parent dir where echo.bat or echo will be created
542      * @param content the content of the script file
543      * @throws Exception if any
544      */
545     private static void createAndCallScript(File dir, String content) throws Exception {
546         if (!dir.exists()) {
547             assertTrue(dir.mkdirs(), "Can't create dir:" + dir.getAbsolutePath());
548         }
549 
550         // Create a script file
551         File bat;
552         if (Os.isFamily(Os.FAMILY_WINDOWS)) {
553             bat = new File(dir, "echo.bat");
554         } else {
555             bat = new File(dir, "echo");
556         }
557 
558         Writer w = Files.newBufferedWriter(bat.toPath());
559         try {
560             IOUtil.copy(content, w);
561         } finally {
562             IOUtil.close(w);
563         }
564 
565         // Change permission
566         makeExecutable(bat);
567 
568         Commandline cmd = new Commandline();
569         cmd.setExecutable(bat.getAbsolutePath());
570         cmd.setWorkingDirectory(dir);
571 
572         // Execute the script file
573         executeCommandLine(cmd);
574     }
575 
576     /**
577      * Execute the command line
578      *
579      * @param cmd not null
580      * @throws Exception if any
581      */
582     private static void executeCommandLine(Commandline cmd) throws Exception {
583         CommandLineUtils.StringStreamConsumer err = new CommandLineUtils.StringStreamConsumer();
584 
585         try {
586             System.out.println("Command line is: " + StringUtils.join(cmd.getShellCommandline(), " "));
587 
588             int exitCode = CommandLineUtils.executeCommandLine(cmd, new DefaultConsumer(), err);
589 
590             if (exitCode != 0) {
591                 String msg = "Exit code: " + exitCode + " - " + err.getOutput();
592                 throw new Exception(msg.toString());
593             }
594         } catch (CommandLineException e) {
595             throw new Exception("Unable to execute command: " + e.getMessage(), e);
596         }
597     }
598 }