From 9c530b1121866675eac15d276c1628187518242e Mon Sep 17 00:00:00 2001 From: Vladimir Dolzhenko Date: Mon, 2 Mar 2020 23:08:11 +0100 Subject: [PATCH] Add performance stress tests --- .../perf/AbstractPerformanceProjectsTest.kt | 190 +++++++++------ .../jetbrains/kotlin/idea/perf/FileUtils.kt | 14 ++ .../kotlin/idea/perf/PerfTestBuilder.kt | 2 +- .../idea/perf/PerformanceProjectsTest.kt | 14 +- .../kotlin/idea/perf/PerformanceStressTest.kt | 119 +++++++++ .../kotlin/idea/perf/ProjectBuilder.kt | 227 ++++++++++++++++++ .../kotlin/idea/testFramework/Fixture.kt | 4 + .../idea/testFramework/ProjectOpenAction.kt | 82 ++++++- .../idea/testFramework/gradleRoutines.kt | 41 +++- .../perfTest/simpleTemplate/build.gradle.kts | 33 +++ 10 files changed, 636 insertions(+), 90 deletions(-) create mode 100644 idea/performanceTests/test/org/jetbrains/kotlin/idea/perf/FileUtils.kt create mode 100644 idea/performanceTests/test/org/jetbrains/kotlin/idea/perf/PerformanceStressTest.kt create mode 100644 idea/performanceTests/test/org/jetbrains/kotlin/idea/perf/ProjectBuilder.kt create mode 100644 idea/testData/perfTest/simpleTemplate/build.gradle.kts 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 2d1535668d2..3ca32ae69f8 100644 --- a/idea/performanceTests/test/org/jetbrains/kotlin/idea/perf/AbstractPerformanceProjectsTest.kt +++ b/idea/performanceTests/test/org/jetbrains/kotlin/idea/perf/AbstractPerformanceProjectsTest.kt @@ -13,22 +13,16 @@ import com.intellij.codeInsight.daemon.impl.IdentifierHighlighterPassFactory import com.intellij.codeInsight.lookup.LookupElement import com.intellij.ide.startup.impl.StartupManagerImpl import com.intellij.openapi.actionSystem.IdeActions -import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.runWriteAction import com.intellij.openapi.editor.EditorFactory -import com.intellij.openapi.externalSystem.service.project.manage.ExternalProjectsManagerImpl import com.intellij.openapi.fileEditor.FileDocumentManager import com.intellij.openapi.fileEditor.FileEditorManager -import com.intellij.openapi.module.ModuleManager import com.intellij.openapi.project.DumbService import com.intellij.openapi.project.Project import com.intellij.openapi.projectRoots.JavaSdk import com.intellij.openapi.projectRoots.Sdk import com.intellij.openapi.projectRoots.impl.JavaAwareProjectJdkTableImpl import com.intellij.openapi.startup.StartupManager -import com.intellij.openapi.vcs.changes.ChangeListManager -import com.intellij.openapi.vcs.changes.ChangeListManagerImpl -import com.intellij.openapi.vfs.VirtualFileManager import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiFile import com.intellij.psi.PsiManager @@ -40,14 +34,11 @@ import com.intellij.testFramework.fixtures.impl.CodeInsightTestFixtureImpl import com.intellij.util.ArrayUtilRt import com.intellij.util.ThrowableRunnable import com.intellij.util.indexing.UnindexedFilesUpdater -import com.intellij.util.io.exists import com.intellij.util.ui.UIUtil -import org.jetbrains.kotlin.idea.configuration.getModulesWithKotlinFiles import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager import org.jetbrains.kotlin.idea.framework.KotlinSdkType import org.jetbrains.kotlin.idea.perf.Stats.Companion.WARM_UP import org.jetbrains.kotlin.idea.perf.Stats.Companion.runAndMeasure -import org.jetbrains.kotlin.idea.project.getAndCacheLanguageLevelByDependencies import org.jetbrains.kotlin.idea.test.invalidateLibraryCache import org.jetbrains.kotlin.idea.testFramework.* import org.jetbrains.kotlin.idea.testFramework.Fixture.Companion.cleanupCaches @@ -55,9 +46,9 @@ import org.jetbrains.kotlin.idea.testFramework.Fixture.Companion.close import org.jetbrains.kotlin.idea.testFramework.Fixture.Companion.isAKotlinScriptFile import org.jetbrains.kotlin.idea.testFramework.Fixture.Companion.openFileInEditor import org.jetbrains.kotlin.idea.testFramework.Fixture.Companion.openFixture +import org.jetbrains.kotlin.idea.testFramework.ProjectOpenAction.SIMPLE_JAVA_MODULE import org.jetbrains.kotlin.idea.util.getProjectJdkTableSafe import java.io.File -import java.nio.file.Paths abstract class AbstractPerformanceProjectsTest : UsefulTestCase() { @@ -114,12 +105,14 @@ abstract class AbstractPerformanceProjectsTest : UsefulTestCase() { ThrowableRunnable { super.tearDown() }, ThrowableRunnable { if (myProject != null) { + logMessage { "myProject is about to be closed" } DaemonCodeAnalyzerSettings.getInstance().isImportHintEnabled = true // return default value to avoid unnecessary save (StartupManager.getInstance(project()) as StartupManagerImpl).checkCleared() (DaemonCodeAnalyzer.getInstance(project()) as DaemonCodeAnalyzerImpl).cleanupAfterTest() closeProject(project()) myApplication.setDataProvider(null) myProject = null + logMessage { "myProject is closed" } } }).run() } @@ -129,6 +122,15 @@ abstract class AbstractPerformanceProjectsTest : UsefulTestCase() { return if (lastIndexOf >= 0) fileName.substring(lastIndexOf + 1) else fileName } + protected fun perfOpenHelloWorld(stats: Stats, note: String = ""): Project = + perfOpenProject( + name = "helloKotlin", + stats = stats, + note = note, + path = "idea/testData/perfTest/helloKotlin", + openAction = SIMPLE_JAVA_MODULE + ) + protected fun perfOpenProject( name: String, stats: Stats, @@ -147,64 +149,35 @@ abstract class AbstractPerformanceProjectsTest : UsefulTestCase() { var lastProject: Project? = null var counter = 0 + val openProject = OpenProject( + projectPath = path, + projectName = name, + jdk = jdk18, + projectOpenAction = openAction + ) + performanceTest { - name("open project${if (note.isNotEmpty()) " $note" else ""}") + name("open project $name${if (note.isNotEmpty()) " $note" else ""}") stats(stats) warmUpIterations(warmUpIterations) iterations(iterations) checkStability(!fast) test { - val project = openAction.openProject( - projectPath = path, - projectName = name, - jdk = jdk18 - ) - - dispatchAllInvocationEvents() - - runStartupActivities(project) - - logMessage { "project $name is ${if (project.isInitialized) "initialized" else "not initialized"}" } - - with(ChangeListManager.getInstance(project) as ChangeListManagerImpl) { - waitUntilRefreshed() - } - - it.value = project + it.value = ProjectOpenAction.openProject(openProject) } tearDown { it.value?.let { project -> - - runAndMeasure("refresh gradle project $name") { - refreshGradleProjectIfNeeded(projectPath, project) - } - - ApplicationManager.getApplication().executeOnPooledThread { - DumbService.getInstance(project).waitForSmartMode() - - for (module in getModulesWithKotlinFiles(project)) { - module.getAndCacheLanguageLevelByDependencies() - } - }.get() - - val modules = ModuleManager.getInstance(project).modules - assertTrue("project $name has to have at least one module", modules.isNotEmpty()) - - logMessage { "modules of $name: ${modules.map { m -> m.name }}" } - lastProject = project - VirtualFileManager.getInstance().syncRefresh() - - runWriteAction { - project.save() - } + openAction.postOpenProject(openProject = openProject, project = project) logMessage { "project '$name' successfully opened" } // close all project but last - we're going to return and use it further if (counter < warmUpIterations + iterations - 1) { myApplication.setDataProvider(null) + logMessage { "project '$name' is about to be closed" } closeProject(project) + logMessage { "project '$name' successfully closed" } } counter++ } @@ -235,22 +208,6 @@ abstract class AbstractPerformanceProjectsTest : UsefulTestCase() { return lastProject ?: error("unable to open project $name at $path") } - private fun refreshGradleProjectIfNeeded(projectPath: String, project: Project) { - if (listOf("build.gradle.kts", "build.gradle").map { name -> Paths.get(projectPath, name).exists() }.find { e -> e } != true) return - - ExternalProjectsManagerImpl.getInstance(project).setStoreExternally(true) - - refreshGradleProject(projectPath, project) - - dispatchAllInvocationEvents() - - // WARNING: [VD] DO NOT SAVE PROJECT AS IT COULD PERSIST WRONG MODULES INFO - -// runInEdtAndWait { -// PlatformTestUtil.saveProject(project) -// } - } - fun perfTypeAndAutocomplete( stats: Stats, fileName: String, @@ -294,7 +251,7 @@ abstract class AbstractPerformanceProjectsTest : UsefulTestCase() { testBlock = { fixture: Fixture -> fixture.complete() }, - tearDownCheck = { _, value: Array? -> + tearDownCheck = { fixture, value: Array? -> val items = value?.map { e -> e.lookupString }?.toList() ?: emptyList() for (lookupElement in lookupElements) { assertTrue("'$lookupElement' has to be present in items $items", items.contains(lookupElement)) @@ -369,12 +326,12 @@ abstract class AbstractPerformanceProjectsTest : UsefulTestCase() { iterations(15) profileEnabled(true) setUp { - val tasksIdx = editor.document.text.indexOf(marker) - assertTrue("marker '$marker' not found in $fileName", tasksIdx > 0) + val markerOffset = editor.document.text.indexOf(marker) + assertTrue("marker '$marker' not found in $fileName", markerOffset > 0) if (typeAfterMarker) { - editor.caretModel.moveToOffset(tasksIdx + marker.length + 1) + editor.caretModel.moveToOffset(markerOffset + marker.length + 1) } else { - editor.caretModel.moveToOffset(tasksIdx - 1) + editor.caretModel.moveToOffset(markerOffset - 1) } for (surroundItem in surroundItems) { @@ -420,7 +377,8 @@ abstract class AbstractPerformanceProjectsTest : UsefulTestCase() { ) = perfTypeAndHighlight( project(), stats, fileName, marker, insertString, surroundItems, typeAfterMarker = typeAfterMarker, - revertChangesAtTheEnd = revertChangesAtTheEnd, note = note + revertChangesAtTheEnd = revertChangesAtTheEnd, + note = note ) fun perfTypeAndHighlight( @@ -488,6 +446,92 @@ abstract class AbstractPerformanceProjectsTest : UsefulTestCase() { } } + fun perfType( + stats: Stats, + fileName: String, + marker: String, + insertString: String, + surroundItems: String = "\n", + typeAfterMarker: Boolean = true, + revertChangesAtTheEnd: Boolean = true, + note: String = "", + delay: Long? = null + ) = perfType( + project(), stats, fileName, marker, insertString, surroundItems, + typeAfterMarker = typeAfterMarker, + revertChangesAtTheEnd = revertChangesAtTheEnd, + note = note, + delay = delay + ) + + fun perfType( + project: Project, + stats: Stats, + fileName: String, + marker: String, + insertString: String, + surroundItems: String = "\n", + typeAfterMarker: Boolean = true, + revertChangesAtTheEnd: Boolean = true, + note: String = "", + delay: Long? = null + ) { + performanceTest, List> { + name("type ${notePrefix(note)}$fileName") + stats(stats) + warmUpIterations(8) + iterations(15) + setUp { + val fixture = openFixture(project, fileName) + val editor = fixture.editor + + val initialText = editor.document.text + updateScriptDependenciesIfNeeded(fileName, fixture, project) + + val tasksIdx = editor.document.text.indexOf(marker) + assertTrue("marker '$marker' not found in $fileName", tasksIdx > 0) + if (typeAfterMarker) { + editor.caretModel.moveToOffset(tasksIdx + marker.length + 1) + } else { + editor.caretModel.moveToOffset(tasksIdx - 1) + } + + for (surroundItem in surroundItems) { + EditorTestUtil.performTypingAction(editor, surroundItem) + } + + editor.caretModel.moveToOffset(editor.caretModel.offset - if (typeAfterMarker) 1 else 2) + + if (!typeAfterMarker) { + for (surroundItem in surroundItems) { + EditorTestUtil.performTypingAction(editor, surroundItem) + } + editor.caretModel.moveToOffset(editor.caretModel.offset - 2) + } + + it.setUpValue = Pair(initialText, fixture) + } + test { + val fixture = it.setUpValue!!.second + for (i in insertString.indices) { + fixture.type(insertString[i]) + delay?.let { d -> Thread.sleep(d) } + } + it.value = fixture.doHighlighting() + } + tearDown { + it.value?.let { list -> + assertNotEmpty(list) + } + it.setUpValue?.let { pair -> + pair.second.revertChanges(revertChangesAtTheEnd, pair.first) + } + commitAllDocuments() + } + profileEnabled(true) + } + } + fun perfCopyAndPaste( stats: Stats, sourceFileName: String, diff --git a/idea/performanceTests/test/org/jetbrains/kotlin/idea/perf/FileUtils.kt b/idea/performanceTests/test/org/jetbrains/kotlin/idea/perf/FileUtils.kt new file mode 100644 index 00000000000..9a39f5add70 --- /dev/null +++ b/idea/performanceTests/test/org/jetbrains/kotlin/idea/perf/FileUtils.kt @@ -0,0 +1,14 @@ +/* + * 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.perf + +import java.nio.file.* + +fun Path.copyRecursively(targetDirectory: Path) { + val src = this + Files.walk(src) + .forEach { source -> Files.copy(source, targetDirectory.resolve(src.relativize(source)), StandardCopyOption.REPLACE_EXISTING) } +} \ No newline at end of file diff --git a/idea/performanceTests/test/org/jetbrains/kotlin/idea/perf/PerfTestBuilder.kt b/idea/performanceTests/test/org/jetbrains/kotlin/idea/perf/PerfTestBuilder.kt index 6ac0ae64974..9af4bdf022f 100644 --- a/idea/performanceTests/test/org/jetbrains/kotlin/idea/perf/PerfTestBuilder.kt +++ b/idea/performanceTests/test/org/jetbrains/kotlin/idea/perf/PerfTestBuilder.kt @@ -16,7 +16,7 @@ class PerfTestBuilder { private var profileEnabled: Boolean = false private var checkStability: Boolean = true - fun run() { + internal fun run() { stats.perfTest( testName = name, warmUpIterations = warmUpIterations, diff --git a/idea/performanceTests/test/org/jetbrains/kotlin/idea/perf/PerformanceProjectsTest.kt b/idea/performanceTests/test/org/jetbrains/kotlin/idea/perf/PerformanceProjectsTest.kt index ab9154ce475..058642bd4b6 100644 --- a/idea/performanceTests/test/org/jetbrains/kotlin/idea/perf/PerformanceProjectsTest.kt +++ b/idea/performanceTests/test/org/jetbrains/kotlin/idea/perf/PerformanceProjectsTest.kt @@ -20,8 +20,7 @@ import org.jetbrains.kotlin.idea.perf.Stats.Companion.tcSuite import org.jetbrains.kotlin.idea.testFramework.Fixture import org.jetbrains.kotlin.idea.testFramework.Fixture.Companion.cleanupCaches import org.jetbrains.kotlin.idea.testFramework.Fixture.Companion.isAKotlinScriptFile -import org.jetbrains.kotlin.idea.testFramework.ProjectOpenAction.EXISTING_IDEA_PROJECT -import org.jetbrains.kotlin.idea.testFramework.ProjectOpenAction.SIMPLE_JAVA_MODULE +import org.jetbrains.kotlin.idea.testFramework.ProjectOpenAction.GRADLE_PROJECT import java.util.concurrent.atomic.AtomicLong import kotlin.test.assertNotEquals @@ -217,20 +216,11 @@ class PerformanceProjectsTest : AbstractPerformanceProjectsTest() { stats = stats, note = "", path = "../perfTestProject", - openAction = EXISTING_IDEA_PROJECT, + openAction = GRADLE_PROJECT, fast = fast ) } - private fun perfOpenHelloWorld(stats: Stats, note: String = ""): Project = - perfOpenProject( - name = "helloKotlin", - stats = stats, - note = note, - path = "idea/testData/perfTest/helloKotlin", - openAction = SIMPLE_JAVA_MODULE - ) - private fun perfScriptDependenciesBuildGradleKts(it: Stats) { perfScriptDependencies("build.gradle.kts", stats = it) } diff --git a/idea/performanceTests/test/org/jetbrains/kotlin/idea/perf/PerformanceStressTest.kt b/idea/performanceTests/test/org/jetbrains/kotlin/idea/perf/PerformanceStressTest.kt new file mode 100644 index 00000000000..90ff87daaac --- /dev/null +++ b/idea/performanceTests/test/org/jetbrains/kotlin/idea/perf/PerformanceStressTest.kt @@ -0,0 +1,119 @@ +/* + * 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.perf + +import org.jetbrains.kotlin.idea.perf.Stats.Companion.tcSuite + +class PerformanceStressTest : AbstractPerformanceProjectsTest() { + + companion object { + + @JvmStatic + var warmedUp: Boolean = false + + @JvmStatic + val hwStats: Stats = Stats("helloWorld project") + + init { + // there is no @AfterClass for junit3.8 + Runtime.getRuntime().addShutdownHook(Thread { hwStats.close() }) + } + + } + + override fun setUp() { + super.setUp() + // warm up: open simple small project + if (!warmedUp) { + warmUpProject(hwStats, "src/HelloMain.kt") { + openProject { + name("helloWorld") + + kotlinFile("HelloMain") { + topFunction("main") { + param("args", "Array") + body("""println("Hello World!")""") + } + } + } + } + warmedUp = true + } + } + + fun testLotsOfOverloadedMethods() { + // KT-35135 + val generatedTypes = mutableListOf(listOf()) + generateTypes(arrayOf("Int", "String", "Long", "List", "Array"), generatedTypes) + + tcSuite("Lots of overloaded method project") { + myProject = openProject { + name("kt-35135") + buildGradle("idea/testData/perfTest/simpleTemplate/") + + kotlinFile("OverloadX") { + pkg("pkg") + + topClass("OverloadX") { + openClass() + + for (types in generatedTypes) { + function("foo") { + openFunction() + returnType("String") + for ((index, type) in types.withIndex()) { + param("arg$index", type) + } + body("TODO()") + } + } + } + } + + kotlinFile("SomeClass") { + pkg("pkg") + + topClass("SomeClass") { + superClass("OverloadX") + + body("ov") + } + } + } + + val stats = Stats("kt-35135 project") + stats.use { stats -> + perfType( + stats, + "src/main/java/pkg/SomeClass.kt", + "ov", + "override fun foo(): String = TODO()", + note = "override fun foo()", + delay = 50 + ) + } + } + } + + private tailrec fun generateTypes(types: Array, results: MutableList>, index: Int = 0, maxCount: Int = 3000) { + val newResults = mutableListOf>() + for (list in results) { + if (list.size < index) continue + for (t in types) { + val newList = mutableListOf() + newList.addAll(list) + newList.add(t) + newResults.add(newList.toList()) + if (results.size + newResults.size >= maxCount) { + results.addAll(newResults) + return + } + } + } + results.addAll(newResults) + generateTypes(types, results, index + 1, maxCount) + } +} \ No newline at end of file diff --git a/idea/performanceTests/test/org/jetbrains/kotlin/idea/perf/ProjectBuilder.kt b/idea/performanceTests/test/org/jetbrains/kotlin/idea/perf/ProjectBuilder.kt new file mode 100644 index 00000000000..3a6ad8be9d2 --- /dev/null +++ b/idea/performanceTests/test/org/jetbrains/kotlin/idea/perf/ProjectBuilder.kt @@ -0,0 +1,227 @@ +/* + * 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.perf + +import com.intellij.openapi.project.Project +import com.intellij.openapi.projectRoots.JavaSdk +import com.intellij.openapi.projectRoots.impl.JavaAwareProjectJdkTableImpl +import com.intellij.util.io.* +import org.jetbrains.kotlin.idea.testFramework.OpenProject +import org.jetbrains.kotlin.idea.testFramework.ProjectOpenAction +import java.nio.file.Files +import java.nio.file.Paths + +abstract class AbstractSource { + protected val body = StringBuilder() + + protected open fun newLineAfterCodeBlock(): Boolean = true + + protected open fun intendLevel(): Int = 0 + + private fun intendPrefix() = " ".repeat(2 * intendLevel()) + + fun body(code: Any) { + this.body.append(code.toString().prependIndent(intendPrefix())) + if (newLineAfterCodeBlock()) this.body.append('\n') + } + + override fun toString(): String = body.toString() +} + +abstract class AbstractClassSource(val name: String) : AbstractSource() { + override fun intendLevel(): Int = 1 + + fun function(name: String, funSource: FunSource.() -> Unit) { + val funSource = FunSource(name).apply(funSource) + super.body(funSource) + } +} + +class CompanionObjectSource : AbstractClassSource("") { + override fun toString(): String = "companion object {\n$body}" +} + +class ClassSource(name: String, private val topLevel: Boolean = true) : AbstractClassSource(name) { + + private var openClass: Boolean = false + + private var superClass: String? = null + private val interfaces = mutableListOf() + + fun superClass(superClass: String) { + check(this.superClass == null) { "super class is already specified for $name: ${this.superClass}" } + this.superClass = superClass + } + + fun interfaces(iface: String) { + interfaces.add(iface) + } + + fun openClass() { + openClass(true) + } + + fun openClass(openClass: Boolean) { + this.openClass = openClass + } + + fun companionObject(companionObjectSource: CompanionObjectSource.() -> Unit) { + check(topLevel) { "companion object is allowed only in top-level classes, wrong class: $name" } + val source = CompanionObjectSource().apply(companionObjectSource) + super.body(source) + } + + private fun openPrefix(): String = if (!openClass) "" else "open " + + private fun superAndInterfaces(): String { + val superStr = if (superClass != null) { + ": $superClass()" + } else "" + + return if (interfaces.isEmpty()) superStr + else { + val interfacesStr = interfaces.joinToString() + if (superStr.isEmpty()) ": $interfacesStr" else "$superStr, $interfacesStr" + } + } + + override fun toString(): String = "${openPrefix()}class $name ${superAndInterfaces()} {\n$body}" +} + +class FunSource(val name: String) : AbstractSource() { + private val params = mutableMapOf() + private var openFunction: Boolean = false + private var returnType: String? = null + + override fun intendLevel(): Int = 1 + + fun openFunction() { + this.openFunction = true + } + + fun param(name: String, type: String) { + this.params[name] = type + } + + fun returnType(returnType: String) { + this.returnType = returnType + } + + private fun modifiers(): String { + val s = if (openFunction) "open" else "" + return if (s.isEmpty()) s else "$s " + } + + private fun retType(): String { + returnType ?: return "" + return ": $returnType " + } + + override fun toString(): String = + "${modifiers()}fun $name(${params.map { it.key + ": " + it.value }.joinToString()}) ${retType()}{\n$body}" +} + +class KotlinFileSource : AbstractSource() { + + internal var pkg: String? = null + + fun pkg(pkg: String) { + check(this.pkg == null) { "package directive is already specified: ${this.pkg}" } + this.pkg = pkg + super.body("package $pkg\n") + } + + fun topClass(name: String, clsSource: ClassSource.() -> Unit) { + val classSource = ClassSource(name).apply(clsSource) + super.body(classSource) + } + + fun topFunction(name: String, funSource: FunSource.() -> Unit) { + val funSource = FunSource(name).apply(funSource) + super.body(funSource) + } +} + +class ProjectBuilder { + internal var buildGradle: String? = null + internal lateinit var name: String + private val kotlinFiles = mutableListOf>() + + fun buildGradle(buildGradle: String) { + this.buildGradle = buildGradle + } + + fun name(name: String) { + this.name = name + } + + fun kotlinFile(name: String, kotlinFileSource: KotlinFileSource.() -> Unit) { + val source = KotlinFileSource().apply(kotlinFileSource) + kotlinFiles.add(Pair(name, source)) + } + + internal fun generateFiles(): String { + val targetDirectory = Files.createTempDirectory(name) + Runtime.getRuntime().addShutdownHook(Thread { + targetDirectory.delete(true) + }) + + val javaMainSrcDir = if (buildGradle != null) { + val buildGradlePath = Paths.get(buildGradle) + when { + buildGradlePath.isFile() -> { + buildGradlePath.copy(targetDirectory) + } + buildGradlePath.isDirectory() -> { + val buildGradleFile = + listOf("build.gradle.kts", "build.gradle").map { buildGradlePath.resolve(it) }.firstOrNull { it.exists() } + ?: error("neither build.gradle.kts nor build.gradle found at $buildGradlePath") + buildGradleFile.copy(targetDirectory.resolve(buildGradleFile.fileName)) + } + else -> error("illegal type of build gradle path: $buildGradlePath") + } + + targetDirectory.resolve("src/main/java") + } else { + targetDirectory.resolve("src") + } + javaMainSrcDir.createDirectories() + kotlinFiles.forEach { (name, source) -> + val srcDir = source.pkg?.let { pkg -> + val path = javaMainSrcDir.resolve(pkg.replace('.', '/')) + path.createDirectories() + path + } ?: javaMainSrcDir + srcDir.resolve("$name.kt").toFile().writeText(source.toString()) + } + // + return targetDirectory.toAbsolutePath().toFile().absolutePath + } +} + +fun openProject(initializer: ProjectBuilder.() -> Unit): Project { + val projectBuilder = ProjectBuilder().apply(initializer) + val projectPath = projectBuilder.generateFiles() + + val jdkTableImpl = JavaAwareProjectJdkTableImpl.getInstanceEx() + val homePath = if (jdkTableImpl.internalJdk.homeDirectory!!.name == "jre") { + jdkTableImpl.internalJdk.homeDirectory!!.parent.path + } else { + jdkTableImpl.internalJdk.homePath!! + } + val javaSdk = JavaSdk.getInstance() + val jdk18 = javaSdk.createJdk("1.8", homePath) + val openAction = if (projectBuilder.buildGradle != null) ProjectOpenAction.GRADLE_PROJECT else ProjectOpenAction.SIMPLE_JAVA_MODULE + val openProject = OpenProject( + projectPath = projectPath, + projectName = projectBuilder.name, + jdk = jdk18, + projectOpenAction = openAction + ) + val project = ProjectOpenAction.openProject(openProject) + openAction.postOpenProject(project = project, openProject = openProject) + return project +} \ No newline at end of file diff --git a/idea/performanceTests/test/org/jetbrains/kotlin/idea/testFramework/Fixture.kt b/idea/performanceTests/test/org/jetbrains/kotlin/idea/testFramework/Fixture.kt index d044d30db11..75dbc423ab8 100644 --- a/idea/performanceTests/test/org/jetbrains/kotlin/idea/testFramework/Fixture.kt +++ b/idea/performanceTests/test/org/jetbrains/kotlin/idea/testFramework/Fixture.kt @@ -61,6 +61,10 @@ class Fixture(val project: Project, val editor: Editor, val psiFile: PsiFile, va delegate.type(s) } + fun type(c: Char) { + delegate.type(c) + } + fun performEditorAction(actionId: String): Boolean { selectEditor() return delegate.performEditorAction(actionId) 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 15e8a245af3..1c133e83f32 100644 --- a/idea/performanceTests/test/org/jetbrains/kotlin/idea/testFramework/ProjectOpenAction.kt +++ b/idea/performanceTests/test/org/jetbrains/kotlin/idea/testFramework/ProjectOpenAction.kt @@ -6,20 +6,34 @@ package org.jetbrains.kotlin.idea.testFramework import com.intellij.ide.highlighter.ModuleFileType +import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.runWriteAction +import com.intellij.openapi.externalSystem.service.project.manage.ExternalProjectsManagerImpl import com.intellij.openapi.module.ModuleManager import com.intellij.openapi.module.ModuleTypeId +import com.intellij.openapi.project.DumbService import com.intellij.openapi.project.Project import com.intellij.openapi.project.ex.ProjectManagerEx import com.intellij.openapi.projectRoots.Sdk import com.intellij.openapi.roots.ProjectRootManager +import com.intellij.openapi.vcs.changes.ChangeListManager +import com.intellij.openapi.vcs.changes.ChangeListManagerImpl import com.intellij.openapi.vfs.LocalFileSystem +import com.intellij.openapi.vfs.VirtualFileManager import com.intellij.testFramework.PsiTestUtil +import com.intellij.testFramework.UsefulTestCase +import com.intellij.util.io.exists +import org.jetbrains.kotlin.idea.configuration.getModulesWithKotlinFiles +import org.jetbrains.kotlin.idea.perf.Stats.Companion.runAndMeasure +import org.jetbrains.kotlin.idea.project.getAndCacheLanguageLevelByDependencies import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil import java.io.File +import java.nio.file.Paths import kotlin.test.assertNotNull import kotlin.test.assertTrue +data class OpenProject(val projectPath: String, val projectName: String, val jdk: Sdk, val projectOpenAction: ProjectOpenAction) + enum class ProjectOpenAction { SIMPLE_JAVA_MODULE { override fun openProject(projectPath: String, projectName: String, jdk: Sdk): Project { @@ -73,7 +87,73 @@ enum class ProjectOpenAction { return project } + + private fun refreshGradleProjectIfNeeded(projectPath: String, project: Project) { + if (listOf("build.gradle.kts", "build.gradle").map { name -> Paths.get(projectPath, name).exists() } + .find { e -> e } != true) return + + ExternalProjectsManagerImpl.getInstance(project).setStoreExternally(true) + + refreshGradleProject(projectPath, project) + + dispatchAllInvocationEvents() + + // WARNING: [VD] DO NOT SAVE PROJECT AS IT COULD PERSIST WRONG MODULES INFO + +// runInEdtAndWait { +// PlatformTestUtil.saveProject(project) +// } + } + + override fun postOpenProject(project: Project, openProject: OpenProject) { + runAndMeasure("refresh gradle project ${openProject.projectName}") { + refreshGradleProjectIfNeeded(openProject.projectPath, project) + } + + super.postOpenProject(project, openProject) + } }; abstract fun openProject(projectPath: String, projectName: String, jdk: Sdk): Project -} \ No newline at end of file + + open fun postOpenProject(project: Project, openProject: OpenProject) { + ApplicationManager.getApplication().executeOnPooledThread { + DumbService.getInstance(project).waitForSmartMode() + + for (module in getModulesWithKotlinFiles(project)) { + module.getAndCacheLanguageLevelByDependencies() + } + }.get() + + val modules = ModuleManager.getInstance(project).modules + UsefulTestCase.assertTrue("project ${openProject.projectName} has to have at least one module", modules.isNotEmpty()) + + logMessage { "modules of ${openProject.projectName}: ${modules.map { m -> m.name }}" } + + VirtualFileManager.getInstance().syncRefresh() + + //runWriteAction { project.save() } + } + + companion object { + fun openProject(openProject: OpenProject): Project { + val project = openProject.projectOpenAction.openProject( + projectPath = openProject.projectPath, + projectName = openProject.projectName, + jdk = openProject.jdk + ) + + dispatchAllInvocationEvents() + + runStartupActivities(project) + + logMessage { "project ${openProject.projectName} is ${if (project.isInitialized) "initialized" else "not initialized"}" } + + with(ChangeListManager.getInstance(project) as ChangeListManagerImpl) { + waitUntilRefreshed() + } + return project + } + } +} + 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 42c8d607b4b..3c4f6cf4d2c 100644 --- a/idea/performanceTests/test/org/jetbrains/kotlin/idea/testFramework/gradleRoutines.kt +++ b/idea/performanceTests/test/org/jetbrains/kotlin/idea/testFramework/gradleRoutines.kt @@ -5,7 +5,13 @@ 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.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 @@ -16,10 +22,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() } @@ -58,6 +65,34 @@ 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) + //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) + } + } + ExternalSystemUtil.refreshProject(project, externalSystemId, externalProjectPath, callback, false, progressExecutionMode, true) } diff --git a/idea/testData/perfTest/simpleTemplate/build.gradle.kts b/idea/testData/perfTest/simpleTemplate/build.gradle.kts new file mode 100644 index 00000000000..612c4b4d60d --- /dev/null +++ b/idea/testData/perfTest/simpleTemplate/build.gradle.kts @@ -0,0 +1,33 @@ +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { + kotlin("jvm") version "1.3.61" +} + +version = "1.0.0-SNAPSHOT" + +tasks.withType { + kotlinOptions { + jvmTarget = "1.8" + freeCompilerArgs = listOf("-Xjsr305=strict") + } +} + +repositories { + mavenCentral() +} + +dependencies { + compile("org.jetbrains.kotlin:kotlin-stdlib-jdk8") + compile("org.jetbrains.kotlin:kotlin-reflect") + compile("org.jetbrains:annotations:16.0.1") +} + +val compileKotlin: KotlinCompile by tasks +compileKotlin.kotlinOptions { + jvmTarget = "1.8" +} +val compileTestKotlin: KotlinCompile by tasks +compileTestKotlin.kotlinOptions { + jvmTarget = "1.8" +} \ No newline at end of file