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