Add performance stress tests

This commit is contained in:
Vladimir Dolzhenko
2020-03-02 23:08:11 +01:00
parent 9ea97a3dfb
commit 9c530b1121
10 changed files with 636 additions and 90 deletions
@@ -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<Unit, Project> {
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<LookupElement>? ->
tearDownCheck = { fixture, value: Array<LookupElement>? ->
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<Pair<String, Fixture>, List<HighlightInfo>> {
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,
@@ -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) }
}
@@ -16,7 +16,7 @@ class PerfTestBuilder<SV, TV> {
private var profileEnabled: Boolean = false
private var checkStability: Boolean = true
fun run() {
internal fun run() {
stats.perfTest(
testName = name,
warmUpIterations = warmUpIterations,
@@ -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)
}
@@ -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<String>")
body("""println("Hello World!")""")
}
}
}
}
warmedUp = true
}
}
fun testLotsOfOverloadedMethods() {
// KT-35135
val generatedTypes = mutableListOf(listOf<String>())
generateTypes(arrayOf("Int", "String", "Long", "List<Int>", "Array<Int>"), 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<String>, results: MutableList<List<String>>, index: Int = 0, maxCount: Int = 3000) {
val newResults = mutableListOf<List<String>>()
for (list in results) {
if (list.size < index) continue
for (t in types) {
val newList = mutableListOf<String>()
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)
}
}
@@ -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<String>()
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<String, String>()
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<Pair<String, KotlinFileSource>>()
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
}
@@ -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)
@@ -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
}
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
}
}
}
@@ -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<ProjectData>?) {
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)
}
+33
View File
@@ -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<KotlinCompile> {
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"
}