ffbb6888bfcff56414dc28a93412d956e76c0607
[idea/community.git] / platform / platform-tests / testSrc / com / intellij / execution / GeneralCommandLineTest.java
1 /*
2  * Copyright 2000-2015 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.execution;
17
18 import com.intellij.execution.configurations.GeneralCommandLine;
19 import com.intellij.execution.util.ExecUtil;
20 import com.intellij.openapi.application.PathManager;
21 import com.intellij.openapi.util.SystemInfo;
22 import com.intellij.openapi.util.io.FileUtil;
23 import com.intellij.openapi.util.text.StringUtil;
24 import com.intellij.util.ArrayUtil;
25 import org.jetbrains.annotations.NotNull;
26 import org.jetbrains.annotations.Nullable;
27 import org.junit.Test;
28
29 import java.io.File;
30 import java.io.IOException;
31 import java.io.InputStream;
32 import java.net.URISyntaxException;
33 import java.net.URL;
34 import java.util.*;
35
36 import static com.intellij.openapi.util.Pair.pair;
37 import static com.intellij.util.containers.ContainerUtil.newHashMap;
38 import static org.junit.Assert.*;
39 import static org.junit.Assume.assumeTrue;
40
41 public class GeneralCommandLineTest {
42
43   private static final String[] ARGUMENTS = {
44     "with space",
45     "\"quoted\"",
46     "\"quoted with spaces\"",
47     "",
48     "  ",
49     "param 1",
50     "\"",
51     "quote\"inside",
52     "space \"and \"quotes\" inside",
53     "\"space \"and \"quotes\" inside\"",
54     "param2",
55     "trailing slash\\",
56      // "two trailing slashes\\\\" /* doesn't work on Windows*/
57   };
58
59   @Test
60   public void printCommandLine() {
61     GeneralCommandLine commandLine = new GeneralCommandLine();
62     commandLine.setExePath("e x e path");
63     commandLine.addParameter("with space");
64     commandLine.addParameter("\"quoted\"");
65     commandLine.addParameter("\"quoted with spaces\"");
66     commandLine.addParameters("param 1", "param2");
67     commandLine.addParameter("trailing slash\\");
68     assertEquals("\"e x e path\"" +
69                  " \"with space\"" +
70                  " \\\"quoted\\\"" +
71                  " \"\\\"quoted with spaces\\\"\"" +
72                  " \"param 1\"" +
73                  " param2" +
74                  " \"trailing slash\\\"",
75                  commandLine.getCommandLineString());
76   }
77
78   @Test
79   public void unicodePath() throws Exception {
80     String mark = String.valueOf(new Random().nextInt());
81     File script;
82     if (SystemInfo.isWindows) {
83       script = ExecUtil.createTempExecutableScript(
84         "path with spaces 'and quotes' и юникодом ", ".cmd",
85         "@echo " + mark + "\n"
86       );
87     }
88     else {
89       script = ExecUtil.createTempExecutableScript(
90         "path with spaces 'and quotes' и юникодом ", ".sh",
91         "#!/bin/sh\n" + "echo " + mark + "\n"
92       );
93     }
94
95     try {
96       GeneralCommandLine commandLine = new GeneralCommandLine(script.getPath());
97       String output = execAndGetOutput(commandLine, null);
98       assertEquals(mark + "\n", StringUtil.convertLineSeparators(output));
99     }
100     finally {
101       FileUtil.delete(script);
102     }
103   }
104
105   @Test
106   public void unicodeClassPath() throws Exception {
107     assumeTrue(SystemInfo.isUnix);
108
109     File dir = FileUtil.createTempDirectory("path with spaces 'and quotes' и юникодом ", ".tmp");
110     try {
111       GeneralCommandLine commandLine = makeJavaCommand(ParamPassingTest.class, dir);
112       String output = execAndGetOutput(commandLine, null);
113       assertEquals("=====\n=====\n", StringUtil.convertLineSeparators(output));
114     }
115     finally {
116       FileUtil.delete(dir);
117     }
118   }
119
120   @Test
121   public void testPassingArgumentsToJavaApp() throws Exception {
122     GeneralCommandLine commandLine = makeJavaCommand(ParamPassingTest.class, null);
123     String[] args = ArrayUtil.mergeArrays(ARGUMENTS, "&<>()@^|", "\"&<>()@^|\"");
124     commandLine.addParameters(args);
125     String output = execAndGetOutput(commandLine, null);
126     assertParamPassingTestOutput(output, args);
127   }
128
129   @Test
130   public void testPassingArgumentsToJavaAppThroughWinShell() throws Exception {
131     assumeTrue(SystemInfo.isWindows);
132     // passing "^" argument doesn't work for cmd.exe
133     String[] args = ARGUMENTS;
134     GeneralCommandLine commandLine = makeJavaCommand(ParamPassingTest.class, null);
135     String oldExePath = commandLine.getExePath();
136     commandLine.setExePath("cmd.exe");
137     // the test will fails if "call" is omitted
138     commandLine.getParametersList().prependAll("/D", "/C", "call", oldExePath);
139     commandLine.addParameters(args);
140     String output = execAndGetOutput(commandLine, null);
141     assertParamPassingTestOutput(output, args);
142   }
143
144   @Test
145   public void testPassingArgumentsToJavaAppThroughCmdScriptAndWinShell() throws Exception {
146     assumeTrue(SystemInfo.isWindows);
147     // passing "^" argument doesn't work for cmd.exe
148     String[] args = ARGUMENTS;
149     File cmdScript = createCmdFileLaunchingJavaApp();
150     GeneralCommandLine commandLine = new GeneralCommandLine();
151     commandLine.setExePath("cmd.exe");
152     // the test will fails if "call" is omitted
153     commandLine.addParameters("/D", "/C", "call", cmdScript.getAbsolutePath());
154     commandLine.addParameters(args);
155     String output = execAndGetOutput(commandLine, null);
156     assertParamPassingTestOutput(output, args);
157   }
158
159   @NotNull
160   private File createCmdFileLaunchingJavaApp() throws Exception {
161     File cmdScript = FileUtil.createTempFile(new File(PathManager.getTempPath(), "My Program Files" /* path with spaces */),
162                                              "my-script", ".cmd", true, true);
163     GeneralCommandLine commandLine = makeJavaCommand(ParamPassingTest.class, null);
164     FileUtil.writeToFile(cmdScript, "@" + commandLine.getCommandLineString() + " %*");
165     if (!cmdScript.setExecutable(true, true)) {
166       throw new ExecutionException("Failed to make temp file executable: " + cmdScript);
167     }
168     return cmdScript;
169   }
170
171   private static void assertParamPassingTestOutput(@NotNull String actualOutput, @NotNull String... expectedOutputParameters) {
172     String content = StringUtil.join(expectedOutputParameters, "\n");
173     if (expectedOutputParameters.length > 0) {
174       content += "\n";
175     }
176     assertEquals(content, StringUtil.convertLineSeparators(actualOutput));
177   }
178
179   @Test
180   public void unicodeArguments() throws Exception {
181     assumeTrue("UTF-8".equals(System.getProperty("file.encoding")));
182
183     File script;
184     GeneralCommandLine commandLine;
185     String encoding = null;
186     if (SystemInfo.isWindows) {
187       script = ExecUtil.createTempExecutableScript(
188         "args.", ".js",
189         "WSH.Echo(\"=====\");\n" +
190         "for (i = 0; i < WSH.Arguments.length; i++) {\n" +
191         "  WSH.Echo(WSH.Arguments(i));\n" +
192         "}\n" +
193         "WSH.Echo(\"=====\");\n"
194       );
195       commandLine = new GeneralCommandLine("cscript", "//Nologo", "//U", script.getPath());
196       encoding = "UTF-16LE";
197     }
198     else {
199       script = ExecUtil.createTempExecutableScript(
200         "args.", ".sh",
201         "#!/bin/sh\n\n" +
202         "echo \"=====\"\n" +
203         "for f in \"$@\" ; do echo \"$f\"; done\n" +
204         "echo \"=====\"\n"
205       );
206       commandLine = new GeneralCommandLine(script.getPath());
207     }
208
209     try {
210       commandLine.addParameters("немного", "юникодных", "параметров");
211       String output = execAndGetOutput(commandLine, encoding);
212       assertEquals("=====\nнемного\nюникодных\nпараметров\n=====\n", StringUtil.convertLineSeparators(output));
213     }
214     finally {
215       FileUtil.delete(script);
216     }
217   }
218
219   @Test
220   public void winShellCommand() throws Exception {
221     assumeTrue(SystemInfo.isWindows);
222
223     String string = "http://localhost/wtf?a=b&c=d";
224     String echo = ExecUtil.execAndReadLine(new GeneralCommandLine(ExecUtil.getWindowsShellName(), "/c", "echo", string));
225     assertEquals('"' + string + '"', echo);
226   }
227
228   @Test
229   public void winShellScriptQuoting() throws Exception {
230     assumeTrue(SystemInfo.isWindows);
231     String scriptPrefix = "my_script";
232     for (String cmdScriptExt : new String[] {".cmd", ".bat"}) {
233       File script = ExecUtil.createTempExecutableScript(
234         scriptPrefix, cmdScriptExt,
235         "@echo %1\n"
236       );
237       String param = "a&b";
238       GeneralCommandLine commandLine = new GeneralCommandLine(script.getAbsolutePath(), param);
239       String text = commandLine.getPreparedCommandLine(Platform.WINDOWS);
240       assertEquals(commandLine.getExePath() + "\n" + StringUtil.wrapWithDoubleQuote(param), text);
241       try {
242         String output = execAndGetOutput(commandLine, null);
243         assertEquals(StringUtil.wrapWithDoubleQuote(param), output.trim());
244       }
245       finally {
246         FileUtil.delete(script);
247       }
248     }
249   }
250
251   @Test
252   public void winShellQuotingWithExtraSwitch() throws Exception {
253     assumeTrue(SystemInfo.isWindows);
254     String param = "a&b";
255     GeneralCommandLine commandLine = new GeneralCommandLine("cmd", "/D", "/C", "echo", param);
256     String output = execAndGetOutput(commandLine, null);
257     assertEquals(StringUtil.wrapWithDoubleQuote(param), output.trim());
258   }
259
260   @Test
261   public void hackyEnvMap () throws Exception {
262     Map<String, String> env = new GeneralCommandLine().getEnvironment();
263
264     //noinspection ConstantConditions
265     env.putAll(null);
266
267     try {
268       env.put("key1", null);
269       fail("null values should be rejected");
270     }
271     catch (AssertionError ignored) { }
272
273     try {
274       Map<String, String> indirect = newHashMap(pair("key2", (String)null));
275       env.putAll(indirect);
276       fail("null values should be rejected");
277     }
278     catch (AssertionError ignored) { }
279   }
280
281   @Test
282   public void environmentPassing() throws Exception {
283     Map<String, String> testEnv = new HashMap<String, String>();
284     testEnv.put("VALUE_1", "some value");
285     testEnv.put("VALUE_2", "another\n\"value\"");
286
287     GeneralCommandLine commandLine = makeJavaCommand(EnvPassingTest.class, null);
288     checkEnvPassing(commandLine, testEnv, true);
289     checkEnvPassing(commandLine, testEnv, false);
290   }
291
292   @Test
293   public void unicodeEnvironment() throws Exception {
294     assumeTrue("UTF-8".equals(System.getProperty("file.encoding")));
295
296     Map<String, String> testEnv = new HashMap<String, String>();
297     testEnv.put("VALUE_1", "немного");
298     testEnv.put("VALUE_2", "юникода");
299
300     GeneralCommandLine commandLine = makeJavaCommand(EnvPassingTest.class, null);
301     checkEnvPassing(commandLine, testEnv, true);
302     checkEnvPassing(commandLine, testEnv, false);
303   }
304
305   @Test
306   public void emptyEnvironmentPassing() throws Exception {
307     Map<String, String> env = newHashMap(pair("a", "b"), pair("", "c"));
308     Map<String, String> expected = newHashMap(pair("a", "b"));
309     GeneralCommandLine commandLine = makeJavaCommand(EnvPassingTest.class, null);
310     checkEnvPassing(commandLine, env, expected, false);
311   }
312
313   private static String execAndGetOutput(GeneralCommandLine commandLine, @Nullable String encoding) throws Exception {
314     Process process = commandLine.createProcess();
315     String stdOut = loadTextFromStream(process.getInputStream(), encoding);
316     String stdErr = loadTextFromStream(process.getErrorStream(), encoding);
317     int result = process.waitFor();
318     assertEquals("Command:\n" + commandLine.getCommandLineString()
319                  + "\nStandard output:\n" + stdOut
320                  + "\nStandard error:\n" + stdErr,
321                  0, result);
322     return stdOut;
323   }
324
325   private static String loadTextFromStream(@NotNull InputStream stream, @Nullable String encoding) throws IOException {
326     byte[] bytes = FileUtil.loadBytes(stream);
327     return encoding != null ? new String(bytes, encoding) : new String(bytes);
328   }
329
330   private GeneralCommandLine makeJavaCommand(Class<?> testClass, @Nullable File copyTo) throws IOException, URISyntaxException {
331     String className = testClass.getName();
332     URL url = getClass().getClassLoader().getResource(className.replace(".", "/") + ".class");
333     assertNotNull(url);
334
335     GeneralCommandLine commandLine = new GeneralCommandLine();
336     commandLine.setExePath(System.getProperty("java.home") + (SystemInfo.isWindows ? "\\bin\\java.exe" : "/bin/java"));
337
338     String encoding = System.getProperty("file.encoding");
339     if (encoding != null) {
340       commandLine.addParameter("-D" + "file.encoding=" + encoding);
341     }
342
343     commandLine.addParameter("-cp");
344     String[] packages = className.split("\\.");
345     File classFile = new File(url.toURI());
346     if (copyTo == null) {
347       File dir = classFile;
348       for (String ignored : packages) dir = dir.getParentFile();
349       commandLine.addParameter(dir.getPath());
350     }
351     else {
352       File dir = copyTo;
353       for (int i = 0; i < packages.length - 1; i++) dir = new File(dir, packages[i]);
354       FileUtil.copy(classFile, new File(dir, classFile.getName()));
355       commandLine.addParameter(copyTo.getPath());
356     }
357
358     commandLine.addParameter(className);
359     commandLine.setRedirectErrorStream(true);
360
361     return commandLine;
362   }
363
364   private static void checkEnvPassing(GeneralCommandLine commandLine, Map<String, String> testEnv, boolean passParentEnv) throws Exception {
365     checkEnvPassing(commandLine, testEnv, testEnv, passParentEnv);
366   }
367
368   private static void checkEnvPassing(GeneralCommandLine commandLine,
369                                       Map<String, String> testEnv,
370                                       Map<String, String> expectedOutputEnv,
371                                       boolean passParentEnv) throws Exception {
372     commandLine.getEnvironment().putAll(testEnv);
373     commandLine.setPassParentEnvironment(passParentEnv);
374     String output = execAndGetOutput(commandLine, null);
375
376     Set<String> lines = new HashSet<String>(Arrays.asList(StringUtil.convertLineSeparators(output).split("\n")));
377     lines.remove("=====");
378
379     for (Map.Entry<String, String> entry : expectedOutputEnv.entrySet()) {
380       String str = EnvPassingTest.format(entry);
381       assertTrue("\"" + str + "\" should be in " + lines,
382                  lines.contains(str));
383     }
384
385     Map<String, String> parentEnv = System.getenv();
386     List<String> missed = new ArrayList<String>();
387     for (Map.Entry<String, String> entry : parentEnv.entrySet()) {
388       String str = EnvPassingTest.format(entry);
389       if (!lines.contains(str)) {
390         missed.add(str);
391       }
392     }
393
394     long pctMissed = Math.round((100.0 * missed.size()) / parentEnv.size());
395     if (passParentEnv && pctMissed > 25 || !passParentEnv && pctMissed < 75) {
396       fail("% missed: " + pctMissed + ", missed: " + missed + ", passed: " + lines);
397     }
398   }
399 }