Refactor MultiplePluginVersionGradleImportingTestCase

By this commit:
- Used `isTeamcityBuild` flag for a separating local run of the tests from CI
  For local run by default uses master version of gradle-plugin and
  LATEST_SUPPORTED_GRADLE_VERSION of the Gradle.
  But you can specify versions for local run by overriding sysenv
  `IMPORTING_TESTS_LOCAL_RUN_PARAMS`.
  For example: export IMPORTING_TESTS_LOCAL_RUN_PARAMS=6.7.1:1.4.30
- Moved main logic from `GradleImportingTestCase` to
  `MultiplePluginVersionGradleImportingTestCase` for removing the first one later
- Removed `AbstractModelBuilderTest` as useless only
 `DistributionLocator` left from this class.
This commit is contained in:
Alexander Dudinsky
2021-02-12 15:52:40 +03:00
parent b59f668a50
commit e96f64e6c9
6 changed files with 560 additions and 311 deletions
@@ -1,233 +0,0 @@
/*
* Copyright 2010-2019 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 org.jetbrains.kotlin.idea.codeInsight.gradle;
import com.google.common.collect.Multimap;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.io.StreamUtil;
import com.intellij.testFramework.IdeaTestUtil;
import com.intellij.util.containers.ContainerUtil;
import org.codehaus.groovy.runtime.typehandling.ShortTypeHandling;
import org.gradle.tooling.BuildActionExecuter;
import org.gradle.tooling.GradleConnector;
import org.gradle.tooling.ProjectConnection;
import org.gradle.tooling.internal.consumer.DefaultGradleConnector;
import org.gradle.util.GradleVersion;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.gradle.model.ClassSetProjectImportModelProvider;
import org.jetbrains.plugins.gradle.model.ExternalProject;
import org.jetbrains.plugins.gradle.model.ProjectImportAction;
import org.jetbrains.plugins.gradle.service.execution.GradleExecutionHelper;
import org.jetbrains.plugins.gradle.tooling.builder.ModelBuildScriptClasspathBuilderImpl;
import org.jetbrains.plugins.gradle.util.GradleConstants;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.rules.TestName;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assume.assumeThat;
// part of org.jetbrains.plugins.gradle.tooling.builder.AbstractModelBuilderTest
@RunWith(value = Parameterized.class)
public abstract class AbstractModelBuilderTest {
public static final Object[][] SUPPORTED_GRADLE_VERSIONS = {{"4.9"}, {"5.6.4"}, {"6.1.1"}, {"6.5.1"}};
private static final Pattern TEST_METHOD_NAME_PATTERN = Pattern.compile("(.*)\\[(\\d*: with Gradle-.*)\\]");
private static File ourTempDir;
@NotNull
private final String gradleVersion;
private File testDir;
private ProjectImportAction.AllModels allModels;
@Rule public TestName name = new TestName();
@Rule public VersionMatcherRule versionMatcherRule = new VersionMatcherRule();
public AbstractModelBuilderTest(@NotNull String gradleVersion) {
this.gradleVersion = gradleVersion;
}
@Parameterized.Parameters(name = "{index}: with Gradle-{0}")
public static Collection<Object[]> data() {
return Arrays.asList(SUPPORTED_GRADLE_VERSIONS);
}
@Before
public void setUp() throws Exception {
assumeThat(gradleVersion, versionMatcherRule.getMatcher());
ensureTempDirCreated();
String methodName = name.getMethodName();
Matcher m = TEST_METHOD_NAME_PATTERN.matcher(methodName);
if (m.matches()) {
methodName = m.group(1);
}
testDir = new File(ourTempDir, methodName);
FileUtil.ensureExists(testDir);
InputStream buildScriptStream = getClass().getResourceAsStream("/" + methodName + "/" + GradleConstants.DEFAULT_SCRIPT_NAME);
try {
FileUtil.writeToFile(
new File(testDir, GradleConstants.DEFAULT_SCRIPT_NAME),
FileUtil.loadTextAndClose(buildScriptStream)
);
}
finally {
StreamUtil.closeStream(buildScriptStream);
}
InputStream settingsStream = getClass().getResourceAsStream("/" + methodName + "/" + GradleConstants.SETTINGS_FILE_NAME);
try {
if (settingsStream != null) {
FileUtil.writeToFile(
new File(testDir, GradleConstants.SETTINGS_FILE_NAME),
FileUtil.loadTextAndClose(settingsStream)
);
}
}
finally {
StreamUtil.closeStream(settingsStream);
}
GradleConnector connector = GradleConnector.newConnector();
URI distributionUri = new DistributionLocator().getDistributionFor(GradleVersion.version(gradleVersion));
connector.useDistribution(distributionUri);
connector.forProjectDirectory(testDir);
int daemonMaxIdleTime = 10;
try {
daemonMaxIdleTime = Integer.parseInt(System.getProperty("gradleDaemonMaxIdleTime", "10"));
}
catch (NumberFormatException ignore) {
}
((DefaultGradleConnector) connector).daemonMaxIdleTime(daemonMaxIdleTime, TimeUnit.SECONDS);
ProjectConnection connection = connector.connect();
try {
ProjectImportAction projectImportAction = new ProjectImportAction(false);
projectImportAction.addProjectImportModelProvider(new ClassSetProjectImportModelProvider(getModels()));
BuildActionExecuter<ProjectImportAction.AllModels> buildActionExecutor = connection.action(projectImportAction);
File initScript = GradleExecutionHelper.generateInitScript(false, getToolingExtensionClasses());
assertNotNull(initScript);
String jdkHome = IdeaTestUtil.requireRealJdkHome();
buildActionExecutor.setJavaHome(new File(jdkHome));
buildActionExecutor.setJvmArguments("-Xmx128m", "-XX:MaxPermSize=64m");
buildActionExecutor
.withArguments("--info", "--recompile-scripts", GradleConstants.INIT_SCRIPT_CMD_OPTION, initScript.getAbsolutePath());
allModels = buildActionExecutor.run();
assertNotNull(allModels);
}
finally {
connection.close();
}
}
@NotNull
private static Set<Class<?>> getToolingExtensionClasses() {
Set<Class<?>> classes = ContainerUtil.set(
ExternalProject.class,
// gradle-tooling-extension-api jar
ProjectImportAction.class,
// gradle-tooling-extension-impl jar
ModelBuildScriptClasspathBuilderImpl.class,
Multimap.class,
ShortTypeHandling.class
);
ContainerUtil.addAllNotNull(classes, doGetToolingExtensionClasses());
return classes;
}
@NotNull
private static Set<Class<?>> doGetToolingExtensionClasses() {
return Collections.emptySet();
}
@After
public void tearDown() throws Exception {
if (testDir != null) {
FileUtil.delete(testDir);
}
}
protected abstract Set<Class<?>> getModels();
private static void ensureTempDirCreated() throws IOException {
if (ourTempDir != null) return;
ourTempDir = new File(FileUtil.getTempDirectory(), "gradleTests");
FileUtil.delete(ourTempDir);
FileUtil.ensureExists(ourTempDir);
}
public static class DistributionLocator {
private static final String RELEASE_REPOSITORY_ENV = "GRADLE_RELEASE_REPOSITORY";
private static final String SNAPSHOT_REPOSITORY_ENV = "GRADLE_SNAPSHOT_REPOSITORY";
private static final String GRADLE_RELEASE_REPO = "https://services.gradle.org/distributions";
private static final String GRADLE_SNAPSHOT_REPO = "https://services.gradle.org/distributions-snapshots";
@NotNull private final String myReleaseRepoUrl;
@NotNull private final String mySnapshotRepoUrl;
public DistributionLocator() {
this(DistributionLocator.getRepoUrl(false), DistributionLocator.getRepoUrl(true));
}
public DistributionLocator(@NotNull String releaseRepoUrl, @NotNull String snapshotRepoUrl) {
myReleaseRepoUrl = releaseRepoUrl;
mySnapshotRepoUrl = snapshotRepoUrl;
}
@NotNull
public URI getDistributionFor(@NotNull GradleVersion version) throws URISyntaxException {
return getDistribution(getDistributionRepository(version), version, "gradle", "bin");
}
@NotNull
private String getDistributionRepository(@NotNull GradleVersion version) {
return version.isSnapshot() ? mySnapshotRepoUrl : myReleaseRepoUrl;
}
private static URI getDistribution(
@NotNull String repositoryUrl,
@NotNull GradleVersion version,
@NotNull String archiveName,
@NotNull String archiveClassifier
) throws URISyntaxException {
return new URI(String.format("%s/%s-%s-%s.zip", repositoryUrl, archiveName, version.getVersion(), archiveClassifier));
}
@NotNull
public static String getRepoUrl(boolean isSnapshotUrl) {
String envRepoUrl = System.getenv(isSnapshotUrl ? SNAPSHOT_REPOSITORY_ENV : RELEASE_REPOSITORY_ENV);
if (envRepoUrl != null) return envRepoUrl;
return isSnapshotUrl ? GRADLE_SNAPSHOT_REPO : GRADLE_RELEASE_REPO;
}
}
}
@@ -0,0 +1,58 @@
/*
* Copyright 2010-2019 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 org.jetbrains.kotlin.idea.codeInsight.gradle;
import org.gradle.util.GradleVersion;
import org.jetbrains.annotations.NotNull;
import java.net.URI;
import java.net.URISyntaxException;
public class DistributionLocator {
private static final String RELEASE_REPOSITORY_ENV = "GRADLE_RELEASE_REPOSITORY";
private static final String SNAPSHOT_REPOSITORY_ENV = "GRADLE_SNAPSHOT_REPOSITORY";
private static final String GRADLE_RELEASE_REPO = "https://services.gradle.org/distributions";
private static final String GRADLE_SNAPSHOT_REPO = "https://services.gradle.org/distributions-snapshots";
@NotNull private final String myReleaseRepoUrl;
@NotNull private final String mySnapshotRepoUrl;
public DistributionLocator() {
this(getRepoUrl(false), getRepoUrl(true));
}
public DistributionLocator(@NotNull String releaseRepoUrl, @NotNull String snapshotRepoUrl) {
myReleaseRepoUrl = releaseRepoUrl;
mySnapshotRepoUrl = snapshotRepoUrl;
}
@NotNull
public URI getDistributionFor(@NotNull GradleVersion version) throws URISyntaxException {
return getDistribution(getDistributionRepository(version), version, "gradle", "bin");
}
@NotNull
private String getDistributionRepository(@NotNull GradleVersion version) {
return version.isSnapshot() ? mySnapshotRepoUrl : myReleaseRepoUrl;
}
private static URI getDistribution(
@NotNull String repositoryUrl,
@NotNull GradleVersion version,
@NotNull String archiveName,
@NotNull String archiveClassifier
) throws URISyntaxException {
return new URI(String.format("%s/%s-%s-%s.zip", repositoryUrl, archiveName, version.getVersion(), archiveClassifier));
}
@NotNull
public static String getRepoUrl(boolean isSnapshotUrl) {
String envRepoUrl = System.getenv(isSnapshotUrl ? SNAPSHOT_REPOSITORY_ENV : RELEASE_REPOSITORY_ENV);
if (envRepoUrl != null) return envRepoUrl;
return isSnapshotUrl ? GRADLE_SNAPSHOT_REPO : GRADLE_RELEASE_REPO;
}
}
@@ -17,10 +17,10 @@ abstract class MasterPluginVersionGradleImportingTestCase : MultiplePluginVersio
@JvmStatic
@Parameterized.Parameters(name = "{index}: Gradle-{0}, KotlinGradlePlugin-{1}")
fun data(): Collection<Array<Any>> {
return (AbstractModelBuilderTest.SUPPORTED_GRADLE_VERSIONS).map { gradleVersion ->
return (SUPPORTED_GRADLE_VERSIONS).map { gradleVersion ->
arrayOf<Any>(
gradleVersion[0],
LATEST_SUPPORTED_VERSION
gradleVersion,
MASTER_VERSION_OF_PLUGIN
)
}.toList()
}
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.
*/
@@ -9,17 +9,101 @@
*/
package org.jetbrains.kotlin.idea.codeInsight.gradle
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.PathManager
import com.intellij.openapi.application.Result
import com.intellij.openapi.application.WriteAction
import com.intellij.openapi.externalSystem.importing.ImportSpec
import com.intellij.openapi.externalSystem.importing.ImportSpecBuilder
import com.intellij.openapi.externalSystem.model.ProjectSystemId
import com.intellij.openapi.externalSystem.model.settings.ExternalSystemExecutionSettings
import com.intellij.openapi.externalSystem.settings.ExternalProjectSettings
import com.intellij.openapi.externalSystem.settings.ExternalSystemSettingsListenerAdapter
import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil
import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.openapi.fileEditor.impl.LoadTextUtil
import com.intellij.openapi.fileTypes.FileTypeManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.JavaSdk
import com.intellij.openapi.projectRoots.ProjectJdkTable
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.projectRoots.impl.ProjectJdkTableImpl
import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil
import com.intellij.openapi.roots.ProjectRootManager
import com.intellij.openapi.ui.Messages
import com.intellij.openapi.ui.TestDialog
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.newvfs.impl.VfsRootAccess
import com.intellij.testFramework.IdeaTestUtil
import com.intellij.testFramework.RunAll
import com.intellij.testFramework.UsefulTestCase
import com.intellij.testFramework.VfsTestUtil
import com.intellij.util.ArrayUtilRt
import com.intellij.util.PathUtil
import com.intellij.util.SmartList
import com.intellij.util.ThrowableRunnable
import com.intellij.util.containers.ContainerUtil
import junit.framework.TestCase
import org.gradle.StartParameter
import org.gradle.util.GradleVersion
import org.gradle.wrapper.GradleWrapperMain
import org.gradle.wrapper.PathAssembler
import org.intellij.lang.annotations.Language
import org.jetbrains.annotations.NonNls
import org.jetbrains.kotlin.idea.test.GradleProcessOutputInterceptor
import org.jetbrains.kotlin.idea.test.KotlinSdkCreationChecker
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase
import org.jetbrains.kotlin.idea.util.getProjectJdkTableSafe
import org.jetbrains.kotlin.test.JUnitParameterizedWithIdeaConfigurationRunner
import org.jetbrains.kotlin.test.KotlinTestUtils
import org.jetbrains.kotlin.test.RunnerFactoryWithMuteInDatabase
import org.jetbrains.plugins.gradle.settings.DistributionType
import org.jetbrains.plugins.gradle.settings.GradleProjectSettings
import org.jetbrains.plugins.gradle.settings.GradleSettings
import org.jetbrains.plugins.gradle.settings.GradleSystemSettings
import org.jetbrains.plugins.gradle.util.GradleConstants
import org.jetbrains.plugins.gradle.util.GradleUtil
import org.jetbrains.plugins.groovy.GroovyFileType
import org.junit.Assume
import org.junit.Rule
import org.junit.rules.TestName
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import java.io.File
import java.io.IOException
import java.io.StringWriter
import java.net.URISyntaxException
import java.util.*
import java.util.zip.ZipException
import java.util.zip.ZipFile
import kotlin.collections.HashMap
const val mppImportTestMinVersionForMaster = "6.1+"
const val legacyMppImportTestMinVersionForMaster = "6.1+"
@RunWith(value = JUnitParameterizedWithIdeaConfigurationRunner::class)
@Parameterized.UseParametersRunnerFactory(RunnerFactoryWithMuteInDatabase::class)
abstract class MultiplePluginVersionGradleImportingTestCase : ExternalSystemImportingTestCase() {
protected var sdkCreationChecker: KotlinSdkCreationChecker? = null
private val removedSdks: MutableList<Sdk> = SmartList()
@JvmField
@Rule
var name = TestName()
@JvmField
@Rule
var testWatcher = ImportingTestWatcher()
@JvmField
@Parameterized.Parameter(0)
var gradleVersion: String = ""
@JvmField
@Parameterized.Parameter(1)
var gradleKotlinPluginVersion: String = ""
abstract class MultiplePluginVersionGradleImportingTestCase : GradleImportingTestCase() {
@Rule
@JvmField
var pluginVersionMatchingRule = PluginTargetVersionsRule()
@@ -27,76 +111,405 @@ abstract class MultiplePluginVersionGradleImportingTestCase : GradleImportingTes
val project: Project
get() = myProject
private val isMasterVersion: Boolean
get() = gradleKotlinPluginVersion == MASTER_VERSION_OF_PLUGIN
override fun isApplicableTest(): Boolean {
open fun isApplicableTest(): Boolean {
return pluginVersionMatchingRule.matches(
gradleVersion, gradleKotlinPluginVersion,
gradleKotlinPluginVersionType == LATEST_SUPPORTED_VERSION
gradleVersion,
gradleKotlinPluginVersion.substringBefore("-"),
isMasterVersion
)
}
override fun importProject() {
super.importProject(skipIndexing = true)
private lateinit var myProjectSettings: GradleProjectSettings
private lateinit var myJdkHome: String
open fun jvmHeapArgsByGradleVersion(version: String): String = when {
version.startsWith("4.") ->
// work-around due to memory leak in class loaders in gradle. The amount of used memory in the gradle daemon
// is drammatically increased on every reimport of project due to sequential compilation of build scripts.
// see more details in https://github.com/gradle/gradle/commit/b483d29f315758913791fe58d572fa6bafa0395c
"-Xmx256m -XX:MaxPermSize=64m"
else ->
// 128M should be enough for gradle 5.0+ (leak is fixed), and <4.0 (amount of tests is less)
"-Xms128M -Xmx192m -XX:MaxPermSize=64m"
}
@JvmField
@Parameterized.Parameter(1)
var gradleKotlinPluginVersionType: String = MINIMAL_SUPPORTED_VERSION
override fun setUp() {
myJdkHome = IdeaTestUtil.requireRealJdkHome()
super.setUp()
Assume.assumeTrue(isApplicableTest())
removedSdks.clear()
runWrite {
val jdkTable = getProjectJdkTableSafe()
jdkTable.findJdk(GRADLE_JDK_NAME)?.let { jdkTable.removeJdk(it) }
val jdkHomeDir = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(File(myJdkHome))!!
val jdk = SdkConfigurationUtil.setupSdk(
arrayOfNulls(0), jdkHomeDir, JavaSdk.getInstance(), true, null,
GRADLE_JDK_NAME
)
TestCase.assertNotNull("Cannot create JDK for $myJdkHome", jdk)
if (!jdkTable.allJdks.contains(jdk)) {
(jdkTable as ProjectJdkTableImpl).addTestJdk(jdk!!, testRootDisposable)
ProjectRootManager.getInstance(myProject).projectSdk = jdk
}
FileTypeManager.getInstance().associateExtension(GroovyFileType.GROOVY_FILE_TYPE, "gradle")
}
myProjectSettings = GradleProjectSettings().apply {
this.isUseQualifiedModuleNames = false
}
System.setProperty(
ExternalSystemExecutionSettings.REMOTE_PROCESS_IDLE_TTL_IN_MS_KEY,
GRADLE_DAEMON_TTL_MS.toString()
)
val gradleKotlinPluginVersion: String
get() = KOTLIN_GRADLE_PLUGIN_VERSION_DESCRIPTION_TO_VERSION[gradleKotlinPluginVersionType] ?: gradleKotlinPluginVersionType
val distribution = WriteAction.computeAndWait<PathAssembler.LocalDistribution, Throwable> { configureWrapper() }
val allowedRoots = ArrayList<String>()
collectAllowedRoots(allowedRoots, distribution)
if (allowedRoots.isNotEmpty()) {
VfsRootAccess.allowRootAccess(myTestFixture.testRootDisposable, *ArrayUtilRt.toStringArray(allowedRoots))
}
GradleSettings.getInstance(myProject).gradleVmOptions =
"${jvmHeapArgsByGradleVersion(gradleVersion)} -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=${System.getProperty("user.dir")}"
sdkCreationChecker = KotlinSdkCreationChecker()
GradleProcessOutputInterceptor.install(testRootDisposable)
}
override fun tearDown() {
RunAll(
ThrowableRunnable {
runWrite {
Arrays.stream(ProjectJdkTable.getInstance().allJdks).forEach { jdk: Sdk ->
(ProjectJdkTable.getInstance() as ProjectJdkTableImpl).removeTestJdk(jdk)
if (jdk is Disposable) {
Disposer.dispose((jdk as Disposable))
}
}
removedSdks.forEach { sdk -> SdkConfigurationUtil.addSdk(sdk) }
removedSdks.clear()
}
},
ThrowableRunnable {
Messages.setTestDialog(TestDialog.DEFAULT)
deleteBuildSystemDirectory()
// was FileUtil.delete(BuildManager.getInstance().buildSystemDirectory.toFile())
sdkCreationChecker?.removeNewKotlinSdk()
},
ThrowableRunnable {
super.tearDown()
}
).run()
}
override fun collectAllowedRoots(roots: MutableList<String>) {
super.collectAllowedRoots(roots)
roots.add(myJdkHome)
roots.addAll(ExternalSystemTestCase.collectRootsInside(myJdkHome))
roots.add(PathManager.getConfigPath())
}
protected open fun collectAllowedRoots(
roots: MutableList<String>,
distribution: PathAssembler.LocalDistribution?
) {
//Note: could be required to use:
//Environment.getEnvVariable("JAVA_HOME")
roots.add(myJdkHome)
}
override fun getName(): String {
return if (name.methodName == null) super.getName() else FileUtil.sanitizeFileName(name.methodName)
}
override fun getExternalSystemConfigFileName(): String = "build.gradle"
@Throws(IOException::class)
protected open fun importProjectUsingSingeModulePerGradleProject(config: String? = null, skipIndexing: Boolean? = null) {
currentExternalProjectSettings.isResolveModulePerSourceSet = false
importProject(config, skipIndexing)
}
open fun importProject() {
importProject(skipIndexing = true)
}
override fun importProject(skipIndexing: Boolean?) {
ExternalSystemApiUtil.subscribe(
myProject,
GradleConstants.SYSTEM_ID,
object : ExternalSystemSettingsListenerAdapter<ExternalProjectSettings>() {
override fun onProjectsLinked(settings: Collection<ExternalProjectSettings>) {
val item = ContainerUtil.getFirstItem<Any>(settings)
if (item is GradleProjectSettings) {
item.gradleJvm = GRADLE_JDK_NAME
}
}
})
super.importProject(skipIndexing)
}
@Throws(IOException::class)
override fun importProject(@NonNls @Language("Groovy") config: String?, skipIndexing: Boolean?) {
var config = config
config = injectRepo(config)
super.importProject(config, skipIndexing)
}
override fun handleImportFailure(errorMessage: String, errorDetails: String?) {
val gradleOutput = GradleProcessOutputInterceptor.getInstance()?.getOutput().orEmpty()
// Typically Gradle error message consists of a line with the description of the error followed by
// a multi-line stacktrace. The idea is to cut off the stacktrace if it is already contained in
// the intercepted Gradle process output to avoid unnecessary verbosity.
val compactErrorMessage = when (val indexOfNewLine = errorMessage.indexOf('\n')) {
-1 -> errorMessage
else -> {
val compactErrorMessage = errorMessage.substring(0, indexOfNewLine)
val theRest = errorMessage.substring(indexOfNewLine + 1)
if (theRest in gradleOutput) compactErrorMessage else errorMessage
}
}
val failureMessage = buildString {
append("Gradle import failed: ").append(compactErrorMessage).append('\n')
if (!errorDetails.isNullOrBlank()) append("Error details: ").append(errorDetails).append('\n')
append("Gradle process output (BEGIN):\n")
append(gradleOutput)
if (!gradleOutput.endsWith('\n')) append('\n')
append("Gradle process output (END)")
}
fail(failureMessage)
}
protected open fun injectRepo(@NonNls @Language("Groovy") config: String?): String {
var config = config ?: ""
config = """allprojects {
repositories {
maven {
url 'https://repo.labs.intellij.net/repo1'
}
}}
$config"""
return config
}
override fun createImportSpec(): ImportSpec? {
val importSpecBuilder = ImportSpecBuilder(super.createImportSpec())
importSpecBuilder.withArguments("--stacktrace")
return importSpecBuilder.build()
}
override fun getCurrentExternalProjectSettings(): GradleProjectSettings = myProjectSettings
override fun getExternalSystemId(): ProjectSystemId = GradleConstants.SYSTEM_ID
@Throws(IOException::class)
protected open fun createSettingsFile(@NonNls @Language("Groovy") content: String?): VirtualFile? {
return createProjectSubFile("settings.gradle", content)
}
protected open fun testDataDirName(): String = ""
protected open fun testDataDirectory(): File {
val baseDir = "${PluginTestCaseBase.getTestDataPathBase()}/gradle/${testDataDirName()}/"
return File(baseDir, getTestName(true).substringBefore("_"))
}
protected fun configureKotlinVersionAndProperties(text: String, properties: Map<String, String>? = null): String {
var result = text
(properties ?: getDefaultPropertiesMap()).forEach { (key, value) ->
result = result.replace("{{${key}}}", value)
}
return result
}
protected open fun configureByFiles(properties: Map<String, String>? = null): List<VirtualFile> {
val rootDir = testDataDirectory()
assert(rootDir.exists()) { "Directory ${rootDir.path} doesn't exist" }
val unitedProperties = HashMap(properties ?: emptyMap()).apply { putAll(getDefaultPropertiesMap()) }
return rootDir.walk().mapNotNull {
when {
it.isDirectory -> null
!it.name.endsWith(SUFFIX) -> {
val text =
configureKotlinVersionAndProperties(FileUtil.loadFile(it, /* convertLineSeparators = */ true), unitedProperties)
val virtualFile = createProjectSubFile(it.path.substringAfter(rootDir.path + File.separator), text)
// Real file with expected testdata allows to throw nicer exceptions in
// case of mismatch, as well as open interactive diff window in IDEA
virtualFile.putUserData(VfsTestUtil.TEST_DATA_FILE_PATH, it.absolutePath)
virtualFile
}
else -> null
}
}.toList()
}
protected fun importProjectFromTestData(skipIndexing: Boolean? = null): List<VirtualFile> {
val files = configureByFiles()
importProject(skipIndexing)
return files
}
protected fun checkFiles(files: List<VirtualFile>) {
FileDocumentManager.getInstance().saveAllDocuments()
files.filter {
it.name == GradleConstants.DEFAULT_SCRIPT_NAME
|| it.name == GradleConstants.KOTLIN_DSL_SCRIPT_NAME
|| it.name == GradleConstants.SETTINGS_FILE_NAME
}
.forEach {
if (it.name == GradleConstants.SETTINGS_FILE_NAME && !File(testDataDirectory(), it.name + SUFFIX).exists()) return@forEach
val actualText = configureKotlinVersionAndProperties(LoadTextUtil.loadText(it).toString())
val expectedFileName = if (File(testDataDirectory(), it.name + ".$gradleVersion" + SUFFIX).exists()) {
it.name + ".$gradleVersion" + SUFFIX
} else {
it.name + SUFFIX
}
KotlinTestUtils.assertEqualsToFile(File(testDataDirectory(), expectedFileName), actualText)
{ s -> configureKotlinVersionAndProperties(s) }
}
}
protected open fun enableGradleDebugWithSuspend() {
GradleSystemSettings.getInstance().gradleVmOptions = "-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005"
}
@Throws(IOException::class, URISyntaxException::class)
private fun configureWrapper(): PathAssembler.LocalDistribution {
val distributionUri = DistributionLocator().getDistributionFor(GradleVersion.version(gradleVersion))
myProjectSettings.distributionType = DistributionType.DEFAULT_WRAPPED
val wrapperJarFrom = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(wrapperJar())!!
val wrapperJarFromTo = createProjectSubFile("gradle/wrapper/gradle-wrapper.jar")
runWrite {
wrapperJarFromTo.setBinaryContent(wrapperJarFrom.contentsToByteArray())
}
val properties = Properties()
properties.setProperty("distributionBase", "GRADLE_USER_HOME")
properties.setProperty("distributionPath", "wrapper/dists")
properties.setProperty("zipStoreBase", "GRADLE_USER_HOME")
properties.setProperty("zipStorePath", "wrapper/dists")
properties.setProperty("distributionUrl", distributionUri.toString())
val writer = StringWriter()
properties.store(writer, null)
createProjectSubFile("gradle/wrapper/gradle-wrapper.properties", writer.toString())
val wrapperConfiguration =
GradleUtil.getWrapperConfiguration(projectPath)
val localDistribution = PathAssembler(
StartParameter.DEFAULT_GRADLE_USER_HOME
).getDistribution(wrapperConfiguration)
val zip = localDistribution.zipFile
try {
if (zip.exists()) {
val zipFile = ZipFile(zip)
zipFile.close()
}
} catch (e: ZipException) {
e.printStackTrace()
println("Corrupted file will be removed: " + zip.path)
FileUtil.delete(zip)
} catch (e: IOException) {
e.printStackTrace()
}
return localDistribution
}
private fun runWrite(f: () -> Unit) {
object : WriteAction<Any>() {
override fun run(result: Result<Any>) {
f()
}
}.execute()
}
private fun getDefaultPropertiesMap(): Map<String, String> {
val defaultProperties = HashMap<String, String>()
defaultProperties["kotlin_plugin_version"] = gradleKotlinPluginVersion
defaultProperties["kotlin_plugin_repositories"] = repositories(false)
defaultProperties["kts_kotlin_plugin_repositories"] = repositories(true)
return defaultProperties
}
private fun repositories(useKts: Boolean): String {
val customRepositories = arrayOf(
"https://dl.bintray.com/kotlin/kotlin-dev",
)
val customMavenRepositories = customRepositories.joinToString("\n") { if (useKts) "maven(\"$it\")" else "maven { url '$it' } " }
return """
mavenCentral()
mavenLocal()
google()
jcenter()
$customMavenRepositories
""".trimIndent()
}
private fun wrapperJar(): File {
return File(PathUtil.getJarPathForClass(GradleWrapperMain::class.java))
}
companion object {
const val MINIMAL_SUPPORTED_VERSION = "minimal"// minimal supported version
const val LATEST_STABLE_VERSUON = "latest stable"
const val LATEST_SUPPORTED_VERSION = "master"// gradle plugin from current build
const val GRADLE_JDK_NAME = "Gradle JDK"
private const val GRADLE_DAEMON_TTL_MS = 10000
//should be extended with LATEST_SUPPORTED_VERSION
private val KOTLIN_GRADLE_PLUGIN_VERSIONS = listOf(MINIMAL_SUPPORTED_VERSION, LATEST_STABLE_VERSUON, LATEST_SUPPORTED_VERSION)
private val KOTLIN_GRADLE_PLUGIN_VERSION_DESCRIPTION_TO_VERSION = mapOf(
MINIMAL_SUPPORTED_VERSION to MINIMAL_SUPPORTED_GRADLE_PLUGIN_VERSION,
LATEST_STABLE_VERSUON to LATEST_STABLE_GRADLE_PLUGIN_VERSION,
LATEST_SUPPORTED_VERSION to readPluginVersion()
)
@JvmStatic
protected val SUFFIX = ".after"
private fun readPluginVersion() =
File("libraries/tools/kotlin-gradle-plugin/build/libs").listFiles()?.map { it.name }
const val RELEASED_A_YEAR_AGO_GRADLE_PLUGIN_VERSION = "1.3.72"
const val LATEST_STABLE_RELEASE_GRADLE_PLUGIN_VERSION = "1.4.30"
val MASTER_VERSION_OF_PLUGIN
get() = File("libraries/tools/kotlin-gradle-plugin/build/libs").listFiles()?.map { it.name }
?.firstOrNull { it.contains("-original.jar") }?.replace(
"kotlin-gradle-plugin-",
""
)?.replace("-original.jar", "") ?: "1.5.255-SNAPSHOT"
const val LATEST_SUPPORTED_GRADLE_VERSION = "6.5.1"
val SUPPORTED_GRADLE_VERSIONS = arrayOf("4.9", "5.6.4", LATEST_SUPPORTED_GRADLE_VERSION)
private val LOCAL_RUN_PARAMS: Array<Any> =
System.getenv("IMPORTING_TESTS_LOCAL_RUN_PARAMS") // You can specify versions for local run. For example: "6.7.1:1.4.30"
?.replace(" ", "")
?.split(":")?.toTypedArray()
?: arrayOf(LATEST_SUPPORTED_GRADLE_VERSION, MASTER_VERSION_OF_PLUGIN)
private val isTeamcityBuild = UsefulTestCase.IS_UNDER_TEAMCITY
@JvmStatic
@Parameterized.Parameters(name = "{index}: Gradle-{0}, KotlinGradlePlugin-{1}")
fun data(): Collection<Array<Any>> {
return (AbstractModelBuilderTest.SUPPORTED_GRADLE_VERSIONS).flatMap { gradleVersion ->
KOTLIN_GRADLE_PLUGIN_VERSIONS.map { kotlinVersion ->
arrayOf<Any>(
gradleVersion[0],
kotlinVersion
)
if (isTeamcityBuild)
return listOf(LOCAL_RUN_PARAMS)
return (SUPPORTED_GRADLE_VERSIONS).flatMap { gradleVersion ->
arrayOf(
RELEASED_A_YEAR_AGO_GRADLE_PLUGIN_VERSION,
LATEST_STABLE_RELEASE_GRADLE_PLUGIN_VERSION,
MASTER_VERSION_OF_PLUGIN
).map { pluginVersion ->
arrayOf(gradleVersion, pluginVersion)
}
}.toList()
}
}
}
fun repositories(useKts: Boolean): String {
return """
mavenLocal()
mavenCentral()
gradlePluginPortal()
google()
jcenter()
""".trimIndent()
}
override fun configureByFiles(properties: Map<String, String>?): List<VirtualFile> {
val unitedProperties = HashMap(properties ?: emptyMap())
unitedProperties["kotlin_plugin_version"] = gradleKotlinPluginVersion
unitedProperties["kotlin_plugin_repositories"] = repositories(false)
unitedProperties["kts_kotlin_plugin_repositories"] = repositories(true)
return super.configureByFiles(unitedProperties)
}
}
}
@@ -4,20 +4,25 @@
*/
package org.jetbrains.kotlin.idea.codeInsight.gradle;
import org.gradle.util.GradleVersion;
import org.hamcrest.CustomMatcher;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.gradle.tooling.annotation.PluginTargetVersions;
import org.jetbrains.plugins.gradle.tooling.annotation.TargetVersions;
import org.jetbrains.plugins.gradle.tooling.util.VersionMatcher;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
// modified copy of org.jetbrains.plugins.gradle.tooling.VersionMatcherRule
public class PluginTargetVersionsRule extends TestWatcher {
private final String[][] versionRules = {{"1.3.0 <=> 1.4.0", "4.0+"}, {"1.4.0+", "6.0+"}};
//Specify rules for different version Kotlin Gradle plugin and Gradle
private class TargetVersionsImpl implements TargetVersions {
private final String value;
@@ -41,40 +46,46 @@ public class PluginTargetVersionsRule extends TestWatcher {
}
}
@Nullable
private CustomMatcher<String> gradleVersionMatcher;
@Nullable
private CustomMatcher gradleVersionMatcher;
private CustomMatcher<String> pluginVersionMatcher;
@Nullable
private CustomMatcher pluginVersionMatcher;
private boolean skipForMaster;
@Nullable
private CustomMatcher gradleVersionMatcherForLatestPlugin;
private final List<CustomMatcher<String>[]> versionCombinationsMatcher = new ArrayList<>();
@NotNull
public boolean matches(String gradleVersion, String pluginVersion, boolean isLatestPluginVersion) {
boolean matchGradleVersion = gradleVersionMatcher == null || gradleVersionMatcher.matches(gradleVersion);
if (isLatestPluginVersion) {
if (gradleVersionMatcherForLatestPlugin != null) {
return gradleVersionMatcherForLatestPlugin.matches(gradleVersion);
}
return matchGradleVersion;
public boolean matches(String gradleVersion, String pluginVersion, boolean isMaster) {
if (skipForMaster && isMaster) {
return false;
}
boolean matchGradleVersion = gradleVersionMatcher == null || gradleVersionMatcher.matches(gradleVersion);
boolean pluginVersionMatches = pluginVersionMatcher == null || pluginVersionMatcher.matches(pluginVersion);
return matchGradleVersion && pluginVersionMatches;
return matchGradleVersion && pluginVersionMatches &&
versionCombinationsMatcher.stream().anyMatch(i -> i[0].matches(pluginVersion) && i[1].matches(gradleVersion));
}
@Override
@SuppressWarnings("unchecked")
protected void starting(Description d) {
final PluginTargetVersions pluginTargetVersions = d.getAnnotation(PluginTargetVersions.class);
Arrays.stream(versionRules).forEach(i -> versionCombinationsMatcher.add(
new CustomMatcher[] {
VersionMatcherRule.produceMatcher("Plugin", new TargetVersionsImpl(i[0])),
VersionMatcherRule.produceMatcher("Gradle", new TargetVersionsImpl(i[1]))
}));
PluginTargetVersions pluginTargetVersions = d.getAnnotation(PluginTargetVersions.class);
if (d.getAnnotation(TargetVersions.class) != null && pluginTargetVersions != null) {
throw new IllegalArgumentException(String.format("Annotations %s and %s could not be used together. ",
TargetVersions.class.getName(), PluginTargetVersions.class.getName()));
}
if (pluginTargetVersions == null) return;
gradleVersionMatcher = pluginTargetVersions.gradleVersion().isEmpty() ? null : VersionMatcherRule.produceMatcher("Gradle", new TargetVersionsImpl(pluginTargetVersions.gradleVersion()));
pluginVersionMatcher = pluginTargetVersions.pluginVersion().isEmpty() ? null : VersionMatcherRule.produceMatcher("Plugin", new TargetVersionsImpl(pluginTargetVersions.pluginVersion()));
gradleVersionMatcherForLatestPlugin = pluginTargetVersions.gradleVersionForLatestPlugin().isEmpty() ? null : VersionMatcherRule.produceMatcher("Gradle for latest plugin", new TargetVersionsImpl(pluginTargetVersions.gradleVersionForLatestPlugin()));
gradleVersionMatcher = pluginTargetVersions.gradleVersion().isEmpty() ? null : VersionMatcherRule
.produceMatcher("Gradle", new TargetVersionsImpl(pluginTargetVersions.gradleVersion()));
pluginVersionMatcher = pluginTargetVersions.pluginVersion().isEmpty() ? null : VersionMatcherRule
.produceMatcher("Plugin", new TargetVersionsImpl(pluginTargetVersions.pluginVersion()));
skipForMaster = pluginTargetVersions.skipForMaster();
}
}
@@ -16,5 +16,5 @@ public @interface PluginTargetVersions {
String pluginVersion() default "1.3.50+";
String gradleVersionForLatestPlugin() default "";
boolean skipForMaster() default false;
}