Skip to content

Commit 76f7e11

Browse files
authored
[MPMD-395] Build doesn't fail for invalid CPD format (#150)
This closes #150
1 parent c541707 commit 76f7e11

File tree

7 files changed

+239
-76
lines changed

7 files changed

+239
-76
lines changed

src/main/java/org/apache/maven/plugins/pmd/exec/CpdExecutor.java

+13-71
Original file line numberDiff line numberDiff line change
@@ -24,25 +24,19 @@
2424
import java.io.IOException;
2525
import java.io.ObjectInputStream;
2626
import java.io.ObjectOutputStream;
27-
import java.io.OutputStreamWriter;
28-
import java.io.Writer;
2927
import java.nio.charset.Charset;
3028
import java.util.Objects;
31-
import java.util.function.Predicate;
3229

3330
import net.sourceforge.pmd.cpd.CPDConfiguration;
34-
import net.sourceforge.pmd.cpd.CPDReport;
3531
import net.sourceforge.pmd.cpd.CPDReportRenderer;
3632
import net.sourceforge.pmd.cpd.CSVRenderer;
3733
import net.sourceforge.pmd.cpd.CpdAnalysis;
38-
import net.sourceforge.pmd.cpd.Match;
3934
import net.sourceforge.pmd.cpd.SimpleRenderer;
4035
import net.sourceforge.pmd.cpd.XMLRenderer;
4136
import net.sourceforge.pmd.lang.Language;
4237
import org.apache.maven.plugin.MojoExecutionException;
4338
import org.apache.maven.plugins.pmd.ExcludeDuplicationsFromFile;
4439
import org.apache.maven.reporting.MavenReportException;
45-
import org.codehaus.plexus.util.FileUtils;
4640
import org.slf4j.Logger;
4741
import org.slf4j.LoggerFactory;
4842

