diff --git a/compiler/android-tests/build.gradle.kts b/compiler/android-tests/build.gradle.kts index ff2ebe34d13..407a9b4dd7d 100644 --- a/compiler/android-tests/build.gradle.kts +++ b/compiler/android-tests/build.gradle.kts @@ -34,6 +34,7 @@ dependencies { testCompile(jpsBuildTest()) testRuntimeOnly(compile(intellijCoreDep()) { includeJars("intellij-core") }) + testRuntimeOnly(compile(intellijDep()) { includeJars("jna", rootProject = rootProject) }) testCompile("org.junit.platform:junit-platform-launcher:${commonVer("org.junit.platform", "")}") } diff --git a/compiler/fir/raw-fir/light-tree2fir/tests/org/jetbrains/kotlin/fir/lightTree/AbstractLightTree2FirConverterTestCase.kt b/compiler/fir/raw-fir/light-tree2fir/tests/org/jetbrains/kotlin/fir/lightTree/AbstractLightTree2FirConverterTestCase.kt index 19e68c18ff0..b4db07d9a75 100644 --- a/compiler/fir/raw-fir/light-tree2fir/tests/org/jetbrains/kotlin/fir/lightTree/AbstractLightTree2FirConverterTestCase.kt +++ b/compiler/fir/raw-fir/light-tree2fir/tests/org/jetbrains/kotlin/fir/lightTree/AbstractLightTree2FirConverterTestCase.kt @@ -17,7 +17,7 @@ import java.nio.file.Paths abstract class AbstractLightTree2FirConverterTestCase : AbstractRawFirBuilderTestCase() { - override fun doTest(filePath: String) { + fun doTest(filePath: String) { val firFile = LightTree2Fir( session = FirSessionFactory.createEmptySession(), scopeProvider = StubFirScopeProvider, diff --git a/compiler/incremental-compilation-impl/build.gradle.kts b/compiler/incremental-compilation-impl/build.gradle.kts index c95e68dbf6a..1b32bd34e76 100644 --- a/compiler/incremental-compilation-impl/build.gradle.kts +++ b/compiler/incremental-compilation-impl/build.gradle.kts @@ -26,7 +26,7 @@ dependencies { testCompile(intellijDep()) { includeJars("log4j", "jdom") } testRuntime(project(":kotlin-reflect")) testRuntime(project(":core:descriptors.runtime")) - testRuntime(intellijDep()) { includeJars("lz4-java", rootProject = rootProject) } + testRuntime(intellijDep()) { includeJars("lz4-java", "jna", rootProject = rootProject) } } sourceSets { diff --git a/compiler/incremental-compilation-impl/test/org/jetbrains/kotlin/incremental/parsing/ClassesFqNamesTest.kt b/compiler/incremental-compilation-impl/test/org/jetbrains/kotlin/incremental/parsing/ClassesFqNamesTest.kt index 99036a5efe4..2a65a2baec5 100644 --- a/compiler/incremental-compilation-impl/test/org/jetbrains/kotlin/incremental/parsing/ClassesFqNamesTest.kt +++ b/compiler/incremental-compilation-impl/test/org/jetbrains/kotlin/incremental/parsing/ClassesFqNamesTest.kt @@ -6,7 +6,6 @@ package org.jetbrains.kotlin.incremental.parsing import com.intellij.openapi.util.io.FileUtil -import com.intellij.testFramework.UsefulTestCase import org.jetbrains.kotlin.test.testFramework.KtUsefulTestCase import org.junit.After import org.junit.Before @@ -138,6 +137,6 @@ class ClassesFqNamesTest : KtUsefulTestCase() { val expected = expectedClasses.sorted().joinToString("\n") val actual = classesFqNames(setOf(testKt)).sorted().joinToString("\n") - UsefulTestCase.assertEquals(expected, actual) + assertEquals(expected, actual) } -} \ No newline at end of file +} diff --git a/compiler/tests-common/build.gradle.kts b/compiler/tests-common/build.gradle.kts index ca417c987e9..10adacd5170 100644 --- a/compiler/tests-common/build.gradle.kts +++ b/compiler/tests-common/build.gradle.kts @@ -57,13 +57,6 @@ dependencies { testCompileOnly(project(":kotlin-reflect-api")) testCompileOnly(toolsJar()) testCompileOnly(intellijCoreDep()) { includeJars("intellij-core") } - testCompile(intellijDep()) { - includeJars( - "testFramework", // needed for parsing tests - "platform-ide-util-io", // needed for CLI process utils in KotlinIntegrationTestBase - rootProject = rootProject - ) - } Platform[202] { testCompile(intellijDep()) { includeJars("intellij-deps-fastutil-8.3.1-1") } } @@ -77,6 +70,7 @@ dependencies { "asm-all", "log4j", "jdom", + "jna", rootProject = rootProject ) isTransitive = false diff --git a/compiler/tests-common/tests/com/intellij/execution/ExecutionException.java b/compiler/tests-common/tests/com/intellij/execution/ExecutionException.java new file mode 100644 index 00000000000..e405761d5d9 --- /dev/null +++ b/compiler/tests-common/tests/com/intellij/execution/ExecutionException.java @@ -0,0 +1,29 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package com.intellij.execution; + +import com.intellij.openapi.util.NlsContexts; + +import java.io.IOException; + +public class ExecutionException extends Exception { + public ExecutionException(final @NlsContexts.DialogMessage String s) { + super(s); + } + + public ExecutionException(final Throwable cause) { + super(cause == null ? null : cause.getMessage(), cause); + } + + public ExecutionException(final @NlsContexts.DialogMessage String s, Throwable cause) { + super(s, cause); + } + + public IOException toIOException() { + final Throwable cause = getCause(); + return cause instanceof IOException ? (IOException)cause : new IOException(this); + } +} diff --git a/compiler/tests-common/tests/com/intellij/execution/IllegalEnvVarException.java b/compiler/tests-common/tests/com/intellij/execution/IllegalEnvVarException.java new file mode 100644 index 00000000000..e4aa4d84faf --- /dev/null +++ b/compiler/tests-common/tests/com/intellij/execution/IllegalEnvVarException.java @@ -0,0 +1,14 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package com.intellij.execution; + +import com.intellij.openapi.util.NlsContexts; + +public class IllegalEnvVarException extends ExecutionException { + public IllegalEnvVarException(String message) { + super(message); + } +} diff --git a/compiler/tests-common/tests/com/intellij/execution/configurations/GeneralCommandLine.java b/compiler/tests-common/tests/com/intellij/execution/configurations/GeneralCommandLine.java new file mode 100644 index 00000000000..7f3accf3574 --- /dev/null +++ b/compiler/tests-common/tests/com/intellij/execution/configurations/GeneralCommandLine.java @@ -0,0 +1,519 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package com.intellij.execution.configurations; + +import com.intellij.diagnostic.LoadingState; +import com.intellij.execution.CommandLineUtil; +import com.intellij.execution.ExecutionException; +import com.intellij.execution.IllegalEnvVarException; +import com.intellij.execution.Platform; +import com.intellij.execution.process.ProcessNotCreatedException; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.NlsSafe; +import com.intellij.openapi.util.SystemInfoRt; +import com.intellij.openapi.util.UserDataHolder; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.encoding.EncodingManager; +import com.intellij.util.EnvironmentUtil; +import com.intellij.util.containers.CollectionFactory; +import com.intellij.util.containers.FastUtilHashingStrategies; +import com.intellij.util.execution.ParametersListUtil; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenCustomHashMap; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.*; + +public class GeneralCommandLine implements UserDataHolder { + private static final Logger LOG = Logger.getInstance(GeneralCommandLine.class); + + /** + * Determines the scope of a parent environment passed to a child process. + *

+ * {@code NONE} means a child process will receive an empty environment.
+ * {@code SYSTEM} will provide it with the same environment as an IDE.
+ * {@code CONSOLE} provides the child with a similar environment as if it was launched from, well, a console. + * On OS X, a console environment is simulated (see {@link EnvironmentUtil#getEnvironmentMap()} for reasons it's needed + * and details on how it works). On Windows and Unix hosts, this option is no different from {@code SYSTEM} + * since there is no drastic distinction in environment between GUI and console apps. + */ + public enum ParentEnvironmentType {NONE, SYSTEM, CONSOLE} + + private String myExePath; + private File myWorkDirectory; + private final Map myEnvParams = new MyMap(); + private ParentEnvironmentType myParentEnvironmentType = ParentEnvironmentType.CONSOLE; + private final ParametersList myProgramParams = new ParametersList(); + private Charset myCharset = defaultCharset(); + private boolean myRedirectErrorStream; + private File myInputFile; + private Map myUserData; + + public GeneralCommandLine() { } + + public GeneralCommandLine(@NonNls String... command) { + this(Arrays.asList(command)); + } + + public GeneralCommandLine(@NonNls @NotNull List command) { + int size = command.size(); + if (size > 0) { + setExePath(command.get(0)); + if (size > 1) { + addParameters(command.subList(1, size)); + } + } + } + + protected GeneralCommandLine(@NotNull GeneralCommandLine original) { + myExePath = original.myExePath; + myWorkDirectory = original.myWorkDirectory; + myEnvParams.putAll(original.myEnvParams); + myParentEnvironmentType = original.myParentEnvironmentType; + original.myProgramParams.copyTo(myProgramParams); + myCharset = original.myCharset; + myRedirectErrorStream = original.myRedirectErrorStream; + myInputFile = original.myInputFile; + myUserData = null; // user data should not be copied over + } + + private static Charset defaultCharset() { + return LoadingState.COMPONENTS_LOADED.isOccurred() ? EncodingManager.getInstance().getDefaultConsoleEncoding() : Charset.defaultCharset(); + } + + public @NotNull @NlsSafe String getExePath() { + return myExePath; + } + + public @NotNull GeneralCommandLine withExePath(@NotNull @NlsSafe String exePath) { + myExePath = exePath.trim(); + return this; + } + + public void setExePath(@NotNull @NlsSafe String exePath) { + withExePath(exePath); + } + + public File getWorkDirectory() { + return myWorkDirectory; + } + + @NotNull + public GeneralCommandLine withWorkDirectory(@Nullable String path) { + return withWorkDirectory(path != null ? new File(path) : null); + } + + @NotNull + public GeneralCommandLine withWorkDirectory(@Nullable File workDirectory) { + myWorkDirectory = workDirectory; + return this; + } + + public void setWorkDirectory(@Nullable String path) { + withWorkDirectory(path); + } + + public void setWorkDirectory(@Nullable File workDirectory) { + withWorkDirectory(workDirectory); + } + + /** + * Note: the map returned is forgiving to passing null values into putAll(). + */ + @NotNull + public Map getEnvironment() { + return myEnvParams; + } + + @NotNull + public GeneralCommandLine withEnvironment(@Nullable Map environment) { + if (environment != null) { + getEnvironment().putAll(environment); + } + return this; + } + + @NotNull + public GeneralCommandLine withEnvironment(@NonNls @NotNull String key, @NonNls @NotNull String value) { + getEnvironment().put(key, value); + return this; + } + + public boolean isPassParentEnvironment() { + return myParentEnvironmentType != ParentEnvironmentType.NONE; + } + + /** @deprecated use {@link #withParentEnvironmentType(ParentEnvironmentType)} */ + @Deprecated + public void setPassParentEnvironment(boolean passParentEnvironment) { + withParentEnvironmentType(passParentEnvironment ? ParentEnvironmentType.CONSOLE : ParentEnvironmentType.NONE); + } + + @NotNull + public ParentEnvironmentType getParentEnvironmentType() { + return myParentEnvironmentType; + } + + @NotNull + public GeneralCommandLine withParentEnvironmentType(@NotNull ParentEnvironmentType type) { + myParentEnvironmentType = type; + return this; + } + + /** + * Returns an environment that will be inherited by a child process. + * @see #getEffectiveEnvironment() + */ + @NotNull + public Map getParentEnvironment() { + switch (myParentEnvironmentType) { + case SYSTEM: + return System.getenv(); + case CONSOLE: + return EnvironmentUtil.getEnvironmentMap(); + default: + return Collections.emptyMap(); + } + } + + /** + * Returns an environment as seen by a child process, + * that is the {@link #getEnvironment() environment} merged with the {@link #getParentEnvironment() parent} one. + */ + @NotNull + public Map getEffectiveEnvironment() { + Map env = new MyMap(); + setupEnvironment(env); + return env; + } + + public void addParameters(@NonNls String... parameters) { + withParameters(parameters); + } + + public void addParameters(@NotNull List parameters) { + withParameters(parameters); + } + + @NotNull + public GeneralCommandLine withParameters(@NotNull @NonNls String... parameters) { + for (String parameter : parameters) addParameter(parameter); + return this; + } + + @NotNull + public GeneralCommandLine withParameters(@NotNull List parameters) { + for (String parameter : parameters) addParameter(parameter); + return this; + } + + public void addParameter(@NonNls @NotNull String parameter) { + myProgramParams.add(parameter); + } + + @NotNull + public ParametersList getParametersList() { + return myProgramParams; + } + + @NotNull + public Charset getCharset() { + return myCharset; + } + + @NotNull + public GeneralCommandLine withCharset(@NotNull Charset charset) { + myCharset = charset; + return this; + } + + public void setCharset(@NotNull Charset charset) { + withCharset(charset); + } + + public boolean isRedirectErrorStream() { + return myRedirectErrorStream; + } + + @NotNull + public GeneralCommandLine withRedirectErrorStream(boolean redirectErrorStream) { + myRedirectErrorStream = redirectErrorStream; + return this; + } + + public void setRedirectErrorStream(boolean redirectErrorStream) { + withRedirectErrorStream(redirectErrorStream); + } + + public File getInputFile() { + return myInputFile; + } + + @NotNull + public GeneralCommandLine withInput(@Nullable File file) { + myInputFile = file; + return this; + } + + /** + * 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. + */ + @NlsSafe + @NotNull + 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. + */ + @NotNull + public String getCommandLineString(@Nullable String exeName) { + return ParametersListUtil.join(getCommandLineList(exeName)); + } + + @NotNull + public List getCommandLineList(@Nullable String exeName) { + List<@NlsSafe String> commands = new ArrayList<>(); + String exe = StringUtil.notNullize(exeName, StringUtil.notNullize(myExePath, "")); + commands.add(exe); + + commands.addAll(myProgramParams.getList()); + return commands; + } + + /** + * Prepares command (quotes and escapes all arguments) and returns it as a newline-separated list. + * + * @return command as a newline-separated list. + * @see #getPreparedCommandLine(Platform) + */ + @NotNull + public String getPreparedCommandLine() { + return getPreparedCommandLine(Platform.current()); + } + + /** + * Prepares command (quotes and escapes all arguments) and returns it as a newline-separated list + * (suitable e.g. for passing in an environment variable). + * + * @param platform a target platform + * @return command as a newline-separated list. + */ + @NotNull + public String getPreparedCommandLine(@NotNull Platform platform) { + String exePath = myExePath != null ? myExePath : ""; + return StringUtil.join(prepareCommandLine(exePath, myProgramParams.getList(), platform), "\n"); + } + + @NotNull + protected List prepareCommandLine(@NotNull String command, @NotNull List parameters, @NotNull Platform platform) { + return CommandLineUtil.toCommandLine(command, parameters, platform); + } + + @NotNull + public Process createProcess() throws ExecutionException { + if (LOG.isDebugEnabled()) { + LOG.debug("Executing [" + getCommandLineString() + "]"); + LOG.debug(" environment: " + myEnvParams + " (+" + myParentEnvironmentType + ")"); + LOG.debug(" charset: " + myCharset); + } + + List commands = validateAndPrepareCommandLine(); + try { + return startProcess(commands); + } + catch (IOException e) { + LOG.debug(e); + throw new ProcessNotCreatedException(e.getMessage(), e, this); + } + } + + public @NotNull ProcessBuilder toProcessBuilder() throws ExecutionException { + List escapedCommands = validateAndPrepareCommandLine(); + return toProcessBuilderInternal(escapedCommands); + } + + @NotNull + private List validateAndPrepareCommandLine() throws ExecutionException { + try { + if (myWorkDirectory != null) { + if (!myWorkDirectory.exists()) { + throw new ExecutionException(myWorkDirectory + " does not exist"); + } + if (!myWorkDirectory.isDirectory()) { + throw new ExecutionException(myWorkDirectory + " is not a directory"); + } + } + + if (StringUtil.isEmptyOrSpaces(myExePath)) { + throw new ExecutionException("executable not specified"); + } + } + catch (ExecutionException e) { + LOG.debug(e); + throw e; + } + + for (Map.Entry entry : myEnvParams.entrySet()) { + String name = entry.getKey(); + String value = entry.getValue(); + if (!EnvironmentUtil.isValidName(name)) throw new IllegalEnvVarException("run.configuration.invalid.env.name: " + name); + if (!EnvironmentUtil.isValidValue(value)) throw new IllegalEnvVarException("run.configuration.invalid.env.value: " + name + ", " + value); + } + + String exePath = myExePath; + if (SystemInfoRt.isMac && myParentEnvironmentType == ParentEnvironmentType.CONSOLE && exePath.indexOf(File.separatorChar) == -1) { + String systemPath = System.getenv("PATH"); + String shellPath = EnvironmentUtil.getValue("PATH"); + if (!Objects.equals(systemPath, shellPath)) { + File exeFile = PathEnvironmentVariableUtil.findInPath(myExePath, shellPath, null); + if (exeFile != null) { + LOG.debug(exePath + " => " + exeFile); + exePath = exeFile.getPath(); + } + } + } + + return prepareCommandLine(exePath, myProgramParams.getList(), Platform.current()); + } + + /** + * @implNote for subclasses: + *

On Windows the escapedCommands argument must never be modified or augmented in any way. + * Windows command line handling is extremely fragile and vague, and the exact escaping of a particular argument may vary + * depending on values of the preceding arguments. + *

+     *   [foo] [^] -> [foo] [^^]
+     * 
+ * but: + *
+     *   [foo] ["] [^] -> [foo] [\"] ["^"]
+     * 
+ * Notice how the last parameter escaping changes after prepending another argument.

+ *

If you need to alter the command line passed in, override the {@link #prepareCommandLine(String, List, Platform)} method instead.

+ */ + @NotNull + protected Process startProcess(@NotNull List escapedCommands) throws IOException { + if (LOG.isDebugEnabled()) { + LOG.debug("Building process with commands: " + escapedCommands); + } + return toProcessBuilderInternal(escapedCommands).start(); + } + + // This is caused by the fact there are external usages overriding startProcess(List). + // Ideally, it should have been startProcess(ProcessBuilder), and the design would be more straightforward. + @NotNull + private ProcessBuilder toProcessBuilderInternal(@NotNull List escapedCommands) { + ProcessBuilder builder = new ProcessBuilder(escapedCommands); + setupEnvironment(builder.environment()); + builder.directory(myWorkDirectory); + builder.redirectErrorStream(myRedirectErrorStream); + if (myInputFile != null) { + builder.redirectInput(ProcessBuilder.Redirect.from(myInputFile)); + } + return buildProcess(builder); + } + + /** + * Executed with pre-filled ProcessBuilder as the param and + * gives the last chance to configure starting process + * parameters before a process is started + * @param builder filed ProcessBuilder + */ + @NotNull + protected ProcessBuilder buildProcess(@NotNull ProcessBuilder builder) { + return builder; + } + + protected void setupEnvironment(@NotNull Map environment) { + environment.clear(); + + if (myParentEnvironmentType != ParentEnvironmentType.NONE) { + environment.putAll(getParentEnvironment()); + } + + if (SystemInfoRt.isUnix) { + File workDirectory = getWorkDirectory(); + if (workDirectory != null) { + environment.put("PWD", FileUtil.toSystemDependentName(workDirectory.getAbsolutePath())); + } + } + + if (!myEnvParams.isEmpty()) { + if (SystemInfoRt.isWindows) { + Map envVars = CollectionFactory.createCaseInsensitiveStringMap(); + envVars.putAll(environment); + envVars.putAll(myEnvParams); + environment.clear(); + environment.putAll(envVars); + } + else { + environment.putAll(myEnvParams); + } + } + } + + /** + * Normally, double quotes in parameters are escaped, so they arrive to a called program as-is. + * But some commands (e.g. {@code 'cmd /c start "title" ...'}) should get their quotes non-escaped. + * Wrapping a parameter by this method (instead of using quotes) will do exactly this. + * + * @see com.intellij.execution.util.ExecUtil#getTerminalCommand(String, String) + */ + @NotNull + public static String inescapableQuote(@NotNull String parameter) { + return CommandLineUtil.specialQuote(parameter); + } + + @Override + public String toString() { + return myExePath + " " + myProgramParams; + } + + @Nullable + @Override + public T getUserData(@NotNull Key key) { + if (myUserData == null) return null; + @SuppressWarnings("unchecked") T t = (T)myUserData.get(key); + return t; + } + + @Override + public void putUserData(@NotNull Key key, @Nullable T value) { + if (myUserData == null) { + if (value == null) return; + myUserData = new HashMap<>(); + } + myUserData.put(key, value); + } + + private static final class MyMap extends Object2ObjectOpenCustomHashMap { + private MyMap() { + super(FastUtilHashingStrategies.getStringStrategy(!SystemInfoRt.isWindows)); + } + + @Override + public void putAll(Map map) { + if (map != null) { + super.putAll(map); + } + } + } +} diff --git a/compiler/tests-common/tests/com/intellij/execution/configurations/ParametersList.java b/compiler/tests-common/tests/com/intellij/execution/configurations/ParametersList.java new file mode 100644 index 00000000000..631b31268b4 --- /dev/null +++ b/compiler/tests-common/tests/com/intellij/execution/configurations/ParametersList.java @@ -0,0 +1,391 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package com.intellij.execution.configurations; + +import com.intellij.openapi.application.Application; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.PathMacros; +import com.intellij.openapi.util.Condition; +import com.intellij.openapi.util.NlsSafe; +import com.intellij.openapi.util.NotNullLazyValue; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.util.ArrayUtilRt; +import com.intellij.util.EnvironmentUtil; +import com.intellij.util.ObjectUtils; +import com.intellij.util.SmartList; +import com.intellij.util.containers.CollectionFactory; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.JBIterable; +import com.intellij.util.execution.ParametersListUtil; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.TestOnly; + +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public final class ParametersList implements Cloneable { + private static final Pattern PROPERTY_PATTERN = Pattern.compile("-D(\\S+?)(?:=(.+))?"); + private static final Pattern MACRO_PATTERN = Pattern.compile("\\$\\{([^}]+)}"); + + private final List myParameters = new ArrayList<>(); + private final List myGroups = new SmartList<>(); + private final NotNullLazyValue> myMacroMap = NotNullLazyValue.createValue(ParametersList::computeMacroMap); + + public boolean hasParameter(@NotNull @NonNls String parameter) { + return myParameters.contains(parameter); + } + + public boolean hasProperty(@NotNull @NonNls String propertyName) { + return getPropertyValue(propertyName) != null; + } + + @Nullable + public String getPropertyValue(@NotNull @NonNls String propertyName) { + String exact = "-D" + propertyName; + String prefix = "-D" + propertyName + "="; + int index = indexOfParameter(o -> o.equals(exact) || o.startsWith(prefix)); + if (index < 0) return null; + String str = myParameters.get(index); + return str.length() == exact.length() ? "" : str.substring(prefix.length()); + } + + @NotNull + public Map getProperties() { + return getProperties(""); + } + + @NotNull + public Map getProperties(@NonNls String valueIfMissing) { + Map result = new LinkedHashMap<>(); + JBIterable matchers = JBIterable.from(myParameters).map(PROPERTY_PATTERN::matcher).filter(Matcher::matches); + for (Matcher matcher : matchers) { + result.put(matcher.group(1), StringUtil.notNullize(matcher.group(2), valueIfMissing)); + } + return result; + } + + @NotNull + public String getParametersString() { + return join(getList()); + } + + public String [] getArray() { + return ArrayUtilRt.toStringArray(getList()); + } + + @NotNull + public List getList() { + if (myGroups.isEmpty()) { + return Collections.unmodifiableList(myParameters); + } + + List params = new ArrayList<>(myParameters); + for (ParamsGroup group : myGroups) { + params.addAll(group.getParameters()); + } + return Collections.unmodifiableList(params); + } + + public void clearAll() { + myParameters.clear(); + myGroups.clear(); + } + + public void prepend(@NotNull @NonNls String parameter) { + addAt(0, parameter); + } + + public void prependAll(@NonNls String ... parameter) { + addAll(parameter); + Collections.rotate(myParameters, parameter.length); + } + + public void addParametersString(@Nullable @NonNls String parameters) { + if (StringUtil.isEmptyOrSpaces(parameters)) return; + for (String param : parse(parameters)) { + add(param); + } + } + + public void add(@Nullable @NonNls String parameter) { + if (parameter == null) return; + myParameters.add(expandMacros(parameter)); + } + + @NotNull + public ParamsGroup addParamsGroup(@NotNull @NonNls String groupId) { + return addParamsGroup(new ParamsGroup(groupId)); + } + + @NotNull + public ParamsGroup addParamsGroup(@NotNull ParamsGroup group) { + myGroups.add(group); + return group; + } + + @NotNull + public ParamsGroup addParamsGroupAt(int index, @NotNull ParamsGroup group) { + myGroups.add(index, group); + return group; + } + + @NotNull + public ParamsGroup addParamsGroupAt(int index, @NotNull @NonNls String groupId) { + ParamsGroup group = new ParamsGroup(groupId); + myGroups.add(index, group); + return group; + } + + public int getParamsGroupsCount() { + return myGroups.size(); + } + + @NotNull + public List getParameters() { + return Collections.unmodifiableList(myParameters); + } + + @NotNull + public List getParamsGroups() { + return Collections.unmodifiableList(myGroups); + } + + @NotNull + public ParamsGroup getParamsGroupAt(int index) { + return myGroups.get(index); + } + + @Nullable + public ParamsGroup getParamsGroup(@NotNull @NonNls String name) { + for (ParamsGroup group : myGroups) { + if (name.equals(group.getId())) return group; + } + return null; + } + + @Nullable + public ParamsGroup removeParamsGroup(int index) { + return myGroups.remove(index); + } + + public void addAt(int index, @NotNull @NonNls String parameter) { + myParameters.add(index, expandMacros(parameter)); + } + + /** + * Keeps the {@code } property if defined; or defines it with {@code System.getProperty()} as a value if present. + */ + public void defineSystemProperty(@NotNull @NonNls String propertyName) { + defineProperty(propertyName, System.getProperty(propertyName)); + } + + /** + * Keeps the {@code } property if defined; otherwise appends the new one ignoring null values. + */ + public void defineProperty(@NotNull @NonNls String propertyName, @Nullable @NonNls String propertyValue) { + if (propertyValue == null) return; + @NlsSafe String exact = "-D" + propertyName; + @NlsSafe String prefix = "-D" + propertyName + "="; + int index = indexOfParameter(o -> o.equals(exact) || o.startsWith(prefix)); + if (index > -1) return; + String value = propertyValue.isEmpty() ? exact : prefix + expandMacros(propertyValue); + myParameters.add(value); + } + + /** + * Adds {@code -D} to the list; replaces the value of the last property if defined. + */ + public void addProperty(@NotNull @NonNls String propertyName) { + @NlsSafe String exact = "-D" + propertyName; + @NlsSafe String prefix = "-D" + propertyName + "="; + replaceOrAddAt(exact, myParameters.size(), o -> o.equals(exact) || o.startsWith(prefix)); + } + + /** + * Adds {@code -D=} to the list ignoring null values; + * replaces the value of the last property if defined. + */ + public void addProperty(@NotNull @NonNls String propertyName, @Nullable @NonNls String propertyValue) { + if (propertyValue == null) return; + @NlsSafe String exact = "-D" + propertyName; + @NlsSafe String prefix = "-D" + propertyName + "="; + String value = propertyValue.isEmpty() ? exact : prefix + expandMacros(propertyValue); + replaceOrAddAt(value, myParameters.size(), o -> o.equals(exact) || o.startsWith(prefix)); + } + + /** + * Adds {@code -D=} to the list ignoring null, empty and spaces-only values; + * replaces the value of the last property if defined. + */ + public void addNotEmptyProperty(@NotNull @NonNls String propertyName, @Nullable @NonNls String propertyValue) { + if (StringUtil.isEmptyOrSpaces(propertyValue)) return; + addProperty(propertyName, propertyValue); + } + + /** + * Replaces the last parameter that starts with the {@code } with {@code }; + * otherwise appends {@code } to the list. + */ + public void replaceOrAppend(@NotNull @NonNls String parameterPrefix, @NotNull @NonNls String replacement) { + replaceOrAddAt(expandMacros(replacement), myParameters.size(), o -> o.startsWith(parameterPrefix)); + } + + /** + * Replaces the last parameter that starts with the {@code } with {@code }; + * otherwise prepends this list with {@code }. + */ + public void replaceOrPrepend(@NotNull @NonNls String parameterPrefix, @NotNull @NonNls String replacement) { + replaceOrAddAt(expandMacros(replacement), 0, o -> o.startsWith(parameterPrefix)); + } + + private void replaceOrAddAt(@NotNull String replacement, + int position, + @NotNull Condition existingCondition) { + int index = indexOfParameter(existingCondition); + boolean setNewValue = StringUtil.isNotEmpty(replacement); + if (index > -1 && setNewValue) { + myParameters.set(index, replacement); + } + else if (index > -1) { + myParameters.remove(index); + } + else if (setNewValue) { + myParameters.add(position, replacement); + } + } + + private int indexOfParameter(@NotNull @NonNls Condition condition) { + return ContainerUtil.lastIndexOf(myParameters, condition); + } + + public void set(int ind, @NotNull @NonNls String value) { + myParameters.set(ind, value); + } + + public String get(int ind) { + return myParameters.get(ind); + } + + @Nullable + public String getLast() { + return myParameters.size() > 0 ? myParameters.get(myParameters.size() - 1) : null; + } + + public void add(@NotNull @NonNls String name, @NotNull @NonNls String value) { + myParameters.add(name); // do not expand macros in parameter name + add(value); + } + + public void addAll(@NonNls String ... parameters) { + addAll(Arrays.asList(parameters)); + } + + public void addAll(@NotNull @NonNls List parameters) { + // Don't use myParameters.addAll(parameters) , it does not call expandMacros(parameter) + for (String parameter : parameters) { + add(parameter); + } + } + + /** @noinspection MethodDoesntCallSuperMethod*/ + @Override + public ParametersList clone() { + return copyTo(new ParametersList()); + } + + @NotNull + public ParametersList copyTo(@NotNull ParametersList target) { + target.myParameters.addAll(myParameters); + for (ParamsGroup group : myGroups) { + target.myGroups.add(group.clone()); + } + return target; + } + + /** + * @see ParametersListUtil#join(List) + */ + @NotNull + public static String join(@NotNull @NonNls List parameters) { + return ParametersListUtil.join(parameters); + } + + /** + * @see ParametersListUtil#join(List) + */ + @NotNull + public static String join(@NonNls String ... parameters) { + return ParametersListUtil.join(parameters); + } + + /** + * @see ParametersListUtil#parseToArray(String) + */ + public static String [] parse(@NotNull @NonNls String string) { + return ParametersListUtil.parseToArray(string); + } + + @NotNull + public String expandMacros(@NotNull @NonNls String text) { + int start = text.indexOf("${"); + if (start < 0) return text; + Map macroMap = myMacroMap.getValue(); + Matcher matcher = MACRO_PATTERN.matcher(text); + StringBuilder sb = null; + while (matcher.find(start)) { + String value = macroMap.get(matcher.group(1)); + if (value != null) { + if (sb == null) sb = new StringBuilder(2 * text.length()).append(text, 0, matcher.start()); + else sb.append(text, start, matcher.start()); + sb.append(value); + start = matcher.end(); + } + else { + if (sb != null) sb.append(text, start, matcher.start() + 2); + start = matcher.start() + 2; + } + } + return sb == null ? text : sb.append(text, start, text.length()).toString(); + } + + private static Map ourTestMacros; + + @TestOnly + public static void setTestMacros(@Nullable @NonNls Map testMacros) { + ourTestMacros = testMacros; + } + + @NotNull + private static Map computeMacroMap() { + // ApplicationManager.getApplication() will return null if executed in ParameterListTest + Application application = ApplicationManager.getApplication(); + if (application == null || application.isUnitTestMode() && ourTestMacros != null) { + return ObjectUtils.notNull(ourTestMacros, Collections.emptyMap()); + } + + Map map = CollectionFactory.createCaseInsensitiveStringMap(); + Map pathMacros = PathMacros.getInstance().getUserMacros(); + if (!pathMacros.isEmpty()) { + for (String name : pathMacros.keySet()) { + ContainerUtil.putIfNotNull(name, pathMacros.get(name), map); + } + } + Map env = EnvironmentUtil.getEnvironmentMap(); + for (String name : env.keySet()) { + ContainerUtil.putIfAbsent(name, env.get(name), map); + } + return map; + } + + @NonNls + @Override + public String toString() { + return myParameters + (myGroups.isEmpty() ? "" : " and " + myGroups); + } +} diff --git a/compiler/tests-common/tests/com/intellij/execution/configurations/ParamsGroup.java b/compiler/tests-common/tests/com/intellij/execution/configurations/ParamsGroup.java new file mode 100644 index 00000000000..474cfe1050d --- /dev/null +++ b/compiler/tests-common/tests/com/intellij/execution/configurations/ParamsGroup.java @@ -0,0 +1,73 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package com.intellij.execution.configurations; + +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public final class ParamsGroup implements Cloneable { + + private final String myGroupId; + private final ParametersList myParamList; + + public ParamsGroup(@NotNull String groupId) { + this(groupId, new ParametersList()); + } + + private ParamsGroup(@NotNull String groupId, @NotNull ParametersList paramList) { + myGroupId = groupId; + myParamList = paramList; + } + + @NotNull + public String getId() { + return myGroupId; + } + + public void addParameter(@NotNull String parameter) { + myParamList.add(parameter); + } + + public void addParameterAt(int index, @NotNull String parameter) { + myParamList.addAt(index, parameter); + } + + public void addParameters(@NotNull String ... parameters) { + for (String parameter : parameters) { + addParameter(parameter); + } + } + + public void addParameters(@NotNull List parameters) { + for (String parameter : parameters) { + addParameter(parameter); + } + } + + public void addParametersString(@NotNull String parametersString) { + addParameters(ParametersList.parse(parametersString)); + } + + public List getParameters() { + return myParamList.getList(); + } + + public ParametersList getParametersList() { + return myParamList; + } + + /** @noinspection MethodDoesntCallSuperMethod*/ + @Override + public ParamsGroup clone() { + return new ParamsGroup(myGroupId, myParamList.clone()); + } + + @Override + public String toString() { + return myGroupId + ":" + myParamList; + } +} diff --git a/compiler/tests-common/tests/com/intellij/execution/configurations/PathEnvironmentVariableUtil.java b/compiler/tests-common/tests/com/intellij/execution/configurations/PathEnvironmentVariableUtil.java new file mode 100644 index 00000000000..63a5bfebb5b --- /dev/null +++ b/compiler/tests-common/tests/com/intellij/execution/configurations/PathEnvironmentVariableUtil.java @@ -0,0 +1,176 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package com.intellij.execution.configurations; + +import com.intellij.openapi.util.SystemInfo; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.util.EnvironmentUtil; +import com.intellij.util.SmartList; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.io.FileFilter; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +public final class PathEnvironmentVariableUtil { + + private static final String PATH = "PATH"; + + private PathEnvironmentVariableUtil() { } + + /** + * Finds an executable file with the specified base name, that is located in a directory + * listed in PATH environment variable. + * + * @param fileBaseName file base name + * @return {@link File} instance or null if not found + */ + @Nullable + public static File findInPath(@NotNull @NonNls String fileBaseName) { + return findInPath(fileBaseName, null); + } + + /** + * Finds an executable file with the specified base name, that is located in a directory + * listed in PATH environment variable and is accepted by filter. + * + * @param fileBaseName file base name + * @param filter exe file filter + * @return {@link File} instance or null if not found + */ + @Nullable + public static File findInPath(@NotNull String fileBaseName, @Nullable FileFilter filter) { + return findInPath(fileBaseName, getPathVariableValue(), filter); + } + + /** + * Finds an executable file with the specified base name, that is located in a directory + * listed in the passed PATH environment variable value and is accepted by filter. + * + * @param fileBaseName file base name + * @param pathVariableValue value of PATH environment variable + * @param filter exe file filter + * @return {@link File} instance or null if not found + */ + @Nullable + public static File findInPath(@NotNull String fileBaseName, @Nullable String pathVariableValue, @Nullable FileFilter filter) { + List exeFiles = findExeFilesInPath(true, filter, pathVariableValue, fileBaseName); + return ContainerUtil.getFirstItem(exeFiles); + } + + /** + * Finds all executable files with the specified base name, that are located in directories + * from PATH environment variable. + * + * @param fileBaseName file base name + * @return file list + */ + @NotNull + public static List findAllExeFilesInPath(@NotNull String fileBaseName) { + return findAllExeFilesInPath(fileBaseName, null); + } + + @NotNull + public static List findAllExeFilesInPath(@NotNull String fileBaseName, @Nullable FileFilter filter) { + return findExeFilesInPath(false, filter, getPathVariableValue(), fileBaseName); + } + + @NotNull + private static List findExeFilesInPath(boolean stopAfterFirstMatch, + @Nullable FileFilter filter, + @Nullable String pathEnvVarValue, + String... fileBaseNames) { + if (pathEnvVarValue == null) { + return Collections.emptyList(); + } + List result = new SmartList<>(); + List dirPaths = getPathDirs(pathEnvVarValue); + for (String dirPath : dirPaths) { + File dir = new File(dirPath); + if (dir.isAbsolute() && dir.isDirectory()) { + for (String fileBaseName : fileBaseNames) { + File exeFile = new File(dir, fileBaseName); + if (exeFile.isFile() && exeFile.canExecute()) { + if (filter == null || filter.accept(exeFile)) { + result.add(exeFile); + if (stopAfterFirstMatch) { + return result; + } + } + } + } + } + } + return result; + } + + @NotNull + public static List getPathDirs(@NotNull String pathEnvVarValue) { + return StringUtil.split(pathEnvVarValue, File.pathSeparator, true, true); + } + + @NotNull + public static List getWindowsExecutableFileExtensions() { + if (SystemInfo.isWindows) { + String allExtensions = System.getenv("PATHEXT"); + if (allExtensions != null) { + Collection extensions = StringUtil.split(allExtensions, ";", true, true); + extensions = ContainerUtil.filter(extensions, s -> !StringUtil.isEmpty(s) && s.startsWith(".")); + return ContainerUtil.map2List(extensions, s -> StringUtil.toLowerCase(s)); + } + } + return Collections.emptyList(); + } + + @NotNull + public static String findExecutableInWindowsPath(@NotNull String exePath) { + return findExecutableInWindowsPath(exePath, exePath); + } + + @Contract("_, !null -> !null") + public static String findExecutableInWindowsPath(@NotNull String exePath, @Nullable String defaultPath) { + if (SystemInfo.isWindows) { + if (!StringUtil.containsChar(exePath, '/') && !StringUtil.containsChar(exePath, '\\')) { + List executableFileExtensions = getWindowsExecutableFileExtensions(); + + String[] baseNames = ContainerUtil.map2Array(executableFileExtensions, String.class, s -> exePath + s); + List exeFiles = findExeFilesInPath(true, null, getPathVariableValue(), baseNames); + File foundFile = ContainerUtil.getFirstItem(exeFiles); + if (foundFile != null) { + return foundFile.getAbsolutePath(); + } + } + } + return defaultPath; + } + + /** + * Retrieves the value of PATH environment variable + */ + @Nullable + public static String getPathVariableValue() { + return EnvironmentUtil.getValue(PATH); + } + + @Nullable + public static File findExecutableInPathOnAnyOS(@NotNull @NonNls String fileBaseName) { + if (SystemInfo.isWindows) { + String[] fileNames = ContainerUtil.map2Array(getWindowsExecutableFileExtensions(), String.class, + (String extension) -> fileBaseName + extension); + List exeFiles = findExeFilesInPath(true, null, getPathVariableValue(), fileNames); + return ContainerUtil.getFirstItem(exeFiles); + } + else { + return findInPath(fileBaseName); + } + } +} diff --git a/compiler/tests-common/tests/com/intellij/execution/process/OSProcessHandler.java b/compiler/tests-common/tests/com/intellij/execution/process/OSProcessHandler.java new file mode 100644 index 00000000000..bed5cecb423 --- /dev/null +++ b/compiler/tests-common/tests/com/intellij/execution/process/OSProcessHandler.java @@ -0,0 +1,255 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package com.intellij.execution.process; + +import com.intellij.execution.ExecutionException; +import com.intellij.execution.configurations.GeneralCommandLine; +import com.intellij.openapi.application.Application; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.progress.EmptyProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.progress.Task; +import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.util.ExceptionUtil; +import com.intellij.util.io.BaseDataReader; +import com.intellij.util.io.BaseOutputReader; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +public class OSProcessHandler extends BaseOSProcessHandler { + private static final Logger LOG = Logger.getInstance(OSProcessHandler.class); + private static final Set REPORTED_EXECUTIONS = Collections.newSetFromMap(new ConcurrentHashMap<>()); + private static final long ALLOWED_TIMEOUT_THRESHOLD = 10; + + private static final Key> DELETE_FILES_ON_TERMINATION = Key.create("OSProcessHandler.FileToDelete"); + + private final boolean myHasErrorStream; + @NotNull + private final ModalityState myModality; + private boolean myHasPty; + private final Set myFilesToDelete; + + public OSProcessHandler(@NotNull GeneralCommandLine commandLine) throws ExecutionException { + super(startProcess(commandLine), commandLine.getCommandLineString(), commandLine.getCharset()); + + setHasPty(isPtyProcess(getProcess())); + myHasErrorStream = !commandLine.isRedirectErrorStream(); + myFilesToDelete = commandLine.getUserData(DELETE_FILES_ON_TERMINATION); + myModality = getDefaultModality(); + } + + @NotNull + public static ModalityState getDefaultModality() { + Application app = ApplicationManager.getApplication(); + return app == null ? ModalityState.NON_MODAL : app.getDefaultModalityState(); + } + + /** + * {@code commandLine} must not be empty (for correct thread attribution in the stacktrace) + */ + public OSProcessHandler(@NotNull Process process, /*@NotNull*/ String commandLine, @Nullable Charset charset) { + this(process, commandLine, charset, null); + } + + /** + * {@code commandLine} must not be empty (for correct thread attribution in the stacktrace) + */ + private OSProcessHandler( + @NotNull Process process, /*@NotNull*/ + String commandLine, + @Nullable Charset charset, + @Nullable Set filesToDelete + ) { + super(process, commandLine, charset); + setHasPty(isPtyProcess(process)); + myFilesToDelete = filesToDelete; + myHasErrorStream = true; + myModality = getDefaultModality(); + } + + @NotNull + private static Process startProcess(@NotNull GeneralCommandLine commandLine) throws ExecutionException { + try { + return commandLine.createProcess(); + } + catch (Throwable e) { + deleteTempFiles(commandLine.getUserData(DELETE_FILES_ON_TERMINATION)); + throw e; + } + } + + @Override + public boolean waitFor() { + checkEdtAndReadAction(this); + return super.waitFor(); + } + + @Override + public boolean waitFor(long timeoutInMilliseconds) { + if (timeoutInMilliseconds > ALLOWED_TIMEOUT_THRESHOLD) { + checkEdtAndReadAction(this); + } + return super.waitFor(timeoutInMilliseconds); + } + + /** + * Checks if we are going to wait for {@code processHandler} to finish on EDT or under ReadAction. Logs error if we do so. + *

+ * HOW-TO fix an error from this method: + *
    + *
  • You are on the pooled thread under {@link com.intellij.openapi.application.ReadAction ReadAction}: + *
      + *
    • Synchronous (you need to return execution result or derived information to the caller) - get rid the ReadAction or synchronicity. + * * Move execution part out of the code executed under ReadAction, or make your execution asynchronous - execute on + * * {@link Task.Backgroundable other thread} and invoke a callback.
    • + *
    • Non-synchronous (you don't need to return something) - execute on another thread. E.g. using {@link Task.Backgroundable}
    • + *
    + *
  • + * + *
  • You are on EDT: + *
      + * + *
    • Outside of {@link com.intellij.openapi.application.WriteAction WriteAction}: + *
        + *
      • Synchronous (you need to return execution result or derived information to the caller) - execute under + * {@link ProgressManager#runProcessWithProgressSynchronously(Runnable, String, boolean, com.intellij.openapi.project.Project) modal progress}.
      • + *
      • Non-synchronous (you don't need to return something) - execute on the pooled thread. E.g. using {@link Task.Backgroundable}
      • + *
      + *
    • + * + *
    • Under {@link com.intellij.openapi.application.WriteAction WriteAction} + *
        + *
      • Synchronous (you need to return execution result or derived information to the caller) - get rid the WriteAction or synchronicity. + * Move execution part out of the code executed under WriteAction, or make your execution asynchronous - execute on + * {@link Task.Backgroundable other thread} and invoke a callback.
      • + *
      • Non-synchronous (you don't need to return something) - execute on the pooled thread. E.g. using {@link Task.Backgroundable}
      • + *
      + *
    • + *
+ * + * @apiNote works only in the internal non-headless mode. Reports once per running session per stacktrace per cause. + */ + private static void checkEdtAndReadAction(@NotNull ProcessHandler processHandler) { + Application application = ApplicationManager.getApplication(); + if (application == null || !application.isInternal() || application.isHeadlessEnvironment()) { + return; + } + @NonNls String message = null; + if (application.isDispatchThread()) { + message = "Synchronous execution on EDT: "; + } + else if (application.isReadAccessAllowed()) { + message = "Synchronous execution under ReadAction: "; + } + if (message != null && REPORTED_EXECUTIONS.add(ExceptionUtil.currentStackTrace())) { + LOG.error(message + processHandler + ", see com.intellij.execution.process.OSProcessHandler#checkEdtAndReadAction() Javadoc for resolutions"); + } + } + + private static void deleteTempFiles(Set tempFiles) { + if (tempFiles != null) { + try { + for (File file : tempFiles) { + FileUtil.delete(file); + } + } + catch (Throwable t) { + LOG.error("failed to delete temp. files", t); + } + } + } + + private static boolean isPtyProcess(Process process) { + Class c = process.getClass(); + while (c != null) { + if ("com.pty4j.unix.UnixPtyProcess".equals(c.getName()) || "com.pty4j.windows.WinPtyProcess".equals(c.getName())) { + return true; + } + c = c.getSuperclass(); + } + return false; + } + + @Override + protected void onOSProcessTerminated(int exitCode) { + if (myModality != ModalityState.NON_MODAL) { + ProgressManager.getInstance().runProcess(() -> super.onOSProcessTerminated(exitCode), new EmptyProgressIndicator(myModality)); + } else { + super.onOSProcessTerminated(exitCode); + } + deleteTempFiles(myFilesToDelete); + } + + @Override + protected boolean processHasSeparateErrorStream() { + return myHasErrorStream; + } + + @Override + protected void doDestroyProcess() { + getProcess().destroy(); + } + + /** + *

In case of PTY this process handler will use blocking read because {@link InputStream#available()} doesn't work for Pty4j, and there + * is no reason to "disconnect" leaving PTY alive. See {@link BaseDataReader.SleepingPolicy} for more info.

+ * + *

The value should be set before {@link #startNotify()} invocation. + * It is set by default in case of using GeneralCommandLine based constructor.

+ * + * @param hasPty {@code true} if process is PTY-based. + */ + private void setHasPty(boolean hasPty) { + myHasPty = hasPty; + } + + /** + * Rule of thumb: use {@link BaseOutputReader.Options#BLOCKING} for short-living process that you never want to "disconnect" from. + * See {@link BaseDataReader.SleepingPolicy} for the whole story. + */ + @NotNull + @Override + protected BaseOutputReader.Options readerOptions() { + return myHasPty ? BaseOutputReader.Options.BLOCKING : super.readerOptions(); // blocking read in case of PTY-based process + } + + /** + * Registers a file to delete after the given command line finishes. + * In order to have an effect, the command line has to be executed with {@link #OSProcessHandler(GeneralCommandLine)}. + */ + public static void deleteFileOnTermination(@NotNull GeneralCommandLine commandLine, @NotNull File fileToDelete) { + Set set = commandLine.getUserData(DELETE_FILES_ON_TERMINATION); + if (set == null) { + set = new HashSet<>(); + commandLine.putUserData(DELETE_FILES_ON_TERMINATION, set); + } + set.add(fileToDelete); + } + + public static class Silent extends OSProcessHandler { + public Silent(@NotNull GeneralCommandLine commandLine) throws ExecutionException { + super(commandLine); + } + + @NotNull + @Override + protected BaseOutputReader.Options readerOptions() { + return BaseOutputReader.Options.forMostlySilentProcess(); + } + } +} diff --git a/compiler/tests-common/tests/com/intellij/execution/process/ProcessNotCreatedException.java b/compiler/tests-common/tests/com/intellij/execution/process/ProcessNotCreatedException.java new file mode 100644 index 00000000000..17a4070c85b --- /dev/null +++ b/compiler/tests-common/tests/com/intellij/execution/process/ProcessNotCreatedException.java @@ -0,0 +1,28 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package com.intellij.execution.process; + +import com.intellij.execution.ExecutionException; +import com.intellij.execution.configurations.GeneralCommandLine; +import com.intellij.openapi.util.NlsContexts; + +public class ProcessNotCreatedException extends ExecutionException { + private final GeneralCommandLine myCommandLine; + + public ProcessNotCreatedException(final @NlsContexts.DialogMessage String message, final GeneralCommandLine commandLine) { + super(message); + myCommandLine = commandLine; + } + + public ProcessNotCreatedException(final @NlsContexts.DialogMessage String message, final Throwable cause, final GeneralCommandLine commandLine) { + super(message, cause); + myCommandLine = commandLine; + } + + public GeneralCommandLine getCommandLine() { + return myCommandLine; + } +} diff --git a/compiler/tests-common/tests/com/intellij/testFramework/TestDataPath.java b/compiler/tests-common/tests/com/intellij/testFramework/TestDataPath.java new file mode 100644 index 00000000000..e3a20a97f46 --- /dev/null +++ b/compiler/tests-common/tests/com/intellij/testFramework/TestDataPath.java @@ -0,0 +1,17 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package com.intellij.testFramework; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +public @interface TestDataPath { + String value(); +} diff --git a/compiler/tests-common/tests/org/jetbrains/kotlin/integration/KotlinIntegrationTestBase.java b/compiler/tests-common/tests/org/jetbrains/kotlin/integration/KotlinIntegrationTestBase.java index 4711def56eb..d52cc3fe357 100644 --- a/compiler/tests-common/tests/org/jetbrains/kotlin/integration/KotlinIntegrationTestBase.java +++ b/compiler/tests-common/tests/org/jetbrains/kotlin/integration/KotlinIntegrationTestBase.java @@ -39,7 +39,6 @@ import org.jetbrains.kotlin.utils.KotlinPaths; import org.jetbrains.kotlin.utils.PathUtil; import java.io.File; -import java.io.IOException; import java.util.regex.Pattern; @WithMutedInDatabaseRunTest @@ -97,7 +96,7 @@ public abstract class KotlinIntegrationTestBase extends TestCaseWithTmpdir { return content; } - private void check(String testDataDir, String baseName, String content) throws IOException { + private void check(String testDataDir, String baseName, String content) { File expectedFile = new File(testDataDir, baseName + ".expected"); String normalizedContent = normalizeOutput(new File(testDataDir), content); @@ -162,7 +161,7 @@ public abstract class KotlinIntegrationTestBase extends TestCaseWithTmpdir { } @Override - public void onTextAvailable(ProcessEvent event, Key outputType) { + public void onTextAvailable(@NotNull ProcessEvent event, @NotNull Key outputType) { if (outputType == ProcessOutputTypes.STDERR) { err.append(event.getText()); } @@ -175,6 +174,6 @@ public abstract class KotlinIntegrationTestBase extends TestCaseWithTmpdir { } @Override - public void processTerminated(ProcessEvent event) {} + public void processTerminated(@NotNull ProcessEvent event) {} } } diff --git a/compiler/tests-common/tests/org/jetbrains/kotlin/test/KotlinTestUtils.java b/compiler/tests-common/tests/org/jetbrains/kotlin/test/KotlinTestUtils.java index 2ceeca4ab3a..dd21ca107a5 100644 --- a/compiler/tests-common/tests/org/jetbrains/kotlin/test/KotlinTestUtils.java +++ b/compiler/tests-common/tests/org/jetbrains/kotlin/test/KotlinTestUtils.java @@ -7,11 +7,7 @@ package org.jetbrains.kotlin.test; import com.google.common.collect.Lists; import com.intellij.openapi.Disposable; -import com.intellij.openapi.editor.Caret; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.editor.Editor; import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; @@ -130,16 +126,6 @@ public class KotlinTestUtils { CompilerConfiguration configuration = new CompilerConfiguration(); configuration.put(CommonConfigurationKeys.MODULE_NAME, TEST_MODULE_NAME); - if ("true".equals(System.getProperty("kotlin.ni"))) { - // Enable new inference for tests which do not declare their own language version settings - CommonConfigurationKeysKt.setLanguageVersionSettings(configuration, new CompilerTestLanguageVersionSettings( - Collections.emptyMap(), - LanguageVersionSettingsImpl.DEFAULT.getApiVersion(), - LanguageVersionSettingsImpl.DEFAULT.getLanguageVersion(), - Collections.emptyMap() - )); - } - configuration.put(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, new MessageCollector() { @Override public void clear() { @@ -250,29 +236,6 @@ public class KotlinTestUtils { JvmResolveUtil.analyze(ktFiles, environment); } - public static void assertEqualsToFile(@NotNull File expectedFile, @NotNull Editor editor) { - assertEqualsToFile(expectedFile, editor, true); - } - - public static void assertEqualsToFile(@NotNull File expectedFile, @NotNull Editor editor, Boolean enableSelectionTags) { - Caret caret = editor.getCaretModel().getCurrentCaret(); - List tags = Lists.newArrayList( - new TagsTestDataUtil.TagInfo<>(caret.getOffset(), true, "caret") - ); - - if (enableSelectionTags) { - int selectionStart = caret.getSelectionStart(); - int selectionEnd = caret.getSelectionEnd(); - - tags.add(new TagsTestDataUtil.TagInfo<>(selectionStart, true, "selection")); - tags.add(new TagsTestDataUtil.TagInfo<>(selectionEnd, false, "selection")); - } - - String afterText = TagsTestDataUtil.insertTagsInText(tags, editor.getDocument().getText(), (TagsTestDataUtil.TagInfo t) -> null); - - assertEqualsToFile(expectedFile, afterText); - } - public static void assertEqualsToFile(@NotNull Path expectedFile, @NotNull String actual) { assertEqualsToFile(expectedFile.toFile(), actual); } @@ -389,31 +352,6 @@ public class KotlinTestUtils { return files; } - public static String getLastCommentedLines(@NotNull Document document) { - List resultLines = new ArrayList<>(); - for (int i = document.getLineCount() - 1; i >= 0; i--) { - int lineStart = document.getLineStartOffset(i); - int lineEnd = document.getLineEndOffset(i); - if (document.getCharsSequence().subSequence(lineStart, lineEnd).toString().trim().isEmpty()) { - continue; - } - - if ("//".equals(document.getCharsSequence().subSequence(lineStart, lineStart + 2).toString())) { - resultLines.add(document.getCharsSequence().subSequence(lineStart + 2, lineEnd)); - } - else { - break; - } - } - Collections.reverse(resultLines); - StringBuilder result = new StringBuilder(); - for (CharSequence line : resultLines) { - result.append(line).append("\n"); - } - result.delete(result.length() - 1, result.length()); - return result.toString(); - } - public enum CommentType { ALL, LINE_COMMENT, diff --git a/compiler/tests-common/tests/org/jetbrains/kotlin/test/TagsTestDataUtil.java b/compiler/tests-common/tests/org/jetbrains/kotlin/test/TagsTestDataUtil.java deleted file mode 100644 index 8ddb73f60e3..00000000000 --- a/compiler/tests-common/tests/org/jetbrains/kotlin/test/TagsTestDataUtil.java +++ /dev/null @@ -1,241 +0,0 @@ -/* - * Copyright 2010-2015 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 org.jetbrains.kotlin.test; - -import com.google.common.collect.Lists; -import com.intellij.codeInsight.daemon.LineMarkerInfo; -import com.intellij.codeInsight.daemon.impl.HighlightInfo; -import com.intellij.lang.annotation.HighlightSeverity; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.util.text.StringUtil; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Collection; -import java.util.List; -import java.util.function.Function; -import java.util.stream.Collectors; - -public class TagsTestDataUtil { - public static String insertInfoTags(List lineMarkers, boolean withDescription, String text) { - List lineMarkerPoints = toLineMarkerTagPoints(lineMarkers, withDescription); - - return insertTagsInText(lineMarkerPoints, text, (TagInfo t) -> null); - } - - public static String insertInfoTags(List highlights, String text) { - List highlightPoints = toHighlightTagPoints(highlights); - - return insertTagsInText(highlightPoints, text, (TagInfo t) -> null); - } - - public static String generateTextWithCaretAndSelection(@NotNull Editor editor) { - List points = Lists.newArrayList(); - points.add(new TagInfo(editor.getCaretModel().getOffset(), true, "caret")); - if (editor.getSelectionModel().hasSelection()) { - points.add(new TagInfo(editor.getSelectionModel().getSelectionStart(), true, "selection")); - points.add(new TagInfo(editor.getSelectionModel().getSelectionEnd(), false, "selection")); - } - - return insertTagsInText(points, editor.getDocument().getText(), (TagInfo t) -> null); - } - - public static String insertTagsInText(List tags, String text, Function computeExtraAttributes) { - StringBuilder builder = new StringBuilder(text); - - // Need to sort tags for inserting them in reverse order to have valid offsets for yet not inserted tags. - // Can't just sort in reverse order but do sort plus reverse instead to preserve final order for tags with the same offset. - List sortedTagPoints = Lists.reverse(tags.stream().sorted().collect(Collectors.toList())); - - // Insert tags into text starting from the end for preventing invalidating previous tags offsets - for (TagInfo point : sortedTagPoints) { - String tagText; - if (point.isStart) { - String attributesString = point.getAttributesString(); - String extraAttributes = computeExtraAttributes.apply(point); - - String allAttributes; - if (extraAttributes != null) { - allAttributes = attributesString + " " + extraAttributes; - } else { - allAttributes = attributesString; - } - - String closeSuffix = point.isClosed ? "/" : ""; - if (attributesString.isEmpty()) { - tagText = String.format("<%s%s>", point.getName(), closeSuffix); - } - else { - tagText = String.format("<%s %s%s>", point.getName(), allAttributes, closeSuffix); - } - } - else { - tagText = String.format("", point.getName()); - } - - builder.insert(point.offset, tagText); - } - - return builder.toString(); - } - - @NotNull - public static List toLineMarkerTagPoints( - Collection lineMarkers, - boolean withDescription - ) { - List lineMarkerPoints = Lists.newArrayList(); - for (LineMarkerInfo markerInfo : lineMarkers) { - lineMarkerPoints.add(new LineMarkerTagPoint(markerInfo.startOffset, true, markerInfo, withDescription)); - lineMarkerPoints.add(new LineMarkerTagPoint(markerInfo.endOffset, false, markerInfo, withDescription)); - } - return lineMarkerPoints; - } - - @NotNull - public static List toHighlightTagPoints(Collection highlights) { - List highlightPoints = Lists.newArrayList(); - for (HighlightInfo highlight : highlights) { - highlightPoints.add(new HighlightTagPoint(highlight.startOffset, true, highlight)); - highlightPoints.add(new HighlightTagPoint(highlight.endOffset, false, highlight)); - } - return highlightPoints; - } - - public static class TagInfo implements Comparable> { - protected final int offset; - protected final boolean isStart; - protected final boolean isClosed; - protected final boolean isFixed; - public final Data data; - - public TagInfo(int offset, boolean start, Data data) { - this(offset, start, false, false, data); - } - - public TagInfo(int offset, boolean isStart, boolean isClosed, boolean isFixed, Data data) { - if (isClosed && !isStart) { - throw new IllegalArgumentException("isClosed should be true only for start tags"); - } - - this.offset = offset; - this.isStart = isStart; - this.isClosed = isClosed; - this.isFixed = isFixed; - this.data = data; - } - - @Override - public int compareTo(@NotNull TagInfo other) { - if (offset != other.offset) { - return ((Integer) offset).compareTo(other.offset); - } - - if (isStart != other.isStart) { - // All "starts" should go after "ends" for same offset - return isStart ? -1 : 1; - } - - if (isFixed || other.isFixed) { - return 0; - } - - String thisTag = this.getName(); - String otherTag = other.getName(); - - // Invert order for end tags - return thisTag.compareTo(otherTag) * (isStart ? -1 : 1); - } - - @NotNull - public String getName() { - return data.toString(); - } - - @NotNull - public String getAttributesString() { - return ""; - } - } - - public static class HighlightTagPoint extends TagInfo { - private final HighlightInfo highlightInfo; - - private HighlightTagPoint(int offset, boolean start, HighlightInfo info) { - super(offset, start, info); - highlightInfo = info; - } - - @NotNull - @Override - public String getName() { - return highlightInfo.getSeverity().equals(HighlightSeverity.INFORMATION) - ? "info" - : highlightInfo.getSeverity().toString().toLowerCase(); - } - - @NotNull - @Override - public String getAttributesString() { - if (isStart) { - if (highlightInfo.getDescription() != null) { - return String.format("descr=\"%s\" textAttributesKey=\"%s\"", - sanitizeLineBreaks(highlightInfo.getDescription()), - highlightInfo.forcedTextAttributesKey - ); - } - else { - return String.format("textAttributesKey=\"%s\"", highlightInfo.forcedTextAttributesKey); - } - } - else { - return ""; - } - } - } - - public static class LineMarkerTagPoint extends TagInfo { - private final boolean withDescription; - - public LineMarkerTagPoint(int offset, boolean start, LineMarkerInfo info, boolean withDescription) { - super(offset, start, info); - this.withDescription = withDescription; - } - - @NotNull - @Override - public String getName() { - return "lineMarker"; - } - - @NotNull - @Override - public String getAttributesString() { - return withDescription ? String.format("descr=\"%s\"", sanitizeLineMarkerTooltip(data.getLineMarkerTooltip())) : "descr=\"*\""; - } - } - - private static @NotNull String sanitizeLineMarkerTooltip(@Nullable String originalText) { - if (originalText == null) return "null"; - String noHtmlTags = StringUtil.removeHtmlTags(originalText); - return sanitizeLineBreaks(noHtmlTags); - } - - private static String sanitizeLineBreaks(String originalText) { - return StringUtil.replace(originalText, "\n", " "); - } -} diff --git a/compiler/tests-common/tests/org/jetbrains/kotlin/test/testFramework/KtParsingTestCase.java b/compiler/tests-common/tests/org/jetbrains/kotlin/test/testFramework/KtParsingTestCase.java index bdb834a0195..53008ebf6e2 100644 --- a/compiler/tests-common/tests/org/jetbrains/kotlin/test/testFramework/KtParsingTestCase.java +++ b/compiler/tests-common/tests/org/jetbrains/kotlin/test/testFramework/KtParsingTestCase.java @@ -16,63 +16,35 @@ package org.jetbrains.kotlin.test.testFramework; -import com.intellij.ide.util.AppPropertiesComponentImpl; -import com.intellij.ide.util.PropertiesComponent; -import com.intellij.lang.*; -import com.intellij.lang.impl.PsiBuilderFactoryImpl; -import com.intellij.mock.*; -import com.intellij.openapi.Disposable; +import com.intellij.lang.ParserDefinition; +import com.intellij.mock.MockProject; import com.intellij.openapi.application.PathManager; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.editor.EditorFactory; -import com.intellij.openapi.fileEditor.FileDocumentManager; -import com.intellij.openapi.fileEditor.impl.LoadTextUtil; -import com.intellij.openapi.fileTypes.FileTypeManager; -import com.intellij.openapi.fileTypes.FileTypeRegistry; -import com.intellij.openapi.options.SchemeManagerFactory; -import com.intellij.openapi.progress.EmptyProgressIndicator; -import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.progress.impl.CoreProgressManager; -import com.intellij.openapi.util.Disposer; -import com.intellij.openapi.util.Key; -import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vfs.CharsetToolkit; -import com.intellij.pom.PomModel; -import com.intellij.pom.core.impl.PomModelImpl; -import com.intellij.pom.tree.TreeAspect; -import com.intellij.psi.*; -import com.intellij.psi.impl.*; -import com.intellij.psi.util.CachedValuesManager; -import com.intellij.testFramework.LightVirtualFile; -import com.intellij.testFramework.MockSchemeManagerFactory; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.impl.DebugUtil; import com.intellij.testFramework.TestDataFile; -import com.intellij.util.CachedValuesManagerImpl; -import com.intellij.util.Function; -import junit.framework.TestCase; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; -import org.jetbrains.kotlin.idea.KotlinFileType; -import org.picocontainer.ComponentAdapter; -import org.picocontainer.MutablePicoContainer; +import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles; +import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment; +import org.jetbrains.kotlin.config.CompilerConfiguration; +import org.jetbrains.kotlin.test.util.KtTestUtil; import java.io.File; import java.io.IOException; -import java.util.Set; @SuppressWarnings("ALL") public abstract class KtParsingTestCase extends KtPlatformLiteFixture { - public static final Key HARD_REF_TO_DOCUMENT_KEY = Key.create("HARD_REF_TO_DOCUMENT_KEY"); - protected String myFilePrefix = ""; protected String myFileExt; protected final String myFullDataPath; protected PsiFile myFile; - private MockPsiManager myPsiManager; - private PsiFileFactoryImpl myFileFactory; - protected Language myLanguage; private final ParserDefinition[] myDefinitions; private final boolean myLowercaseFirstLetter; + private KotlinCoreEnvironment myEnvironment; + protected KtParsingTestCase(@NonNls @NotNull String dataPath, @NotNull String fileExt, @NotNull ParserDefinition... definitions) { this(dataPath, fileExt, false, definitions); } @@ -87,88 +59,9 @@ public abstract class KtParsingTestCase extends KtPlatformLiteFixture { @Override protected void setUp() throws Exception { super.setUp(); - initApplication(); - ComponentAdapter component = getApplication().getPicoContainer().getComponentAdapter(ProgressManager.class.getName()); - - myProject = new MockProjectEx(getTestRootDisposable()); - myPsiManager = new MockPsiManager(myProject); - myFileFactory = new PsiFileFactoryImpl(myPsiManager); - MutablePicoContainer appContainer = getApplication().getPicoContainer(); - final MockEditorFactory editorFactory = new MockEditorFactory(); - MockFileTypeManager mockFileTypeManager = new MockFileTypeManager(KotlinFileType.INSTANCE); - MockFileDocumentManagerImpl mockFileDocumentManager = new MockFileDocumentManagerImpl(new Function() { - @Override - public Document fun(CharSequence charSequence) { - return editorFactory.createDocument(charSequence); - } - }, HARD_REF_TO_DOCUMENT_KEY); - - registerApplicationService(PropertiesComponent.class, new AppPropertiesComponentImpl()); - registerApplicationService(PsiBuilderFactory.class, new PsiBuilderFactoryImpl()); - registerApplicationService(DefaultASTFactory.class, new DefaultASTFactoryImpl()); - registerApplicationService(SchemeManagerFactory.class, new MockSchemeManagerFactory()); - registerApplicationService(FileTypeManager.class, mockFileTypeManager); - registerApplicationService(FileDocumentManager.class, mockFileDocumentManager); - - registerApplicationService(ProgressManager.class, new CoreProgressManager()); - - registerComponentInstance(appContainer, FileTypeRegistry.class, mockFileTypeManager); - registerComponentInstance(appContainer, FileTypeManager.class, mockFileTypeManager); - registerComponentInstance(appContainer, EditorFactory.class, editorFactory); - registerComponentInstance(appContainer, FileDocumentManager.class, mockFileDocumentManager); - registerComponentInstance(appContainer, PsiDocumentManager.class, new MockPsiDocumentManager()); - - myProject.registerService(PsiManager.class, myPsiManager); - myProject.registerService(CachedValuesManager.class, new CachedValuesManagerImpl(myProject, new PsiCachedValuesFactory(myProject))); - myProject.registerService(TreeAspect.class, new TreeAspect()); - - for (ParserDefinition definition : myDefinitions) { - addExplicitExtension(LanguageParserDefinitions.INSTANCE, definition.getFileNodeType().getLanguage(), definition); - } - if (myDefinitions.length > 0) { - configureFromParserDefinition(myDefinitions[0], myFileExt); - } - - // That's for reparse routines - final PomModelImpl pomModel = new PomModelImpl(myProject); - myProject.registerService(PomModel.class, pomModel); - } - - public void configureFromParserDefinition(ParserDefinition definition, String extension) { - myLanguage = definition.getFileNodeType().getLanguage(); - myFileExt = extension; - addExplicitExtension(LanguageParserDefinitions.INSTANCE, this.myLanguage, definition); - registerComponentInstance( - getApplication().getPicoContainer(), FileTypeManager.class, - new MockFileTypeManager(new MockLanguageFileType(myLanguage, myFileExt))); - } - - protected void addExplicitExtension(final LanguageExtension instance, final Language language, final T object) { - instance.addExplicitExtension(language, object); - Disposer.register(myProject, new Disposable() { - @Override - public void dispose() { - instance.removeExplicitExtension(language, object); - } - }); - } - - protected void registerApplicationService(final Class aClass, T object) { - getApplication().registerService(aClass, object); - Disposer.register(myProject, new Disposable() { - @Override - public void dispose() { - getApplication().getPicoContainer().unregisterComponent(aClass.getName()); - } - }); - } - - public MockProjectEx getProject() { - return myProject; - } - - public MockPsiManager getPsiManager() { - return myPsiManager; + myEnvironment = KotlinCoreEnvironment.createForTests(getTestRootDisposable(), CompilerConfiguration.EMPTY, + EnvironmentConfigFiles.JVM_CONFIG_FILES); + myProject = (MockProject) myEnvironment.getProject(); } @Override @@ -176,7 +69,7 @@ public abstract class KtParsingTestCase extends KtPlatformLiteFixture { super.tearDown(); myFile = null; myProject = null; - myPsiManager = null; + myEnvironment = null; } protected String getTestDataPath() { @@ -200,92 +93,12 @@ public abstract class KtParsingTestCase extends KtPlatformLiteFixture { return true; } - protected void doTest(boolean checkResult) { - String name = getTestName(); - try { - String text = loadFile(name + "." + myFileExt); - myFile = createPsiFile(name, text); - ensureParsed(myFile); - assertEquals("light virtual file text mismatch", text, ((LightVirtualFile)myFile.getVirtualFile()).getContent().toString()); - assertEquals("virtual file text mismatch", text, LoadTextUtil.loadText(myFile.getVirtualFile())); - assertEquals("doc text mismatch", text, myFile.getViewProvider().getDocument().getText()); - assertEquals("psi text mismatch", text, myFile.getText()); - ensureCorrectReparse(myFile); - if (checkResult){ - checkResult(name, myFile); - } - else{ - toParseTreeText(myFile, skipSpaces(), includeRanges()); - } - } - catch (IOException e) { - throw new RuntimeException(e); - } - } - - protected void doTest(String suffix) throws IOException { - String name = getTestName(); - String text = loadFile(name + "." + myFileExt); - myFile = createPsiFile(name, text); - ensureParsed(myFile); - assertEquals(text, myFile.getText()); - checkResult(name + suffix, myFile); - } - - protected void doCodeTest(String code) throws IOException { - String name = getTestName(); - myFile = createPsiFile("a", code); - ensureParsed(myFile); - assertEquals(code, myFile.getText()); - checkResult(myFilePrefix + name, myFile); - } - protected PsiFile createPsiFile(String name, String text) { return createFile(name + "." + myFileExt, text); } protected PsiFile createFile(@NonNls String name, String text) { - LightVirtualFile virtualFile = new LightVirtualFile(name, myLanguage, text); - virtualFile.setCharset(CharsetToolkit.UTF8_CHARSET); - return createFile(virtualFile); - } - - protected PsiFile createFile(LightVirtualFile virtualFile) { - return myFileFactory.trySetupPsiForFile(virtualFile, myLanguage, true, false); - } - - protected void checkResult(@NonNls @TestDataFile String targetDataName, final PsiFile file) throws IOException { - doCheckResult(myFullDataPath, file, checkAllPsiRoots(), targetDataName, skipSpaces(), includeRanges()); - } - - public static void doCheckResult(String testDataDir, - PsiFile file, - boolean checkAllPsiRoots, - String targetDataName, - boolean skipSpaces, - boolean printRanges) throws IOException { - FileViewProvider provider = file.getViewProvider(); - Set languages = provider.getLanguages(); - - if (!checkAllPsiRoots || languages.size() == 1) { - doCheckResult(testDataDir, targetDataName + ".txt", toParseTreeText(file, skipSpaces, printRanges).trim()); - return; - } - - for (Language language : languages) { - PsiFile root = provider.getPsi(language); - String expectedName = targetDataName + "." + language.getID() + ".txt"; - doCheckResult(testDataDir, expectedName, toParseTreeText(root, skipSpaces, printRanges).trim()); - } - } - - protected void checkResult(String actual) throws IOException { - String name = getTestName(); - doCheckResult(myFullDataPath, myFilePrefix + name + ".txt", actual); - } - - protected void checkResult(@TestDataFile @NonNls String targetDataName, String actual) throws IOException { - doCheckResult(myFullDataPath, targetDataName, actual); + return KtTestUtil.createFile(name, text, myProject); } public static void doCheckResult(String fullPath, String targetDataName, String actual) throws IOException { @@ -304,23 +117,4 @@ public abstract class KtParsingTestCase extends KtPlatformLiteFixture { public static String loadFileDefault(String dir, String name) throws IOException { return FileUtil.loadFile(new File(dir, name), CharsetToolkit.UTF8, true).trim(); } - - public static void ensureParsed(PsiFile file) { - file.accept(new PsiElementVisitor() { - @Override - public void visitElement(PsiElement element) { - element.acceptChildren(this); - } - }); - } - - public static void ensureCorrectReparse(@NotNull PsiFile file) { - String psiToStringDefault = DebugUtil.psiToString(file, false, false); - String fileText = file.getText(); - DiffLog diffLog = (new BlockSupportImpl()).reparseRange( - file, file.getNode(), TextRange.allOf(fileText), fileText, new EmptyProgressIndicator(), fileText); - diffLog.performActualPsiChange(file); - - TestCase.assertEquals(psiToStringDefault, DebugUtil.psiToString(file, false, false)); - } } diff --git a/compiler/tests-common/tests/org/jetbrains/kotlin/test/testFramework/KtPlatformLiteFixture.java b/compiler/tests-common/tests/org/jetbrains/kotlin/test/testFramework/KtPlatformLiteFixture.java index 344dc591e27..e99fc4d9d72 100644 --- a/compiler/tests-common/tests/org/jetbrains/kotlin/test/testFramework/KtPlatformLiteFixture.java +++ b/compiler/tests-common/tests/org/jetbrains/kotlin/test/testFramework/KtPlatformLiteFixture.java @@ -16,25 +16,10 @@ package org.jetbrains.kotlin.test.testFramework; -import com.intellij.core.CoreEncodingProjectManager; -import com.intellij.mock.MockApplication; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.fileTypes.FileTypeManager; -import com.intellij.openapi.vfs.encoding.EncodingManager; -import org.picocontainer.MutablePicoContainer; +import com.intellij.mock.MockProject; public abstract class KtPlatformLiteFixture extends KtUsefulTestCase { - protected MockProjectEx myProject; - - public static MockApplication getApplication() { - return (MockApplication) ApplicationManager.getApplication(); - } - - public void initApplication() { - MockApplication instance = new MockApplication(getTestRootDisposable()); - ApplicationManager.setApplication(instance, FileTypeManager::getInstance, getTestRootDisposable()); - getApplication().registerService(EncodingManager.class, CoreEncodingProjectManager.class); - } + protected MockProject myProject; @Override protected void tearDown() throws Exception { @@ -42,12 +27,4 @@ public abstract class KtPlatformLiteFixture extends KtUsefulTestCase { clearFields(this); myProject = null; } - - @SuppressWarnings("unchecked") - public static T registerComponentInstance(MutablePicoContainer container, Class key, T implementation) { - Object old = container.getComponentInstance(key); - container.unregisterComponent(key); - container.registerComponentInstance(key, implementation); - return (T)old; - } } diff --git a/compiler/tests-common/tests/org/jetbrains/kotlin/test/testFramework/KtUsefulTestCase.java b/compiler/tests-common/tests/org/jetbrains/kotlin/test/testFramework/KtUsefulTestCase.java index 657ee289ec8..0f6835751b1 100644 --- a/compiler/tests-common/tests/org/jetbrains/kotlin/test/testFramework/KtUsefulTestCase.java +++ b/compiler/tests-common/tests/org/jetbrains/kotlin/test/testFramework/KtUsefulTestCase.java @@ -5,38 +5,28 @@ package org.jetbrains.kotlin.test.testFramework; -import com.intellij.codeInsight.CodeInsightSettings; -import com.intellij.concurrency.IdeaForkJoinWorkerThreadFactory; -import com.intellij.diagnostic.PerformanceWatcher; import com.intellij.openapi.Disposable; import com.intellij.openapi.application.Application; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.PathManager; import com.intellij.openapi.application.impl.ApplicationInfoImpl; import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.fileTypes.StdFileTypes; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.JDOMUtil; import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFileVisitor; import com.intellij.psi.PsiDocumentManager; -import com.intellij.psi.codeStyle.CodeStyleSettings; -import com.intellij.psi.impl.DocumentCommitProcessor; -import com.intellij.psi.impl.DocumentCommitThread; -import com.intellij.psi.impl.source.PostprocessReformattingAspect; import com.intellij.rt.execution.junit.FileComparisonFailure; -import com.intellij.testFramework.*; import com.intellij.util.*; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.PeekableIterator; import com.intellij.util.containers.PeekableIteratorWrapper; -import com.intellij.util.indexing.FileBasedIndex; -import com.intellij.util.indexing.FileBasedIndexImpl; import com.intellij.util.lang.CompoundRuntimeException; import gnu.trove.Equality; import gnu.trove.THashSet; @@ -63,7 +53,6 @@ import java.lang.reflect.Modifier; import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.util.*; -import java.util.concurrent.TimeUnit; import java.util.function.Supplier; @SuppressWarnings("ALL") @@ -79,9 +68,6 @@ public abstract class KtUsefulTestCase extends TestCase { private Application application; - static { - IdeaForkJoinWorkerThreadFactory.setupPoisonFactory(); - } protected static final Logger LOG = Logger.getInstance(KtUsefulTestCase.class); @NotNull @@ -93,14 +79,12 @@ public abstract class KtUsefulTestCase extends TestCase { private String myTempDir; private static final String DEFAULT_SETTINGS_EXTERNALIZED; - private static final CodeInsightSettings defaultSettings = new CodeInsightSettings(); static { // Radar #5755208: Command line Java applications need a way to launch without a Dock icon. System.setProperty("apple.awt.UIElement", "true"); try { Element oldS = new Element("temp"); - defaultSettings.writeExternal(oldS); DEFAULT_SETTINGS_EXTERNALIZED = JDOMUtil.writeElement(oldS); } catch (Exception e) { @@ -176,7 +160,7 @@ public abstract class KtUsefulTestCase extends TestCase { boolean isStressTest = isStressTest(); ApplicationInfoImpl.setInStressTest(isStressTest); - + Registry.getInstance().markAsLoaded(); // turn off Disposer debugging for performance tests Disposer.setDebugMode(!isStressTest); } @@ -287,39 +271,6 @@ public abstract class KtUsefulTestCase extends TestCase { containerMap.clear(); } - static void doCheckForSettingsDamage(@NotNull CodeStyleSettings oldCodeStyleSettings, @NotNull CodeStyleSettings currentCodeStyleSettings) { - final CodeInsightSettings settings = CodeInsightSettings.getInstance(); - // don't use method references here to make stack trace reading easier - //noinspection Convert2MethodRef - new RunAll() - .append(() -> { - try { - checkCodeInsightSettingsEqual(defaultSettings, settings); - } - catch (AssertionError error) { - CodeInsightSettings clean = new CodeInsightSettings(); - for (Field field : clean.getClass().getFields()) { - try { - ReflectionUtil.copyFieldValue(clean, settings, field); - } - catch (Exception ignored) { - } - } - throw error; - } - }) - .append(() -> { - currentCodeStyleSettings.getIndentOptions(StdFileTypes.JAVA); - try { - checkCodeStyleSettingsEqual(oldCodeStyleSettings, currentCodeStyleSettings); - } - finally { - currentCodeStyleSettings.clearCodeStyleSettings(); - } - }) - .run(); - } - @NotNull public Disposable getTestRootDisposable() { return myTestRootDisposable; @@ -725,10 +676,6 @@ public abstract class KtUsefulTestCase extends TestCase { Assert.fail(value + " should be equal to one of " + Arrays.toString(values)); } - public static void printThreadDump() { - PerformanceWatcher.dumpThreadsToConsole("Thread dump:"); - } - public static void assertEmpty(@NotNull Object[] array) { assertOrderedEquals(array); } @@ -885,27 +832,6 @@ public abstract class KtUsefulTestCase extends TestCase { } } - private static void checkCodeStyleSettingsEqual(@NotNull CodeStyleSettings expected, @NotNull CodeStyleSettings settings) { - if (!expected.equals(settings)) { - Element oldS = new Element("temp"); - expected.writeExternal(oldS); - Element newS = new Element("temp"); - settings.writeExternal(newS); - - String newString = JDOMUtil.writeElement(newS); - String oldString = JDOMUtil.writeElement(oldS); - Assert.assertEquals("Code style settings damaged", oldString, newString); - } - } - - private static void checkCodeInsightSettingsEqual(@NotNull CodeInsightSettings oldSettings, @NotNull CodeInsightSettings settings) { - if (!oldSettings.equals(settings)) { - Element newS = new Element("temp"); - settings.writeExternal(newS); - Assert.assertEquals("Code insight settings damaged", DEFAULT_SETTINGS_EXTERNALIZED, JDOMUtil.writeElement(newS)); - } - } - /** * @return true for a test which performs A LOT of computations. * Such test should typically avoid performing expensive checks, e.g. data structure consistency complex validations. @@ -925,13 +851,6 @@ public abstract class KtUsefulTestCase extends TestCase { return name != null && (name.contains("Stress") || name.contains("Slow")); } - public static void doPostponedFormatting(@NotNull Project project) { - DocumentUtil.writeInRunUndoTransparentAction(() -> { - PsiDocumentManager.getInstance(project).commitAllDocuments(); - PostprocessReformattingAspect.getInstance(project).doPostponedFormatting(); - }); - } - protected void assertNoThrowable(@NotNull Runnable closure) { String throwableName = null; try { diff --git a/compiler/tests/org/jetbrains/kotlin/parsing/AbstractParsingTest.java b/compiler/tests/org/jetbrains/kotlin/parsing/AbstractParsingTest.java index 0ae37e545d7..3202ac98e2a 100644 --- a/compiler/tests/org/jetbrains/kotlin/parsing/AbstractParsingTest.java +++ b/compiler/tests/org/jetbrains/kotlin/parsing/AbstractParsingTest.java @@ -16,9 +16,6 @@ package org.jetbrains.kotlin.parsing; -import com.intellij.openapi.fileTypes.FileTypeManager; -import com.intellij.openapi.fileTypes.FileTypeRegistry; -import com.intellij.openapi.util.Getter; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.io.FileUtilRt; import com.intellij.psi.PsiErrorElement; @@ -136,21 +133,4 @@ public abstract class AbstractParsingTest extends KtParsingTestCase { return createPsiFile(FileUtil.getNameWithoutExtension(PathUtil.getFileName(filePath)), fileContent); } } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - - // Temp workaround for setting FileTypeRegistry.ourInstanceGetter to CoreFileTypeRegistry forever - // Reproducible with pattern test kind: - // org.jetbrains.kotlin.parsing.JetParsingTestGenerated$Psi||org.jetbrains.kotlin.codegen.TestlibTest||org.jetbrains.kotlin.completion.MultiFileJvmBasicCompletionTestGenerated - - //noinspection AssignmentToStaticFieldFromInstanceMethod - FileTypeRegistry.ourInstanceGetter = new Getter() { - @Override - public FileTypeRegistry get() { - return FileTypeManager.getInstance(); - } - }; - } } diff --git a/js/js.tests/test/com/intellij/mock/MockVirtualFileSystem.java b/js/js.tests/test/com/intellij/mock/MockVirtualFileSystem.java new file mode 100644 index 00000000000..3e33e55a491 --- /dev/null +++ b/js/js.tests/test/com/intellij/mock/MockVirtualFileSystem.java @@ -0,0 +1,126 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package com.intellij.mock; + +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.DeprecatedVirtualFileSystem; +import com.intellij.openapi.vfs.VfsUtilCore; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.VirtualFileSystem; +import com.intellij.testFramework.LightVirtualFile; +import com.intellij.util.containers.CollectionFactory; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.util.Map; + +public final class MockVirtualFileSystem extends DeprecatedVirtualFileSystem { + private static final String PROTOCOL = "mock"; + + private final MyVirtualFile myRoot = new MyVirtualFile("", null) { + @NotNull + @Override + public VirtualFileSystem getFileSystem() { + return MockVirtualFileSystem.this; + } + }; + + @NotNull + @Override + public MyVirtualFile findFileByPath(@NotNull String path) { + String normalized = path.replace(File.separatorChar, '/').replace('/', ':'); + if (StringUtil.startsWithChar(normalized, ':')) normalized = normalized.substring(1); + MyVirtualFile file = myRoot; + for (String component : StringUtil.split(normalized, ":")) { + file = file.getOrCreate(component); + } + return file; + } + + @NotNull + public MockVirtualFileSystem file(@NotNull String path, @NotNull String data) { + MyVirtualFile file = findFileByPath(path); + file.setContent(null, data, false); + return this; + } + + @NotNull + public VirtualFile getRoot() { + return myRoot; + } + + @NotNull + @Override + public String getProtocol() { + return PROTOCOL; + } + + @Override + public void refresh(boolean asynchronous) { } + + @Override + public VirtualFile refreshAndFindFileByPath(@NotNull String path) { + return findFileByPath(path); + } + + private static class MyVirtualFile extends LightVirtualFile { + private final MyVirtualFile myParent; + private Map myChildren; + + private MyVirtualFile(String name, MyVirtualFile parent) { + super(name); + myParent = parent; + } + + @NotNull + @Override + public VirtualFileSystem getFileSystem() { + return myParent.getFileSystem(); + } + + @NotNull + public MyVirtualFile getOrCreate(@NotNull String name) { + MyVirtualFile file = findChild(name); + if (file == null) { + if (myChildren == null) { + myChildren = CollectionFactory.createSmallMemoryFootprintMap(); + } + file = new MyVirtualFile(name, this); + myChildren.put(name, file); + } + return file; + } + + @Override + public boolean isDirectory() { + return myParent == null || (myChildren != null && !myChildren.isEmpty()); + } + + @NotNull + @Override + public String getPath() { + MockVirtualFileSystem.MyVirtualFile parent = getParent(); + return parent == null ? getName() : parent.getPath() + "/" + getName(); + } + + @Override + public MyVirtualFile getParent() { + return myParent; + } + + @Override + public VirtualFile[] getChildren() { + return myChildren == null ? EMPTY_ARRAY : VfsUtilCore.toVirtualFileArray(myChildren.values()); + } + + @Nullable + @Override + public MyVirtualFile findChild(@NotNull String name) { + return myChildren == null ? null : myChildren.get(name); + } + } +} diff --git a/libraries/tools/kotlin-gradle-statistics/build.gradle.kts b/libraries/tools/kotlin-gradle-statistics/build.gradle.kts index 306d012ebcb..e8a3f67020c 100644 --- a/libraries/tools/kotlin-gradle-statistics/build.gradle.kts +++ b/libraries/tools/kotlin-gradle-statistics/build.gradle.kts @@ -16,7 +16,9 @@ sourceSets { if (kotlinBuildProperties.isJpsBuildEnabled || kotlinBuildProperties.useFir) { none() } else { - projectDefault() + none() + +// projectDefault() } } } diff --git a/native/commonizer/tests/org/jetbrains/kotlin/commonizer/AbstractCommonizationFromSourcesTest.kt b/native/commonizer/tests/org/jetbrains/kotlin/commonizer/AbstractCommonizationFromSourcesTest.kt index 333bf1221c2..bd3e66da989 100644 --- a/native/commonizer/tests/org/jetbrains/kotlin/commonizer/AbstractCommonizationFromSourcesTest.kt +++ b/native/commonizer/tests/org/jetbrains/kotlin/commonizer/AbstractCommonizationFromSourcesTest.kt @@ -6,7 +6,6 @@ package org.jetbrains.kotlin.commonizer import com.intellij.openapi.Disposable -import com.intellij.testFramework.PlatformTestUtil.lowercaseFirstLetter import org.jetbrains.kotlin.analyzer.ModuleInfo import org.jetbrains.kotlin.analyzer.common.CommonDependenciesContainer import org.jetbrains.kotlin.analyzer.common.CommonPlatformAnalyzerServices diff --git a/plugins/android-extensions/android-extensions-compiler/test/org/jetbrains/kotlin/android/synthetic/test/CompilerTestUtils.kt b/plugins/android-extensions/android-extensions-compiler/test/org/jetbrains/kotlin/android/synthetic/test/CompilerTestUtils.kt index 806f0bf11a1..ad114886b1d 100755 --- a/plugins/android-extensions/android-extensions-compiler/test/org/jetbrains/kotlin/android/synthetic/test/CompilerTestUtils.kt +++ b/plugins/android-extensions/android-extensions-compiler/test/org/jetbrains/kotlin/android/synthetic/test/CompilerTestUtils.kt @@ -6,7 +6,6 @@ package org.jetbrains.kotlin.android.synthetic.test import com.intellij.mock.MockProject -import com.intellij.testFramework.registerServiceInstance import kotlinx.android.extensions.CacheImplementation import org.jetbrains.kotlin.android.synthetic.AndroidConfigurationKeys import org.jetbrains.kotlin.android.synthetic.AndroidExtensionPropertiesComponentContainerContributor