forked from phoedos/pmd
Allow to write tests in Kotlin
This commit is contained in:
@ -39,6 +39,11 @@
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<artifactId>kotlin-maven-plugin</artifactId>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-antrun-plugin</artifactId>
|
||||
|
@ -1,89 +0,0 @@
|
||||
/**
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.java.ast;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import net.sourceforge.pmd.lang.ast.Node;
|
||||
import net.sourceforge.pmd.lang.java.ParserTstUtil;
|
||||
|
||||
public class Java11Test {
|
||||
private static String loadSource(String name) {
|
||||
try {
|
||||
return IOUtils.toString(Java10Test.class.getResourceAsStream("jdkversiontests/java11/" + name),
|
||||
StandardCharsets.UTF_8);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLocalVariableSyntaxForLambdaParametersWithJava10() {
|
||||
ASTCompilationUnit compilationUnit = ParserTstUtil.parseAndTypeResolveJava("10",
|
||||
loadSource("LocalVariableSyntaxForLambdaParameters.java"));
|
||||
|
||||
List<ASTLambdaExpression> lambdas = compilationUnit.findDescendantsOfType(ASTLambdaExpression.class);
|
||||
Assert.assertEquals(4, lambdas.size());
|
||||
|
||||
// (var x) -> String.valueOf(x);
|
||||
List<ASTFormalParameter> formalParameters = lambdas.get(0).findDescendantsOfType(ASTFormalParameter.class);
|
||||
Assert.assertEquals(1, formalParameters.size());
|
||||
ASTType type = formalParameters.get(0).getFirstChildOfType(ASTType.class);
|
||||
assertEquals("var", type.getTypeImage());
|
||||
assertEquals(1, type.jjtGetNumChildren());
|
||||
ASTReferenceType referenceType = type.getFirstChildOfType(ASTReferenceType.class);
|
||||
assertNotNull(referenceType);
|
||||
assertEquals(1, referenceType.jjtGetNumChildren());
|
||||
ASTClassOrInterfaceType classType = referenceType.getFirstChildOfType(ASTClassOrInterfaceType.class);
|
||||
assertNotNull(classType);
|
||||
assertEquals("var", classType.getImage());
|
||||
|
||||
// (var x, var y) -> x + y;
|
||||
formalParameters = lambdas.get(1).findDescendantsOfType(ASTFormalParameter.class);
|
||||
Assert.assertEquals(2, formalParameters.size());
|
||||
type = formalParameters.get(0).getFirstChildOfType(ASTType.class);
|
||||
assertEquals("var", type.getTypeImage());
|
||||
assertEquals(1, type.jjtGetNumChildren());
|
||||
referenceType = type.getFirstChildOfType(ASTReferenceType.class);
|
||||
assertNotNull(referenceType);
|
||||
assertEquals(1, referenceType.jjtGetNumChildren());
|
||||
classType = referenceType.getFirstChildOfType(ASTClassOrInterfaceType.class);
|
||||
assertNotNull(classType);
|
||||
assertEquals("var", classType.getImage());
|
||||
type = formalParameters.get(1).getFirstChildOfType(ASTType.class);
|
||||
assertEquals("var", type.getTypeImage());
|
||||
assertEquals(1, type.jjtGetNumChildren());
|
||||
|
||||
// (@Nonnull var x) -> String.valueOf(x);
|
||||
formalParameters = lambdas.get(2).findDescendantsOfType(ASTFormalParameter.class);
|
||||
Assert.assertEquals(1, formalParameters.size());
|
||||
Node firstChild = formalParameters.get(0).jjtGetChild(0);
|
||||
Assert.assertTrue(firstChild instanceof ASTAnnotation);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLocalVariableSyntaxForLambdaParametersWithJava11() {
|
||||
ASTCompilationUnit compilationUnit = ParserTstUtil.parseAndTypeResolveJava("11",
|
||||
loadSource("LocalVariableSyntaxForLambdaParameters.java"));
|
||||
|
||||
List<ASTLambdaExpression> lambdas = compilationUnit.findDescendantsOfType(ASTLambdaExpression.class);
|
||||
Assert.assertEquals(4, lambdas.size());
|
||||
|
||||
// (var x) -> String.valueOf(x);
|
||||
List<ASTFormalParameter> formalParameters = lambdas.get(0).findDescendantsOfType(ASTFormalParameter.class);
|
||||
Assert.assertEquals(1, formalParameters.size());
|
||||
Assert.assertNull(formalParameters.get(0).getTypeNode());
|
||||
Assert.assertTrue(formalParameters.get(0).isTypeInferred());
|
||||
}
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
import io.kotlintest.should
|
||||
import io.kotlintest.shouldBe
|
||||
import net.sourceforge.pmd.lang.ast.test.matchNode
|
||||
import net.sourceforge.pmd.lang.java.ast.*
|
||||
|
||||
import org.junit.Test
|
||||
|
||||
class Java11Test {
|
||||
|
||||
|
||||
@Test
|
||||
fun testLocalVariableSyntaxForLambdaParametersWithJava10() {
|
||||
|
||||
val lambdas = listOf(
|
||||
"(var x) -> String.valueOf(x)",
|
||||
"(var x, var y) -> x + y",
|
||||
"(@Nonnull var x) -> String.valueOf(x)"
|
||||
).map { parseExpression<ASTLambdaExpression>(it, javaVersion = "10") }
|
||||
|
||||
// (var x) -> String.valueOf(x)
|
||||
lambdas[0] should matchNode<ASTLambdaExpression> {
|
||||
child<ASTFormalParameters> {
|
||||
child<ASTFormalParameter> {
|
||||
child<ASTType> {
|
||||
it.typeImage shouldBe "var"
|
||||
|
||||
child<ASTReferenceType> {
|
||||
child<ASTClassOrInterfaceType> {
|
||||
it.image shouldBe "var"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
child<ASTVariableDeclaratorId> { }
|
||||
}
|
||||
}
|
||||
|
||||
unspecifiedChild()
|
||||
}
|
||||
|
||||
// (var x, var y) -> x + y
|
||||
lambdas[1] should matchNode<ASTLambdaExpression> {
|
||||
child<ASTFormalParameters> {
|
||||
child<ASTFormalParameter> {
|
||||
child<ASTType> {
|
||||
it.typeImage shouldBe "var"
|
||||
|
||||
child<ASTReferenceType> {
|
||||
child<ASTClassOrInterfaceType> {
|
||||
it.image shouldBe "var"
|
||||
}
|
||||
}
|
||||
}
|
||||
child<ASTVariableDeclaratorId> { }
|
||||
}
|
||||
|
||||
child<ASTFormalParameter> {
|
||||
child<ASTType> {
|
||||
it.typeImage shouldBe "var"
|
||||
|
||||
child<ASTReferenceType> {
|
||||
child<ASTClassOrInterfaceType> {
|
||||
it.image shouldBe "var"
|
||||
}
|
||||
}
|
||||
}
|
||||
child<ASTVariableDeclaratorId> { }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
unspecifiedChild()
|
||||
}
|
||||
|
||||
// (@Nonnull var x) -> String.valueOf(x)
|
||||
lambdas[2] should matchNode<ASTLambdaExpression> {
|
||||
child<ASTFormalParameters> {
|
||||
child<ASTFormalParameter> {
|
||||
child<ASTAnnotation>(ignoreChildren = true) {}
|
||||
unspecifiedChildren(2)
|
||||
}
|
||||
}
|
||||
unspecifiedChild()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLocalVariableSyntaxForLambdaParametersWithJava11() {
|
||||
|
||||
val lambda: ASTLambdaExpression = parseExpression("(var x) -> String.valueOf(x)", javaVersion = "11")
|
||||
|
||||
lambda should matchNode<ASTLambdaExpression> {
|
||||
child<ASTFormalParameters> {
|
||||
child<ASTFormalParameter> {
|
||||
it.isTypeInferred shouldBe true
|
||||
child<ASTVariableDeclaratorId> { }
|
||||
}
|
||||
}
|
||||
|
||||
unspecifiedChild()
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package net.sourceforge.pmd.lang.java.ast
|
||||
|
||||
import net.sourceforge.pmd.lang.ast.Node
|
||||
import net.sourceforge.pmd.lang.java.ParserTstUtil
|
||||
|
||||
|
||||
const val defaultJavaVersion = "11"
|
||||
|
||||
inline fun <reified N : Node> parseExpression(expr: String, javaVersion: String = defaultJavaVersion): N =
|
||||
parseAstExpression(expr, javaVersion).getFirstDescendantOfType(N::class.java)
|
||||
|
||||
fun parseAstExpression(expr: String, javaVersion: String = defaultJavaVersion): ASTExpression {
|
||||
|
||||
val source = """
|
||||
class Foo {
|
||||
{
|
||||
Object o = $expr;
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
val acu = ParserTstUtil.parseAndTypeResolveJava(javaVersion, source)
|
||||
|
||||
return acu.getFirstDescendantOfType(ASTVariableInitializer::class.java).jjtGetChild(0) as ASTExpression
|
||||
}
|
||||
|
||||
|
||||
inline fun <reified N : Node> parseStatement(stmt: String, javaVersion: String = defaultJavaVersion): N =
|
||||
parseStatement(stmt, javaVersion).getFirstChildOfType(N::class.java)
|
||||
|
||||
|
||||
fun parseStatement(statement: String, javaVersion: String = defaultJavaVersion): ASTBlockStatement {
|
||||
|
||||
// place the param in a statement parsing context
|
||||
val source = """
|
||||
class Foo {
|
||||
{
|
||||
$statement
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
val root = ParserTstUtil.parseAndTypeResolveJava(javaVersion, source)
|
||||
|
||||
return root.getFirstDescendantOfType(ASTBlockStatement::class.java)
|
||||
}
|
||||
|
||||
// also need e.g. parseDeclaration
|
@ -38,5 +38,68 @@
|
||||
<version>1.10.19</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-stdlib</artifactId>
|
||||
<version>${kotlin.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-stdlib-jdk8</artifactId>
|
||||
<version>${kotlin.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-test-junit</artifactId>
|
||||
<version>${kotlin.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.kotlintest</groupId>
|
||||
<artifactId>kotlintest-runner-junit5</artifactId>
|
||||
<version>3.1.8</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Use pmd-java for tests -->
|
||||
<dependency>
|
||||
<groupId>net.sourceforge.pmd</groupId>
|
||||
<artifactId>pmd-java</artifactId>
|
||||
<version>6.6.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<!-- The kotlin plugin has to run before the java plugin-->
|
||||
<plugin>
|
||||
<artifactId>kotlin-maven-plugin</artifactId>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<version>${kotlin.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>kotlin-compile</id>
|
||||
<goals>
|
||||
<goal>compile</goal>
|
||||
</goals>
|
||||
<phase>process-sources</phase>
|
||||
<configuration>
|
||||
<sourceDirs>
|
||||
<sourceDir>${project.basedir}/src/main/kotlin</sourceDir>
|
||||
<sourceDir>${project.basedir}/src/main/java</sourceDir>
|
||||
</sourceDirs>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
@ -0,0 +1,222 @@
|
||||
package net.sourceforge.pmd.lang.ast.test
|
||||
|
||||
import arrow.legacy.disjunctionTry
|
||||
import io.kotlintest.Matcher
|
||||
import io.kotlintest.Result
|
||||
import net.sourceforge.pmd.lang.ast.Node
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
|
||||
/**
|
||||
* Wraps a node, providing easy access to [it]. Additional matching
|
||||
* methods are provided to match children.
|
||||
*
|
||||
* @property it Wrapped node
|
||||
* @param <N> Type of the node
|
||||
*/
|
||||
class NWrapper<N : Node> private constructor(val it: N, private val childMatchersAreIgnored: Boolean) {
|
||||
|
||||
/** Index to which the next child matcher will apply. */
|
||||
private var nextChildMatcherIdx = 0
|
||||
|
||||
private fun shiftChild(num: Int = 1): Node {
|
||||
|
||||
checkChildExists(nextChildMatcherIdx)
|
||||
|
||||
val ret = it.jjtGetChild(nextChildMatcherIdx)
|
||||
|
||||
nextChildMatcherIdx += num
|
||||
return ret
|
||||
}
|
||||
|
||||
|
||||
private fun checkChildExists(childIdx: Int) =
|
||||
assertTrue("Node has fewer children than expected, child #$childIdx doesn't exist") {
|
||||
childIdx < it.numChildren
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Specify that the next [num] children will only be tested for existence,
|
||||
* but not for type, or anything else.
|
||||
*/
|
||||
fun unspecifiedChildren(num: Int) {
|
||||
shiftChild(num)
|
||||
// Checks that the last child mentioned exists
|
||||
checkChildExists(nextChildMatcherIdx - 1)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Specify that the next child will only be tested for existence,
|
||||
* but not for type, or anything else.
|
||||
*/
|
||||
fun unspecifiedChild() = unspecifiedChildren(1)
|
||||
|
||||
|
||||
/**
|
||||
* Specify that the next child will be tested against the assertions
|
||||
* defined by the lambda.
|
||||
*
|
||||
* This method asserts that the child exists, and that it is of the
|
||||
* required type [M]. The lambda is then executed on it. Subsequent
|
||||
* calls to this method at the same tree level will test the next
|
||||
* children.
|
||||
*
|
||||
* @param ignoreChildren If true, calls to [child] in the [nodeSpec] are ignored.
|
||||
* The number of children of the child is not asserted either.
|
||||
* @param nodeSpec Sequence of assertions to carry out on the child node
|
||||
*
|
||||
* @param M Expected type of the child
|
||||
*/
|
||||
inline fun <reified M : Node> child(ignoreChildren: Boolean = false, noinline nodeSpec: NWrapper<M>.() -> Unit) =
|
||||
childImpl(ignoreChildren, M::class.java, nodeSpec)
|
||||
|
||||
|
||||
@PublishedApi
|
||||
internal fun <M : Node> childImpl(ignoreChildren: Boolean, childType: Class<M>, nodeSpec: NWrapper<M>.() -> Unit) {
|
||||
if (!childMatchersAreIgnored) executeWrapper(childType, shiftChild(), ignoreChildren, nodeSpec)
|
||||
}
|
||||
|
||||
|
||||
override fun toString(): String {
|
||||
return "NWrapper<${it.xPathNodeName}>"
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
|
||||
internal val Node.numChildren: Int
|
||||
get() = this.jjtGetNumChildren()
|
||||
|
||||
|
||||
private val <M : Node> Class<M>.nodeName
|
||||
get() =
|
||||
if (simpleName.startsWith("AST", ignoreCase = false))
|
||||
simpleName.substring("AST".length)
|
||||
else simpleName
|
||||
|
||||
/**
|
||||
* Execute wrapper assertions on a node.
|
||||
*
|
||||
* @param childType Expected type of [toWrap]
|
||||
* @param toWrap Node on which to execute the assertions
|
||||
* @param ignoreChildrenMatchers Ignore the children matchers in [spec]
|
||||
* @param spec Assertions to carry out on [toWrap]
|
||||
*
|
||||
* @throws AssertionError If some assertions fail
|
||||
*/
|
||||
@PublishedApi
|
||||
internal fun <M : Node> executeWrapper(childType: Class<M>, toWrap: Node, ignoreChildrenMatchers: Boolean, spec: NWrapper<M>.() -> Unit) {
|
||||
|
||||
assertTrue("Expected node to have type ${childType.nodeName}, actual ${toWrap.javaClass.nodeName}") {
|
||||
childType.isInstance(toWrap)
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val wrapper = NWrapper(toWrap as M, ignoreChildrenMatchers)
|
||||
|
||||
wrapper.spec()
|
||||
|
||||
assertFalse("<${childType.nodeName}>: Wrong number of children, expected ${wrapper.nextChildMatcherIdx}, actual ${wrapper.it.numChildren}") {
|
||||
!ignoreChildrenMatchers && wrapper.nextChildMatcherIdx != wrapper.it.numChildren
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Matcher for a node, using [NWrapper] to specify a subtree against which
|
||||
* the tested node will be tested.
|
||||
*
|
||||
* Use it with [io.kotlintest.should], e.g. `nodeshould matchNode<ASTExpression> {}`.
|
||||
*
|
||||
* @param N Expected type of the node
|
||||
*
|
||||
* @param ignoreChildren If true, calls to [NWrapper.child] in the [nodeSpec] are ignored.
|
||||
* The number of children of the child is not asserted either.
|
||||
*
|
||||
* @param nodeSpec Sequence of assertions to carry out on the node, which can be referred to by [NWrapper.it].
|
||||
* Assertions may onsist of [NWrapper.child] calls, which perform the same type of node
|
||||
* matching on a child of the tested node.
|
||||
*
|
||||
* @return A matcher for AST nodes, suitable for use by [io.kotlintest.should].
|
||||
*
|
||||
* ### Samples
|
||||
*
|
||||
* node should matchNode<ASTStatement> {
|
||||
*
|
||||
* // nesting matchers allow to specify a whole subtree
|
||||
* child<ASTForStatement> {
|
||||
*
|
||||
* // This would fail if the first child of the ForStatement wasn't a ForInit
|
||||
* child<ASTForInit> {
|
||||
* child<ASTLocalVariableDeclaration> {
|
||||
*
|
||||
* // If the parameter ignoreChildren is set to true, the number of children is not asserted
|
||||
* // Calls to "child" in the block are completely ignored
|
||||
* // The only checks carried out here are the type test and the assertions of the block
|
||||
* child<ASTType>(ignoreChildren = true) {
|
||||
*
|
||||
* // In a "child" block, the tested node can be referred to as "it"
|
||||
* // Here, its static type is ASTType, so we can inspect properties
|
||||
* // of the node and make assertions
|
||||
*
|
||||
* it.typeImage shouldBe "int"
|
||||
* it.type shouldNotBe null
|
||||
* }
|
||||
*
|
||||
* // We don't care about that node, we only care that there is "some" node
|
||||
* unspecifiedChild()
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* // The subtree is ignored, but we check a ForUpdate is present at this child position
|
||||
* child<ASTForUpdate>(ignoreChildren = true) {}
|
||||
*
|
||||
* // Here, ignoreChildren is not specified and takes its default value of false.
|
||||
* // The lambda has no "child" calls and the node will be asserted to have no children
|
||||
* child<ASTBlock> {}
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
inline fun <reified N : Node> matchNode(ignoreChildren: Boolean = false, noinline nodeSpec: NWrapper<N>.() -> Unit) = object : Matcher<Node> {
|
||||
override fun test(value: Node): Result {
|
||||
val matchRes = disjunctionTry {
|
||||
NWrapper.executeWrapper(N::class.java, value, ignoreChildren, nodeSpec)
|
||||
}
|
||||
|
||||
val didMatch = matchRes.isRight()
|
||||
|
||||
|
||||
// Output when the node should have matched and did not
|
||||
//
|
||||
val failureMessage: String = matchRes.fold({
|
||||
// Here the node failed
|
||||
it.message ?: "The node did not match the pattern (no cause specified)"
|
||||
}, {
|
||||
// The node matched, which was expected
|
||||
"SHOULD NOT BE OUTPUT"
|
||||
})
|
||||
|
||||
val negatedMessage = matchRes.fold({
|
||||
// the node didn't match, which was expected
|
||||
"SHOULD NOT BE OUTPUT"
|
||||
}, {
|
||||
"The node should not have matched this pattern"
|
||||
})
|
||||
|
||||
|
||||
return Result(didMatch, failureMessage, negatedMessage)
|
||||
}
|
||||
}
|
||||
|
||||
// This one preserves the stack trace
|
||||
// It's still hard to read because of the inlines, and possibly only IntelliJ knows how to do that
|
||||
// I'll try to get kotlintest to preserve the original stack trace
|
||||
|
||||
//inline fun <reified M : Node> Node.shouldMatchNode(ignoreChildren: Boolean = false, noinline nodeSpec: NWrapper<M>.() -> Unit) {
|
||||
// NWrapper.executeWrapper(M::class.java, this, ignoreChildren, nodeSpec)
|
||||
//}
|
@ -0,0 +1,107 @@
|
||||
package net.sourceforge.pmd.lang.ast.test
|
||||
|
||||
import io.kotlintest.should
|
||||
import io.kotlintest.shouldBe
|
||||
import io.kotlintest.specs.FunSpec
|
||||
import net.sourceforge.pmd.lang.java.ast.*
|
||||
|
||||
|
||||
class DslTest : FunSpec({
|
||||
|
||||
failureTest("Empty matcher spec should check the number of children",
|
||||
messageContains = setOf("Wrong", "number", "children", "expected 0", "actual 2")) {
|
||||
|
||||
parseStatement("int i = 0;") should matchNode<ASTLocalVariableDeclaration> {}
|
||||
}
|
||||
|
||||
test("Matcher with ignoreChildren should not check the number of children") {
|
||||
|
||||
parseStatement("int i = 0;") should matchNode<ASTLocalVariableDeclaration>(ignoreChildren = true) {}
|
||||
}
|
||||
|
||||
failureTest("Incorrect node type should cause failure",
|
||||
messageContains = setOf("Expression", "actual LocalVariableDeclaration")) {
|
||||
parseStatement("int i = 0;") should matchNode<ASTExpression>(ignoreChildren = true) {}
|
||||
}
|
||||
|
||||
failureTest("Specifying any child in a pattern should cause the number of children to be checked",
|
||||
messageContains = setOf("number", "children", "expected 1", "actual 2")) {
|
||||
|
||||
parseStatement("int i = 0;") should matchNode<ASTLocalVariableDeclaration> {
|
||||
child<ASTType>(ignoreChildren = true) {}
|
||||
// There's a VarDeclarator
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
test("Unspecified children should shift the next child matchers") {
|
||||
parseStatement("int i = 0;") should matchNode<ASTLocalVariableDeclaration> {
|
||||
unspecifiedChild()
|
||||
child<ASTVariableDeclarator>(ignoreChildren = true) {}
|
||||
}
|
||||
}
|
||||
|
||||
test("Unspecified children should count in total number of children") {
|
||||
parseStatement("int i = 0;") should matchNode<ASTLocalVariableDeclaration> {
|
||||
unspecifiedChildren(2)
|
||||
}
|
||||
}
|
||||
|
||||
failureTest("Unspecified children should be counted in the number of expected children",
|
||||
messageContains = setOf("#2 doesn't exist")) {
|
||||
|
||||
parseStatement("int i = 0;") should matchNode<ASTLocalVariableDeclaration> {
|
||||
unspecifiedChildren(3)
|
||||
}
|
||||
}
|
||||
|
||||
failureTest("Assertions are always executed in order",
|
||||
messageContains = setOf("PrimitiveType")) {
|
||||
|
||||
parseStatement("int[] i = 0;") should matchNode<ASTLocalVariableDeclaration> {
|
||||
|
||||
child<ASTType> {
|
||||
|
||||
// Here we check that the child type check fails before the assertion
|
||||
child<ASTPrimitiveType> {}
|
||||
|
||||
it.typeImage shouldBe "bratwurst"
|
||||
|
||||
}
|
||||
|
||||
unspecifiedChild()
|
||||
}
|
||||
}
|
||||
|
||||
failureTest("Assertions are always executed in order #2",
|
||||
messageContains = setOf("bratwurst")) {
|
||||
|
||||
parseStatement("int[] i = 0;") should matchNode<ASTLocalVariableDeclaration> {
|
||||
|
||||
child<ASTType> {
|
||||
|
||||
it.typeImage shouldBe "bratwurst"
|
||||
|
||||
child<ASTPrimitiveType> {}
|
||||
|
||||
}
|
||||
|
||||
unspecifiedChild()
|
||||
}
|
||||
}
|
||||
|
||||
failureTest("Leaf nodes should assert that they have no children",
|
||||
messageContains = setOf("number", "children", "expected 0")) {
|
||||
|
||||
parseStatement("int[] i = 0;") should matchNode<ASTLocalVariableDeclaration> {
|
||||
|
||||
child<ASTType> {} // This should fail
|
||||
unspecifiedChild()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
})
|
||||
|
||||
|
||||
|
@ -0,0 +1,41 @@
|
||||
package net.sourceforge.pmd.lang.ast.test
|
||||
|
||||
import net.sourceforge.pmd.lang.LanguageRegistry
|
||||
import net.sourceforge.pmd.lang.ast.Node
|
||||
import net.sourceforge.pmd.lang.java.JavaLanguageModule
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTBlockStatement
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit
|
||||
import java.io.StringReader
|
||||
|
||||
|
||||
// These could be used directly by the pmd-java test module
|
||||
|
||||
fun parseStatement(statement: String): Node {
|
||||
|
||||
// place the param in a statement parsing context
|
||||
val source = """
|
||||
class Foo {
|
||||
{
|
||||
$statement
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
val root = parseCompilationUnit(source)
|
||||
|
||||
return root.getFirstDescendantOfType(ASTBlockStatement::class.java).jjtGetChild(0)
|
||||
}
|
||||
|
||||
fun parseCompilationUnit(sourceCode: String): ASTCompilationUnit {
|
||||
|
||||
val languageVersionHandler = LanguageRegistry.getLanguage(JavaLanguageModule.NAME).defaultVersion.languageVersionHandler
|
||||
val rootNode = languageVersionHandler.getParser(languageVersionHandler.defaultParserOptions).parse(":test:", StringReader(sourceCode))
|
||||
languageVersionHandler.getQualifiedNameResolutionFacade(ClassLoader.getSystemClassLoader()).start(rootNode)
|
||||
languageVersionHandler.symbolFacade.start(rootNode)
|
||||
languageVersionHandler.dataFlowFacade.start(rootNode)
|
||||
languageVersionHandler.getTypeResolutionFacade(ClassLoader.getSystemClassLoader()).start(rootNode)
|
||||
languageVersionHandler.multifileFacade.start(rootNode)
|
||||
return rootNode as ASTCompilationUnit
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,30 @@
|
||||
package net.sourceforge.pmd.lang.ast.test
|
||||
|
||||
import io.kotlintest.matchers.string.shouldContainIgnoringCase
|
||||
import io.kotlintest.shouldThrow
|
||||
import io.kotlintest.specs.AbstractFunSpec
|
||||
|
||||
|
||||
// Improve on the KotlinTest DSL for our specific needs
|
||||
// a testing DSL testing a testing DSL!
|
||||
|
||||
|
||||
fun AbstractFunSpec.failureTest(testName: String,
|
||||
messageContains: Set<String> = emptySet(),
|
||||
param: io.kotlintest.TestContext.() -> kotlin.Unit) {
|
||||
|
||||
this.expectFailure<AssertionError>(testName, messageContains, param)
|
||||
}
|
||||
|
||||
inline fun <reified T : Throwable> AbstractFunSpec.expectFailure(testName: String,
|
||||
messageContains: Set<String> = emptySet(),
|
||||
noinline param: io.kotlintest.TestContext.() -> kotlin.Unit) {
|
||||
test(testName) {
|
||||
val exception = shouldThrow<T> {
|
||||
this.param() // this is the test context here
|
||||
}
|
||||
|
||||
for (substr in messageContains) exception.message.shouldContainIgnoringCase(substr)
|
||||
|
||||
}
|
||||
}
|
116
pom.xml
116
pom.xml
@ -260,6 +260,14 @@ Additionally it includes CPD, the copy-paste-detector. CPD finds duplicated code
|
||||
<maven.compiler.source>1.${java.version}</maven.compiler.source>
|
||||
<maven.compiler.target>1.${java.version}</maven.compiler.target>
|
||||
|
||||
<maven.compiler.test.source>1.8</maven.compiler.test.source>
|
||||
<maven.compiler.test.target>1.8</maven.compiler.test.target>
|
||||
|
||||
|
||||
<kotlin.compiler.jvmTarget>${maven.compiler.test.target}</kotlin.compiler.jvmTarget>
|
||||
<kotlin.version>1.2.61</kotlin.version>
|
||||
|
||||
|
||||
<javacc.version>5.0</javacc.version>
|
||||
<surefire.version>2.22.0</surefire.version>
|
||||
<checkstyle.version>3.0.0</checkstyle.version>
|
||||
@ -275,6 +283,7 @@ Additionally it includes CPD, the copy-paste-detector. CPD finds duplicated code
|
||||
<argLine>-Xmx512m -Dfile.encoding=${project.build.sourceEncoding}</argLine>
|
||||
|
||||
<pmd.build-tools.version>1.2</pmd.build-tools.version>
|
||||
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
@ -329,6 +338,30 @@ Additionally it includes CPD, the copy-paste-detector. CPD finds duplicated code
|
||||
<artifactId>maven-clean-plugin</artifactId>
|
||||
<version>3.1.0</version>
|
||||
</plugin>
|
||||
|
||||
<!-- Kotlin compiler for test-compile -->
|
||||
<!-- The kotlin plugin has to run before the java plugin-->
|
||||
<plugin>
|
||||
<artifactId>kotlin-maven-plugin</artifactId>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<version>${kotlin.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>kotlin-test-compile</id>
|
||||
<goals>
|
||||
<goal>test-compile</goal>
|
||||
</goals>
|
||||
<phase>process-test-sources</phase>
|
||||
<configuration>
|
||||
<sourceDirs>
|
||||
<sourceDir>${project.basedir}/src/test/kotlin</sourceDir>
|
||||
<sourceDir>${project.basedir}/src/test/java</sourceDir>
|
||||
</sourceDirs>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
@ -336,6 +369,36 @@ Additionally it includes CPD, the copy-paste-detector. CPD finds duplicated code
|
||||
<configuration>
|
||||
<release>${java.version}</release>
|
||||
</configuration>
|
||||
<executions>
|
||||
<!-- Replacing default-compile as it is treated specially by maven -->
|
||||
<execution>
|
||||
<id>default-compile</id>
|
||||
<phase>none</phase>
|
||||
</execution>
|
||||
<!-- Replacing default-testCompile as it is treated specially by maven -->
|
||||
<execution>
|
||||
<id>default-testCompile</id>
|
||||
<phase>none</phase>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>java-compile</id>
|
||||
<phase>compile</phase>
|
||||
<goals>
|
||||
<goal>compile</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>java-test-compile</id>
|
||||
<phase>test-compile</phase>
|
||||
<goals>
|
||||
<goal>testCompile</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<source>${maven.compiler.test.source}</source>
|
||||
<target>${maven.compiler.test.target}</target>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
@ -369,7 +432,27 @@ Additionally it includes CPD, the copy-paste-detector. CPD finds duplicated code
|
||||
<configuration>
|
||||
<forkMode>once</forkMode>
|
||||
<runOrder>alphabetical</runOrder>
|
||||
<testSourceDirectory>${project.basedir}/src/test/java</testSourceDirectory>
|
||||
<testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
|
||||
</configuration>
|
||||
<dependencies>
|
||||
<!-- Junit 5 = Platform + Jupiter (5) + Vintage (3 & 4)-->
|
||||
|
||||
<!-- Junit platform -->
|
||||
<!-- Needed to use kotlintest -->
|
||||
<dependency>
|
||||
<groupId>org.junit.platform</groupId>
|
||||
<artifactId>junit-platform-surefire-provider</artifactId>
|
||||
<version>1.2.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Junit 3 & 4 engine -->
|
||||
<dependency>
|
||||
<groupId>org.junit.vintage</groupId>
|
||||
<artifactId>junit-vintage-engine</artifactId>
|
||||
<version>5.3.0-M1</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
@ -853,6 +936,39 @@ Additionally it includes CPD, the copy-paste-detector. CPD finds duplicated code
|
||||
<artifactId>system-rules</artifactId>
|
||||
<version>1.8.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- TEST DEPENDENCIES -->
|
||||
|
||||
<!-- Kotlin -->
|
||||
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-stdlib</artifactId>
|
||||
<version>${kotlin.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-stdlib-jdk8</artifactId>
|
||||
<version>${kotlin.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-test-junit</artifactId>
|
||||
<version>${kotlin.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.kotlintest</groupId>
|
||||
<artifactId>kotlintest-runner-junit5</artifactId>
|
||||
<version>3.1.8</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
|
Reference in New Issue
Block a user