@@ -152,6 +146,8 @@ private CpdResult run() throws MavenReportException {
152146
cpdConfiguration.setIgnoreAnnotations(request.isIgnoreAnnotations());
153147
cpdConfiguration.setIgnoreLiterals(request.isIgnoreLiterals());
154148
cpdConfiguration.setIgnoreIdentifiers(request.isIgnoreIdentifiers());
149+
// we are not running CPD through CLI and deal with any errors during analysis on our own
150+
cpdConfiguration.setSkipLexicalErrors(true);
155151

156152
String languageId = request.getLanguage();
157153
if ("javascript".equals(languageId)) {
@@ -167,64 +163,24 @@ private CpdResult run() throws MavenReportException {
167163
request.getFiles().forEach(f -> cpdConfiguration.addInputPath(f.toPath()));
168164

169165
LOG.debug("Executing CPD...");
170-
171-
// always create XML format. we need to output it even if the file list is empty or we have no duplications
172-
// so the "check" goals can check for violations
173166
try (CpdAnalysis cpd = CpdAnalysis.create(cpdConfiguration)) {
174-
cpd.performAnalysis(report -> {
175-
try {
176-
writeXmlReport(report);
177-
178-
// html format is handled by maven site report, xml format has already been rendered
179-
String format = request.getFormat();
180-
if (!"html".equals(format) && !"xml".equals(format)) {
181-
writeFormattedReport(report);
182-
}
183-
} catch (MavenReportException e) {
184-
LOG.error("Error while writing CPD report", e);
185-
}
186-
});
167+
CpdReportConsumer reportConsumer = new CpdReportConsumer(request, excludeDuplicationsFromFile);
168+
cpd.performAnalysis(reportConsumer);
187169
} catch (IOException e) {
188-
LOG.error("Error while executing CPD", e);
170+
throw new MavenReportException("Error while executing CPD", e);
189171
}
190172
LOG.debug("CPD finished.");
191173

192-
return new CpdResult(new File(request.getTargetDirectory(), "cpd.xml"), request.getOutputEncoding());
193-
}
194-
195-
private void writeXmlReport(CPDReport cpd) throws MavenReportException {
196-
File targetFile = writeReport(cpd, new XMLRenderer(request.getOutputEncoding()), "xml");
197-
if (request.isIncludeXmlInSite()) {
198-
File siteDir = new File(request.getReportOutputDirectory());
199-
siteDir.mkdirs();
200-
try {
201-
FileUtils.copyFile(targetFile, new File(siteDir, "cpd.xml"));
202-
} catch (IOException e) {
203-
throw new MavenReportException(e.getMessage(), e);
204-
}
205-
}
206-
}
207-
208-
private File writeReport(CPDReport cpd, CPDReportRenderer r, String extension) throws MavenReportException {
209-
if (r == null) {
210-
return null;
174+
// in constrast to pmd goal, we don't have a parameter for cpd like "skipPmdError" - if there
175+
// are any errors during CPD analysis, the maven build fails.
176+
int cpdErrors = cpdConfiguration.getReporter().numErrors();
177+
if (cpdErrors == 1) {
178+
throw new MavenReportException("There was 1 error while executing CPD");
179+
} else if (cpdErrors > 1) {
180+
throw new MavenReportException("There were " + cpdErrors + " errors while executing CPD");
211181
}
212182

213-
File targetDir = new File(request.getTargetDirectory());
214-
targetDir.mkdirs();
215-
File targetFile = new File(targetDir, "cpd." + extension);
216-
try (Writer writer = new OutputStreamWriter(new FileOutputStream(targetFile), request.getOutputEncoding())) {
217-
r.render(cpd.filterMatches(filterMatches()), writer);
218-
writer.flush();
219-
} catch (IOException ioe) {
220-
throw new MavenReportException(ioe.getMessage(), ioe);
221-
}
222-
return targetFile;
223-
}
224-
225-
private void writeFormattedReport(CPDReport cpd) throws MavenReportException {
226-
CPDReportRenderer r = createRenderer(request.getFormat(), request.getOutputEncoding());
227-
writeReport(cpd, r, request.getFormat());
183+
return new CpdResult(new File(request.getTargetDirectory(), "cpd.xml"), request.getOutputEncoding());
228184
}
229185

230186
/**
@@ -255,18 +211,4 @@ public static CPDReportRenderer createRenderer(String format, String outputEncod
255211

256212
return renderer;
257213
}
258-
259-
private Predicate<Match> filterMatches() {
260-
return (Match match) -> {
261-
LOG.debug("Filtering duplications. Using " + excludeDuplicationsFromFile.countExclusions()
262-
+ " configured exclusions.");
263-
264-
if (excludeDuplicationsFromFile.isExcludedFromFailure(match)) {
265-
LOG.debug("Excluded " + match + " duplications.");
266-
return false;
267-
} else {
268-
return true;
269-
}
270-
};
271-
}
272214
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.maven.plugins.pmd.exec;
20+
21+
import java.io.File;
22+
import java.io.FileOutputStream;
23+
import java.io.IOException;
24+
import java.io.OutputStreamWriter;
25+
import java.io.Writer;
26+
import java.util.function.Consumer;
27+
import java.util.function.Predicate;
28+
29+
import net.sourceforge.pmd.cpd.CPDReport;
30+
import net.sourceforge.pmd.cpd.CPDReportRenderer;
31+
import net.sourceforge.pmd.cpd.Match;
32+
import net.sourceforge.pmd.cpd.XMLRenderer;
33+
import org.apache.maven.plugins.pmd.ExcludeDuplicationsFromFile;
34+
import org.apache.maven.reporting.MavenReportException;
35+
import org.codehaus.plexus.util.FileUtils;
36+
import org.slf4j.Logger;
37+
import org.slf4j.LoggerFactory;
38+
39+
class CpdReportConsumer implements Consumer<CPDReport> {
40+
private static final Logger LOG = LoggerFactory.getLogger(CpdReportConsumer.class);
41+
42+
private final CpdRequest request;
43+
private final ExcludeDuplicationsFromFile excludeDuplicationsFromFile;
44+
45+
CpdReportConsumer(CpdRequest request, ExcludeDuplicationsFromFile excludeDuplicationsFromFile) {
46+
this.request = request;
47+
this.excludeDuplicationsFromFile = excludeDuplicationsFromFile;
48+
}
49+
50+
@Override
51+
public void accept(CPDReport report) {
52+
try {
53+
// always create XML format. we need to output it even if the file list is empty or we have no
54+
// duplications so that the "check" goals can check for violations
55+
writeXmlReport(report);
56+
57+
// HTML format is handled by maven site report, XML format has already been rendered.
58+
// a renderer is only needed for other formats
59+
String format = request.getFormat();
60+
if (!"html".equals(format) && !"xml".equals(format)) {
61+
writeFormattedReport(report);
62+
}
63+
} catch (IOException | MavenReportException e) {
64+
throw new RuntimeException(e);
65+
}
66+
}
67+
68+
private void writeXmlReport(CPDReport cpd) throws IOException {
69+
File targetFile = writeReport(cpd, new XMLRenderer(request.getOutputEncoding()), "xml");
70+
if (request.isIncludeXmlInSite()) {
71+
File siteDir = new File(request.getReportOutputDirectory());
72+
if (!siteDir.exists() && !siteDir.mkdirs()) {
73+
throw new IOException("Couldn't create report output directory: " + siteDir);
74+
}
75+
FileUtils.copyFile(targetFile, new File(siteDir, "cpd.xml"));
76+
}
77+
}
78+
79+
private void writeFormattedReport(CPDReport cpd) throws IOException, MavenReportException {
80+
CPDReportRenderer r = CpdExecutor.createRenderer(request.getFormat(), request.getOutputEncoding());
81+
writeReport(cpd, r, request.getFormat());
82+
}
83+
84+
private File writeReport(CPDReport cpd, CPDReportRenderer renderer, String extension) throws IOException {
85+
if (renderer == null) {
86+
return null;
87+
}
88+
89+
File targetDir = new File(request.getTargetDirectory());
90+
if (!targetDir.exists() && !targetDir.mkdirs()) {
91+
throw new IOException("Couldn't create report output directory: " + targetDir);
92+
}
93+
94+
File targetFile = new File(targetDir, "cpd." + extension);
95+
try (Writer writer = new OutputStreamWriter(new FileOutputStream(targetFile), request.getOutputEncoding())) {
96+
renderer.render(cpd.filterMatches(filterMatches()), writer);
97+
writer.flush();
98+
}
99+
return targetFile;
100+
}
101+
102+
private Predicate<Match> filterMatches() {
103+
return (Match match) -> {
104+
LOG.debug(
105+
"Filtering duplications. Using {} configured exclusions.",
106+
excludeDuplicationsFromFile.countExclusions());
107+
108+
if (excludeDuplicationsFromFile.isExcludedFromFailure(match)) {
109+
LOG.debug("Excluded {} duplications.", match);
110+
return false;
111+
} else {
112+
return true;
113+
}
114+
};
115+
}
116+
}

src/test/java/org/apache/maven/plugins/pmd/CpdReportTest.java

+30-3
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
import org.apache.commons.io.FileUtils;
2828
import org.apache.commons.lang3.StringUtils;
29+
import org.apache.maven.reporting.MavenReportException;
2930
import org.w3c.dom.Document;
3031

3132
/**
@@ -65,8 +66,7 @@ public void testDefaultConfiguration() throws Exception {
6566
assertTrue(lowerCaseContains(str, "tmp = tmp + str.substring( i, i + 1);"));
6667

6768
// the version should be logged
68-
String output = CapturingPrintStream.getOutput();
69-
assertTrue(output.contains("PMD version: " + AbstractPmdReport.getPmdVersion()));
69+
assertLogOutputContains("PMD version: " + AbstractPmdReport.getPmdVersion());
7070
}
7171

7272
/**
@@ -130,7 +130,8 @@ public void testInvalidFormat() throws Exception {
130130

131131
fail("MavenReportException must be thrown");
132132
} catch (Exception e) {
133-
assertTrue(true);
133+
assertMavenReportException("There was 1 error while executing CPD", e);
134+
assertLogOutputContains("Can't find CPD custom format xhtml");
134135
}
135136
}
136137

@@ -240,4 +241,30 @@ public void testExclusionsConfiguration() throws Exception {
240241
String str = readFile(generatedFile);
241242
assertEquals(0, StringUtils.countMatches(str, "<duplication"));
242243
}
244+
245+
public void testWithCpdErrors() throws Exception {
246+
try {
247+
generateReport("cpd", "CpdReportTest/with-cpd-errors/pom.xml");
248+
fail("MavenReportException must be thrown");
249+
} catch (Exception e) {
250+
assertMavenReportException("There was 1 error while executing CPD", e);
251+
assertLogOutputContains("Lexical error in file");
252+
assertLogOutputContains("BadFile.java");
253+
}
254+
}
255+
256+
private static void assertMavenReportException(String expectedMessage, Exception exception) {
257+
// The maven report exception might be wrapped in a RuntimeException
258+
assertTrue(
259+
"Expected MavenReportException, but was: " + exception,
260+
exception instanceof MavenReportException || exception.getCause() instanceof MavenReportException);
261+
assertTrue(
262+
"Wrong message: expected: " + expectedMessage + ", but was: " + exception.toString(),
263+
exception.toString().contains(expectedMessage));
264+
}
265+
266+
private static void assertLogOutputContains(String expectedMessage) {
267+
String log = CapturingPrintStream.getOutput();
268+
assertTrue("Expected '" + expectedMessage + "' in log, but was:\n" + log, log.contains(expectedMessage));
269+
}
243270
}

src/test/java/org/apache/maven/plugins/pmd/exec/ExecutorTest.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@
2323
import java.net.URL;
2424
import java.net.URLClassLoader;
2525

26-
import junit.framework.Assert;
2726
import junit.framework.TestCase;
2827
import org.apache.commons.lang3.SystemUtils;
28+
import org.junit.Assert;
2929

3030
public class ExecutorTest extends TestCase {
3131
public void testBuildClasspath() throws MalformedURLException {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<!--
2+
Licensed to the Apache Software Foundation (ASF) under one
3+
or more contributor license agreements. See the NOTICE file
4+
distributed with this work for additional information
5+
regarding copyright ownership. The ASF licenses this file
6+
to you under the Apache License, Version 2.0 (the
7+
"License"); you may not use this file except in compliance
8+
with the License. You may obtain a copy of the License at
9+
10+
http://www.apache.org/licenses/LICENSE-2.0
11+
12+
Unless required by applicable law or agreed to in writing,
13+
software distributed under the License is distributed on an
14+
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
KIND, either express or implied. See the License for the
16+
specific language governing permissions and limitations
17+
under the License.
18+
-->
19+
20+
<project>
21+
<modelVersion>4.0.0</modelVersion>
22+
<groupId>unit.CpdReportTest</groupId>
23+
<artifactId>cpd-configuration-with-pmd-errors</artifactId>
24+
<packaging>jar</packaging>
25+
<version>1.0-SNAPSHOT</version>
26+
<inceptionYear>2006</inceptionYear>
27+
<name>Maven CPD Plugin Test for failing build when CPD errors occurred</name>
28+
<url>http://maven.apache.org</url>
29+
<build>
30+
<finalName>with-cpd-errors</finalName>
31+
<plugins>
32+
<plugin>
33+
<groupId>org.apache.maven.plugins</groupId>
34+
<artifactId>maven-pmd-plugin</artifactId>
35+
<configuration>
36+
<project implementation="org.apache.maven.plugins.pmd.stubs.DefaultConfigurationMavenProjectStub"/>
37+
<outputDirectory>${basedir}/target/test/unit/CpdReportTest/with-cpd-errors/target/site</outputDirectory>
38+
<targetDirectory>${basedir}/target/test/unit/CpdReportTest/with-cpd-errors/target</targetDirectory>
39+
<localRepository>${localRepository}</localRepository>
40+
<format>xml</format>
41+
<linkXRef>false</linkXRef>
42+
<xrefLocation>${basedir}/target/test/unit/CpdReportTest/with-cpd-errors/target/site/xref</xrefLocation>
43+
<minimumTokens>100</minimumTokens>
44+
<compileSourceRoots>
45+
<compileSourceRoot>${basedir}/src/test/resources/unit/CpdReportTest/with-cpd-errors/src/main/java</compileSourceRoot>
46+
</compileSourceRoots>
47+
<inputEncoding>UTF-8</inputEncoding>
48+
</configuration>
49+
</plugin>
50+
</plugins>
51+
</build>
52+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package sample;
20+
21+
public class BadFile {
22+
public void foo() {
23+
// this is a bad character � it's U+FFFD REPLACEMENT CHARACTER
24+
int ab = 1;
25+
}
26+
}

0 commit comments

Comments
 (0)