View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    *  contributor license agreements.  See the NOTICE file distributed with
4    *  this work for additional information regarding copyright ownership.
5    *  The ASF licenses this file to You under the Apache License, Version 2.0
6    *  (the "License"); you may not use this file except in compliance with
7    *  the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   *  Unless required by applicable law or agreed to in writing, software
12   *  distributed under the License is distributed on an "AS IS" BASIS,
13   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   *  See the License for the specific language governing permissions and
15   *  limitations under the License.
16   */
17  
18  package org.apache.commons.exec;
19  
20  import static org.junit.jupiter.api.Assertions.assertArrayEquals;
21  import static org.junit.jupiter.api.Assertions.assertEquals;
22  import static org.junit.jupiter.api.Assertions.assertFalse;
23  import static org.junit.jupiter.api.Assertions.assertThrows;
24  import static org.junit.jupiter.api.Assertions.assertTrue;
25  
26  import java.io.File;
27  import java.util.HashMap;
28  import java.util.Map;
29  
30  import org.apache.commons.exec.util.StringUtils;
31  import org.junit.jupiter.api.Test;
32  
33  /**
34   */
35  public class CommandLineTest {
36  
37      @Test
38      public void testAddArgument() {
39          final CommandLine cmdl = new CommandLine("test");
40  
41          cmdl.addArgument("foo");
42          cmdl.addArgument("bar");
43          assertEquals("[test, foo, bar]", cmdl.toString());
44          assertArrayEquals(new String[] { "test", "foo", "bar" }, cmdl.toStrings());
45      }
46  
47      @Test
48      public void testAddArguments() {
49          final CommandLine cmdl = new CommandLine("test");
50          cmdl.addArguments("foo bar");
51          assertEquals("[test, foo, bar]", cmdl.toString());
52          assertArrayEquals(new String[] { "test", "foo", "bar" }, cmdl.toStrings());
53      }
54  
55      @Test
56      public void testAddArgumentsArray() {
57          final CommandLine cmdl = new CommandLine("test");
58          cmdl.addArguments(new String[] { "foo", "bar" });
59          assertEquals("[test, foo, bar]", cmdl.toString());
60          assertArrayEquals(new String[] { "test", "foo", "bar" }, cmdl.toStrings());
61      }
62  
63      @Test
64      public void testAddArgumentsArrayNull() {
65          final CommandLine cmdl = new CommandLine("test");
66          cmdl.addArguments((String[]) null);
67          assertEquals("[test]", cmdl.toString());
68          assertArrayEquals(new String[] { "test" }, cmdl.toStrings());
69      }
70  
71      @Test
72      public void testAddArgumentsWithQuotes() {
73          final CommandLine cmdl = new CommandLine("test");
74          cmdl.addArguments("'foo' \"bar\"");
75          assertEquals("[test, foo, bar]", cmdl.toString());
76          assertArrayEquals(new String[] { "test", "foo", "bar" }, cmdl.toStrings());
77      }
78  
79      @Test
80      public void testAddArgumentsWithQuotesAndSpaces() {
81          final CommandLine cmdl = new CommandLine("test");
82          cmdl.addArguments("'fo o' \"ba r\"");
83          assertEquals("[test, \"fo o\", \"ba r\"]", cmdl.toString());
84          assertArrayEquals(new String[] { "test", "\"fo o\"", "\"ba r\"" }, cmdl.toStrings());
85      }
86  
87      @Test
88      public void testAddArgumentWithBothQuotes() {
89          final CommandLine cmdl = new CommandLine("test");
90          assertThrows(IllegalArgumentException.class, () -> cmdl.addArgument("b\"a'r"));
91      }
92  
93      @Test
94      public void testAddArgumentWithQuote() {
95          final CommandLine cmdl = new CommandLine("test");
96          cmdl.addArgument("foo");
97          cmdl.addArgument("ba\"r");
98          assertEquals("[test, foo, 'ba\"r']", cmdl.toString());
99          assertArrayEquals(new String[] { "test", "foo", "'ba\"r'" }, cmdl.toStrings());
100     }
101 
102     @Test
103     public void testAddArgumentWithQuotesAround() {
104         final CommandLine cmdl = new CommandLine("test");
105         cmdl.addArgument("\'foo\'");
106         cmdl.addArgument("\"bar\"");
107         cmdl.addArgument("\"fe z\"");
108         assertEquals("[test, foo, bar, \"fe z\"]", cmdl.toString());
109         assertArrayEquals(new String[] { "test", "foo", "bar", "\"fe z\"" }, cmdl.toStrings());
110     }
111 
112     @Test
113     public void testAddArgumentWithSingleQuote() {
114         final CommandLine cmdl = new CommandLine("test");
115 
116         cmdl.addArgument("foo");
117         cmdl.addArgument("ba'r");
118         assertEquals("[test, foo, \"ba'r\"]", cmdl.toString());
119         assertArrayEquals(new String[] { "test", "foo", "\"ba\'r\"" }, cmdl.toStrings());
120     }
121 
122     @Test
123     public void testAddArgumentWithSpace() {
124         final CommandLine cmdl = new CommandLine("test");
125         cmdl.addArgument("foo");
126         cmdl.addArgument("ba r");
127         assertEquals("[test, foo, \"ba r\"]", cmdl.toString());
128         assertArrayEquals(new String[] { "test", "foo", "\"ba r\"" }, cmdl.toStrings());
129     }
130 
131     @Test
132     public void testAddNullArgument() {
133         final CommandLine cmdl = new CommandLine("test");
134 
135         cmdl.addArgument(null);
136         assertEquals("[test]", cmdl.toString());
137         assertArrayEquals(new String[] { "test" }, cmdl.toStrings());
138     }
139 
140     /**
141      * A little example how to add two command line arguments in one line, e.g. to make commenting out some options less error prone.
142      */
143     @Test
144     public void testAddTwoArguments() {
145 
146         final CommandLine userAddCL1 = new CommandLine("useradd");
147         userAddCL1.addArgument("-g");
148         userAddCL1.addArgument("tomcat");
149         userAddCL1.addArgument("foo");
150 
151         final CommandLine userAddCL2 = new CommandLine("useradd");
152         userAddCL2.addArgument("-g").addArgument("tomcat");
153         userAddCL2.addArgument("foo");
154 
155         assertEquals(userAddCL1.toString(), userAddCL2.toString());
156     }
157 
158     /**
159      * Test expanding the command line based on a user-supplied map.
160      */
161     @Test
162     public void testCommandLineParsingWithExpansion1() {
163 
164         CommandLine cmdl;
165 
166         final HashMap<String, Object> substitutionMap = new HashMap<>();
167         substitutionMap.put("JAVA_HOME", "/usr/local/java");
168         substitutionMap.put("appMainClass", "foo.bar.Main");
169         substitutionMap.put("file1", new File("./pom.xml"));
170         substitutionMap.put("file2", new File(".\\temp\\READ ME.txt"));
171 
172         final HashMap<String, String> incompleteMap = new HashMap<>();
173         incompleteMap.put("JAVA_HOME", "/usr/local/java");
174 
175         // do not pass substitution map
176         cmdl = CommandLine.parse("${JAVA_HOME}/bin/java ${appMainClass}");
177         assertTrue(cmdl.getExecutable().startsWith("${JAVA_HOME}"));
178         assertArrayEquals(new String[] { "${appMainClass}" }, cmdl.getArguments());
179 
180         // pass arguments with an empty map
181         cmdl = CommandLine.parse("${JAVA_HOME}/bin/java ${appMainClass}", new HashMap<>());
182         assertTrue(cmdl.getExecutable().startsWith("${JAVA_HOME}"));
183         assertArrayEquals(new String[] { "${appMainClass}" }, cmdl.getArguments());
184 
185         // pass an complete substitution map
186         cmdl = CommandLine.parse("${JAVA_HOME}/bin/java ${appMainClass}", substitutionMap);
187         assertFalse(cmdl.getExecutable().contains("${JAVA_HOME}"));
188         assertTrue(cmdl.getExecutable().indexOf("local") > 0);
189         assertArrayEquals(new String[] { "foo.bar.Main" }, cmdl.getArguments());
190 
191         // pass an incomplete substitution map resulting in unresolved variables
192         cmdl = CommandLine.parse("${JAVA_HOME}/bin/java ${appMainClass}", incompleteMap);
193         assertFalse(cmdl.getExecutable().contains("${JAVA_HOME}"));
194         assertTrue(cmdl.getExecutable().indexOf("local") > 0);
195         assertArrayEquals(new String[] { "${appMainClass}" }, cmdl.getArguments());
196 
197         // pass a file
198         cmdl = CommandLine.parse("${JAVA_HOME}/bin/java ${appMainClass} ${file1} ${file2}", substitutionMap);
199         assertFalse(cmdl.getExecutable().contains("${file}"));
200     }
201 
202     /**
203      * Test expanding the command line based on a user-supplied map. The main goal of the test is to setup a command line using macros and reuse it multiple
204      * times.
205      */
206     @Test
207     public void testCommandLineParsingWithExpansion2() {
208 
209         CommandLine cmdl;
210         String[] result;
211 
212         // build the user supplied parameters
213         final HashMap<String, String> substitutionMap = new HashMap<>();
214         substitutionMap.put("JAVA_HOME", "C:\\Programme\\jdk1.5.0_12");
215         substitutionMap.put("appMainClass", "foo.bar.Main");
216 
217         // build the command line
218         cmdl = new CommandLine("${JAVA_HOME}\\bin\\java");
219         cmdl.addArgument("-class");
220         cmdl.addArgument("${appMainClass}");
221         cmdl.addArgument("${file}");
222 
223         // build the first command line
224         substitutionMap.put("file", "C:\\Document And Settings\\documents\\432431.pdf");
225         cmdl.setSubstitutionMap(substitutionMap);
226         result = cmdl.toStrings();
227 
228         // verify the first command line
229         // please note - the executable argument is changed to using platform specific file separator char
230         // whereas all other variable substitution are not touched
231         assertEquals(StringUtils.fixFileSeparatorChar("C:\\Programme\\jdk1.5.0_12\\bin\\java"), result[0]);
232         assertEquals("-class", result[1]);
233         assertEquals("foo.bar.Main", result[2]);
234         assertEquals("\"C:\\Document And Settings\\documents\\432431.pdf\"", result[3]);
235 
236         // verify the first command line again but by
237         // accessing the executable and arguments directly
238         final String executable = cmdl.getExecutable();
239         final String[] arguments = cmdl.getArguments();
240         assertEquals(StringUtils.fixFileSeparatorChar("C:\\Programme\\jdk1.5.0_12\\bin\\java"), executable);
241         assertEquals("-class", arguments[0]);
242         assertEquals("foo.bar.Main", arguments[1]);
243         assertEquals("\"C:\\Document And Settings\\documents\\432431.pdf\"", arguments[2]);
244 
245         // build the second command line with updated parameters resulting in a different command line
246         substitutionMap.put("file", "C:\\Document And Settings\\documents\\432432.pdf");
247         result = cmdl.toStrings();
248         assertEquals(StringUtils.fixFileSeparatorChar("C:\\Programme\\jdk1.5.0_12\\bin\\java"), result[0]);
249         assertEquals("-class", result[1]);
250         assertEquals("foo.bar.Main", result[2]);
251         assertEquals("\"C:\\Document And Settings\\documents\\432432.pdf\"", result[3]);
252     }
253 
254     @Test
255     public void testCommandLineParsingWithExpansion3() {
256         final CommandLine cmdl = CommandLine.parse("AcroRd32.exe");
257         cmdl.addArgument("/p");
258         cmdl.addArgument("/h");
259         cmdl.addArgument("${file}", false);
260         final HashMap<String, String> params = new HashMap<>();
261         params.put("file", "C:\\Document And Settings\\documents\\432432.pdf");
262         cmdl.setSubstitutionMap(params);
263         final String[] result = cmdl.toStrings();
264         assertEquals("AcroRd32.exe", result[0]);
265         assertEquals("/p", result[1]);
266         assertEquals("/h", result[2]);
267         assertEquals("C:\\Document And Settings\\documents\\432432.pdf", result[3]);
268 
269     }
270 
271     /**
272      * Create a command line with pre-quoted strings to test SANDBOX-192, e.g. "runMemorySud.cmd", "10", "30", "-XX:+UseParallelGC",
273      * "\"-XX:ParallelGCThreads=2\""
274      */
275     @Test
276     public void testComplexAddArgument() {
277         final CommandLine cmdl = new CommandLine("runMemorySud.cmd");
278         cmdl.addArgument("10", false);
279         cmdl.addArgument("30", false);
280         cmdl.addArgument("-XX:+UseParallelGC", false);
281         cmdl.addArgument("\"-XX:ParallelGCThreads=2\"", false);
282         assertArrayEquals(new String[] { "runMemorySud.cmd", "10", "30", "-XX:+UseParallelGC", "\"-XX:ParallelGCThreads=2\"" }, cmdl.toStrings());
283     }
284 
285     /**
286      * Create a command line with pre-quoted strings to test SANDBOX-192, e.g. "runMemorySud.cmd", "10", "30", "-XX:+UseParallelGC",
287      * "\"-XX:ParallelGCThreads=2\""
288      */
289     @Test
290     public void testComplexAddArguments1() {
291         final CommandLine cmdl = new CommandLine("runMemorySud.cmd");
292         cmdl.addArguments(new String[] { "10", "30", "-XX:+UseParallelGC", "\"-XX:ParallelGCThreads=2\"" }, false);
293         assertArrayEquals(new String[] { "runMemorySud.cmd", "10", "30", "-XX:+UseParallelGC", "\"-XX:ParallelGCThreads=2\"" }, cmdl.toStrings());
294     }
295 
296     /**
297      * Create a command line with pre-quoted strings to test SANDBOX-192, e.g. "runMemorySud.cmd", "10", "30", "-XX:+UseParallelGC",
298      * "\"-XX:ParallelGCThreads=2\"" Please not that we re forced to add additional single quotes to get the test working - don't know if this is a bug or a
299      * feature.
300      */
301     @Test
302     public void testComplexAddArguments2() {
303         final CommandLine cmdl = new CommandLine("runMemorySud.cmd");
304         cmdl.addArguments("10 30 -XX:+UseParallelGC '\"-XX:ParallelGCThreads=2\"'", false);
305         assertArrayEquals(new String[] { "runMemorySud.cmd", "10", "30", "-XX:+UseParallelGC", "\"-XX:ParallelGCThreads=2\"" }, cmdl.toStrings());
306     }
307 
308     @Test
309     public void testCopyConstructor() {
310         final Map<String, String> map = new HashMap<>();
311         map.put("bar", "bar");
312         final CommandLine other = new CommandLine("test");
313         other.addArgument("foo");
314         other.setSubstitutionMap(map);
315 
316         final CommandLine cmdl = new CommandLine(other);
317         assertEquals(other.getExecutable(), cmdl.getExecutable());
318         assertArrayEquals(other.getArguments(), cmdl.getArguments());
319         assertEquals(other.isFile(), cmdl.isFile());
320         assertEquals(other.getSubstitutionMap(), cmdl.getSubstitutionMap());
321 
322     }
323 
324     @Test
325     public void testExecutable() {
326         final CommandLine cmdl = new CommandLine("test");
327         assertEquals("[test]", cmdl.toString());
328         assertArrayEquals(new String[] { "test" }, cmdl.toStrings());
329         assertEquals("test", cmdl.getExecutable());
330         assertTrue(cmdl.getArguments().length == 0);
331     }
332 
333     @Test
334     public void testExecutableWhitespaceString() {
335         assertThrows(IllegalArgumentException.class, () -> new CommandLine("   "));
336     }
337 
338     @Test
339     public void testExecutableZeroLengthString() {
340         assertThrows(IllegalArgumentException.class, () -> new CommandLine(""));
341     }
342 
343     @Test
344     public void testNullExecutable() {
345         assertThrows(NullPointerException.class, () -> new CommandLine((String) null));
346     }
347 
348     @Test
349     public void testParseCommandLine() {
350         final CommandLine cmdl = CommandLine.parse("test foo bar");
351         assertEquals("[test, foo, bar]", cmdl.toString());
352         assertArrayEquals(new String[] { "test", "foo", "bar" }, cmdl.toStrings());
353     }
354 
355     @Test
356     public void testParseCommandLineWithNull() {
357         assertThrows(IllegalArgumentException.class, () -> CommandLine.parse(null));
358     }
359 
360     @Test
361     public void testParseCommandLineWithOnlyWhitespace() {
362         assertThrows(IllegalArgumentException.class, () -> CommandLine.parse("  "));
363     }
364 
365     @Test
366     public void testParseCommandLineWithQuotes() {
367         final CommandLine cmdl = CommandLine.parse("test \"foo\" \'ba r\'");
368         assertEquals("[test, foo, \"ba r\"]", cmdl.toString());
369         assertArrayEquals(new String[] { "test", "foo", "\"ba r\"" }, cmdl.toStrings());
370     }
371 
372     @Test
373     public void testParseCommandLineWithUnevenQuotes() {
374         assertThrows(IllegalArgumentException.class, () -> CommandLine.parse("test \"foo bar"), "IllegalArgumentException must be thrown due to uneven quotes");
375     }
376 
377     /**
378      * A command line parsing puzzle from Tino Schoellhorn - ImageMagix expects a "500x>" parameter (including quotes) and it is simply not possible to do that
379      * without adding a space, e.g. "500x> ".
380      */
381     @Test
382     public void testParseComplexCommandLine1() {
383         final HashMap<String, String> substitutionMap = new HashMap<>();
384         substitutionMap.put("in", "source.jpg");
385         substitutionMap.put("out", "target.jpg");
386         final CommandLine cmdl = CommandLine.parse("cmd /C convert ${in} -resize \"\'500x> \'\" ${out}", substitutionMap);
387         assertEquals("[cmd, /C, convert, source.jpg, -resize, \"500x> \", target.jpg]", cmdl.toString());
388     }
389 
390     /**
391      * Another command line parsing puzzle from Kai Hu - as far as I understand it there is no way to express that in a one-line command string.
392      */
393     @Test
394     public void testParseComplexCommandLine2() {
395         final String commandLine = "./script/jrake cruise:publish_installers " + "INSTALLER_VERSION=unstable_2_1 "
396                 + "INSTALLER_PATH=\"/var/lib/ cruise-agent/installers\" " + "INSTALLER_DOWNLOAD_SERVER=\'something\' " + "WITHOUT_HELP_DOC=true";
397         final CommandLine cmdl = CommandLine.parse(commandLine);
398         final String[] args = cmdl.getArguments();
399         assertEquals(args[0], "cruise:publish_installers");
400         assertEquals(args[1], "INSTALLER_VERSION=unstable_2_1");
401         // assertEquals(args[2], "INSTALLER_PATH=\"/var/lib/ cruise-agent/installers\"");
402         // assertEquals(args[3], "INSTALLER_DOWNLOAD_SERVER='something'");
403         assertEquals(args[4], "WITHOUT_HELP_DOC=true");
404     }
405 
406     /**
407      * Test the following command line
408      *
409      * cmd.exe /C c:\was51\Web Sphere\AppServer\bin\versionInfo.bat
410      */
411     @Test
412     public void testParseRealLifeCommandLine_1() {
413 
414         final String commandLine = "cmd.exe /C \"c:\\was51\\Web Sphere\\AppServer\\bin\\versionInfo.bat\"";
415 
416         final CommandLine cmdl = CommandLine.parse(commandLine);
417         final String[] args = cmdl.getArguments();
418         assertEquals("/C", args[0]);
419         assertEquals("\"c:\\was51\\Web Sphere\\AppServer\\bin\\versionInfo.bat\"", args[1]);
420     }
421 
422     /**
423      * Test the toString() method.
424      *
425      * @throws Exception the test failed
426      */
427     @Test
428     public void testToString() throws Exception {
429         CommandLine cmdl;
430         final HashMap<String, String> params = new HashMap<>();
431 
432         // use no arguments
433         cmdl = CommandLine.parse("AcroRd32.exe", params);
434         assertEquals("[AcroRd32.exe]", cmdl.toString());
435 
436         // use an argument containing spaces
437         params.put("file", "C:\\Document And Settings\\documents\\432432.pdf");
438         cmdl = CommandLine.parse("AcroRd32.exe /p /h '${file}'", params);
439         assertEquals("[AcroRd32.exe, /p, /h, \"C:\\Document And Settings\\documents\\432432.pdf\"]", cmdl.toString());
440 
441         // use an argument without spaces
442         params.put("file", "C:\\documents\\432432.pdf");
443         cmdl = CommandLine.parse("AcroRd32.exe /p /h '${file}'", params);
444         assertEquals("[AcroRd32.exe, /p, /h, C:\\documents\\432432.pdf]", cmdl.toString());
445     }
446 
447     /**
448      * Test that toString() produces output that is useful for troubleshooting.
449      *
450      * @throws Exception the test failed
451      */
452     @Test
453     public void testToStringTroubleshooting() throws Exception {
454         System.out.println("testToStringTroubleshooting");
455         // On HP-UX quotes handling leads to errors,
456         // also usage of quotes isn't mandatory on other platforms too
457         // so it probably should work correctly either way.
458         final CommandLine cmd1 = new CommandLine("sh").addArgument("-c").addArgument("echo 1", false);
459         final CommandLine cmd2 = new CommandLine("sh").addArgument("-c").addArgument("echo").addArgument("1");
460         System.out.println("cmd1: " + cmd1.toString());
461         System.out.println("cmd2: " + cmd2.toString());
462         assertTrue(!cmd1.toString().equals(cmd2.toString()), "toString() is useful for troubleshooting");
463     }
464 
465 }