Support formats csv and txt for CPD

This commit is contained in:
Andreas Dangel committed 2023-11-14 20:21:21 +01:00
1 parent dd2bb98aa0
commit 9dd646a030
6 files changed
+303 -23

No files matched your search

@@ -1,2 +1,4 @@
invoker.goals = verify
invoker.goals.1 = verify
invoker.goals.2 = pmd:cpd-check -Dformat=csv
invoker.goals.3 = pmd:cpd-check -Dformat=txt
invoker.buildResult = failure
+28 -2
View File
@@ -14,6 +14,9 @@ String readFile(File file) throws IOException {
File buildLogPath = new File(basedir, "build.log");
String buildLog = readFile(buildLogPath);
if (buildLog.contains("An API incompatibility was encountered while")) {
throw new RuntimeException("Executing failed due to API incompatibility");
}
if (!buildLog.contains("[INFO] CPD Failure: Found 8 lines of duplicated code at locations:")) {
throw new RuntimeException("No CPD failures detected, did CPD run?");
}
@@ -23,8 +26,7 @@ if (!buildLog.contains(classA + " line 3")) {
}
File cpdXmlReport = new File(basedir, "target/cpd.xml");
if(!cpdXmlReport.exists())
{
if (!cpdXmlReport.exists()) {
throw new FileNotFoundException("Could not find cpd xml report: " + cpdXmlReport);
}
String cpdXml = readFile(cpdXmlReport);
@@ -34,3 +36,27 @@ if (!cpdXml.contains("<duplication lines=\"8\" tokens=\"67\">")) {
if (!cpdXml.contains(classA + "\"/>")) {
throw new RuntimeException("Expected duplication has not been reported");
}
File csvReport = new File(basedir, "target/cpd.csv");
if (!csvReport.exists()) {
throw new FileNotFoundException("Could not find cpd csv report: " + csvReport);
}
String csv = readFile(csvReport);
if (!csv.contains("8,67,2,3,")) {
throw new RuntimeException("Expected duplication in CSV has not been reported");
}
if (!csv.contains(classA + ",")) {
throw new RuntimeException("Expected duplication in CSV has not been reported");
}
File textReport = new File(basedir, "target/cpd.txt");
if (!textReport.exists()) {
throw new FileNotFoundException("Could not find cpd text report: " + textReport);
}
String text = readFile(textReport);
if (!text.contains("Found a 8 line (67 tokens) duplication in the following files:")) {
throw new RuntimeException("Expected duplication in TXT has not been reported");
}
if (!text.contains("Starting at line 3 of ") && !text.contains(classA.toString())) {
throw new RuntimeException("Expected duplication in TXT has not been reported");
}
@@ -0,0 +1,132 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
// This class has been taken from 7.0.0-SNAPSHOT
// Changes: implements old interface CPDRenderer, old render(Iterator<Match> matches, Writer writer) method
package net.sourceforge.pmd.cpd;
import java.io.IOException;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.apache.commons.lang3.StringEscapeUtils;
import net.sourceforge.pmd.cpd.renderer.CPDRenderer;
import net.sourceforge.pmd.lang.document.FileLocation;
import net.sourceforge.pmd.lang.document.TextFile;
import net.sourceforge.pmd.lang.java.JavaLanguageModule;
/**
* Renders a report to CSV. The CSV format renders each match (duplication)
* as a single line with the following columns:
* <ul>
* <li>lines (optional): The number of lines the first mark of a match spans.
* Only output if the {@code lineCountPerFile} is disabled (see ctor params).</li>
* <li>tokens: The number of duplicated tokens in a match (size of the match).</li>
* <li>occurrences: The number of duplicates in a match (number of times the tokens were found in distinct places).</li>
* </ul>
*
* <p>Trailing each line are pairs (or triples, if {@code lineCountPerFile} is enabled)
* of fields describing each file where the duplication was found in the format
* {@code (start line, line count (optional), file path)}. These repeat at least twice.
*
* <h3>Examples</h3>
* <p>
* Example without {@code lineCountPerFile}:
* <pre>{@code
* lines,tokens,occurrences
* 10,75,2,48,/var/file1,73,/var/file2
* }</pre>
* This describes one match with the following characteristics:
* <ul>
* <li>The first duplicate instance is 10 lines long;
* <li>75 duplicated tokens;
* <li>2 duplicate instances;
* <li>The first duplicate instance is in file {@code /var/file1} and starts at line 48;</li>
* <li>The second duplicate instance is in file {@code /var/file2} and starts at line 73.</li>
* </ul>
* <p>
* Example with {@code lineCountPerFile}:
* <pre>{@code
* tokens,occurrences
* 75,2,48,10,/var/file1,73,12,/var/file2
* }</pre>
* This describes one match with the following characteristics:
* <ul>
* <li>75 duplicated tokens
* <li>2 duplicate instances
* <li>The first duplicate instance is in file {@code /var/file1}, starts at line 48, and is 10 lines long;</li>
* <li>The second duplicate instance is in file {@code /var/file2}, starts at line 73, and is 12 lines long.</li>
* </ul>
*/
public class CSVRenderer implements CPDReportRenderer, CPDRenderer {
private final char separator;
private final boolean lineCountPerFile;
public static final char DEFAULT_SEPARATOR = ',';
public static final boolean DEFAULT_LINECOUNTPERFILE = false;
public CSVRenderer() {
this(DEFAULT_SEPARATOR, DEFAULT_LINECOUNTPERFILE);
}
public CSVRenderer(boolean lineCountPerFile) {
this(DEFAULT_SEPARATOR, lineCountPerFile);
}
public CSVRenderer(char separatorChar) {
this(separatorChar, DEFAULT_LINECOUNTPERFILE);
}
public CSVRenderer(char separatorChar, boolean lineCountPerFile) {
this.separator = separatorChar;
this.lineCountPerFile = lineCountPerFile;
}
@Override
public void render(CPDReport report, Writer writer) throws IOException {
if (!lineCountPerFile) {
writer.append("lines").append(separator);
}
writer.append("tokens").append(separator).append("occurrences").append(System.lineSeparator());
for (Match match : report.getMatches()) {
if (!lineCountPerFile) {
writer.append(String.valueOf(match.getLineCount())).append(separator);
}
writer.append(String.valueOf(match.getTokenCount())).append(separator)
.append(String.valueOf(match.getMarkCount())).append(separator);
for (Iterator<Mark> marks = match.iterator(); marks.hasNext();) {
Mark mark = marks.next();
FileLocation loc = mark.getLocation();
writer.append(String.valueOf(loc.getStartLine())).append(separator);
if (lineCountPerFile) {
writer.append(String.valueOf(loc.getLineCount())).append(separator);
}
writer.append(StringEscapeUtils.escapeCsv(report.getDisplayName(loc.getFileId())));
if (marks.hasNext()) {
writer.append(separator);
}
}
writer.append(System.lineSeparator());
}
writer.flush();
}
// ------------------- compat extensions --------------------
@Override
public void render(Iterator<Match> matches, Writer writer) throws IOException {
RendererHelper.render(matches, writer, this);
}
}
@@ -0,0 +1,49 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.cpd;
import java.io.IOException;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import net.sourceforge.pmd.cpd.renderer.CPDRenderer;
import net.sourceforge.pmd.lang.document.TextFile;
import net.sourceforge.pmd.lang.java.JavaLanguageModule;
final class RendererHelper {
private RendererHelper() {
// utility class
}
static void render(Iterator<Match> matches, Writer writer, CPDReportRenderer renderer) throws IOException {
List<Match> matchesList = new ArrayList<>();
matches.forEachRemaining(matchesList::add);
List<TextFile> textFiles = new ArrayList<>();
Set<String> paths = new HashSet<>();
for (Match match : matchesList) {
for (Mark mark : match.getMarkSet()) {
paths.add(mark.getFilename());
}
}
for (String path : paths) {
textFiles.add(TextFile.forPath(Paths.get(path), StandardCharsets.UTF_8, JavaLanguageModule.getInstance().getDefaultVersion()));
}
try (SourceManager sourceManager = new SourceManager(textFiles)) {
CPDReport report = new CPDReport(sourceManager, matchesList, Collections.emptyMap());
renderer.render(report, writer);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
@@ -0,0 +1,90 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
// This class has been taken from 7.0.0-SNAPSHOT
// Changes: implements old interface CPDRenderer, old render(Iterator<Match> matches, Writer writer) method
package net.sourceforge.pmd.cpd;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
import java.util.Iterator;
import net.sourceforge.pmd.cpd.renderer.CPDRenderer;
import net.sourceforge.pmd.lang.document.Chars;
import net.sourceforge.pmd.lang.document.FileLocation;
import net.sourceforge.pmd.util.StringUtil;
public class SimpleRenderer implements CPDReportRenderer, CPDRenderer {
private String separator;
private boolean trimLeadingWhitespace;
public static final String DEFAULT_SEPARATOR = "=====================================================================";
public SimpleRenderer() {
this(false);
}
public SimpleRenderer(boolean trimLeadingWhitespace) {
this(DEFAULT_SEPARATOR);
this.trimLeadingWhitespace = trimLeadingWhitespace;
}
public SimpleRenderer(String theSeparator) {
separator = theSeparator;
}
@Override
public void render(CPDReport report, Writer writer0) throws IOException {
PrintWriter writer = new PrintWriter(writer0);
Iterator<Match> matches = report.getMatches().iterator();
if (matches.hasNext()) {
renderOn(report, writer, matches.next());
}
while (matches.hasNext()) {
Match match = matches.next();
writer.println(separator);
renderOn(report, writer, match);
}
writer.flush();
}
private void renderOn(CPDReport report, PrintWriter writer, Match match) throws IOException {
writer.append("Found a ").append(String.valueOf(match.getLineCount())).append(" line (").append(String.valueOf(match.getTokenCount()))
.append(" tokens) duplication in the following files: ").println();
for (Mark mark : match) {
FileLocation loc = mark.getLocation();
writer.append("Starting at line ")
.append(String.valueOf(loc.getStartLine()))
.append(" of ").append(report.getDisplayName(loc.getFileId()))
.println();
}
writer.println(); // add a line to separate the source from the desc above
Chars source = report.getSourceCodeSlice(match.getFirstMark());
if (trimLeadingWhitespace) {
for (Chars line : StringUtil.linesWithTrimIndent(source)) {
line.writeFully(writer);
writer.println();
}
return;
}
source.writeFully(writer);
writer.println();
}
// ------------------- compat extensions --------------------
@Override
public void render(Iterator<Match> matches, Writer writer) throws IOException {
RendererHelper.render(matches, writer, this);
}
}
@@ -169,25 +169,6 @@ public final class XMLRenderer implements CPDReportRenderer, CPDRenderer {
// ------------------- compat extensions --------------------
@Override
public void render(Iterator<Match> matches, Writer writer) throws IOException {
List<Match> matchesList = new ArrayList<>();
matches.forEachRemaining(matchesList::add);
List<TextFile> textFiles = new ArrayList<>();
Set<String> paths = new HashSet<>();
for (Match match : matchesList) {
for (Mark mark : match.getMarkSet()) {
paths.add(mark.getFilename());
}
}
for (String path : paths) {
textFiles.add(TextFile.forPath(Paths.get(path), StandardCharsets.UTF_8, JavaLanguageModule.getInstance().getDefaultVersion()));
}
try (SourceManager sourcManager = new SourceManager(textFiles)) {
CPDReport report = new CPDReport(sourcManager, matchesList, Collections.emptyMap());
render(report, writer);
} catch (Exception e) {
throw new RuntimeException(e);
}
RendererHelper.render(matches, writer, this);
}
}