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.codeInsight.lookup.LookupElement
import com.intellij.ide.startup.impl.StartupManagerImpl import com.intellij.ide.startup.impl.StartupManagerImpl
import com.intellij.openapi.actionSystem.IdeActions import com.intellij.openapi.actionSystem.IdeActions
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.runWriteAction import com.intellij.openapi.application.runWriteAction
import com.intellij.openapi.editor.EditorFactory 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.FileDocumentManager
import com.intellij.openapi.fileEditor.FileEditorManager import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.module.ModuleManager
import com.intellij.openapi.project.DumbService import com.intellij.openapi.project.DumbService
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.JavaSdk import com.intellij.openapi.projectRoots.JavaSdk
import com.intellij.openapi.projectRoots.Sdk import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.projectRoots.impl.JavaAwareProjectJdkTableImpl import com.intellij.openapi.projectRoots.impl.JavaAwareProjectJdkTableImpl
import com.intellij.openapi.startup.StartupManager 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.PsiDocumentManager
import com.intellij.psi.PsiFile import com.intellij.psi.PsiFile
import com.intellij.psi.PsiManager 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.ArrayUtilRt
import com.intellij.util.ThrowableRunnable import com.intellij.util.ThrowableRunnable
import com.intellij.util.indexing.UnindexedFilesUpdater import com.intellij.util.indexing.UnindexedFilesUpdater
import com.intellij.util.io.exists
import com.intellij.util.ui.UIUtil 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.core.script.ScriptConfigurationManager
import org.jetbrains.kotlin.idea.framework.KotlinSdkType 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.WARM_UP
import org.jetbrains.kotlin.idea.perf.Stats.Companion.runAndMeasure 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.test.invalidateLibraryCache
import org.jetbrains.kotlin.idea.testFramework.* import org.jetbrains.kotlin.idea.testFramework.*
import org.jetbrains.kotlin.idea.testFramework.Fixture.Companion.cleanupCaches 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.isAKotlinScriptFile
import org.jetbrains.kotlin.idea.testFramework.Fixture.Companion.openFileInEditor import org.jetbrains.kotlin.idea.testFramework.Fixture.Companion.openFileInEditor
import org.jetbrains.kotlin.idea.testFramework.Fixture.Companion.openFixture 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 org.jetbrains.kotlin.idea.util.getProjectJdkTableSafe
import java.io.File import java.io.File
import java.nio.file.Paths
abstract class AbstractPerformanceProjectsTest : UsefulTestCase() { abstract class AbstractPerformanceProjectsTest : UsefulTestCase() {
@@ -114,12 +105,14 @@ abstract class AbstractPerformanceProjectsTest : UsefulTestCase() {
ThrowableRunnable { super.tearDown() }, ThrowableRunnable { super.tearDown() },
ThrowableRunnable { ThrowableRunnable {
if (myProject != null) { if (myProject != null) {
logMessage { "myProject is about to be closed" }
DaemonCodeAnalyzerSettings.getInstance().isImportHintEnabled = true // return default value to avoid unnecessary save DaemonCodeAnalyzerSettings.getInstance().isImportHintEnabled = true // return default value to avoid unnecessary save
(StartupManager.getInstance(project()) as StartupManagerImpl).checkCleared() (StartupManager.getInstance(project()) as StartupManagerImpl).checkCleared()
(DaemonCodeAnalyzer.getInstance(project()) as DaemonCodeAnalyzerImpl).cleanupAfterTest() (DaemonCodeAnalyzer.getInstance(project()) as DaemonCodeAnalyzerImpl).cleanupAfterTest()
closeProject(project()) closeProject(project())
myApplication.setDataProvider(null) myApplication.setDataProvider(null)
myProject = null myProject = null
logMessage { "myProject is closed" }
} }
}).run() }).run()
} }
@@ -129,6 +122,15 @@ abstract class AbstractPerformanceProjectsTest : UsefulTestCase() {
return if (lastIndexOf >= 0) fileName.substring(lastIndexOf + 1) else fileName 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( protected fun perfOpenProject(
name: String, name: String,
stats: Stats, stats: Stats,
@@ -147,64 +149,35 @@ abstract class AbstractPerformanceProjectsTest : UsefulTestCase() {
var lastProject: Project? = null var lastProject: Project? = null
var counter = 0 var counter = 0
val openProject = OpenProject(
projectPath = path,
projectName = name,
jdk = jdk18,
projectOpenAction = openAction
)
performanceTest<Unit, Project> { performanceTest<Unit, Project> {
name("open project${if (note.isNotEmpty()) " $note" else ""}") name("open project $name${if (note.isNotEmpty()) " $note" else ""}")
stats(stats) stats(stats)
warmUpIterations(warmUpIterations) warmUpIterations(warmUpIterations)
iterations(iterations) iterations(iterations)
checkStability(!fast) checkStability(!fast)
test { test {
val project = openAction.openProject( it.value = ProjectOpenAction.openProject(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
} }
tearDown { tearDown {
it.value?.let { project -> 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 lastProject = project
VirtualFileManager.getInstance().syncRefresh() openAction.postOpenProject(openProject = openProject, project = project)
runWriteAction {
project.save()
}
logMessage { "project '$name' successfully opened" } logMessage { "project '$name' successfully opened" }
// close all project but last - we're going to return and use it further // close all project but last - we're going to return and use it further
if (counter < warmUpIterations + iterations - 1) { if (counter < warmUpIterations + iterations - 1) {
myApplication.setDataProvider(null) myApplication.setDataProvider(null)
logMessage { "project '$name' is about to be closed" }
closeProject(project) closeProject(project)
logMessage { "project '$name' successfully closed" }
} }
counter++ counter++
} }
@@ -235,22 +208,6 @@ abstract class AbstractPerformanceProjectsTest : UsefulTestCase() {
return lastProject ?: error("unable to open project $name at $path") 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( fun perfTypeAndAutocomplete(
stats: Stats, stats: Stats,
fileName: String, fileName: String,
@@ -294,7 +251,7 @@ abstract class AbstractPerformanceProjectsTest : UsefulTestCase() {
testBlock = { fixture: Fixture -> testBlock = { fixture: Fixture ->
fixture.complete() fixture.complete()
}, },
tearDownCheck = { _, value: Array<LookupElement>? -> tearDownCheck = { fixture, value: Array<LookupElement>? ->
val items = value?.map { e -> e.lookupString }?.toList() ?: emptyList() val items = value?.map { e -> e.lookupString }?.toList() ?: emptyList()
for (lookupElement in lookupElements) { for (lookupElement in lookupElements) {
assertTrue("'$lookupElement' has to be present in items $items", items.contains(lookupElement)) assertTrue("'$lookupElement' has to be present in items $items", items.contains(lookupElement))
@@ -369,12 +326,12 @@ abstract class AbstractPerformanceProjectsTest : UsefulTestCase() {
iterations(15) iterations(15)
profileEnabled(true) profileEnabled(true)
setUp { setUp {
val tasksIdx = editor.document.text.indexOf(marker) val markerOffset = editor.document.text.indexOf(marker)
assertTrue("marker '$marker' not found in $fileName", tasksIdx > 0) assertTrue("marker '$marker' not found in $fileName", markerOffset > 0)
if (typeAfterMarker) { if (typeAfterMarker) {
editor.caretModel.moveToOffset(tasksIdx + marker.length + 1) editor.caretModel.moveToOffset(markerOffset + marker.length + 1)
} else { } else {
editor.caretModel.moveToOffset(tasksIdx - 1) editor.caretModel.moveToOffset(markerOffset - 1)
} }
for (surroundItem in surroundItems) { for (surroundItem in surroundItems) {
@@ -420,7 +377,8 @@ abstract class AbstractPerformanceProjectsTest : UsefulTestCase() {
) = perfTypeAndHighlight( ) = perfTypeAndHighlight(
project(), stats, fileName, marker, insertString, surroundItems, project(), stats, fileName, marker, insertString, surroundItems,
typeAfterMarker = typeAfterMarker, typeAfterMarker = typeAfterMarker,
revertChangesAtTheEnd = revertChangesAtTheEnd, note = note revertChangesAtTheEnd = revertChangesAtTheEnd,
note = note
) )
fun perfTypeAndHighlight( 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( fun perfCopyAndPaste(
stats: Stats, stats: Stats,
sourceFileName: String, 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 profileEnabled: Boolean = false
private var checkStability: Boolean = true private var checkStability: Boolean = true
fun run() { internal fun run() {
stats.perfTest( stats.perfTest(
testName = name, testName = name,
warmUpIterations = warmUpIterations, 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
import org.jetbrains.kotlin.idea.testFramework.Fixture.Companion.cleanupCaches import org.jetbrains.kotlin.idea.testFramework.Fixture.Companion.cleanupCaches
import org.jetbrains.kotlin.idea.testFramework.Fixture.Companion.isAKotlinScriptFile 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.GRADLE_PROJECT
import org.jetbrains.kotlin.idea.testFramework.ProjectOpenAction.SIMPLE_JAVA_MODULE
import java.util.concurrent.atomic.AtomicLong import java.util.concurrent.atomic.AtomicLong
import kotlin.test.assertNotEquals import kotlin.test.assertNotEquals
@@ -217,20 +216,11 @@ class PerformanceProjectsTest : AbstractPerformanceProjectsTest() {
stats = stats, stats = stats,
note = "", note = "",
path = "../perfTestProject", path = "../perfTestProject",
openAction = EXISTING_IDEA_PROJECT, openAction = GRADLE_PROJECT,
fast = fast 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) { private fun perfScriptDependenciesBuildGradleKts(it: Stats) {
perfScriptDependencies("build.gradle.kts", stats = it) 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) delegate.type(s)
} }
fun type(c: Char) {
delegate.type(c)
}
fun performEditorAction(actionId: String): Boolean { fun performEditorAction(actionId: String): Boolean {
selectEditor() selectEditor()
return delegate.performEditorAction(actionId) return delegate.performEditorAction(actionId)
@@ -6,20 +6,34 @@
package org.jetbrains.kotlin.idea.testFramework package org.jetbrains.kotlin.idea.testFramework
import com.intellij.ide.highlighter.ModuleFileType import com.intellij.ide.highlighter.ModuleFileType
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.runWriteAction 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.ModuleManager
import com.intellij.openapi.module.ModuleTypeId import com.intellij.openapi.module.ModuleTypeId
import com.intellij.openapi.project.DumbService
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.project.ex.ProjectManagerEx import com.intellij.openapi.project.ex.ProjectManagerEx
import com.intellij.openapi.projectRoots.Sdk import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.roots.ProjectRootManager 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.LocalFileSystem
import com.intellij.openapi.vfs.VirtualFileManager
import com.intellij.testFramework.PsiTestUtil 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 org.jetbrains.kotlin.idea.test.ConfigLibraryUtil
import java.io.File import java.io.File
import java.nio.file.Paths
import kotlin.test.assertNotNull import kotlin.test.assertNotNull
import kotlin.test.assertTrue import kotlin.test.assertTrue
data class OpenProject(val projectPath: String, val projectName: String, val jdk: Sdk, val projectOpenAction: ProjectOpenAction)
enum class ProjectOpenAction { enum class ProjectOpenAction {
SIMPLE_JAVA_MODULE { SIMPLE_JAVA_MODULE {
override fun openProject(projectPath: String, projectName: String, jdk: Sdk): Project { override fun openProject(projectPath: String, projectName: String, jdk: Sdk): Project {
@@ -73,7 +87,73 @@ enum class ProjectOpenAction {
return project 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 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 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.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.service.project.manage.ExternalProjectsManagerImpl
import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil
import com.intellij.openapi.externalSystem.util.ExternalSystemUtil 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.settings.GradleProjectSettings
import org.jetbrains.plugins.gradle.util.GradleConstants import org.jetbrains.plugins.gradle.util.GradleConstants
import org.jetbrains.plugins.gradle.util.GradleLog import org.jetbrains.plugins.gradle.util.GradleLog
import java.io.File
import kotlin.test.assertNotNull import kotlin.test.assertNotNull
fun refreshGradleProject(projectPath: String, project: Project) { fun refreshGradleProject(projectPath: String, project: Project) {
_importProject(projectPath, project) _importProject(File(projectPath).absolutePath, project)
dispatchAllInvocationEvents() dispatchAllInvocationEvents()
} }
@@ -58,6 +65,34 @@ private fun _attachGradleProjectAndRefresh(
if (settings.getLinkedProjectSettings(externalProjectPath) == null) { if (settings.getLinkedProjectSettings(externalProjectPath) == null) {
settings.linkProject(gradleProjectSettings) 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"
}