diff --git a/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/codeInsight/gradle/AbstractModelBuilderTest.java b/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/codeInsight/gradle/AbstractModelBuilderTest.java deleted file mode 100644 index dfc538a9f49..00000000000 --- a/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/codeInsight/gradle/AbstractModelBuilderTest.java +++ /dev/null @@ -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 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 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> getToolingExtensionClasses() { - Set> 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> doGetToolingExtensionClasses() { - return Collections.emptySet(); - } - - @After - public void tearDown() throws Exception { - if (testDir != null) { - FileUtil.delete(testDir); - } - } - - protected abstract Set> 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; - } - } -} diff --git a/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/codeInsight/gradle/DistributionLocator.java b/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/codeInsight/gradle/DistributionLocator.java new file mode 100644 index 00000000000..772c02d0be6 --- /dev/null +++ b/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/codeInsight/gradle/DistributionLocator.java @@ -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; + } +} diff --git a/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/codeInsight/gradle/MasterPluginVersionGradleImportingTestCase.kt b/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/codeInsight/gradle/MasterPluginVersionGradleImportingTestCase.kt index 0433c7a64b8..dfdca32338e 100644 --- a/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/codeInsight/gradle/MasterPluginVersionGradleImportingTestCase.kt +++ b/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/codeInsight/gradle/MasterPluginVersionGradleImportingTestCase.kt @@ -17,10 +17,10 @@ abstract class MasterPluginVersionGradleImportingTestCase : MultiplePluginVersio @JvmStatic @Parameterized.Parameters(name = "{index}: Gradle-{0}, KotlinGradlePlugin-{1}") fun data(): Collection> { - return (AbstractModelBuilderTest.SUPPORTED_GRADLE_VERSIONS).map { gradleVersion -> + return (SUPPORTED_GRADLE_VERSIONS).map { gradleVersion -> arrayOf( - gradleVersion[0], - LATEST_SUPPORTED_VERSION + gradleVersion, + MASTER_VERSION_OF_PLUGIN ) }.toList() } diff --git a/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/codeInsight/gradle/MultiplePluginVersionGradleImportingTestCase.kt b/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/codeInsight/gradle/MultiplePluginVersionGradleImportingTestCase.kt index c373357449a..b7a278d6ee5 100644 --- a/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/codeInsight/gradle/MultiplePluginVersionGradleImportingTestCase.kt +++ b/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/codeInsight/gradle/MultiplePluginVersionGradleImportingTestCase.kt @@ -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 = 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 { configureWrapper() } + + val allowedRoots = ArrayList() + 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) { + super.collectAllowedRoots(roots) + roots.add(myJdkHome) + roots.addAll(ExternalSystemTestCase.collectRootsInside(myJdkHome)) + roots.add(PathManager.getConfigPath()) + } + + protected open fun collectAllowedRoots( + roots: MutableList, + 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() { + override fun onProjectsLinked(settings: Collection) { + val item = ContainerUtil.getFirstItem(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? = null): String { + var result = text + (properties ?: getDefaultPropertiesMap()).forEach { (key, value) -> + result = result.replace("{{${key}}}", value) + } + return result + } + + protected open fun configureByFiles(properties: Map? = null): List { + 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 { + val files = configureByFiles() + importProject(skipIndexing) + return files + } + + protected fun checkFiles(files: List) { + 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() { + override fun run(result: Result) { + f() + } + }.execute() + } + + private fun getDefaultPropertiesMap(): Map { + val defaultProperties = HashMap() + 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 = + 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> { - return (AbstractModelBuilderTest.SUPPORTED_GRADLE_VERSIONS).flatMap { gradleVersion -> - KOTLIN_GRADLE_PLUGIN_VERSIONS.map { kotlinVersion -> - arrayOf( - 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?): List { - 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) - } -} - +} \ No newline at end of file diff --git a/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/codeInsight/gradle/PluginTargetVersionsRule.java b/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/codeInsight/gradle/PluginTargetVersionsRule.java index 7fcf0dd53a6..b26a53d0d0e 100644 --- a/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/codeInsight/gradle/PluginTargetVersionsRule.java +++ b/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/codeInsight/gradle/PluginTargetVersionsRule.java @@ -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 gradleVersionMatcher; @Nullable - private CustomMatcher gradleVersionMatcher; + private CustomMatcher pluginVersionMatcher; - @Nullable - private CustomMatcher pluginVersionMatcher; + private boolean skipForMaster; - @Nullable - private CustomMatcher gradleVersionMatcherForLatestPlugin; + private final List[]> 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(); } } diff --git a/idea/idea-gradle/tests/org/jetbrains/plugins/gradle/tooling/annotation/PluginTargetVersions.java b/idea/idea-gradle/tests/org/jetbrains/plugins/gradle/tooling/annotation/PluginTargetVersions.java index cb1aa9ed9de..16f7a2a9267 100644 --- a/idea/idea-gradle/tests/org/jetbrains/plugins/gradle/tooling/annotation/PluginTargetVersions.java +++ b/idea/idea-gradle/tests/org/jetbrains/plugins/gradle/tooling/annotation/PluginTargetVersions.java @@ -16,5 +16,5 @@ public @interface PluginTargetVersions { String pluginVersion() default "1.3.50+"; - String gradleVersionForLatestPlugin() default ""; + boolean skipForMaster() default false; } \ No newline at end of file