Compare commits

...

9 commits

13 changed files with 1037 additions and 725 deletions

View file

@ -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
run: ./bld compile test

2
.idea/misc.xml generated
View file

@ -25,7 +25,7 @@
</option>
<option name="skipTestSources" value="false" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="17" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build" />
</component>
</project>

View file

@ -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

View file

@ -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,24 +34,24 @@ public class PmdOperationBuild extends Project {
public PmdOperationBuild() {
pkg = "rife.bld.extension";
name = "bld-pmd";
version = version(1, 2, 3);
version = version(1, 3, 0);
javaRelease = 17;
downloadSources = true;
autoDownloadPurge = true;
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));
scope(runtime)
.include(dependency("org.slf4j", "slf4j-simple", version(2, 0, 17)));
scope(test)
.include(dependency("org.junit.jupiter", "junit-jupiter", version(5, 12, 2)))
.include(dependency("org.junit.platform", "junit-platform-console-standalone", version(1, 12, 2)))
.include(dependency("org.junit.jupiter", "junit-jupiter", version(5, 13, 0)))
.include(dependency("org.junit.platform", "junit-platform-console-standalone", version(1, 13, 0)))
.include(dependency("org.assertj", "assertj-core", version(3, 27, 3)));
javadocOperation()
@ -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;
}
}
}

View file

