Added gradle.kts and annotator gradle.kts autocompletion performance benchmark
This commit is contained in:
+328
-108
@@ -5,12 +5,13 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.idea.perf
|
package org.jetbrains.kotlin.idea.perf
|
||||||
|
|
||||||
|
import com.intellij.codeInsight.completion.CompletionType
|
||||||
import com.intellij.codeInsight.daemon.*
|
import com.intellij.codeInsight.daemon.*
|
||||||
import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerImpl
|
import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerImpl
|
||||||
import com.intellij.codeInsight.daemon.impl.HighlightInfo
|
import com.intellij.codeInsight.daemon.impl.HighlightInfo
|
||||||
import com.intellij.codeInsight.daemon.impl.IdentifierHighlighterPassFactory
|
import com.intellij.codeInsight.daemon.impl.IdentifierHighlighterPassFactory
|
||||||
|
import com.intellij.codeInsight.lookup.LookupElement
|
||||||
import com.intellij.ide.highlighter.ModuleFileType
|
import com.intellij.ide.highlighter.ModuleFileType
|
||||||
import com.intellij.ide.impl.ProjectUtil
|
|
||||||
import com.intellij.ide.startup.impl.StartupManagerImpl
|
import com.intellij.ide.startup.impl.StartupManagerImpl
|
||||||
import com.intellij.idea.IdeaTestApplication
|
import com.intellij.idea.IdeaTestApplication
|
||||||
import com.intellij.lang.ExternalAnnotatorsFilter
|
import com.intellij.lang.ExternalAnnotatorsFilter
|
||||||
@@ -19,17 +20,22 @@ import com.intellij.lang.StdLanguages
|
|||||||
import com.intellij.lang.injection.InjectedLanguageManager
|
import com.intellij.lang.injection.InjectedLanguageManager
|
||||||
import com.intellij.lang.java.JavaLanguage
|
import com.intellij.lang.java.JavaLanguage
|
||||||
import com.intellij.openapi.application.ApplicationManager
|
import com.intellij.openapi.application.ApplicationManager
|
||||||
|
import com.intellij.openapi.application.runWriteAction
|
||||||
import com.intellij.openapi.editor.Document
|
import com.intellij.openapi.editor.Document
|
||||||
import com.intellij.openapi.editor.EditorFactory
|
import com.intellij.openapi.editor.EditorFactory
|
||||||
|
import com.intellij.openapi.externalSystem.importing.ImportSpecBuilder
|
||||||
|
import com.intellij.openapi.externalSystem.service.execution.ProgressExecutionMode
|
||||||
|
import com.intellij.openapi.externalSystem.service.project.manage.ExternalProjectsManagerImpl
|
||||||
|
import com.intellij.openapi.externalSystem.util.ExternalSystemUtil
|
||||||
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.fileEditor.impl.FileEditorManagerImpl
|
import com.intellij.openapi.fileEditor.impl.FileEditorManagerImpl
|
||||||
import com.intellij.openapi.module.Module
|
|
||||||
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.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.project.guessProjectDir
|
||||||
import com.intellij.openapi.project.impl.ProjectImpl
|
import com.intellij.openapi.project.impl.ProjectImpl
|
||||||
import com.intellij.openapi.projectRoots.JavaSdk
|
import com.intellij.openapi.projectRoots.JavaSdk
|
||||||
import com.intellij.openapi.projectRoots.ProjectJdkTable
|
import com.intellij.openapi.projectRoots.ProjectJdkTable
|
||||||
@@ -38,34 +44,39 @@ import com.intellij.openapi.projectRoots.impl.JavaAwareProjectJdkTableImpl
|
|||||||
import com.intellij.openapi.roots.FileIndexFacade
|
import com.intellij.openapi.roots.FileIndexFacade
|
||||||
import com.intellij.openapi.roots.ProjectRootManager
|
import com.intellij.openapi.roots.ProjectRootManager
|
||||||
import com.intellij.openapi.startup.StartupManager
|
import com.intellij.openapi.startup.StartupManager
|
||||||
import com.intellij.openapi.util.Computable
|
|
||||||
import com.intellij.openapi.util.Disposer
|
|
||||||
import com.intellij.openapi.vcs.changes.ChangeListManager
|
import com.intellij.openapi.vcs.changes.ChangeListManager
|
||||||
import com.intellij.openapi.vcs.changes.ChangeListManagerImpl
|
import com.intellij.openapi.vcs.changes.ChangeListManagerImpl
|
||||||
import com.intellij.openapi.vfs.LocalFileSystem
|
import com.intellij.openapi.vfs.LocalFileSystem
|
||||||
|
import com.intellij.openapi.vfs.VirtualFile
|
||||||
import com.intellij.openapi.vfs.VirtualFileManager
|
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
|
||||||
import com.intellij.psi.impl.search.IndexPatternBuilder
|
import com.intellij.psi.impl.search.IndexPatternBuilder
|
||||||
|
import com.intellij.psi.impl.source.resolve.reference.ReferenceProvidersRegistry
|
||||||
|
import com.intellij.psi.search.FilenameIndex
|
||||||
|
import com.intellij.psi.search.GlobalSearchScope
|
||||||
import com.intellij.psi.xml.XmlFileNSInfoProvider
|
import com.intellij.psi.xml.XmlFileNSInfoProvider
|
||||||
import com.intellij.testFramework.*
|
import com.intellij.testFramework.*
|
||||||
|
import com.intellij.testFramework.fixtures.EditorTestFixture
|
||||||
import com.intellij.testFramework.fixtures.impl.CodeInsightTestFixtureImpl
|
import com.intellij.testFramework.fixtures.impl.CodeInsightTestFixtureImpl
|
||||||
import com.intellij.testFramework.propertyBased.MadTestingUtil
|
|
||||||
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.io.exists
|
||||||
import com.intellij.xml.XmlSchemaProvider
|
import com.intellij.xml.XmlSchemaProvider
|
||||||
import org.jetbrains.kotlin.idea.KotlinLanguage
|
import org.jetbrains.kotlin.idea.KotlinLanguage
|
||||||
|
import org.jetbrains.kotlin.idea.configuration.getModulesWithKotlinFiles
|
||||||
import org.jetbrains.kotlin.idea.core.script.ScriptDefinitionsManager
|
import org.jetbrains.kotlin.idea.core.script.ScriptDefinitionsManager
|
||||||
import org.jetbrains.kotlin.idea.core.script.ScriptDependenciesManager
|
import org.jetbrains.kotlin.idea.core.script.ScriptDependenciesManager
|
||||||
import org.jetbrains.kotlin.idea.core.script.settings.KotlinScriptingSettings
|
import org.jetbrains.kotlin.idea.core.script.settings.KotlinScriptingSettings
|
||||||
import org.jetbrains.kotlin.idea.core.util.toPsiFile
|
import org.jetbrains.kotlin.idea.core.util.toPsiFile
|
||||||
import org.jetbrains.kotlin.idea.framework.KotlinSdkType
|
import org.jetbrains.kotlin.idea.framework.KotlinSdkType
|
||||||
|
import org.jetbrains.kotlin.idea.project.getAndCacheLanguageLevelByDependencies
|
||||||
import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil
|
import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil
|
||||||
import org.jetbrains.kotlin.idea.test.invalidateLibraryCache
|
import org.jetbrains.kotlin.idea.test.invalidateLibraryCache
|
||||||
import org.jetbrains.plugins.gradle.service.project.GradleProjectOpenProcessor
|
import org.jetbrains.plugins.gradle.service.project.GradleProjectOpenProcessor
|
||||||
|
import org.jetbrains.plugins.gradle.util.GradleConstants
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
|
|
||||||
@@ -84,7 +95,7 @@ abstract class AbstractPerformanceProjectsTest : UsefulTestCase() {
|
|||||||
super.setUp()
|
super.setUp()
|
||||||
|
|
||||||
myApplication = IdeaTestApplication.getInstance()
|
myApplication = IdeaTestApplication.getInstance()
|
||||||
ApplicationManager.getApplication().runWriteAction {
|
runWriteAction {
|
||||||
val jdkTableImpl = JavaAwareProjectJdkTableImpl.getInstanceEx()
|
val jdkTableImpl = JavaAwareProjectJdkTableImpl.getInstanceEx()
|
||||||
val homePath = if (jdkTableImpl.internalJdk.homeDirectory!!.name == "jre") {
|
val homePath = if (jdkTableImpl.internalJdk.homeDirectory!!.name == "jre") {
|
||||||
jdkTableImpl.internalJdk.homeDirectory!!.parent.path
|
jdkTableImpl.internalJdk.homeDirectory!!.parent.path
|
||||||
@@ -104,7 +115,7 @@ abstract class AbstractPerformanceProjectsTest : UsefulTestCase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected fun warmUpProject(stats: Stats) {
|
protected fun warmUpProject(stats: Stats) {
|
||||||
val project = innerPerfOpenProject("helloKotlin", stats, "warm-up")
|
val project = perfOpenHelloWorld(stats, "warm-up")
|
||||||
try {
|
try {
|
||||||
val perfHighlightFile = perfHighlightFile(project, "src/HelloMain.kt", stats, "warm-up")
|
val perfHighlightFile = perfHighlightFile(project, "src/HelloMain.kt", stats, "warm-up")
|
||||||
assertTrue("kotlin project has been not imported properly", perfHighlightFile.isNotEmpty())
|
assertTrue("kotlin project has been not imported properly", perfHighlightFile.isNotEmpty())
|
||||||
@@ -114,6 +125,7 @@ abstract class AbstractPerformanceProjectsTest : UsefulTestCase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun tearDown() {
|
override fun tearDown() {
|
||||||
|
commitAllDocuments()
|
||||||
RunAll(
|
RunAll(
|
||||||
ThrowableRunnable { super.tearDown() },
|
ThrowableRunnable { super.tearDown() },
|
||||||
ThrowableRunnable {
|
ThrowableRunnable {
|
||||||
@@ -127,41 +139,47 @@ abstract class AbstractPerformanceProjectsTest : UsefulTestCase() {
|
|||||||
}).run()
|
}).run()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun simpleFilename(fileName: String): String {
|
fun simpleFilename(fileName: String): String {
|
||||||
val lastIndexOf = fileName.lastIndexOf('/')
|
val lastIndexOf = fileName.lastIndexOf('/')
|
||||||
return if (lastIndexOf >= 0) fileName.substring(lastIndexOf + 1) else fileName
|
return if (lastIndexOf >= 0) fileName.substring(lastIndexOf + 1) else fileName
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun perfOpenKotlinProject(stats: Stats) =
|
protected fun perfOpenKotlinProjectFast(stats: Stats) =
|
||||||
perfOpenProject("perfTestProject", stats = stats, path = "..")
|
perfOpenKotlinProject(stats, fast = true)
|
||||||
|
|
||||||
protected fun perfOpenProject(name: String, stats: Stats, path: String = "idea/testData/perfTest") {
|
protected fun perfOpenKotlinProject(stats: Stats, fast: Boolean = false) {
|
||||||
myProject = innerPerfOpenProject(name, stats, path = path, note = "")
|
myProject = innerPerfOpenProject("kotlin", stats = stats, path = "../perfTestProject", note = "", fast = fast)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected fun perfOpenHelloWorld(stats: Stats, note: String = ""): Project =
|
||||||
|
innerPerfOpenProject("helloKotlin", stats, note, path = "idea/testData/perfTest/helloKotlin", simpleModule = true)
|
||||||
|
|
||||||
protected fun innerPerfOpenProject(
|
protected fun innerPerfOpenProject(
|
||||||
name: String,
|
name: String,
|
||||||
stats: Stats,
|
stats: Stats,
|
||||||
note: String,
|
note: String,
|
||||||
path: String = "idea/testData/perfTest"
|
path: String,
|
||||||
|
simpleModule: Boolean = false,
|
||||||
|
fast: Boolean = false
|
||||||
): Project {
|
): Project {
|
||||||
val projectPath = File("$path/$name").canonicalPath
|
val projectPath = File("$path").canonicalPath
|
||||||
|
|
||||||
val warmUpIterations = 1
|
val warmUpIterations = if (fast) 0 else 1
|
||||||
val iterations = 3
|
val iterations = if (fast) 1 else 3
|
||||||
val projectManagerEx = ProjectManagerEx.getInstanceEx()
|
val projectManagerEx = ProjectManagerEx.getInstanceEx()
|
||||||
|
|
||||||
var lastProject: Project? = null
|
var lastProject: Project? = null
|
||||||
var counter = 0
|
var counter = 0
|
||||||
|
|
||||||
stats.perfTest<Unit, Pair<Project, Boolean>>(
|
stats.perfTest<Unit, Project>(
|
||||||
warmUpIterations = warmUpIterations,
|
warmUpIterations = warmUpIterations,
|
||||||
iterations = iterations,
|
iterations = iterations,
|
||||||
testName = "open project${if (note.isNotEmpty()) " $note" else ""}",
|
testName = "open project${if (note.isNotEmpty()) " $note" else ""}",
|
||||||
test = {
|
test = {
|
||||||
val projectPathExists = Paths.get(projectPath, ".idea").exists()
|
val project = if (!simpleModule) {
|
||||||
val project = if (projectPathExists) {
|
val project = projectManagerEx.loadProject(name, path)
|
||||||
val project = ProjectUtil.openProject(projectPath, null, false)!!
|
assertNotNull(project)
|
||||||
|
//projectManagerEx.openTestProject(project!!)
|
||||||
project
|
project
|
||||||
} else {
|
} else {
|
||||||
val project = projectManagerEx.loadAndOpenProject(projectPath)!!
|
val project = projectManagerEx.loadAndOpenProject(projectPath)!!
|
||||||
@@ -174,8 +192,6 @@ abstract class AbstractPerformanceProjectsTest : UsefulTestCase() {
|
|||||||
FileEditorManagerImpl::class.java
|
FileEditorManagerImpl::class.java
|
||||||
)
|
)
|
||||||
|
|
||||||
projectManagerEx.openTestProject(project)
|
|
||||||
|
|
||||||
dispatchAllInvocationEvents()
|
dispatchAllInvocationEvents()
|
||||||
|
|
||||||
with(StartupManager.getInstance(project) as StartupManagerImpl) {
|
with(StartupManager.getInstance(project) as StartupManagerImpl) {
|
||||||
@@ -187,30 +203,23 @@ abstract class AbstractPerformanceProjectsTest : UsefulTestCase() {
|
|||||||
waitUntilRefreshed()
|
waitUntilRefreshed()
|
||||||
}
|
}
|
||||||
|
|
||||||
it.value = Pair(project, projectPathExists)
|
it.value = project
|
||||||
},
|
},
|
||||||
tearDown = {
|
tearDown = {
|
||||||
it.value?.let { pair ->
|
it.value?.let { project ->
|
||||||
val project = pair.first
|
|
||||||
|
|
||||||
// import gradle project if `$project/.idea` is present but modules are not imported
|
runAndMeasure("refresh gradle project $name") {
|
||||||
// it is a temporary dirty hack as it is fixed in latest IC:
|
refreshGradleProjectIfNeeded(projectPath, project)
|
||||||
// ProjectUtil.openProject picks up gradle import via extension point
|
|
||||||
if (pair.second && ModuleManager.getInstance(project).modules.isEmpty()) {
|
|
||||||
dispatchAllInvocationEvents()
|
|
||||||
|
|
||||||
val virtualFile = LocalFileSystem.getInstance().refreshAndFindFileByPath(projectPath)!!
|
|
||||||
|
|
||||||
FileDocumentManager.getInstance().saveAllDocuments()
|
|
||||||
|
|
||||||
GradleProjectOpenProcessor.openGradleProject(project, null, Paths.get(virtualFile.path))
|
|
||||||
|
|
||||||
dispatchAllInvocationEvents()
|
|
||||||
runInEdtAndWait {
|
|
||||||
PlatformTestUtil.saveProject(project)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ApplicationManager.getApplication().executeOnPooledThread {
|
||||||
|
DumbService.getInstance(project).waitForSmartMode()
|
||||||
|
|
||||||
|
for (module in getModulesWithKotlinFiles(project)) {
|
||||||
|
module.getAndCacheLanguageLevelByDependencies()
|
||||||
|
}
|
||||||
|
}.get()
|
||||||
|
|
||||||
assertTrue(
|
assertTrue(
|
||||||
"project has to have at least one module",
|
"project has to have at least one module",
|
||||||
ModuleManager.getInstance(project).modules.isNotEmpty()
|
ModuleManager.getInstance(project).modules.isNotEmpty()
|
||||||
@@ -219,6 +228,12 @@ abstract class AbstractPerformanceProjectsTest : UsefulTestCase() {
|
|||||||
lastProject = project
|
lastProject = project
|
||||||
VirtualFileManager.getInstance().syncRefresh()
|
VirtualFileManager.getInstance().syncRefresh()
|
||||||
|
|
||||||
|
runWriteAction {
|
||||||
|
project.save()
|
||||||
|
}
|
||||||
|
|
||||||
|
println("# 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) {
|
||||||
closeProject(project)
|
closeProject(project)
|
||||||
@@ -248,13 +263,218 @@ abstract class AbstractPerformanceProjectsTest : UsefulTestCase() {
|
|||||||
return lastProject!!
|
return lastProject!!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun openGradleProject(projectPath: String, project: Project) {
|
||||||
|
dispatchAllInvocationEvents()
|
||||||
|
|
||||||
|
val virtualFile = LocalFileSystem.getInstance().refreshAndFindFileByPath(projectPath)!!
|
||||||
|
|
||||||
|
FileDocumentManager.getInstance().saveAllDocuments()
|
||||||
|
|
||||||
|
val path = Paths.get(virtualFile.path)
|
||||||
|
GradleProjectOpenProcessor.openGradleProject(project, null, path)
|
||||||
|
|
||||||
|
dispatchAllInvocationEvents()
|
||||||
|
runInEdtAndWait {
|
||||||
|
PlatformTestUtil.saveProject(project)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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(false)
|
||||||
|
|
||||||
|
GradleProjectOpenProcessor.openGradleProject(project, null, Paths.get(projectPath))
|
||||||
|
|
||||||
|
ExternalSystemUtil.refreshProjects(
|
||||||
|
ImportSpecBuilder(project, GradleConstants.SYSTEM_ID)
|
||||||
|
.forceWhenUptodate()
|
||||||
|
.useDefaultCallback()
|
||||||
|
.use(ProgressExecutionMode.MODAL_SYNC)
|
||||||
|
)
|
||||||
|
|
||||||
|
dispatchAllInvocationEvents()
|
||||||
|
ExternalProjectsManagerImpl.getInstance(project).setStoreExternally(false)
|
||||||
|
dispatchAllInvocationEvents()
|
||||||
|
|
||||||
|
// WARNING: [VD] DO NOT SAVE PROJECT AS IT COULD PERSIST WRONG MODULES INFO
|
||||||
|
|
||||||
|
// runInEdtAndWait {
|
||||||
|
// PlatformTestUtil.saveProject(project)
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param lookupElements perform basic autocompletion and check presence of suggestion if elements are not empty
|
||||||
|
*/
|
||||||
|
fun typeAndCheckLookup(
|
||||||
|
project: Project,
|
||||||
|
fileName: String,
|
||||||
|
marker: String,
|
||||||
|
insertString: String,
|
||||||
|
surroundItems: String = "\n",
|
||||||
|
lookupElements: List<String>,
|
||||||
|
revertChangesAtTheEnd: Boolean = true
|
||||||
|
) {
|
||||||
|
val fileInEditor = openFileInEditor(project, fileName)
|
||||||
|
val virtualFile = fileInEditor.psiFile.virtualFile
|
||||||
|
val editor = EditorFactory.getInstance().getEditors(fileInEditor.document, project)[0]
|
||||||
|
val editorFixture = EditorTestFixture(project, editor, virtualFile)
|
||||||
|
|
||||||
|
val initialText = editor.document.text
|
||||||
|
try {
|
||||||
|
if (isAKotlinScriptFile(fileName)) {
|
||||||
|
ScriptDependenciesManager.updateScriptDependenciesSynchronously(virtualFile, project)
|
||||||
|
}
|
||||||
|
|
||||||
|
val tasksIdx = fileInEditor.document.text.indexOf(marker)
|
||||||
|
assertTrue(tasksIdx > 0)
|
||||||
|
editor.caretModel.moveToOffset(tasksIdx + marker.length + 1)
|
||||||
|
|
||||||
|
for (surroundItem in surroundItems) {
|
||||||
|
EditorTestUtil.performTypingAction(editor, surroundItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
editor.caretModel.moveToOffset(editor.caretModel.offset - 1)
|
||||||
|
editorFixture.type(insertString)
|
||||||
|
|
||||||
|
if (lookupElements.isNotEmpty()) {
|
||||||
|
val elements = editorFixture.complete(CompletionType.BASIC, 1) ?: emptyArray()
|
||||||
|
val items = elements.map { it.lookupString }.toList()
|
||||||
|
for (lookupElement in lookupElements) {
|
||||||
|
assertTrue("'$lookupElement' has to be present in items", items.contains(lookupElement))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
// TODO: [VD] revert ?
|
||||||
|
//editorFixture.performEditorAction(IdeActions.SELECTED_CHANGES_ROLLBACK)
|
||||||
|
if (revertChangesAtTheEnd) {
|
||||||
|
runWriteAction {
|
||||||
|
editor.document.setText(initialText)
|
||||||
|
commitDocument(project, editor.document)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun perfTypeAndAutocomplete(
|
||||||
|
stats: Stats,
|
||||||
|
fileName: String,
|
||||||
|
marker: String,
|
||||||
|
insertString: String,
|
||||||
|
surroundItems: String = "\n",
|
||||||
|
lookupElements: List<String>,
|
||||||
|
typeAfterMarker: Boolean = true,
|
||||||
|
revertChangesAtTheEnd: Boolean = true,
|
||||||
|
note: String = ""
|
||||||
|
) = perfTypeAndAutocomplete(
|
||||||
|
myProject!!, stats, fileName, marker, insertString, surroundItems,
|
||||||
|
lookupElements = lookupElements, typeAfterMarker = typeAfterMarker,
|
||||||
|
revertChangesAtTheEnd = revertChangesAtTheEnd, note = note
|
||||||
|
)
|
||||||
|
|
||||||
|
fun perfTypeAndAutocomplete(
|
||||||
|
project: Project,
|
||||||
|
stats: Stats,
|
||||||
|
fileName: String,
|
||||||
|
marker: String,
|
||||||
|
insertString: String,
|
||||||
|
surroundItems: String = "\n",
|
||||||
|
lookupElements: List<String>,
|
||||||
|
typeAfterMarker: Boolean = true,
|
||||||
|
revertChangesAtTheEnd: Boolean = true,
|
||||||
|
note: String = ""
|
||||||
|
) {
|
||||||
|
assertTrue("lookupElements has to be not empty", lookupElements.isNotEmpty())
|
||||||
|
stats.perfTest<Pair<String, FixtureEditorFile>, Array<LookupElement>>(
|
||||||
|
warmUpIterations = 8,
|
||||||
|
iterations = 15,
|
||||||
|
testName = "typeAndAutocomplete ${notePrefix(note)}$fileName",
|
||||||
|
setUp = {
|
||||||
|
val fileInEditor = openFileInEditor(project, fileName)
|
||||||
|
val file = fileInEditor.psiFile
|
||||||
|
val virtualFile = file.virtualFile
|
||||||
|
val editor = EditorFactory.getInstance().getEditors(fileInEditor.document, project)[0]
|
||||||
|
val fixture = EditorTestFixture(project, editor, virtualFile)
|
||||||
|
val initialText = editor.document.text
|
||||||
|
if (isAKotlinScriptFile(fileName)) {
|
||||||
|
runAndMeasure("update script dependencies for $fileName") {
|
||||||
|
ScriptDependenciesManager.updateScriptDependenciesSynchronously(virtualFile, project)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val tasksIdx = fileInEditor.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)
|
||||||
|
}
|
||||||
|
|
||||||
|
fixture.type(insertString)
|
||||||
|
|
||||||
|
it.setUpValue = Pair(initialText, FixtureEditorFile(file, editor.document, fixture))
|
||||||
|
},
|
||||||
|
test = {
|
||||||
|
val fixture = it.setUpValue!!.second.fixture
|
||||||
|
it.value = fixture.complete(CompletionType.BASIC, 1) ?: emptyArray()
|
||||||
|
},
|
||||||
|
tearDown = {
|
||||||
|
val items = it.value?.map { e -> e.lookupString }?.toList() ?: emptyList()
|
||||||
|
try {
|
||||||
|
for (lookupElement in lookupElements) {
|
||||||
|
assertTrue("'$lookupElement' has to be present in items", items.contains(lookupElement))
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
it.setUpValue?.let { pair ->
|
||||||
|
val document = pair.second.document
|
||||||
|
val file = pair.second.psiFile
|
||||||
|
val text = pair.first
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (revertChangesAtTheEnd) {
|
||||||
|
runWriteAction {
|
||||||
|
// TODO: [VD] revert ?
|
||||||
|
//editorFixture.performEditorAction(IdeActions.SELECTED_CHANGES_ROLLBACK)
|
||||||
|
document.setText(text)
|
||||||
|
saveDocument(document)
|
||||||
|
commitDocument(project, document)
|
||||||
|
}
|
||||||
|
dispatchAllInvocationEvents()
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
cleanupCaches(project, file.virtualFile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
commitAllDocuments()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
protected fun enableAnnotatorsAndLoadDefinitions() = enableAnnotatorsAndLoadDefinitions(myProject!!)
|
protected fun enableAnnotatorsAndLoadDefinitions() = enableAnnotatorsAndLoadDefinitions(myProject!!)
|
||||||
|
|
||||||
protected fun enableAnnotatorsAndLoadDefinitions(project: Project) {
|
protected fun enableAnnotatorsAndLoadDefinitions(project: Project) {
|
||||||
|
ReferenceProvidersRegistry.getInstance() // pre-load tons of classes
|
||||||
InjectedLanguageManager.getInstance(project) // zillion of Dom Sem classes
|
InjectedLanguageManager.getInstance(project) // zillion of Dom Sem classes
|
||||||
LanguageAnnotators.INSTANCE.allForLanguage(JavaLanguage.INSTANCE) // pile of annotator classes loads
|
with(LanguageAnnotators.INSTANCE) {
|
||||||
LanguageAnnotators.INSTANCE.allForLanguage(StdLanguages.XML)
|
allForLanguage(JavaLanguage.INSTANCE) // pile of annotator classes loads
|
||||||
LanguageAnnotators.INSTANCE.allForLanguage(KotlinLanguage.INSTANCE)
|
allForLanguage(StdLanguages.XML)
|
||||||
|
allForLanguage(KotlinLanguage.INSTANCE)
|
||||||
|
}
|
||||||
DaemonAnalyzerTestCase.assertTrue(
|
DaemonAnalyzerTestCase.assertTrue(
|
||||||
"side effect: to load extensions",
|
"side effect: to load extensions",
|
||||||
ProblemHighlightFilter.EP_NAME.extensions.toMutableList()
|
ProblemHighlightFilter.EP_NAME.extensions.toMutableList()
|
||||||
@@ -264,6 +484,7 @@ abstract class AbstractPerformanceProjectsTest : UsefulTestCase() {
|
|||||||
.plus(ExternalAnnotatorsFilter.EXTENSION_POINT_NAME.extensions)
|
.plus(ExternalAnnotatorsFilter.EXTENSION_POINT_NAME.extensions)
|
||||||
.plus(IndexPatternBuilder.EP_NAME.extensions).isNotEmpty()
|
.plus(IndexPatternBuilder.EP_NAME.extensions).isNotEmpty()
|
||||||
)
|
)
|
||||||
|
|
||||||
// side effect: to load script definitions"
|
// side effect: to load script definitions"
|
||||||
val scriptDefinitionsManager = ScriptDefinitionsManager.getInstance(project)
|
val scriptDefinitionsManager = ScriptDefinitionsManager.getInstance(project)
|
||||||
scriptDefinitionsManager.getAllDefinitions()
|
scriptDefinitionsManager.getAllDefinitions()
|
||||||
@@ -282,7 +503,7 @@ abstract class AbstractPerformanceProjectsTest : UsefulTestCase() {
|
|||||||
val modulePath = "$projectPath/$name${ModuleFileType.DOT_DEFAULT_EXTENSION}"
|
val modulePath = "$projectPath/$name${ModuleFileType.DOT_DEFAULT_EXTENSION}"
|
||||||
val projectFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(File(projectPath))!!
|
val projectFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(File(projectPath))!!
|
||||||
val srcFile = projectFile.findChild("src")!!
|
val srcFile = projectFile.findChild("src")!!
|
||||||
val module = ApplicationManager.getApplication().runWriteAction(Computable<Module> {
|
val module = runWriteAction {
|
||||||
val projectRootManager = ProjectRootManager.getInstance(project)
|
val projectRootManager = ProjectRootManager.getInstance(project)
|
||||||
with(projectRootManager) {
|
with(projectRootManager) {
|
||||||
projectSdk = jdk18
|
projectSdk = jdk18
|
||||||
@@ -291,7 +512,7 @@ abstract class AbstractPerformanceProjectsTest : UsefulTestCase() {
|
|||||||
val module = moduleManager.newModule(modulePath, ModuleTypeId.JAVA_MODULE)
|
val module = moduleManager.newModule(modulePath, ModuleTypeId.JAVA_MODULE)
|
||||||
PsiTestUtil.addSourceRoot(module, srcFile)
|
PsiTestUtil.addSourceRoot(module, srcFile)
|
||||||
module
|
module
|
||||||
})
|
}
|
||||||
ConfigLibraryUtil.configureKotlinRuntimeAndSdk(module, jdk18)
|
ConfigLibraryUtil.configureKotlinRuntimeAndSdk(module, jdk18)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -305,9 +526,12 @@ abstract class AbstractPerformanceProjectsTest : UsefulTestCase() {
|
|||||||
note: String = ""
|
note: String = ""
|
||||||
): List<HighlightInfo> {
|
): List<HighlightInfo> {
|
||||||
return highlightFile {
|
return highlightFile {
|
||||||
|
val isWarmUp = note == "warm-up"
|
||||||
var highlightInfos: List<HighlightInfo> = emptyList()
|
var highlightInfos: List<HighlightInfo> = emptyList()
|
||||||
stats.perfTest<EditorFile, List<HighlightInfo>>(
|
stats.perfTest<EditorFile, List<HighlightInfo>>(
|
||||||
testName = "highlighting ${if (note.isNotEmpty()) "$note " else ""}${simpleFilename(fileName)}",
|
warmUpIterations = if (isWarmUp) 1 else 3,
|
||||||
|
iterations = if (isWarmUp) 2 else 10,
|
||||||
|
testName = "highlighting ${notePrefix(note)}${simpleFilename(fileName)}",
|
||||||
setUp = {
|
setUp = {
|
||||||
it.setUpValue = openFileInEditor(project, fileName)
|
it.setUpValue = openFileInEditor(project, fileName)
|
||||||
},
|
},
|
||||||
@@ -332,12 +556,12 @@ abstract class AbstractPerformanceProjectsTest : UsefulTestCase() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun highlightFile(block: () -> List<HighlightInfo>): List<HighlightInfo> {
|
fun <T> highlightFile(block: () -> T): T {
|
||||||
var highlightInfos: List<HighlightInfo> = emptyList()
|
var value: T? = null
|
||||||
IdentifierHighlighterPassFactory.doWithHighlightingEnabled {
|
IdentifierHighlighterPassFactory.doWithHighlightingEnabled {
|
||||||
highlightInfos = block()
|
value = block()
|
||||||
}
|
}
|
||||||
return highlightInfos
|
return value!!
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun highlightFile(project: Project, psiFile: PsiFile): List<HighlightInfo> {
|
private fun highlightFile(project: Project, psiFile: PsiFile): List<HighlightInfo> {
|
||||||
@@ -347,74 +571,41 @@ abstract class AbstractPerformanceProjectsTest : UsefulTestCase() {
|
|||||||
return CodeInsightTestFixtureImpl.instantiateAndRun(psiFile, editor, ArrayUtilRt.EMPTY_INT_ARRAY, true)
|
return CodeInsightTestFixtureImpl.instantiateAndRun(psiFile, editor, ArrayUtilRt.EMPTY_INT_ARRAY, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun perfFileAnalysis(name: String, stats: Stats, note: String = "") =
|
protected fun perfScriptDependencies(name: String, stats: Stats, note: String = "") =
|
||||||
perfFileAnalysis(myProject!!, name, stats, note = note)
|
perfScriptDependencies(myProject!!, name, stats, note = note)
|
||||||
|
|
||||||
private fun perfFileAnalysis(
|
private fun perfScriptDependencies(
|
||||||
project: Project,
|
project: Project,
|
||||||
fileName: String,
|
fileName: String,
|
||||||
stats: Stats,
|
stats: Stats,
|
||||||
note: String = ""
|
note: String = ""
|
||||||
) {
|
) {
|
||||||
val disposable = Disposer.newDisposable("perfFileAnalysis $fileName")
|
if (!isAKotlinScriptFile(fileName)) return
|
||||||
|
stats.perfTest<EditorFile, EditorFile>(
|
||||||
MadTestingUtil.enableAllInspections(project, disposable)
|
testName = "updateScriptDependencies ${notePrefix(note)}${simpleFilename(fileName)}",
|
||||||
|
setUp = { it.setUpValue = openFileInEditor(project, fileName) },
|
||||||
try {
|
test = {
|
||||||
IdentifierHighlighterPassFactory.doWithHighlightingEnabled {
|
ScriptDependenciesManager.updateScriptDependenciesSynchronously(it.setUpValue!!.psiFile.virtualFile, project)
|
||||||
stats.perfTest(
|
it.value = it.setUpValue
|
||||||
testName = "fileAnalysis ${if (note.isNotEmpty()) "$note " else ""}${simpleFilename(fileName)}",
|
},
|
||||||
setUp = perfFileAnalysisSetUp(project, fileName),
|
tearDown = {
|
||||||
test = perfFileAnalysisTest(project),
|
it.setUpValue?.let { ef -> cleanupCaches(project, ef.psiFile.virtualFile) }
|
||||||
tearDown = perfFileAnalysisTearDown(fileName, project)
|
it.value?.let { v -> assertNotNull(v) }
|
||||||
)
|
|
||||||
}
|
}
|
||||||
} finally {
|
)
|
||||||
Disposer.dispose(disposable)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun perfFileAnalysisSetUp(
|
fun notePrefix(note: String) = if (note.isNotEmpty()) {
|
||||||
project: Project,
|
if (note.endsWith("/")) note else "$note "
|
||||||
fileName: String
|
} else ""
|
||||||
): (TestData<EditorFile, List<HighlightInfo>>) -> Unit {
|
|
||||||
return {
|
|
||||||
val fileInEditor = openFileInEditor(project, fileName)
|
|
||||||
|
|
||||||
// Note: Kotlin scripts require dependencies to be loaded
|
|
||||||
if (isAKotlinScriptFile(fileName)) {
|
|
||||||
val vFile = fileInEditor.psiFile.virtualFile
|
|
||||||
ScriptDependenciesManager.updateScriptDependenciesSynchronously(vFile, project)
|
|
||||||
}
|
|
||||||
|
|
||||||
//enableHints(false)
|
|
||||||
|
|
||||||
println("fileAnalysis -> $fileName\n")
|
|
||||||
it.setUpValue = fileInEditor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// quite simple impl - good so far
|
// quite simple impl - good so far
|
||||||
fun isAKotlinScriptFile(fileName: String) = fileName.endsWith(".kts")
|
fun isAKotlinScriptFile(fileName: String) = fileName.endsWith(".kts")
|
||||||
|
|
||||||
private fun perfFileAnalysisTest(project: Project): (TestData<EditorFile, List<HighlightInfo>>) -> Unit {
|
fun cleanupCaches(project: Project, vFile: VirtualFile) {
|
||||||
return {
|
commitAllDocuments()
|
||||||
val fileInEditor = it.setUpValue!!
|
FileEditorManager.getInstance(project).closeFile(vFile)
|
||||||
it.value = highlightFile(project, fileInEditor.psiFile)
|
PsiManager.getInstance(project).dropPsiCaches()
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun perfFileAnalysisTearDown(
|
|
||||||
fileName: String,
|
|
||||||
project: Project
|
|
||||||
): (TestData<EditorFile, List<HighlightInfo>>) -> Unit {
|
|
||||||
return {
|
|
||||||
commitAllDocuments()
|
|
||||||
//println("fileAnalysis <- $fileName:\n${it.value?.joinToString("\n")}\n")
|
|
||||||
println("fileAnalysis <- $fileName:\n${it.value?.size ?: 0} highlightInfos\n")
|
|
||||||
FileEditorManager.getInstance(project).closeFile(it.setUpValue!!.psiFile.virtualFile)
|
|
||||||
PsiManager.getInstance(project).dropPsiCaches()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun openFileInEditor(project: Project, name: String): EditorFile {
|
fun openFileInEditor(project: Project, name: String): EditorFile {
|
||||||
@@ -434,18 +625,47 @@ abstract class AbstractPerformanceProjectsTest : UsefulTestCase() {
|
|||||||
assertNotNull("doc not found for $vFile", EditorFactory.getInstance().getEditors(document))
|
assertNotNull("doc not found for $vFile", EditorFactory.getInstance().getEditors(document))
|
||||||
assertTrue("expected non empty doc", document.text.isNotEmpty())
|
assertTrue("expected non empty doc", document.text.isNotEmpty())
|
||||||
|
|
||||||
|
val offset = psiFile.textOffset
|
||||||
|
assertTrue("side effect: to load the text", offset >= 0)
|
||||||
|
|
||||||
waitForAllEditorsFinallyLoaded(project)
|
waitForAllEditorsFinallyLoaded(project)
|
||||||
|
|
||||||
return EditorFile(psiFile = psiFile, document = document)
|
return EditorFile(psiFile = psiFile, document = document)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun baseName(name: String): String {
|
||||||
|
val index = name.lastIndexOf("/")
|
||||||
|
return if (index > 0) name.substring(index + 1) else name
|
||||||
|
}
|
||||||
|
|
||||||
private fun projectFileByName(project: Project, name: String): PsiFile {
|
private fun projectFileByName(project: Project, name: String): PsiFile {
|
||||||
val fileManager = VirtualFileManager.getInstance()
|
val fileManager = VirtualFileManager.getInstance()
|
||||||
val url = "file://${File("${project.basePath}/$name").absolutePath}"
|
val url = "${project.guessProjectDir()}/$name"
|
||||||
val virtualFile = fileManager.refreshAndFindFileByUrl(url)
|
val virtualFile = fileManager.refreshAndFindFileByUrl(url)
|
||||||
return virtualFile!!.toPsiFile(project)!!
|
if (virtualFile != null) {
|
||||||
|
return virtualFile!!.toPsiFile(project)!!
|
||||||
|
}
|
||||||
|
|
||||||
|
val baseFileName = baseName(name)
|
||||||
|
val projectBaseName = baseName(project.name)
|
||||||
|
|
||||||
|
val virtualFiles = FilenameIndex.getVirtualFilesByName(
|
||||||
|
project,
|
||||||
|
baseFileName, true,
|
||||||
|
GlobalSearchScope.projectScope(project)
|
||||||
|
)
|
||||||
|
.filter { it.canonicalPath?.contains("/$projectBaseName/$name") ?: false }.toList()
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
"expected the only file with name '$name'\n, it were: [${virtualFiles.map { it.canonicalPath }.joinToString("\n")}]",
|
||||||
|
1,
|
||||||
|
virtualFiles.size
|
||||||
|
)
|
||||||
|
return virtualFiles.iterator().next().toPsiFile(project)!!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class FixtureEditorFile(val psiFile: PsiFile, val document: Document, val fixture: EditorTestFixture)
|
||||||
|
|
||||||
data class EditorFile(val psiFile: PsiFile, val document: Document)
|
data class EditorFile(val psiFile: PsiFile, val document: Document)
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,25 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.idea.perf
|
package org.jetbrains.kotlin.idea.perf
|
||||||
|
|
||||||
|
import com.intellij.codeInsight.daemon.impl.HighlightInfo
|
||||||
|
import com.intellij.lang.LanguageAnnotators
|
||||||
|
import com.intellij.lang.LanguageExtensionPoint
|
||||||
|
import com.intellij.lang.annotation.AnnotationHolder
|
||||||
|
import com.intellij.lang.annotation.Annotator
|
||||||
|
import com.intellij.openapi.editor.EditorFactory
|
||||||
|
import com.intellij.openapi.extensions.ExtensionPointName
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.openapi.util.Disposer
|
||||||
|
import com.intellij.psi.PsiElement
|
||||||
|
import com.intellij.testFramework.PlatformTestUtil
|
||||||
|
import com.intellij.testFramework.fixtures.EditorTestFixture
|
||||||
|
import com.intellij.testFramework.propertyBased.MadTestingUtil
|
||||||
|
import org.jetbrains.kotlin.idea.core.script.ScriptDependenciesManager
|
||||||
|
import org.jetbrains.kotlin.idea.highlighter.KotlinPsiChecker
|
||||||
|
import org.jetbrains.kotlin.idea.highlighter.KotlinPsiCheckerAndHighlightingUpdater
|
||||||
|
import java.util.concurrent.atomic.AtomicLong
|
||||||
|
import kotlin.test.assertNotEquals
|
||||||
|
|
||||||
class PerformanceProjectsTest : AbstractPerformanceProjectsTest() {
|
class PerformanceProjectsTest : AbstractPerformanceProjectsTest() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -15,11 +34,21 @@ class PerformanceProjectsTest : AbstractPerformanceProjectsTest() {
|
|||||||
@JvmStatic
|
@JvmStatic
|
||||||
val hwStats: Stats = Stats("helloWorld project")
|
val hwStats: Stats = Stats("helloWorld project")
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
val timer: AtomicLong = AtomicLong()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// there is no @AfterClass for junit3.8
|
// there is no @AfterClass for junit3.8
|
||||||
Runtime.getRuntime().addShutdownHook(Thread(Runnable { hwStats.close() }))
|
Runtime.getRuntime().addShutdownHook(Thread(Runnable { hwStats.close() }))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun resetTimestamp() {
|
||||||
|
timer.set(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun markTimestamp() {
|
||||||
|
timer.set(System.nanoTime())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setUp() {
|
override fun setUp() {
|
||||||
@@ -34,7 +63,7 @@ class PerformanceProjectsTest : AbstractPerformanceProjectsTest() {
|
|||||||
|
|
||||||
fun testHelloWorldProject() {
|
fun testHelloWorldProject() {
|
||||||
tcSuite("Hello world project") {
|
tcSuite("Hello world project") {
|
||||||
perfOpenProject("helloKotlin", hwStats)
|
myProject = perfOpenHelloWorld(hwStats)
|
||||||
|
|
||||||
// highlight
|
// highlight
|
||||||
perfHighlightFile("src/HelloMain.kt", hwStats)
|
perfHighlightFile("src/HelloMain.kt", hwStats)
|
||||||
@@ -50,41 +79,243 @@ class PerformanceProjectsTest : AbstractPerformanceProjectsTest() {
|
|||||||
|
|
||||||
perfHighlightFile("compiler/psi/src/org/jetbrains/kotlin/psi/KtFile.kt", stats = it)
|
perfHighlightFile("compiler/psi/src/org/jetbrains/kotlin/psi/KtFile.kt", stats = it)
|
||||||
|
|
||||||
|
perfHighlightFile("idea/idea-analysis/src/org/jetbrains/kotlin/idea/util/PsiPrecedences.kt", stats = it)
|
||||||
|
|
||||||
perfHighlightFile("compiler/psi/src/org/jetbrains/kotlin/psi/KtElement.kt", stats = it)
|
perfHighlightFile("compiler/psi/src/org/jetbrains/kotlin/psi/KtElement.kt", stats = it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun testKotlinProjectHighlightBuildGradle() {
|
fun testKotlinProjectCompletionKtFile() {
|
||||||
tcSuite("Kotlin project highlight build gradle") {
|
tcSuite("Kotlin completion ktFile") {
|
||||||
val stats = Stats("kotlin project highlight build gradle")
|
val stats = Stats("Kotlin completion ktFile")
|
||||||
stats.use {
|
stats.use { stat ->
|
||||||
perfOpenKotlinProject(it)
|
perfOpenKotlinProjectFast(stat)
|
||||||
|
|
||||||
enableAnnotatorsAndLoadDefinitions()
|
perfTypeAndAutocomplete(
|
||||||
|
stat,
|
||||||
|
"compiler/psi/src/org/jetbrains/kotlin/psi/KtFile.kt",
|
||||||
|
"override fun getDeclarations(): List<KtDeclaration> {",
|
||||||
|
"val q = import",
|
||||||
|
lookupElements = listOf("importDirectives"),
|
||||||
|
note = "in-method getDeclarations-import"
|
||||||
|
)
|
||||||
|
|
||||||
perfFileAnalysisBuildGradleKts(it)
|
perfTypeAndAutocomplete(
|
||||||
perfFileAnalysisIdeaBuildGradleKts(it)
|
stat,
|
||||||
perfFileAnalysisJpsGradleKts(it)
|
"compiler/psi/src/org/jetbrains/kotlin/psi/KtFile.kt",
|
||||||
perfFileAnalysisVersionGradleKts(it)
|
"override fun getDeclarations(): List<KtDeclaration> {",
|
||||||
|
"val q = import",
|
||||||
|
typeAfterMarker = false,
|
||||||
|
lookupElements = listOf("importDirectives"),
|
||||||
|
note = "out-of-method import"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun testKotlinProjectCompletionBuildGradle() {
|
||||||
|
tcSuite("Kotlin completion gradle.kts") {
|
||||||
|
val stats = Stats("kotlin completion gradle.kts")
|
||||||
|
stats.use { stat ->
|
||||||
|
runAndMeasure("open kotlin project") {
|
||||||
|
perfOpenKotlinProjectFast(stat)
|
||||||
|
}
|
||||||
|
|
||||||
|
runAndMeasure("type and autocomplete") {
|
||||||
|
perfTypeAndAutocomplete(
|
||||||
|
stat,
|
||||||
|
"build.gradle.kts",
|
||||||
|
"tasks {",
|
||||||
|
"crea",
|
||||||
|
lookupElements = listOf("create"),
|
||||||
|
note = "tasks-create"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testKotlinProjectScriptDependenciesBuildGradle() {
|
||||||
|
tcSuite("Kotlin scriptDependencies gradle.kts") {
|
||||||
|
val stats = Stats("kotlin scriptDependencies gradle.kts")
|
||||||
|
stats.use { stat ->
|
||||||
|
perfOpenKotlinProjectFast(stat)
|
||||||
|
|
||||||
|
perfScriptDependenciesBuildGradleKts(stat)
|
||||||
|
perfScriptDependenciesIdeaBuildGradleKts(stat)
|
||||||
|
perfScriptDependenciesJpsGradleKts(stat)
|
||||||
|
perfScriptDependenciesVersionGradleKts(stat)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testKotlinProjectBuildGradle() {
|
||||||
|
tcSuite("Kotlin gradle.kts") {
|
||||||
|
val stats = Stats("kotlin gradle.kts")
|
||||||
|
stats.use { stat ->
|
||||||
|
perfOpenKotlinProjectFast(stat)
|
||||||
|
|
||||||
|
perfFileAnalysisBuildGradleKts(stat)
|
||||||
|
perfFileAnalysisIdeaBuildGradleKts(stat)
|
||||||
|
perfFileAnalysisJpsGradleKts(stat)
|
||||||
|
perfFileAnalysisVersionGradleKts(stat)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun perfScriptDependenciesBuildGradleKts(it: Stats) {
|
||||||
|
perfScriptDependencies("build.gradle.kts", stats = it)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun perfScriptDependenciesIdeaBuildGradleKts(it: Stats) {
|
||||||
|
perfScriptDependencies("idea/build.gradle.kts", stats = it, note = "idea/")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun perfScriptDependenciesJpsGradleKts(it: Stats) {
|
||||||
|
perfScriptDependencies("gradle/jps.gradle.kts", stats = it, note = "gradle/")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun perfScriptDependenciesVersionGradleKts(it: Stats) {
|
||||||
|
perfScriptDependencies("gradle/versions.gradle.kts", stats = it, note = "gradle/")
|
||||||
|
}
|
||||||
|
|
||||||
private fun perfFileAnalysisBuildGradleKts(it: Stats) {
|
private fun perfFileAnalysisBuildGradleKts(it: Stats) {
|
||||||
perfFileAnalysis("build.gradle.kts", stats = it)
|
perfKtsFileAnalysis("build.gradle.kts", stats = it)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun perfFileAnalysisIdeaBuildGradleKts(it: Stats) {
|
private fun perfFileAnalysisIdeaBuildGradleKts(it: Stats) {
|
||||||
perfFileAnalysis("idea/build.gradle.kts", stats = it, note = "idea/")
|
perfKtsFileAnalysis("idea/build.gradle.kts", stats = it, note = "idea/")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun perfFileAnalysisJpsGradleKts(it: Stats) {
|
private fun perfFileAnalysisJpsGradleKts(it: Stats) {
|
||||||
perfFileAnalysis("gradle/jps.gradle.kts", stats = it, note = "gradle/")
|
perfKtsFileAnalysis("gradle/jps.gradle.kts", stats = it, note = "gradle/")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun perfFileAnalysisVersionGradleKts(it: Stats) {
|
private fun perfFileAnalysisVersionGradleKts(it: Stats) {
|
||||||
perfFileAnalysis("gradle/versions.gradle.kts", stats = it, note = "gradle/")
|
perfKtsFileAnalysis("gradle/versions.gradle.kts", stats = it, note = "gradle/")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun perfKtsFileAnalysis(
|
||||||
|
fileName: String,
|
||||||
|
stats: Stats,
|
||||||
|
note: String = ""
|
||||||
|
) {
|
||||||
|
val project = myProject!!
|
||||||
|
val disposable = Disposer.newDisposable("perfKtsFileAnalysis $fileName")
|
||||||
|
|
||||||
|
MadTestingUtil.enableAllInspections(project, disposable)
|
||||||
|
|
||||||
|
replaceWithCustomHighlighter()
|
||||||
|
|
||||||
|
try {
|
||||||
|
highlightFile {
|
||||||
|
val testName = "fileAnalysis ${notePrefix(note)}${simpleFilename(fileName)}"
|
||||||
|
val extraStats = Stats("${stats.name} $testName")
|
||||||
|
val extraTimingsNs = mutableListOf<Long>()
|
||||||
|
|
||||||
|
val warmUpIterations = 3
|
||||||
|
val iterations = 10
|
||||||
|
|
||||||
|
stats.perfTest(
|
||||||
|
warmUpIterations = warmUpIterations,
|
||||||
|
iterations = iterations,
|
||||||
|
testName = testName,
|
||||||
|
setUp = perfKtsFileAnalysisSetUp(project, fileName),
|
||||||
|
test = perfKtsFileAnalysisTest(),
|
||||||
|
tearDown = perfKtsFileAnalysisTearDown(extraTimingsNs, project)
|
||||||
|
)
|
||||||
|
|
||||||
|
extraStats.printWarmUpTimings(
|
||||||
|
"annotator",
|
||||||
|
Array(warmUpIterations, init = { null }),
|
||||||
|
extraTimingsNs.take(warmUpIterations).toLongArray()
|
||||||
|
)
|
||||||
|
|
||||||
|
extraStats.appendTimings(
|
||||||
|
"annotator",
|
||||||
|
Array(iterations, init = { null }),
|
||||||
|
extraTimingsNs.drop(warmUpIterations).toLongArray()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
Disposer.dispose(disposable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun replaceWithCustomHighlighter() {
|
||||||
|
val pointName = ExtensionPointName.create<LanguageExtensionPoint<Annotator>>(LanguageAnnotators.EP_NAME)
|
||||||
|
val extensionPoint = pointName.getPoint(null)
|
||||||
|
|
||||||
|
val point = LanguageExtensionPoint<Annotator>()
|
||||||
|
point.language = "kotlin"
|
||||||
|
point.implementationClass = TestKotlinPsiChecker::class.java.name
|
||||||
|
|
||||||
|
val extensions = extensionPoint.extensions
|
||||||
|
val filteredExtensions =
|
||||||
|
extensions.filter { it.language != "kotlin" || it.implementationClass != KotlinPsiCheckerAndHighlightingUpdater::class.java.name }
|
||||||
|
.toList()
|
||||||
|
// custom highlighter is already registered if filteredExtensions has the same size as extensions
|
||||||
|
if (filteredExtensions.size < extensions.size) {
|
||||||
|
PlatformTestUtil.maskExtensions(pointName, filteredExtensions + listOf(point), testRootDisposable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun perfKtsFileAnalysisSetUp(
|
||||||
|
project: Project,
|
||||||
|
fileName: String
|
||||||
|
): (TestData<FixtureEditorFile, Pair<Long, List<HighlightInfo>>>) -> Unit {
|
||||||
|
return {
|
||||||
|
val fileInEditor = openFileInEditor(project, fileName)
|
||||||
|
|
||||||
|
val file = fileInEditor.psiFile
|
||||||
|
val virtualFile = file.virtualFile
|
||||||
|
val editor = EditorFactory.getInstance().getEditors(fileInEditor.document, project)[0]
|
||||||
|
val fixture = EditorTestFixture(project, editor, virtualFile)
|
||||||
|
|
||||||
|
// Note: Kotlin scripts require dependencies to be loaded
|
||||||
|
if (isAKotlinScriptFile(fileName)) {
|
||||||
|
ScriptDependenciesManager.updateScriptDependenciesSynchronously(fileInEditor.psiFile.virtualFile, project)
|
||||||
|
}
|
||||||
|
|
||||||
|
resetTimestamp()
|
||||||
|
it.setUpValue = FixtureEditorFile(file, editor.document, fixture)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun perfKtsFileAnalysisTest(): (TestData<FixtureEditorFile, Pair<Long, List<HighlightInfo>>>) -> Unit {
|
||||||
|
return {
|
||||||
|
it.value = it.setUpValue?.let { fef ->
|
||||||
|
Pair(System.nanoTime(), fef.fixture.doHighlighting())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun perfKtsFileAnalysisTearDown(
|
||||||
|
extraTimingsNs: MutableList<Long>,
|
||||||
|
project: Project
|
||||||
|
): (TestData<FixtureEditorFile, Pair<Long, List<HighlightInfo>>>) -> Unit {
|
||||||
|
return {
|
||||||
|
it.setUpValue?.let { fef ->
|
||||||
|
it.value?.let { v ->
|
||||||
|
assertTrue(v.second.isNotEmpty())
|
||||||
|
assertNotEquals(0, timer.get())
|
||||||
|
|
||||||
|
extraTimingsNs.add(timer.get() - v.first)
|
||||||
|
|
||||||
|
}
|
||||||
|
cleanupCaches(project, fef.psiFile.virtualFile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class TestKotlinPsiChecker : KotlinPsiChecker() {
|
||||||
|
override fun annotate(
|
||||||
|
element: PsiElement, holder: AnnotationHolder
|
||||||
|
) {
|
||||||
|
super.annotate(element, holder)
|
||||||
|
markTimestamp()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -13,13 +13,14 @@ import kotlin.system.measureNanoTime
|
|||||||
import java.lang.ref.WeakReference
|
import java.lang.ref.WeakReference
|
||||||
import kotlin.math.exp
|
import kotlin.math.exp
|
||||||
import kotlin.math.ln
|
import kotlin.math.ln
|
||||||
|
import kotlin.system.measureTimeMillis
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
class Stats(val name: String = "", val header: Array<String> = arrayOf("Name", "ValueMS", "StdDev")) : Closeable {
|
class Stats(val name: String = "", val header: Array<String> = arrayOf("Name", "ValueMS", "StdDev")) : Closeable {
|
||||||
private val perfTestRawDataMs = mutableListOf<Long>()
|
private val perfTestRawDataMs = mutableListOf<Long>()
|
||||||
|
|
||||||
private val statsFile: File =
|
private val statsFile: File = File("build/stats${statFilePrefix()}.csv").absoluteFile
|
||||||
File("build/stats${if (name.isNotEmpty()) "-${name.toLowerCase().replace(' ', '-')}" else ""}.csv")
|
|
||||||
.absoluteFile
|
|
||||||
private val statsOutput: BufferedWriter
|
private val statsOutput: BufferedWriter
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@@ -28,20 +29,32 @@ class Stats(val name: String = "", val header: Array<String> = arrayOf("Name", "
|
|||||||
statsOutput.appendln(header.joinToString())
|
statsOutput.appendln(header.joinToString())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun statFilePrefix() = if (name.isNotEmpty()) "-${name.toLowerCase().replace(' ', '-').replace('/', '_')}" else ""
|
||||||
|
|
||||||
private fun append(id: String, timingsNs: LongArray) {
|
private fun append(id: String, timingsNs: LongArray) {
|
||||||
val meanNs = timingsNs.average()
|
val meanNs = timingsNs.average()
|
||||||
val meanMs = meanNs.toLong().nsToMs
|
val meanMs = meanNs.toLong().nsToMs
|
||||||
|
|
||||||
val stdDivMs = (sqrt(
|
val stdDevMs = if (timingsNs.size > 1) (sqrt(
|
||||||
timingsNs.fold(0.0,
|
timingsNs.fold(0.0,
|
||||||
{ accumulator, next -> accumulator + (1.0 * (next - meanMs)).pow(2.0) })
|
{ accumulator, next -> accumulator + (1.0 * (next - meanNs)).pow(2) })
|
||||||
) / timingsNs.size).toLong().nsToMs
|
) / (timingsNs.size - 1)).toLong().nsToMs
|
||||||
|
else 0
|
||||||
|
|
||||||
println("##teamcity[buildStatisticValue key='$id' value='$meanMs']")
|
val geomMeanMs = geomMean(timingsNs.toList()).nsToMs
|
||||||
println("##teamcity[buildStatisticValue key='$id stdDev' value='$stdDivMs']")
|
|
||||||
|
for (v in listOf(
|
||||||
|
Triple("mean", "", meanMs),
|
||||||
|
Triple("stdDev", " stdDev", stdDevMs),
|
||||||
|
Triple("geomMean", "geomMean", geomMeanMs)
|
||||||
|
)) {
|
||||||
|
println("##teamcity[testStarted name='$id : ${v.first}' captureStandardOutput='true']")
|
||||||
|
println("##teamcity[buildStatisticValue key='$id${v.second}' value='${v.third}']")
|
||||||
|
println("##teamcity[testFinished name='$id : ${v.first}' duration='${v.third}']")
|
||||||
|
}
|
||||||
|
|
||||||
perfTestRawDataMs.addAll(timingsNs.map { it.nsToMs }.toList())
|
perfTestRawDataMs.addAll(timingsNs.map { it.nsToMs }.toList())
|
||||||
append(arrayOf(id, meanMs, stdDivMs))
|
append(arrayOf(id, meanMs, stdDevMs))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun append(values: Array<Any>) {
|
private fun append(values: Array<Any>) {
|
||||||
@@ -67,7 +80,7 @@ class Stats(val name: String = "", val header: Array<String> = arrayOf("Name", "
|
|||||||
test: (TestData<K, T>) -> Unit,
|
test: (TestData<K, T>) -> Unit,
|
||||||
tearDown: (TestData<K, T>) -> Unit = { null }
|
tearDown: (TestData<K, T>) -> Unit = { null }
|
||||||
) {
|
) {
|
||||||
val namePrefix = "$name: $testName"
|
val namePrefix = "$testName"
|
||||||
val timingsNs = LongArray(iterations)
|
val timingsNs = LongArray(iterations)
|
||||||
val errors = Array<Throwable?>(iterations, init = { null })
|
val errors = Array<Throwable?>(iterations, init = { null })
|
||||||
|
|
||||||
@@ -76,22 +89,46 @@ class Stats(val name: String = "", val header: Array<String> = arrayOf("Name", "
|
|||||||
|
|
||||||
mainPhase(iterations, setUp, test, tearDown, timingsNs, namePrefix, errors)
|
mainPhase(iterations, setUp, test, tearDown, timingsNs, namePrefix, errors)
|
||||||
|
|
||||||
for (attempt in 0 until iterations) {
|
assertEquals(iterations, timingsNs.size)
|
||||||
for (n in listOf("$namePrefix #$attempt", "performance test: $namePrefix #$attempt")) {
|
appendTimings(namePrefix, errors, timingsNs)
|
||||||
println("##teamcity[testStarted name='$n' captureStandardOutput='true']")
|
|
||||||
if (errors[attempt] != null) {
|
|
||||||
tcPrintErrors(n, listOf(errors[attempt]!!))
|
|
||||||
}
|
|
||||||
val spentMs = timingsNs[attempt].nsToMs
|
|
||||||
println("##teamcity[buildStatisticValue key='$n' value='$spentMs']")
|
|
||||||
println("##teamcity[testFinished name='$n' duration='$spentMs']")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
append(namePrefix, timingsNs)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun printWarmUpTimings(
|
||||||
|
prefix: String,
|
||||||
|
errors: Array<Throwable?>,
|
||||||
|
warmUpTimingsNs: LongArray
|
||||||
|
) {
|
||||||
|
assertEquals(warmUpTimingsNs.size, errors.size)
|
||||||
|
for (timing in warmUpTimingsNs.withIndex()) {
|
||||||
|
val attempt = timing.index
|
||||||
|
val n = "$name: $prefix warm-up #$attempt"
|
||||||
|
printTestStarted(n)
|
||||||
|
val t = errors[attempt]
|
||||||
|
if (t != null) printTestFinished(n, t!!) else printTestFinished(n, timing.value.nsToMs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun appendTimings(
|
||||||
|
prefix: String,
|
||||||
|
errors: Array<Throwable?>,
|
||||||
|
timingsNs: LongArray
|
||||||
|
) {
|
||||||
|
assertEquals(timingsNs.size, errors.size)
|
||||||
|
val namePrefix = "$name: $prefix"
|
||||||
|
for (attempt in 0 until timingsNs.size) {
|
||||||
|
val n = "$namePrefix #$attempt"
|
||||||
|
printTestStarted(n)
|
||||||
|
if (errors[attempt] != null) {
|
||||||
|
tcPrintErrors(n, listOf(errors[attempt]!!))
|
||||||
|
}
|
||||||
|
val spentMs = timingsNs[attempt].nsToMs
|
||||||
|
printTestFinished(n, spentMs)
|
||||||
|
}
|
||||||
|
|
||||||
|
append(namePrefix, timingsNs)
|
||||||
|
}
|
||||||
|
|
||||||
private fun <K, T> mainPhase(
|
private fun <K, T> mainPhase(
|
||||||
iterations: Int,
|
iterations: Int,
|
||||||
setUp: (TestData<K, T>) -> Unit,
|
setUp: (TestData<K, T>) -> Unit,
|
||||||
@@ -107,18 +144,24 @@ class Stats(val name: String = "", val header: Array<String> = arrayOf("Name", "
|
|||||||
testData.reset()
|
testData.reset()
|
||||||
triggerGC(attempt)
|
triggerGC(attempt)
|
||||||
|
|
||||||
setUp(testData)
|
val setUpMillis = measureTimeMillis {
|
||||||
|
setUp(testData)
|
||||||
|
}
|
||||||
|
println("-- setup took $setUpMillis ms")
|
||||||
try {
|
try {
|
||||||
val spentNs = measureNanoTime {
|
val spentNs = measureNanoTime {
|
||||||
test(testData)
|
test(testData)
|
||||||
}
|
}
|
||||||
timingsNs[attempt] = spentNs
|
timingsNs[attempt] = spentNs
|
||||||
} catch (t: Throwable) {
|
} catch (t: Throwable) {
|
||||||
println("error at $namePrefix #$attempt:")
|
println("# error at $namePrefix #$attempt:")
|
||||||
t.printStackTrace()
|
t.printStackTrace()
|
||||||
errors[attempt] = t
|
errors[attempt] = t
|
||||||
} finally {
|
} finally {
|
||||||
tearDown(testData)
|
val tearDownMillis = measureTimeMillis {
|
||||||
|
tearDown(testData)
|
||||||
|
}
|
||||||
|
println("-- tearDown took $tearDownMillis ms")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (t: Throwable) {
|
} catch (t: Throwable) {
|
||||||
@@ -135,43 +178,55 @@ class Stats(val name: String = "", val header: Array<String> = arrayOf("Name", "
|
|||||||
tearDown: (TestData<K, T>) -> Unit
|
tearDown: (TestData<K, T>) -> Unit
|
||||||
) {
|
) {
|
||||||
val testData = TestData<K, T>(null, null)
|
val testData = TestData<K, T>(null, null)
|
||||||
|
val warmUpTimingsNs = LongArray(warmUpIterations)
|
||||||
|
val errors: Array<Throwable?> = Array(warmUpIterations, init = { null })
|
||||||
for (attempt in 0 until warmUpIterations) {
|
for (attempt in 0 until warmUpIterations) {
|
||||||
testData.reset()
|
testData.reset()
|
||||||
|
|
||||||
triggerGC(attempt)
|
triggerGC(attempt)
|
||||||
|
|
||||||
val n = "$namePrefix warm-up #$attempt"
|
|
||||||
println("##teamcity[testStarted name='$n' captureStandardOutput='true']")
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setUp(testData)
|
val setUpMillis = measureTimeMillis {
|
||||||
|
setUp(testData)
|
||||||
|
}
|
||||||
|
println("-- setup took $setUpMillis ms")
|
||||||
var spentNs: Long = 0
|
var spentNs: Long = 0
|
||||||
try {
|
try {
|
||||||
spentNs = measureNanoTime {
|
spentNs = measureNanoTime {
|
||||||
test(testData)
|
test(testData)
|
||||||
}
|
}
|
||||||
} catch (t: Throwable) {
|
|
||||||
println("error at $n:\n")
|
|
||||||
|
|
||||||
t.printStackTrace()
|
|
||||||
|
|
||||||
println("\n")
|
|
||||||
|
|
||||||
tcPrintErrors(n, listOf(t))
|
|
||||||
throw t
|
|
||||||
} finally {
|
} finally {
|
||||||
tearDown(testData)
|
val tearDownMillis = measureTimeMillis {
|
||||||
|
tearDown(testData)
|
||||||
|
}
|
||||||
|
println("-- tearDown took $tearDownMillis ms")
|
||||||
}
|
}
|
||||||
val spentMs = spentNs.nsToMs
|
warmUpTimingsNs[attempt] = spentNs
|
||||||
println("##teamcity[buildStatisticValue key='$n' value='$spentMs']")
|
|
||||||
println("##teamcity[testFinished name='$n' duration='$spentMs']")
|
|
||||||
} catch (t: Throwable) {
|
} catch (t: Throwable) {
|
||||||
println("##teamcity[testFinished name='$n']")
|
errors[attempt] = t
|
||||||
println("error at $n:")
|
|
||||||
tcPrintErrors(n, listOf(t))
|
|
||||||
throw t
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
printWarmUpTimings(namePrefix, errors, warmUpTimingsNs)
|
||||||
|
|
||||||
|
errors.find { it != null }?.let { throw it }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun printTestStarted(testName: String) {
|
||||||
|
println("##teamcity[testStarted name='$testName' captureStandardOutput='true']")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun printTestFinished(testName: String, spentMs: Long) {
|
||||||
|
println("##teamcity[buildStatisticValue key='$testName' value='$spentMs']")
|
||||||
|
println("##teamcity[testFinished name='$testName' duration='$spentMs']")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun printTestFinished(testName: String, error: Throwable) {
|
||||||
|
println("error at $testName:")
|
||||||
|
tcPrintErrors(testName, listOf(error))
|
||||||
|
|
||||||
|
println("##teamcity[buildStatisticValue key='$testName' value='-1']")
|
||||||
|
println("##teamcity[testFinished name='$testName']")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun triggerGC(attempt: Int) {
|
private fun triggerGC(attempt: Int) {
|
||||||
@@ -204,6 +259,13 @@ data class TestData<SV, V>(var setUpValue: SV?, var value: V?) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline fun runAndMeasure(note: String, block: () -> Unit) {
|
||||||
|
val openProjectMillis = measureTimeMillis {
|
||||||
|
block()
|
||||||
|
}
|
||||||
|
println("-- $note took $openProjectMillis ms")
|
||||||
|
}
|
||||||
|
|
||||||
inline fun tcSuite(name: String, block: () -> Unit) {
|
inline fun tcSuite(name: String, block: () -> Unit) {
|
||||||
println("##teamcity[testSuiteStarted name='$name']")
|
println("##teamcity[testSuiteStarted name='$name']")
|
||||||
block()
|
block()
|
||||||
|
|||||||
@@ -5,6 +5,12 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.idea.perf
|
package org.jetbrains.kotlin.idea.perf
|
||||||
|
|
||||||
|
import com.intellij.openapi.application.ApplicationManager
|
||||||
|
import com.intellij.openapi.application.runWriteAction
|
||||||
|
import com.intellij.openapi.editor.Document
|
||||||
|
import com.intellij.openapi.fileEditor.FileDocumentManager
|
||||||
|
import com.intellij.openapi.fileEditor.FileEditorManager
|
||||||
|
import com.intellij.openapi.fileEditor.impl.text.TextEditorImpl
|
||||||
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.psi.PsiDocumentManager
|
import com.intellij.psi.PsiDocumentManager
|
||||||
@@ -13,15 +19,37 @@ import com.intellij.testFramework.EdtTestUtil
|
|||||||
import com.intellij.util.ThrowableRunnable
|
import com.intellij.util.ThrowableRunnable
|
||||||
import com.intellij.util.ui.UIUtil
|
import com.intellij.util.ui.UIUtil
|
||||||
import org.jetbrains.kotlin.idea.parameterInfo.HintType
|
import org.jetbrains.kotlin.idea.parameterInfo.HintType
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
import java.util.concurrent.TimeoutException
|
||||||
|
|
||||||
fun commitAllDocuments() {
|
fun commitAllDocuments() {
|
||||||
|
val fileDocumentManager = FileDocumentManager.getInstance()
|
||||||
|
runInEdtAndWait {
|
||||||
|
fileDocumentManager.saveAllDocuments()
|
||||||
|
}
|
||||||
|
|
||||||
ProjectManagerEx.getInstanceEx().openProjects.forEach { project ->
|
ProjectManagerEx.getInstanceEx().openProjects.forEach { project ->
|
||||||
val psiDocumentManagerBase = PsiDocumentManager.getInstance(project) as PsiDocumentManagerBase
|
val psiDocumentManagerBase = PsiDocumentManager.getInstance(project) as PsiDocumentManagerBase
|
||||||
|
|
||||||
EdtTestUtil.runInEdtAndWait(ThrowableRunnable {
|
runInEdtAndWait {
|
||||||
psiDocumentManagerBase.clearUncommittedDocuments()
|
psiDocumentManagerBase.clearUncommittedDocuments()
|
||||||
psiDocumentManagerBase.commitAllDocuments()
|
psiDocumentManagerBase.commitAllDocuments()
|
||||||
})
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun commitDocument(project: Project, document: Document) {
|
||||||
|
val psiDocumentManagerBase = PsiDocumentManager.getInstance(project) as PsiDocumentManagerBase
|
||||||
|
runInEdtAndWait {
|
||||||
|
psiDocumentManagerBase.commitDocument(document)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveDocument(document: Document) {
|
||||||
|
val fileDocumentManager = FileDocumentManager.getInstance()
|
||||||
|
|
||||||
|
runInEdtAndWait {
|
||||||
|
fileDocumentManager.saveDocument(document)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,5 +75,29 @@ fun closeProject(project: Project) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun waitForAllEditorsFinallyLoaded(project: Project) {
|
fun waitForAllEditorsFinallyLoaded(project: Project) {
|
||||||
// nothing
|
waitForAllEditorsFinallyLoaded(project, 5, TimeUnit.MINUTES)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun waitForAllEditorsFinallyLoaded(project: Project, timeout: Long, unit: TimeUnit) {
|
||||||
|
ApplicationManager.getApplication().assertIsDispatchThread()
|
||||||
|
val deadline = unit.toMillis(timeout) + System.currentTimeMillis()
|
||||||
|
while (true) {
|
||||||
|
if (System.currentTimeMillis() > deadline) throw TimeoutException()
|
||||||
|
if (waitABitForEditorLoading(project)) break
|
||||||
|
UIUtil.dispatchAllInvocationEvents()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun waitABitForEditorLoading(project: Project): Boolean {
|
||||||
|
for (editor in FileEditorManager.getInstance(project).allEditors) {
|
||||||
|
if (editor is TextEditorImpl) {
|
||||||
|
try {
|
||||||
|
editor.waitForLoaded(100, TimeUnit.MILLISECONDS)
|
||||||
|
} catch (ignored: TimeoutException) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
@@ -25,6 +25,14 @@ fun commitAllDocuments() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun commitDocument(project: Project, document: Document) {
|
||||||
|
val psiDocumentManagerBase = PsiDocumentManager.getInstance(project) as PsiDocumentManagerBase
|
||||||
|
|
||||||
|
EdtTestUtil.runInEdtAndWait(ThrowableRunnable {
|
||||||
|
psiDocumentManagerBase.commitDocument(document)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fun enableHints(enable: Boolean) =
|
fun enableHints(enable: Boolean) =
|
||||||
HintType.values().forEach { it.option.set(enable) }
|
HintType.values().forEach { it.option.set(enable) }
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user