mirror of
https://github.com/jlengrand/kotlin.git
synced 2026-03-10 08:31:29 +00:00
[Test] Remove dependencies on IDEA classes from test modules
Some of IDEA services (like in `com.intellij/execution`) was copied, because they are used in tests but jars with them compiled with jdk 11 and we run our tests on jdk 8, so their bytecode can not be read
This commit is contained in:
@@ -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", "")}")
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
* <p>
|
||||
* {@code NONE} means a child process will receive an empty environment. <br/>
|
||||
* {@code SYSTEM} will provide it with the same environment as an IDE. <br/>
|
||||
* {@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<String, String> 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<Object, Object> myUserData;
|
||||
|
||||
public GeneralCommandLine() { }
|
||||
|
||||
public GeneralCommandLine(@NonNls String... command) {
|
||||
this(Arrays.asList(command));
|
||||
}
|
||||
|
||||
public GeneralCommandLine(@NonNls @NotNull List<String> 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<String, String> getEnvironment() {
|
||||
return myEnvParams;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public GeneralCommandLine withEnvironment(@Nullable Map<String, String> 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<String, String> 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<String, String> getEffectiveEnvironment() {
|
||||
Map<String, String> env = new MyMap();
|
||||
setupEnvironment(env);
|
||||
return env;
|
||||
}
|
||||
|
||||
public void addParameters(@NonNls String... parameters) {
|
||||
withParameters(parameters);
|
||||
}
|
||||
|
||||
public void addParameters(@NotNull List<String> 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<String> 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.<br/>
|
||||
* Warning: resulting string is not OS-dependent - <b>do not</b> 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.<br/>
|
||||
* Warning: resulting string is not OS-dependent - <b>do not</b> 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<String> getCommandLineList(@Nullable String exeName) {
|
||||
List<@NlsSafe String> commands = new ArrayList<>();
|
||||
String exe = StringUtil.notNullize(exeName, StringUtil.notNullize(myExePath, "<null>"));
|
||||
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<String> prepareCommandLine(@NotNull String command, @NotNull List<String> 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<String> 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<String> escapedCommands = validateAndPrepareCommandLine();
|
||||
return toProcessBuilderInternal(escapedCommands);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private List<String> 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<String, String> 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:
|
||||
* <p>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.
|
||||
* <pre>
|
||||
* [foo] [^] -> [foo] [^^]
|
||||
* </pre>
|
||||
* but:
|
||||
* <pre>
|
||||
* [foo] ["] [^] -> [foo] [\"] ["^"]
|
||||
* </pre>
|
||||
* Notice how the last parameter escaping changes after prepending another argument.</p>
|
||||
* <p>If you need to alter the command line passed in, override the {@link #prepareCommandLine(String, List, Platform)} method instead.</p>
|
||||
*/
|
||||
@NotNull
|
||||
protected Process startProcess(@NotNull List<String> 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<String>).
|
||||
// Ideally, it should have been startProcess(ProcessBuilder), and the design would be more straightforward.
|
||||
@NotNull
|
||||
private ProcessBuilder toProcessBuilderInternal(@NotNull List<String> 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<String, String> 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<String, String> 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> T getUserData(@NotNull Key<T> key) {
|
||||
if (myUserData == null) return null;
|
||||
@SuppressWarnings("unchecked") T t = (T)myUserData.get(key);
|
||||
return t;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void putUserData(@NotNull Key<T> 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<String, String> {
|
||||
private MyMap() {
|
||||
super(FastUtilHashingStrategies.getStringStrategy(!SystemInfoRt.isWindows));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putAll(Map<? extends String, ? extends String> map) {
|
||||
if (map != null) {
|
||||
super.putAll(map);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<String> myParameters = new ArrayList<>();
|
||||
private final List<ParamsGroup> myGroups = new SmartList<>();
|
||||
private final NotNullLazyValue<Map<String, String>> 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<String, String> getProperties() {
|
||||
return getProperties("");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Map<String, String> getProperties(@NonNls String valueIfMissing) {
|
||||
Map<String, String> result = new LinkedHashMap<>();
|
||||
JBIterable<Matcher> 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<String> getList() {
|
||||
if (myGroups.isEmpty()) {
|
||||
return Collections.unmodifiableList(myParameters);
|
||||
}
|
||||
|
||||
List<String> 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<String> getParameters() {
|
||||
return Collections.unmodifiableList(myParameters);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<ParamsGroup> 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 <propertyName>} 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 <propertyName>} 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<propertyName>} 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<propertyName>=<propertyValue>} 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<propertyName>=<propertyValue>} 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 <parameterPrefix>} with {@code <replacement>};
|
||||
* otherwise appends {@code <replacement>} 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 <parameterPrefix>} with {@code <replacement>};
|
||||
* otherwise prepends this list with {@code <replacement>}.
|
||||
*/
|
||||
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<? super String> 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<? super String> 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<String> 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<String> 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<String, String> 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<String, String> ourTestMacros;
|
||||
|
||||
@TestOnly
|
||||
public static void setTestMacros(@Nullable @NonNls Map<String, String> testMacros) {
|
||||
ourTestMacros = testMacros;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Map<String, String> 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<String, String> map = CollectionFactory.createCaseInsensitiveStringMap();
|
||||
Map<String, String> pathMacros = PathMacros.getInstance().getUserMacros();
|
||||
if (!pathMacros.isEmpty()) {
|
||||
for (String name : pathMacros.keySet()) {
|
||||
ContainerUtil.putIfNotNull(name, pathMacros.get(name), map);
|
||||
}
|
||||
}
|
||||
Map<String, String> 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);
|
||||
}
|
||||
}
|
||||
@@ -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<String> parameters) {
|
||||
for (String parameter : parameters) {
|
||||
addParameter(parameter);
|
||||
}
|
||||
}
|
||||
|
||||
public void addParametersString(@NotNull String parametersString) {
|
||||
addParameters(ParametersList.parse(parametersString));
|
||||
}
|
||||
|
||||
public List<String> 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;
|
||||
}
|
||||
}
|
||||
@@ -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<File> 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<File> findAllExeFilesInPath(@NotNull String fileBaseName) {
|
||||
return findAllExeFilesInPath(fileBaseName, null);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static List<File> findAllExeFilesInPath(@NotNull String fileBaseName, @Nullable FileFilter filter) {
|
||||
return findExeFilesInPath(false, filter, getPathVariableValue(), fileBaseName);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static List<File> findExeFilesInPath(boolean stopAfterFirstMatch,
|
||||
@Nullable FileFilter filter,
|
||||
@Nullable String pathEnvVarValue,
|
||||
String... fileBaseNames) {
|
||||
if (pathEnvVarValue == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<File> result = new SmartList<>();
|
||||
List<String> 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<String> getPathDirs(@NotNull String pathEnvVarValue) {
|
||||
return StringUtil.split(pathEnvVarValue, File.pathSeparator, true, true);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static List<String> getWindowsExecutableFileExtensions() {
|
||||
if (SystemInfo.isWindows) {
|
||||
String allExtensions = System.getenv("PATHEXT");
|
||||
if (allExtensions != null) {
|
||||
Collection<String> 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<String> executableFileExtensions = getWindowsExecutableFileExtensions();
|
||||
|
||||
String[] baseNames = ContainerUtil.map2Array(executableFileExtensions, String.class, s -> exePath + s);
|
||||
List<File> 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<File> exeFiles = findExeFilesInPath(true, null, getPathVariableValue(), fileNames);
|
||||
return ContainerUtil.getFirstItem(exeFiles);
|
||||
}
|
||||
else {
|
||||
return findInPath(fileBaseName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<String> REPORTED_EXECUTIONS = Collections.newSetFromMap(new ConcurrentHashMap<>());
|
||||
private static final long ALLOWED_TIMEOUT_THRESHOLD = 10;
|
||||
|
||||
private static final Key<Set<File>> DELETE_FILES_ON_TERMINATION = Key.create("OSProcessHandler.FileToDelete");
|
||||
|
||||
private final boolean myHasErrorStream;
|
||||
@NotNull
|
||||
private final ModalityState myModality;
|
||||
private boolean myHasPty;
|
||||
private final Set<? extends File> 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<? extends File> 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.
|
||||
* <br/><br/>
|
||||
* HOW-TO fix an error from this method:
|
||||
* <ul>
|
||||
* <li>You are on the pooled thread under {@link com.intellij.openapi.application.ReadAction ReadAction}:
|
||||
* <ul>
|
||||
* <li>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.</li>
|
||||
* <li>Non-synchronous (you don't need to return something) - execute on another thread. E.g. using {@link Task.Backgroundable}</li>
|
||||
* </ul>
|
||||
* </li>
|
||||
*
|
||||
* <li>You are on EDT:
|
||||
* <ul>
|
||||
*
|
||||
* <li>Outside of {@link com.intellij.openapi.application.WriteAction WriteAction}:
|
||||
* <ul>
|
||||
* <li>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}.</li>
|
||||
* <li>Non-synchronous (you don't need to return something) - execute on the pooled thread. E.g. using {@link Task.Backgroundable}</li>
|
||||
* </ul>
|
||||
* </li>
|
||||
*
|
||||
* <li>Under {@link com.intellij.openapi.application.WriteAction WriteAction}
|
||||
* <ul>
|
||||
* <li>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.</li>
|
||||
* <li>Non-synchronous (you don't need to return something) - execute on the pooled thread. E.g. using {@link Task.Backgroundable}</li>
|
||||
* </ul>
|
||||
* </li>
|
||||
* </ul></li></ul>
|
||||
*
|
||||
* @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<? extends File> 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();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>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.</p>
|
||||
*
|
||||
* <p>The value should be set before {@link #startNotify()} invocation.
|
||||
* It is set by default in case of using GeneralCommandLine based constructor.</p>
|
||||
*
|
||||
* @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<File> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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) {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<TagsTestDataUtil.TagInfo> 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<CharSequence> 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,
|
||||
|
||||
@@ -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<LineMarkerInfo> lineMarkers, boolean withDescription, String text) {
|
||||
List<LineMarkerTagPoint> lineMarkerPoints = toLineMarkerTagPoints(lineMarkers, withDescription);
|
||||
|
||||
return insertTagsInText(lineMarkerPoints, text, (TagInfo t) -> null);
|
||||
}
|
||||
|
||||
public static String insertInfoTags(List<HighlightInfo> highlights, String text) {
|
||||
List<HighlightTagPoint> highlightPoints = toHighlightTagPoints(highlights);
|
||||
|
||||
return insertTagsInText(highlightPoints, text, (TagInfo t) -> null);
|
||||
}
|
||||
|
||||
public static String generateTextWithCaretAndSelection(@NotNull Editor editor) {
|
||||
List<TagInfo> points = Lists.newArrayList();
|
||||
points.add(new TagInfo<String>(editor.getCaretModel().getOffset(), true, "caret"));
|
||||
if (editor.getSelectionModel().hasSelection()) {
|
||||
points.add(new TagInfo<String>(editor.getSelectionModel().getSelectionStart(), true, "selection"));
|
||||
points.add(new TagInfo<String>(editor.getSelectionModel().getSelectionEnd(), false, "selection"));
|
||||
}
|
||||
|
||||
return insertTagsInText(points, editor.getDocument().getText(), (TagInfo t) -> null);
|
||||
}
|
||||
|
||||
public static String insertTagsInText(List<? extends TagInfo> tags, String text, Function<TagInfo, String> 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<? extends TagInfo> 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("</%s>", point.getName());
|
||||
}
|
||||
|
||||
builder.insert(point.offset, tagText);
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static List<LineMarkerTagPoint> toLineMarkerTagPoints(
|
||||
Collection<LineMarkerInfo> lineMarkers,
|
||||
boolean withDescription
|
||||
) {
|
||||
List<LineMarkerTagPoint> 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<HighlightTagPoint> toHighlightTagPoints(Collection<HighlightInfo> highlights) {
|
||||
List<HighlightTagPoint> 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<Data> implements Comparable<TagInfo<?>> {
|
||||
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<HighlightInfo> {
|
||||
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<LineMarkerInfo> {
|
||||
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", " ");
|
||||
}
|
||||
}
|
||||
@@ -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<Document> 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<CharSequence, Document>() {
|
||||
@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 <T> void addExplicitExtension(final LanguageExtension<T> 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 <T> void registerApplicationService(final Class<T> 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<Language> 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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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> T registerComponentInstance(MutablePicoContainer container, Class<T> key, T implementation) {
|
||||
Object old = container.getComponentInstance(key);
|
||||
container.unregisterComponent(key);
|
||||
container.registerComponentInstance(key, implementation);
|
||||
return (T)old;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<FileTypeRegistry>() {
|
||||
@Override
|
||||
public FileTypeRegistry get() {
|
||||
return FileTypeManager.getInstance();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
126
js/js.tests/test/com/intellij/mock/MockVirtualFileSystem.java
Normal file
126
js/js.tests/test/com/intellij/mock/MockVirtualFileSystem.java
Normal file
@@ -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<String, MyVirtualFile> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,9 @@ sourceSets {
|
||||
if (kotlinBuildProperties.isJpsBuildEnabled || kotlinBuildProperties.useFir) {
|
||||
none()
|
||||
} else {
|
||||
projectDefault()
|
||||
none()
|
||||
|
||||
// projectDefault()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user