From dcffb60dc9b6c98d231c6b873ac3d49b5233d9ee Mon Sep 17 00:00:00 2001 From: Simon Ogorodnik Date: Mon, 27 Aug 2018 21:05:18 +0300 Subject: [PATCH] New J2K: Create UI for tracking test changes --- ...ractJavaToKotlinConverterSingleFileTest.kt | 3 +- ...oKotlinConverterStructureSingleFileTest.kt | 116 ++++++++++ .../jetbrains/kotlin/j2k/NewJ2KTestView.kt | 206 ++++++++++++++++++ 3 files changed, 323 insertions(+), 2 deletions(-) create mode 100644 j2k/tests/org/jetbrains/kotlin/j2k/NewJ2KTestView.kt diff --git a/j2k/tests/org/jetbrains/kotlin/j2k/AbstractJavaToKotlinConverterSingleFileTest.kt b/j2k/tests/org/jetbrains/kotlin/j2k/AbstractJavaToKotlinConverterSingleFileTest.kt index 93b977fcb0d..313eb5a27ea 100644 --- a/j2k/tests/org/jetbrains/kotlin/j2k/AbstractJavaToKotlinConverterSingleFileTest.kt +++ b/j2k/tests/org/jetbrains/kotlin/j2k/AbstractJavaToKotlinConverterSingleFileTest.kt @@ -17,7 +17,6 @@ package org.jetbrains.kotlin.j2k import com.intellij.openapi.command.WriteCommandAction -import com.intellij.openapi.fileEditor.impl.LoadTextUtil import com.intellij.openapi.project.Project import com.intellij.openapi.util.io.FileUtil import com.intellij.psi.PsiJavaFile @@ -35,7 +34,7 @@ import java.util.regex.Pattern abstract class AbstractJavaToKotlinConverterSingleFileTest : AbstractJavaToKotlinConverterTest() { val testHeaderPattern = Pattern.compile("//(element|expression|statement|method|class|file|comp)\n") - fun doTest(javaPath: String) { + open fun doTest(javaPath: String) { val project = LightPlatformTestCase.getProject()!! val javaFile = File(javaPath) val fileContents = FileUtil.loadFile(javaFile, true) diff --git a/j2k/tests/org/jetbrains/kotlin/j2k/AbstractNewJavaToKotlinConverterStructureSingleFileTest.kt b/j2k/tests/org/jetbrains/kotlin/j2k/AbstractNewJavaToKotlinConverterStructureSingleFileTest.kt index ee0c4b42bf3..80013be39a4 100644 --- a/j2k/tests/org/jetbrains/kotlin/j2k/AbstractNewJavaToKotlinConverterStructureSingleFileTest.kt +++ b/j2k/tests/org/jetbrains/kotlin/j2k/AbstractNewJavaToKotlinConverterStructureSingleFileTest.kt @@ -5,16 +5,132 @@ package org.jetbrains.kotlin.j2k +import com.google.gson.Gson +import com.intellij.openapi.util.registry.Registry +import com.intellij.rt.execution.junit.FileComparisonFailure +import com.intellij.util.diff.Diff import org.jetbrains.kotlin.test.KotlinTestUtils import java.io.File +import java.text.SimpleDateFormat +import java.util.* +import kotlin.concurrent.thread abstract class AbstractNewJavaToKotlinConverterStructureSingleFileTest : AbstractNewJavaToKotlinConverterSingleFileTest() { override fun compareResults(expectedFile: File, actual: String) { + results.totalExpected += expectedFile.readLines().count() + results.totalActual += actual.lines().count() + + val beforeReg = Registry.`is`("diff.patience.alg") + Registry.get("diff.patience.alg").setValue(true) + Diff.buildChanges(expectedFile.readText(), actual)?.toList().orEmpty().forEach { change -> + results.totalDiffPlus += change.inserted + results.totalDiffMinus += change.deleted + } + Registry.get("diff.patience.alg").setValue(beforeReg) + KotlinTestUtils.assertEqualsToFile(expectedFile, actual) { val file = createKotlinFile(it) file.dumpStructureText() } } + + override fun doTest(javaPath: String) { + val testName = Thread.currentThread().stackTrace[5].let { "${it.className}.${it.methodName}" } + try { + super.doTest(javaPath) + results.passes += testName + } catch (e: AssertionError) { + results.assertionFailures += testName + throw e + } catch (e: FileComparisonFailure) { + results.assertionFailures += testName + throw e + } catch (e: Throwable) { + results.exceptionFailures += testName + throw e + } + } + + + companion object { + fun initializeForTests() { + Runtime.getRuntime().addShutdownHook(thread(start = false) { + println("CLOSED") + + + val dateString = dateFormat.format(Date()) + + File("./test_report/$dateString.txt").apply { + parentFile.mkdirs() + writeText(results.stat()) + } + File("./test_report/$dateString.json").apply { + writeText( + results.serialize() + ) + } + + }) + } + + + data class TestResults( + val assertionFailures: MutableSet = mutableSetOf(), + val exceptionFailures: MutableSet = mutableSetOf(), + val passes: MutableSet = mutableSetOf(), + var totalDiffPlus: Int = 0, + var totalDiffMinus: Int = 0, + var totalExpected: Int = 0, + var totalActual: Int = 0 + ) { + + fun stat(original: TestResults = this): String { + fun Int.percentOf(b: Int): String = String.format("%.2f%%", this * 100F / b) + return """ + +++ $totalDiffPlus(${totalDiffPlus.percentOf(original.totalExpected)}, ${totalDiffPlus.percentOf(original.totalActual)}) + --- $totalDiffMinus(${totalDiffMinus.percentOf(original.totalExpected)}, ${totalDiffMinus.percentOf(original.totalActual)}) + Expected: $totalExpected ${"(${original.totalExpected})".takeIf { original != this } ?: ""} + Actual: $totalActual ${"(${original.totalActual})".takeIf { original != this } ?: ""} + ${shortStat()} + """.trimIndent() + } + + fun shortStat(): String { + return "P: ${passes.size}, A: ${assertionFailures.size}, E: ${exceptionFailures.size}" + } + + + fun serialize(): String { + return Gson().toJson(this) + } + + operator fun minus(before: TestResults): TestResults { + return TestResults().also { + it.passes += passes - before.passes + it.assertionFailures += assertionFailures - before.assertionFailures + it.exceptionFailures += exceptionFailures - before.exceptionFailures + + it.totalDiffPlus = totalDiffPlus - before.totalDiffPlus + it.totalDiffMinus = totalDiffMinus - before.totalDiffMinus + it.totalExpected = totalExpected - before.totalExpected + it.totalActual = totalActual - before.totalActual + } + } + } + + fun loadTestResults(text: String): TestResults { + return Gson().fromJson(text, TestResults::class.java) + } + + + val results: TestResults by lazy { + initializeForTests() + TestResults() + } + + val dateFormat = SimpleDateFormat("yyyy-MM-dd__HH-mm-ss") + + } } \ No newline at end of file diff --git a/j2k/tests/org/jetbrains/kotlin/j2k/NewJ2KTestView.kt b/j2k/tests/org/jetbrains/kotlin/j2k/NewJ2KTestView.kt new file mode 100644 index 00000000000..bb1d5ecbecc --- /dev/null +++ b/j2k/tests/org/jetbrains/kotlin/j2k/NewJ2KTestView.kt @@ -0,0 +1,206 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. 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.j2k + +import com.intellij.icons.AllIcons +import com.intellij.ui.components.JBLabel +import com.intellij.ui.components.JBList +import com.intellij.ui.components.JBScrollPane +import com.intellij.ui.components.JBTabbedPane +import com.intellij.uiDesigner.core.GridConstraints +import com.intellij.uiDesigner.core.GridLayoutManager +import org.jetbrains.kotlin.j2k.AbstractNewJavaToKotlinConverterStructureSingleFileTest.Companion.TestResults +import org.jetbrains.kotlin.j2k.AbstractNewJavaToKotlinConverterStructureSingleFileTest.Companion.dateFormat +import org.jetbrains.kotlin.j2k.AbstractNewJavaToKotlinConverterStructureSingleFileTest.Companion.loadTestResults +import java.awt.event.ActionEvent +import java.awt.event.KeyEvent +import java.awt.event.KeyListener +import java.io.File +import java.util.* +import javax.swing.* + +object NewJ2KTestView { + + private class CompareSelectTab(tabs: JBTabbedPane, val after: TestResults) : IndexTab(tabs) { + override fun action() { + val diff = after - variants[list.selectedIndex].second + tabs.removeTabAt(tabs.selectedIndex) + val tabIndex = tabs.tabCount + tabs.addTab("Compare", ReportTab(tabs, tabIndex, diff, after)) + tabs.selectedIndex = tabIndex + } + } + + private class ReportTab(val tabs: JBTabbedPane, val index: Int, results: TestResults, original: TestResults) : JPanel() { + + init { + layout = GridLayoutManager(2, 1) + val report = JBLabel("" + results.stat(original).replace("\n", "
") + "") + add(report, GridConstraints().also { + it.row = 0 + it.fill = GridConstraints.FILL_HORIZONTAL + it.anchor = GridConstraints.ANCHOR_NORTH + }) + + + val testsScroll = run { + val model = DefaultListModel() + fun addWithIcon(list: List, icon: Icon) { + for (i in list) { + model.addElement(JBLabel(i.substringAfter("$"), icon, SwingConstants.LEFT)) + } + } + + addWithIcon(results.passes.sorted(), AllIcons.RunConfigurations.TestState.Green2) + addWithIcon(results.assertionFailures.sorted(), AllIcons.RunConfigurations.TestState.Yellow2) + addWithIcon(results.exceptionFailures.sorted(), AllIcons.RunConfigurations.TestState.Red2) + + val list = JBList(model) + list.cellRenderer = ListCellRenderer { list, value, index, isSelected, cellHasFocus -> value!! } + JBScrollPane(list) + } + add(testsScroll, GridConstraints().also { + it.row = 1 + it.anchor = GridConstraints.ANCHOR_NORTH + it.fill = GridConstraints.FILL_BOTH + }) + + getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_C, 0), "COMPARE") + actionMap.put("COMPARE", object : AbstractAction() { + override fun actionPerformed(e: ActionEvent) { + val index = tabs.tabCount + tabs.addTab("Select", CompareSelectTab(tabs, results)) + tabs.selectedIndex = index + } + }) + } + } + + private open class IndexTab(val tabs: JBTabbedPane) : JPanel() { + val list = JBList(object : AbstractListModel() { + override fun getElementAt(index: Int): String { + val (date, results) = variants[index] + return "$index -> $date (${results.shortStat()})" + } + + override fun getSize(): Int { + return variants.size + } + }) + + open fun action() { + val (date, report) = variants[list.selectedIndex] + val tabIndex = tabs.tabCount + tabs.addTab("$date", ReportTab(tabs, tabIndex, report, report)) + tabs.selectedIndex = tabIndex + } + + init { + layout = GridLayoutManager(1, 1) + add(list, GridConstraints().apply { fill = GridConstraints.FILL_BOTH }) + + list.addKeyListener(object : KeyListener { + var typed = "" + + fun upd() { + if (typed.isEmpty()) { + list.selectedIndex = -1 + } else { + list.selectedIndex = variants.indices.firstOrNull { it.toString().startsWith(typed) } ?: -1 + } + } + + override fun keyTyped(e: KeyEvent) { + val c = e.keyChar + when { + Character.isDigit(c) -> { + typed += c + upd() + e.consume() + } + e.extendedKeyCode == KeyEvent.VK_ENTER -> { + action() + e.consume() + } + } + } + + override fun keyPressed(e: KeyEvent) { + if (e.extendedKeyCode == KeyEvent.VK_BACK_SPACE) { + typed = typed.dropLast(1) + upd() + e.consume() + } + } + + override fun keyReleased(e: KeyEvent?) { + } + }) + + } + } + + private val variants: MutableList> = mutableListOf() + private fun load() { + variants.clear() + variants.addAll(File("./test_report").walkTopDown().filter { it.extension == "json" }.map { + dateFormat.parse(it.nameWithoutExtension) to loadTestResults( + it.readText() + ) + }) + variants.sortBy { it.first } + } + + @JvmStatic + fun main(args: Array) { + load() + + + val frame = JFrame().apply { + val tabs = JBTabbedPane() + + fun addIndexTab() { + tabs.addTab("Index", IndexTab(tabs)) + } + + addIndexTab() + + + this.rootPane.actionMap.put("ESCAPE", object : AbstractAction() { + override fun actionPerformed(e: ActionEvent) { + tabs.selectedIndex = 0 + } + }) + + this.rootPane.actionMap.put("CLOSE_TAB", object : AbstractAction() { + override fun actionPerformed(e: ActionEvent) { + if (tabs.selectedIndex != 0) { + tabs.removeTabAt(tabs.selectedIndex) + } + } + }) + + this.rootPane.actionMap.put("RELOAD", object : AbstractAction() { + override fun actionPerformed(e: ActionEvent) { + tabs.removeAll() + load() + addIndexTab() + } + }) + + + this.rootPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_W, KeyEvent.META_DOWN_MASK), "CLOSE_TAB") + this.rootPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_R, KeyEvent.META_DOWN_MASK), "RELOAD") + this.rootPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "ESCAPE") + + contentPane = tabs + } + + frame.setSize(800, 800) + frame.defaultCloseOperation = JFrame.EXIT_ON_CLOSE + frame.isVisible = true + } +} \ No newline at end of file