From c4a89c02011814b1b24bc7f3d5f4a336437f27e8 Mon Sep 17 00:00:00 2001 From: Dmitriy Dolovov Date: Thu, 27 Feb 2020 22:23:06 +0700 Subject: [PATCH] IDE perf tests for K/N: check successful Gradle project import --- .../perf/AbstractPerformanceProjectsTest.kt | 2 + .../GradleProcessOutputInterceptor.kt | 63 ++++++++++++++ .../GradleProcessOutputInterceptor.kt.192 | 63 ++++++++++++++ .../idea/testFramework/ProjectOpenAction.kt | 5 ++ ...tatefulTestGradleProjectRefreshCallback.kt | 84 +++++++++++++++++++ .../idea/testFramework/gradleRoutines.kt | 44 ++-------- .../idea/testFramework/gradleRoutines.kt.191 | 43 +++------- .../idea/testFramework/gradleRoutines.kt.201 | 16 +++- 8 files changed, 249 insertions(+), 71 deletions(-) create mode 100644 idea/performanceTests/test/org/jetbrains/kotlin/idea/testFramework/GradleProcessOutputInterceptor.kt create mode 100644 idea/performanceTests/test/org/jetbrains/kotlin/idea/testFramework/GradleProcessOutputInterceptor.kt.192 create mode 100644 idea/performanceTests/test/org/jetbrains/kotlin/idea/testFramework/StatefulTestGradleProjectRefreshCallback.kt diff --git a/idea/performanceTests/test/org/jetbrains/kotlin/idea/perf/AbstractPerformanceProjectsTest.kt b/idea/performanceTests/test/org/jetbrains/kotlin/idea/perf/AbstractPerformanceProjectsTest.kt index 3ca32ae69f8..eba96f2a275 100644 --- a/idea/performanceTests/test/org/jetbrains/kotlin/idea/perf/AbstractPerformanceProjectsTest.kt +++ b/idea/performanceTests/test/org/jetbrains/kotlin/idea/perf/AbstractPerformanceProjectsTest.kt @@ -82,6 +82,8 @@ abstract class AbstractPerformanceProjectsTest : UsefulTestCase() { jdkTable.addJdk(internal, testRootDisposable) KotlinSdkType.setUpIfNeeded() } + + GradleProcessOutputInterceptor.install(testRootDisposable) } protected fun warmUpProject(stats: Stats, vararg filesToHighlight: String, openProject: () -> Project) { diff --git a/idea/performanceTests/test/org/jetbrains/kotlin/idea/testFramework/GradleProcessOutputInterceptor.kt b/idea/performanceTests/test/org/jetbrains/kotlin/idea/testFramework/GradleProcessOutputInterceptor.kt new file mode 100644 index 00000000000..aacdb63281a --- /dev/null +++ b/idea/performanceTests/test/org/jetbrains/kotlin/idea/testFramework/GradleProcessOutputInterceptor.kt @@ -0,0 +1,63 @@ +/* + * Copyright 2010-2020 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.testFramework + +import com.intellij.openapi.Disposable +import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskId +import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskNotificationEvent +import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskNotificationListener +import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskNotificationListener.EP_NAME as EP +import com.intellij.testFramework.ExtensionTestUtil.maskExtensions +import org.jetbrains.kotlin.idea.framework.GRADLE_SYSTEM_ID +import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull +import java.lang.Exception +import kotlin.test.assertNull + +interface GradleProcessOutputInterceptor { + companion object { + fun getInstance(): GradleProcessOutputInterceptor? = EP.extensions.firstIsInstanceOrNull() + + fun install(parentDisposable: Disposable) { + val installedExtensions = EP.extensions + + assertNull( + installedExtensions.firstIsInstanceOrNull(), + "Another ${GradleProcessOutputInterceptor::class.java.simpleName} is already installed" + ) + + maskExtensions( + EP, + listOf(GradleProcessOutputInterceptorImpl()) + installedExtensions, + parentDisposable + ) + } + } + + fun reset() + fun getOutput(): String +} + +private class GradleProcessOutputInterceptorImpl : GradleProcessOutputInterceptor, ExternalSystemTaskNotificationListener { + private val buffer = StringBuilder() + + override fun onTaskOutput(id: ExternalSystemTaskId, text: String, stdOut: Boolean) { + if (id.projectSystemId == GRADLE_SYSTEM_ID && text.isNotEmpty()) + buffer.append(text) + } + + override fun reset() = buffer.setLength(0) + override fun getOutput() = buffer.toString() + + override fun onSuccess(id: ExternalSystemTaskId) = Unit + override fun onFailure(id: ExternalSystemTaskId, e: Exception) = Unit + override fun onStatusChange(event: ExternalSystemTaskNotificationEvent) = Unit + override fun onCancel(id: ExternalSystemTaskId) = Unit + override fun onEnd(id: ExternalSystemTaskId) = Unit + override fun beforeCancel(id: ExternalSystemTaskId) = Unit + + @Suppress("UnstableApiUsage") + override fun onStart(id: ExternalSystemTaskId) = Unit +} diff --git a/idea/performanceTests/test/org/jetbrains/kotlin/idea/testFramework/GradleProcessOutputInterceptor.kt.192 b/idea/performanceTests/test/org/jetbrains/kotlin/idea/testFramework/GradleProcessOutputInterceptor.kt.192 new file mode 100644 index 00000000000..8380ca3f4be --- /dev/null +++ b/idea/performanceTests/test/org/jetbrains/kotlin/idea/testFramework/GradleProcessOutputInterceptor.kt.192 @@ -0,0 +1,63 @@ +/* + * Copyright 2010-2020 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.testFramework + +import com.intellij.openapi.Disposable +import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskId +import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskNotificationEvent +import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskNotificationListener +import com.intellij.testFramework.PlatformTestUtil.maskExtensions +import org.jetbrains.kotlin.idea.framework.GRADLE_SYSTEM_ID +import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull +import kotlin.test.assertNull +import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskNotificationListener.EP_NAME as EP + +interface GradleProcessOutputInterceptor { + companion object { + fun getInstance(): GradleProcessOutputInterceptor? = EP.extensions.firstIsInstanceOrNull() + + fun install(parentDisposable: Disposable) { + val installedExtensions = EP.extensions + + assertNull( + installedExtensions.firstIsInstanceOrNull(), + "Another ${GradleProcessOutputInterceptor::class.java.simpleName} is already installed" + ) + + maskExtensions( + EP, + listOf(GradleProcessOutputInterceptorImpl()) + installedExtensions, + parentDisposable + ) + } + } + + fun reset() + fun getOutput(): String +} + +private class GradleProcessOutputInterceptorImpl : GradleProcessOutputInterceptor, ExternalSystemTaskNotificationListener { + private val buffer = StringBuilder() + + override fun onTaskOutput(id: ExternalSystemTaskId, text: String, stdOut: Boolean) { + if (id.projectSystemId == GRADLE_SYSTEM_ID && text.isNotEmpty()) + buffer.append(text) + } + + override fun reset() = buffer.setLength(0) + override fun getOutput() = buffer.toString() + + override fun onSuccess(id: ExternalSystemTaskId) = Unit + override fun onFailure(id: ExternalSystemTaskId, e: Exception) = Unit + override fun onStatusChange(event: ExternalSystemTaskNotificationEvent) = Unit + override fun onCancel(id: ExternalSystemTaskId) = Unit + override fun onEnd(id: ExternalSystemTaskId) = Unit + override fun beforeCancel(id: ExternalSystemTaskId) = Unit + override fun onQueued(id: ExternalSystemTaskId, workingDir: String?) = Unit + + @Suppress("UnstableApiUsage") + override fun onStart(id: ExternalSystemTaskId) = Unit +} diff --git a/idea/performanceTests/test/org/jetbrains/kotlin/idea/testFramework/ProjectOpenAction.kt b/idea/performanceTests/test/org/jetbrains/kotlin/idea/testFramework/ProjectOpenAction.kt index 1c133e83f32..5220c650776 100644 --- a/idea/performanceTests/test/org/jetbrains/kotlin/idea/testFramework/ProjectOpenAction.kt +++ b/idea/performanceTests/test/org/jetbrains/kotlin/idea/testFramework/ProjectOpenAction.kt @@ -85,6 +85,11 @@ enum class ProjectOpenAction { refreshGradleProject(projectPath, project) + assertTrue( + ModuleManager.getInstance(project).modules.isNotEmpty(), + "Gradle project $projectName at $projectPath has to have at least one module" + ) + return project } diff --git a/idea/performanceTests/test/org/jetbrains/kotlin/idea/testFramework/StatefulTestGradleProjectRefreshCallback.kt b/idea/performanceTests/test/org/jetbrains/kotlin/idea/testFramework/StatefulTestGradleProjectRefreshCallback.kt new file mode 100644 index 00000000000..21c06df42ea --- /dev/null +++ b/idea/performanceTests/test/org/jetbrains/kotlin/idea/testFramework/StatefulTestGradleProjectRefreshCallback.kt @@ -0,0 +1,84 @@ +/* + * Copyright 2010-2020 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.testFramework + +import com.intellij.openapi.components.ServiceManager +import com.intellij.openapi.externalSystem.model.DataNode +import com.intellij.openapi.externalSystem.model.project.ProjectData +import com.intellij.openapi.externalSystem.service.project.ExternalProjectRefreshCallback +import com.intellij.openapi.externalSystem.service.project.ProjectDataManager +import com.intellij.openapi.project.Project +import com.intellij.openapi.project.guessProjectDir +import java.io.Closeable +import kotlin.test.assertFalse +import kotlin.test.fail + +class StatefulTestGradleProjectRefreshCallback( + private val projectPath: String, + private val project: Project +) : ExternalProjectRefreshCallback, Closeable { + + private class Error(val message: String, val details: String? = null) + + private var alreadyUsed = false + private var error: Error? = null + + init { + GradleProcessOutputInterceptor.getInstance()?.reset() + } + + override fun onSuccess(externalProject: DataNode?) { + checkAlreadyUsed() + + if (externalProject == null) { + error = Error("Got null external project after Gradle import") + return + } + ServiceManager.getService(ProjectDataManager::class.java).importData(externalProject, project, true) + } + + override fun onFailure(errorMessage: String, errorDetails: String?) { + checkAlreadyUsed() + + error = Error(errorMessage, errorDetails) + } + + override fun close() = assertError() + + fun assertError() { + val error = error ?: return + + val failure = buildString { + appendln("Gradle import failed for ${project.name} at $projectPath") + + project.guessProjectDir() + + append("=".repeat(40)).appendln(" Error message:") + appendln(error.message.trimEnd()) + + append("=".repeat(40)).appendln(" Error details:") + appendln(error.details?.trimEnd().orEmpty()) + + append("=".repeat(40)).appendln(" Gradle process output:") + appendln(GradleProcessOutputInterceptor.getInstance()?.getOutput()?.trimEnd() ?: "") + + appendln("=".repeat(40)) + } + + fail(failure) + } + + private fun checkAlreadyUsed() { + assertFalse( + alreadyUsed, + "${StatefulTestGradleProjectRefreshCallback::class.java} can be used only once." + + " Please create a new instance of ${StatefulTestGradleProjectRefreshCallback::class.java} every time you" + + " do import from Gradle." + ) + + alreadyUsed = true + } +} diff --git a/idea/performanceTests/test/org/jetbrains/kotlin/idea/testFramework/gradleRoutines.kt b/idea/performanceTests/test/org/jetbrains/kotlin/idea/testFramework/gradleRoutines.kt index 3c4f6cf4d2c..9bc9ff48199 100644 --- a/idea/performanceTests/test/org/jetbrains/kotlin/idea/testFramework/gradleRoutines.kt +++ b/idea/performanceTests/test/org/jetbrains/kotlin/idea/testFramework/gradleRoutines.kt @@ -5,13 +5,8 @@ package org.jetbrains.kotlin.idea.testFramework -import com.intellij.openapi.components.ServiceManager -import com.intellij.openapi.externalSystem.model.DataNode -import com.intellij.openapi.externalSystem.model.project.ProjectData -import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskId +import com.intellij.openapi.externalSystem.importing.ImportSpecBuilder import com.intellij.openapi.externalSystem.service.execution.ProgressExecutionMode -import com.intellij.openapi.externalSystem.service.project.ExternalProjectRefreshCallback -import com.intellij.openapi.externalSystem.service.project.ProjectDataManager import com.intellij.openapi.externalSystem.service.project.manage.ExternalProjectsManagerImpl import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil import com.intellij.openapi.externalSystem.util.ExternalSystemUtil @@ -58,41 +53,20 @@ private fun _attachGradleProjectAndRefresh( ExternalSystemUtil.ensureToolWindowInitialized(project, GradleConstants.SYSTEM_ID) } } - //ExternalProjectsManagerImpl.getInstance(project).setStoreExternally(false) ExternalProjectsManagerImpl.disableProjectWatcherAutoUpdate(project) val settings = ExternalSystemApiUtil.getSettings(project, GradleConstants.SYSTEM_ID) if (settings.getLinkedProjectSettings(externalProjectPath) == null) { settings.linkProject(gradleProjectSettings) } - //ExternalSystemUtil.refreshProject(project, GradleConstants.SYSTEM_ID, externalProjectPath, false, ProgressExecutionMode.MODAL_SYNC) - val progressExecutionMode = ProgressExecutionMode.MODAL_SYNC - val externalSystemId = GradleConstants.SYSTEM_ID - val callback = object : ExternalProjectRefreshCallback { - override fun onFailure(errorMessage: String, errorDetails: String?) { - super.onFailure(errorMessage, errorDetails) - throw RuntimeException(errorMessage) - } - - override fun onFailure( - externalTaskId: ExternalSystemTaskId, - errorMessage: String, - errorDetails: String? - ) { - super.onFailure(externalTaskId, errorMessage, errorDetails) - throw RuntimeException(errorMessage) - } - - override fun onSuccess(externalProject: DataNode?) { - if (externalProject == null) { - return - } - val synchronous = progressExecutionMode == ProgressExecutionMode.MODAL_SYNC - ServiceManager.getService( - ProjectDataManager::class.java - ).importData(externalProject, project, synchronous) - } + StatefulTestGradleProjectRefreshCallback(externalProjectPath, project).use { callback -> + ExternalSystemUtil.refreshProject( + externalProjectPath, + ImportSpecBuilder(project, GradleConstants.SYSTEM_ID) + .use(ProgressExecutionMode.MODAL_SYNC) + .callback(callback) + .build() + ) } - ExternalSystemUtil.refreshProject(project, externalSystemId, externalProjectPath, callback, false, progressExecutionMode, true) } diff --git a/idea/performanceTests/test/org/jetbrains/kotlin/idea/testFramework/gradleRoutines.kt.191 b/idea/performanceTests/test/org/jetbrains/kotlin/idea/testFramework/gradleRoutines.kt.191 index cf8c2922f95..d03a6bedf0a 100644 --- a/idea/performanceTests/test/org/jetbrains/kotlin/idea/testFramework/gradleRoutines.kt.191 +++ b/idea/performanceTests/test/org/jetbrains/kotlin/idea/testFramework/gradleRoutines.kt.191 @@ -5,50 +5,27 @@ package org.jetbrains.kotlin.idea.testFramework -import com.intellij.ide.impl.ProjectUtil import com.intellij.openapi.externalSystem.importing.ImportSpecBuilder import com.intellij.openapi.externalSystem.service.execution.ProgressExecutionMode import com.intellij.openapi.externalSystem.util.ExternalSystemUtil -import com.intellij.openapi.fileEditor.FileDocumentManager import com.intellij.openapi.project.Project -import com.intellij.openapi.vfs.LocalFileSystem -import com.intellij.testFramework.PlatformTestUtil -import com.intellij.testFramework.runInEdtAndWait -import com.intellij.openapi.project.ex.ProjectManagerEx import org.jetbrains.plugins.gradle.service.project.GradleProjectOpenProcessor import org.jetbrains.plugins.gradle.util.GradleConstants -import java.io.File import java.nio.file.Paths fun refreshGradleProject(projectPath: String, project: Project) { GradleProjectOpenProcessor.openGradleProject(project, null, Paths.get(projectPath)) - val gradleArguments = System.getProperty("kotlin.test.gradle.import.arguments") - ExternalSystemUtil.refreshProjects( - ImportSpecBuilder(project, GradleConstants.SYSTEM_ID) - .forceWhenUptodate() - .useDefaultCallback() - .use(ProgressExecutionMode.MODAL_SYNC) - .also { - gradleArguments?.run(it::withArguments) - } - ) + StatefulTestGradleProjectRefreshCallback(projectPath, project).use { callback -> + ExternalSystemUtil.refreshProjects( + ImportSpecBuilder(project, GradleConstants.SYSTEM_ID) + .use(ProgressExecutionMode.MODAL_SYNC) + .forceWhenUptodate() + .withArguments(System.getProperty("kotlin.test.gradle.import.arguments")) + .callback(callback) + .build() + ) + } dispatchAllInvocationEvents() } - -fun openGradleProject(projectPath: String, project: Project) { - dispatchAllInvocationEvents() - - val virtualFile = LocalFileSystem.getInstance().refreshAndFindFileByPath(projectPath)!! - - FileDocumentManager.getInstance().saveAllDocuments() - - val path = Paths.get(virtualFile.path) - GradleProjectOpenProcessor.openGradleProject(project, null, path) - - dispatchAllInvocationEvents() - runInEdtAndWait { - PlatformTestUtil.saveProject(project) - } - } diff --git a/idea/performanceTests/test/org/jetbrains/kotlin/idea/testFramework/gradleRoutines.kt.201 b/idea/performanceTests/test/org/jetbrains/kotlin/idea/testFramework/gradleRoutines.kt.201 index cfeabed0fc2..716d52cdf1b 100644 --- a/idea/performanceTests/test/org/jetbrains/kotlin/idea/testFramework/gradleRoutines.kt.201 +++ b/idea/performanceTests/test/org/jetbrains/kotlin/idea/testFramework/gradleRoutines.kt.201 @@ -5,6 +5,7 @@ package org.jetbrains.kotlin.idea.testFramework +import com.intellij.openapi.externalSystem.importing.ImportSpecBuilder import com.intellij.openapi.externalSystem.service.execution.ProgressExecutionMode import com.intellij.openapi.externalSystem.service.project.manage.ExternalProjectsManagerImpl import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil @@ -16,10 +17,11 @@ import org.jetbrains.plugins.gradle.service.project.open.setupGradleSettings import org.jetbrains.plugins.gradle.settings.GradleProjectSettings import org.jetbrains.plugins.gradle.util.GradleConstants import org.jetbrains.plugins.gradle.util.GradleLog +import java.io.File import kotlin.test.assertNotNull fun refreshGradleProject(projectPath: String, project: Project) { - _importProject(projectPath, project) + _importProject(File(projectPath).absolutePath, project) dispatchAllInvocationEvents() } @@ -55,6 +57,14 @@ private fun _attachGradleProjectAndRefresh( if (settings.getLinkedProjectSettings(externalProjectPath) == null) { settings.linkProject(gradleProjectSettings) } - //ExternalSystemUtil.refreshProject(project, GradleConstants.SYSTEM_ID, externalProjectPath, true, ProgressExecutionMode.MODAL_SYNC) - ExternalSystemUtil.refreshProject(project, GradleConstants.SYSTEM_ID, externalProjectPath, false, ProgressExecutionMode.MODAL_SYNC) + + StatefulTestGradleProjectRefreshCallback(externalProjectPath, project).use { callback -> + ExternalSystemUtil.refreshProject( + externalProjectPath, + ImportSpecBuilder(project, GradleConstants.SYSTEM_ID) + .use(ProgressExecutionMode.MODAL_SYNC) + .callback(callback) + .build() + ) + } }