From 8a605ad62d3d99e1ea14cbc368674bd05e0ca447 Mon Sep 17 00:00:00 2001 From: "Erik C. Thauvin" Date: Fri, 2 May 2025 23:41:14 -0700 Subject: [PATCH 1/9] Cleanup tests --- .../rife/bld/extension/PmdOperationTest.java | 584 ---------------- .../rife/bld/extension/PmdOperationTests.java | 658 ++++++++++++++++++ 2 files changed, 658 insertions(+), 584 deletions(-) delete mode 100644 src/test/java/rife/bld/extension/PmdOperationTest.java create mode 100644 src/test/java/rife/bld/extension/PmdOperationTests.java diff --git a/src/test/java/rife/bld/extension/PmdOperationTest.java b/src/test/java/rife/bld/extension/PmdOperationTest.java deleted file mode 100644 index 77321d6..0000000 --- a/src/test/java/rife/bld/extension/PmdOperationTest.java +++ /dev/null @@ -1,584 +0,0 @@ -/* - * Copyright 2023-2025 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package rife.bld.extension; - -import net.sourceforge.pmd.PMDConfiguration; -import net.sourceforge.pmd.lang.LanguageRegistry; -import net.sourceforge.pmd.lang.rule.RulePriority; -import org.assertj.core.api.AutoCloseableSoftAssertions; -import org.junit.jupiter.api.Test; -import rife.bld.BaseProject; -import rife.bld.operations.exceptions.ExitStatusException; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.PrintWriter; -import java.net.URI; -import java.net.URISyntaxException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.List; -import java.util.Properties; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatCode; - -/** - * PmdOperationTest class - * - * @author Erik C. Thauvin - * @since 1.0 - */ -class PmdOperationTest { - static final String BAR = "bar"; - static final String CATEGORY_FOO = "category/foo.xml"; - static final Path CODE_STYLE_SAMPLE = Path.of("src/test/resources/java/CodeStyle.java"); - static final String CODE_STYLE_XML = "category/java/codestyle.xml"; - static final String COMMAND_NAME = "pmd"; - static final String DESIGN_XML = "category/java/design.xml"; - static final String DOCUMENTATION_XML = "category/java/documentation.xml"; - static final Path ERROR_PRONE_SAMPLE = Path.of("src/test/resources/java/ErrorProne.java"); - static final String ERROR_PRONE_XML = "category/java/errorprone.xml"; - static final File FILE_BAR = new File(BAR); - static final String FOO = "foo"; - static final File FILE_FOO = new File(FOO); - static final Path PATH_BAR = Path.of(BAR); - static final Path PATH_FOO = Path.of(FOO); - static final String PERFORMANCE_XML = "category/java/performance.xml"; - static final String SECURITY_XML = "category/java/security.xml"; - static final String TEST = "test"; - - PmdOperation newPmdOperation() { - return new PmdOperation() - .inputPaths(Path.of("src/main"), Path.of("src/test")) - .cache("build/" + COMMAND_NAME + "/pmd-cache") - .failOnViolation(false) - .reportFile(Paths.get("build", COMMAND_NAME, "pmd-test-report.txt")); - } - - @Test - void testAddExcludes() { - var pmd = newPmdOperation().ruleSets(CATEGORY_FOO).addExcludes(PATH_FOO); - var config = pmd.initConfiguration(COMMAND_NAME); - assertThat(config.getExcludes()).containsExactly(PATH_FOO); - - pmd = pmd.addExcludes(PATH_BAR); - config = pmd.initConfiguration(COMMAND_NAME); - assertThat(config.getExcludes()).containsExactly(PATH_FOO, PATH_BAR); - } - - @Test - void testAddExcludesFiles() { - var pmd = newPmdOperation().ruleSets(CATEGORY_FOO).addExcludesFiles(FILE_FOO); - var config = pmd.initConfiguration(COMMAND_NAME); - assertThat(config.getExcludes()).containsExactly(FILE_FOO.toPath()); - - pmd = pmd.addExcludesFiles(FILE_BAR); - config = pmd.initConfiguration(COMMAND_NAME); - assertThat(config.getExcludes()).containsExactly(FILE_FOO.toPath(), FILE_BAR.toPath()); - } - - @Test - void testAddExcludesStrings() { - var pmd = newPmdOperation().ruleSets(CATEGORY_FOO).addExcludesStrings(FOO); - var config = pmd.initConfiguration(COMMAND_NAME); - assertThat(config.getExcludes()).containsExactly(PATH_FOO); - - pmd = pmd.addExcludesStrings(BAR); - config = pmd.initConfiguration(COMMAND_NAME); - assertThat(config.getExcludes()).containsExactly(PATH_FOO, PATH_BAR); - } - - @Test - void testAddInputPaths() throws ExitStatusException { - var project = new BaseProject(); - var pmd = new PmdOperation().fromProject(project); - - assertThat(pmd.inputPaths()).as("default").containsExactly(project.srcMainDirectory().toPath(), - project.srcTestDirectory().toPath()); - - var err = pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME)); - - pmd.inputPaths().clear(); - pmd = pmd.addInputPaths(project.srcMainDirectory(), project.srcTestDirectory()); - - assertThat(pmd.inputPaths()).as("File...").containsExactly(project.srcMainDirectory().toPath(), - project.srcTestDirectory().toPath()); - - pmd.inputPaths().clear(); - pmd = pmd.addInputPathsFiles(List.of(project.srcMainDirectory(), project.srcTestDirectory())); - - assertThat(pmd.inputPaths()).as("List(File...)") - .containsExactly(project.srcMainDirectory().toPath(), project.srcTestDirectory().toPath()); - - pmd.inputPaths().clear(); - pmd = pmd.addInputPaths(project.srcMainDirectory().getAbsolutePath(), - project.srcTestDirectory().getAbsolutePath()); - - assertThat(pmd.inputPaths()).as("String...") - .containsExactly(project.srcMainDirectory().toPath(), project.srcTestDirectory().toPath()); - - pmd.inputPaths().clear(); - pmd = pmd.addInputPathsStrings( - List.of(project.srcMainDirectory().getAbsolutePath(), project.srcTestDirectory().getAbsolutePath())); - - assertThat(pmd.inputPaths()).as("List(String...)") - .containsExactly(project.srcMainDirectory().toPath(), project.srcTestDirectory().toPath()); - - pmd.inputPaths().clear(); - pmd = pmd.addInputPaths(project.srcMainDirectory().toPath(), project.srcTestDirectory().toPath()); - - assertThat(pmd.inputPaths()).as("Path...") - .containsExactly(project.srcMainDirectory().toPath(), project.srcTestDirectory().toPath()); - - pmd.inputPaths().clear(); - pmd = pmd.addInputPaths(List.of(project.srcMainDirectory().toPath(), project.srcTestDirectory().toPath())); - - assertThat(pmd.inputPaths()).as("List(Path)") - .containsExactly(project.srcMainDirectory().toPath(), project.srcTestDirectory().toPath()); - - assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))) - .isGreaterThan(0).isEqualTo(err); - } - - @Test - void testAddRuleSets() throws ExitStatusException { - var pmd = new PmdOperation().fromProject(new BaseProject()); - - assertThat(pmd.ruleSets()).containsExactly(PmdOperation.RULE_SET_DEFAULT); - - pmd.addRuleSet(ERROR_PRONE_XML); - - assertThat(pmd.ruleSets()).containsExactly(PmdOperation.RULE_SET_DEFAULT, ERROR_PRONE_XML); - - var err = pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME)); - - pmd.ruleSets().clear(); - - pmd.addRuleSet(List.of(PmdOperation.RULE_SET_DEFAULT, ERROR_PRONE_XML)); - - assertThat(pmd.ruleSets()).as("collection") - .containsExactly(PmdOperation.RULE_SET_DEFAULT, ERROR_PRONE_XML); - - assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))) - .isGreaterThan(0).isEqualTo(err); - } - - @Test - void testCache() throws ExitStatusException { - var cache = Path.of("build/pmd/temp-cache"); - var pmd = newPmdOperation() - .ruleSets(CODE_STYLE_XML) - .inputPaths(List.of(CODE_STYLE_SAMPLE)) - .cache(cache); - assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))).isGreaterThan(0); - var f = cache.toFile(); - assertThat(f.exists()).as("file exits").isTrue(); - assertThat(f.delete()).as("delete file").isTrue(); - } - - @Test - void testEncoding() { - var pmd = newPmdOperation().ruleSets(CATEGORY_FOO).encoding("UTF-16"); - PMDConfiguration config = pmd.initConfiguration(COMMAND_NAME); - assertThat(config.getSourceEncoding()).as("UTF-16").isEqualTo(StandardCharsets.UTF_16); - - pmd = pmd.encoding(StandardCharsets.ISO_8859_1); - config = pmd.initConfiguration(COMMAND_NAME); - assertThat(config.getSourceEncoding()).as("ISO_8859").isEqualTo(StandardCharsets.ISO_8859_1); - } - - @Test - void testExcludes() { - var foz = Path.of("foz/baz"); - var baz = Path.of("baz/foz"); - - var pmd = newPmdOperation().ruleSets(CATEGORY_FOO).excludes(PATH_FOO, PATH_BAR); - var config = pmd.initConfiguration(COMMAND_NAME); - assertThat(pmd.excludes()).containsExactly(List.of(PATH_FOO, PATH_BAR).toArray(new Path[0])); - assertThat(config.getExcludes()).containsExactly(List.of(PATH_FOO, PATH_BAR).toArray(new Path[0])); - - var excludes = List.of(List.of(PATH_FOO, PATH_BAR), List.of(foz, baz)); - for (var exclude : excludes) { - pmd = newPmdOperation().ruleSets(CATEGORY_FOO).excludes(exclude); - config = pmd.initConfiguration(COMMAND_NAME); - assertThat(config.getExcludes()).containsExactly(exclude.toArray(new Path[0])); - } - } - - @Test - void testExcludesFiles() { - var foz = new File("foz"); - var baz = new File("baz"); - - var pmd = newPmdOperation().ruleSets(CATEGORY_FOO).excludesFiles(FILE_FOO, FILE_BAR); - var config = pmd.initConfiguration(COMMAND_NAME); - assertThat(config.getExcludes()).containsExactly(FILE_FOO.toPath(), FILE_BAR.toPath()); - - var excludes = List.of(List.of(FILE_FOO, FILE_BAR), List.of(foz, baz)); - for (var exclude : excludes) { - pmd = newPmdOperation().ruleSets(CATEGORY_FOO).excludesFiles(exclude); - config = pmd.initConfiguration(COMMAND_NAME); - assertThat(config.getExcludes()).containsExactly(exclude.stream().map(File::toPath).toArray(Path[]::new)); - } - } - - @Test - void testExcludesStrings() { - var foz = "foz"; - var baz = "baz"; - - var pmd = newPmdOperation().ruleSets(CATEGORY_FOO).excludesStrings(FOO, BAR); - var config = pmd.initConfiguration(COMMAND_NAME); - assertThat(pmd.excludes()).containsExactly(PATH_FOO, PATH_BAR); - assertThat(config.getExcludes()).containsExactly(PATH_FOO, PATH_BAR); - - var excludes = List.of(List.of(FOO, BAR), List.of(foz, baz)); - for (var exclude : excludes) { - pmd = newPmdOperation().ruleSets(CATEGORY_FOO).excludesStrings(exclude); - config = pmd.initConfiguration(COMMAND_NAME); - assertThat(config.getExcludes()).containsExactly(exclude.stream().map(Paths::get).toArray(Path[]::new)); - } - } - - @Test - void testExecute() throws ExitStatusException { - var pmd = new PmdOperation().fromProject(new BaseProject()); - - assertThat(pmd.inputPaths()).containsExactly(Paths.get("src/main").toAbsolutePath(), - Paths.get("src/test").toAbsolutePath()); - - pmd.inputPaths().clear(); - pmd.inputPaths("src/main/java", "src/test/java") - .ruleSets("config/pmd.xml"); - - assertThat(pmd.ruleSets()).containsExactly("config/pmd.xml"); - assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))).isEqualTo(0); - } - - @Test - void testExecuteNoProject() { - var pmd = new PmdOperation(); - assertThatCode(pmd::execute).isInstanceOf(ExitStatusException.class); - } - - @Test - void testFailOnError() { - var pmd = newPmdOperation().ruleSets("src/test/resources/xml/old.xml") - .inputPaths(Path.of("src/test/resources/java/Documentation.java")); - assertThatCode(() -> pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))) - .isInstanceOf(ExitStatusException.class); - assertThatCode(() -> pmd.failOnError(false).performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))) - .doesNotThrowAnyException(); - } - - @Test - void testFailOnValidation() { - var pmd = newPmdOperation().ruleSets(DOCUMENTATION_XML) - .inputPaths(Path.of("src/test/resources/java/Documentation.java")); - assertThatCode(() -> pmd.failOnViolation(true).performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))) - .isInstanceOf(ExitStatusException.class); - assertThatCode(() -> pmd.failOnViolation(false).performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))) - .doesNotThrowAnyException(); - } - - @Test - void testIgnoreFile() throws ExitStatusException { - var pmd = newPmdOperation() - .ruleSets(ERROR_PRONE_XML, CODE_STYLE_XML) - .ignoreFile(Path.of("src/test/resources/ignore.txt")); - assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))).isGreaterThan(0); - - pmd.inputPaths().clear(); - pmd.inputPaths(ERROR_PRONE_SAMPLE, CODE_STYLE_SAMPLE); - assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))).isEqualTo(0); - - pmd.ruleSets().clear(); - pmd.inputPaths().clear(); - assertThat(pmd.inputPaths(ERROR_PRONE_SAMPLE) - .ignoreFile(new File("src/test/resources/ignore-single.txt")) - .performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))).isEqualTo(0); - } - - @Test - void testIncrementalAnalysis() { - var pmd = newPmdOperation().ruleSets(CATEGORY_FOO).incrementalAnalysis(true); - var config = pmd.initConfiguration(COMMAND_NAME); - assertThat(config.isIgnoreIncrementalAnalysis()).isFalse(); - } - - @Test - void testInputPaths() throws ExitStatusException { - var pmd = newPmdOperation() - .ruleSets(PmdOperation.RULE_SET_DEFAULT, CODE_STYLE_XML) - .inputPaths(ERROR_PRONE_SAMPLE, CODE_STYLE_SAMPLE); - assertThat(pmd.inputPaths()).as("Path....").containsExactly(ERROR_PRONE_SAMPLE, CODE_STYLE_SAMPLE); - assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))).isGreaterThan(0); - - pmd = newPmdOperation() - .ruleSets(PmdOperation.RULE_SET_DEFAULT, CODE_STYLE_XML) - .inputPaths(ERROR_PRONE_SAMPLE.toFile(), CODE_STYLE_SAMPLE.toFile()); - assertThat(pmd.inputPaths()).as("File...").containsExactly(ERROR_PRONE_SAMPLE, CODE_STYLE_SAMPLE); - assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))).isGreaterThan(0); - - pmd = newPmdOperation() - .ruleSets(PmdOperation.RULE_SET_DEFAULT, CODE_STYLE_XML) - .inputPaths(ERROR_PRONE_SAMPLE.toString(), CODE_STYLE_SAMPLE.toString()); - assertThat(pmd.inputPaths()).as("String...").containsExactly(ERROR_PRONE_SAMPLE, CODE_STYLE_SAMPLE); - assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))).isGreaterThan(0); - - pmd = newPmdOperation() - .ruleSets(PmdOperation.RULE_SET_DEFAULT, CODE_STYLE_XML) - .inputPathsFiles(List.of(ERROR_PRONE_SAMPLE.toFile(), CODE_STYLE_SAMPLE.toFile())); - assertThat(pmd.inputPaths()).as("List(Path...)").containsExactly(ERROR_PRONE_SAMPLE, CODE_STYLE_SAMPLE); - assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))).isGreaterThan(0); - - pmd = newPmdOperation() - .ruleSets(PmdOperation.RULE_SET_DEFAULT, CODE_STYLE_XML) - .inputPathsStrings(List.of(ERROR_PRONE_SAMPLE.toString(), CODE_STYLE_SAMPLE.toString())); - assertThat(pmd.inputPaths()).as("List(String...)").containsExactly(ERROR_PRONE_SAMPLE, CODE_STYLE_SAMPLE); - assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))).isGreaterThan(0); - } - - @Test - void testJavaBestPractices() throws ExitStatusException { - var pmd = newPmdOperation().ruleSets("category/java/bestpractices.xml") - .inputPaths(Path.of("src/test/resources/java/BestPractices.java")); - assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))).isGreaterThan(0); - } - - @Test - void testJavaCodeStyle() throws ExitStatusException { - var pmd = newPmdOperation().ruleSets(CODE_STYLE_XML).inputPaths(CODE_STYLE_SAMPLE); - assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))).isGreaterThan(0); - } - - @Test - void testJavaCodeStyleAndErrorProne() throws ExitStatusException { - var pmd = newPmdOperation().ruleSets(CODE_STYLE_XML).inputPaths(CODE_STYLE_SAMPLE); - assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))) - .as("code style").isGreaterThan(0); - - pmd = pmd.ruleSets(ERROR_PRONE_XML).inputPaths(ERROR_PRONE_SAMPLE); - assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))) - .as("code style + error prone").isGreaterThan(0); - } - - @Test - void testJavaDesign() throws ExitStatusException { - var pmd = newPmdOperation() - .ruleSets(DESIGN_XML) - .inputPaths("src/test/resources/java/Design.java") - .cache(new File("build/pmd/design-cache")); - assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))).isGreaterThan(0); - } - - @Test - void testJavaDocumentation() throws ExitStatusException { - var pmd = newPmdOperation() - .ruleSets(DOCUMENTATION_XML) - .inputPaths(Path.of("src/test/resources/java/Documentation.java")); - assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))).isGreaterThan(0); - } - - @Test - void testJavaErrorProne() throws ExitStatusException { - var pmd = newPmdOperation().ruleSets(ERROR_PRONE_XML).inputPaths(ERROR_PRONE_SAMPLE); - assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))).isGreaterThan(0); - } - - @Test - void testJavaMultiThreading() throws ExitStatusException { - var pmd = newPmdOperation().ruleSets("category/java/multithreading.xml") - .inputPaths(Path.of("src/test/resources/java/MultiThreading.java")); - assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))).isGreaterThan(0); - } - - @Test - void testJavaPerformance() throws ExitStatusException { - var pmd = newPmdOperation() - .ruleSets(PERFORMANCE_XML) - .inputPaths(Path.of("src/test/resources/java/Performance.java")); - assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))).isGreaterThan(0); - } - - @Test - void testJavaQuickStart() throws ExitStatusException { - var pmd = newPmdOperation().ruleSets(PmdOperation.RULE_SET_DEFAULT) - .inputPaths(new File("src/test/resources/java/")); - assertThat(pmd.ruleSets()).containsExactly(PmdOperation.RULE_SET_DEFAULT); - assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))).isGreaterThan(0); - } - - @Test - void testJavaSecurity() throws ExitStatusException { - var pmd = newPmdOperation().ruleSets(SECURITY_XML) - .inputPaths(Path.of("src/test/resources/java/Security.java")); - assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))).isGreaterThan(0); - } - - @Test - void testLanguageVersions() throws ExitStatusException { - var language = LanguageRegistry.PMD.getLanguageById("java"); - assertThat(language).isNotNull(); - - var pmd = newPmdOperation() - .forceLanguageVersion(language.getLatestVersion()) - .defaultLanguageVersions(language.getVersions()) - .languageVersions(language.getDefaultVersion()) - .ruleSets(PmdOperation.RULE_SET_DEFAULT); - assertThat(pmd.languageVersions()).contains(language.getDefaultVersion()); - assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))).isGreaterThan(0); - - assertThat(pmd.defaultLanguageVersions(language.getVersion("17"), language.getVersion("21")) - .languageVersions(language.getVersions()).performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))) - .as("17 & 21").isGreaterThan(0); - - } - - @Test - void testMainOperation() throws ExitStatusException { - var pmd = newPmdOperation().inputPaths(new File("src/main")) - .performPmdAnalysis(TEST, newPmdOperation().initConfiguration(COMMAND_NAME)); - assertThat(pmd).isEqualTo(0); - } - - @Test - void testPrependAuxClasspath() { - var pmd = newPmdOperation().ruleSets(CATEGORY_FOO).prependAuxClasspath(FOO, BAR); - assertThat(pmd.prependAuxClasspath()).isEqualTo(FOO + File.pathSeparator + BAR); - } - - @Test - void testPriority() throws ExitStatusException { - var pmd = newPmdOperation().inputPaths(CODE_STYLE_SAMPLE).minimumPriority(RulePriority.HIGH); - assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))).isEqualTo(0); - } - - @Test - void testRelativizeRoots() { - var baz = Path.of("baz/foz"); - - var pmd = newPmdOperation().ruleSets(List.of(CATEGORY_FOO)).relativizeRoots(PATH_FOO). - relativizeRoots(PATH_BAR.toFile()).relativizeRoots(baz.toString()) - .relativizeRoots(List.of(PATH_FOO, PATH_BAR, baz)); - var config = pmd.initConfiguration(COMMAND_NAME); - assertThat(config.getRelativizeRoots()).isEqualTo(pmd.relativizeRoots()) - .containsExactly(PATH_FOO, PATH_BAR, baz, PATH_FOO, PATH_BAR, baz); - - pmd = newPmdOperation().ruleSets(List.of(CATEGORY_FOO)) - .relativizeRootsFiles(List.of(PATH_FOO.toFile(), PATH_BAR.toFile(), baz.toFile())); - config = pmd.initConfiguration(COMMAND_NAME); - assertThat(config.getRelativizeRoots()).as("List(File...)").isEqualTo(pmd.relativizeRoots()) - .containsExactly(PATH_FOO, PATH_BAR, baz); - - pmd = newPmdOperation().ruleSets(List.of(CATEGORY_FOO)) - .relativizeRootsStrings(List.of(PATH_FOO.toString(), PATH_BAR.toString(), baz.toString())); - config = pmd.initConfiguration(COMMAND_NAME); - assertThat(config.getRelativizeRoots()).as("List(String....)").isEqualTo(pmd.relativizeRoots()) - .containsExactly(PATH_FOO, PATH_BAR, baz); - } - - @Test - void testReportFile() throws FileNotFoundException, ExitStatusException { - var report = new File("build", "pmd-report-file"); - report.deleteOnExit(); - var pmd = newPmdOperation().ruleSets(List.of(ERROR_PRONE_XML, DESIGN_XML)).reportFile(report); - pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME)); - assertThat(report).exists(); - - try (var writer = new PrintWriter(report)) { - writer.write(""); - } - assertThat(report).isEmpty(); - - pmd.reportFile(report.getAbsolutePath()).performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME)); - assertThat(report).isNotEmpty(); - } - - @Test - void testReportFormat() throws IOException, ExitStatusException { - var pmd = newPmdOperation().ruleSets(ERROR_PRONE_XML).reportFormat("xml").inputPaths(ERROR_PRONE_SAMPLE); - assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))).isGreaterThan(0); - try (var softly = new AutoCloseableSoftAssertions()) { - try (var br = Files.newBufferedReader(pmd.reportFile())) { - softly.assertThat(br.readLine()).as("xml report") - .startsWith(""); - } - } - } - - @Test - void testReportProperties() throws ExitStatusException { - var pmd = newPmdOperation().ruleSets(CODE_STYLE_XML, ERROR_PRONE_XML) - .includeLineNumber(true) - .reportProperties(new Properties()); - assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))).isGreaterThan(0); - } - - @Test - void testRuleSetsConfigFile() throws ExitStatusException { - var pmd = newPmdOperation().ruleSets("src/test/resources/pmd.xml") - .ignoreFile("src/test/resources/ignore-all.txt"); - assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))).isEqualTo(0); - } - - @Test - void testRuleSetsEmpty() throws ExitStatusException { - var pmd = newPmdOperation().ruleSets("").failOnError(false); - assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))).isEqualTo(0); - } - - @Test - void testShowSuppressed() { - var pmd = newPmdOperation().ruleSets(ERROR_PRONE_XML).showSuppressed(true); - var config = pmd.initConfiguration(COMMAND_NAME); - assertThat(config.isShowSuppressedViolations()).isTrue(); - } - - @Test - void testSuppressedMarker() { - var pmd = newPmdOperation().ruleSets(ERROR_PRONE_XML).suppressedMarker(TEST); - var config = pmd.initConfiguration(COMMAND_NAME); - assertThat(config.getSuppressMarker()).isEqualTo(TEST); - } - - @Test - void testThreads() { - var pmd = newPmdOperation().ruleSets(ERROR_PRONE_XML).threads(5); - var config = pmd.initConfiguration(COMMAND_NAME); - assertThat(config.getThreads()).isEqualTo(5); - } - - @Test - void testUri() throws URISyntaxException { - var uri = new URI("https://example.com"); - var pmd = newPmdOperation().ruleSets(ERROR_PRONE_XML).uri(uri); - var config = pmd.initConfiguration(COMMAND_NAME); - assertThat(config.getUri()).isEqualTo(uri); - } - - @Test - void testXml() throws ExitStatusException { - var pmd = newPmdOperation().addInputPaths("src/test/resources/pmd.xml") - .ruleSets("src/test/resources/xml/basic.xml"); - assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))).isGreaterThan(0); - } -} diff --git a/src/test/java/rife/bld/extension/PmdOperationTests.java b/src/test/java/rife/bld/extension/PmdOperationTests.java new file mode 100644 index 0000000..3808fd2 --- /dev/null +++ b/src/test/java/rife/bld/extension/PmdOperationTests.java @@ -0,0 +1,658 @@ +/* + * Copyright 2023-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package rife.bld.extension; + +import net.sourceforge.pmd.PMDConfiguration; +import net.sourceforge.pmd.lang.LanguageRegistry; +import net.sourceforge.pmd.lang.rule.RulePriority; +import org.assertj.core.api.AutoCloseableSoftAssertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import rife.bld.BaseProject; +import rife.bld.operations.exceptions.ExitStatusException; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.PrintWriter; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Properties; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; + +/** + * PmdOperationTests class + * + * @author Erik C. Thauvin + * @since 1.0 + */ +class PmdOperationTests { + static final String ANALYSIS_FAILURE = "analysis should fail"; + static final String ANALYSIS_SUCCESS = "analysis should succeed"; + static final String BAR = "bar"; + static final String CATEGORY_FOO = "category/foo.xml"; + static final Path CODE_STYLE_SAMPLE = Path.of("src/test/resources/java/CodeStyle.java"); + static final String CODE_STYLE_XML = "category/java/codestyle.xml"; + static final String COMMAND_NAME = "pmd"; + static final String DESIGN_XML = "category/java/design.xml"; + static final String DOCUMENTATION_XML = "category/java/documentation.xml"; + static final Path ERROR_PRONE_SAMPLE = Path.of("src/test/resources/java/ErrorProne.java"); + static final String ERROR_PRONE_XML = "category/java/errorprone.xml"; + static final File FILE_BAR = new File(BAR); + static final String FOO = "foo"; + static final File FILE_FOO = new File(FOO); + static final Path PATH_BAR = Path.of(BAR); + static final Path PATH_FOO = Path.of(FOO); + static final String PERFORMANCE_XML = "category/java/performance.xml"; + static final String SECURITY_XML = "category/java/security.xml"; + static final String TEST = "test"; + + PmdOperation newPmdOperation() { + return new PmdOperation() + .inputPaths(Path.of("src/main"), Path.of("src/test")) + .cache("build/" + COMMAND_NAME + "/pmd-cache") + .failOnViolation(false) + .reportFile(Paths.get("build", COMMAND_NAME, "pmd-test-report.txt")); + } + + @Nested + @DisplayName("Configuration Tests") + class ConfigurationTests { + @Test + void cache() throws ExitStatusException { + var cache = Path.of("build/pmd/temp-cache"); + var pmd = newPmdOperation() + .ruleSets(CODE_STYLE_XML) + .inputPaths(List.of(CODE_STYLE_SAMPLE)) + .cache(cache); + assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))) + .as(ANALYSIS_FAILURE).isGreaterThan(0); + var f = cache.toFile(); + assertThat(f.exists()).as("cache should exist").isTrue(); + assertThat(f.delete()).as("cache should be deleted").isTrue(); + } + + @Test + void encoding() { + var pmd = newPmdOperation().ruleSets(CATEGORY_FOO).encoding("UTF-16"); + PMDConfiguration config = pmd.initConfiguration(COMMAND_NAME); + assertThat(config.getSourceEncoding()).as("encoding should be UTF-16").isEqualTo(StandardCharsets.UTF_16); + + pmd = pmd.encoding(StandardCharsets.ISO_8859_1); + config = pmd.initConfiguration(COMMAND_NAME); + assertThat(config.getSourceEncoding()).as("encoding should be ISO_8859") + .isEqualTo(StandardCharsets.ISO_8859_1); + } + + @Test + void ignoreFile() throws ExitStatusException { + var pmd = newPmdOperation() + .ruleSets(ERROR_PRONE_XML, CODE_STYLE_XML) + .ignoreFile(Path.of("src/test/resources/ignore.txt")); + assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))) + .as("%s for error prone and code style", ANALYSIS_FAILURE).isGreaterThan(0); + + pmd.inputPaths().clear(); + pmd.inputPaths(ERROR_PRONE_SAMPLE, CODE_STYLE_SAMPLE); + assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))) + .as("%s for error prone and code style samples", ANALYSIS_SUCCESS).isEqualTo(0); + + pmd.ruleSets().clear(); + pmd.inputPaths().clear(); + assertThat(pmd.inputPaths(ERROR_PRONE_SAMPLE) + .ignoreFile(new File("src/test/resources/ignore-single.txt")) + .performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))) + .as("%s for error prone sample", ANALYSIS_SUCCESS).isEqualTo(0); + } + + @Test + void incrementalAnalysis() { + var pmd = newPmdOperation().ruleSets(CATEGORY_FOO).incrementalAnalysis(true); + var config = pmd.initConfiguration(COMMAND_NAME); + assertThat(config.isIgnoreIncrementalAnalysis()).isFalse(); + } + + @Test + void languageVersions() throws ExitStatusException { + var language = LanguageRegistry.PMD.getLanguageById("java"); + assertThat(language).isNotNull(); + + var pmd = newPmdOperation() + .forceLanguageVersion(language.getLatestVersion()) + .defaultLanguageVersions(language.getVersions()) + .languageVersions(language.getDefaultVersion()) + .ruleSets(PmdOperation.RULE_SET_DEFAULT); + + assertThat(pmd.languageVersions()).as("should contain default language version") + .contains(language.getDefaultVersion()); + assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))) + .as(ANALYSIS_FAILURE).isGreaterThan(0); + + assertThat(pmd.defaultLanguageVersions(language.getVersion("17"), language.getVersion("21")) + .languageVersions(language.getVersions()) + .performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))) + .as("%s with language versions 17 & 21", ANALYSIS_FAILURE).isGreaterThan(0); + + } + + @Test + void prependAuxClasspath() { + var pmd = newPmdOperation().ruleSets(CATEGORY_FOO).prependAuxClasspath(FOO, BAR); + assertThat(pmd.prependAuxClasspath()).isEqualTo(FOO + File.pathSeparator + BAR); + } + + @Test + void priority() throws ExitStatusException { + var pmd = newPmdOperation().inputPaths(CODE_STYLE_SAMPLE).minimumPriority(RulePriority.HIGH); + assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))) + .as(ANALYSIS_SUCCESS).isEqualTo(0); + } + + @Test + void relativizeRoots() { + var baz = Path.of("baz/foz"); + + var pmd = newPmdOperation().ruleSets(List.of(CATEGORY_FOO)).relativizeRoots(PATH_FOO). + relativizeRoots(PATH_BAR.toFile()).relativizeRoots(baz.toString()) + .relativizeRoots(List.of(PATH_FOO, PATH_BAR, baz)); + var config = pmd.initConfiguration(COMMAND_NAME); + assertThat(config.getRelativizeRoots()).as("multiple roots").isEqualTo(pmd.relativizeRoots()) + .containsExactly(PATH_FOO, PATH_BAR, baz, PATH_FOO, PATH_BAR, baz); + + pmd = newPmdOperation().ruleSets(List.of(CATEGORY_FOO)) + .relativizeRootsFiles(List.of(PATH_FOO.toFile(), PATH_BAR.toFile(), baz.toFile())); + config = pmd.initConfiguration(COMMAND_NAME); + assertThat(config.getRelativizeRoots()).as("List(File...)").isEqualTo(pmd.relativizeRoots()) + .containsExactly(PATH_FOO, PATH_BAR, baz); + + pmd = newPmdOperation().ruleSets(List.of(CATEGORY_FOO)) + .relativizeRootsStrings(List.of(PATH_FOO.toString(), PATH_BAR.toString(), baz.toString())); + config = pmd.initConfiguration(COMMAND_NAME); + assertThat(config.getRelativizeRoots()).as("List(String....)").isEqualTo(pmd.relativizeRoots()) + .containsExactly(PATH_FOO, PATH_BAR, baz); + } + + @Test + void showSuppressed() { + var pmd = newPmdOperation().ruleSets(ERROR_PRONE_XML).showSuppressed(true); + var config = pmd.initConfiguration(COMMAND_NAME); + assertThat(config.isShowSuppressedViolations()).isTrue(); + } + + @Test + void suppressedMarker() { + var pmd = newPmdOperation().ruleSets(ERROR_PRONE_XML).suppressedMarker(TEST); + var config = pmd.initConfiguration(COMMAND_NAME); + assertThat(config.getSuppressMarker()).isEqualTo(TEST); + } + + @Test + void threads() { + var pmd = newPmdOperation().ruleSets(ERROR_PRONE_XML).threads(5); + var config = pmd.initConfiguration(COMMAND_NAME); + assertThat(config.getThreads()).isEqualTo(5); + } + + @Test + void uri() throws URISyntaxException { + var uri = new URI("https://example.com"); + var pmd = newPmdOperation().ruleSets(ERROR_PRONE_XML).uri(uri); + var config = pmd.initConfiguration(COMMAND_NAME); + assertThat(config.getUri()).isEqualTo(uri); + } + } + + @Nested + @DisplayName("Exclusion Tests") + class ExclusionTests { + @Test + void addExcludes() { + var pmd = newPmdOperation().ruleSets(CATEGORY_FOO).addExcludes(PATH_FOO); + var config = pmd.initConfiguration(COMMAND_NAME); + assertThat(config.getExcludes()).containsExactly(PATH_FOO); + + pmd = pmd.addExcludes(PATH_BAR); + config = pmd.initConfiguration(COMMAND_NAME); + assertThat(config.getExcludes()).containsExactly(PATH_FOO, PATH_BAR); + } + + @Test + void addExcludesFiles() { + var pmd = newPmdOperation().ruleSets(CATEGORY_FOO).addExcludesFiles(FILE_FOO); + var config = pmd.initConfiguration(COMMAND_NAME); + assertThat(config.getExcludes()).containsExactly(FILE_FOO.toPath()); + + pmd = pmd.addExcludesFiles(FILE_BAR); + config = pmd.initConfiguration(COMMAND_NAME); + assertThat(config.getExcludes()).containsExactly(FILE_FOO.toPath(), FILE_BAR.toPath()); + } + + @Test + void addExcludesStrings() { + var pmd = newPmdOperation().ruleSets(CATEGORY_FOO).addExcludesStrings(FOO); + var config = pmd.initConfiguration(COMMAND_NAME); + assertThat(config.getExcludes()).containsExactly(PATH_FOO); + + pmd = pmd.addExcludesStrings(BAR); + config = pmd.initConfiguration(COMMAND_NAME); + assertThat(config.getExcludes()).containsExactly(PATH_FOO, PATH_BAR); + } + + @Test + void excludes() { + var foz = Path.of("foz/baz"); + var baz = Path.of("baz/foz"); + + var pmd = newPmdOperation().ruleSets(CATEGORY_FOO).excludes(PATH_FOO, PATH_BAR); + var config = pmd.initConfiguration(COMMAND_NAME); + assertThat(pmd.excludes()).containsExactly(List.of(PATH_FOO, PATH_BAR).toArray(new Path[0])); + assertThat(config.getExcludes()).containsExactly(List.of(PATH_FOO, PATH_BAR).toArray(new Path[0])); + + var excludes = List.of(List.of(PATH_FOO, PATH_BAR), List.of(foz, baz)); + for (var exclude : excludes) { + pmd = newPmdOperation().ruleSets(CATEGORY_FOO).excludes(exclude); + config = pmd.initConfiguration(COMMAND_NAME); + assertThat(config.getExcludes()).containsExactly(exclude.toArray(new Path[0])); + } + } + + @Test + void excludesFiles() { + var foz = new File("foz"); + var baz = new File("baz"); + + var pmd = newPmdOperation().ruleSets(CATEGORY_FOO).excludesFiles(FILE_FOO, FILE_BAR); + var config = pmd.initConfiguration(COMMAND_NAME); + assertThat(config.getExcludes()).containsExactly(FILE_FOO.toPath(), FILE_BAR.toPath()); + + var excludes = List.of(List.of(FILE_FOO, FILE_BAR), List.of(foz, baz)); + for (var exclude : excludes) { + pmd = newPmdOperation().ruleSets(CATEGORY_FOO).excludesFiles(exclude); + config = pmd.initConfiguration(COMMAND_NAME); + assertThat(config.getExcludes()).containsExactly(exclude.stream().map(File::toPath).toArray(Path[]::new)); + } + } + + @Test + void excludesStrings() { + var foz = "foz"; + var baz = "baz"; + + var pmd = newPmdOperation().ruleSets(CATEGORY_FOO).excludesStrings(FOO, BAR); + var config = pmd.initConfiguration(COMMAND_NAME); + assertThat(pmd.excludes()).containsExactly(PATH_FOO, PATH_BAR); + assertThat(config.getExcludes()).containsExactly(PATH_FOO, PATH_BAR); + + var excludes = List.of(List.of(FOO, BAR), List.of(foz, baz)); + for (var exclude : excludes) { + pmd = newPmdOperation().ruleSets(CATEGORY_FOO).excludesStrings(exclude); + config = pmd.initConfiguration(COMMAND_NAME); + assertThat(config.getExcludes()).containsExactly(exclude.stream().map(Paths::get).toArray(Path[]::new)); + } + } + } + + @Nested + @DisplayName("Execution Tests") + class ExecutionTests { + @Test + void execute() throws ExitStatusException { + var pmd = new PmdOperation().fromProject(new BaseProject()); + + assertThat(pmd.inputPaths()).containsExactly(Paths.get("src/main").toAbsolutePath(), + Paths.get("src/test").toAbsolutePath()); + + pmd.inputPaths().clear(); + pmd.inputPaths("src/main/java", "src/test/java") + .ruleSets("config/pmd.xml"); + + assertThat(pmd.ruleSets()).containsExactly("config/pmd.xml"); + assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))) + .as(ANALYSIS_SUCCESS).isEqualTo(0); + } + + @Test + void executeNoProject() { + var pmd = new PmdOperation(); + assertThatCode(pmd::execute).isInstanceOf(ExitStatusException.class); + } + } + + @Nested + @DisplayName("Failure Tests") + class FailureTests { + @Test + void failOnError() { + var pmd = newPmdOperation().ruleSets("src/test/resources/xml/old.xml") + .inputPaths(Path.of("src/test/resources/java/Documentation.java")); + assertThatCode(() -> pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))) + .isInstanceOf(ExitStatusException.class); + assertThatCode(() -> pmd.failOnError(false).performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))) + .doesNotThrowAnyException(); + } + + @Test + void failOnValidation() { + var pmd = newPmdOperation().ruleSets(DOCUMENTATION_XML) + .inputPaths(Path.of("src/test/resources/java/Documentation.java")); + assertThatCode(() -> pmd.failOnViolation(true).performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))) + .isInstanceOf(ExitStatusException.class); + assertThatCode(() -> pmd.failOnViolation(false).performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))) + .doesNotThrowAnyException(); + } + } + + @Nested + @DisplayName("Input Paths Tests") + class InputPathsTests { + @Test + void addInputPaths() throws ExitStatusException { + var project = new BaseProject(); + var pmd = new PmdOperation().fromProject(project); + + assertThat(pmd.inputPaths()).as("default input paths") + .containsExactly(project.srcMainDirectory().toPath(), project.srcTestDirectory().toPath()); + + var err = pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME)); + + pmd.inputPaths().clear(); + pmd = pmd.addInputPaths(project.srcMainDirectory(), project.srcTestDirectory()); + + assertThat(pmd.inputPaths()).as("File...").containsExactly(project.srcMainDirectory().toPath(), + project.srcTestDirectory().toPath()); + + pmd.inputPaths().clear(); + pmd = pmd.addInputPathsFiles(List.of(project.srcMainDirectory(), project.srcTestDirectory())); + + assertThat(pmd.inputPaths()).as("List(File...)") + .containsExactly(project.srcMainDirectory().toPath(), project.srcTestDirectory().toPath()); + + pmd.inputPaths().clear(); + pmd = pmd.addInputPaths(project.srcMainDirectory().getAbsolutePath(), + project.srcTestDirectory().getAbsolutePath()); + + assertThat(pmd.inputPaths()).as("String...") + .containsExactly(project.srcMainDirectory().toPath(), project.srcTestDirectory().toPath()); + + pmd.inputPaths().clear(); + pmd = pmd.addInputPathsStrings( + List.of(project.srcMainDirectory().getAbsolutePath(), project.srcTestDirectory().getAbsolutePath())); + + assertThat(pmd.inputPaths()).as("List(String...)") + .containsExactly(project.srcMainDirectory().toPath(), project.srcTestDirectory().toPath()); + + pmd.inputPaths().clear(); + pmd = pmd.addInputPaths(project.srcMainDirectory().toPath(), project.srcTestDirectory().toPath()); + + assertThat(pmd.inputPaths()).as("Path...") + .containsExactly(project.srcMainDirectory().toPath(), project.srcTestDirectory().toPath()); + + pmd.inputPaths().clear(); + pmd = pmd.addInputPaths(List.of(project.srcMainDirectory().toPath(), project.srcTestDirectory().toPath())); + + assertThat(pmd.inputPaths()).as("List(Path)") + .containsExactly(project.srcMainDirectory().toPath(), project.srcTestDirectory().toPath()); + + assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))) + .as(ANALYSIS_FAILURE).isGreaterThan(0).isEqualTo(err); + } + + @Test + void fileArray() throws ExitStatusException { + var pmd = newPmdOperation() + .ruleSets(PmdOperation.RULE_SET_DEFAULT, CODE_STYLE_XML) + .inputPaths(ERROR_PRONE_SAMPLE.toFile(), CODE_STYLE_SAMPLE.toFile()); + assertThat(pmd.inputPaths()).containsExactly(ERROR_PRONE_SAMPLE, CODE_STYLE_SAMPLE); + assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))) + .as(ANALYSIS_FAILURE).isGreaterThan(0); + } + + @Test + void mainOperation() throws ExitStatusException { + var pmd = newPmdOperation().inputPaths(new File("src/main")) + .performPmdAnalysis(TEST, newPmdOperation().initConfiguration(COMMAND_NAME)); + assertThat(pmd).as(ANALYSIS_SUCCESS).isEqualTo(0); + } + + @Test + void pathArray() throws ExitStatusException { + var pmd = newPmdOperation() + .ruleSets(PmdOperation.RULE_SET_DEFAULT, CODE_STYLE_XML) + .inputPaths(ERROR_PRONE_SAMPLE, CODE_STYLE_SAMPLE); + assertThat(pmd.inputPaths()).containsExactly(ERROR_PRONE_SAMPLE, CODE_STYLE_SAMPLE); + assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))) + .as(ANALYSIS_FAILURE).isGreaterThan(0); + } + + @Test + void pathList() throws ExitStatusException { + var pmd = newPmdOperation() + .ruleSets(PmdOperation.RULE_SET_DEFAULT, CODE_STYLE_XML) + .inputPathsFiles(List.of(ERROR_PRONE_SAMPLE.toFile(), CODE_STYLE_SAMPLE.toFile())); + assertThat(pmd.inputPaths()).containsExactly(ERROR_PRONE_SAMPLE, CODE_STYLE_SAMPLE); + assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))) + .as(ANALYSIS_FAILURE).isGreaterThan(0); + } + + @Test + void stringArray() throws ExitStatusException { + var pmd = newPmdOperation() + .ruleSets(PmdOperation.RULE_SET_DEFAULT, CODE_STYLE_XML) + .inputPaths(ERROR_PRONE_SAMPLE.toString(), CODE_STYLE_SAMPLE.toString()); + assertThat(pmd.inputPaths()).containsExactly(ERROR_PRONE_SAMPLE, CODE_STYLE_SAMPLE); + assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))) + .as(ANALYSIS_FAILURE).isGreaterThan(0); + } + + @Test + void stringList() throws ExitStatusException { + var pmd = newPmdOperation() + .ruleSets(PmdOperation.RULE_SET_DEFAULT, CODE_STYLE_XML) + .inputPathsStrings(List.of(ERROR_PRONE_SAMPLE.toString(), CODE_STYLE_SAMPLE.toString())); + assertThat(pmd.inputPaths()).containsExactly(ERROR_PRONE_SAMPLE, CODE_STYLE_SAMPLE); + assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))) + .as(ANALYSIS_FAILURE).isGreaterThan(0); + } + + @Test + void xml() throws ExitStatusException { + var pmd = newPmdOperation().addInputPaths("src/test/resources/pmd.xml") + .ruleSets("src/test/resources/xml/basic.xml"); + assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))) + .as(ANALYSIS_FAILURE).isGreaterThan(0); + } + } + + @Nested + @DisplayName("Reporting Tests") + class ReportingTests { + @Test + void reportFile() throws FileNotFoundException, ExitStatusException { + var report = new File("build", "pmd-report-file"); + report.deleteOnExit(); + var pmd = newPmdOperation().ruleSets(List.of(ERROR_PRONE_XML, DESIGN_XML)).reportFile(report); + pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME)); + assertThat(report).as("report should exist").exists(); + + try (var writer = new PrintWriter(report)) { + writer.write(""); + } + assertThat(report).as("report should not be empty").isEmpty(); + + pmd.reportFile(report.getAbsolutePath()).performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME)); + assertThat(report).as("report should not be empty").isNotEmpty(); + } + + @Test + void reportFormat() throws IOException, ExitStatusException { + var pmd = newPmdOperation().ruleSets(ERROR_PRONE_XML).reportFormat("xml").inputPaths(ERROR_PRONE_SAMPLE); + assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))) + .as(ANALYSIS_FAILURE).isGreaterThan(0); + try (var softly = new AutoCloseableSoftAssertions()) { + try (var br = Files.newBufferedReader(pmd.reportFile())) { + softly.assertThat(br.readLine()).as("XML report for %s", pmd.reportFile().toString()) + .startsWith(""); + } + } + } + + @Test + void reportProperties() throws ExitStatusException { + var pmd = newPmdOperation().ruleSets(CODE_STYLE_XML, ERROR_PRONE_XML) + .includeLineNumber(true) + .reportProperties(new Properties()); + assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))) + .as(ANALYSIS_FAILURE).isGreaterThan(0); + } + } + + @Nested + @DisplayName("RuleSets Tests") + class RuleSetsTests { + @Test + void addRuleSets() throws ExitStatusException { + var pmd = new PmdOperation().fromProject(new BaseProject()); + + assertThat(pmd.ruleSets()).containsExactly(PmdOperation.RULE_SET_DEFAULT); + + pmd.addRuleSet(ERROR_PRONE_XML); + + assertThat(pmd.ruleSets()).containsExactly(PmdOperation.RULE_SET_DEFAULT, ERROR_PRONE_XML); + + var err = pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME)); + + pmd.ruleSets().clear(); + + pmd.addRuleSet(List.of(PmdOperation.RULE_SET_DEFAULT, ERROR_PRONE_XML)); + + assertThat(pmd.ruleSets()).as("rulesets as collection") + .containsExactly(PmdOperation.RULE_SET_DEFAULT, ERROR_PRONE_XML); + + assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))) + .as(ANALYSIS_FAILURE).isGreaterThan(0).isEqualTo(err); + } + + @Test + void javaBestPractices() throws ExitStatusException { + var pmd = newPmdOperation().ruleSets("category/java/bestpractices.xml") + .inputPaths(Path.of("src/test/resources/java/BestPractices.java")); + assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))) + .as(ANALYSIS_FAILURE).isGreaterThan(0); + } + + @Test + void javaCodeStyle() throws ExitStatusException { + var pmd = newPmdOperation().ruleSets(CODE_STYLE_XML).inputPaths(CODE_STYLE_SAMPLE); + assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))) + .as(ANALYSIS_FAILURE).isGreaterThan(0); + } + + @Test + void javaCodeStyleAndErrorProne() throws ExitStatusException { + var pmd = newPmdOperation().ruleSets(CODE_STYLE_XML).inputPaths(CODE_STYLE_SAMPLE); + assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))) + .as("analysis with code style").isGreaterThan(0); + + pmd = pmd.ruleSets(ERROR_PRONE_XML).inputPaths(ERROR_PRONE_SAMPLE); + assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))) + .as("analysis with code style and error prone").isGreaterThan(0); + } + + @Test + void javaDesign() throws ExitStatusException { + var pmd = newPmdOperation() + .ruleSets(DESIGN_XML) + .inputPaths("src/test/resources/java/Design.java") + .cache(new File("build/pmd/design-cache")); + assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))) + .as(ANALYSIS_FAILURE).isGreaterThan(0); + } + + @Test + void javaDocumentation() throws ExitStatusException { + var pmd = newPmdOperation() + .ruleSets(DOCUMENTATION_XML) + .inputPaths(Path.of("src/test/resources/java/Documentation.java")); + assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))) + .as(ANALYSIS_FAILURE).isGreaterThan(0); + } + + @Test + void javaErrorProne() throws ExitStatusException { + var pmd = newPmdOperation().ruleSets(ERROR_PRONE_XML).inputPaths(ERROR_PRONE_SAMPLE); + assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))) + .as(ANALYSIS_FAILURE).isGreaterThan(0); + } + + @Test + void javaMultiThreading() throws ExitStatusException { + var pmd = newPmdOperation().ruleSets("category/java/multithreading.xml") + .inputPaths(Path.of("src/test/resources/java/MultiThreading.java")); + assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))) + .as(ANALYSIS_FAILURE).isGreaterThan(0); + } + + @Test + void javaPerformance() throws ExitStatusException { + var pmd = newPmdOperation() + .ruleSets(PERFORMANCE_XML) + .inputPaths(Path.of("src/test/resources/java/Performance.java")); + assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))) + .as(ANALYSIS_FAILURE).isGreaterThan(0); + } + + @Test + void javaQuickStart() throws ExitStatusException { + var pmd = newPmdOperation().ruleSets(PmdOperation.RULE_SET_DEFAULT) + .inputPaths(new File("src/test/resources/java/")); + assertThat(pmd.ruleSets()).containsExactly(PmdOperation.RULE_SET_DEFAULT); + assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))) + .as(ANALYSIS_FAILURE).isGreaterThan(0); + } + + @Test + void javaSecurity() throws ExitStatusException { + var pmd = newPmdOperation().ruleSets(SECURITY_XML) + .inputPaths(Path.of("src/test/resources/java/Security.java")); + assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))) + .as(ANALYSIS_FAILURE).isGreaterThan(0); + } + + @Test + void ruleSetsConfigFile() throws ExitStatusException { + var pmd = newPmdOperation().ruleSets("src/test/resources/pmd.xml") + .ignoreFile("src/test/resources/ignore-all.txt"); + assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))) + .as(ANALYSIS_SUCCESS).isEqualTo(0); + } + + @Test + void ruleSetsEmpty() throws ExitStatusException { + var pmd = newPmdOperation().ruleSets("").failOnError(false); + assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME))) + .as(ANALYSIS_SUCCESS).isEqualTo(0); + } + } +} From a5d070a022c1dc696bef75d0c7fbe7951915852c Mon Sep 17 00:00:00 2001 From: "Erik C. Thauvin" Date: Wed, 28 May 2025 14:26:17 -0700 Subject: [PATCH 2/9] Generate and convert JUnit reports for xunit-viewer --- lib/bld/bld-wrapper.properties | 1 + .../rife/bld/extension/PmdOperationBuild.java | 38 ++++++++++++++++++- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/lib/bld/bld-wrapper.properties b/lib/bld/bld-wrapper.properties index 4745e94..f656651 100644 --- a/lib/bld/bld-wrapper.properties +++ b/lib/bld/bld-wrapper.properties @@ -1,5 +1,6 @@ bld.downloadExtensionJavadoc=false bld.downloadExtensionSources=true bld.downloadLocation= +bld.extension-exec=com.uwyn.rife2:bld-exec:1.0.5 bld.repositories=MAVEN_LOCAL,MAVEN_CENTRAL,RIFE2_SNAPSHOTS,RIFE2_RELEASES bld.version=2.2.1 diff --git a/src/bld/java/rife/bld/extension/PmdOperationBuild.java b/src/bld/java/rife/bld/extension/PmdOperationBuild.java index cbaa6ba..dcd6e0b 100644 --- a/src/bld/java/rife/bld/extension/PmdOperationBuild.java +++ b/src/bld/java/rife/bld/extension/PmdOperationBuild.java @@ -21,6 +21,9 @@ import rife.bld.publish.PublishDeveloper; import rife.bld.publish.PublishLicense; import rife.bld.publish.PublishScm; +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.List; import static rife.bld.dependencies.Repository.*; @@ -31,13 +34,13 @@ public class PmdOperationBuild extends Project { public PmdOperationBuild() { pkg = "rife.bld.extension"; name = "bld-pmd"; - version = version(1, 2, 3); + version = version(1, 2, 4, "SNAPSHOT"); javaRelease = 17; downloadSources = true; autoDownloadPurge = true; - + repositories = List.of(MAVEN_LOCAL, MAVEN_CENTRAL, RIFE2_RELEASES, RIFE2_SNAPSHOTS); var pmd = version(7, 13, 0); @@ -88,4 +91,35 @@ public class PmdOperationBuild extends Project { public static void main(String[] args) { new PmdOperationBuild().start(args); } + + @Override + public void test() throws Exception { + var testResultsDir = "build/test-results/test/"; + + var op = testOperation().fromProject(this); + op.testToolOptions().reportsDir(new File(testResultsDir)); + + Exception ex = null; + try { + op.execute(); + } catch (Exception e) { + ex = e; + } + + var xunitViewer = new File("/usr/bin/xunit-viewer"); + if (xunitViewer.exists() && xunitViewer.canExecute()) { + var reportsDir = "build/reports/tests/test/"; + + Files.createDirectories(Path.of(reportsDir)); + + new ExecOperation() + .fromProject(this) + .command(xunitViewer.getPath(), "-r", testResultsDir, "-o", reportsDir + "index.html") + .execute(); + } + + if (ex != null) { + throw ex; + } + } } From cebccf754c3ac2d286ab8e2426bcbbdee127da3e Mon Sep 17 00:00:00 2001 From: "Erik C. Thauvin" Date: Sat, 31 May 2025 01:42:10 -0700 Subject: [PATCH 3/9] Bump PMD to version 7.14.0 --- src/bld/java/rife/bld/extension/PmdOperationBuild.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bld/java/rife/bld/extension/PmdOperationBuild.java b/src/bld/java/rife/bld/extension/PmdOperationBuild.java index dcd6e0b..242d833 100644 --- a/src/bld/java/rife/bld/extension/PmdOperationBuild.java +++ b/src/bld/java/rife/bld/extension/PmdOperationBuild.java @@ -43,7 +43,7 @@ public class PmdOperationBuild extends Project { repositories = List.of(MAVEN_LOCAL, MAVEN_CENTRAL, RIFE2_RELEASES, RIFE2_SNAPSHOTS); - var pmd = version(7, 13, 0); + var pmd = version(7, 14, 0); scope(compile) .include(dependency("com.uwyn.rife2", "bld", version(2, 2, 1))) .include(dependency("net.sourceforge.pmd", "pmd-java", pmd)); From 71e9ab7f98f653e5a1099aecba766bd4260bff90 Mon Sep 17 00:00:00 2001 From: "Erik C. Thauvin" Date: Sat, 31 May 2025 01:44:36 -0700 Subject: [PATCH 4/9] Add collect files recursively configuration option --- .../java/rife/bld/extension/PmdOperation.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/main/java/rife/bld/extension/PmdOperation.java b/src/main/java/rife/bld/extension/PmdOperation.java index 25b25c6..577d5e0 100644 --- a/src/main/java/rife/bld/extension/PmdOperation.java +++ b/src/main/java/rife/bld/extension/PmdOperation.java @@ -353,6 +353,19 @@ public class PmdOperation extends AbstractOperation { return cache(Path.of(cache)); } + /** + * When specified, any directory mentioned with {@link #inputPaths()} will only be searched for files that are + * direct children. By default, subdirectories are recursively included. + * + * @param collectFilesRecursively whether to collect files recursively or not + * @return this operation + * @since 1.2.4 + */ + public PmdOperation collectFilesRecursively(boolean collectFilesRecursively) { + this.collectFilesRecursively_ = collectFilesRecursively; + return this; + } + /** * Sets the default language version to be used for all input files. * @@ -625,6 +638,11 @@ public class PmdOperation extends AbstractOperation { // addRelativizeRoots config.addRelativizeRoots(relativizeRoots_.stream().toList()); + // collectFilesRecursively + if (!collectFilesRecursively_) { + config.collectFilesRecursively(false); + } + // prependAuxClasspath if (prependClasspath_ != null) { config.prependAuxClasspath(prependClasspath_); From 630f3fa8d273db9c76d351a788afa23d849e26fe Mon Sep 17 00:00:00 2001 From: "Erik C. Thauvin" Date: Sat, 31 May 2025 01:45:44 -0700 Subject: [PATCH 5/9] Add displaying configuration and processing errors --- .../bld/extension/PmdAnalysisResults.java | 47 ++++ .../java/rife/bld/extension/PmdOperation.java | 235 ++++++++---------- .../extension/PmdAnalysisResultsTests.java | 57 +++++ 3 files changed, 208 insertions(+), 131 deletions(-) create mode 100644 src/main/java/rife/bld/extension/PmdAnalysisResults.java create mode 100644 src/test/java/rife/bld/extension/PmdAnalysisResultsTests.java diff --git a/src/main/java/rife/bld/extension/PmdAnalysisResults.java b/src/main/java/rife/bld/extension/PmdAnalysisResults.java new file mode 100644 index 0000000..bc7c650 --- /dev/null +++ b/src/main/java/rife/bld/extension/PmdAnalysisResults.java @@ -0,0 +1,47 @@ +/* + * Copyright 2023-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package rife.bld.extension; + +/** + * Represents the results of a PMD analysis, containing various counts + * related to violations, errors, and rules. + * + * @param violations The number of violations found during the analysis + * @param suppressedViolations The number of suppressed violations found during the analysis + * @param errors The number of errors returned during the analysis + * @param processingErrors The number of processing errors returned during the analysis + * @param configurationErrors The number of processing errors returning during the analysis + * @param rulesChecked The number of rules checked during the analysis + * @since 1.2.4 + */ +public record PmdAnalysisResults( + int violations, + int suppressedViolations, + int errors, + int processingErrors, + int configurationErrors, + int rulesChecked) { + + /** + * Checks if the analysis results indicate no errors of any type. + * + * @return {@code true} if there are no errors, {@code false} otherwise. + */ + public boolean hasNoErrors() { + return errors == 0 && processingErrors == 0 && configurationErrors == 0; + } +} diff --git a/src/main/java/rife/bld/extension/PmdOperation.java b/src/main/java/rife/bld/extension/PmdOperation.java index 577d5e0..db90bcc 100644 --- a/src/main/java/rife/bld/extension/PmdOperation.java +++ b/src/main/java/rife/bld/extension/PmdOperation.java @@ -20,6 +20,7 @@ import net.sourceforge.pmd.PMDConfiguration; import net.sourceforge.pmd.PmdAnalysis; import net.sourceforge.pmd.lang.LanguageVersion; import net.sourceforge.pmd.lang.rule.RulePriority; +import net.sourceforge.pmd.reporting.Report; import rife.bld.BaseProject; import rife.bld.operations.AbstractOperation; import rife.bld.operations.exceptions.ExitStatusException; @@ -41,105 +42,41 @@ import java.util.logging.Logger; * @since 1.0 */ public class PmdOperation extends AbstractOperation { + /** + * The default logger. + */ + public static final Logger LOGGER = Logger.getLogger(PmdOperation.class.getName()); /** * The default rule set. *

* Set to: {@code rulesets/java/quickstart.xml} */ public static final String RULE_SET_DEFAULT = "rulesets/java/quickstart.xml"; - private static final Logger LOGGER = Logger.getLogger(PmdOperation.class.getName()); + private static final String PMD_DIR = "pmd"; - /** - * The list of paths to exclude. - */ private final Collection excludes_ = new ArrayList<>(); - /** - * The input paths (source) list. - */ private final Collection inputPaths_ = new ArrayList<>(); - /** - * The relative roots paths. - */ private final Collection relativizeRoots_ = new ArrayList<>(); - /** - * The rule sets list. - */ private final Collection ruleSets_ = new ArrayList<>(); - /** - * The cache location. - */ private Path cache_; - /** - * The encoding. - */ + private boolean collectFilesRecursively_ = true; private Charset encoding_ = StandardCharsets.UTF_8; - /** - * The fail on error toggle. - */ private boolean failOnError_ = true; - /** - * The fail on violation toggle. - */ private boolean failOnViolation_; - /** - * The forced language. - */ private LanguageVersion forcedLanguageVersion_; - /** - * The path of the ignore file - */ private Path ignoreFile_; - /** - * The include line number toggle. - */ private boolean includeLineNumber_ = true; - /** - * The incremental analysis toggle. - */ private boolean incrementalAnalysis_ = true; - /** - * The input URI. - */ private URI inputUri_; - /** - * The default language version(s). - */ private Collection languageVersions_ = new ArrayList<>(); - /** - * The classpath to prepend. - */ private String prependClasspath_; - /** - * The project reference. - */ private BaseProject project_; - /** - * The path to the report page. - */ private Path reportFile_; - /** - * The report format. - */ private String reportFormat_ = "text"; - /** - * The report properties. - */ private Properties reportProperties_; - /** - * The rule priority. - */ private RulePriority rulePriority_ = RulePriority.LOW; - /** - * The show suppressed flag. - */ private boolean showSuppressed_; - /** - * THe suppressed marker. - */ private String suppressedMarker_ = "NOPMD"; - /** - * The number of threads. - */ private int threads_ = 1; /** @@ -216,18 +153,18 @@ public class PmdOperation extends AbstractOperation { } /** - * Adds paths to source files, or directories containing source files to analyze.\ + * Adds paths to source files or directories containing source files to analyze.\ * * @param inputPath one or more paths * @return this operation * @see #inputPaths(Path...) */ public PmdOperation addInputPaths(Path... inputPath) { - return inputPaths(List.of(inputPath)); + return addInputPaths(List.of(inputPath)); } /** - * Adds paths to source files, or directories containing source files to analyze. + * Adds paths to source files or directories containing source files to analyze. * * @param inputPath one or more paths * @return this operation @@ -238,7 +175,7 @@ public class PmdOperation extends AbstractOperation { } /** - * Adds paths to source files, or directories containing source files to analyze. + * Adds paths to source files or directories containing source files to analyze. * * @param inputPath one or more paths * @return this operation @@ -249,7 +186,7 @@ public class PmdOperation extends AbstractOperation { } /** - * Adds paths to source files, or directories containing source files to analyze. + * Adds paths to source files or directories containing source files to analyze. * * @param inputPath a collection of input paths * @return this operation @@ -261,7 +198,7 @@ public class PmdOperation extends AbstractOperation { } /** - * Adds paths to source files, or directories containing source files to analyze. + * Adds paths to source files or directories containing source files to analyze. * * @param inputPath a collection of input paths * @return this operation @@ -272,7 +209,7 @@ public class PmdOperation extends AbstractOperation { } /** - * Adds paths to source files, or directories containing source files to analyze. + * Adds paths to source files or directories containing source files to analyze. * * @param inputPath a collection of input paths * @return this operation @@ -379,7 +316,7 @@ public class PmdOperation extends AbstractOperation { /** * Sets the default language version to be used for all input files. * - * @param languageVersion a collection language versions + * @param languageVersion a collection language version * @return this operation */ public PmdOperation defaultLanguageVersions(Collection languageVersion) { @@ -733,7 +670,7 @@ public class PmdOperation extends AbstractOperation { } /** - * Sets paths to source files, or directories containing source files to analyze. + * Sets paths to source files or directories containing source files to analyze. * * @param inputPath one or more paths * @return this operation @@ -744,7 +681,7 @@ public class PmdOperation extends AbstractOperation { } /** - * Sets paths to source files, or directories containing source files to analyze. + * Sets paths to source files or directories containing source files to analyze. *

* Previous entries are disregarded. * @@ -757,7 +694,7 @@ public class PmdOperation extends AbstractOperation { } /** - * Sets paths to source files, or directories containing source files to analyze. + * Sets paths to source files or directories containing source files to analyze. *

* Previous entries are disregarded. * @@ -770,7 +707,7 @@ public class PmdOperation extends AbstractOperation { } /** - * Sets paths to source files, or directories containing source files to analyze. + * Sets paths to source files or directories containing source files to analyze. *

* Previous entries are disregarded. * @@ -785,7 +722,7 @@ public class PmdOperation extends AbstractOperation { } /** - * Returns paths to source files, or directories containing source files to analyze. + * Returns paths to source files or directories containing source files to analyze. * * @return the input paths */ @@ -794,7 +731,7 @@ public class PmdOperation extends AbstractOperation { } /** - * Sets paths to source files, or directories containing source files to analyze. + * Sets paths to source files or directories containing source files to analyze. *

* Previous entries are disregarded. * @@ -807,7 +744,7 @@ public class PmdOperation extends AbstractOperation { } /** - * Sets paths to source files, or directories containing source files to analyze. + * Sets paths to source files or directories containing source files to analyze. *

* Previous entries are disregarded. * @@ -832,7 +769,7 @@ public class PmdOperation extends AbstractOperation { /** * Sets the default language versions. * - * @param languageVersions a collection language versions + * @param languageVersions a collection language version * @return this operation */ public PmdOperation languageVersions(Collection languageVersions) { @@ -867,10 +804,12 @@ public class PmdOperation extends AbstractOperation { * @return the number of violations * @throws ExitStatusException if an error occurs */ - @SuppressWarnings({"PMD.CloseResource", "PMD.AvoidInstantiatingObjectsInLoops"}) - public int performPmdAnalysis(String commandName, PMDConfiguration config) throws ExitStatusException { + @SuppressWarnings("PMD.CloseResource") + public PmdAnalysisResults performPmdAnalysis(String commandName, PMDConfiguration config) + throws ExitStatusException { var pmd = PmdAnalysis.create(config); var report = pmd.performAnalysisAndCollectReport(); + if (LOGGER.isLoggable(Level.INFO) && !silent()) { LOGGER.log(Level.INFO, "[{0}] inputPaths{1}", new Object[]{commandName, inputPaths_}); LOGGER.log(Level.INFO, "[{0}] ruleSets{1}", new Object[]{commandName, ruleSets_}); @@ -878,52 +817,48 @@ public class PmdOperation extends AbstractOperation { var numViolations = report.getViolations().size(); if (numViolations > 0) { - for (var v : report.getViolations()) { - if (LOGGER.isLoggable(Level.WARNING) && !silent()) { - final String msg; - if (includeLineNumber_) { - msg = "[{0}] {1}:{2}\n\t{3} ({4})\n\t\t--> {5}"; - } else { - msg = "[{0}] {1} (line: {2})\n\t{3} ({4})\n\t\t--> {5}"; - } - LOGGER.log(Level.WARNING, msg, - new Object[]{commandName, - v.getFileId().getUriString(), - v.getBeginLine(), - v.getRule().getName(), - v.getRule().getExternalInfoUrl() //TODO bug in PMD? - .replace("${pmd.website.baseurl}", - "https://docs.pmd-code.org/latest"), - v.getDescription()}); - } - } - - var violations = String.format( - "[%s] %d rule violations were found. See the report at: %s", commandName, numViolations, - config.getReportFilePath().toUri()); - if (config.isFailOnViolation()) { - if (LOGGER.isLoggable(Level.SEVERE) && !silent()) { - LOGGER.log(Level.SEVERE, violations); - } - throw new ExitStatusException(ExitStatusException.EXIT_FAILURE); - } else if (LOGGER.isLoggable(Level.WARNING) && !silent()) { - LOGGER.warning(violations); - } + printViolations(commandName, config, report); } else if (pmd.getReporter().numErrors() > 0 && failOnError_) { throw new ExitStatusException(ExitStatusException.EXIT_FAILURE); - } else { - var rules = pmd.getRulesets(); - if (!rules.isEmpty()) { - int count = 0; - for (var rule : rules) { - count += rule.getRules().size(); - } - if (LOGGER.isLoggable(Level.INFO) && !silent()) { - LOGGER.info(String.format("[%s] %d rules were checked.", commandName, count)); - } + } + + int rulesChecked = 0; + var rules = pmd.getRulesets(); + if (!rules.isEmpty()) { + for (var rule : rules) { + rulesChecked += rule.getRules().size(); + } + if (LOGGER.isLoggable(Level.INFO) && !silent()) { + LOGGER.info(String.format("[%s] %d rules were checked.", commandName, rulesChecked)); } } - return numViolations; + + var result = new PmdAnalysisResults( + numViolations, + report.getSuppressedViolations().size(), + pmd.getReporter().numErrors(), + report.getProcessingErrors().size(), + report.getConfigurationErrors().size(), + rulesChecked + ); + + if (result.processingErrors() > 0 && LOGGER.isLoggable(Level.WARNING) && !silent()) { + for (var err : report.getProcessingErrors()) { + LOGGER.warning(String.format("[%s] %s", commandName, err.getMsg())); + } + } + + if (result.configurationErrors() > 0 && LOGGER.isLoggable(Level.WARNING) && !silent()) { + for (var err : report.getConfigurationErrors()) { + LOGGER.warning(String.format("[%s] %s", commandName, err.issue())); + } + } + + if (LOGGER.isLoggable(Level.FINEST) && !silent()) { + LOGGER.finest(result.toString()); + } + + return result; } /** @@ -931,7 +866,7 @@ public class PmdOperation extends AbstractOperation { * is currently configured, the ClassLoader used to load the PMDConfiguration class will be used as the parent * ClassLoader of the created ClassLoader. *

- * If the classpath String looks like a URL to a file (i.e. starts with {@code file://}) the file will be read with + * If the classpath String looks like a URL to a file (i.e., starts with {@code file://}) the file will be read with * each line representing an entry on the classpath. * * @param classpath one or more classpath @@ -951,6 +886,44 @@ public class PmdOperation extends AbstractOperation { return prependClasspath_; } + private void printViolations(String commandName, PMDConfiguration config, Report report) + throws ExitStatusException { + for (var v : report.getViolations()) { + if (LOGGER.isLoggable(Level.WARNING) && !silent()) { + final String msg; + if (includeLineNumber_) { + msg = "[%s] %s:%d\n\t%s (%s)\n\t\t--> %s"; + } else { + msg = "[%s] %s (line: %d)\n\t%s (%s)\n\t\t--> %s"; + } + LOGGER.log(Level.WARNING, + String.format(msg, + commandName, + v.getFileId().getUriString(), + v.getBeginLine(), + v.getRule().getName(), + v.getRule().getExternalInfoUrl(), + v.getDescription())); + } + } + + var violations = new StringBuilder( + String.format("[%s] %d rule violations were found.", commandName, report.getViolations().size())); + + if (config.getReportFilePath() != null) { + violations.append(" See the report at: ").append(config.getReportFilePath().toUri()); + } + + if (config.isFailOnViolation()) { + if (LOGGER.isLoggable(Level.SEVERE) && !silent()) { + LOGGER.log(Level.SEVERE, violations.toString()); + } + throw new ExitStatusException(ExitStatusException.EXIT_FAILURE); + } else if (LOGGER.isLoggable(Level.WARNING) && !silent()) { + LOGGER.warning(violations.toString()); + } + } + /** * Adds several paths to shorten paths that are output in the report. * diff --git a/src/test/java/rife/bld/extension/PmdAnalysisResultsTests.java b/src/test/java/rife/bld/extension/PmdAnalysisResultsTests.java new file mode 100644 index 0000000..5f632f1 --- /dev/null +++ b/src/test/java/rife/bld/extension/PmdAnalysisResultsTests.java @@ -0,0 +1,57 @@ +/* + * Copyright 2023-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package rife.bld.extension; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +class PmdAnalysisResultsTests { + @Nested + @DisplayName("Errors Tests") + class ErrorsTests { + @Test + void hasConfigurationErrors() { + var results = new PmdAnalysisResults( + 0, 0, 0, 0, 1, 0); + assertThat(results.hasNoErrors()).isFalse(); + } + + @Test + void hasErrors() { + var results = new PmdAnalysisResults( + 0, 0, 1, 0, 0, 0); + assertThat(results.hasNoErrors()).isFalse(); + } + + @Test + void hasNoErrors() { + var results = new PmdAnalysisResults( + 0, 0, 0, 0, 0, 0); + assertThat(results.hasNoErrors()).isTrue(); + } + + @Test + void hasProcessingErrors() { + var results = new PmdAnalysisResults( + 0, 0, 0, 1, 0, 0); + assertThat(results.hasNoErrors()).isFalse(); + } + } +} From be90889b8d16bb586c1c7ceb57544a9730552c1f Mon Sep 17 00:00:00 2001 From: "Erik C. Thauvin" Date: Sat, 31 May 2025 01:54:43 -0700 Subject: [PATCH 6/9] Rework tests to use the PmdAnalysisResults record --- .github/workflows/bld.yml | 3 +- .idea/misc.xml | 2 +- .../rife/bld/extension/PmdOperationTests.java | 989 ++++++++++-------- src/test/resources/{ => txt}/ignore-all.txt | 0 .../resources/{ => txt}/ignore-single.txt | 0 src/test/resources/{ => txt}/ignore.txt | 0 src/test/resources/{ => xml}/pmd.xml | 0 7 files changed, 547 insertions(+), 447 deletions(-) rename src/test/resources/{ => txt}/ignore-all.txt (100%) rename src/test/resources/{ => txt}/ignore-single.txt (100%) rename src/test/resources/{ => txt}/ignore.txt (100%) rename src/test/resources/{ => xml}/pmd.xml (100%) diff --git a/.github/workflows/bld.yml b/.github/workflows/bld.yml index 947f3c4..b827666 100644 --- a/.github/workflows/bld.yml +++ b/.github/workflows/bld.yml @@ -7,7 +7,6 @@ jobs: strategy: matrix: java-version: [ 17, 21, 24 ] - kotlin-version: [ 1.9.25, 2.0.21, 2.1.20 ] os: [ ubuntu-latest, windows-latest, macos-latest ] runs-on: ${{ matrix.os }} @@ -28,4 +27,4 @@ jobs: run: ./bld download - name: Run tests - run: ./bld compile test \ No newline at end of file + run: ./bld compile test diff --git a/.idea/misc.xml b/.idea/misc.xml index 8cfd866..88ff6dc 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -25,7 +25,7 @@