Add performance stress tests
This commit is contained in:
+117
-73
@@ -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,
|
||||
|
||||
+2
-12
@@ -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)
|
||||
|
||||
+81
-1
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+38
-3
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
Reference in New Issue
Block a user