diff --git a/pmd-dart/pom.xml b/pmd-dart/pom.xml index 74e75d3dd1..c18d893086 100644 --- a/pmd-dart/pom.xml +++ b/pmd-dart/pom.xml @@ -54,5 +54,10 @@ pmd-test test + + net.sourceforge.pmd + pmd-lang-test + test + diff --git a/pmd-dart/src/test/java/net/sourceforge/pmd/cpd/DartTokenizerTest2.java b/pmd-dart/src/test/java/net/sourceforge/pmd/cpd/DartTokenizerTest2.java new file mode 100644 index 0000000000..e3db041ad1 --- /dev/null +++ b/pmd-dart/src/test/java/net/sourceforge/pmd/cpd/DartTokenizerTest2.java @@ -0,0 +1,52 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.cpd; + +import org.jetbrains.annotations.NotNull; +import org.junit.Test; + +import net.sourceforge.pmd.cpd.test.CpdTextComparisonTest; + +public class DartTokenizerTest2 extends CpdTextComparisonTest { + + public DartTokenizerTest2() { + super(".dart"); + } + + @NotNull + @Override + protected String getResourcePrefix() { + return ""; + } + + @Test + public void testComment() { + doTest("comment"); + } + + + @Test + public void testMultiline() { + doTest("string_multiline"); + } + + @Test + public void testStringWithBackslashes() { + doTest("string_with_backslashes"); + } + + @Test + public void testIncrement() { + doTest("increment"); + } + + + @NotNull + @Override + public Tokenizer newTokenizer() { + return new DartTokenizer(); + } + +} diff --git a/pmd-dart/src/test/resources/net/sourceforge/pmd/cpd/comment.txt b/pmd-dart/src/test/resources/net/sourceforge/pmd/cpd/comment.txt new file mode 100644 index 0000000000..6a7b4ca2c1 --- /dev/null +++ b/pmd-dart/src/test/resources/net/sourceforge/pmd/cpd/comment.txt @@ -0,0 +1,7 @@ + [Image] or [Truncated image[ Bcol Ecol +L3 + [var] 1 3 + [x] 5 5 + [=] 7 7 + [0] 9 9 + [EOF] -1 -1 diff --git a/pmd-dart/src/test/resources/net/sourceforge/pmd/cpd/increment.txt b/pmd-dart/src/test/resources/net/sourceforge/pmd/cpd/increment.txt new file mode 100644 index 0000000000..e2eaf416a6 --- /dev/null +++ b/pmd-dart/src/test/resources/net/sourceforge/pmd/cpd/increment.txt @@ -0,0 +1,207 @@ + [Image] or [Truncated image[ Bcol Ecol +L3 + [var] 1 3 + [x] 5 5 + [=] 7 7 + [0] 9 9 +L5 + [void] 1 4 + [increment1] 6 15 + [(] 16 16 + [)] 17 17 + [{] 19 19 + [x] 21 21 + [+=] 23 24 + [1] 26 26 + [}] 29 29 +L6 + [void] 1 4 + [increment2] 6 15 + [(] 16 16 + [)] 17 17 + [{] 19 19 + [x] 21 21 + [+=] 23 24 + [1] 26 26 + [}] 29 29 +L7 + [void] 1 4 + [increment3] 6 15 + [(] 16 16 + [)] 17 17 + [{] 19 19 + [x] 21 21 + [+=] 23 24 + [1] 26 26 + [}] 29 29 +L8 + [void] 1 4 + [increment4] 6 15 + [(] 16 16 + [)] 17 17 + [{] 19 19 + [x] 21 21 + [+=] 23 24 + [1] 26 26 + [}] 29 29 +L9 + [void] 1 4 + [increment5] 6 15 + [(] 16 16 + [)] 17 17 + [{] 19 19 + [x] 21 21 + [+=] 23 24 + [1] 26 26 + [}] 29 29 +L10 + [void] 1 4 + [increment6] 6 15 + [(] 16 16 + [)] 17 17 + [{] 19 19 + [x] 21 21 + [+=] 23 24 + [1] 26 26 + [}] 29 29 +L11 + [void] 1 4 + [increment7] 6 15 + [(] 16 16 + [)] 17 17 + [{] 19 19 + [x] 21 21 + [+=] 23 24 + [1] 26 26 + [}] 29 29 +L12 + [void] 1 4 + [increment8] 6 15 + [(] 16 16 + [)] 17 17 + [{] 19 19 + [x] 21 21 + [+=] 23 24 + [1] 26 26 + [}] 29 29 +L13 + [void] 1 4 + [increment9] 6 15 + [(] 16 16 + [)] 17 17 + [{] 19 19 + [x] 21 21 + [+=] 23 24 + [1] 26 26 + [}] 29 29 +L14 + [void] 1 4 + [increment10] 6 16 + [(] 17 17 + [)] 18 18 + [{] 20 20 + [x] 22 22 + [+=] 24 25 + [1] 27 27 + [}] 30 30 +L15 + [void] 1 4 + [increment11] 6 16 + [(] 17 17 + [)] 18 18 + [{] 20 20 + [x] 22 22 + [+=] 24 25 + [1] 27 27 + [}] 30 30 +L16 + [void] 1 4 + [increment12] 6 16 + [(] 17 17 + [)] 18 18 + [{] 20 20 + [x] 22 22 + [+=] 24 25 + [1] 27 27 + [}] 30 30 +L17 + [void] 1 4 + [increment13] 6 16 + [(] 17 17 + [)] 18 18 + [{] 20 20 + [x] 22 22 + [+=] 24 25 + [1] 27 27 + [}] 30 30 +L18 + [void] 1 4 + [increment14] 6 16 + [(] 17 17 + [)] 18 18 + [{] 20 20 + [x] 22 22 + [+=] 24 25 + [1] 27 27 + [}] 30 30 +L19 + [void] 1 4 + [increment15] 6 16 + [(] 17 17 + [)] 18 18 + [{] 20 20 + [x] 22 22 + [+=] 24 25 + [1] 27 27 + [}] 30 30 +L20 + [void] 1 4 + [increment16] 6 16 + [(] 17 17 + [)] 18 18 + [{] 20 20 + [x] 22 22 + [+=] 24 25 + [1] 27 27 + [}] 30 30 +L21 + [void] 1 4 + [increment17] 6 16 + [(] 17 17 + [)] 18 18 + [{] 20 20 + [x] 22 22 + [+=] 24 25 + [1] 27 27 + [}] 30 30 +L22 + [void] 1 4 + [increment18] 6 16 + [(] 17 17 + [)] 18 18 + [{] 20 20 + [x] 22 22 + [+=] 24 25 + [1] 27 27 + [}] 30 30 +L23 + [void] 1 4 + [increment19] 6 16 + [(] 17 17 + [)] 18 18 + [{] 20 20 + [x] 22 22 + [+=] 24 25 + [1] 27 27 + [}] 30 30 +L24 + [void] 1 4 + [increment20] 6 16 + [(] 17 17 + [)] 18 18 + [{] 20 20 + [x] 22 22 + [+=] 24 25 + [1] 27 27 + [}] 30 30 + [EOF] -1 -1 diff --git a/pmd-dart/src/test/resources/net/sourceforge/pmd/cpd/string_multiline.txt b/pmd-dart/src/test/resources/net/sourceforge/pmd/cpd/string_multiline.txt new file mode 100644 index 0000000000..5d6530a0c5 --- /dev/null +++ b/pmd-dart/src/test/resources/net/sourceforge/pmd/cpd/string_multiline.txt @@ -0,0 +1,18 @@ + [Image] or [Truncated image[ Bcol Ecol +L1 + [class] 1 5 + [MyClass] 7 13 + [{] 15 15 +L2 + [var] 5 7 + [s1] 9 10 + [=] 12 12 + ['''\nYou can create\nmulti-line st[ 14 69 +L7 + [var] 5 7 + [s2] 9 10 + [=] 12 12 + ["""This is also a\nmulti-line stri[ 14 52 +L9 + [}] 1 1 + [EOF] -1 -1 diff --git a/pmd-dart/src/test/resources/net/sourceforge/pmd/cpd/string_with_backslashes.txt b/pmd-dart/src/test/resources/net/sourceforge/pmd/cpd/string_with_backslashes.txt new file mode 100644 index 0000000000..fdaa1d6491 --- /dev/null +++ b/pmd-dart/src/test/resources/net/sourceforge/pmd/cpd/string_with_backslashes.txt @@ -0,0 +1,13 @@ + [Image] or [Truncated image[ Bcol Ecol +L1 + [class] 1 5 + [MyClass] 7 13 + [{] 15 15 +L2 + [final] 3 7 + [stringWithBackslashes] 9 29 + [=] 31 31 + ["Escaping\\ spaces\\ should work"] 33 63 +L3 + [}] 1 1 + [EOF] -1 -1 diff --git a/pmd-lang-test/src/main/kotlin/net/sourceforge/pmd/cpd/test/CpdTextComparisonTest.kt b/pmd-lang-test/src/main/kotlin/net/sourceforge/pmd/cpd/test/CpdTextComparisonTest.kt new file mode 100644 index 0000000000..cf321aa29c --- /dev/null +++ b/pmd-lang-test/src/main/kotlin/net/sourceforge/pmd/cpd/test/CpdTextComparisonTest.kt @@ -0,0 +1,112 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.cpd.test + +import net.sourceforge.pmd.cpd.SourceCode +import net.sourceforge.pmd.cpd.TokenEntry +import net.sourceforge.pmd.cpd.Tokenizer +import net.sourceforge.pmd.cpd.Tokens +import net.sourceforge.pmd.test.BaseTextComparisonTest +import org.apache.commons.lang3.StringUtils + +/** + * CPD test comparing a dump of a file against a saved baseline. + * Each token is printed on a separate line. + * + * @param extensionIncludingDot File extension for the language. + * Baseline files are saved in txt files. + */ +abstract class CpdTextComparisonTest( + override val extensionIncludingDot: String +) : BaseTextComparisonTest() { + + abstract fun newTokenizer(): Tokenizer + + override val resourceLoader: Class<*> + get() = javaClass + + override val resourcePrefix: String + get() = "cpdData" + + override fun transformTextContent(sourceText: String): String { + val sourceCode = SourceCode(SourceCode.StringCodeLoader(sourceText)) + val tokens = Tokens().also { newTokenizer().tokenize(sourceCode, it) } + + return buildString { format(tokens) } + } + + + private fun StringBuilder.format(tokens: Tokens) { + appendHeader().appendln() + + var curLine = -1 + + for (token in tokens.iterator()) { + + if (curLine != token.beginLine && token !== TokenEntry.EOF) { + curLine = token.beginLine + append('L').append(curLine).appendln() + } + + formatLine(token).appendln() + } + } + + private fun StringBuilder.appendHeader() = + formatLine( + escapedImage = "[Image] or [Truncated image[", + bcol = "Bcol", + ecol = "Ecol" + ) + + + private fun StringBuilder.formatLine(token: TokenEntry) = + formatLine( + escapedImage = escapeImage(token.toString()), + bcol = token.beginColumn, + ecol = token.endColumn + ) + + + private fun StringBuilder.formatLine(escapedImage: String, bcol: Any, ecol: Any): StringBuilder { + var colStart = length + colStart = append(Indent).append(escapedImage).padCol(colStart, Col0Width) + colStart = append(Indent).append(bcol).padCol(colStart, Col1Width) + return append(ecol) + } + + private fun StringBuilder.padCol(colStart: Int, colWidth: Int): Int { + for (i in 1..(colStart + colWidth - this.length)) + append(' ') + + return length + } + + + private fun escapeImage(str: String): String { + val escaped = str + .replace("\\", "\\\\") // escape backslashes + .replace(Regex("\\R"), "\\\\n") // escape newlines (normalizing) + .replace(Regex("[]\\[]"), "\\\\$0") // escape [] + + var truncated = StringUtils.truncate(escaped, ImageSize) + + if (truncated.endsWith('\\') && !truncated.endsWith("\\\\")) + truncated = truncated.substring(0, truncated.length - 1) // cut inside an escape + + return if (truncated.length < escaped.length) + "[$truncated[" + else + "[$truncated]" + + } + + private companion object { + const val Indent = " " + const val Col0Width = 40 + const val Col1Width = 10 + Indent.length + val ImageSize = Col0Width - Indent.length - 2 // -2 is for the "[]" + } +} \ No newline at end of file diff --git a/pmd-lang-test/src/main/kotlin/net/sourceforge/pmd/lang/ast/test/BaseTreeDumpTest.kt b/pmd-lang-test/src/main/kotlin/net/sourceforge/pmd/lang/ast/test/BaseTreeDumpTest.kt index 4fab7ba383..2b4adb99e6 100644 --- a/pmd-lang-test/src/main/kotlin/net/sourceforge/pmd/lang/ast/test/BaseTreeDumpTest.kt +++ b/pmd-lang-test/src/main/kotlin/net/sourceforge/pmd/lang/ast/test/BaseTreeDumpTest.kt @@ -4,71 +4,31 @@ package net.sourceforge.pmd.lang.ast.test +import net.sourceforge.pmd.test.BaseTextComparisonTest import net.sourceforge.pmd.util.treeexport.TreeRenderer -import java.nio.file.Path -import java.nio.file.Paths -import kotlin.test.assertEquals /** - * Compare a dump of a file against a saved baseline. + * Compare a dump of an AST against a saved baseline. * * @param printer The node printer used to dump the trees - * @param extension Extension that the unparsed source file is supposed to have + * @param extensionIncludingDot Extension that the unparsed source file is supposed to have */ abstract class BaseTreeDumpTest( - val printer: TreeRenderer, - val extension: String -) { + private val printer: TreeRenderer, + override val extensionIncludingDot: String +) : BaseTextComparisonTest() { abstract val parser: BaseParsingHelper<*, *> - /** - * Executes the test. The test files are looked up using the [parser]. - * The reference test file must be named [fileBaseName] + [ExpectedExt]. - * The source file to parse must be named [fileBaseName] + [extension]. - */ - fun doTest(fileBaseName: String) { - val expectedFile = findTestFile(parser.resourceLoader, "${parser.resourcePrefix}/$fileBaseName$ExpectedExt").toFile() - val sourceFile = findTestFile(parser.resourceLoader, "${parser.resourcePrefix}/$fileBaseName$extension").toFile() + override val resourceLoader: Class<*> + get() = parser.resourceLoader - assert(sourceFile.isFile) { - "Source file $sourceFile is missing" - } - - val parsed = parser.parse(sourceFile.readText()) // UTF-8 - val actual = StringBuilder().also { printer.renderSubtree(parsed, it) }.toString() - - if (!expectedFile.exists()) { - expectedFile.writeText(actual) - throw AssertionError("Reference file doesn't exist, created it at $expectedFile") - } - - val expected = expectedFile.readText() - - assertEquals(expected.normalize(), actual.normalize(), "Tree dump comparison failed, see the reference: $expectedFile") - } - - // Outputting a path makes for better error messages - private val srcTestResources = let { - // this is set from maven surefire - see parent pom.xml configuration for surefire (systemPropertyVariables) - System.getProperty("mvn.project.src.test.resources") - ?.let { Paths.get(it).toAbsolutePath() } - // that's for when the tests are run inside the IDE - ?: Paths.get(javaClass.protectionDomain.codeSource.location.file) - // go up from target/test-classes into the project root - .resolve("../../src/test/resources").normalize() - } - - private fun findTestFile(contextClass: Class<*>, resourcePath: String): Path { - val path = contextClass.`package`.name.replace('.', '/') - return srcTestResources.resolve("$path/$resourcePath") - } - - companion object { - const val ExpectedExt = ".txt" - - fun String.normalize() = replace(Regex("\\R"), "\n") - } + override val resourcePrefix: String + get() = parser.resourcePrefix + override fun transformTextContent(sourceText: String): String = + buildString { + printer.renderSubtree(parser.parse(sourceText), this) + } } diff --git a/pmd-lang-test/src/main/kotlin/net/sourceforge/pmd/test/BaseTextComparisonTest.kt b/pmd-lang-test/src/main/kotlin/net/sourceforge/pmd/test/BaseTextComparisonTest.kt new file mode 100644 index 0000000000..2c08a24590 --- /dev/null +++ b/pmd-lang-test/src/main/kotlin/net/sourceforge/pmd/test/BaseTextComparisonTest.kt @@ -0,0 +1,73 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.test + +import java.nio.file.Path +import java.nio.file.Paths +import kotlin.test.assertEquals + + +/** + * Compare a dump of a file against a saved baseline. + * See subclasses CpdTextComparisonTest, BaseTreeDumpTest. + */ +abstract class BaseTextComparisonTest { + + protected abstract val resourceLoader: Class<*> + protected abstract val resourcePrefix: String + + /** Extension that the unparsed source file is supposed to have. */ + protected abstract val extensionIncludingDot: String + /** Turn the contents of the source file into the "actual" string. */ + protected abstract fun transformTextContent(sourceText: String): String + + /** + * Executes the test. The test files are looked up using the [parser]. + * The reference test file must be named [fileBaseName] + [ExpectedExt]. + * The source file to parse must be named [fileBaseName] + [extensionIncludingDot]. + */ + fun doTest(fileBaseName: String) { + val expectedFile = findTestFile(resourceLoader, "${resourcePrefix}/$fileBaseName$ExpectedExt").toFile() + val sourceFile = findTestFile(resourceLoader, "${resourcePrefix}/$fileBaseName$extensionIncludingDot").toFile() + + assert(sourceFile.isFile) { + "Source file $sourceFile is missing" + } + + val actual = transformTextContent(sourceFile.readText()) // UTF-8 + + if (!expectedFile.exists()) { + expectedFile.writeText(actual) + throw AssertionError("Reference file doesn't exist, created it at $expectedFile") + } + + val expected = expectedFile.readText() + + assertEquals(expected.normalize(), actual.normalize(), "Tree dump comparison failed, see the reference: $expectedFile") + } + + // Outputting a path makes for better error messages + private val srcTestResources = let { + // this is set from maven surefire + System.getProperty("mvn.project.src.test.resources") + ?.let { Paths.get(it).toAbsolutePath() } + // that's for when the tests are run inside the IDE + ?: Paths.get(javaClass.protectionDomain.codeSource.location.file) + // go up from target/test-classes into the project root + .resolve("../../src/test/resources").normalize() + } + + private fun findTestFile(contextClass: Class<*>, resourcePath: String): Path { + val path = contextClass.`package`.name.replace('.', '/') + return srcTestResources.resolve("$path/$resourcePath") + } + + companion object { + const val ExpectedExt = ".txt" + + fun String.normalize() = replace(Regex("\\R"), "\n") + } + +}