@ -20,7 +20,9 @@ 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.extension.pmd.PmdAnalysisResults;
import rife.bld.operations.AbstractOperation;
import rife.bld.operations.exceptions.ExitStatusException;
@ -41,105 +43,41 @@ import java.util.logging.Logger;
* @since 1.0
*/
public class PmdOperation extends AbstractOperation<PmdOperation> {
/**
* The default logger.
*/
public static final Logger LOGGER = Logger.getLogger(PmdOperation.class.getName());
/**
* The default rule set.
* <p>
* 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<Path> excludes_ = new ArrayList<>();
/**
* The input paths (source) list.
*/
private final Collection<Path> inputPaths_ = new ArrayList<>();
/**
* The relative roots paths.
*/
private final Collection<Path> relativizeRoots_ = new ArrayList<>();
/**
* The rule sets list.
*/
private final Collection<String> 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<LanguageVersion> 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 +154,18 @@ public class PmdOperation extends AbstractOperation<PmdOperation> {
}
/**
* 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 +176,7 @@ public class PmdOperation extends AbstractOperation<PmdOperation> {
}
/**
* 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 +187,7 @@ public class PmdOperation extends AbstractOperation<PmdOperation> {
}
/**
* 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 +199,7 @@ public class PmdOperation extends AbstractOperation<PmdOperation> {
}
/**
* 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 +210,7 @@ public class PmdOperation extends AbstractOperation<PmdOperation> {
}
/**
* 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
@ -353,6 +291,19 @@ public class PmdOperation extends AbstractOperation<PmdOperation> {
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.
*
@ -366,7 +317,7 @@ public class PmdOperation extends AbstractOperation<PmdOperation> {
/**
* 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> languageVersion) {
@ -625,6 +576,11 @@ public class PmdOperation extends AbstractOperation<PmdOperation> {
// addRelativizeRoots
config.addRelativizeRoots(relativizeRoots_.stream().toList());
// collectFilesRecursively
if (!collectFilesRecursively_) {
config.collectFilesRecursively(false);
}
// prependAuxClasspath
if (prependClasspath_ != null) {
config.prependAuxClasspath(prependClasspath_);
@ -715,7 +671,7 @@ public class PmdOperation extends AbstractOperation<PmdOperation> {
}
/**
* 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
@ -726,7 +682,7 @@ public class PmdOperation extends AbstractOperation<PmdOperation> {
}
/**
* Sets paths to source files, or directories containing source files to analyze.
* Sets paths to source files or directories containing source files to analyze.
* <p>
* Previous entries are disregarded.
*
@ -739,7 +695,7 @@ public class PmdOperation extends AbstractOperation<PmdOperation> {
}
/**
* Sets paths to source files, or directories containing source files to analyze.
* Sets paths to source files or directories containing source files to analyze.
* <p>
* Previous entries are disregarded.
*
@ -752,7 +708,7 @@ public class PmdOperation extends AbstractOperation<PmdOperation> {
}
/**
* Sets paths to source files, or directories containing source files to analyze.
* Sets paths to source files or directories containing source files to analyze.
* <p>
* Previous entries are disregarded.
*
@ -767,7 +723,7 @@ public class PmdOperation extends AbstractOperation<PmdOperation> {
}
/**
* 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
*/
@ -776,7 +732,7 @@ public class PmdOperation extends AbstractOperation<PmdOperation> {
}
/**
* Sets paths to source files, or directories containing source files to analyze.
* Sets paths to source files or directories containing source files to analyze.
* <p>
* Previous entries are disregarded.
*
@ -789,7 +745,7 @@ public class PmdOperation extends AbstractOperation<PmdOperation> {
}
/**
* Sets paths to source files, or directories containing source files to analyze.
* Sets paths to source files or directories containing source files to analyze.
* <p>
* Previous entries are disregarded.
*
@ -814,7 +770,7 @@ public class PmdOperation extends AbstractOperation<PmdOperation> {
/**
* Sets the default language versions.
*
* @param languageVersions a collection language versions
* @param languageVersions a collection language version
* @return this operation
*/
public PmdOperation languageVersions(Collection<LanguageVersion> languageVersions) {
@ -849,63 +805,66 @@ public class PmdOperation extends AbstractOperation<PmdOperation> {
* @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 {
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_});
}
public PmdAnalysisResults performPmdAnalysis(String commandName, PMDConfiguration config)
throws ExitStatusException {
try (var pmd = PmdAnalysis.create(config)) {
var report = pmd.performAnalysisAndCollectReport();
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()});
}
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_});
}
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);
}
var numViolations = report.getViolations().size();
if (numViolations > 0) {
printViolations(commandName, config, report);
} else if (pmd.getReporter().numErrors() > 0 && failOnError_) {
throw new ExitStatusException(ExitStatusException.EXIT_FAILURE);
} else if (LOGGER.isLoggable(Level.WARNING) && !silent()) {
LOGGER.warning(violations);
}
} else if (pmd.getReporter().numErrors() > 0 && failOnError_) {
throw new ExitStatusException(ExitStatusException.EXIT_FAILURE);
} else {
var rulesChecked = 0;
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));
rulesChecked += rule.getRules().size();
}
}
var result = new PmdAnalysisResults(
numViolations,
report.getSuppressedViolations().size(),
pmd.getReporter().numErrors(),
report.getProcessingErrors().size(),
report.getConfigurationErrors().size(),
rulesChecked
);
if (!silent()) {
if (LOGGER.isLoggable(Level.INFO)) {
LOGGER.info(String.format("[%s] %d rules were checked.", commandName, result.rulesChecked()));
}
if (LOGGER.isLoggable(Level.WARNING)) {
if (result.processingErrors() > 0) {
for (var err : report.getProcessingErrors()) {
LOGGER.warning(String.format("[%s] %s", commandName, err.getMsg()));
}
}
if (result.configurationErrors() > 0) {
for (var err : report.getConfigurationErrors()) {
LOGGER.warning(String.format("[%s] %s", commandName, err.issue()));
}
}
}
if (LOGGER.isLoggable(Level.FINEST)) {
LOGGER.finest(result.toString());
}
}
return result;
}
return numViolations;
}
/**
@ -913,7 +872,7 @@ public class PmdOperation extends AbstractOperation<PmdOperation> {
* is currently configured, the ClassLoader used to load the PMDConfiguration class will be used as the parent
* ClassLoader of the created ClassLoader.
* <p>
* 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
@ -933,6 +892,44 @@ public class PmdOperation extends AbstractOperation<PmdOperation> {
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.
*

View file

@ -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.pmd;
/**
* 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;
}
}

View file

@ -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 <a href="https://erik.thauvin.net/">Erik C. Thauvin</a>
* @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("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
}
}
}
@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);
}
}

View file

@ -0,0 +1,761 @@
/*
* 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.BeforeAll;
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 java.util.logging.ConsoleHandler;
import java.util.logging.Level;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
/**
* PmdOperationTests class
*
* @author <a href="https://erik.thauvin.net/">Erik C. Thauvin</a>
* @since 1.0
*/
class PmdOperationTests {
private static final String ANALYSIS_FAILURE = "analysis should fail";
private static final String ANALYSIS_SUCCESS = "analysis should succeed";
private static final String BAR = "bar";
private static final String CATEGORY_FOO = "category/foo.xml";
private static final Path CODE_STYLE_SAMPLE = Path.of("src/test/resources/java/CodeStyle.java");
private static final String CODE_STYLE_XML = "category/java/codestyle.xml";
private static final String COMMAND_NAME = "pmd";
private static final String DESIGN_XML = "category/java/design.xml";
private static final String DOCUMENTATION_XML = "category/java/documentation.xml";
private static final Path ERROR_PRONE_SAMPLE = Path.of("src/test/resources/java/ErrorProne.java");
private static final String ERROR_PRONE_XML = "category/java/errorprone.xml";
private static final File FILE_BAR = new File(BAR);
private static final String FOO = "foo";
private static final File FILE_FOO = new File(FOO);
private static final Path PATH_BAR = Path.of(BAR);
private static final Path PATH_FOO = Path.of(FOO);
private static final String PERFORMANCE_XML = "category/java/performance.xml";
private static final String SECURITY_XML = "category/java/security.xml";
private static final String TEST = "test";
@BeforeAll
public static void beforeAll() {
var consoleHandler = new ConsoleHandler();
var logger = PmdOperation.LOGGER;
consoleHandler.setLevel(Level.ALL);
logger.addHandler(consoleHandler);
logger.setLevel(Level.ALL);
logger.setUseParentHandlers(false);
}
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)).rulesChecked())
.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 collectFilesRecursively() {
var pmd = newPmdOperation().ruleSets(ERROR_PRONE_XML).collectFilesRecursively(false);
var config = pmd.initConfiguration(COMMAND_NAME);
assertThat(config.collectFilesRecursively()).isFalse();
}
@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 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)).processingErrors())
.as("should have processing errors").isGreaterThan(0);
assertThat(pmd.defaultLanguageVersions(language.getVersion("17"), language.getVersion("21"))
.languageVersions(language.getVersions())
.excludesStrings("src/test/resources/txt", "src/test/resources/xml")
.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME)).configurationErrors())
.as("should have no processing errors").isEqualTo(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)).rulesChecked())
.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("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("Ignore File Tests")
class IgnoreFileTests {
@Test
void ignoreFile() throws ExitStatusException {
var pmd = newPmdOperation()
.ruleSets(ERROR_PRONE_XML, CODE_STYLE_XML)
.inputPaths(ERROR_PRONE_SAMPLE, CODE_STYLE_SAMPLE)
.ignoreFile(Path.of("src/test/resources/txt/ignore.txt"));
assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME)).violations())
.as("%s for error prone and code style", ANALYSIS_SUCCESS).isEqualTo(0);
}
@Test
void ignoreSingleFile() throws ExitStatusException {
var pmd = newPmdOperation()
.ruleSets(ERROR_PRONE_XML)
.inputPaths(ERROR_PRONE_SAMPLE)
.ignoreFile(new File("src/test/resources/txt/ignore-single.txt"));
assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME)).violations())
.as("%s for error prone sample", ANALYSIS_SUCCESS).isEqualTo(0);
}
}
@Nested
@DisplayName("Input Paths Tests")
class InputPathsTests {
@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)).violations())
.as(ANALYSIS_FAILURE).isGreaterThan(0);
}
@Test
void mainOperation() throws ExitStatusException {
var pmd = newPmdOperation()
.inputPaths(new File("src/main"))
.performPmdAnalysis(TEST, newPmdOperation().initConfiguration(COMMAND_NAME))
.violations();
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)).violations())
.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)).violations())
.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)).violations())
.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)).violations())
.as(ANALYSIS_FAILURE).isGreaterThan(0);
}
@Test
void xml() throws ExitStatusException {
var pmd = newPmdOperation().addInputPaths("src/test/resources/xml/pmd.xml")
.ruleSets("src/test/resources/xml/basic.xml");
assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME)).configurationErrors())
.isEqualTo(0);
}
@Nested
@DisplayName("Add Input Paths Tests")
class AddInputPathsTests {
final BaseProject project = new BaseProject();
@Test
void addInputPathsAsFileArray() {
var pmd = new PmdOperation().fromProject(project);
pmd.inputPaths().clear();
pmd = pmd.addInputPaths(project.srcMainDirectory(), project.srcTestDirectory());
assertThat(pmd.inputPaths()).as("File...").containsExactly(project.srcMainDirectory().toPath(),
project.srcTestDirectory().toPath());
}
@Test
void addInputPathsAsFileList() {
var pmd = new PmdOperation().fromProject(project);
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());
}
@Test
void addInputPathsAsPathArray() {
var pmd = new PmdOperation().fromProject(project);
pmd.inputPaths().clear();
pmd = pmd.addInputPaths(project.srcMainDirectory().toPath(), project.srcTestDirectory().toPath());
assertThat(pmd.inputPaths()).as("Path...")
.containsExactly(project.srcMainDirectory().toPath(), project.srcTestDirectory().toPath());
}
@Test
void addInputPathsAsPathList() {
var pmd = new PmdOperation().fromProject(project);
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());
}
@Test
void addInputPathsAsStringArray() {
var pmd = new PmdOperation().fromProject(project);
pmd.inputPaths().clear();
pmd = pmd.addInputPaths(project.srcMainDirectory().getAbsolutePath(),
project.srcTestDirectory().getAbsolutePath());
assertThat(pmd.inputPaths()).as("String...")
.containsExactly(project.srcMainDirectory().toPath(), project.srcTestDirectory().toPath());
}
@Test
void addInputPathsAsStringList() {
var pmd = new PmdOperation().fromProject(project);
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());
}
@Test
void addInputPathsWithDefaults() {
var pmd = new PmdOperation().fromProject(project);
assertThat(pmd.inputPaths()).as("default input paths")
.containsExactly(project.srcMainDirectory().toPath(), project.srcTestDirectory().toPath());
}
}
}
@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)).violations())
.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("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
}
}
}
@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)).processingErrors())
.isEqualTo(0);
}
}
@Nested
@DisplayName("RuleSets Tests")
class RuleSetsTests {
@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)).violations())
.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)).violations())
.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)).violations())
.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)).violations())
.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)).violations())
.as(ANALYSIS_FAILURE).isGreaterThan(0);
assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME)).configurationErrors())
.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)).violations())
.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)).violations())
.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)).violations())
.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)).violations())
.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)).violations())
.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)).violations())
.as(ANALYSIS_FAILURE).isGreaterThan(0);
}
@Test
void ruleSetsConfigFile() throws ExitStatusException {
var pmd = newPmdOperation()
.ruleSets("src/test/resources/xml/pmd.xml")
.ignoreFile("src/test/resources/txt/ignore-all.txt");
assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME)).hasNoErrors()).isTrue();
}
@Test
void ruleSetsEmpty() throws ExitStatusException {
var pmd = newPmdOperation().ruleSets("").failOnError(false);
assertThat(pmd.performPmdAnalysis(TEST, pmd.initConfiguration(COMMAND_NAME)).errors())
.isEqualTo(1);
}
@Nested
@DisplayName("Add RuleSets Tests")
class AddRuleSetsTests {
@Test
void addRuleSetsWithDefault() {
var pmd = new PmdOperation().fromProject(new BaseProject());
assertThat(pmd.ruleSets()).containsExactly(PmdOperation.RULE_SET_DEFAULT);
}
@Test
void addRuleSetsWithErrorProne() {
var pmd = new PmdOperation().fromProject(new BaseProject());
pmd.addRuleSet(ERROR_PRONE_XML);
assertThat(pmd.ruleSets()).containsExactly(PmdOperation.RULE_SET_DEFAULT, ERROR_PRONE_XML);
}
@Test
void addRuleSetsWithList() {
var pmd = new PmdOperation().fromProject(new BaseProject());
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);
}
}
}
}
@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)).violations())
.as(ANALYSIS_SUCCESS).isEqualTo(0);
}
@Test
void executeNoProject() {
var pmd = new PmdOperation();
assertThatCode(pmd::execute).isInstanceOf(ExitStatusException.class);
}
}
}

View file

@ -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.pmd;
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();
}
}
}