Added copy-paste performance tests

This commit is contained in:
Vladimir Dolzhenko
2019-09-23 04:26:12 +02:00
parent 63e5d4fe95
commit 17e2359a41
8 changed files with 346 additions and 59 deletions
@@ -1281,6 +1281,10 @@ fun main(args: Array<String>) {
model("copyPaste/conversion", testMethod = "doPerfTest", pattern = """^([^\.]+)\.java$""")
}
testClass<AbstractPerformanceLiteralKotlinToKotlinCopyPasteTest> {
model("copyPaste/literal", testMethod = "doPerfTest", pattern = """^([^\.]+)\.kt$""")
}
testClass<AbstractPerformanceHighlightingTest> {
model("highlighter", testMethod = "doPerfTest")
}
@@ -111,6 +111,7 @@ import org.jetbrains.kotlin.idea.maven.AbstractKotlinMavenInspectionTest
import org.jetbrains.kotlin.idea.maven.configuration.AbstractMavenConfigureProjectByChangingFileTest
import org.jetbrains.kotlin.idea.navigation.*
import org.jetbrains.kotlin.idea.parameterInfo.AbstractParameterInfoTest
import org.jetbrains.kotlin.idea.perf.*
import org.jetbrains.kotlin.idea.quickfix.AbstractQuickFixMultiFileTest
import org.jetbrains.kotlin.idea.quickfix.AbstractQuickFixMultiModuleTest
import org.jetbrains.kotlin.idea.quickfix.AbstractQuickFixTest
@@ -1210,6 +1211,52 @@ fun main(args: Array<String>) {
}
}
testGroup("idea/performanceTests", "idea/testData") {
testClass<AbstractPerformanceJavaToKotlinCopyPasteConversionTest> {
model("copyPaste/conversion", testMethod = "doPerfTest", pattern = """^([^\.]+)\.java$""")
}
testClass<AbstractPerformanceNewJavaToKotlinCopyPasteConversionTest> {
model("copyPaste/conversion", testMethod = "doPerfTest", pattern = """^([^\.]+)\.java$""")
}
testClass<AbstractPerformanceLiteralKotlinToKotlinCopyPasteTest> {
model("copyPaste/literal", testMethod = "doPerfTest", pattern = """^([^\.]+)\.kt$""")
}
testClass<AbstractPerformanceHighlightingTest> {
model("highlighter", testMethod = "doPerfTest")
}
testClass<AbstractPerformanceAddImportTest> {
model("addImport", testMethod = "doPerfTest", pattern = KT_WITHOUT_DOTS_IN_NAME)
}
}
testGroup("idea/performanceTests", "idea/idea-completion/testData") {
testClass<AbstractPerformanceCompletionIncrementalResolveTest> {
model("incrementalResolve", testMethod = "doPerfTest")
}
testClass<AbstractPerformanceBasicCompletionHandlerTest> {
model("handlers/basic", testMethod = "doPerfTest", pattern = KT_WITHOUT_DOTS_IN_NAME)
}
testClass<AbstractPerformanceSmartCompletionHandlerTest> {
model("handlers/smart", testMethod = "doPerfTest")
}
testClass<AbstractPerformanceKeywordCompletionHandlerTest> {
model("handlers/keywords", testMethod = "doPerfTest")
}
testClass<AbstractPerformanceCompletionCharFilterTest> {
model("handlers/charFilter", testMethod = "doPerfTest", pattern = KT_WITHOUT_DOTS_IN_NAME)
}
}
/*
testGroup("plugins/android-extensions/android-extensions-idea/tests", "plugins/android-extensions/android-extensions-idea/testData") {
testClass<AbstractAndroidCompletionTest> {
model("android/completion", recursive = false, extension = null)
@@ -11,6 +11,7 @@ import com.intellij.openapi.application.runWriteAction
import com.intellij.openapi.util.text.StringUtil
import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.idea.completion.CompletionBindingContextProvider
import org.jetbrains.kotlin.idea.perf.Stats.Companion.WARM_UP
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor
import org.jetbrains.kotlin.idea.testFramework.commitAllDocuments
@@ -56,7 +57,7 @@ abstract class AbstractPerformanceCompletionIncrementalResolveTest : KotlinLight
}
private fun doWarmUpPerfTest() {
innerPerfTest("warm-up") {
innerPerfTest(WARM_UP) {
myFixture.configureByText(
KotlinFileType.INSTANCE,
"class Foo {\n private val value: String? = n<caret>\n}"
@@ -9,6 +9,7 @@ import com.intellij.codeInsight.daemon.impl.HighlightInfo
import com.intellij.openapi.application.runWriteAction
import com.intellij.testFramework.fixtures.impl.CodeInsightTestFixtureImpl.ensureIndexesUpToDate
import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.idea.perf.Stats.Companion.WARM_UP
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
import org.jetbrains.kotlin.idea.testFramework.commitAllDocuments
@@ -45,7 +46,7 @@ abstract class AbstractPerformanceHighlightingTest : KotlinLightCodeInsightFixtu
}
private fun doWarmUpPerfTest() {
innerPerfTest("warm-up") {
innerPerfTest(WARM_UP) {
myFixture.configureByText(
KotlinFileType.INSTANCE,
"class Foo {\n private val value: String? = null\n}"
@@ -12,6 +12,7 @@ import com.intellij.psi.PsiDocumentManager
import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.idea.conversion.copy.AbstractJavaToKotlinCopyPasteConversionTest
import org.jetbrains.kotlin.idea.conversion.copy.ConvertJavaCopyPasteProcessor
import org.jetbrains.kotlin.idea.perf.Stats.Companion.WARM_UP
import org.jetbrains.kotlin.idea.testFramework.commitAllDocuments
import org.jetbrains.kotlin.j2k.J2kConverterExtension
import org.jetbrains.kotlin.test.InTextDirectivesUtils
@@ -47,7 +48,7 @@ abstract class AbstractPerformanceJavaToKotlinCopyPasteConversionTest(private va
private fun doWarmUpPerfTest() {
stats().perfTest<Unit, Unit>(
testName = "warm-up",
testName = WARM_UP,
setUp = {
with(myFixture) {
configureByText(JavaFileType.INSTANCE, "<selection>public class Foo {\nprivate String value;\n}</selection>")
@@ -105,6 +106,7 @@ abstract class AbstractPerformanceJavaToKotlinCopyPasteConversionTest(private va
commitAllDocuments()
validate(path, noConversionExpected)
// to avoid VFS refresh
myFixture.performEditorAction(IdeActions.ACTION_UNDO)
}
)
@@ -0,0 +1,114 @@
/*
* 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.openapi.actionSystem.IdeActions
import com.intellij.openapi.application.runWriteAction
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
import com.intellij.psi.PsiFile
import org.jetbrains.kotlin.idea.AbstractCopyPasteTest
import org.jetbrains.kotlin.idea.perf.Stats.Companion.WARM_UP
import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase
import org.jetbrains.kotlin.test.KotlinTestUtils
import java.io.File
/**
* Inspired by AbstractLiteralKotlinToKotlinCopyPasteTest
*/
abstract class AbstractPerformanceLiteralKotlinToKotlinCopyPasteTest : AbstractCopyPasteTest() {
companion object {
val BASE_PATH = PluginTestCaseBase.getTestDataPathBase() + "/copyPaste/literal"
@JvmStatic
var warmedUp: Boolean = false
@JvmStatic
val stats: Stats = Stats("Literal-k2k-CopyPaste")
init {
// there is no @AfterClass for junit3.8
Runtime.getRuntime().addShutdownHook(Thread(Runnable { stats.close() }))
}
}
override fun getTestDataPath() = BASE_PATH
override fun getProjectDescriptor() = KotlinWithJdkAndRuntimeLightProjectDescriptor.INSTANCE
override fun setUp() {
super.setUp()
if (!warmedUp) {
doWarmUpPerfTest()
warmedUp = true
}
}
private fun doWarmUpPerfTest() {
val fileEditorManager = FileEditorManagerEx.getInstance(project)
stats.perfTest<Pair<PsiFile, PsiFile>, Unit>(
testName = WARM_UP,
iterations = 1,
setUp = {
val file1 = myFixture.configureByText(
"src.kt",
"class Foo {\n <selection>private val value: String? = n<caret></selection>\n}"
)
val file2 = myFixture.configureByText("target.kt", "<caret>")
it.setUpValue = Pair(file1, file2)
},
test = {
fileEditorManager.setSelectedEditor(it.setUpValue!!.first.virtualFile, "")
myFixture.performEditorAction(IdeActions.ACTION_COPY)
fileEditorManager.setSelectedEditor(it.setUpValue!!.second.virtualFile, "")
myFixture.performEditorAction(IdeActions.ACTION_PASTE)
},
tearDown = {
assertEquals("private val value: String? = n", it.setUpValue!!.second.text)
// to avoid VFS refresh
myFixture.performEditorAction(IdeActions.ACTION_UNDO)
runWriteAction {
it.setUpValue!!.first.delete()
it.setUpValue!!.second.delete()
}
}
)
}
fun doPerfTest(path: String) {
val testName = getTestName(false)
val fileEditorManager = FileEditorManagerEx.getInstance(project)
stats.perfTest<Array<PsiFile>, Unit>(
testName = testName,
setUp = {
it.setUpValue = myFixture.configureByFiles("$testName.kt", "$testName.to.kt")
},
test = {
fileEditorManager.setSelectedEditor(it.setUpValue!![0].virtualFile, "")
myFixture.performEditorAction(IdeActions.ACTION_COPY)
fileEditorManager.setSelectedEditor(it.setUpValue!![1].virtualFile, "")
myFixture.performEditorAction(IdeActions.ACTION_PASTE)
},
tearDown = {
KotlinTestUtils.assertEqualsToFile(File(path.replace(".kt", ".expected.kt")), it.setUpValue!![1].text)
// to avoid VFS refresh
myFixture.performEditorAction(IdeActions.ACTION_UNDO)
runWriteAction {
it.setUpValue!!.forEach { f -> f.delete() }
}
}
)
}
}
@@ -0,0 +1,61 @@
/*
* 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.testFramework.TestDataPath;
import org.jetbrains.kotlin.test.JUnit3RunnerWithInners;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import org.jetbrains.kotlin.test.TargetBackend;
import org.jetbrains.kotlin.test.TestMetadata;
import org.junit.runner.RunWith;
import java.io.File;
import java.util.regex.Pattern;
/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */
@SuppressWarnings("all")
@TestMetadata("idea/testData/copyPaste/literal")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public class PerformanceLiteralKotlinToKotlinCopyPasteTestGenerated extends AbstractPerformanceLiteralKotlinToKotlinCopyPasteTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doPerfTest, TargetBackend.ANY, testDataFilePath);
}
public void testAllFilesPresentInLiteral() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/copyPaste/literal"), Pattern.compile("^([^\\.]+)\\.kt$"), TargetBackend.ANY, true);
}
@TestMetadata("CollectionLiteralReference.kt")
public void testCollectionLiteralReference() throws Exception {
runTest("idea/testData/copyPaste/literal/CollectionLiteralReference.kt");
}
@TestMetadata("DontEscapeEntries.kt")
public void testDontEscapeEntries() throws Exception {
runTest("idea/testData/copyPaste/literal/DontEscapeEntries.kt");
}
@TestMetadata("DontEscapeEntries2.kt")
public void testDontEscapeEntries2() throws Exception {
runTest("idea/testData/copyPaste/literal/DontEscapeEntries2.kt");
}
@TestMetadata("DontEscapeThis.kt")
public void testDontEscapeThis() throws Exception {
runTest("idea/testData/copyPaste/literal/DontEscapeThis.kt");
}
@TestMetadata("DontUnescapeLiteralWIthCode.kt")
public void testDontUnescapeLiteralWIthCode() throws Exception {
runTest("idea/testData/copyPaste/literal/DontUnescapeLiteralWIthCode.kt");
}
@TestMetadata("UnescapeFullSelection.kt")
public void testUnescapeFullSelection() throws Exception {
runTest("idea/testData/copyPaste/literal/UnescapeFullSelection.kt");
}
}
@@ -6,6 +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.testFramework.logMessage
import org.jetbrains.kotlin.util.PerformanceCounter
@@ -26,8 +27,6 @@ class Stats(val name: String = "", val header: Array<String> = arrayOf("Name", "
private val statsOutput: BufferedWriter
private val profilerHandler = ProfilerHandler.getInstance()
init {
statsOutput = statsFile.bufferedWriter()
@@ -121,15 +120,40 @@ class Stats(val name: String = "", val header: Array<String> = arrayOf("Name", "
iterations: Int = 20,
setUp: (TestData<SV, TV>) -> Unit = { },
test: (TestData<SV, TV>) -> Unit,
tearDown: (TestData<SV, TV>) -> Unit = { }
tearDown: (TestData<SV, TV>) -> Unit = { },
profileEnabled: Boolean = false
) {
tcSuite(testName) {
warmUpPhase(warmUpIterations, testName, setUp, test, tearDown)
val statInfoArray = mainPhase(iterations, testName, setUp, test, tearDown)
val warmPhaseData = PhaseData(
iterations = warmUpIterations,
testName = testName,
setUp = setUp,
test = test,
tearDown = tearDown,
profileEnabled = profileEnabled
)
val mainPhaseData = PhaseData(
iterations = iterations,
testName = testName,
setUp = setUp,
test = test,
tearDown = tearDown,
profileEnabled = profileEnabled
)
val block = {
warmUpPhase(warmPhaseData)
val statInfoArray = mainPhase(mainPhaseData)
assertEquals(iterations, statInfoArray.size)
appendTimings(testName, statInfoArray)
if (testName != WARM_UP) {
appendTimings(testName, statInfoArray)
}
}
if (testName != WARM_UP) {
tcSuite(testName, block)
} else {
block()
}
}
@@ -140,7 +164,7 @@ class Stats(val name: String = "", val header: Array<String> = arrayOf("Name", "
) {
for (statInfoIndex in statInfoArray.withIndex()) {
val attempt = statInfoIndex.index
val statInfo = statInfoIndex.value!!
val statInfo = statInfoIndex.value ?: continue
val n = "$name: $prefix ${attemptFn(attempt)}"
printTestStarted(n)
val t = statInfo[ERROR_KEY] as? Throwable
@@ -168,83 +192,67 @@ class Stats(val name: String = "", val header: Array<String> = arrayOf("Name", "
append("$name: $prefix", statInfosArray)
}
private fun <K, T> mainPhase(
iterations: Int,
testName: String,
setUp: (TestData<K, T>) -> Unit,
test: (TestData<K, T>) -> Unit,
tearDown: (TestData<K, T>) -> Unit
): Array<StatInfos> {
val statInfosArray = phase(testName, "", iterations, setUp, test, tearDown)
private fun <SV, TV> warmUpPhase(phaseData: PhaseData<SV, TV>) {
val warmUpStatInfosArray = phase(phaseData, WARM_UP)
if (phaseData.testName != WARM_UP) {
printWarmUpTimings(phaseData.testName, warmUpStatInfosArray)
}
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 (!testName.contains(WARM_UP)) {
if (!phaseData.testName.contains(WARM_UP)) {
val calcMean = calcMean(statInfosArray)
val stabilityPercentage = round(calcMean.stdDev * 100.0 / calcMean.mean).toInt()
logMessage { "$testName stability is $stabilityPercentage %" }
check(stabilityPercentage <= 10) { "$testName is not stable: stability above $stabilityPercentage %" }
logMessage { "${phaseData.testName} stability is $stabilityPercentage %" }
check(stabilityPercentage <= 10) { "${phaseData.testName} is not stable: stability above $stabilityPercentage %" }
}
return statInfosArray
}
private fun <K, T> warmUpPhase(
warmUpIterations: Int,
testName: String,
setUp: (TestData<K, T>) -> Unit,
test: (TestData<K, T>) -> Unit,
tearDown: (TestData<K, T>) -> Unit
) {
val warmUpStatInfosArray = phase(testName, "warm-up", warmUpIterations, setUp, test, tearDown)
private fun <SV, TV> phase(phaseData: PhaseData<SV, TV>, phaseName: String): Array<StatInfos> {
val statInfosArray = Array<StatInfos>(phaseData.iterations) { null }
val testData = TestData<SV, TV>(null, null)
printWarmUpTimings(testName, warmUpStatInfosArray)
warmUpStatInfosArray.filterNotNull().map { it[ERROR_KEY] as? Exception }.firstOrNull()?.let { throw it }
}
private fun <K, T> phase(
namePrefix: String,
phaseName: String,
iterations: Int,
setUp: (TestData<K, T>) -> Unit,
test: (TestData<K, T>) -> Unit,
tearDown: (TestData<K, T>) -> Unit
): Array<StatInfos> {
val statInfosArray = Array<StatInfos>(iterations) { null }
val testData = TestData<K, T>(null, null)
val profilerPath = pathToResource("profile/${plainname()}/")
check(with(File(profilerPath)) { exists() || mkdirs() }) { "unable to mkdirs $profilerPath for $namePrefix" }
try {
for (attempt in 0 until iterations) {
for (attempt in 0 until phaseData.iterations) {
testData.reset()
triggerGC(attempt)
val setUpMillis = measureTimeMillis {
setUp(testData)
}
val phaseProfiler = createPhaseProfiler(phaseData, phaseName, attempt)
val setUpMillis = measureTimeMillis { phaseData.setUp(testData) }
logMessage { "setup took $setUpMillis ms" }
val valueMap = HashMap<String, Any>(2 * PerformanceCounter.numberOfCounters + 1)
statInfosArray[attempt] = valueMap
try {
val activityName = "$namePrefix-${if (phaseName.isEmpty()) "" else "$phaseName-"}$attempt"
profilerHandler.startProfiling(activityName)
phaseProfiler.start()
valueMap[TEST_KEY] = measureNanoTime {
test(testData)
phaseData.test(testData)
}
profilerHandler.stopProfiling(profilerPath, activityName)
phaseProfiler.stop()
PerformanceCounter.report { name, counter, nanos ->
valueMap["counter \"$name\": count"] = counter.toLong()
valueMap["counter \"$name\": time"] = nanos.nsToMs
}
} catch (t: Throwable) {
println("# error at $namePrefix #$attempt:")
println("# error at ${phaseData.testName} #$attempt:")
t.printStackTrace()
valueMap[ERROR_KEY] = t
} finally {
try {
val tearDownMillis = measureTimeMillis {
tearDown(testData)
phaseData.tearDown(testData)
}
logMessage { "tearDown took $tearDownMillis ms" }
} finally {
@@ -253,12 +261,30 @@ class Stats(val name: String = "", val header: Array<String> = arrayOf("Name", "
}
}
} catch (t: Throwable) {
println("error at $namePrefix:")
tcPrintErrors(namePrefix, listOf(t))
println("error at ${phaseData.testName}:")
tcPrintErrors(phaseData.testName, listOf(t))
}
return statInfosArray
}
private fun <K, T> createPhaseProfiler(
phaseData: PhaseData<K, T>,
phaseName: String,
attempt: Int
): PhaseProfiler {
val profilerHandler = if (phaseData.profileEnabled) ProfilerHandler.getInstance() else DummyProfilerHandler
return if (profilerHandler != DummyProfilerHandler) {
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
}
}
private fun triggerGC(attempt: Int) {
if (attempt > 0) {
val ref = WeakReference(IntArray(32 * 1024))
@@ -287,6 +313,11 @@ 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()
@@ -354,8 +385,34 @@ class Stats(val name: String = "", val header: Array<String> = arrayOf("Name", "
}
}
}
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>(
val iterations: Int,
val testName: String,
val setUp: (TestData<SV, TV>) -> Unit,
val test: (TestData<SV, TV>) -> Unit,
val tearDown: (TestData<SV, TV>) -> Unit,
val profileEnabled: Boolean = false
)
data class TestData<SV, TV>(var setUpValue: SV?, var value: TV?) {
fun reset() {
setUpValue = null