This is an automated email from the ASF dual-hosted git repository. adangel pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/maven-pmd-plugin.git
The following commit(s) were added to refs/heads/master by this push: new 76f7e11 [MPMD-395] Build doesn't fail for invalid CPD format (#150) 76f7e11 is described below commit 76f7e114a8ac92898c0307daac8dea6a44a149bb Author: Andreas Dangel <adan...@apache.org> AuthorDate: Fri Jun 7 09:21:52 2024 +0200 [MPMD-395] Build doesn't fail for invalid CPD format (#150) This closes #150 --- .../apache/maven/plugins/pmd/exec/CpdExecutor.java | 84 +++------------ .../maven/plugins/pmd/exec/CpdReportConsumer.java | 116 +++++++++++++++++++++ .../apache/maven/plugins/pmd/CpdReportTest.java | 33 +++++- .../maven/plugins/pmd/exec/ExecutorTest.java | 2 +- .../unit/CpdReportTest/with-cpd-errors/pom.xml | 52 +++++++++ .../src/main/java/sample/BadFile.java | 26 +++++ .../cpd-invalid-format-plugin-config.xml | 2 +- 7 files changed, 239 insertions(+), 76 deletions(-) diff --git a/src/main/java/org/apache/maven/plugins/pmd/exec/CpdExecutor.java b/src/main/java/org/apache/maven/plugins/pmd/exec/CpdExecutor.java index 3cdd575..80fee05 100644 --- a/src/main/java/org/apache/maven/plugins/pmd/exec/CpdExecutor.java +++ b/src/main/java/org/apache/maven/plugins/pmd/exec/CpdExecutor.java @@ -24,25 +24,19 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; -import java.io.OutputStreamWriter; -import java.io.Writer; import java.nio.charset.Charset; import java.util.Objects; -import java.util.function.Predicate; import net.sourceforge.pmd.cpd.CPDConfiguration; -import net.sourceforge.pmd.cpd.CPDReport; import net.sourceforge.pmd.cpd.CPDReportRenderer; import net.sourceforge.pmd.cpd.CSVRenderer; import net.sourceforge.pmd.cpd.CpdAnalysis; -import net.sourceforge.pmd.cpd.Match; import net.sourceforge.pmd.cpd.SimpleRenderer; import net.sourceforge.pmd.cpd.XMLRenderer; import net.sourceforge.pmd.lang.Language; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugins.pmd.ExcludeDuplicationsFromFile; import org.apache.maven.reporting.MavenReportException; -import org.codehaus.plexus.util.FileUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -152,6 +146,8 @@ public class CpdExecutor extends Executor { cpdConfiguration.setIgnoreAnnotations(request.isIgnoreAnnotations()); cpdConfiguration.setIgnoreLiterals(request.isIgnoreLiterals()); cpdConfiguration.setIgnoreIdentifiers(request.isIgnoreIdentifiers()); + // we are not running CPD through CLI and deal with any errors during analysis on our own + cpdConfiguration.setSkipLexicalErrors(true); String languageId = request.getLanguage(); if ("javascript".equals(languageId)) { @@ -167,64 +163,24 @@ public class CpdExecutor extends Executor { request.getFiles().forEach(f -> cpdConfiguration.addInputPath(f.toPath())); LOG.debug("Executing CPD..."); - - // always create XML format. we need to output it even if the file list is empty or we have no duplications - // so the "check" goals can check for violations try (CpdAnalysis cpd = CpdAnalysis.create(cpdConfiguration)) { - cpd.performAnalysis(report -> { - try { - writeXmlReport(report); - - // html format is handled by maven site report, xml format has already been rendered - String format = request.getFormat(); - if (!"html".equals(format) && !"xml".equals(format)) { - writeFormattedReport(report); - } - } catch (MavenReportException e) { - LOG.error("Error while writing CPD report", e); - } - }); + CpdReportConsumer reportConsumer = new CpdReportConsumer(request, excludeDuplicationsFromFile); + cpd.performAnalysis(reportConsumer); } catch (IOException e) { - LOG.error("Error while executing CPD", e); + throw new MavenReportException("Error while executing CPD", e); } LOG.debug("CPD finished."); - return new CpdResult(new File(request.getTargetDirectory(), "cpd.xml"), request.getOutputEncoding()); - } - - private void writeXmlReport(CPDReport cpd) throws MavenReportException { - File targetFile = writeReport(cpd, new XMLRenderer(request.getOutputEncoding()), "xml"); - if (request.isIncludeXmlInSite()) { - File siteDir = new File(request.getReportOutputDirectory()); - siteDir.mkdirs(); - try { - FileUtils.copyFile(targetFile, new File(siteDir, "cpd.xml")); - } catch (IOException e) { - throw new MavenReportException(e.getMessage(), e); - } - } - } - - private File writeReport(CPDReport cpd, CPDReportRenderer r, String extension) throws MavenReportException { - if (r == null) { - return null; + // in constrast to pmd goal, we don't have a parameter for cpd like "skipPmdError" - if there + // are any errors during CPD analysis, the maven build fails. + int cpdErrors = cpdConfiguration.getReporter().numErrors(); + if (cpdErrors == 1) { + throw new MavenReportException("There was 1 error while executing CPD"); + } else if (cpdErrors > 1) { + throw new MavenReportException("There were " + cpdErrors + " errors while executing CPD"); } - File targetDir = new File(request.getTargetDirectory()); - targetDir.mkdirs(); - File targetFile = new File(targetDir, "cpd." + extension); - try (Writer writer = new OutputStreamWriter(new FileOutputStream(targetFile), request.getOutputEncoding())) { - r.render(cpd.filterMatches(filterMatches()), writer); - writer.flush(); - } catch (IOException ioe) { - throw new MavenReportException(ioe.getMessage(), ioe); - } - return targetFile; - } - - private void writeFormattedReport(CPDReport cpd) throws MavenReportException { - CPDReportRenderer r = createRenderer(request.getFormat(), request.getOutputEncoding()); - writeReport(cpd, r, request.getFormat()); + return new CpdResult(new File(request.getTargetDirectory(), "cpd.xml"), request.getOutputEncoding()); } /** @@ -255,18 +211,4 @@ public class CpdExecutor extends Executor { return renderer; } - - private Predicate<Match> filterMatches() { - return (Match match) -> { - LOG.debug("Filtering duplications. Using " + excludeDuplicationsFromFile.countExclusions() - + " configured exclusions."); - - if (excludeDuplicationsFromFile.isExcludedFromFailure(match)) { - LOG.debug("Excluded " + match + " duplications."); - return false; - } else { - return true; - } - }; - } } diff --git a/src/main/java/org/apache/maven/plugins/pmd/exec/CpdReportConsumer.java b/src/main/java/org/apache/maven/plugins/pmd/exec/CpdReportConsumer.java new file mode 100644 index 0000000..f383e82 --- /dev/null +++ b/src/main/java/org/apache/maven/plugins/pmd/exec/CpdReportConsumer.java @@ -0,0 +1,116 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * http://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 org.apache.maven.plugins.pmd.exec; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.util.function.Consumer; +import java.util.function.Predicate; + +import net.sourceforge.pmd.cpd.CPDReport; +import net.sourceforge.pmd.cpd.CPDReportRenderer; +import net.sourceforge.pmd.cpd.Match; +import net.sourceforge.pmd.cpd.XMLRenderer; +import org.apache.maven.plugins.pmd.ExcludeDuplicationsFromFile; +import org.apache.maven.reporting.MavenReportException; +import org.codehaus.plexus.util.FileUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +class CpdReportConsumer implements Consumer<CPDReport> { + private static final Logger LOG = LoggerFactory.getLogger(CpdReportConsumer.class); + + private final CpdRequest request; + private final ExcludeDuplicationsFromFile excludeDuplicationsFromFile; + + CpdReportConsumer(CpdRequest request, ExcludeDuplicationsFromFile excludeDuplicationsFromFile) { + this.request = request; + this.excludeDuplicationsFromFile = excludeDuplicationsFromFile; + } + + @Override + public void accept(CPDReport report) { + try { + // always create XML format. we need to output it even if the file list is empty or we have no + // duplications so that the "check" goals can check for violations + writeXmlReport(report); + + // HTML format is handled by maven site report, XML format has already been rendered. + // a renderer is only needed for other formats + String format = request.getFormat(); + if (!"html".equals(format) && !"xml".equals(format)) { + writeFormattedReport(report); + } + } catch (IOException | MavenReportException e) { + throw new RuntimeException(e); + } + } + + private void writeXmlReport(CPDReport cpd) throws IOException { + File targetFile = writeReport(cpd, new XMLRenderer(request.getOutputEncoding()), "xml"); + if (request.isIncludeXmlInSite()) { + File siteDir = new File(request.getReportOutputDirectory()); + if (!siteDir.exists() && !siteDir.mkdirs()) { + throw new IOException("Couldn't create report output directory: " + siteDir); + } + FileUtils.copyFile(targetFile, new File(siteDir, "cpd.xml")); + } + } + + private void writeFormattedReport(CPDReport cpd) throws IOException, MavenReportException { + CPDReportRenderer r = CpdExecutor.createRenderer(request.getFormat(), request.getOutputEncoding()); + writeReport(cpd, r, request.getFormat()); + } + + private File writeReport(CPDReport cpd, CPDReportRenderer renderer, String extension) throws IOException { + if (renderer == null) { + return null; + } + + File targetDir = new File(request.getTargetDirectory()); + if (!targetDir.exists() && !targetDir.mkdirs()) { + throw new IOException("Couldn't create report output directory: " + targetDir); + } + + File targetFile = new File(targetDir, "cpd." + extension); + try (Writer writer = new OutputStreamWriter(new FileOutputStream(targetFile), request.getOutputEncoding())) { + renderer.render(cpd.filterMatches(filterMatches()), writer); + writer.flush(); + } + return targetFile; + } + + private Predicate<Match> filterMatches() { + return (Match match) -> { + LOG.debug( + "Filtering duplications. Using {} configured exclusions.", + excludeDuplicationsFromFile.countExclusions()); + + if (excludeDuplicationsFromFile.isExcludedFromFailure(match)) { + LOG.debug("Excluded {} duplications.", match); + return false; + } else { + return true; + } + }; + } +} diff --git a/src/test/java/org/apache/maven/plugins/pmd/CpdReportTest.java b/src/test/java/org/apache/maven/plugins/pmd/CpdReportTest.java index b7657bf..7b47026 100644 --- a/src/test/java/org/apache/maven/plugins/pmd/CpdReportTest.java +++ b/src/test/java/org/apache/maven/plugins/pmd/CpdReportTest.java @@ -26,6 +26,7 @@ import java.util.Locale; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.maven.reporting.MavenReportException; import org.w3c.dom.Document; /** @@ -65,8 +66,7 @@ public class CpdReportTest extends AbstractPmdReportTestCase { assertTrue(lowerCaseContains(str, "tmp = tmp + str.substring( i, i + 1);")); // the version should be logged - String output = CapturingPrintStream.getOutput(); - assertTrue(output.contains("PMD version: " + AbstractPmdReport.getPmdVersion())); + assertLogOutputContains("PMD version: " + AbstractPmdReport.getPmdVersion()); } /** @@ -130,7 +130,8 @@ public class CpdReportTest extends AbstractPmdReportTestCase { fail("MavenReportException must be thrown"); } catch (Exception e) { - assertTrue(true); + assertMavenReportException("There was 1 error while executing CPD", e); + assertLogOutputContains("Can't find CPD custom format xhtml"); } } @@ -240,4 +241,30 @@ public class CpdReportTest extends AbstractPmdReportTestCase { String str = readFile(generatedFile); assertEquals(0, StringUtils.countMatches(str, "<duplication")); } + + public void testWithCpdErrors() throws Exception { + try { + generateReport("cpd", "CpdReportTest/with-cpd-errors/pom.xml"); + fail("MavenReportException must be thrown"); + } catch (Exception e) { + assertMavenReportException("There was 1 error while executing CPD", e); + assertLogOutputContains("Lexical error in file"); + assertLogOutputContains("BadFile.java"); + } + } + + private static void assertMavenReportException(String expectedMessage, Exception exception) { + // The maven report exception might be wrapped in a RuntimeException + assertTrue( + "Expected MavenReportException, but was: " + exception, + exception instanceof MavenReportException || exception.getCause() instanceof MavenReportException); + assertTrue( + "Wrong message: expected: " + expectedMessage + ", but was: " + exception.toString(), + exception.toString().contains(expectedMessage)); + } + + private static void assertLogOutputContains(String expectedMessage) { + String log = CapturingPrintStream.getOutput(); + assertTrue("Expected '" + expectedMessage + "' in log, but was:\n" + log, log.contains(expectedMessage)); + } } diff --git a/src/test/java/org/apache/maven/plugins/pmd/exec/ExecutorTest.java b/src/test/java/org/apache/maven/plugins/pmd/exec/ExecutorTest.java index fadd719..3aaaf4a 100644 --- a/src/test/java/org/apache/maven/plugins/pmd/exec/ExecutorTest.java +++ b/src/test/java/org/apache/maven/plugins/pmd/exec/ExecutorTest.java @@ -23,9 +23,9 @@ import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; -import junit.framework.Assert; import junit.framework.TestCase; import org.apache.commons.lang3.SystemUtils; +import org.junit.Assert; public class ExecutorTest extends TestCase { public void testBuildClasspath() throws MalformedURLException { diff --git a/src/test/resources/unit/CpdReportTest/with-cpd-errors/pom.xml b/src/test/resources/unit/CpdReportTest/with-cpd-errors/pom.xml new file mode 100644 index 0000000..b080ed0 --- /dev/null +++ b/src/test/resources/unit/CpdReportTest/with-cpd-errors/pom.xml @@ -0,0 +1,52 @@ +<!-- +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you 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 + + http://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. +--> + +<project> + <modelVersion>4.0.0</modelVersion> + <groupId>unit.CpdReportTest</groupId> + <artifactId>cpd-configuration-with-pmd-errors</artifactId> + <packaging>jar</packaging> + <version>1.0-SNAPSHOT</version> + <inceptionYear>2006</inceptionYear> + <name>Maven CPD Plugin Test for failing build when CPD errors occurred</name> + <url>http://maven.apache.org</url> + <build> + <finalName>with-cpd-errors</finalName> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-pmd-plugin</artifactId> + <configuration> + <project implementation="org.apache.maven.plugins.pmd.stubs.DefaultConfigurationMavenProjectStub"/> + <outputDirectory>${basedir}/target/test/unit/CpdReportTest/with-cpd-errors/target/site</outputDirectory> + <targetDirectory>${basedir}/target/test/unit/CpdReportTest/with-cpd-errors/target</targetDirectory> + <localRepository>${localRepository}</localRepository> + <format>xml</format> + <linkXRef>false</linkXRef> + <xrefLocation>${basedir}/target/test/unit/CpdReportTest/with-cpd-errors/target/site/xref</xrefLocation> + <minimumTokens>100</minimumTokens> + <compileSourceRoots> + <compileSourceRoot>${basedir}/src/test/resources/unit/CpdReportTest/with-cpd-errors/src/main/java</compileSourceRoot> + </compileSourceRoots> + <inputEncoding>UTF-8</inputEncoding> + </configuration> + </plugin> + </plugins> + </build> +</project> diff --git a/src/test/resources/unit/CpdReportTest/with-cpd-errors/src/main/java/sample/BadFile.java b/src/test/resources/unit/CpdReportTest/with-cpd-errors/src/main/java/sample/BadFile.java new file mode 100644 index 0000000..49ca2c2 --- /dev/null +++ b/src/test/resources/unit/CpdReportTest/with-cpd-errors/src/main/java/sample/BadFile.java @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * http://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 sample; + +public class BadFile { + public void foo() { + // this is a bad character � it's U+FFFD REPLACEMENT CHARACTER + int a�b = 1; + } +} diff --git a/src/test/resources/unit/invalid-format/cpd-invalid-format-plugin-config.xml b/src/test/resources/unit/invalid-format/cpd-invalid-format-plugin-config.xml index aca6d08..77ec940 100644 --- a/src/test/resources/unit/invalid-format/cpd-invalid-format-plugin-config.xml +++ b/src/test/resources/unit/invalid-format/cpd-invalid-format-plugin-config.xml @@ -37,7 +37,7 @@ under the License. <outputDirectory>${basedir}/target/test/unit/invalid-format/target/site</outputDirectory> <targetDirectory>${basedir}/target/test/unit/invalid-format/target</targetDirectory> <localRepository>${localRepository}</localRepository> - <format>xhtml</format> + <format>xhtml</format> <!-- format "xhtml" does not exist --> <linkXRef>false</linkXRef> <xrefLocation>${basedir}/target/test/unit/invalid-format/target/site/xref</xrefLocation> <minimumTokens>25</minimumTokens>