Use async profiler for perfTests if it presents in env variable; Add kt project copy-n-paste perfTests

This commit is contained in:
Vladimir Dolzhenko
2019-09-27 10:33:49 +02:00
parent de369e6527
commit e2f9eaa483
9 changed files with 258 additions and 649 deletions
+1 -1
View File
@@ -201,7 +201,7 @@ projectTest(taskName = "performanceTest") {
dependsOn(performanceTestRuntime)
testClassesDirs = sourceSets["performanceTest"].output.classesDirs
classpath = performanceTestRuntime
classpath = performanceTestRuntime + files("${System.getenv("ASYNC_PROFILER_HOME")}/build/async-profiler.jar")
workingDir = rootDir
jvmArgs?.removeAll { it.startsWith("-Xmx") }
@@ -13,6 +13,7 @@ import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.ide.highlighter.ModuleFileType
import com.intellij.ide.startup.impl.StartupManagerImpl
import com.intellij.idea.IdeaTestApplication
import com.intellij.openapi.actionSystem.IdeActions
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.runWriteAction
import com.intellij.openapi.editor.EditorFactory
@@ -102,6 +103,7 @@ abstract class AbstractPerformanceProjectsTest : UsefulTestCase() {
assertTrue("kotlin project has been not imported properly", perfHighlightFile.isNotEmpty())
} finally {
closeProject(project)
myApplication.setDataProvider(null)
}
}
@@ -112,9 +114,10 @@ abstract class AbstractPerformanceProjectsTest : UsefulTestCase() {
ThrowableRunnable {
if (myProject != null) {
DaemonCodeAnalyzerSettings.getInstance().isImportHintEnabled = true // return default value to avoid unnecessary save
(StartupManager.getInstance(myProject!!) as StartupManagerImpl).checkCleared()
(DaemonCodeAnalyzer.getInstance(myProject!!) as DaemonCodeAnalyzerImpl).cleanupAfterTest()
closeProject(myProject!!)
(StartupManager.getInstance(project()) as StartupManagerImpl).checkCleared()
(DaemonCodeAnalyzer.getInstance(project()) as DaemonCodeAnalyzerImpl).cleanupAfterTest()
closeProject(project())
myApplication.setDataProvider(null)
myProject = null
}
}).run()
@@ -230,6 +233,7 @@ abstract class AbstractPerformanceProjectsTest : UsefulTestCase() {
// close all project but last - we're going to return and use it further
if (counter < warmUpIterations + iterations - 1) {
closeProject(project)
myApplication.setDataProvider(null)
}
counter++
}
@@ -253,6 +257,8 @@ abstract class AbstractPerformanceProjectsTest : UsefulTestCase() {
dispatchAllInvocationEvents()
Fixture.enableAnnotatorsAndLoadDefinitions(project)
myApplication.setDataProvider(TestDataProvider(project))
}
return lastProject ?: error("unable to open project $name at $path")
@@ -265,7 +271,6 @@ abstract class AbstractPerformanceProjectsTest : UsefulTestCase() {
refreshGradleProject(projectPath, project)
//ExternalProjectsManagerImpl.getInstance(project).setStoreExternally(false)
dispatchAllInvocationEvents()
// WARNING: [VD] DO NOT SAVE PROJECT AS IT COULD PERSIST WRONG MODULES INFO
@@ -286,7 +291,7 @@ abstract class AbstractPerformanceProjectsTest : UsefulTestCase() {
revertChangesAtTheEnd: Boolean = true,
note: String = ""
) = perfTypeAndAutocomplete(
myProject!!, stats, fileName, marker, insertString, surroundItems,
project(), stats, fileName, marker, insertString, surroundItems,
lookupElements = lookupElements, typeAfterMarker = typeAfterMarker,
revertChangesAtTheEnd = revertChangesAtTheEnd, note = note
)
@@ -313,11 +318,7 @@ abstract class AbstractPerformanceProjectsTest : UsefulTestCase() {
val editor = fixture.editor
val initialText = editor.document.text
if (isAKotlinScriptFile(fileName)) {
runAndMeasure("update script dependencies for $fileName") {
ScriptConfigurationManager.updateScriptDependenciesSynchronously(fixture.psiFile, project)
}
}
updateScriptDependenciesIfNeeded(fileName, fixture, project)
val tasksIdx = editor.document.text.indexOf(marker)
assertTrue("marker '$marker' not found in $fileName", tasksIdx > 0)
@@ -360,7 +361,8 @@ abstract class AbstractPerformanceProjectsTest : UsefulTestCase() {
}
commitAllDocuments()
}
}
},
profileEnabled = true
)
}
@@ -374,7 +376,7 @@ abstract class AbstractPerformanceProjectsTest : UsefulTestCase() {
revertChangesAtTheEnd: Boolean = true,
note: String = ""
) = perfTypeAndHighlight(
myProject!!, stats, fileName, marker, insertString, surroundItems,
project(), stats, fileName, marker, insertString, surroundItems,
typeAfterMarker = typeAfterMarker,
revertChangesAtTheEnd = revertChangesAtTheEnd, note = note
)
@@ -399,11 +401,7 @@ abstract class AbstractPerformanceProjectsTest : UsefulTestCase() {
val editor = fixture.editor
val initialText = editor.document.text
if (isAKotlinScriptFile(fileName)) {
runAndMeasure("update script dependencies for $fileName") {
ScriptConfigurationManager.updateScriptDependenciesSynchronously(fixture.psiFile, project)
}
}
updateScriptDependenciesIfNeeded(fileName, fixture, project)
val tasksIdx = editor.document.text.indexOf(marker)
assertTrue("marker '$marker' not found in $fileName", tasksIdx > 0)
@@ -442,10 +440,97 @@ abstract class AbstractPerformanceProjectsTest : UsefulTestCase() {
pair.second.revertChanges(revertChangesAtTheEnd, pair.first)
}
commitAllDocuments()
}
},
profileEnabled = true
)
}
fun perfCopyAndPaste(
stats: Stats,
sourceFileName: String,
sourceInitialMarker: String? = null,
sourceFinalMarker: String? = null,
targetFileName: String,
targetInitialMarker: String? = null,
targetFinalMarker: String? = null,
note: String = ""
) = perfCopyAndPaste(
project(), stats,
sourceFileName, sourceInitialMarker, sourceFinalMarker,
targetFileName, targetInitialMarker, targetFinalMarker,
note
)
fun perfCopyAndPaste(
project: Project,
stats: Stats,
sourceFileName: String,
sourceInitialMarker: String? = null,
sourceFinalMarker: String? = null,
targetFileName: String,
targetInitialMarker: String? = null,
targetFinalMarker: String? = null,
note: String = ""
) {
stats.perfTest<Pair<Array<Fixture>, String>, Boolean>(
warmUpIterations = 8,
iterations = 15,
testName = "${notePrefix(note)}$sourceFileName",
setUp = {
val fixture1 = openFixture(project, sourceFileName)
val fixture2 = openFixture(project, targetFileName)
val initialText2 = fixture2.document.text
updateScriptDependenciesIfNeeded(sourceFileName, fixture1, project)
updateScriptDependenciesIfNeeded(sourceFileName, fixture2, project)
fixture1.selectMarkers(sourceInitialMarker, sourceFinalMarker)
fixture2.selectMarkers(targetInitialMarker, targetFinalMarker)
it.setUpValue = Pair(arrayOf(fixture1, fixture2), initialText2)
},
test = {
it.setUpValue?.let { setUpValue ->
val fixture1 = setUpValue.first[0]
val fixture2 = setUpValue.first[1]
it.value = fixture1.performEditorAction(IdeActions.ACTION_COPY) &&
fixture2.performEditorAction(IdeActions.ACTION_PASTE)
}
},
tearDown = {
try {
commitAllDocuments()
it.value?.let { performed ->
assertTrue("copy-n-paste has not performed well", performed)
// files could be different due to spaces
//assertEquals(it.setUpValue!!.first.document.text, it.setUpValue!!.second.document.text)
}
} finally {
it.setUpValue?.let { setUpValue ->
// pair.second.performEditorAction(IdeActions.ACTION_UNDO)
val fixture2 = setUpValue.first[1]
fixture2.applyText(setUpValue.second)
}
commitAllDocuments()
}
},
profileEnabled = true
)
}
private fun updateScriptDependenciesIfNeeded(
fileName: String,
fixture: Fixture,
project: Project
) {
if (isAKotlinScriptFile(fileName)) {
runAndMeasure("update script dependencies for $fileName") {
ScriptConfigurationManager.updateScriptDependenciesSynchronously(fixture.psiFile, project)
}
}
}
private fun initKotlinProject(
project: Project,
projectPath: String,
@@ -468,7 +553,7 @@ abstract class AbstractPerformanceProjectsTest : UsefulTestCase() {
}
protected fun perfHighlightFile(name: String, stats: Stats): List<HighlightInfo> =
perfHighlightFile(myProject!!, name, stats)
perfHighlightFile(project(), name, stats)
protected fun perfHighlightFile(
project: Project,
@@ -495,7 +580,8 @@ abstract class AbstractPerformanceProjectsTest : UsefulTestCase() {
commitAllDocuments()
FileEditorManager.getInstance(project).closeFile(it.setUpValue!!.psiFile.virtualFile)
PsiManager.getInstance(project).dropPsiCaches()
}
},
profileEnabled = !isWarmUp
)
highlightInfos
}
@@ -517,7 +603,9 @@ abstract class AbstractPerformanceProjectsTest : UsefulTestCase() {
}
protected fun perfScriptDependencies(name: String, stats: Stats, note: String = "") =
perfScriptDependencies(myProject!!, name, stats, note = note)
perfScriptDependencies(project(), name, stats, note = note)
private fun project() = myProject ?: error("project has not been initialized")
private fun perfScriptDependencies(
project: Project,
@@ -536,7 +624,8 @@ abstract class AbstractPerformanceProjectsTest : UsefulTestCase() {
tearDown = {
it.setUpValue?.let { ef -> cleanupCaches(project, ef.psiFile.virtualFile) }
it.value?.let { v -> assertNotNull(v) }
}
},
profileEnabled = true
)
}
@@ -1,551 +0,0 @@
/*
* Copyright 2010-2019 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.codeInsight.daemon.*
import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerImpl
import com.intellij.codeInsight.daemon.impl.HighlightInfo
import com.intellij.codeInsight.daemon.impl.IdentifierHighlighterPassFactory
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.ide.highlighter.ModuleFileType
import com.intellij.ide.startup.impl.StartupManagerImpl
import com.intellij.idea.IdeaTestApplication
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.fileEditor.impl.FileEditorManagerImpl
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.project.impl.ProjectImpl
import com.intellij.openapi.projectRoots.JavaSdk
import com.intellij.openapi.projectRoots.ProjectJdkTable
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.projectRoots.impl.JavaAwareProjectJdkTableImpl
import com.intellij.openapi.roots.ProjectRootManager
import com.intellij.openapi.roots.ex.ProjectRootManagerEx
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.LocalFileSystem
import com.intellij.openapi.vfs.VirtualFileManager
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiManager
import com.intellij.testFramework.*
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 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.ConfigLibraryUtil
import org.jetbrains.kotlin.idea.test.invalidateLibraryCache
import org.jetbrains.kotlin.idea.testFramework.*
import org.jetbrains.kotlin.idea.testFramework.Fixture.Companion.cleanupCaches
import org.jetbrains.kotlin.idea.testFramework.Fixture.Companion.isAKotlinScriptFile
import org.jetbrains.kotlin.idea.testFramework.Fixture.Companion.openFileInEditor
import org.jetbrains.kotlin.idea.testFramework.Fixture.Companion.openFixture
import java.io.File
import java.nio.file.Paths
abstract class AbstractPerformanceProjectsTest : UsefulTestCase() {
// myProject is not required for all potential perf test cases
protected var myProject: Project? = null
private lateinit var jdk18: Sdk
private lateinit var myApplication: IdeaTestApplication
override fun isStressTest(): Boolean = true
override fun isPerformanceTest(): Boolean = false
override fun setUp() {
super.setUp()
myApplication = IdeaTestApplication.getInstance()
runWriteAction {
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()
jdk18 = javaSdk.createJdk("1.8", homePath)
val internal = javaSdk.createJdk("IDEA jdk", homePath)
val jdkTable = ProjectJdkTable.getInstance()
jdkTable.addJdk(jdk18, testRootDisposable)
jdkTable.addJdk(internal, testRootDisposable)
KotlinSdkType.setUpIfNeeded()
}
}
protected fun warmUpProject(stats: Stats) {
val project = perfOpenHelloWorld(stats, WARM_UP)
try {
val perfHighlightFile = perfHighlightFile(project, "src/HelloMain.kt", stats, WARM_UP)
assertTrue("kotlin project has been not imported properly", perfHighlightFile.isNotEmpty())
} finally {
closeProject(project)
}
}
override fun tearDown() {
commitAllDocuments()
RunAll(
ThrowableRunnable { super.tearDown() },
ThrowableRunnable {
if (myProject != null) {
DaemonCodeAnalyzerSettings.getInstance().isImportHintEnabled = true // return default value to avoid unnecessary save
(StartupManager.getInstance(myProject!!) as StartupManagerImpl).checkCleared()
(DaemonCodeAnalyzer.getInstance(myProject!!) as DaemonCodeAnalyzerImpl).cleanupAfterTest()
closeProject(myProject!!)
myProject = null
}
}).run()
}
fun simpleFilename(fileName: String): String {
val lastIndexOf = fileName.lastIndexOf('/')
return if (lastIndexOf >= 0) fileName.substring(lastIndexOf + 1) else fileName
}
protected fun perfOpenKotlinProjectFast(stats: Stats) =
perfOpenKotlinProject(stats, fast = true)
protected fun perfOpenKotlinProject(stats: Stats, fast: Boolean = false) {
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(
name: String,
stats: Stats,
note: String,
path: String,
simpleModule: Boolean = false,
fast: Boolean = false
): Project {
val projectPath = File(path).canonicalPath
assertTrue("path $path does not exist, check README.md", File(projectPath).exists())
val warmUpIterations = if (fast) 0 else 1
val iterations = if (fast) 1 else 3
val projectManagerEx = ProjectManagerEx.getInstanceEx()
var lastProject: Project? = null
var counter = 0
stats.perfTest<Unit, Project>(
warmUpIterations = warmUpIterations,
iterations = iterations,
testName = "open project${if (note.isNotEmpty()) " $note" else ""}",
test = {
val project = if (!simpleModule) {
val project = projectManagerEx.loadProject(Paths.get(path), name)
assertNotNull("project $name at $path is not loaded", project)
val projectRootManager = ProjectRootManager.getInstance(project!!)
runWriteAction {
with(projectRootManager) {
projectSdk = jdk18
}
}
assertTrue("project $name at $path is not opened", projectManagerEx.openProject(project))
project
} else {
val project = projectManagerEx.loadAndOpenProject(projectPath)!!
initKotlinProject(project, projectPath, name)
project
}
(project as ProjectImpl).registerComponentImplementation(
FileEditorManager::class.java,
FileEditorManagerImpl::class.java
)
dispatchAllInvocationEvents()
with(StartupManager.getInstance(project) as StartupManagerImpl) {
ProjectRootManagerEx.getInstanceEx(project).markRootsForRefresh()
VirtualFileManager.getInstance().syncRefresh()
runStartupActivities()
runPostStartupActivities()
}
logMessage { "project $name is ${if (project.isInitialized) "initialized" else "not initialized"}" }
with(ChangeListManager.getInstance(project) as ChangeListManagerImpl) {
waitUntilRefreshed()
}
it.value = project
},
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 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()
}
logMessage { "project '$name' successfully opened" }
// close all project but last - we're going to return and use it further
if (counter < warmUpIterations + iterations - 1) {
closeProject(project)
}
counter++
}
}
)
// indexing
lastProject?.let { project ->
invalidateLibraryCache(project)
CodeInsightTestFixtureImpl.ensureIndexesUpToDate(project)
dispatchAllInvocationEvents()
logMessage { "project $name is ${if (project.isInitialized) "initialized" else "not initialized"}" }
with(DumbService.getInstance(project)) {
queueTask(UnindexedFilesUpdater(project))
completeJustSubmittedTasks()
}
dispatchAllInvocationEvents()
Fixture.enableAnnotatorsAndLoadDefinitions(project)
}
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)
//ExternalProjectsManagerImpl.getInstance(project).setStoreExternally(false)
dispatchAllInvocationEvents()
// WARNING: [VD] DO NOT SAVE PROJECT AS IT COULD PERSIST WRONG MODULES INFO
// runInEdtAndWait {
// PlatformTestUtil.saveProject(project)
// }
}
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, Fixture>, Array<LookupElement>>(
warmUpIterations = 8,
iterations = 15,
testName = "typeAndAutocomplete ${notePrefix(note)}$fileName",
setUp = {
val fixture = openFixture(project, fileName)
val editor = fixture.editor
val initialText = editor.document.text
if (isAKotlinScriptFile(fileName)) {
runAndMeasure("update script dependencies for $fileName") {
ScriptConfigurationManager.updateScriptDependenciesSynchronously(fixture.psiFile, 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)
}
fixture.type(insertString)
it.setUpValue = Pair(initialText, fixture)
},
test = {
val fixture = it.setUpValue!!.second
it.value = fixture.complete()
},
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 ->
pair.second.revertChanges(revertChangesAtTheEnd, pair.first)
}
commitAllDocuments()
}
}
)
}
fun perfTypeAndHighlight(
stats: Stats,
fileName: String,
marker: String,
insertString: String,
surroundItems: String = "\n",
typeAfterMarker: Boolean = true,
revertChangesAtTheEnd: Boolean = true,
note: String = ""
) = perfTypeAndHighlight(
myProject!!, stats, fileName, marker, insertString, surroundItems,
typeAfterMarker = typeAfterMarker,
revertChangesAtTheEnd = revertChangesAtTheEnd, note = note
)
fun perfTypeAndHighlight(
project: Project,
stats: Stats,
fileName: String,
marker: String,
insertString: String,
surroundItems: String = "\n",
typeAfterMarker: Boolean = true,
revertChangesAtTheEnd: Boolean = true,
note: String = ""
) {
stats.perfTest<Pair<String, Fixture>, List<HighlightInfo>>(
warmUpIterations = 8,
iterations = 15,
testName = "typeAndHighlight ${notePrefix(note)}$fileName",
setUp = {
val fixture = openFixture(project, fileName)
val editor = fixture.editor
val initialText = editor.document.text
if (isAKotlinScriptFile(fileName)) {
runAndMeasure("update script dependencies for $fileName") {
ScriptConfigurationManager.updateScriptDependenciesSynchronously(fixture.psiFile, 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)
}
fixture.type(insertString)
it.setUpValue = Pair(initialText, fixture)
},
test = {
val fixture = it.setUpValue!!.second
it.value = fixture.doHighlighting()
},
tearDown = {
it.value?.let { list ->
assertNotEmpty(list)
}
it.setUpValue?.let { pair ->
pair.second.revertChanges(revertChangesAtTheEnd, pair.first)
}
commitAllDocuments()
}
)
}
private fun initKotlinProject(
project: Project,
projectPath: String,
name: String
) {
val modulePath = "$projectPath/$name${ModuleFileType.DOT_DEFAULT_EXTENSION}"
val projectFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(File(projectPath))!!
val srcFile = projectFile.findChild("src")!!
val module = runWriteAction {
val projectRootManager = ProjectRootManager.getInstance(project)
with(projectRootManager) {
projectSdk = jdk18
}
val moduleManager = ModuleManager.getInstance(project)
val module = moduleManager.newModule(modulePath, ModuleTypeId.JAVA_MODULE)
PsiTestUtil.addSourceRoot(module, srcFile)
module
}
ConfigLibraryUtil.configureKotlinRuntimeAndSdk(module, jdk18)
}
protected fun perfHighlightFile(name: String, stats: Stats): List<HighlightInfo> =
perfHighlightFile(myProject!!, name, stats)
protected fun perfHighlightFile(
project: Project,
fileName: String,
stats: Stats,
note: String = ""
): List<HighlightInfo> {
return highlightFile {
val isWarmUp = note == WARM_UP
var highlightInfos: List<HighlightInfo> = emptyList()
stats.perfTest<EditorFile, List<HighlightInfo>>(
warmUpIterations = if (isWarmUp) 1 else 3,
iterations = if (isWarmUp) 2 else 10,
testName = "highlighting ${notePrefix(note)}${simpleFilename(fileName)}",
setUp = {
it.setUpValue = openFileInEditor(project, fileName)
},
test = {
val file = it.setUpValue
it.value = highlightFile(project, file!!.psiFile)
},
tearDown = {
highlightInfos = it.value ?: emptyList()
commitAllDocuments()
FileEditorManager.getInstance(project).closeFile(it.setUpValue!!.psiFile.virtualFile)
PsiManager.getInstance(project).dropPsiCaches()
}
)
highlightInfos
}
}
fun <T> highlightFile(block: () -> T): T {
var value: T? = null
IdentifierHighlighterPassFactory.doWithHighlightingEnabled {
value = block()
}
return value!!
}
private fun highlightFile(project: Project, psiFile: PsiFile): List<HighlightInfo> {
val document = FileDocumentManager.getInstance().getDocument(psiFile.virtualFile)!!
val editor = EditorFactory.getInstance().getEditors(document).first()
PsiDocumentManager.getInstance(project).commitAllDocuments()
return CodeInsightTestFixtureImpl.instantiateAndRun(psiFile, editor, ArrayUtilRt.EMPTY_INT_ARRAY, true)
}
protected fun perfScriptDependencies(name: String, stats: Stats, note: String = "") =
perfScriptDependencies(myProject!!, name, stats, note = note)
private fun perfScriptDependencies(
project: Project,
fileName: String,
stats: Stats,
note: String = ""
) {
if (!isAKotlinScriptFile(fileName)) return
stats.perfTest<EditorFile, EditorFile>(
testName = "updateScriptDependencies ${notePrefix(note)}${simpleFilename(fileName)}",
setUp = { it.setUpValue = openFileInEditor(project, fileName) },
test = {
ScriptConfigurationManager.updateScriptDependenciesSynchronously(it.setUpValue!!.psiFile, project)
it.value = it.setUpValue
},
tearDown = {
it.setUpValue?.let { ef -> cleanupCaches(project, ef.psiFile.virtualFile) }
it.value?.let { v -> assertNotNull(v) }
}
)
}
fun notePrefix(note: String) = if (note.isNotEmpty()) {
if (note.endsWith("/")) note else "$note "
} else ""
}
@@ -102,6 +102,21 @@ class PerformanceProjectsTest : AbstractPerformanceProjectsTest() {
}
}
fun testKotlinProjectCopyAndPaste() {
tcSuite("Kotlin copy-and-paste") {
val stats = Stats("Kotlin copy-and-paste")
stats.use { stat ->
perfOpenKotlinProjectFast(stat)
perfCopyAndPaste(
stat,
sourceFileName = "compiler/psi/src/org/jetbrains/kotlin/psi/KtFile.kt",
targetFileName = "compiler/psi/src/org/jetbrains/kotlin/psi/KtImportInfo.kt"
)
}
}
}
fun testKotlinProjectCompletionKtFile() {
tcSuite("Kotlin completion ktFile") {
val stats = Stats("Kotlin completion ktFile")
@@ -239,7 +254,8 @@ class PerformanceProjectsTest : AbstractPerformanceProjectsTest() {
testName = testName,
setUp = perfKtsFileAnalysisSetUp(project, fileName),
test = perfKtsFileAnalysisTest(),
tearDown = perfKtsFileAnalysisTearDown(extraTimingsNs, project)
tearDown = perfKtsFileAnalysisTearDown(extraTimingsNs, project),
profileEnabled = true
)
extraStats.printWarmUpTimings(
@@ -6,8 +6,7 @@
package org.jetbrains.kotlin.idea.perf
import org.jetbrains.kotlin.idea.perf.WholeProjectPerformanceTest.Companion.nsToMs
import org.jetbrains.kotlin.idea.perf.profilers.async.DummyProfilerHandler
import org.jetbrains.kotlin.idea.perf.profilers.async.ProfilerHandler
import org.jetbrains.kotlin.idea.perf.profilers.*
import org.jetbrains.kotlin.idea.testFramework.logMessage
import org.jetbrains.kotlin.util.PerformanceCounter
import java.io.*
@@ -19,7 +18,11 @@ import kotlin.test.assertEquals
typealias StatInfos = Map<String, Any>?
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"),
val acceptanceStabilityLevel: Int = 25
) : Closeable {
private val perfTestRawDataMs = mutableListOf<Long>()
@@ -49,7 +52,7 @@ class Stats(val name: String = "", val header: Array<String> = arrayOf("Name", "
for (v in listOf(
Triple("mean", "", calcMean.mean.toLong()),
Triple("stdDev", " stdDev", calcMean.stdDev.toLong()),
Triple("geomMean", "geomMean", calcMean.geomMean.toLong())
Triple("geomMean", " geomMean", calcMean.geomMean.toLong())
)) {
val n = "$id : ${v.first}"
@@ -63,7 +66,7 @@ class Stats(val name: String = "", val header: Array<String> = arrayOf("Name", "
.fold(mutableSetOf<String>(), { acc, keys -> acc.addAll(keys); acc })
.filter { it != TEST_KEY && it != ERROR_KEY }
.sorted().forEach { perfCounterName ->
val values = statInfosArray.map { (it?.get(perfCounterName) as Long) }.toLongArray()
val values = statInfosArray.map { (it?.get(perfCounterName) as? Long) ?: 0L }.toLongArray()
val statInfoMean = calcMean(values)
val n = "$id : $perfCounterName"
@@ -147,6 +150,23 @@ class Stats(val name: String = "", val header: Array<String> = arrayOf("Name", "
assertEquals(iterations, statInfoArray.size)
if (testName != WARM_UP) {
appendTimings(testName, statInfoArray)
// do not estimate stability for warm-up
if (!testName.contains(WARM_UP)) {
val calcMean = calcMean(statInfoArray)
val stabilityPercentage = round(calcMean.stdDev * 100.0 / calcMean.mean).toInt()
logMessage { "$testName stability is $stabilityPercentage %" }
val stabilityName = "$name: $testName stability"
val stable = stabilityPercentage <= acceptanceStabilityLevel
printTestStarted(stabilityName)
printStatValue(stabilityName, stabilityPercentage)
if (stable) {
printTestFinished(stabilityName)
} else {
printTestFailed(stabilityName, "stability above $stabilityPercentage %")
}
}
}
}
@@ -202,19 +222,8 @@ class Stats(val name: String = "", val header: Array<String> = arrayOf("Name", "
warmUpStatInfosArray.filterNotNull().map { it[ERROR_KEY] as? Exception }.firstOrNull()?.let { throw it }
}
private fun <SV, TV> mainPhase(phaseData: PhaseData<SV, TV>): Array<StatInfos> {
val statInfosArray = phase(phaseData, "")
// do not estimate stability for warm-up
if (!phaseData.testName.contains(WARM_UP)) {
val calcMean = calcMean(statInfosArray)
val stabilityPercentage = round(calcMean.stdDev * 100.0 / calcMean.mean).toInt()
logMessage { "${phaseData.testName} stability is $stabilityPercentage %" }
check(stabilityPercentage <= 10) { "${phaseData.testName} is not stable: stability above $stabilityPercentage %" }
}
return statInfosArray
}
private fun <SV, TV> mainPhase(phaseData: PhaseData<SV, TV>): Array<StatInfos> =
phase(phaseData, "")
private fun <SV, TV> phase(phaseData: PhaseData<SV, TV>, phaseName: String): Array<StatInfos> {
val statInfosArray = Array<StatInfos>(phaseData.iterations) { null }
@@ -275,13 +284,13 @@ class Stats(val name: String = "", val header: Array<String> = arrayOf("Name", "
val profilerHandler = if (phaseData.profileEnabled) ProfilerHandler.getInstance() else DummyProfilerHandler
return if (profilerHandler != DummyProfilerHandler) {
val profilerPath = pathToResource("profile/${plainname()}/")
val profilerPath = pathToResource("profile/${plainname()}")
check(with(File(profilerPath)) { exists() || mkdirs() }) { "unable to mkdirs $profilerPath for ${phaseData.testName}" }
val activityName = "${phaseData.testName}-${if (phaseName.isEmpty()) "" else "$phaseName-"}$attempt"
ActualPhaseProfiler(activityName, profilerPath, profilerHandler)
} else {
dummyPhaseProfiler
DummyPhaseProfiler
}
}
@@ -313,11 +322,6 @@ class Stats(val name: String = "", val header: Array<String> = arrayOf("Name", "
const val WARM_UP = "warm-up"
private val dummyPhaseProfiler = object : PhaseProfiler {
override fun start() {}
override fun stop() {}
}
inline fun runAndMeasure(note: String, block: () -> Unit) {
val openProjectMillis = measureTimeMillis {
block()
@@ -329,6 +333,14 @@ class Stats(val name: String = "", val header: Array<String> = arrayOf("Name", "
println("##teamcity[testStarted name='$testName' captureStandardOutput='true']")
}
fun printStatValue(name: String, value: Any) {
println("##teamcity[buildStatisticValue key='$name' value='$value']")
}
private fun printTestFinished(testName: String) {
println("##teamcity[testFinished name='$testName']")
}
fun printTestFinished(testName: String, spentMs: Long, includeStatValue: Boolean = true) {
if (includeStatValue) {
printStatValue(testName, spentMs)
@@ -336,16 +348,15 @@ class Stats(val name: String = "", val header: Array<String> = arrayOf("Name", "
println("##teamcity[testFinished name='$testName' duration='$spentMs']")
}
fun printStatValue(name: String, value: Any) {
println("##teamcity[buildStatisticValue key='$name' value='$value']")
}
fun printTestFinished(testName: String, error: Throwable) {
println("error at $testName:")
tcPrintErrors(testName, listOf(error))
printStatValue(testName, -1)
println("##teamcity[testFinished name='$testName']")
tcPrintErrors(testName, listOf(error))
//printTestFinished(testName)
}
private fun printTestFailed(testName: String, details: String) {
println("##teamcity[testFailed name='$testName' message='Exceptions reported' details='${tcEscape(details)}']")
}
inline fun tcSuite(name: String, block: () -> Unit) {
@@ -361,7 +372,7 @@ class Stats(val name: String = "", val header: Array<String> = arrayOf("Name", "
printTestStarted(name)
val (time, errors) = block()
tcPrintErrors(name, errors)
printTestFinished(name, time, includeStatValue = false)
//printTestFinished(name, time, includeStatValue = false)
}
private fun tcEscape(s: String) = s.replace("|", "||")
@@ -381,27 +392,11 @@ class Stats(val name: String = "", val header: Array<String> = arrayOf("Name", "
}
errorDetailsPrintWriter.close()
val details = detailsWriter.toString()
println("##teamcity[testFailed name='$name' message='Exceptions reported' details='${tcEscape(details)}']")
printTestFailed(name, details)
}
}
}
private interface PhaseProfiler {
fun start()
fun stop()
}
private class ActualPhaseProfiler(val activityName: String, val profilerPath: String, val profilerHandler: ProfilerHandler) :
PhaseProfiler {
override fun start() {
profilerHandler.startProfiling(activityName)
}
override fun stop() {
profilerHandler.stopProfiling(profilerPath, activityName)
}
}
}
data class PhaseData<SV, TV>(
@@ -0,0 +1,32 @@
/*
* Copyright 2010-2019 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.profilers
interface PhaseProfiler {
fun start()
fun stop()
}
object DummyPhaseProfiler : PhaseProfiler {
override fun start() {}
override fun stop() {}
}
class ActualPhaseProfiler(
private val activityName: String,
private val profilerPath: String,
private val profilerHandler: ProfilerHandler
) :
PhaseProfiler {
override fun start() {
profilerHandler.startProfiling(activityName)
}
override fun stop() {
profilerHandler.stopProfiling(profilerPath, activityName)
}
}
@@ -3,9 +3,10 @@
* 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.profilers.async
package org.jetbrains.kotlin.idea.perf.profilers
import com.intellij.openapi.util.SystemInfo
import org.jetbrains.kotlin.idea.perf.profilers.async.AsyncProfilerHandler
import org.jetbrains.kotlin.idea.testFramework.logMessage
interface ProfilerHandler {
@@ -8,8 +8,8 @@ package org.jetbrains.kotlin.idea.perf.profilers.async
import com.intellij.openapi.util.SystemInfo
import com.intellij.openapi.util.io.FileUtil
import com.intellij.util.containers.ContainerUtil
import org.jetbrains.kotlin.idea.perf.profilers.ProfilerHandler
import org.jetbrains.kotlin.idea.testFramework.logMessage
//import one.profiler.AsyncProfiler
import java.io.File
import java.nio.file.Files
import java.nio.file.StandardCopyOption
@@ -33,6 +33,7 @@ internal class AsyncProfilerHandler : ProfilerHandler {
}
private fun execute(command: String) {
logMessage { "asyncProfiler command: '$command'" }
executeMethod.invoke(asyncProfiler, command)
}
@@ -49,7 +50,8 @@ internal class AsyncProfilerHandler : ProfilerHandler {
override fun stopProfiling(snapshotsPath: String, activityName: String, options: List<String>) {
val combinedOptions = ArrayList(options)
val commandBuilder = AsyncProfilerCommandBuilder(snapshotsPath)
val stopAndDumpCommands = commandBuilder.buildStopAndDumpCommands(activityName, combinedOptions)
val name = activityName.replace(' ', '_').replace('/', '_')
val stopAndDumpCommands = commandBuilder.buildStopAndDumpCommands(name, combinedOptions)
for (stopCommand in stopAndDumpCommands) {
execute(stopCommand)
}
@@ -22,6 +22,7 @@ import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.EditorFactory
import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.guessProjectDir
import com.intellij.openapi.roots.FileIndexFacade
@@ -35,10 +36,10 @@ import com.intellij.psi.search.FilenameIndex
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.xml.XmlFileNSInfoProvider
import com.intellij.testFramework.EditorTestUtil
import com.intellij.testFramework.UsefulTestCase
import com.intellij.testFramework.fixtures.EditorTestFixture
import com.intellij.testFramework.runInEdtAndWait
import com.intellij.xml.XmlSchemaProvider
import junit.framework.TestCase.*
import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager
import org.jetbrains.kotlin.idea.core.script.ScriptDefinitionsManager
@@ -48,7 +49,7 @@ import org.jetbrains.kotlin.parsing.KotlinParserDefinition
class Fixture(val project: Project, val editor: Editor, val psiFile: PsiFile, val vFile: VirtualFile = psiFile.virtualFile) {
private val delegate = EditorTestFixture(project, editor, vFile)
private var delegate = EditorTestFixture(project, editor, vFile)
val document: Document
get() = editor.document
@@ -59,26 +60,49 @@ class Fixture(val project: Project, val editor: Editor, val psiFile: PsiFile, va
delegate.type(s)
}
fun performEditorAction(actionId: String): Boolean {
selectEditor()
return delegate.performEditorAction(actionId)
}
fun complete(type: CompletionType = CompletionType.BASIC, invocationCount: Int = 1): Array<LookupElement> =
delegate.complete(type, invocationCount) ?: emptyArray()
fun revertChanges(revertChangesAtTheEnd: Boolean, text: String) {
try {
if (revertChangesAtTheEnd) {
runWriteAction {
// TODO: [VD] revert ?
//editorFixture.performEditorAction(IdeActions.SELECTED_CHANGES_ROLLBACK)
document.setText(text)
saveDocument(document)
commitDocument(project, document)
}
dispatchAllInvocationEvents()
// TODO: [VD] revert ?
//editorFixture.performEditorAction(IdeActions.SELECTED_CHANGES_ROLLBACK)
applyText(text)
}
} finally {
cleanupCaches(project, vFile)
}
}
fun selectMarkers(initialMarker: String?, finalMarker: String?) {
selectEditor()
val text = editor.document.text
editor.selectionModel.setSelection(
initialMarker?.let { marker -> text.indexOf(marker) } ?: 0,
finalMarker?.let { marker -> text.indexOf(marker) } ?: text.length)
}
private fun selectEditor() {
val fileEditorManagerEx = FileEditorManagerEx.getInstanceEx(project)
fileEditorManagerEx.openFile(vFile, true)
check(fileEditorManagerEx.selectedEditor?.file == vFile) { "unable to open $vFile" }
}
fun applyText(text: String) {
runWriteAction {
document.setText(text)
saveDocument(document)
commitDocument(project, document)
}
dispatchAllInvocationEvents()
}
companion object {
// quite simple impl - good so far
fun isAKotlinScriptFile(fileName: String) = fileName.endsWith(KotlinParserDefinition.STD_SCRIPT_EXT)
@@ -112,15 +136,16 @@ class Fixture(val project: Project, val editor: Editor, val psiFile: PsiFile, va
scriptDefinitionsManager.getAllDefinitions()
dispatchAllInvocationEvents()
UsefulTestCase.assertTrue(scriptDefinitionsManager.isReady())
UsefulTestCase.assertFalse(KotlinScriptingSettings.getInstance(project).isAutoReloadEnabled)
assertTrue(scriptDefinitionsManager.isReady())
assertFalse(KotlinScriptingSettings.getInstance(project).isAutoReloadEnabled)
}
fun openFixture(project: Project, fileName: String): Fixture {
val fileInEditor = openFileInEditor(project, fileName)
val file = fileInEditor.psiFile
val editor = EditorFactory.getInstance().getEditors(fileInEditor.document, project)[0]
val editorFactory = EditorFactory.getInstance()
val editor = editorFactory.getEditors(fileInEditor.document, project)[0]
return Fixture(project, editor, file)
}
@@ -148,7 +173,7 @@ class Fixture(val project: Project, val editor: Editor, val psiFile: PsiFile, va
)
.filter { it.canonicalPath?.contains("/$projectBaseName/$name") ?: false }.toList()
UsefulTestCase.assertEquals(
assertEquals(
"expected the only file with name '$name'\n, it were: [${virtualFiles.map { it.canonicalPath }.joinToString("\n")}]",
1,
virtualFiles.size
@@ -163,18 +188,18 @@ class Fixture(val project: Project, val editor: Editor, val psiFile: PsiFile, va
val psiFile = projectFileByName(project, name)
val vFile = psiFile.virtualFile
UsefulTestCase.assertTrue("file $vFile is not indexed yet", FileIndexFacade.getInstance(project).isInContent(vFile))
assertTrue("file $vFile is not indexed yet", FileIndexFacade.getInstance(project).isInContent(vFile))
runInEdtAndWait {
fileEditorManager.openFile(vFile, true)
}
val document = fileDocumentManager.getDocument(vFile)!!
val document = fileDocumentManager.getDocument(vFile) ?: error("no document for $vFile found")
UsefulTestCase.assertNotNull("doc not found for $vFile", EditorFactory.getInstance().getEditors(document))
UsefulTestCase.assertTrue("expected non empty doc", document.text.isNotEmpty())
assertNotNull("doc not found for $vFile", EditorFactory.getInstance().getEditors(document))
assertTrue("expected non empty doc", document.text.isNotEmpty())
val offset = psiFile.textOffset
UsefulTestCase.assertTrue("side effect: to load the text", offset >= 0)
assertTrue("side effect: to load the text", offset >= 0)
waitForAllEditorsFinallyLoaded(project)
@@ -204,7 +229,7 @@ class Fixture(val project: Project, val editor: Editor, val psiFile: PsiFile, va
}
val tasksIdx = fileInEditor.document.text.indexOf(marker)
UsefulTestCase.assertTrue(tasksIdx > 0)
assertTrue(tasksIdx > 0)
editor.caretModel.moveToOffset(tasksIdx + marker.length + 1)
for (surroundItem in surroundItems) {
@@ -218,7 +243,7 @@ class Fixture(val project: Project, val editor: Editor, val psiFile: PsiFile, va
val elements = fixture.complete()
val items = elements.map { it.lookupString }.toList()
for (lookupElement in lookupElements) {
UsefulTestCase.assertTrue("'$lookupElement' has to be present in items", items.contains(lookupElement))
assertTrue("'$lookupElement' has to be present in items", items.contains(lookupElement))
}
}
} finally {