From bbde48757316cd0e734da1b27497371d54abaeb8 Mon Sep 17 00:00:00 2001 From: max-martynov Date: Mon, 23 Jan 2023 19:38:04 +0100 Subject: [PATCH 1/3] Implement tests based on counting incoming and outgoing edges for PHP only --- .../src/test/kotlin/BasePsiRequiredTest.kt | 57 ++++++- .../php/PhpControlFlowEdgeProviderTest.kt | 140 ++++++++++++------ .../resources/data/php/PhpFlowMethods.php | 2 +- 3 files changed, 147 insertions(+), 52 deletions(-) diff --git a/psiminer-core/src/test/kotlin/BasePsiRequiredTest.kt b/psiminer-core/src/test/kotlin/BasePsiRequiredTest.kt index d4c8034..ea756ce 100644 --- a/psiminer-core/src/test/kotlin/BasePsiRequiredTest.kt +++ b/psiminer-core/src/test/kotlin/BasePsiRequiredTest.kt @@ -1,9 +1,12 @@ import com.intellij.openapi.application.ReadAction +import com.intellij.openapi.editor.Document +import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiElement import com.intellij.testFramework.fixtures.BasePlatformTestCase import org.junit.jupiter.api.AfterAll import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.TestInstance +import psi.graphs.Edge import psi.language.JavaHandler import psi.language.KotlinHandler import psi.language.LanguageHandler @@ -15,7 +18,6 @@ abstract class BasePsiRequiredTest(private val psiSourceFile: File) : BasePlatfo abstract val handler: LanguageHandler private val methods = mutableMapOf() - private class ResourceException(resourceRoot: String) : RuntimeException("Can't find resources in $resourceRoot") // We should define the root resources folder @@ -35,8 +37,8 @@ abstract class BasePsiRequiredTest(private val psiSourceFile: File) : BasePlatfo val methodName = handler.methodProvider.getNameNode(it).text methods[methodName] = it } - } } + } } @AfterAll @@ -50,6 +52,54 @@ abstract class BasePsiRequiredTest(private val psiSourceFile: File) : BasePlatfo fun getMethod(methodName: String): PsiElement = methods[methodName] ?: throw UnknownMethodException(methodName, psiSourceFile.name) + /** + * Returns a Pair(lineNumber, columnNumber) where psiElement located in file. + * Both lineNumber and columnNumber are 1-based. + */ + private fun getPositionInFile(psiElement: PsiElement): Pair { + val document = getDocument(psiElement) + val textOffset = psiElement.textOffset + val lineNumber = document.getLineNumber(textOffset) // here 0-based + val prevLineEndOffset = document.getLineEndOffset(lineNumber - 1) + val columnNumber = textOffset - prevLineEndOffset + return Pair(lineNumber + 1, columnNumber) + } + + private fun getDocument(psiElement: PsiElement): Document { + val containingFile = psiElement.containingFile + val project = containingFile.project + val psiDocumentManager = PsiDocumentManager.getInstance(project) + return psiDocumentManager.getDocument(containingFile) + ?: throw DocumentNotFoundException(psiSourceFile.name, psiElement.text) + } + + private class DocumentNotFoundException(fileName: String, psiElementText: String) : + RuntimeException("Can't find document for PsiElement $psiElementText in file $fileName") + + data class Vertex(val elementText: String, val positionInFile: Pair) + + fun countOutgoingEdges(edges: List): Map = + edges.groupBy { edge -> edge.from } + .mapKeys { (psiElement, _) -> Vertex(psiElement.toString(), getPositionInFile(psiElement)) } + .mapValues { (_, toVertices) -> toVertices.size } + + fun countIncomingEdges(edges: List): Map = + edges.groupBy { edge -> edge.to } + .mapKeys { (psiElement, _) -> Vertex(psiElement.toString(), getPositionInFile(psiElement)) } + .mapValues { (_, fromVertices) -> fromVertices.size } + + data class CorrectNumberOfIncomingAndOutgoingEdges( + val incoming: Map>, + val outgoing: Map> + ) + + fun Map.containsAll(other: Map?): Boolean { + if (other == null) { + return false + } + return this.entries.containsAll(other.entries) + } + companion object { // We cannot get the root of the class resources automatically. private const val resourcesRoot: String = "data" @@ -58,6 +108,7 @@ abstract class BasePsiRequiredTest(private val psiSourceFile: File) : BasePlatfo open class JavaPsiRequiredTest(source: String) : BasePsiRequiredTest(dataFolder.resolve("$source.$ext")) { override val handler: LanguageHandler = JavaHandler() + companion object { val dataFolder = File("java") const val ext = "java" @@ -66,6 +117,7 @@ open class JavaPsiRequiredTest(source: String) : BasePsiRequiredTest(dataFolder. open class KotlinPsiRequiredTest(source: String) : BasePsiRequiredTest(dataFolder.resolve("$source.$ext")) { override val handler: LanguageHandler = KotlinHandler() + companion object { val dataFolder = File("kotlin") const val ext = "kt" @@ -74,6 +126,7 @@ open class KotlinPsiRequiredTest(source: String) : BasePsiRequiredTest(dataFolde open class PhpPsiRequiredTest(source: String) : BasePsiRequiredTest(dataFolder.resolve("$source.$ext")) { override val handler: LanguageHandler = PhpHandler() + companion object { val dataFolder = File("php") const val ext = "php" diff --git a/psiminer-core/src/test/kotlin/psi/graphs/edgeProviders/php/PhpControlFlowEdgeProviderTest.kt b/psiminer-core/src/test/kotlin/psi/graphs/edgeProviders/php/PhpControlFlowEdgeProviderTest.kt index d766a0f..4323ccf 100644 --- a/psiminer-core/src/test/kotlin/psi/graphs/edgeProviders/php/PhpControlFlowEdgeProviderTest.kt +++ b/psiminer-core/src/test/kotlin/psi/graphs/edgeProviders/php/PhpControlFlowEdgeProviderTest.kt @@ -2,7 +2,6 @@ package psi.graphs.edgeProviders.php import PhpPsiRequiredTest import com.intellij.openapi.application.ReadAction -import com.intellij.psi.PsiElement import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.ValueSource import psi.graphs.EdgeType @@ -16,11 +15,8 @@ internal class PhpControlFlowEdgeProviderTest : PhpPsiRequiredTest("PhpFlowMetho @ValueSource( strings = [ "straightWriteMethod", - "straightReadWriteMethod", "ifMethod", - "forMethod", - "foreachMethod", - "multipleReturns" + "forMethod" ] ) fun `test control flow extraction from PHP methods`(methodName: String) { @@ -30,10 +26,16 @@ internal class PhpControlFlowEdgeProviderTest : PhpPsiRequiredTest("PhpFlowMetho val codeGraph = graphMiner.mine(psiRoot) val controlFlowEdges = codeGraph.edges.withType(EdgeType.ControlFlow).flatMap { (_, edges) -> edges.forward() } - val textRepresentation = controlFlowEdges.map { - Pair(it.from.shortText(), it.to.shortText()) - }.toSet() - println(textRepresentation) + assertTrue( + countIncomingEdges(controlFlowEdges).containsAll( + correctNumberOfControlFlowEdges.incoming[methodName] + ) + ) + assertTrue( + countOutgoingEdges(controlFlowEdges).containsAll( + correctNumberOfControlFlowEdges.outgoing[methodName] + ) + ) } } @@ -41,11 +43,8 @@ internal class PhpControlFlowEdgeProviderTest : PhpPsiRequiredTest("PhpFlowMetho @ValueSource( strings = [ "straightWriteMethod", - "straightReadWriteMethod", "ifMethod", - "forMethod", - "foreachMethod", - "multipleReturns" + "forMethod" ] ) fun `test control element extraction from PHP methods`(methodName: String) { @@ -53,46 +52,89 @@ internal class PhpControlFlowEdgeProviderTest : PhpPsiRequiredTest("PhpFlowMetho val graphMiner = PhpGraphMiner() ReadAction.run { val codeGraph = graphMiner.mine(psiRoot) - val controlElementEdges = + val controlFlowEdges = codeGraph.edges.withType(EdgeType.ControlElement).flatMap { (_, edges) -> edges.forward() } - val textRepresentation = controlElementEdges.map { - Pair(it.from.shortText(), it.to.shortText()) - }.toSet() - println(textRepresentation) + println("Incoming: ${countIncomingEdges(controlFlowEdges)}") + println("Outgoing: ${countOutgoingEdges(controlFlowEdges)}") + assertTrue( + countIncomingEdges(controlFlowEdges).containsAll( + correctNumberOfControlElementEdges.incoming[methodName] + ) + ) + assertTrue( + countOutgoingEdges(controlFlowEdges).containsAll( + correctNumberOfControlElementEdges.outgoing[methodName] + ) + ) } } - @ParameterizedTest - @ValueSource( - strings = [ - "straightWriteMethod", - "straightReadWriteMethod", - "ifMethod", - "forMethod", - "foreachMethod", - "multipleReturns" - ] - ) - fun `test return element extraction from PHP methods`(methodName: String) { - val psiRoot = getMethod(methodName) - val graphMiner = PhpGraphMiner() - ReadAction.run { - val codeGraph = graphMiner.mine(psiRoot) - val controlElementEdges = - codeGraph.edges.withType(EdgeType.ReturnsTo).flatMap { (_, edges) -> edges.forward() } - val textRepresentation = controlElementEdges.map { - Pair(it.from.shortText(), it.to.shortText()) - }.toSet() - println(textRepresentation) - } - } + companion object { - private fun PsiElement.shortText(): String { - val lines = text.lines() - return if (lines.size <= 1) { - text - } else { - lines[0] + "...${lines.size}" - } + val correctNumberOfControlFlowEdges = CorrectNumberOfIncomingAndOutgoingEdges( + incoming = mapOf( + "straightWriteMethod" to mapOf( + Vertex("VariableImpl: a", Pair(9, 9)) to 1, + Vertex("VariableImpl: b", Pair(10, 9)) to 1, + Vertex("VariableImpl: c", Pair(11, 9)) to 1 + ), + "ifMethod" to mapOf( + Vertex("BinaryExpressionImpl: \$a > 1", Pair(26, 13)) to 1, + Vertex("VariableImpl: b", Pair(27, 13)) to 1, + Vertex("Statement", Pair(33, 9)) to 3 + ), + "forMethod" to mapOf( + Vertex("VariableImpl: i", Pair(57, 14)) to 1, + Vertex("VariableImpl: i", Pair(57, 22)) to 2, + Vertex("Break", Pair(59, 17)) to 1 + ) + ), + outgoing = mapOf( + "straightWriteMethod" to mapOf( + Vertex("MethodImpl: straightWriteMethod", Pair(7, 21)) to 1, + Vertex("VariableImpl: a", Pair(9, 9)) to 1, + Vertex("VariableImpl: b", Pair(10, 9)) to 1 + ), + "ifMethod" to mapOf( + Vertex("BinaryExpressionImpl: \$a > 1", Pair(26, 13)) to 2, + Vertex("BinaryExpressionImpl: \$a < 0", Pair(28, 20)) to 2, + Vertex("VariableImpl: c", Pair(29, 13)) to 1 + ), + "forMethod" to mapOf( + Vertex("VariableImpl: i", Pair(57, 14)) to 1, + Vertex("BinaryExpressionImpl: \$i == 1", Pair(58, 17)) to 2 + ) + ) + ) + + val correctNumberOfControlElementEdges = CorrectNumberOfIncomingAndOutgoingEdges( + incoming = mapOf( + "straightWriteMethod" to mapOf(), + "ifMethod" to mapOf( + Vertex("If", Pair(26, 9)) to 1, // $a = 1 -> if ($a > 1)... + Vertex("VariableImpl: a", Pair(26, 13)) to 1, // if ($a > 1)... -> $a + Vertex("If", Pair(28, 16)) to 1, // if ($a > 1)... -> else if ($a < 0)... + Vertex("VariableImpl: a", Pair(28, 20)) to 1 // else if ($a < 0)... -> $a + ), + "forMethod" to mapOf( + Vertex("For", Pair(57, 9)) to 3, // 1. forMethod -> for () 2. $i -> for() 3. break -> for() + Vertex("VariableImpl: i", Pair(57, 14)) to 1, // for () -> $i = 0 + Vertex("BinaryExpressionImpl: \$i < 2", Pair(57, 22)) to 1 // for () -> $i < 2 + ) + ), + outgoing = mapOf( + "straightWriteMethod" to mapOf(), + "ifMethod" to mapOf( + Vertex("VariableImpl: a", Pair(25, 9)) to 1, // $a = 1 -> if ($a > 1)... + Vertex("If", Pair(26, 9)) to 1 // if ($a > 1)... -> $a + ), + "forMethod" to mapOf( + Vertex("MethodImpl: forMethod", Pair(55, 21)) to 1, // forMethod -> for() + Vertex("For", Pair(57, 9)) to 2, // 1. for() -> $i = 0 2. for() -> $i < 2 + Vertex("VariableImpl: i", Pair(57, 22)) to 1, // $i -> $i < 2 + Vertex("Break", Pair(59, 17)) to 1 // break -> for() + ) + ) + ) } } diff --git a/psiminer-core/src/test/resources/data/php/PhpFlowMethods.php b/psiminer-core/src/test/resources/data/php/PhpFlowMethods.php index 906b8ed..7557ac1 100644 --- a/psiminer-core/src/test/resources/data/php/PhpFlowMethods.php +++ b/psiminer-core/src/test/resources/data/php/PhpFlowMethods.php @@ -80,6 +80,6 @@ public function multipleReturns(): int return 1; } } - return 2; + return 2; } } \ No newline at end of file From 273c7ac9455980a4af16f68e7e00ce57bea91b94 Mon Sep 17 00:00:00 2001 From: max-martynov Date: Wed, 25 Jan 2023 21:44:13 +0100 Subject: [PATCH 2/3] Move graph logic into new class BaseGraphTest, modify line and column number calculations --- .../src/test/kotlin/BasePsiRequiredTest.kt | 51 ----------- .../test/kotlin/psi/graphs/BaseGraphTest.kt | 90 +++++++++++++++++++ .../php/PhpControlFlowEdgeProviderTest.kt | 69 +++++++------- 3 files changed, 122 insertions(+), 88 deletions(-) create mode 100644 psiminer-core/src/test/kotlin/psi/graphs/BaseGraphTest.kt diff --git a/psiminer-core/src/test/kotlin/BasePsiRequiredTest.kt b/psiminer-core/src/test/kotlin/BasePsiRequiredTest.kt index ea756ce..7a0896e 100644 --- a/psiminer-core/src/test/kotlin/BasePsiRequiredTest.kt +++ b/psiminer-core/src/test/kotlin/BasePsiRequiredTest.kt @@ -1,12 +1,9 @@ import com.intellij.openapi.application.ReadAction -import com.intellij.openapi.editor.Document -import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiElement import com.intellij.testFramework.fixtures.BasePlatformTestCase import org.junit.jupiter.api.AfterAll import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.TestInstance -import psi.graphs.Edge import psi.language.JavaHandler import psi.language.KotlinHandler import psi.language.LanguageHandler @@ -52,54 +49,6 @@ abstract class BasePsiRequiredTest(private val psiSourceFile: File) : BasePlatfo fun getMethod(methodName: String): PsiElement = methods[methodName] ?: throw UnknownMethodException(methodName, psiSourceFile.name) - /** - * Returns a Pair(lineNumber, columnNumber) where psiElement located in file. - * Both lineNumber and columnNumber are 1-based. - */ - private fun getPositionInFile(psiElement: PsiElement): Pair { - val document = getDocument(psiElement) - val textOffset = psiElement.textOffset - val lineNumber = document.getLineNumber(textOffset) // here 0-based - val prevLineEndOffset = document.getLineEndOffset(lineNumber - 1) - val columnNumber = textOffset - prevLineEndOffset - return Pair(lineNumber + 1, columnNumber) - } - - private fun getDocument(psiElement: PsiElement): Document { - val containingFile = psiElement.containingFile - val project = containingFile.project - val psiDocumentManager = PsiDocumentManager.getInstance(project) - return psiDocumentManager.getDocument(containingFile) - ?: throw DocumentNotFoundException(psiSourceFile.name, psiElement.text) - } - - private class DocumentNotFoundException(fileName: String, psiElementText: String) : - RuntimeException("Can't find document for PsiElement $psiElementText in file $fileName") - - data class Vertex(val elementText: String, val positionInFile: Pair) - - fun countOutgoingEdges(edges: List): Map = - edges.groupBy { edge -> edge.from } - .mapKeys { (psiElement, _) -> Vertex(psiElement.toString(), getPositionInFile(psiElement)) } - .mapValues { (_, toVertices) -> toVertices.size } - - fun countIncomingEdges(edges: List): Map = - edges.groupBy { edge -> edge.to } - .mapKeys { (psiElement, _) -> Vertex(psiElement.toString(), getPositionInFile(psiElement)) } - .mapValues { (_, fromVertices) -> fromVertices.size } - - data class CorrectNumberOfIncomingAndOutgoingEdges( - val incoming: Map>, - val outgoing: Map> - ) - - fun Map.containsAll(other: Map?): Boolean { - if (other == null) { - return false - } - return this.entries.containsAll(other.entries) - } - companion object { // We cannot get the root of the class resources automatically. private const val resourcesRoot: String = "data" diff --git a/psiminer-core/src/test/kotlin/psi/graphs/BaseGraphTest.kt b/psiminer-core/src/test/kotlin/psi/graphs/BaseGraphTest.kt new file mode 100644 index 0000000..a634368 --- /dev/null +++ b/psiminer-core/src/test/kotlin/psi/graphs/BaseGraphTest.kt @@ -0,0 +1,90 @@ +package psi.graphs + +import BasePsiRequiredTest +import com.intellij.openapi.editor.Document +import com.intellij.psi.PsiDocumentManager +import com.intellij.psi.PsiElement +import com.intellij.psi.util.parentOfType +import com.jetbrains.php.lang.psi.elements.impl.MethodImpl +import org.junit.jupiter.api.TestInstance +import psi.language.LanguageHandler +import psi.language.PhpHandler +import java.io.File + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +abstract class BaseGraphTest(private val psiSourceFile: File) : BasePsiRequiredTest(psiSourceFile) { + + fun countOutgoingEdges(edges: List): Map = + edges.groupBy { edge -> edge.from } + .mapKeys { (psiElement, _) -> Vertex(psiElement.toString(), getPositionInFunction(psiElement)) } + .mapValues { (_, toVertices) -> toVertices.size } + + fun countIncomingEdges(edges: List): Map = + edges.groupBy { edge -> edge.to } + .mapKeys { (psiElement, _) -> Vertex(psiElement.toString(), getPositionInFunction(psiElement)) } + .mapValues { (_, fromVertices) -> fromVertices.size } + + fun Map.containsAll(other: Map?): Boolean { + if (other == null) { + return false + } + return this.entries.containsAll(other.entries) + } + + abstract fun PsiElement.methodRoot(): PsiElement + + private fun Document.getLineNumber(psiElement: PsiElement) = + this.getLineNumber(psiElement.textOffset) + + /** + * For PsiElement returns its position relatively to the function declaration. + * + * Function declaration corresponds to lineNumber=0. + * Column number is also 0-based. + */ + private fun getPositionInFunction(psiElement: PsiElement): Pair { + val document = getDocument(psiElement) + val functionDeclarationLineNumber = document.getLineNumber(psiElement.methodRoot()) + val lineNumber = document.getLineNumber(psiElement) - functionDeclarationLineNumber + val prevLineEndOffset = document.getLineEndOffset(lineNumber + functionDeclarationLineNumber - 1) + val columnNumber = psiElement.textOffset - prevLineEndOffset - 1 + return Pair(lineNumber, columnNumber) + } + + private fun getDocument(psiElement: PsiElement): Document { + val containingFile = psiElement.containingFile + val project = containingFile.project + val psiDocumentManager = PsiDocumentManager.getInstance(project) + return psiDocumentManager.getDocument(containingFile) + ?: throw DocumentNotFoundException(psiSourceFile.name, psiElement.text) + } + + data class Vertex(val elementText: String, val positionInFile: Pair) + + data class CorrectNumberOfIncomingAndOutgoingEdges( + val incoming: Map>, + val outgoing: Map> + ) + + private class DocumentNotFoundException(fileName: String, psiElementText: String) : + RuntimeException("Can't find document for PsiElement $psiElementText in file $fileName") + + class MethodRootNotFoundException(psiElementText: String) : + RuntimeException("Can't find method root for $psiElementText") +} + +open class PhpGraphTest(source: String) : BaseGraphTest(dataFolder.resolve("$source.$ext")) { + + override fun PsiElement.methodRoot(): PsiElement = + when (this) { + is MethodImpl -> this + else -> this.parentOfType() as PsiElement? ?: throw MethodRootNotFoundException(this.text) + } + + override val handler: LanguageHandler = PhpHandler() + + companion object { + val dataFolder = File("php") + const val ext = "php" + } +} diff --git a/psiminer-core/src/test/kotlin/psi/graphs/edgeProviders/php/PhpControlFlowEdgeProviderTest.kt b/psiminer-core/src/test/kotlin/psi/graphs/edgeProviders/php/PhpControlFlowEdgeProviderTest.kt index 4323ccf..5d9e222 100644 --- a/psiminer-core/src/test/kotlin/psi/graphs/edgeProviders/php/PhpControlFlowEdgeProviderTest.kt +++ b/psiminer-core/src/test/kotlin/psi/graphs/edgeProviders/php/PhpControlFlowEdgeProviderTest.kt @@ -1,15 +1,12 @@ package psi.graphs.edgeProviders.php -import PhpPsiRequiredTest import com.intellij.openapi.application.ReadAction import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.ValueSource -import psi.graphs.EdgeType -import psi.graphs.forward +import psi.graphs.* import psi.graphs.graphMiners.PhpGraphMiner -import psi.graphs.withType -internal class PhpControlFlowEdgeProviderTest : PhpPsiRequiredTest("PhpFlowMethods") { +internal class PhpControlFlowEdgeProviderTest : PhpGraphTest("PhpFlowMethods") { @ParameterizedTest @ValueSource( @@ -54,8 +51,6 @@ internal class PhpControlFlowEdgeProviderTest : PhpPsiRequiredTest("PhpFlowMetho val codeGraph = graphMiner.mine(psiRoot) val controlFlowEdges = codeGraph.edges.withType(EdgeType.ControlElement).flatMap { (_, edges) -> edges.forward() } - println("Incoming: ${countIncomingEdges(controlFlowEdges)}") - println("Outgoing: ${countOutgoingEdges(controlFlowEdges)}") assertTrue( countIncomingEdges(controlFlowEdges).containsAll( correctNumberOfControlElementEdges.incoming[methodName] @@ -74,35 +69,35 @@ internal class PhpControlFlowEdgeProviderTest : PhpPsiRequiredTest("PhpFlowMetho val correctNumberOfControlFlowEdges = CorrectNumberOfIncomingAndOutgoingEdges( incoming = mapOf( "straightWriteMethod" to mapOf( - Vertex("VariableImpl: a", Pair(9, 9)) to 1, - Vertex("VariableImpl: b", Pair(10, 9)) to 1, - Vertex("VariableImpl: c", Pair(11, 9)) to 1 + Vertex("VariableImpl: a", Pair(2, 8)) to 1, + Vertex("VariableImpl: b", Pair(3, 8)) to 1, + Vertex("VariableImpl: c", Pair(4, 8)) to 1 ), "ifMethod" to mapOf( - Vertex("BinaryExpressionImpl: \$a > 1", Pair(26, 13)) to 1, - Vertex("VariableImpl: b", Pair(27, 13)) to 1, - Vertex("Statement", Pair(33, 9)) to 3 + Vertex("BinaryExpressionImpl: \$a > 1", Pair(3, 12)) to 1, + Vertex("VariableImpl: b", Pair(4, 12)) to 1, + Vertex("Statement", Pair(10, 8)) to 3 ), "forMethod" to mapOf( - Vertex("VariableImpl: i", Pair(57, 14)) to 1, - Vertex("VariableImpl: i", Pair(57, 22)) to 2, - Vertex("Break", Pair(59, 17)) to 1 + Vertex("VariableImpl: i", Pair(2, 13)) to 1, + Vertex("VariableImpl: i", Pair(2, 21)) to 2, + Vertex("Break", Pair(4, 16)) to 1 ) ), outgoing = mapOf( "straightWriteMethod" to mapOf( - Vertex("MethodImpl: straightWriteMethod", Pair(7, 21)) to 1, - Vertex("VariableImpl: a", Pair(9, 9)) to 1, - Vertex("VariableImpl: b", Pair(10, 9)) to 1 + Vertex("MethodImpl: straightWriteMethod", Pair(0, 20)) to 1, + Vertex("VariableImpl: a", Pair(2, 8)) to 1, + Vertex("VariableImpl: b", Pair(3, 8)) to 1 ), "ifMethod" to mapOf( - Vertex("BinaryExpressionImpl: \$a > 1", Pair(26, 13)) to 2, - Vertex("BinaryExpressionImpl: \$a < 0", Pair(28, 20)) to 2, - Vertex("VariableImpl: c", Pair(29, 13)) to 1 + Vertex("BinaryExpressionImpl: \$a > 1", Pair(3, 12)) to 2, + Vertex("BinaryExpressionImpl: \$a < 0", Pair(5, 19)) to 2, + Vertex("VariableImpl: c", Pair(6, 12)) to 1 ), "forMethod" to mapOf( - Vertex("VariableImpl: i", Pair(57, 14)) to 1, - Vertex("BinaryExpressionImpl: \$i == 1", Pair(58, 17)) to 2 + Vertex("VariableImpl: i", Pair(2, 13)) to 1, + Vertex("BinaryExpressionImpl: \$i == 1", Pair(3, 16)) to 2 ) ) ) @@ -111,28 +106,28 @@ internal class PhpControlFlowEdgeProviderTest : PhpPsiRequiredTest("PhpFlowMetho incoming = mapOf( "straightWriteMethod" to mapOf(), "ifMethod" to mapOf( - Vertex("If", Pair(26, 9)) to 1, // $a = 1 -> if ($a > 1)... - Vertex("VariableImpl: a", Pair(26, 13)) to 1, // if ($a > 1)... -> $a - Vertex("If", Pair(28, 16)) to 1, // if ($a > 1)... -> else if ($a < 0)... - Vertex("VariableImpl: a", Pair(28, 20)) to 1 // else if ($a < 0)... -> $a + Vertex("If", Pair(3, 8)) to 1, // $a = 1 -> if ($a > 1)... + Vertex("VariableImpl: a", Pair(3, 12)) to 1, // if ($a > 1)... -> $a + Vertex("If", Pair(5, 15)) to 1, // if ($a > 1)... -> else if ($a < 0)... + Vertex("VariableImpl: a", Pair(5, 19)) to 1 // else if ($a < 0)... -> $a ), "forMethod" to mapOf( - Vertex("For", Pair(57, 9)) to 3, // 1. forMethod -> for () 2. $i -> for() 3. break -> for() - Vertex("VariableImpl: i", Pair(57, 14)) to 1, // for () -> $i = 0 - Vertex("BinaryExpressionImpl: \$i < 2", Pair(57, 22)) to 1 // for () -> $i < 2 + Vertex("For", Pair(2, 8)) to 3, // 1. forMethod -> for () 2. $i -> for() 3. break -> for() + Vertex("VariableImpl: i", Pair(2, 13)) to 1, // for () -> $i = 0 + Vertex("BinaryExpressionImpl: \$i < 2", Pair(2, 21)) to 1 // for () -> $i < 2 ) ), outgoing = mapOf( "straightWriteMethod" to mapOf(), "ifMethod" to mapOf( - Vertex("VariableImpl: a", Pair(25, 9)) to 1, // $a = 1 -> if ($a > 1)... - Vertex("If", Pair(26, 9)) to 1 // if ($a > 1)... -> $a + Vertex("VariableImpl: a", Pair(2, 8)) to 1, // $a = 1 -> if ($a > 1)... + Vertex("If", Pair(3, 8)) to 1 // if ($a > 1)... -> $a ), "forMethod" to mapOf( - Vertex("MethodImpl: forMethod", Pair(55, 21)) to 1, // forMethod -> for() - Vertex("For", Pair(57, 9)) to 2, // 1. for() -> $i = 0 2. for() -> $i < 2 - Vertex("VariableImpl: i", Pair(57, 22)) to 1, // $i -> $i < 2 - Vertex("Break", Pair(59, 17)) to 1 // break -> for() + Vertex("MethodImpl: forMethod", Pair(0, 20)) to 1, // forMethod -> for() + Vertex("For", Pair(2, 8)) to 2, // 1. for() -> $i = 0 2. for() -> $i < 2 + Vertex("VariableImpl: i", Pair(2, 21)) to 1, // $i -> $i < 2 + Vertex("Break", Pair(4, 16)) to 1 // break -> for() ) ) ) From 47e13d11de066eab2d87dbd9187c353b2badcc43 Mon Sep 17 00:00:00 2001 From: max-martynov Date: Thu, 26 Jan 2023 17:19:11 +0100 Subject: [PATCH 3/3] Simplify containsAll --- .../test/kotlin/psi/graphs/BaseGraphTest.kt | 10 ++---- .../php/PhpControlFlowEdgeProviderTest.kt | 32 +++++++++---------- 2 files changed, 19 insertions(+), 23 deletions(-) diff --git a/psiminer-core/src/test/kotlin/psi/graphs/BaseGraphTest.kt b/psiminer-core/src/test/kotlin/psi/graphs/BaseGraphTest.kt index a634368..b916cd1 100644 --- a/psiminer-core/src/test/kotlin/psi/graphs/BaseGraphTest.kt +++ b/psiminer-core/src/test/kotlin/psi/graphs/BaseGraphTest.kt @@ -24,13 +24,6 @@ abstract class BaseGraphTest(private val psiSourceFile: File) : BasePsiRequiredT .mapKeys { (psiElement, _) -> Vertex(psiElement.toString(), getPositionInFunction(psiElement)) } .mapValues { (_, fromVertices) -> fromVertices.size } - fun Map.containsAll(other: Map?): Boolean { - if (other == null) { - return false - } - return this.entries.containsAll(other.entries) - } - abstract fun PsiElement.methodRoot(): PsiElement private fun Document.getLineNumber(psiElement: PsiElement) = @@ -71,6 +64,9 @@ abstract class BaseGraphTest(private val psiSourceFile: File) : BasePsiRequiredT class MethodRootNotFoundException(psiElementText: String) : RuntimeException("Can't find method root for $psiElementText") + + class CorrectValueNotProvidedException(methodName: String, edgeType: String) : + RuntimeException("No correct number of $edgeType edges for method $methodName provided") } open class PhpGraphTest(source: String) : BaseGraphTest(dataFolder.resolve("$source.$ext")) { diff --git a/psiminer-core/src/test/kotlin/psi/graphs/edgeProviders/php/PhpControlFlowEdgeProviderTest.kt b/psiminer-core/src/test/kotlin/psi/graphs/edgeProviders/php/PhpControlFlowEdgeProviderTest.kt index 5d9e222..9c9d2d8 100644 --- a/psiminer-core/src/test/kotlin/psi/graphs/edgeProviders/php/PhpControlFlowEdgeProviderTest.kt +++ b/psiminer-core/src/test/kotlin/psi/graphs/edgeProviders/php/PhpControlFlowEdgeProviderTest.kt @@ -23,15 +23,15 @@ internal class PhpControlFlowEdgeProviderTest : PhpGraphTest("PhpFlowMethods") { val codeGraph = graphMiner.mine(psiRoot) val controlFlowEdges = codeGraph.edges.withType(EdgeType.ControlFlow).flatMap { (_, edges) -> edges.forward() } - assertTrue( - countIncomingEdges(controlFlowEdges).containsAll( - correctNumberOfControlFlowEdges.incoming[methodName] - ) + assertContainsElements( + countIncomingEdges(controlFlowEdges).entries, + correctNumberOfControlFlowEdges.incoming[methodName]?.entries + ?: throw CorrectValueNotProvidedException(methodName, "control flow") ) - assertTrue( - countOutgoingEdges(controlFlowEdges).containsAll( - correctNumberOfControlFlowEdges.outgoing[methodName] - ) + assertContainsElements( + countOutgoingEdges(controlFlowEdges).entries, + correctNumberOfControlFlowEdges.outgoing[methodName]?.entries + ?: throw CorrectValueNotProvidedException(methodName, "control flow") ) } } @@ -51,15 +51,15 @@ internal class PhpControlFlowEdgeProviderTest : PhpGraphTest("PhpFlowMethods") { val codeGraph = graphMiner.mine(psiRoot) val controlFlowEdges = codeGraph.edges.withType(EdgeType.ControlElement).flatMap { (_, edges) -> edges.forward() } - assertTrue( - countIncomingEdges(controlFlowEdges).containsAll( - correctNumberOfControlElementEdges.incoming[methodName] - ) + assertContainsElements( + countIncomingEdges(controlFlowEdges).entries, + correctNumberOfControlElementEdges.incoming[methodName]?.entries + ?: throw CorrectValueNotProvidedException(methodName, "control element") ) - assertTrue( - countOutgoingEdges(controlFlowEdges).containsAll( - correctNumberOfControlElementEdges.outgoing[methodName] - ) + assertContainsElements( + countOutgoingEdges(controlFlowEdges).entries, + correctNumberOfControlElementEdges.outgoing[methodName]?.entries + ?: throw CorrectValueNotProvidedException(methodName, "control element") ) } }