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(4, shellCommandline.length, "Command line size");
186 
187         assertEquals("cmd.exe", shellCommandline[0]);
188         assertEquals("/X", shellCommandline[1]);
189         assertEquals("/C", shellCommandline[2]);
190         String expectedShellCmd = "\"c:" + File.separator + "Program Files" + File.separator + "xxx\" a b";
191         expectedShellCmd = "\"" + expectedShellCmd + "\"";
192         assertEquals(expectedShellCmd, shellCommandline[3]);
193     }
194 
195     /**
196      * <p>testGetShellCommandLineWindowsWithSeveralQuotes.</p>
197      *
198      * @throws java.lang.Exception if any.
199      */
200     @Test
201     public void testGetShellCommandLineWindowsWithSeveralQuotes() throws Exception {
202         Commandline cmd = new Commandline(new CmdShell());
203         cmd.setExecutable("c:\\Program Files\\xxx");
204         cmd.addArguments(new String[] {"c:\\Documents and Settings\\whatever", "b"});
205         String[] shellCommandline = cmd.getShellCommandline();
206 
207         assertEquals(4, shellCommandline.length, "Command line size");
208 
209         assertEquals("cmd.exe", shellCommandline[0]);
210         assertEquals("/X", shellCommandline[1]);
211         assertEquals("/C", shellCommandline[2]);
212         String expectedShellCmd = "\"c:" + File.separator + "Program Files" + File.separator
213                 + "xxx\" \"c:\\Documents and Settings\\whatever\" b";
214         expectedShellCmd = "\"" + expectedShellCmd + "\"";
215         assertEquals(expectedShellCmd, shellCommandline[3]);
216     }
217 
218     /**
219      * Test the command line generated for the bash shell
220      *
221      * @throws java.lang.Exception
222      */
223     @Test
224     public void testGetShellCommandLineBash() throws Exception {
225         Commandline cmd = new Commandline(new BourneShell());
226         cmd.setExecutable("/bin/echo");
227         cmd.addArguments(new String[] {"hello world"});
228 
229         String[] shellCommandline = cmd.getShellCommandline();
230 
231         assertEquals(3, shellCommandline.length, "Command line size");
232 
233         assertEquals("/bin/sh", shellCommandline[0]);
234         assertEquals("-c", shellCommandline[1]);
235         String expectedShellCmd = "'/bin/echo' 'hello world'";
236         if (Os.isFamily(Os.FAMILY_WINDOWS)) {
237             expectedShellCmd = "'\\bin\\echo' \'hello world\'";
238         }
239         assertEquals(expectedShellCmd, shellCommandline[2]);
240     }
241 
242     /**
243      * Test the command line generated for the bash shell
244      *
245      * @throws java.lang.Exception
246      */
247     @Test
248     public void testGetShellCommandLineBash_WithWorkingDirectory() throws Exception {
249         Commandline cmd = new Commandline(new BourneShell());
250         cmd.setExecutable("/bin/echo");
251         cmd.addArguments(new String[] {"hello world"});
252         File root = File.listRoots()[0];
253         File workingDirectory = new File(root, "path with spaces");
254         cmd.setWorkingDirectory(workingDirectory);
255 
256         String[] shellCommandline = cmd.getShellCommandline();
257 
258         assertEquals(3, shellCommandline.length, "Command line size");
259 
260         assertEquals("/bin/sh", shellCommandline[0]);
261         assertEquals("-c", shellCommandline[1]);
262         String expectedShellCmd = "cd '" + root.getAbsolutePath() + "path with spaces' && '/bin/echo' 'hello world'";
263         if (Os.isFamily(Os.FAMILY_WINDOWS)) {
264             expectedShellCmd = "cd '" + root.getAbsolutePath() + "path with spaces' && '\\bin\\echo' 'hello world'";
265         }
266         assertEquals(expectedShellCmd, shellCommandline[2]);
267     }
268 
269     /**
270      * Test the command line generated for the bash shell
271      *
272      * @throws java.lang.Exception
273      */
274     @Test
275     public void testGetShellCommandLineBash_WithSingleQuotedArg() throws Exception {
276         Commandline cmd = new Commandline(new BourneShell());
277         cmd.setExecutable("/bin/echo");
278         cmd.addArguments(new String[] {"\'hello world\'"});
279 
280         String[] shellCommandline = cmd.getShellCommandline();
281 
282         assertEquals(3, shellCommandline.length, "Command line size");
283 
284         assertEquals("/bin/sh", shellCommandline[0]);
285         assertEquals("-c", shellCommandline[1]);
286         String expectedShellCmd = "'/bin/echo' ''\"'\"'hello world'\"'\"''";
287         if (Os.isFamily(Os.FAMILY_WINDOWS)) {
288             expectedShellCmd = expectedShellCmd.replace("/bin/echo", "\\bin\\echo");
289         }
290         assertEquals(expectedShellCmd, shellCommandline[2]);
291     }
292 
293     /**
294      * <p>testGetShellCommandLineNonWindows.</p>
295      *
296      * @throws java.lang.Exception if any.
297      */
298     @Test
299     public void testGetShellCommandLineNonWindows() throws Exception {
300         Commandline cmd = new Commandline(new BourneShell());
301         cmd.setExecutable("/usr/bin");
302         cmd.addArguments(new String[] {"a", "b"});
303         String[] shellCommandline = cmd.getShellCommandline();
304 
305         assertEquals(3, shellCommandline.length, "Command line size");
306 
307         assertEquals("/bin/sh", shellCommandline[0]);
308         assertEquals("-c", shellCommandline[1]);
309 
310         if (Os.isFamily(Os.FAMILY_WINDOWS)) {
311             assertEquals("'\\usr\\bin' 'a' 'b'", shellCommandline[2]);
312         } else {
313             assertEquals("'/usr/bin' 'a' 'b'", shellCommandline[2]);
314         }
315     }
316 
317     /**
318      * <p>testEnvironment.</p>
319      *
320      * @throws java.lang.Exception if any.
321      */
322     @Test
323     public void testEnvironment() throws Exception {
324         Commandline cmd = new Commandline();
325         cmd.addEnvironment("name", "value");
326         assertEquals("name=value", cmd.getEnvironmentVariables()[0]);
327     }
328 
329     /**
330      * <p>testEnvironmentWitOverrideSystemEnvironment.</p>
331      *
332      * @throws java.lang.Exception if any.
333      */
334     @Test
335     public void testEnvironmentWitOverrideSystemEnvironment() throws Exception {
336         Commandline cmd = new Commandline();
337         cmd.addSystemEnvironment();
338         cmd.addEnvironment("JAVA_HOME", "/usr/jdk1.5");
339         String[] environmentVariables = cmd.getEnvironmentVariables();
340 
341         for (String environmentVariable : environmentVariables) {
342             if ("JAVA_HOME=/usr/jdk1.5".equals(environmentVariable)) {
343                 return;
344             }
345         }
346 
347         fail("can't find JAVA_HOME=/usr/jdk1.5");
348     }
349 
350     /**
351      * Test an executable with a single apostrophe <code>'</code> in its path
352      *
353      * @throws java.lang.Exception
354      */
355     @Test
356     public void testQuotedPathWithSingleApostrophe() throws Exception {
357         File dir = new File(System.getProperty("basedir"), "target/test/quotedpath'test");
358         createAndCallScript(dir, "echo Quoted");
359 
360         dir = new File(System.getProperty("basedir"), "target/test/quoted path'test");
361         createAndCallScript(dir, "echo Quoted");
362     }
363 
364     /**
365      * Test an executable with shell-expandable content in its path.
366      *
367      * @throws java.lang.Exception
368      */
369     @Test
370     public void testPathWithShellExpansionStrings() throws Exception {
371         File dir = new File(System.getProperty("basedir"), "target/test/dollar$test");
372         createAndCallScript(dir, "echo Quoted");
373     }
374 
375     /**
376      * Test an executable with a single quotation mark <code>\"</code> in its path only for non Windows box.
377      *
378      * @throws java.lang.Exception
379      */
380     @Test
381     public void testQuotedPathWithQuotationMark() throws Exception {
382         if (Os.isFamily(Os.FAMILY_WINDOWS)) {
383             System.out.println("testQuotedPathWithQuotationMark() skipped on Windows");
384             return;
385         }
386 
387         File dir = new File(System.getProperty("basedir"), "target/test/quotedpath\"test");
388         createAndCallScript(dir, "echo Quoted");
389 
390         dir = new File(System.getProperty("basedir"), "target/test/quoted path\"test");
391         createAndCallScript(dir, "echo Quoted");
392     }
393 
394     /**
395      * Test an executable with a single quotation mark <code>\"</code> and <code>'</code> in its path only for non
396      * Windows box.
397      *
398      * @throws java.lang.Exception
399      */
400     @Test
401     public void testQuotedPathWithQuotationMarkAndApostrophe() throws Exception {
402         if (Os.isFamily(Os.FAMILY_WINDOWS)) {
403             System.out.println("testQuotedPathWithQuotationMarkAndApostrophe() skipped on Windows");
404             return;
405         }
406 
407         File dir = new File(System.getProperty("basedir"), "target/test/quotedpath\"'test");
408         createAndCallScript(dir, "echo Quoted");
409 
410         dir = new File(System.getProperty("basedir"), "target/test/quoted path\"'test");
411         createAndCallScript(dir, "echo Quoted");
412     }
413 
414     /**
415      * Test an executable with a quote in its path and no space
416      *
417      * @throws java.lang.Exception
418      */
419     @Test
420     public void testOnlyQuotedPath() throws Exception {
421         File dir = new File(System.getProperty("basedir"), "target/test/quotedpath\'test");
422 
423         File javaHome = new File(System.getProperty("java.home"));
424         File java;
425         if (Os.isFamily(Os.FAMILY_WINDOWS)) {
426             java = new File(javaHome, "/bin/java.exe");
427         } else {
428             java = new File(javaHome, "/bin/java");
429         }
430 
431         if (!java.exists()) {
432             throw new IOException(java.getAbsolutePath() + " doesn't exist");
433         }
434 
435         String javaBinStr = java.getAbsolutePath();
436         if (Os.isFamily(Os.FAMILY_WINDOWS) && javaBinStr.contains(" ")) {
437             javaBinStr = "\"" + javaBinStr + "\"";
438         }
439 
440         createAndCallScript(dir, javaBinStr + " -version");
441     }
442 
443     /**
444      * <p>testDollarSignInArgumentPath.</p>
445      *
446      * @throws java.lang.Exception if any.
447      */
448     @Test
449     public void testDollarSignInArgumentPath() throws Exception {
450         File dir = new File(System.getProperty("basedir"), "target/test");
451         if (!dir.exists()) {
452             assertTrue(dir.mkdirs(), "Can't create dir:" + dir.getAbsolutePath());
453         }
454 
455         Writer writer = null;
456         try {
457             writer = Files.newBufferedWriter(dir.toPath().resolve("test$1.txt"));
458             IOUtil.copy("Success", writer);
459         } finally {
460             IOUtil.close(writer);
461         }
462 
463         Commandline cmd = new Commandline();
464         // cmd.getShell().setShellCommand( "/bin/sh" );
465         cmd.getShell().setQuotedArgumentsEnabled(true);
466         cmd.setExecutable("cat");
467         if (Os.isFamily(Os.FAMILY_WINDOWS)) {
468             cmd.setExecutable("dir");
469         }
470         cmd.setWorkingDirectory(dir);
471         cmd.createArg().setLine("test$1.txt");
472 
473         executeCommandLine(cmd);
474     }
475 
476     /**
477      * <p>testTimeOutException.</p>
478      *
479      * @throws java.lang.Exception if any.
480      */
481     @Test
482     public void testTimeOutException() throws Exception {
483         File javaHome = new File(System.getProperty("java.home"));
484         File java;
485         if (Os.isFamily(Os.FAMILY_WINDOWS)) {
486             java = new File(javaHome, "/bin/java.exe");
487         } else {
488             java = new File(javaHome, "/bin/java");
489         }
490 
491         if (!java.exists()) {
492             throw new IOException(java.getAbsolutePath() + " doesn't exist");
493         }
494 
495         Commandline cli = new Commandline();
496         cli.setExecutable(java.getAbsolutePath());
497         cli.createArg().setLine("-version");
498         CommandLineUtils.StringStreamConsumer err = new CommandLineUtils.StringStreamConsumer();
499         try {
500             // if the os is faster than 1s to execute java -version the unit will fail :-)
501             CommandLineUtils.executeCommandLine(cli, new DefaultConsumer(), err, 1);
502         } catch (CommandLineTimeOutException e) {
503             // it works
504         }
505     }
506 
507     /**
508      * Make the file executable for Unix box.
509      *
510      * @param path not null
511      * @throws IOException if any
512      */
513     private static void makeExecutable(File path) throws IOException {
514         if (path == null) {
515             throw new IllegalArgumentException("The file is null");
516         }
517 
518         if (!path.isFile()) {
519             throw new IllegalArgumentException("The file '" + path.getAbsolutePath() + "' should be a file");
520         }
521 
522         if (!Os.isFamily(Os.FAMILY_WINDOWS)) {
523             Process proc = Runtime.getRuntime().exec(new String[] {"chmod", "a+x", path.getAbsolutePath()});
524             while (true) {
525                 try {
526                     proc.waitFor();
527                     break;
528                 } catch (InterruptedException e) {
529                     // ignore
530                 }
531             }
532         }
533     }
534 
535     /**
536      * Create and execute a script file in the given dir with the given content. The script file will be called
537      * <code>echo.bat</code> for Windows box, otherwise <code>echo</code>.
538      *
539      * @param dir the parent dir where echo.bat or echo will be created
540      * @param content the content of the script file
541      * @throws Exception if any
542      */
543     private static void createAndCallScript(File dir, String content) throws Exception {
544         if (!dir.exists()) {
545             assertTrue(dir.mkdirs(), "Can't create dir:" + dir.getAbsolutePath());
546         }
547 
548         // Create a script file
549         File bat;
550         if (Os.isFamily(Os.FAMILY_WINDOWS)) {
551             bat = new File(dir, "echo.bat");
552         } else {
553             bat = new File(dir, "echo");
554         }
555 
556         Writer w = Files.newBufferedWriter(bat.toPath());
557         try {
558             IOUtil.copy(content, w);
559         } finally {
560             IOUtil.close(w);
561         }
562 
563         // Change permission
564         makeExecutable(bat);
565 
566         Commandline cmd = new Commandline();
567         cmd.setExecutable(bat.getAbsolutePath());
568         cmd.setWorkingDirectory(dir);
569 
570         // Execute the script file
571         executeCommandLine(cmd);
572     }
573 
574     /**
575      * Execute the command line
576      *
577      * @param cmd not null
578      * @throws Exception if any
579      */
580     private static void executeCommandLine(Commandline cmd) throws Exception {
581         CommandLineUtils.StringStreamConsumer err = new CommandLineUtils.StringStreamConsumer();
582 
583         try {
584             System.out.println("Command line is: " + StringUtils.join(cmd.getShellCommandline(), " "));
585 
586             int exitCode = CommandLineUtils.executeCommandLine(cmd, new DefaultConsumer(), err);
587 
588             if (exitCode != 0) {
589                 String msg = "Exit code: " + exitCode + " - " + err.getOutput();
590                 throw new Exception(msg.toString());
591             }
592         } catch (CommandLineException e) {
593             throw new Exception("Unable to execute command: " + e.getMessage(), e);
594         }
595     }
596 }