diff --git a/images/src/org/intellij/images/actions/EditExternallyAction.java b/images/src/org/intellij/images/actions/EditExternallyAction.java index 2273bb65830a..57bf0d018ad2 100644 --- a/images/src/org/intellij/images/actions/EditExternallyAction.java +++ b/images/src/org/intellij/images/actions/EditExternallyAction.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2011 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -87,7 +87,7 @@ public final class EditExternallyAction extends AnAction { commandLine.addParameter(VfsUtil.virtualToIoFile(file).getAbsolutePath()); } } - commandLine.setWorkingDirectory(new File(executablePath).getParentFile()); + commandLine.setWorkDirectory(new File(executablePath).getParentFile()); try { commandLine.createProcess(); diff --git a/java/java-impl/src/com/intellij/javadoc/JavadocConfiguration.java b/java/java-impl/src/com/intellij/javadoc/JavadocConfiguration.java index 59a0d7b4632e..4913ac98998d 100644 --- a/java/java-impl/src/com/intellij/javadoc/JavadocConfiguration.java +++ b/java/java-impl/src/com/intellij/javadoc/JavadocConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2011 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -164,7 +164,7 @@ public class JavadocConfiguration implements ModuleRunProfile, JDOMExternalizabl cmdLine.getParametersList().prepend("-J-mx" + HEAP_SIZE + "m"); } } - cmdLine.setWorkingDirectory(null); + cmdLine.setWorkDirectory((File)null); @NonNls final String javadocExecutableName = File.separator + (SystemInfo.isWindows ? "javadoc.exe" : "javadoc"); @NonNls String exePath = jdkPath.replace('/', File.separatorChar) + javadocExecutableName; if (new File(exePath).exists()) { @@ -250,10 +250,9 @@ public class JavadocConfiguration implements ModuleRunProfile, JDOMExternalizabl } try { - File sourcepathTempFile = FileUtil.createTempFile("javadoc", "args.txt"); - sourcepathTempFile.deleteOnExit(); - parameters.add("@" + sourcepathTempFile.getCanonicalPath()); - final PrintWriter writer = new PrintWriter(new FileWriter(sourcepathTempFile)); + final File sourcePathTempFile = FileUtil.createTempFile("javadoc", "args.txt", true); + parameters.add("@" + sourcePathTempFile.getCanonicalPath()); + final PrintWriter writer = new PrintWriter(new FileWriter(sourcePathTempFile)); try { final Collection packages = new HashSet(); final Collection sources = new HashSet(); @@ -290,7 +289,7 @@ public class JavadocConfiguration implements ModuleRunProfile, JDOMExternalizabl } sourcePath.append(file.getPath()); } - writer.println(GeneralCommandLine.quote(sourcePath.toString())); + writer.println(sourcePath.toString()); } finally { writer.close(); @@ -340,7 +339,7 @@ public class JavadocConfiguration implements ModuleRunProfile, JDOMExternalizabl final PsiJavaFile javaFile = (PsiJavaFile)file; final String packageName = javaFile.getPackageName(); if (containsPackagePrefix(module, packageName) || (packageName.length() == 0 && !(javaFile instanceof JspFile))) { - mySourceFiles.add(GeneralCommandLine.quote(FileUtil.toSystemIndependentName(fileOrDir.getPath()))); + mySourceFiles.add(FileUtil.toSystemIndependentName(fileOrDir.getPath())); } else { myPackages.add(packageName); diff --git a/java/java-tests/java-tests.iml b/java/java-tests/java-tests.iml index a2196312e368..e2708d77ad84 100644 --- a/java/java-tests/java-tests.iml +++ b/java/java-tests/java-tests.iml @@ -25,6 +25,7 @@ + diff --git a/java/java-tests/testSrc/com/intellij/execution/configurations/JavaCommandLineTest.java b/java/java-tests/testSrc/com/intellij/execution/configurations/JavaCommandLineTest.java new file mode 100644 index 000000000000..2708ac91181c --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/execution/configurations/JavaCommandLineTest.java @@ -0,0 +1,100 @@ +/* + * Copyright 2000-2011 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.execution.configurations; + +import com.intellij.execution.CantRunException; +import com.intellij.execution.ExecutionBundle; +import com.intellij.execution.ExecutionException; +import com.intellij.execution.process.DefaultJavaProcessHandler; +import com.intellij.ide.IdeBundle; +import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.openapi.projectRoots.impl.JavaAwareProjectJdkTableImpl; +import com.intellij.testFramework.LightIdeaTestCase; +import junit.framework.Assert; + +public class JavaCommandLineTest extends LightIdeaTestCase { + public void testJdk() { + try { + CommandLineBuilder.createFromJavaParameters(new JavaParameters()); + fail("CantRunException (main class is not specified) expected"); + } + catch (CantRunException e) { + Assert.assertEquals(ExecutionBundle.message("run.configuration.error.no.jdk.specified"), e.getMessage()); + } + } + + public void testMainClass() { + try { + JavaParameters javaParameters = new JavaParameters(); + javaParameters.setJdk(getProjectJDK()); + CommandLineBuilder.createFromJavaParameters(javaParameters); + fail("CantRunException (main class is not specified) expected"); + } + catch (CantRunException e) { + assertEquals(ExecutionBundle.message("main.class.is.not.specified.error.message"), e.getMessage()); + } + } + + public void testClasspath() throws CantRunException { + JavaParameters javaParameters; + String commandLineString; + + javaParameters = new JavaParameters(); + final Sdk internalJdk = JavaAwareProjectJdkTableImpl.getInstanceEx().getInternalJdk(); + javaParameters.setJdk(internalJdk); + javaParameters.setMainClass("Main"); + commandLineString = CommandLineBuilder.createFromJavaParameters(javaParameters).getCommandLineString(); + assertTrue(containsClassPath(commandLineString)); + + javaParameters = new JavaParameters(); + javaParameters.setJdk(internalJdk); + javaParameters.setMainClass("Main"); + javaParameters.getVMParametersList().add("-cp"); + javaParameters.getVMParametersList().add(".."); + commandLineString = CommandLineBuilder.createFromJavaParameters(javaParameters).getCommandLineString(); + commandLineString = removeClassPath(commandLineString, "-cp .."); + assertTrue(!containsClassPath(commandLineString)); + + javaParameters = new JavaParameters(); + javaParameters.setJdk(internalJdk); + javaParameters.setMainClass("Main"); + javaParameters.getVMParametersList().add("-classpath"); + javaParameters.getVMParametersList().add(".."); + commandLineString = CommandLineBuilder.createFromJavaParameters(javaParameters).getCommandLineString(); + commandLineString = removeClassPath(commandLineString, "-classpath .."); + assertTrue(!containsClassPath(commandLineString)); + } + + private static boolean containsClassPath(String commandLineString) { + return commandLineString.contains("-cp") || commandLineString.contains("-classpath"); + } + + private static String removeClassPath(String commandLineString, String pathString) { + int i = commandLineString.indexOf(pathString); + commandLineString = commandLineString.substring(0, i) + commandLineString.substring(i + pathString.length()); + return commandLineString; + } + + public void testCreateProcess() { + try { + new DefaultJavaProcessHandler(new GeneralCommandLine()); + fail("ExecutionException (executable is not specified) expected"); + } + catch (ExecutionException e) { + assertEquals(IdeBundle.message("run.configuration.error.executable.not.specified"), e.getMessage()); + } + } +} diff --git a/java/java-tests/testSrc/com/intellij/execution/configurations/JavaParametersTest.java b/java/java-tests/testSrc/com/intellij/execution/configurations/JavaParametersTest.java new file mode 100644 index 000000000000..9d1007f1bc1f --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/execution/configurations/JavaParametersTest.java @@ -0,0 +1,109 @@ +/* + * Copyright 2000-2011 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.execution.configurations; + +import com.intellij.execution.CantRunException; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.DependencyScope; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.roots.ModuleRootManagerTestCase; + +/** + * @author nik + */ +public class JavaParametersTest extends ModuleRootManagerTestCase { + public void testLibrary() throws Exception { + addLibraryDependency(myModule, createJDomLibrary()); + assertClasspath(myModule, JavaParameters.JDK_AND_CLASSES_AND_TESTS, + getRtJar(), getJDomJar()); + assertClasspath(myModule, JavaParameters.CLASSES_ONLY, + getJDomJar()); + assertClasspath(myModule, JavaParameters.CLASSES_AND_TESTS, + getJDomJar()); + assertClasspath(myProject, JavaParameters.JDK_AND_CLASSES_AND_TESTS, + getRtJar(), getJDomJar()); + } + + public void testModuleSourcesAndOutput() throws Exception { + addSourceRoot(myModule, false); + addSourceRoot(myModule, true); + final VirtualFile output = setModuleOutput(myModule, false); + final VirtualFile testOutput = setModuleOutput(myModule, true); + + assertClasspath(myModule, JavaParameters.CLASSES_ONLY, + output); + assertClasspath(myModule, JavaParameters.CLASSES_AND_TESTS, + testOutput, output); + assertClasspath(myModule, JavaParameters.JDK_AND_CLASSES_AND_TESTS, + getRtJar(), testOutput, output); + } + + public void testLibraryScope() throws Exception { + addLibraryDependency(myModule, createJDomLibrary(), DependencyScope.RUNTIME, false); + addLibraryDependency(myModule, createAsmLibrary(), DependencyScope.TEST, false); + + assertClasspath(myModule, JavaParameters.CLASSES_AND_TESTS, + getJDomJar(), getAsmJar()); + assertClasspath(myModule, JavaParameters.CLASSES_ONLY, + getJDomJar()); + } + + public void testProvidedScope() throws Exception { + addLibraryDependency(myModule, createJDomLibrary(), DependencyScope.PROVIDED, false); + + assertClasspath(myModule, JavaParameters.CLASSES_AND_TESTS, getJDomJar()); + assertClasspath(myModule, JavaParameters.CLASSES_ONLY); + } + + public void testModuleDependency() throws Exception { + final Module dep = createModule("dep"); + final VirtualFile depOutput = setModuleOutput(dep, false); + final VirtualFile depTestOutput = setModuleOutput(dep, true); + addLibraryDependency(dep, createJDomLibrary()); + addModuleDependency(myModule, dep); + + assertClasspath(myModule, JavaParameters.CLASSES_ONLY, + depOutput, getJDomJar()); + assertClasspath(myModule, JavaParameters.CLASSES_AND_TESTS, + depTestOutput, depOutput, getJDomJar()); + } + + public void testModuleDependencyScope() throws Exception { + final Module dep = createModule("dep"); + addLibraryDependency(dep, createJDomLibrary()); + addModuleDependency(myModule, dep, DependencyScope.TEST, true); + + assertClasspath(myModule, JavaParameters.CLASSES_ONLY); + assertClasspath(myModule, JavaParameters.CLASSES_AND_TESTS, + getJDomJar()); + + assertClasspath(myProject, JavaParameters.CLASSES_ONLY, + getJDomJar()); + } + + private static void assertClasspath(final Module module, final int type, VirtualFile... roots) throws CantRunException { + final JavaParameters javaParameters = new JavaParameters(); + javaParameters.configureByModule(module, type); + assertRoots(javaParameters.getClassPath(), roots); + } + + private void assertClasspath(final Project project, final int type, VirtualFile... roots) throws CantRunException { + final JavaParameters javaParameters = new JavaParameters(); + javaParameters.configureByProject(project, type, getTestProjectJdk()); + assertRoots(javaParameters.getClassPath(), roots); + } +} diff --git a/platform/lang-impl/src/com/intellij/codeInspection/AbstractInspectionToolStarter.java b/platform/lang-impl/src/com/intellij/codeInspection/AbstractInspectionToolStarter.java index 7a522b306b7e..9e3bf6e4c513 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/AbstractInspectionToolStarter.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/AbstractInspectionToolStarter.java @@ -1,13 +1,30 @@ +/* + * Copyright 2000-2011 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.intellij.codeInspection; -import com.intellij.execution.configurations.GeneralCommandLine; +import com.intellij.execution.configurations.ParametersList; import com.intellij.openapi.application.ApplicationStarter; +import com.intellij.util.containers.CollectionFactory; import com.sampullara.cli.Args; import org.jetbrains.annotations.NotNull; /** * @author Roman.Chernyatchik */ +@SuppressWarnings("UseOfSystemOutOrSystemErr") public abstract class AbstractInspectionToolStarter implements ApplicationStarter { protected InspectionApplication myApplication; protected InspectionToolCmdlineOptions myOptions; @@ -19,7 +36,8 @@ public abstract class AbstractInspectionToolStarter implements ApplicationStarte myOptions = createCmdlineOptions(); try { Args.parse(myOptions, args); - } catch (Exception e) { + } + catch (Exception e) { printHelpAndExit(args, myOptions); return; } @@ -32,17 +50,18 @@ public abstract class AbstractInspectionToolStarter implements ApplicationStarte } // TODO[romeo] : if config given - parse config and set attrs - //Properties p = new Properties(); - // p.put("input", "inputfile"); - // p.put("o", "outputfile"); - // p.put("someflag", "true"); - // p.put("m", "10"); - // p.put("values", "1:2:3"); - // p.put("strings", "sam;dave;jolly"); - // PropertiesArgs.parse(tc, p); - try{ + //Properties p = new Properties(); + // p.put("input", "inputfile"); + // p.put("o", "outputfile"); + // p.put("someflag", "true"); + // p.put("m", "10"); + // p.put("values", "1:2:3"); + // p.put("strings", "sam;dave;jolly"); + // PropertiesArgs.parse(tc, p); + try { myOptions.validate(); - } catch (InspectionToolCmdlineOptions.CmdlineArgsValidationException e) { + } + catch (InspectionToolCmdlineOptions.CmdlineArgsValidationException e) { System.err.println(e.getMessage()); if (!myOptions.suppressHelp()) { printHelpAndExit(args, myOptions); @@ -66,23 +85,22 @@ public abstract class AbstractInspectionToolStarter implements ApplicationStarte myApplication.startup(); } - private void initApplication(@NotNull final InspectionApplication application, - @NotNull final InspectionToolCmdlineOptions opts) { + private static void initApplication(@NotNull final InspectionApplication application, + @NotNull final InspectionToolCmdlineOptions opts) { opts.initApplication(application); } - private boolean verbose(final InspectionToolCmdlineOptions opts) { + private static boolean verbose(final InspectionToolCmdlineOptions opts) { return opts.getVerboseLevelProperty() > 0; } protected void printArgs(String[] args, StringBuilder buff) { if (args.length < 2) { buff.append(" no arguments"); - } else { - for (int i = 1, argsLength = args.length; i < argsLength; i++) { - String arg = args[i]; - buff.append(' ').append(GeneralCommandLine.quote(arg)); - } + } + else { + final String argString = ParametersList.join(CollectionFactory.arrayList(args, 1, args.length)); + buff.append(argString); } } diff --git a/platform/platform-api/src/com/intellij/execution/configurations/GeneralCommandLine.java b/platform/platform-api/src/com/intellij/execution/configurations/GeneralCommandLine.java index 1672e82b9946..bdcbdbf5964e 100644 --- a/platform/platform-api/src/com/intellij/execution/configurations/GeneralCommandLine.java +++ b/platform/platform-api/src/com/intellij/execution/configurations/GeneralCommandLine.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2011 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,12 +18,10 @@ package com.intellij.execution.configurations; import com.intellij.execution.ExecutionException; import com.intellij.execution.process.ProcessNotCreatedException; import com.intellij.ide.IdeBundle; -import com.intellij.openapi.diagnostic.Log; -import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.CharsetToolkit; -import com.intellij.util.containers.HashMap; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -31,45 +29,65 @@ import org.jetbrains.annotations.Nullable; import java.io.File; import java.io.IOException; import java.nio.charset.Charset; -import java.util.Iterator; +import java.util.ArrayList; import java.util.List; import java.util.Map; +/** + * OS-independent way of executing external processes with complex parameters. + * + * Main idea of the class is to accept parameters "as-is", just as they should look to an external process, and quote/escape them + * as required by the underlying platform. + * + * todo: check help + */ public class GeneralCommandLine { - private static final Logger LOG = Logger.getInstance("#" + GeneralCommandLine.class.getName()); - private Map myEnvParams; - private boolean myPassParentEnvs; private String myExePath = null; private File myWorkDirectory = null; - private ParametersList myProgramParams = new ParametersList(); + private Map myEnvParams = null; + private boolean myPassParentEnvironment = true; + private final ParametersList myProgramParams = new ParametersList(); private Charset myCharset = CharsetToolkit.getDefaultSystemCharset(); - - public void setExePath(@NonNls final String exePath) { - myExePath = exePath.trim(); - } + private boolean myRedirectErrorStream = false; public String getExePath() { return myExePath; } - public void setWorkDirectory(@NonNls final String path) { - setWorkingDirectory(path != null? new File(path) : null); - } - - public void setWorkingDirectory(final File workingDirectory) { - myWorkDirectory = workingDirectory; + public void setExePath(@NotNull @NonNls final String exePath) { + myExePath = exePath.trim(); } public File getWorkDirectory() { return myWorkDirectory; } - public void setEnvParams(final Map envParams) { + public void setWorkDirectory(@Nullable @NonNls final String path) { + setWorkDirectory(path != null ? new File(path) : null); + } + + public void setWorkDirectory(@Nullable final File workDirectory) { + myWorkDirectory = workDirectory; + } + + /** + * @deprecated use {@link #setWorkDirectory(java.io.File)} (to remove in IDEA 12). + */ + public void setWorkingDirectory(@Nullable final File workDirectory) { + setWorkDirectory(workDirectory); + } + + @Nullable + public Map getEnvParams() { + return myEnvParams; + } + + public void setEnvParams(@Nullable final Map envParams) { myEnvParams = envParams; } - public void setCharset(@NotNull Charset charset) { - myCharset = charset; + public void setPassParentEnvs(final boolean passParentEnvironment) { + myPassParentEnvironment = passParentEnvironment; } public void addParameters(final String... parameters) { @@ -78,7 +96,7 @@ public class GeneralCommandLine { } } - public void addParameters(final List parameters) { + public void addParameters(@NotNull final List parameters) { for (final String parameter : parameters) { addParameter(parameter); } @@ -88,37 +106,105 @@ public class GeneralCommandLine { myProgramParams.add(parameter); } - public String getCommandLineString() { - final StringBuffer buffer = new StringBuffer(quoteParameter(FileUtil.toSystemDependentName(myExePath))); - appendParams( buffer ); - return buffer.toString(); - } - - public String getCommandLineParams() { - final StringBuffer buffer = new StringBuffer(); - appendParams( buffer ); - return buffer.toString(); - } - - private void appendParams( StringBuffer buffer ) { - for( final String param : myProgramParams.getList() ) { - buffer.append(" ").append(quoteParameter(param)); - } + public ParametersList getParametersList() { + return myProgramParams; } + @NotNull public Charset getCharset() { return myCharset; } + public void setCharset(@NotNull final Charset charset) { + myCharset = charset; + } + + public void setRedirectErrorStream(final boolean redirectErrorStream) { + myRedirectErrorStream = redirectErrorStream; + } + + /** + * @deprecated please use {@link #getCommandLineString()} (to remove in IDEA 12). + */ + @SuppressWarnings("UnusedDeclaration") + public String getCommandLineParams() { + return getCommandLineString(); + } + + /** + * Returns string representation of this command line.
+ * Warning: resulting string is not OS-dependent - do not use it for executing this command line. + * + * @return single-string representation of this command line. + */ + public String getCommandLineString() { + return getCommandLineString(null); + } + + /** + * Returns string representation of this command line.
+ * Warning: resulting string is not OS-dependent - do not use it for executing this command line. + * + * @param exeName use this executable name instead of given by {@link #setExePath(String)} + * @return single-string representation of this command line. + */ + public String getCommandLineString(@Nullable final String exeName) { + final List commands = new ArrayList(); + if (exeName != null) { + commands.add(exeName); + } + else if (myExePath != null) { + commands.add(FileUtil.toSystemDependentName(myExePath)); + } + else { + commands.add(""); + } + commands.addAll(myProgramParams.getList()); + return ParametersList.join(commands); + } + + /** + * Returns a list of command and its parameters prepared in OS-dependent way to be executed by e.g. {@link Runtime#exec(String[])}. + * + * @deprecated this method is not intended for internal use (to remove in IDEA 12). + */ + public String[] getCommands() { + return prepareCommands(); + } + + /** + * @deprecated use {@link #addParameter(String)} and {@link #addParameters(String...)} methods for adding parameters - + * any quoting needed will be done on {@link #createProcess()} (to remove in IDEA 12). + */ + @SuppressWarnings("UnusedDeclaration") + public static String quoteParameter(final String parameter) { + return parameter; + } + + /** + * @deprecated use {@link #addParameter(String)} and {@link #addParameters(String...)} methods for adding parameters - + * any quoting needed will be done on {@link #createProcess()} (to remove in IDEA 12). + */ + @SuppressWarnings("UnusedDeclaration") + public static String quote(final String parameter) { + return parameter; + } + public Process createProcess() throws ExecutionException { checkWorkingDirectory(); - try { - final String[] commands = getCommands(); - if(commands[0] == null) throw new ExecutionException(IdeBundle.message("run.configuration.error.executable.not.specified")); - return myWorkDirectory != null - ? Runtime.getRuntime().exec(commands, getEnvParamsArray(), myWorkDirectory) - : Runtime.getRuntime().exec(commands, getEnvParamsArray()); + final String[] commands = prepareCommands(); + if (StringUtil.isEmptyOrSpaces(commands[0])) { + throw new ExecutionException(IdeBundle.message("run.configuration.error.executable.not.specified")); + } + + try { + final ProcessBuilder builder = new ProcessBuilder(commands); + final Map environment = builder.environment(); + setupEnvironment(environment); + builder.directory(myWorkDirectory); + builder.redirectErrorStream(myRedirectErrorStream); + return builder.start(); } catch (IOException e) { throw new ProcessNotCreatedException(e.getMessage(), e, this); @@ -138,98 +224,33 @@ public class GeneralCommandLine { } } - @Nullable - public Map getEnvParams() { - return myEnvParams; - } - - @Nullable - private String[] getEnvParamsArray() { - final Map envParams = collectEnvParams(); - if (envParams == null) return null; - for (Iterator iterator = envParams.keySet().iterator(); iterator.hasNext(); ) { - final String key = iterator.next(); - if (envParams.get(key) == null) { - LOG.info("null value for env variable: " + key); - iterator.remove(); - } - } - final String[] result = new String[envParams.size()]; - int i=0; - for (final String key : envParams.keySet()) { - result[i++] = key + "=" + envParams.get(key).trim(); - } - return result; - } - - @Nullable - private Map collectEnvParams() { - if (myEnvParams == null) { - return null; - } - final Map envParams = new HashMap(); - if (myPassParentEnvs) { - envParams.putAll(System.getenv()); - } - envParams.putAll(myEnvParams); - return envParams; - } - - public String[] getCommands() { + private String[] prepareCommands() { final List parameters = myProgramParams.getList(); final String[] result = new String[parameters.size() + 1]; - result[0] = myExePath; - int index = 1; - for (Iterator iterator = parameters.iterator(); iterator.hasNext(); index++) { - result[index] = iterator.next(); + result[0] = prepareCommand(myExePath != null ? FileUtil.toSystemDependentName(myExePath) : null); + for (int i = 0; i < parameters.size(); i++) { + result[i + 1] = prepareCommand(parameters.get(i)); } return result; } - public ParametersList getParametersList() { - return myProgramParams; - } - - public static String quoteParameter(final String param) { - if (!SystemInfo.isWindows) { - return param; - } - return quote(param); - } - - public static String quote(final String parameter) { - if (parameter == null || !hasWhitespace(parameter)) { - return parameter; // no need to quote - } - if (parameter.length() >= 2 && parameter.startsWith("\"") && parameter.endsWith("\"")) { - return parameter; // already quoted - } - // need to escape trailing slash if any, otherwise it will escape the ending quote - return "\"" + parameter + (parameter.endsWith("\\")? "\\\"" : "\""); - } - - private static boolean hasWhitespace(final String string) { - final int length = string.length(); - for (int i = 0; i < length; i++) { - if (Character.isWhitespace(string.charAt(i))) { - return true; + private static String prepareCommand(String parameter) { + // AFAIK, the only thing needed is escaping double quotes on Windows + if (SystemInfo.isWindows) { + if (parameter.contains("\"")) { + parameter = StringUtil.replace(parameter, "\"", "\\\""); } } - return false; + return parameter; } - public GeneralCommandLine clone() { - final GeneralCommandLine clone = new GeneralCommandLine(); - clone.myCharset = myCharset; - clone.myExePath = myExePath; - clone.myWorkDirectory = myWorkDirectory; - clone.myProgramParams = myProgramParams.clone(); - clone.myEnvParams = myEnvParams != null ? new HashMap(myEnvParams) : null; - return clone; - } - - public void setPassParentEnvs(final boolean passParentEnvs) { - myPassParentEnvs = passParentEnvs; + private void setupEnvironment(final Map environment) { + if (!myPassParentEnvironment) { + environment.clear(); + } + if (myEnvParams != null) { + environment.putAll(myEnvParams); + } } @Override diff --git a/platform/platform-api/src/com/intellij/execution/configurations/ParametersList.java b/platform/platform-api/src/com/intellij/execution/configurations/ParametersList.java index 088c0af8ef7c..6735bbbc86a4 100644 --- a/platform/platform-api/src/com/intellij/execution/configurations/ParametersList.java +++ b/platform/platform-api/src/com/intellij/execution/configurations/ParametersList.java @@ -22,6 +22,8 @@ import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.ArrayUtil; import com.intellij.util.EnvironmentUtil; +import com.intellij.util.StringBuilderSpinAllocator; +import com.intellij.util.containers.CollectionFactory; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; @@ -29,8 +31,9 @@ import org.jetbrains.annotations.Nullable; import java.util.*; -public class ParametersList implements Cloneable{ +public class ParametersList implements Cloneable { private static final Logger LOG = Logger.getInstance("#com.intellij.execution.configurations.ParametersList"); + private List myParameters = new ArrayList(); private Map myMacroMap = null; private List myGroups = new ArrayList(); @@ -47,9 +50,9 @@ public class ParametersList implements Cloneable{ } @Nullable - public String getPropertyValue(@NonNls final String name) { + public String getPropertyValue(@NotNull @NonNls final String name) { + final String prefix = "-D" + name + "="; for (String parameter : myParameters) { - @NonNls String prefix = "-D" + name + "="; if (parameter.startsWith(prefix)) { return parameter.substring(prefix.length()); } @@ -57,29 +60,39 @@ public class ParametersList implements Cloneable{ return null; } + @NotNull public String getParametersString() { - final StringBuilder buffer = new StringBuilder(); - final String separator = " "; - for (final String param : myParameters) { - buffer.append(separator); - buffer.append(GeneralCommandLine.quote(param)); - } - for (ParamsGroup paramsGroup : myGroups) { - // params group parameters string already contains a separator - buffer.append(paramsGroup.getParametersList().getParametersString()); - } - return buffer.toString(); + return join(getList()); } + @NotNull public String[] getArray() { return ArrayUtil.toStringArray(getList()); } + @NotNull + public List getList() { + if (myGroups.isEmpty()) { + return Collections.unmodifiableList(myParameters); + } + + final List params = new ArrayList(); + params.addAll(myParameters); + for (ParamsGroup group : myGroups) { + params.addAll(group.getParameters()); + } + return Collections.unmodifiableList(params); + } + + public void prepend(@NonNls final String parameter) { + addAt(0, parameter); + } + public void addParametersString(final String parameters) { if (parameters != null) { - final String[] parms = parse(parameters); - for (String parm : parms) { - add(parm); + final String[] split = parse(parameters); + for (String param : split) { + add(param); } } } @@ -97,14 +110,12 @@ public class ParametersList implements Cloneable{ return group; } - public ParamsGroup addParamsGroupAt(final int index, - @NotNull final ParamsGroup group) { + public ParamsGroup addParamsGroupAt(final int index, @NotNull final ParamsGroup group) { myGroups.add(index, group); return group; } - public ParamsGroup addParamsGroupAt(final int index, - @NotNull final String groupId) { + public ParamsGroup addParamsGroupAt(final int index, @NotNull final String groupId) { final ParamsGroup group = new ParamsGroup(groupId); myGroups.add(index, group); return group; @@ -173,33 +184,12 @@ public class ParametersList implements Cloneable{ replaceOrAdd(parameter, replacement, 0); } - public List getList() { - if (myGroups.isEmpty()) { - return Collections.unmodifiableList(myParameters); - } - - final List params = new ArrayList(); - - // params - params.addAll(myParameters); - - // recursively add groups - for (ParamsGroup group : myGroups) { - params.addAll(group.getParameters()); - } - return Collections.unmodifiableList(params); - } - - public void prepend(@NonNls final String parameter) { - addAt(0, parameter); - } - - public void add(@NonNls final String name,@NonNls final String value) { + public void add(@NonNls final String name, @NonNls final String value) { add(name); add(value); } - public void addAll(final String[] parameters) { + public void addAll(final String... parameters) { ContainerUtil.addAll(myParameters, parameters); } @@ -207,6 +197,7 @@ public class ParametersList implements Cloneable{ myParameters.addAll(parameters); } + @Override public ParametersList clone() { try { final ParametersList clone = (ParametersList)super.clone(); @@ -223,10 +214,66 @@ public class ParametersList implements Cloneable{ } } - public static String[] parse(final String string){ - return new ParametersTokenizer(string).execute(); + /** + *

Joins list of parameters into single string, which may be then parsed back into list by {@link #parse(String)}.

+ * + *

+ * Conversion rules: + *

    + *
  • double quotes are escaped by backslash (\);
  • + *
  • empty parameters parameters and parameters with spaces inside are surrounded with double quotes (");
  • + *
  • parameters are separated by single whitespace.
  • + *
+ *

+ * + *

Examples:

+ *

+ * ['a', 'b'] => 'a b'
+ * ['a="1 2"', 'b'] => '"a \"1 2\"" b' + *

+ * + * @param parameters a list of parameters to join. + * @return a string with parameters. + */ + @NotNull + public static String join(@NotNull final List parameters) { + return ParametersTokenizer.encode(parameters); } + @NotNull + public static String join(final String... parameters) { + return ParametersTokenizer.encode(Arrays.asList(parameters)); + } + + /** + *

Converts single parameter string (as created by {@link #join(java.util.List)}) into list of parameters.

+ * + *

+ * Conversion rules: + *

    + *
  • starting/whitespaces are trimmed;
  • + *
  • parameters are split by whitespaces, whitespaces itself are dropped
  • + *
  • parameters inside double quotes ("a b") are kept as single one;
  • + *
  • double quotes are dropped, escaped double quotes (\") are un-escaped.
  • + *
+ *

+ * + *

Examples:

+ *

+ * ' a b ' => ['a', 'b']
+ * 'a="1 2" b' => ['a=1 2', 'b']
+ * 'a " " b' => ['a', ' ', 'b']
+ * '"a \"1 2\"" b' => ['a="1 2"', 'b'] + *

+ * + * @param string parameter string to split. + * @return array of parameters. + */ + @NotNull + public static String[] parse(@NotNull final String string) { + final List params = ParametersTokenizer.decode(string); + return ArrayUtil.toStringArray(params); + } public String expandMacros(String text) { final Map macroMap = getMacroMap(); @@ -263,101 +310,88 @@ public class ParametersList implements Cloneable{ return myMacroMap; } - private static class ParametersTokenizer { - private final String myParamsString; - private final List myArray = new ArrayList(); - private final StringBuffer myBuffer = new StringBuffer(128); - private boolean myTokenStarted = false; - private boolean myUnquotedSlash = false; - private boolean mySplittedQuotingStarted = false; - - public ParametersTokenizer(@NotNull final String parmsString) { - myParamsString = parmsString; - } - - public String[] execute() { - boolean inQuotes = false; - - // \" sequence is turned to " inside "" - boolean wasEscaped = false; - - for (int i = 0; i < myParamsString.length(); i++) { - final char c = myParamsString.charAt(i); - - if (inQuotes) { - LOG.assertTrue(!myUnquotedSlash); - if (wasEscaped) { - //if (c != '"') append('\\'); - append(c); - wasEscaped = false; - } - else if (c == '"') { - inQuotes = false; - } - else if (c == '\\') { - myTokenStarted = true; - append(c); - wasEscaped = true; - } - else { - append(c); - } - } - else { - inQuotes = processNotQuoted(c, myBuffer.length() == 0 || myBuffer.charAt(myBuffer.length() - 1) == ' '); - } - } - tokenFinished(); - return ArrayUtil.toStringArray(myArray); - } - - private boolean processNotQuoted(final char c, final boolean isPreviousSpace) { - if (c == '"') { - if (myUnquotedSlash) { - append(c); - myUnquotedSlash = false; - return false; - } - else if (!isPreviousSpace || mySplittedQuotingStarted) { - append(c); - mySplittedQuotingStarted = !mySplittedQuotingStarted; - return false; - } - myTokenStarted = true; - return true; - } - else if (c == ' ') { - tokenFinished(); - } - else if (c == '\\') { - myUnquotedSlash = true; - append(c); - return false; - } - else { - append(c); - } - myUnquotedSlash = false; - return false; - } - - private void append(final char nextChar) { - myBuffer.append(nextChar); - myTokenStarted = true; - } - - private void tokenFinished() { - if (myTokenStarted) { - final String token = myBuffer.length() == 0 ? "\"\"" : myBuffer.toString(); - myArray.add(token); - } - myBuffer.setLength(0); - myTokenStarted = false; - } - } - @Override public String toString() { return myParameters.toString(); } + + private static class ParametersTokenizer { + private ParametersTokenizer() { } + + @NotNull + public static String encode(@NotNull final List parameters) { + final StringBuilder buffer = new StringBuilder(); + for (final String parameter : parameters) { + if (buffer.length() > 0) { + buffer.append(' '); + } + buffer.append(encode(parameter)); + } + return buffer.toString(); + } + + @NotNull + public static String encode(@NotNull String parameter) { + final StringBuilder builder = StringBuilderSpinAllocator.alloc(); + try { + builder.append(parameter); + StringUtil.escapeQuotes(builder); + if (builder.length() == 0 || StringUtil.indexOf(builder, ' ') >= 0) { + StringUtil.quote(builder); + } + return builder.toString(); + } + finally { + StringBuilderSpinAllocator.dispose(builder); + } + } + + @NotNull + public static List decode(@NotNull String parameterString) { + parameterString = parameterString.trim(); + + final ArrayList params = CollectionFactory.arrayList(); + final StringBuilder token = new StringBuilder(128); + boolean inQuotes = false; + boolean escapedQuote = false; + boolean nonEmpty = false; + + for (int i = 0; i < parameterString.length(); i++) { + final char ch = parameterString.charAt(i); + + if (ch == '\"') { + if (!escapedQuote) { + inQuotes = !inQuotes; + nonEmpty = true; + continue; + } + escapedQuote = false; + } + else if (Character.isWhitespace(ch)) { + if (!inQuotes) { + if (token.length() > 0 || nonEmpty) { + params.add(token.toString()); + token.setLength(0); + nonEmpty = false; + } + continue; + } + } + else if (ch == '\\') { + if (i < parameterString.length() - 1 && parameterString.charAt(i + 1) == '"') { + escapedQuote = true; + continue; + } + } + + token.append(ch); + } + + if (token.length() > 0 || nonEmpty) { + params.add(token.toString()); + } + + return params; + } + } } diff --git a/platform/platform-api/src/com/intellij/execution/configurations/ParamsGroup.java b/platform/platform-api/src/com/intellij/execution/configurations/ParamsGroup.java index b7ca25a54cb2..dd25946497d7 100644 --- a/platform/platform-api/src/com/intellij/execution/configurations/ParamsGroup.java +++ b/platform/platform-api/src/com/intellij/execution/configurations/ParamsGroup.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2010 JetBrains s.r.o. + * Copyright 2000-2011 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,7 +31,7 @@ import java.util.List; * different kinds of ruby tests, rails configuration, etc). Coverage support require to reorder args * in cmdline, add rcov runner script, etc. Without groups it would be harder to parse abstract list of arguments */ -public class ParamsGroup implements Cloneable{ +public class ParamsGroup implements Cloneable { private static final Logger LOG = Logger.getInstance(ParamsGroup.class.getName()); private String myGroupId; @@ -73,6 +73,7 @@ public class ParamsGroup implements Cloneable{ return myGroupParams; } + @Override public ParamsGroup clone() { try { final ParamsGroup clone = (ParamsGroup)super.clone(); diff --git a/platform/platform-impl/src/com/intellij/openapi/diff/impl/external/BaseExternalTool.java b/platform/platform-impl/src/com/intellij/openapi/diff/impl/external/BaseExternalTool.java index 9a4b0b55bbb1..ad2735f1cb89 100644 --- a/platform/platform-impl/src/com/intellij/openapi/diff/impl/external/BaseExternalTool.java +++ b/platform/platform-impl/src/com/intellij/openapi/diff/impl/external/BaseExternalTool.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2011 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -59,14 +59,14 @@ abstract class BaseExternalTool implements DiffTool { } public void show(DiffRequest request) { - //ArrayList commandLine = new ArrayList(); GeneralCommandLine commandLine = new GeneralCommandLine(); commandLine.setExePath(getToolPath()); try { commandLine.addParameter(convertToPath(request, 0)); commandLine.addParameter(convertToPath(request, 1)); - Runtime.getRuntime().exec(commandLine.getCommands()); - } catch (IOException e) { + commandLine.createProcess(); + } + catch (Exception e) { ExecutionErrorDialog.show(new ExecutionException(e.getMessage()), DiffBundle.message("cant.launch.diff.tool.error.message"), request.getProject()); } diff --git a/platform/platform-impl/src/com/intellij/openapi/diff/impl/external/DiffManagerImpl.java b/platform/platform-impl/src/com/intellij/openapi/diff/impl/external/DiffManagerImpl.java index 93709d2b8c96..d2c610ffb9e5 100644 --- a/platform/platform-impl/src/com/intellij/openapi/diff/impl/external/DiffManagerImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/diff/impl/external/DiffManagerImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2011 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,6 @@ */ package com.intellij.openapi.diff.impl.external; -import com.intellij.execution.configurations.GeneralCommandLine; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.diff.DiffManager; import com.intellij.openapi.diff.DiffPanel; @@ -42,13 +41,15 @@ import java.util.Arrays; public class DiffManagerImpl extends DiffManager implements JDOMExternalizable { private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.diff.impl.external.DiffManagerImpl"); + private static final Externalizer TOOL_PATH_UPDATE = new Externalizer() { @NonNls private static final String NEW_VALUE = "newValue"; + public String readValue(Element dataElement) { String path = dataElement.getAttributeValue(NEW_VALUE); if (path != null) return path; String prevValue = dataElement.getAttributeValue(VALUE_ATTRIBUTE); - return prevValue != null ? GeneralCommandLine.quote(prevValue.trim()) : null; + return prevValue != null ? prevValue.trim() : null; } public void writeValue(Element dataElement, String path) { @@ -56,6 +57,7 @@ public class DiffManagerImpl extends DiffManager implements JDOMExternalizable { dataElement.setAttribute(NEW_VALUE, path); } }; + static final StringProperty FOLDERS_TOOL = new StringProperty("foldersTool", ""); static final StringProperty FILES_TOOL = new StringProperty("filesTool", ""); static final BooleanProperty ENABLE_FOLDERS = new BooleanProperty( diff --git a/platform/platform-impl/testSrc/com/intellij/execution/EnvPassingTest.java b/platform/platform-impl/testSrc/com/intellij/execution/EnvPassingTest.java new file mode 100644 index 000000000000..b53e701c92c5 --- /dev/null +++ b/platform/platform-impl/testSrc/com/intellij/execution/EnvPassingTest.java @@ -0,0 +1,37 @@ +/* + * Copyright 2000-2011 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.execution; + +import java.util.Map; + +/** + * Invoked from {@link GeneralCommandLineTest} as external process. + */ +@SuppressWarnings("UtilityClassWithoutPrivateConstructor") +public class EnvPassingTest { + public static void main(String[] args) { + final Map environment = System.getenv(); + System.out.println("====="); + for (Map.Entry entry : environment.entrySet()) { + System.out.println(formatEntry(entry)); + } + System.out.println("====="); + } + + public static String formatEntry(final Map.Entry entry) { + return entry.getKey() + "=" + entry.getValue().hashCode(); + } +} diff --git a/platform/platform-impl/testSrc/com/intellij/execution/GeneralCommandLineTest.java b/platform/platform-impl/testSrc/com/intellij/execution/GeneralCommandLineTest.java new file mode 100644 index 000000000000..8b4e8b40275a --- /dev/null +++ b/platform/platform-impl/testSrc/com/intellij/execution/GeneralCommandLineTest.java @@ -0,0 +1,142 @@ +/* + * Copyright 2000-2011 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.execution; + +import com.intellij.execution.configurations.GeneralCommandLine; +import com.intellij.openapi.util.SystemInfo; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.system.ExecUtil; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.testFramework.UsefulTestCase; + +import java.io.File; +import java.net.URL; +import java.util.*; + +public class GeneralCommandLineTest extends UsefulTestCase { + public void testPrintCommandLine() { + final GeneralCommandLine commandLine = new GeneralCommandLine(); + commandLine.setExePath("e x e path"); + commandLine.addParameter("with space"); + commandLine.addParameter("\"quoted\""); + commandLine.addParameter("\"quoted with spaces\""); + commandLine.addParameters("param 1", "param2"); + commandLine.addParameter("trailing slash\\"); + assertEquals("\"e x e path\"" + + " \"with space\"" + + " \\\"quoted\\\"" + + " \"\\\"quoted with spaces\\\"\"" + + " \"param 1\"" + + " param2" + + " \"trailing slash\\\"", + commandLine.getCommandLineString()); + } + + public void testExecuteCommandLine() throws Exception { + final File tempFile; + if (SystemInfo.isWindows) { + final URL url = getClass().getClassLoader().getResource("com/intellij/execution/printArgs.exe"); + assertNotNull(url); + tempFile = FileUtil.createTempFile("path with spaces 'and quotes' ", ".exe"); + FileUtil.copy(new File(url.getFile()), tempFile); + } + else { + tempFile = ExecUtil.createTempExecutableScript( + "path with spaces \"and quotes\" ", ".sh", + "#!/bin/sh\n\n" + + "echo \"=====\"\n" + + "for f in \"$@\" ; do echo $f; done\n" + + "echo \"=====\"\n" + ); + } + + final String[] parameters = { "with space", "\"quoted\"", "\"quoted with spaces\"", "", "param 1", "\"", "param2", "trailing slash\\" }; + try { + final GeneralCommandLine commandLine = new GeneralCommandLine(); + commandLine.setExePath(tempFile.getCanonicalPath()); + commandLine.addParameters(parameters); + commandLine.setRedirectErrorStream(true); + final Process process = commandLine.createProcess(); + final String output = FileUtil.loadTextAndClose(process.getInputStream()); + final int result = process.waitFor(); + + assertEquals("Command:\n" + commandLine.getCommandLineString() + "\nOutput:\n" + output, + 0, result); + assertEquals("=====\n" + StringUtil.join(parameters, "\n") + "\n=====\n", + StringUtil.convertLineSeparators(output)); + } + finally { + FileUtil.delete(tempFile); + } + } + + public void testEnvironmentPassing() throws Exception { + final URL url = getClass().getClassLoader().getResource("com/intellij/execution/EnvPassingTest.class"); + assertNotNull(url); + final File testClass = new File(url.getFile()); + + final GeneralCommandLine commandLine = new GeneralCommandLine(); + commandLine.setExePath("java"); + commandLine.addParameter("-cp"); + commandLine.addParameter(testClass.getParentFile().getParentFile().getParentFile().getParentFile().getAbsolutePath()); + commandLine.addParameter("com.intellij.execution." + testClass.getName().replace(".class", "")); + commandLine.setRedirectErrorStream(true); + + final Map testEnv = new HashMap(); + testEnv.put("VALUE_1", "some value"); + testEnv.put("VALUE_2", "another\n\"value\""); + + checkEnvPassing(commandLine, testEnv, true); + checkEnvPassing(commandLine, testEnv, false); + } + + private static void checkEnvPassing(final GeneralCommandLine commandLine, + final Map testEnv, + final boolean passParentEnv) throws Exception { + commandLine.setEnvParams(testEnv); + commandLine.setPassParentEnvs(passParentEnv); + + final Process process = commandLine.createProcess(); + final String output = FileUtil.loadTextAndClose(process.getInputStream()); + final int result = process.waitFor(); + assertEquals("Command:\n" + commandLine.getCommandLineString() + "\nOutput:\n" + output, + 0, result); + + final Set lines = new HashSet(Arrays.asList(StringUtil.convertLineSeparators(output).split("\n"))); + + for (Map.Entry entry : testEnv.entrySet()) { + final String str = EnvPassingTest.formatEntry(entry); + assertTrue("\"" + str + "\" should be in " + lines, + lines.contains(str)); + } + + final Map parentEnv = System.getenv(); + final List missed = new ArrayList(); + for (Map.Entry entry : parentEnv.entrySet()) { + final String str = EnvPassingTest.formatEntry(entry); + if (passParentEnv) { + assertTrue("\"" + str + "\" should be in " + lines, + lines.contains(str)); + } + else if (lines.contains(str)) { + missed.add(str); + } + } + if (!passParentEnv && missed.size() > 0 && parentEnv.size()/missed.size() < 2) { + fail(missed + " shouldn't be in " + lines + " (ratio: " + parentEnv.size() + '/' + missed.size() + ')'); + } + } +} diff --git a/platform/platform-impl/testSrc/com/intellij/execution/printArgs.c b/platform/platform-impl/testSrc/com/intellij/execution/printArgs.c new file mode 100644 index 000000000000..54ac7debd176 --- /dev/null +++ b/platform/platform-impl/testSrc/com/intellij/execution/printArgs.c @@ -0,0 +1,14 @@ +#include + +/** + * Invoked from {@link GeneralCommandLineTest} as external process. + */ +int main(int argc, char *argv[]) { + int i; + printf("=====\n"); + for (i = 1; i < argc; ++i) { + printf("%s\n", argv[i]); + } + printf("=====\n"); + return 0; +} diff --git a/platform/platform-impl/testSrc/com/intellij/execution/printArgs.exe b/platform/platform-impl/testSrc/com/intellij/execution/printArgs.exe new file mode 100644 index 000000000000..13f43e917733 Binary files /dev/null and b/platform/platform-impl/testSrc/com/intellij/execution/printArgs.exe differ diff --git a/platform/platform-impl/testSrc/com/intellij/openapi/execution/ParametersListTest.java b/platform/platform-impl/testSrc/com/intellij/openapi/execution/ParametersListTest.java index 837e6f6fc962..3c7cfd6d55ec 100644 --- a/platform/platform-impl/testSrc/com/intellij/openapi/execution/ParametersListTest.java +++ b/platform/platform-impl/testSrc/com/intellij/openapi/execution/ParametersListTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2011 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,34 +18,14 @@ package com.intellij.openapi.execution; import com.intellij.execution.configurations.ParametersList; import com.intellij.execution.configurations.ParamsGroup; import com.intellij.testFramework.UsefulTestCase; -import com.intellij.util.ArrayUtil; -import com.intellij.util.Assertion; -import org.jetbrains.annotations.NonNls; +import java.util.Arrays; import java.util.Collections; /** * @author dyoma */ public class ParametersListTest extends UsefulTestCase { - private final Assertion CHECK = new Assertion(); - - public void testAddParametersString() { - checkTokenizer("a b c", new String[]{"a", "b", "c"}); - checkTokenizer("a \"b\"", new String[]{"a", "b"}); - checkTokenizer("a \"b\\\"", new String[]{"a", "b\\\""}); - checkTokenizer("a \"\"", new String[]{"a", "\"\""}); // Bug #12169 - checkTokenizer("a \"x\"", new String[]{"a", "x"}); - checkTokenizer("a \"\" b", new String[]{"a", "\"\"", "b"}); - } - - private void checkTokenizer(@NonNls String parmsString, @NonNls String[] expected) { - ParametersList params = new ParametersList(); - params.addParametersString(parmsString); - String[] strings = ArrayUtil.toStringArray(params.getList()); - CHECK.compareAll(expected, strings); - } - public void testParamsGroup_Empty() { ParametersList params = new ParametersList(); @@ -82,7 +62,7 @@ public class ParametersListTest extends UsefulTestCase { public void testParamsGroup_Remove() { ParametersList params = new ParametersList(); - final ParamsGroup group1 = params.addParamsGroup("id1"); + params.addParamsGroup("id1"); final ParamsGroup group2 = params.addParamsGroup("id2"); final ParamsGroup group3 = params.addParamsGroup("id3"); final ParamsGroup group4 = params.addParamsGroup("id4"); @@ -169,20 +149,61 @@ public class ParametersListTest extends UsefulTestCase { assertEquals("group1_param1 group2_param1 group3_param1", params_clone.getParametersString().trim()); } - public void testParamsWithSpaces() { - checkTokenizer("a b=\"some text\" c", new String[]{"a", "b=\"some", "text\"", "c"}); - checkTokenizer("a b=\"some text with spaces\" c", new String[]{"a", "b=\"some", "text", "with", "spaces\"", "c"}); - checkTokenizer("a b=\"some text with spaces\"more c", new String[]{"a", "b=\"some", "text", "with", "spaces\"more", "c"}); - checkTokenizer("a b=\"some text with spaces \"more c", new String[]{"a", "b=\"some", "text", "with", "spaces", "\"more", "c"}); + public void testAddParametersString() { + checkTokenizer("a b c", + "a", "b", "c"); + checkTokenizer("a \"b\"", + "a", "b"); + checkTokenizer("a \"b\\\"", + "a", "b\""); + checkTokenizer("a \"\"", + "a", ""); // Bug #12169 + checkTokenizer("a \"x\"", + "a", "x"); + checkTokenizer("a \"\\\"\" b", + "a", "\"", "b"); + } - //this test just fixes the way it works; - // i don't see any use cases when it definitely should or shouldn't be this way - checkTokenizer("a \"some text with spaces\"More c", new String[]{"a", "some text with spacesMore", "c"}); + public void testParamsWithSpacesAndQuotes() { + checkTokenizer("a b=\"some text\" c", + "a", "b=some text", "c"); + checkTokenizer("a b=\"some text with spaces\" c", + "a", "b=some text with spaces", "c"); + checkTokenizer("a b=\"some text with spaces\".more c", + "a", "b=some text with spaces.more", "c"); + checkTokenizer("a b=\"some text with spaces \"more c", + "a", "b=some text with spaces more", "c"); + checkTokenizer("a \"some text with spaces\"More c", + "a", "some text with spacesMore", "c"); + checkTokenizer("a \"some text with spaces more c", + "a", "some text with spaces more c"); + checkTokenizer("a\"Some text with spaces \"more c", + "aSome text with spaces more", "c"); + checkTokenizer("a\"Some text with spaces \"more", + "aSome text with spaces more"); + checkTokenizer("a\"Some text with spaces \"more next\"Text moreText\"End c", + "aSome text with spaces more", "nextText moreTextEnd", "c"); + checkTokenizer("\"\"C:\\phing.bat\"", + "C:\\phing.bat"); + checkTokenizer("-Dprop.1=\"some text\" -Dprop.2=\\\"value\\\"", + "-Dprop.1=some text", "-Dprop.2=\"value\""); + } - checkTokenizer("a \"some text with spaces \"more c", new String[]{"a", "some text with spaces more", "c"}); - checkTokenizer("a\"some text with spaces \"more c", new String[]{"a\"some", "text", "with", "spaces", "\"more", "c"}); - checkTokenizer("a\"some text with spaces \"more", new String[]{"a\"some", "text", "with", "spaces", "\"more"}); - checkTokenizer("a\"some text with spaces \"more next\"text moreText\"end c", new String[]{"a\"some", "text", "with", "spaces", "\"more", - "next\"text", "moreText\"end", "c"}); + public void testJoiningParams() throws Exception { + final String[] parameters = {"simpleParam", "param with spaces", "withQuote=\"", "param=\"complex quoted\""}; + final ParametersList parametersList = new ParametersList(); + + parametersList.addAll(parameters); + final String joined = parametersList.getParametersString(); + assertEquals("simpleParam \"param with spaces\" withQuote=\\\" \"param=\\\"complex quoted\\\"\"", + joined); + + checkTokenizer(joined, parameters); + } + + private static void checkTokenizer(final String paramString, final String... expected) { + final ParametersList params = new ParametersList(); + params.addParametersString(paramString); + assertEquals(Arrays.asList(expected), params.getList()); } } diff --git a/plugins/android/src/org/jetbrains/android/compiler/tools/AndroidMavenExecutor.java b/plugins/android/src/org/jetbrains/android/compiler/tools/AndroidMavenExecutor.java index a29c4f78abab..ce7e7b8a5c17 100644 --- a/plugins/android/src/org/jetbrains/android/compiler/tools/AndroidMavenExecutor.java +++ b/plugins/android/src/org/jetbrains/android/compiler/tools/AndroidMavenExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2010 JetBrains s.r.o. + * Copyright 2000-2011 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,6 @@ import com.intellij.execution.configurations.JavaParameters; import com.intellij.openapi.compiler.CompilerMessageCategory; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.module.Module; -import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.containers.HashMap; import org.jetbrains.android.util.AndroidUtils; import org.jetbrains.idea.maven.execution.MavenExternalParameters; @@ -66,10 +65,6 @@ public class AndroidMavenExecutor { MavenRunner.getInstance(module.getProject()).getSettings()); GeneralCommandLine commandLine = CommandLineBuilder.createFromJavaParameters(javaParams); - - String[] commands = commandLine.getCommands(); - String command = StringUtil.join(commands, " "); - LOG.info("Execute: " + command); StringBuilder messageBuilder = new StringBuilder(); boolean success = AndroidUtils.executeCommand(commandLine, messageBuilder); String message = messageBuilder.toString(); diff --git a/plugins/android/src/org/jetbrains/android/util/AndroidUtils.java b/plugins/android/src/org/jetbrains/android/util/AndroidUtils.java index 3060fce3a40c..04fb8bed9771 100644 --- a/plugins/android/src/org/jetbrains/android/util/AndroidUtils.java +++ b/plugins/android/src/org/jetbrains/android/util/AndroidUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2010 JetBrains s.r.o. + * Copyright 2000-2011 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -49,7 +49,6 @@ import com.intellij.openapi.ui.popup.JBPopup; import com.intellij.openapi.ui.popup.JBPopupFactory; import com.intellij.openapi.util.*; import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.wm.ToolWindow; @@ -468,10 +467,6 @@ public class AndroidUtils { GeneralCommandLine commandLine, boolean printOutputToAndroidConsole, ProcessHandler processHandler) { - String[] commands = commandLine.getCommands(); - String command = StringUtil.join(commands, " "); - LOG.info("Execute: " + command); - StringBuilder messageBuilder = new StringBuilder(); String result; boolean success = false; diff --git a/plugins/git4idea/src/git4idea/commands/GitHandler.java b/plugins/git4idea/src/git4idea/commands/GitHandler.java index 0f4ca8692e42..af0492298061 100644 --- a/plugins/git4idea/src/git4idea/commands/GitHandler.java +++ b/plugins/git4idea/src/git4idea/commands/GitHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2011 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -121,7 +121,7 @@ public abstract class GitHandler { if (myAppSettings != null) { myCommandLine.setExePath(myAppSettings.getPathToGit()); } - myCommandLine.setWorkingDirectory(myWorkingDirectory); + myCommandLine.setWorkDirectory(myWorkingDirectory); if (command.name().length() > 0) { myCommandLine.addParameter(command.name()); } @@ -428,9 +428,7 @@ public abstract class GitHandler { * @return a command line with full path to executable replace to "git" */ public String printableCommandLine() { - final GeneralCommandLine line = myCommandLine.clone(); - line.setExePath("git"); - return line.getCommandLineString(); + return myCommandLine.getCommandLineString("git"); } /** diff --git a/plugins/junit/src/com/intellij/execution/junit/TestObject.java b/plugins/junit/src/com/intellij/execution/junit/TestObject.java index c92f33977957..b0c1d939c99a 100644 --- a/plugins/junit/src/com/intellij/execution/junit/TestObject.java +++ b/plugins/junit/src/com/intellij/execution/junit/TestObject.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2011 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,14 +32,10 @@ import com.intellij.execution.junit2.ui.model.RootTestInfo; import com.intellij.execution.junit2.ui.properties.JUnitConsoleProperties; import com.intellij.execution.process.ProcessAdapter; import com.intellij.execution.process.ProcessEvent; -import com.intellij.execution.process.ProcessHandler; import com.intellij.execution.runners.ProgramRunner; import com.intellij.execution.testframework.*; import com.intellij.execution.ui.ConsoleViewContentType; import com.intellij.execution.util.JavaParametersUtil; -import com.intellij.notification.Notification; -import com.intellij.notification.NotificationType; -import com.intellij.notification.Notifications; import com.intellij.openapi.Disposable; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; @@ -48,6 +44,7 @@ import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; import com.intellij.openapi.projectRoots.JavaSdk; import com.intellij.openapi.projectRoots.JavaSdkType; +import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.projectRoots.ex.JavaSdkUtil; import com.intellij.openapi.roots.ModuleRootManager; import com.intellij.openapi.roots.ProjectRootManager; @@ -68,8 +65,13 @@ import com.intellij.util.IJSwingUtilities; import com.intellij.util.PathUtil; import org.jetbrains.annotations.NotNull; -import java.io.*; -import java.util.*; +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; public abstract class TestObject implements JavaCommandLine { protected static final Logger LOG = Logger.getInstance("#com.intellij.execution.junit.TestObject"); @@ -147,10 +149,12 @@ public abstract class TestObject implements JavaCommandLine { return null; } + @Override public String suggestActionName() { throw new RuntimeException(String.valueOf(myConfiguration)); } + @Override public boolean isConfiguredByElement(final JUnitConfiguration configuration, PsiClass testClass, PsiMethod testMethod, @@ -158,31 +162,22 @@ public abstract class TestObject implements JavaCommandLine { return false; } + @Override public void checkConfiguration() throws RuntimeConfigurationException { throw new RuntimeConfigurationError(MESSAGE); } - public ExecutionResult execute() throws ExecutionException { - throw createExecutionException(); - } - + @Override public JavaParameters getJavaParameters() throws ExecutionException { - throw createExecutionException(); + throw new ExecutionException(MESSAGE); } + @Override protected void initialize() throws ExecutionException { - throw createExecutionException(); - } - - protected ProcessHandler startProcess() throws ExecutionException { - throw createExecutionException(); + throw new ExecutionException(MESSAGE); } }; - private static ExecutionException createExecutionException() { - return new ExecutionException(MESSAGE); - } - public void checkConfiguration() throws RuntimeConfigurationException{ if (myConfiguration.isAlternativeJrePathEnabled()){ if (myConfiguration.getAlternativeJrePath() == null || @@ -216,7 +211,7 @@ public abstract class TestObject implements JavaCommandLine { } final Object[] listeners = Extensions.getExtensions(IDEAJUnitListener.EP_NAME); - final StringBuffer buf = new StringBuffer(); + final StringBuilder buf = new StringBuilder(); for (final Object listener : listeners) { boolean enabled = true; for (RunConfigurationExtension ext : Extensions.getExtensions(RunConfigurationExtension.EP_NAME)) { @@ -233,7 +228,7 @@ public abstract class TestObject implements JavaCommandLine { } if (buf.length() > 0) { try { - myListenersFile = FileUtil.createTempFile("junitlisteners", ""); + myListenersFile = FileUtil.createTempFile("junit_listeners_", ""); myListenersFile.deleteOnExit(); myJavaParameters.getProgramParametersList().add("@@" + myListenersFile.getPath()); FileUtil.writeToFile(myListenersFile, buf.toString().getBytes()); @@ -360,40 +355,42 @@ public abstract class TestObject implements JavaCommandLine { private void appendForkInfo() throws ExecutionException { final String forkMode = myConfiguration.getForkMode(); - if (Comparing.strEqual(forkMode, "none")) return; + if (Comparing.strEqual(forkMode, "none")) { + return; + } + if (myRunnerSettings.getData() instanceof DebuggingRunnerData) { - throw new CantRunException("Debug is disabled in fork mode.
Change fork mode to <none> to debug"); - } - File tempFile = null; - try { - tempFile = FileUtil.createTempFile("command.line", ""); - myJavaParameters.getProgramParametersList().add("@@@" + tempFile.getAbsolutePath()); - } - catch (IOException e) { - LOG.error(e); + throw new CantRunException("Debug is disabled in fork mode.
Please change fork mode to <none> to debug."); } + final JavaParameters javaParameters = getJavaParameters(); + final Sdk jdk = javaParameters.getJdk(); + if (jdk == null) { + throw new ExecutionException(ExecutionBundle.message("run.configuration.error.no.jdk.specified")); + } + try { + final File tempFile = FileUtil.createTempFile("command.line", "", true); final PrintWriter writer = new PrintWriter(tempFile, "UTF-8"); try { - writer.print(GeneralCommandLine - .quoteParameter(((JavaSdkType)javaParameters.getJdk().getSdkType()).getVMExecutablePath(javaParameters.getJdk())) + " "); - writer.print(javaParameters.getVMParametersList().getParametersString() + " "); - writer.print("-classpath "); - writer.print(GeneralCommandLine.quoteParameter(javaParameters.getClassPath().getPathsString())); - writer.print("\n" + forkMode); + writer.println(((JavaSdkType)jdk.getSdkType()).getVMExecutablePath(jdk)); + for (String vmParameter : javaParameters.getVMParametersList().getList()) { + writer.println(vmParameter); + } + writer.println("-classpath"); + writer.println(javaParameters.getClassPath().getPathsString()); } finally { writer.close(); } - tempFile.deleteOnExit(); + + myJavaParameters.getProgramParametersList().add("@@@" + forkMode + ',' + tempFile.getAbsolutePath()); } catch (Exception e) { LOG.error(e); } } - protected void addClassesListToJavaParameters(Collection elements, Function nameFunction, String packageName, boolean createTempFile, boolean junit4) { diff --git a/plugins/junit_rt/src/com/intellij/rt/execution/junit/IdeaTestRunner.java b/plugins/junit_rt/src/com/intellij/rt/execution/junit/IdeaTestRunner.java index 66a181a95336..49e94d110e0d 100644 --- a/plugins/junit_rt/src/com/intellij/rt/execution/junit/IdeaTestRunner.java +++ b/plugins/junit_rt/src/com/intellij/rt/execution/junit/IdeaTestRunner.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2011 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,9 +22,7 @@ package com.intellij.rt.execution.junit; import com.intellij.rt.execution.junit.segments.OutputObjectRegistry; import com.intellij.rt.execution.junit.segments.SegmentedOutputStream; -import org.junit.runner.Description; -import java.io.IOException; import java.util.ArrayList; import java.util.List; diff --git a/plugins/junit_rt/src/com/intellij/rt/execution/junit/JUnitForkedStarter.java b/plugins/junit_rt/src/com/intellij/rt/execution/junit/JUnitForkedStarter.java index 80808d8a0f52..9c4d6890a241 100644 --- a/plugins/junit_rt/src/com/intellij/rt/execution/junit/JUnitForkedStarter.java +++ b/plugins/junit_rt/src/com/intellij/rt/execution/junit/JUnitForkedStarter.java @@ -19,16 +19,14 @@ import com.intellij.rt.execution.junit.segments.SegmentedOutputStream; import java.io.*; import java.util.ArrayList; -import java.util.Iterator; import java.util.List; /** -* User: anna -* Date: 4/6/11 -*/ + * @author anna + * @since 6.04.2011 + */ public class JUnitForkedStarter { - private JUnitForkedStarter() { - } + private JUnitForkedStarter() { } public static void main(String[] args) throws Exception { final String testOutputPath = args[0]; @@ -45,7 +43,9 @@ public class JUnitForkedStarter { if (!file.createNewFile()) return; } final FileOutputStream stream = new FileOutputStream(testOutputPath); + //noinspection UseOfSystemOutOrSystemErr PrintStream oldOut = System.out; + //noinspection UseOfSystemOutOrSystemErr PrintStream oldErr = System.err; try { final PrintStream out = new PrintStream(new ForkedVMWrapper(stream, false)); @@ -53,9 +53,11 @@ public class JUnitForkedStarter { System.setOut(out); System.setErr(err); IdeaTestRunner testRunner = (IdeaTestRunner)JUnitStarter.getAgentClass(isJUnit4).newInstance(); + //noinspection IOResourceOpenedButNotSafelyClosed testRunner.setStreams(new SegmentedOutputStream(out, true), new SegmentedOutputStream(err, true), lastIdx); System.exit(testRunner.startRunnerWithArgs(childTestDescription, listeners, false)); - } finally { + } + finally { System.setOut(oldOut); System.setErr(oldErr); stream.close(); @@ -64,69 +66,85 @@ public class JUnitForkedStarter { static int startForkedVMs(String[] args, boolean isJUnit4, - ArrayList listeners, + List listeners, SegmentedOutputStream out, - SegmentedOutputStream err, String path) throws Exception { - final BufferedReader reader = new BufferedReader(new FileReader(path)); - final String commandline = reader.readLine(); - final String forkMode = reader.readLine(); - reader.close(); + SegmentedOutputStream err, + String forkMode, + String path) throws Exception { + final List parameters = new ArrayList(); + final BufferedReader bufferedReader = new BufferedReader(new FileReader(path)); + try { + String line; + while ((line = bufferedReader.readLine()) != null) { + parameters.add(line); + } + } + finally { + bufferedReader.close(); + } + IdeaTestRunner testRunner = (IdeaTestRunner)JUnitStarter.getAgentClass(isJUnit4).newInstance(); testRunner.setStreams(out, err, 0); final Object description = testRunner.getTestToStart(args); TreeSender.sendTree(testRunner, description); - long startTime = System.currentTimeMillis(); + long time = System.currentTimeMillis(); final List children = testRunner.getChildTests(description); final boolean forkTillMethod = forkMode.equalsIgnoreCase("method"); - int result = processChildren(isJUnit4, listeners, out, err, commandline, testRunner, children, 0, forkTillMethod); + int result = processChildren(isJUnit4, listeners, out, err, parameters, testRunner, children, 0, forkTillMethod); - long endTime = System.currentTimeMillis(); - long runTime = endTime - startTime; - new TimeSender(testRunner.getRegistry()).printHeader(runTime); + time = System.currentTimeMillis() - time; + new TimeSender(testRunner.getRegistry()).printHeader(time); return result; } private static int processChildren(boolean isJUnit4, - ArrayList listeners, + List listeners, SegmentedOutputStream out, SegmentedOutputStream err, - String commandline, IdeaTestRunner testRunner, List children, int result, boolean forkTillMethod) - throws IOException, InterruptedException { + List parameters, + IdeaTestRunner testRunner, + List children, + int result, + boolean forkTillMethod) throws IOException, InterruptedException { for (int i = 0, argsLength = children.size(); i < argsLength; i++) { final Object child = children.get(i); final List childTests = testRunner.getChildTests(child); - if (childTests.isEmpty() || !forkTillMethod) { - result = Math.min(runChild(child, isJUnit4, listeners, out, err, commandline, testRunner, forkTillMethod), result); - } else { - result = Math.min(processChildren(isJUnit4, listeners, out, err, commandline, testRunner, childTests, result, forkTillMethod), result); - } + final int childResult = childTests.isEmpty() || !forkTillMethod + ? runChild(child, isJUnit4, listeners, out, err, parameters, testRunner, forkTillMethod) + : processChildren(isJUnit4, listeners, out, err, parameters, testRunner, childTests, result, forkTillMethod); + result = Math.min(childResult, result); } return result; } private static int runChild(Object child, boolean isJUnit4, - ArrayList listeners, + List listeners, SegmentedOutputStream out, SegmentedOutputStream err, - String commandline, IdeaTestRunner testRunner, - boolean forkTillMethod) - throws IOException, InterruptedException { + List parameters, + IdeaTestRunner testRunner, + boolean forkTillMethod) throws IOException, InterruptedException { + //noinspection SSBasedInspection final File tempFile = File.createTempFile("fork", "test"); final String testOutputPath = tempFile.getAbsolutePath(); final int knownObject = testRunner.getRegistry().getKnownObject(child); - String command = commandline + " " + JUnitForkedStarter.class.getName()+ " " + testOutputPath + " " + (knownObject + (forkTillMethod ? 0 : 1)) + " " + - isJUnit4 + " " + testRunner.getStartDescription(child) ; - for (Iterator iterator = listeners.iterator(); iterator.hasNext(); ) { - command += " " + iterator.next(); - } - final Process exec = Runtime.getRuntime().exec(command); - int result = exec.waitFor(); + + final ProcessBuilder builder = new ProcessBuilder(); + builder.add(parameters); + builder.add(JUnitForkedStarter.class.getName()); + builder.add(testOutputPath); + builder.add(String.valueOf(knownObject + (forkTillMethod ? 0 : 1))); + builder.add(String.valueOf(isJUnit4)); + builder.add(testRunner.getStartDescription(child)); + builder.add(listeners); + + final Process exec = builder.createProcess(); + final int result = exec.waitFor(); ForkedVMWrapper.readWrapped(testOutputPath, out.getPrintStream(), err.getPrintStream()); - // tempFile.deleteOnExit(); return result; } } diff --git a/plugins/junit_rt/src/com/intellij/rt/execution/junit/JUnitStarter.java b/plugins/junit_rt/src/com/intellij/rt/execution/junit/JUnitStarter.java index c3d281dae47a..f492409c9e18 100644 --- a/plugins/junit_rt/src/com/intellij/rt/execution/junit/JUnitStarter.java +++ b/plugins/junit_rt/src/com/intellij/rt/execution/junit/JUnitStarter.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2011 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,7 +34,8 @@ public class JUnitStarter { public static final String IDE_VERSION = "-ideVersion"; public static final String JUNIT4_PARAMETER = "-junit4"; private static final String SOCKET = "-socket"; - private static String ourCommandLine; + private static String ourForkMode; + private static String ourCommandFileName; public static void main(String[] args) throws IOException { SegmentedOutputStream out = new SegmentedOutputStream(System.out); @@ -77,7 +78,9 @@ public class JUnitStarter { } else { if (arg.startsWith("@@@")) { - ourCommandLine = arg.substring(3); + final int pos = arg.indexOf(','); + ourForkMode = arg.substring(3, pos); + ourCommandFileName = arg.substring(pos + 1); continue; } else if (arg.startsWith("@@")) { if (new File(arg.substring(2)).exists()) { @@ -191,8 +194,8 @@ public class JUnitStarter { try { System.setOut(new PrintStream(out)); System.setErr(new PrintStream(err)); - if (ourCommandLine != null) { - return JUnitForkedStarter.startForkedVMs(args, isJUnit4, listeners, out, err, ourCommandLine); + if (ourCommandFileName != null) { + return JUnitForkedStarter.startForkedVMs(args, isJUnit4, listeners, out, err, ourForkMode, ourCommandFileName); } IdeaTestRunner testRunner = (IdeaTestRunner)getAgentClass(isJUnit4).newInstance(); testRunner.setStreams(out, err, 0); diff --git a/plugins/junit_rt/src/com/intellij/rt/execution/junit/ProcessBuilder.java b/plugins/junit_rt/src/com/intellij/rt/execution/junit/ProcessBuilder.java new file mode 100644 index 000000000000..5a0e66b97bb7 --- /dev/null +++ b/plugins/junit_rt/src/com/intellij/rt/execution/junit/ProcessBuilder.java @@ -0,0 +1,66 @@ +/* + * Copyright 2000-2011 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.rt.execution.junit; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * Clone of GeneralCommandLine. + */ +public class ProcessBuilder { + public static final boolean isWindows = System.getProperty("os.name").toLowerCase().startsWith("windows"); + + private final List myParameters = new ArrayList(); + + public void add(final String parameter) { + myParameters.add(parameter); + } + + public void add(final List parameters) { + for (int i = 0; i < parameters.size(); i++) { + add((String)parameters.get(i)); + } + } + + public Process createProcess() throws IOException { + if (myParameters.size() < 1) { + throw new IllegalArgumentException("Executable name not specified"); + } + + final String[] command = new String[myParameters.size()]; + for (int i = 0; i < myParameters.size(); i++) { + command[i] = prepareCommand(myParameters.get(i).toString()); + } + + return Runtime.getRuntime().exec(command); + } + + private static String prepareCommand(String parameter) { + // AFAIK, the only thing needed is escaping double quotes on Windows + if (isWindows) { + final StringBuffer buffer = new StringBuffer(parameter); + int pos = 0; + while ((pos = parameter.indexOf('\"', pos)) >= 0) { + buffer.insert(pos, '\\'); + pos += 2; + } + parameter = buffer.toString(); + } + return parameter; + } +}