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  import org.junit.jupiter.api.condition.DisabledOnOs;
33  import org.junit.jupiter.api.condition.OS;
34  
35  import static org.junit.jupiter.api.Assertions.assertEquals;
36  import static org.junit.jupiter.api.Assertions.assertTrue;
37  import static org.junit.jupiter.api.Assertions.fail;
38  
39  /**
40   * <p>CommandlineTest class.</p>
41   *
42   * @author herve
43   * @since 3.4.0
44   */
45  class CommandlineTest {
46      private String baseDir;
47  
48      @BeforeEach
49      void setUp() throws Exception {
50          baseDir = System.getProperty("basedir");
51  
52          if (baseDir == null) {
53              baseDir = new File(".").getCanonicalPath();
54          }
55      }
56  
57      @SuppressWarnings("deprecation")
58      @Test
59      void commandlineWithoutCommandInConstructor() {
60          Commandline cmd = new Commandline(new Shell());
61          cmd.setWorkingDirectory(baseDir);
62          cmd.createArgument().setValue("cd");
63          cmd.createArgument().setValue(".");
64  
65          // NOTE: cmd.toString() uses CommandLineUtils.toString( String[] ), which *quotes* the result.
66          assertEquals("cd .", cmd.toString());
67      }
68  
69      @Test
70      void commandlineWithCommandInConstructor() {
71          Commandline cmd = new Commandline("cd .", new Shell());
72          cmd.setWorkingDirectory(baseDir);
73  
74          // NOTE: cmd.toString() uses CommandLineUtils.toString( String[] ), which *quotes* the result.
75          assertEquals("cd .", cmd.toString());
76      }
77  
78      @Test
79      void executeBinaryOnPath() throws Exception {
80          // Maven startup script on PATH is required for this test
81          Commandline cmd = new Commandline();
82          cmd.setWorkingDirectory(baseDir);
83          cmd.setExecutable("mvn");
84          assertEquals("mvn", cmd.getShell().getOriginalExecutable());
85          cmd.createArg().setValue("-version");
86          Process process = cmd.execute();
87          String out = IOUtil.toString(process.getInputStream());
88          assertTrue(out.contains("Apache Maven"));
89          assertTrue(out.contains("Maven home:"));
90          assertTrue(out.contains("Java version:"));
91      }
92  
93      @SuppressWarnings("deprecation")
94      @Test
95      void execute() throws Exception {
96          // allow it to detect the proper shell here.
97          Commandline cmd = new Commandline();
98          cmd.setWorkingDirectory(baseDir);
99          cmd.setExecutable("echo");
100         assertEquals("echo", cmd.getShell().getOriginalExecutable());
101         cmd.createArgument().setValue("Hello");
102 
103         Process process = cmd.execute();
104         assertEquals("Hello", IOUtil.toString(process.getInputStream()).trim());
105     }
106 
107     @SuppressWarnings("deprecation")
108     @Test
109     void setLine() {
110         Commandline cmd = new Commandline(new Shell());
111         cmd.setWorkingDirectory(baseDir);
112         cmd.setExecutable("echo");
113         cmd.createArgument().setLine(null);
114         cmd.createArgument().setLine("Hello");
115 
116         // NOTE: cmd.toString() uses CommandLineUtils.toString( String[] ), which *quotes* the result.
117         assertEquals("echo Hello", cmd.toString());
118     }
119 
120     @SuppressWarnings("deprecation")
121     @Test
122     void createCommandInReverseOrder() {
123         Commandline cmd = new Commandline(new Shell());
124         cmd.setWorkingDirectory(baseDir);
125         cmd.createArgument().setValue(".");
126         cmd.createArgument(true).setValue("cd");
127 
128         // NOTE: cmd.toString() uses CommandLineUtils.toString( String[] ), which *quotes* the result.
129         assertEquals("cd .", cmd.toString());
130     }
131 
132     @SuppressWarnings("deprecation")
133     @Test
134     void setFile() {
135         Commandline cmd = new Commandline(new Shell());
136         cmd.setWorkingDirectory(baseDir);
137         cmd.createArgument().setValue("more");
138         File f = new File("test.txt");
139         cmd.createArgument().setFile(f);
140         String fileName = f.getAbsolutePath();
141         if (fileName.contains(" ")) {
142             fileName = "\"" + fileName + "\"";
143         }
144 
145         // NOTE: cmd.toString() uses CommandLineUtils.toString( String[] ), which *quotes* the result.
146         assertEquals("more " + fileName, cmd.toString());
147     }
148 
149     @Test
150     void getShellCommandLineWindows() {
151         Commandline cmd = new Commandline(new CmdShell());
152         cmd.setExecutable("c:\\Program Files\\xxx");
153         cmd.addArguments(new String[] {"a", "b"});
154         String[] shellCommandline = cmd.getShellCommandline();
155 
156         assertEquals(5, shellCommandline.length, "Command line size");
157 
158         assertEquals("cmd.exe", shellCommandline[0]);
159         assertEquals("/X", shellCommandline[1]);
160         assertEquals("/D", shellCommandline[2]);
161         assertEquals("/C", shellCommandline[3]);
162         String expectedShellCmd = "\"c:" + File.separator + "Program Files" + File.separator + "xxx\" a b";
163         expectedShellCmd = "\"" + expectedShellCmd + "\"";
164         assertEquals(expectedShellCmd, shellCommandline[4]);
165     }
166 
167     @Test
168     void getShellCommandLineWindowsWithSeveralQuotes() {
169         Commandline cmd = new Commandline(new CmdShell());
170         cmd.setExecutable("c:\\Program Files\\xxx");
171         cmd.addArguments(new String[] {"c:\\Documents and Settings\\whatever", "b"});
172         String[] shellCommandline = cmd.getShellCommandline();
173 
174         assertEquals(5, shellCommandline.length, "Command line size");
175 
176         assertEquals("cmd.exe", shellCommandline[0]);
177         assertEquals("/X", shellCommandline[1]);
178         assertEquals("/D", shellCommandline[2]);
179         assertEquals("/C", shellCommandline[3]);
180         String expectedShellCmd = "\"c:" + File.separator + "Program Files" + File.separator
181                 + "xxx\" \"c:\\Documents and Settings\\whatever\" b";
182         expectedShellCmd = "\"" + expectedShellCmd + "\"";
183         assertEquals(expectedShellCmd, shellCommandline[4]);
184     }
185 
186     /**
187      * Test the command line generated for the bash shell
188      */
189     @Test
190     void getShellCommandLineBash() {
191         Commandline cmd = new Commandline(new BourneShell());
192         cmd.setExecutable("/bin/echo");
193         cmd.addArguments(new String[] {"hello world"});
194 
195         String[] shellCommandline = cmd.getShellCommandline();
196 
197         assertEquals(3, shellCommandline.length, "Command line size");
198 
199         assertEquals("/bin/sh", shellCommandline[0]);
200         assertEquals("-c", shellCommandline[1]);
201         String expectedShellCmd = "'/bin/echo' 'hello world'";
202         if (Os.isFamily(Os.FAMILY_WINDOWS)) {
203             expectedShellCmd = "'\\bin\\echo' 'hello world'";
204         }
205         assertEquals(expectedShellCmd, shellCommandline[2]);
206     }
207 
208     /**
209      * Test the command line generated for the bash shell
210      */
211     @Test
212     void getShellCommandLineBashWithWorkingDirectory() {
213         Commandline cmd = new Commandline(new BourneShell());
214         cmd.setExecutable("/bin/echo");
215         cmd.addArguments(new String[] {"hello world"});
216         File root = File.listRoots()[0];
217         File workingDirectory = new File(root, "path with spaces");
218         cmd.setWorkingDirectory(workingDirectory);
219 
220         String[] shellCommandline = cmd.getShellCommandline();
221 
222         assertEquals(3, shellCommandline.length, "Command line size");
223 
224         assertEquals("/bin/sh", shellCommandline[0]);
225         assertEquals("-c", shellCommandline[1]);
226         String expectedShellCmd = "cd '" + root.getAbsolutePath() + "path with spaces' && '/bin/echo' 'hello world'";
227         if (Os.isFamily(Os.FAMILY_WINDOWS)) {
228             expectedShellCmd = "cd '" + root.getAbsolutePath() + "path with spaces' && '\\bin\\echo' 'hello world'";
229         }
230         assertEquals(expectedShellCmd, shellCommandline[2]);
231     }
232 
233     /**
234      * Test the command line generated for the bash shell
235      */
236     @Test
237     void getShellCommandLineBashWithSingleQuotedArg() {
238         Commandline cmd = new Commandline(new BourneShell());
239         cmd.setExecutable("/bin/echo");
240         cmd.addArguments(new String[] {"'hello world'"});
241 
242         String[] shellCommandline = cmd.getShellCommandline();
243 
244         assertEquals(3, shellCommandline.length, "Command line size");
245 
246         assertEquals("/bin/sh", shellCommandline[0]);
247         assertEquals("-c", shellCommandline[1]);
248         String expectedShellCmd = "'/bin/echo' ''\"'\"'hello world'\"'\"''";
249         if (Os.isFamily(Os.FAMILY_WINDOWS)) {
250             expectedShellCmd = expectedShellCmd.replace("/bin/echo", "\\bin\\echo");
251         }
252         assertEquals(expectedShellCmd, shellCommandline[2]);
253     }
254 
255     @Test
256     void getShellCommandLineNonWindows() {
257         Commandline cmd = new Commandline(new BourneShell());
258         cmd.setExecutable("/usr/bin");
259         cmd.addArguments(new String[] {"a", "b"});
260         String[] shellCommandline = cmd.getShellCommandline();
261 
262         assertEquals(3, shellCommandline.length, "Command line size");
263 
264         assertEquals("/bin/sh", shellCommandline[0]);
265         assertEquals("-c", shellCommandline[1]);
266 
267         if (Os.isFamily(Os.FAMILY_WINDOWS)) {
268             assertEquals("'\\usr\\bin' 'a' 'b'", shellCommandline[2]);
269         } else {
270             assertEquals("'/usr/bin' 'a' 'b'", shellCommandline[2]);
271         }
272     }
273 
274     @Test
275     void environment() throws Exception {
276         Commandline cmd = new Commandline();
277         cmd.addEnvironment("name", "value");
278         assertEquals("name=value", cmd.getEnvironmentVariables()[0]);
279     }
280 
281     @Test
282     void environmentWitOverrideSystemEnvironment() throws Exception {
283         Commandline cmd = new Commandline();
284         cmd.addSystemEnvironment();
285         cmd.addEnvironment("JAVA_HOME", "/usr/jdk1.5");
286         String[] environmentVariables = cmd.getEnvironmentVariables();
287 
288         for (String environmentVariable : environmentVariables) {
289             if ("JAVA_HOME=/usr/jdk1.5".equals(environmentVariable)) {
290                 return;
291             }
292         }
293 
294         fail("can't find JAVA_HOME=/usr/jdk1.5");
295     }
296 
297     /**
298      * Test an executable with a single apostrophe <code>'</code> in its path
299      */
300     @Test
301     void quotedPathWithSingleApostrophe() throws Exception {
302         File dir = new File(System.getProperty("basedir"), "target/test/quotedpath'test");
303         createAndCallScript(dir, "echo Quoted");
304 
305         dir = new File(System.getProperty("basedir"), "target/test/quoted path'test");
306         createAndCallScript(dir, "echo Quoted");
307     }
308 
309     /**
310      * Test an executable with shell-expandable content in its path.
311      */
312     @Test
313     void pathWithShellExpansionStrings() throws Exception {
314         File dir = new File(System.getProperty("basedir"), "target/test/dollar$test");
315         createAndCallScript(dir, "echo Quoted");
316     }
317 
318     /**
319      * Test an executable with a single quotation mark <code>\"</code> in its path only for non Windows box.
320      */
321     @Test
322     @DisabledOnOs(OS.WINDOWS)
323     void quotedPathWithQuotationMark() throws Exception {
324         File dir = new File(System.getProperty("basedir"), "target/test/quotedpath\"test");
325         createAndCallScript(dir, "echo Quoted");
326 
327         dir = new File(System.getProperty("basedir"), "target/test/quoted path\"test");
328         createAndCallScript(dir, "echo Quoted");
329     }
330 
331     /**
332      * Test an executable with a single quotation mark <code>\"</code> and <code>'</code> in its path only for non
333      * Windows box.
334      */
335     @Test
336     @DisabledOnOs(OS.WINDOWS)
337     void quotedPathWithQuotationMarkAndApostrophe() throws Exception {
338         File dir = new File(System.getProperty("basedir"), "target/test/quotedpath\"'test");
339         createAndCallScript(dir, "echo Quoted");
340 
341         dir = new File(System.getProperty("basedir"), "target/test/quoted path\"'test");
342         createAndCallScript(dir, "echo Quoted");
343     }
344 
345     /**
346      * Test an executable with a quote in its path and no space
347      */
348     @Test
349     void onlyQuotedPath() throws Exception {
350         File dir = new File(System.getProperty("basedir"), "target/test/quotedpath'test");
351 
352         File javaHome = new File(System.getProperty("java.home"));
353         File java;
354         if (Os.isFamily(Os.FAMILY_WINDOWS)) {
355             java = new File(javaHome, "/bin/java.exe");
356         } else {
357             java = new File(javaHome, "/bin/java");
358         }
359 
360         if (!java.exists()) {
361             throw new IOException(java.getAbsolutePath() + " doesn't exist");
362         }
363 
364         String javaBinStr = java.getAbsolutePath();
365         if (Os.isFamily(Os.FAMILY_WINDOWS) && javaBinStr.contains(" ")) {
366             javaBinStr = "\"" + javaBinStr + "\"";
367         }
368 
369         createAndCallScript(dir, javaBinStr + " -version");
370     }
371 
372     @Test
373     void dollarSignInArgumentPath() throws Exception {
374         File dir = new File(System.getProperty("basedir"), "target/test");
375         if (!dir.exists()) {
376             assertTrue(dir.mkdirs(), "Can't create dir:" + dir.getAbsolutePath());
377         }
378 
379         try (Writer writer = Files.newBufferedWriter(dir.toPath().resolve("test$1.txt"))) {
380             IOUtil.copy("Success", writer);
381         }
382 
383         Commandline cmd = new Commandline();
384         // cmd.getShell().setShellCommand( "/bin/sh" );
385         cmd.getShell().setQuotedArgumentsEnabled(true);
386         cmd.setExecutable("cat");
387         if (Os.isFamily(Os.FAMILY_WINDOWS)) {
388             cmd.setExecutable("dir");
389         }
390         cmd.setWorkingDirectory(dir);
391         cmd.createArg().setLine("test$1.txt");
392 
393         executeCommandLine(cmd);
394     }
395 
396     @Test
397     void timeOutException() throws Exception {
398         File javaHome = new File(System.getProperty("java.home"));
399         File java;
400         if (Os.isFamily(Os.FAMILY_WINDOWS)) {
401             java = new File(javaHome, "/bin/java.exe");
402         } else {
403             java = new File(javaHome, "/bin/java");
404         }
405 
406         if (!java.exists()) {
407             throw new IOException(java.getAbsolutePath() + " doesn't exist");
408         }
409 
410         Commandline cli = new Commandline();
411         cli.setExecutable(java.getAbsolutePath());
412         cli.createArg().setLine("-version");
413         CommandLineUtils.StringStreamConsumer err = new CommandLineUtils.StringStreamConsumer();
414         try {
415             // if the os is faster than 1s to execute java -version the unit will fail :-)
416             CommandLineUtils.executeCommandLine(cli, new DefaultConsumer(), err, 1);
417         } catch (CommandLineTimeOutException e) {
418             // it works
419         }
420     }
421 
422     /**
423      * Make the file executable for Unix box.
424      *
425      * @param path not null
426      * @throws IOException if any
427      */
428     private static void makeExecutable(File path) throws IOException {
429         if (path == null) {
430             throw new IllegalArgumentException("The file is null");
431         }
432 
433         if (!path.isFile()) {
434             throw new IllegalArgumentException("The file '" + path.getAbsolutePath() + "' should be a file");
435         }
436 
437         if (!Os.isFamily(Os.FAMILY_WINDOWS)) {
438             Process proc = Runtime.getRuntime().exec(new String[] {"chmod", "a+x", path.getAbsolutePath()});
439             while (true) {
440                 try {
441                     proc.waitFor();
442                     break;
443                 } catch (InterruptedException e) {
444                     // ignore
445                 }
446             }
447         }
448     }
449 
450     /**
451      * Create and execute a script file in the given dir with the given content. The script file will be called
452      * <code>echo.bat</code> for Windows box, otherwise <code>echo</code>.
453      *
454      * @param dir the parent dir where echo.bat or echo will be created
455      * @param content the content of the script file
456      * @throws Exception if any
457      */
458     private static void createAndCallScript(File dir, String content) throws Exception {
459         if (!dir.exists()) {
460             assertTrue(dir.mkdirs(), "Can't create dir:" + dir.getAbsolutePath());
461         }
462 
463         // Create a script file
464         File bat;
465         if (Os.isFamily(Os.FAMILY_WINDOWS)) {
466             bat = new File(dir, "echo.bat");
467         } else {
468             bat = new File(dir, "echo");
469         }
470 
471         try (Writer w = Files.newBufferedWriter(bat.toPath())) {
472             IOUtil.copy(content, w);
473         }
474 
475         // Change permission
476         makeExecutable(bat);
477 
478         Commandline cmd = new Commandline();
479         cmd.setExecutable(bat.getAbsolutePath());
480         cmd.setWorkingDirectory(dir);
481 
482         // Execute the script file
483         executeCommandLine(cmd);
484     }
485 
486     /**
487      * Execute the command line
488      *
489      * @param cmd not null
490      * @throws Exception if any
491      */
492     private static void executeCommandLine(Commandline cmd) throws Exception {
493         CommandLineUtils.StringStreamConsumer err = new CommandLineUtils.StringStreamConsumer();
494 
495         try {
496             System.out.println("Command line is: " + StringUtils.join(cmd.getShellCommandline(), " "));
497 
498             int exitCode = CommandLineUtils.executeCommandLine(cmd, new DefaultConsumer(), err);
499 
500             if (exitCode != 0) {
501                 throw new Exception("Exit code: " + exitCode + " - " + err.getOutput());
502             }
503         } catch (CommandLineException e) {
504             throw new Exception("Unable to execute command: " + e.getMessage(), e);
505         }
506     }
507 }