From 522e09075dabfe94dbce26419bbe50e70fb9539b Mon Sep 17 00:00:00 2001 From: Sergey Karashevich Date: Mon, 20 Aug 2018 10:01:40 +0300 Subject: [PATCH] [gui-test] move ProjectViewFixture to use ExtendedCellReader without direct reading nodes value --- .../fixtures/ProjectViewFixture.kt | 101 ++++++++++++------ .../testGuiFramework/framework/Timeouts.kt | 8 ++ .../testGuiFramework/impl/GuiTestUtilKt.kt | 42 +++++++- 3 files changed, 112 insertions(+), 39 deletions(-) diff --git a/platform/testGuiFramework/src/com/intellij/testGuiFramework/fixtures/ProjectViewFixture.kt b/platform/testGuiFramework/src/com/intellij/testGuiFramework/fixtures/ProjectViewFixture.kt index 9ce56d8688b6..6b8e8860f745 100644 --- a/platform/testGuiFramework/src/com/intellij/testGuiFramework/fixtures/ProjectViewFixture.kt +++ b/platform/testGuiFramework/src/com/intellij/testGuiFramework/fixtures/ProjectViewFixture.kt @@ -28,32 +28,38 @@ import com.intellij.openapi.projectRoots.JavaSdk import com.intellij.openapi.roots.JdkOrderEntry import com.intellij.openapi.util.Ref import com.intellij.openapi.util.text.StringUtil +import com.intellij.testGuiFramework.cellReader.ExtendedJTreeCellReader import com.intellij.testGuiFramework.framework.Timeouts import com.intellij.testGuiFramework.impl.GuiTestUtilKt import com.intellij.testGuiFramework.impl.GuiTestUtilKt.computeOnEdt import com.intellij.testGuiFramework.impl.GuiTestUtilKt.runOnEdt +import com.intellij.testGuiFramework.impl.GuiTestUtilKt.tryWithPause import com.intellij.testGuiFramework.impl.GuiTestUtilKt.waitUntil import com.intellij.testGuiFramework.impl.GuiTestUtilKt.withPauseWhenNull import com.intellij.ui.LoadingNode import com.intellij.util.ui.tree.TreeUtil import org.fest.assertions.Assertions.assertThat import org.fest.reflect.core.Reflection.field +import org.fest.swing.cell.JTreeCellReader import org.fest.swing.core.MouseButton import org.fest.swing.core.Robot import org.fest.swing.edt.GuiActionRunner import org.fest.swing.edt.GuiTask -import org.fest.swing.exception.WaitTimedOutError +import org.fest.swing.exception.ComponentLookupException import org.junit.Assert.assertNotNull import java.awt.Point import java.awt.Rectangle import java.util.* import javax.swing.JTree import javax.swing.tree.DefaultMutableTreeNode +import javax.swing.tree.TreeModel import javax.swing.tree.TreePath import kotlin.collections.ArrayList class ProjectViewFixture internal constructor(project: Project, robot: Robot) : ToolWindowFixture("Project", project, robot) { + val nodeReader: JTreeCellReader = ExtendedJTreeCellReader() + private fun selectProjectPane(): PaneFixture = getPaneById("ProjectPane") fun selectAndroidPane(): PaneFixture = getPaneById("AndroidView") @@ -78,41 +84,22 @@ class ProjectViewFixture internal constructor(project: Project, robot: Robot) : * @return NodeFixture object for a pathTo; may be used for expanding, scrolling and clicking node */ fun path(vararg pathTo: String): NodeFixture { - selectProjectPane() - val times = 3 - repeat(times) { - val nodeFixture = getNodeFixture(pathTo) - if (nodeFixture != null) return nodeFixture - } - throw Exception("Unable to find path: ${Arrays.toString(pathTo)} for current project structure in $times times.") - } - - private fun getNodeFixture(pathTo: Array): NodeFixture? { - return try { - withPauseWhenNull(timeout = Timeouts.seconds30) { - try { - getNodeFixtureByPath(pathTo as Array) - } - catch (e: Exception) { - LOG.debug("Exception during getting node by path (${Arrays.toString(pathTo)}) : $e") - null - } - } - } - catch (timedOutError: WaitTimedOutError) { - LOG.warn("Unable to find path: ${Arrays.toString(pathTo)} for current project structure.", timedOutError) - null + val projectPane = selectProjectPane() + val canonicalPath = pathTo.toList().expandSlashedPath() + return tryWithPause(exceptionClass = ComponentLookupException::class.java, + condition = "node with path ${Arrays.toString(pathTo)} will appear", + timeout = Timeouts.seconds30) { + activate() + projectPane.getNode(canonicalPath) } } - private fun getNodeFixtureByPath(pathTo: Array): NodeFixture? { - if (pathTo.size == 1) { - if (pathTo[0].contains("/")) { - val newPath = pathTo[0].split("/".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() - return selectProjectPane().getNode(newPath) - } + + private fun List.expandSlashedPath(): List { + return if (this.size == 1 && this[0].contains("/")) { + this[0].split("/".toRegex()).dropLastWhile { it.isEmpty() } } - return selectProjectPane().getNode(pathTo) + else this } @@ -128,9 +115,53 @@ class ProjectViewFixture internal constructor(project: Project, robot: Robot) : return this } - fun getNode(path: Array): NodeFixture? { + fun getNode(path: List): NodeFixture { val tree = myPane.tree - val root = computeOnEdt { myPane.tree.model.root } ?: throw Exception("Unfortunately the root for a tree model in ProjectView is null") + val root = computeOnEdt { myPane.tree.model.root } ?: throw Exception("The root for a tree model in ProjectView is null") + val pathToNode = traverseChildren(tree, TreePath(root), path) + return NodeFixture(pathToNode.lastPathComponent as DefaultMutableTreeNode, pathToNode, myPane) + } + + private fun traverseChildren(tree: JTree, treePath: TreePath, path: List): TreePath { + val model = tree.model + val parent = treePath.lastPathComponent + val childCount: Int = computeOnEdt { model.getChildCount(parent) } ?: throw Exception("Unable to calculate children for ${path[0]}") + expandIfLoadingNode(childCount, model, parent, tree, treePath) + + for (i in (0 until childCount)){ + val child = computeOnEdt { model.getChild(parent, i) } + if (nodeReader.valueAt(tree, child) == path[0]) { + return if (path.size == 1) + treePath.pathByAddingChild(child) + else + traverseChildren(tree, treePath.pathByAddingChild(child), path.drop(1)) + } + } + throw ComponentLookupException("Unable to find child with name '${path[0]}'") + } + + private fun expandIfLoadingNode(childCount: Int, + model: TreeModel, + parent: Any?, + tree: JTree, + treePath: TreePath) { + if (childCount == 1) { + val singleChild = computeOnEdt { model.getChild(parent, 0) } + if (singleChild is LoadingNode) { + runOnEdt { TreeUtil.selectPath(tree, treePath.pathByAddingChild(singleChild)) } + waitUntil("children will be loaded", Timeouts.seconds30) { + computeOnEdt { model.getChildCount(parent) > 1 || model.getChild(parent, 0) !is LoadingNode }!! + } + } + } + } + + //TODO: remove this method + @Deprecated("Because of unreliable logic for reading Nodes", ReplaceWith("getNode1 function")) + fun getNode2(path: Array): NodeFixture? { + val tree = myPane.tree + val root = computeOnEdt { myPane.tree.model.root } ?: throw Exception( + "Unfortunately the root for a tree model in ProjectView is null") var pivotRoot: Any = root for (pathItem in path) { var (childCount, children) = getChildrenAndCountOnEdt(tree, pivotRoot) @@ -159,6 +190,7 @@ class ProjectViewFixture internal constructor(project: Project, robot: Robot) : return NodeFixture(pivotRoot as DefaultMutableTreeNode, TreeUtil.getPathFromRoot(pivotRoot), myPane) } + //TODO: remove this method private fun getChildrenAndCountOnEdt(tree: JTree, node: Any): Pair> { return computeOnEdt { @@ -213,6 +245,7 @@ class ProjectViewFixture internal constructor(project: Project, robot: Robot) : fun invokeContextMenu() { expand() + myRobot.waitForIdle() myRobot.click(locationOnScreen, MouseButton.RIGHT_BUTTON, 1) } diff --git a/platform/testGuiFramework/src/com/intellij/testGuiFramework/framework/Timeouts.kt b/platform/testGuiFramework/src/com/intellij/testGuiFramework/framework/Timeouts.kt index a14782db4fa7..d71ae71507ea 100644 --- a/platform/testGuiFramework/src/com/intellij/testGuiFramework/framework/Timeouts.kt +++ b/platform/testGuiFramework/src/com/intellij/testGuiFramework/framework/Timeouts.kt @@ -24,3 +24,11 @@ object Timeouts { } fun Timeout.toSec(): Long = duration() / 1000 + +fun Timeout.toPrintable(): String { + return when { + this.duration() > 60000 -> "${this.duration() / 60000}(m)" + this.duration() > 1000 -> "${this.duration() / 1000}(s)" + else -> "${this.duration() / 1000}(ms)" + } +} diff --git a/platform/testGuiFramework/src/com/intellij/testGuiFramework/impl/GuiTestUtilKt.kt b/platform/testGuiFramework/src/com/intellij/testGuiFramework/impl/GuiTestUtilKt.kt index 66b56fbde70b..88c6c892b040 100644 --- a/platform/testGuiFramework/src/com/intellij/testGuiFramework/impl/GuiTestUtilKt.kt +++ b/platform/testGuiFramework/src/com/intellij/testGuiFramework/impl/GuiTestUtilKt.kt @@ -3,8 +3,10 @@ package com.intellij.testGuiFramework.impl import com.intellij.diagnostic.MessagePool import com.intellij.openapi.progress.ProgressManager +import com.intellij.openapi.util.Ref import com.intellij.testGuiFramework.framework.GuiTestUtil import com.intellij.testGuiFramework.framework.Timeouts +import com.intellij.testGuiFramework.framework.toPrintable import com.intellij.testGuiFramework.framework.toSec import com.intellij.ui.EngravedLabel import org.fest.swing.core.ComponentMatcher @@ -201,7 +203,9 @@ object GuiTestUtilKt { * * @throws WaitTimedOutError with the text: "Timed out waiting for $timeout second(s) until {@code conditionText} will be not null" */ - fun withPauseWhenNull(conditionText: String = "function to probe will", timeout: Timeout = Timeouts.defaultTimeout, functionProbeToNull: () -> ReturnType?): ReturnType { + inline fun withPauseWhenNull(conditionText: String = "function to probe will", + timeout: Timeout = Timeouts.defaultTimeout, + crossinline functionProbeToNull: () -> ReturnType?): ReturnType { var result: ReturnType? = null waitUntil("$conditionText will be not null", timeout) { result = functionProbeToNull() @@ -210,19 +214,44 @@ object GuiTestUtilKt { return result!! } - fun waitUntil(condition: String, timeout: Timeout = Timeouts.defaultTimeout, conditionalFunction: () -> Boolean) { + inline fun waitUntil(condition: String, timeout: Timeout = Timeouts.defaultTimeout, crossinline conditionalFunction: () -> Boolean) { Pause.pause(object : Condition("${timeout.toSec()} second(s) until $condition") { override fun test() = conditionalFunction() }, timeout) } + inline fun tryWithPause(exceptionClass: Class, + condition: String = "try block will not throw ${exceptionClass.name} exception", + timeout: Timeout, + crossinline tryBlock: () -> R): R { + val exceptionRef: Ref = Ref.create() + try { + return withPauseWhenNull (condition, timeout) { + try { + tryBlock() + } + catch (e: Exception) { + if (exceptionClass.isInstance(e)) { + exceptionRef.set(e) + return@withPauseWhenNull null + } + throw e + } + } + } + catch (e: WaitTimedOutError) { + throw Exception("Timeout for $condition exceeded ${timeout.toPrintable()}", exceptionRef.get()) + } + } + fun silentWaitUntil(condition: String, timeoutInSeconds: Int = 60, conditionalFunction: () -> Boolean) { try { Pause.pause(object : Condition("$timeoutInSeconds second(s) until $condition silently") { override fun test() = conditionalFunction() }, Timeout.timeout(timeoutInSeconds.toLong(), TimeUnit.SECONDS)) } - catch (ignore: WaitTimedOutError) { } + catch (ignore: WaitTimedOutError) { + } } fun findAllWithBFS(container: Container, clazz: Class): List { @@ -253,7 +282,9 @@ object GuiTestUtilKt { return GuiTestUtil.waitUntilGone(root, timeout, matcher) } - fun GuiTestCase.waitProgressDialogUntilGone(dialogTitle: String, timeoutToAppear: Timeout = Timeouts.seconds05, timeoutToGone: Timeout = Timeouts.defaultTimeout) { + fun GuiTestCase.waitProgressDialogUntilGone(dialogTitle: String, + timeoutToAppear: Timeout = Timeouts.seconds05, + timeoutToGone: Timeout = Timeouts.defaultTimeout) { waitProgressDialogUntilGone(this.robot(), dialogTitle, timeoutToAppear, timeoutToGone) } @@ -311,7 +342,8 @@ object GuiTestUtilKt { inline fun ignoreComponentLookupException(action: () -> Unit) = try { action() } - catch (ignore: ComponentLookupException) { } + catch (ignore: ComponentLookupException) { + } fun ensureCreateHasDone(guiTestCase: GuiTestCase) { try {