From ab21bb94d6885617e3ac2f00e9be2a111ca87294 Mon Sep 17 00:00:00 2001 From: Maximilian Waidelich Date: Mon, 12 Aug 2024 12:48:03 +0200 Subject: [PATCH 1/5] Added new Report Parser for Metrics --- .../hafner/coverage/CyclomaticComplexity.java | 4 +- .../edu/hm/hafner/coverage/IntegerValue.java | 4 +- .../edu/hm/hafner/coverage/LinesOfCode.java | 2 +- .../java/edu/hm/hafner/coverage/Metric.java | 5 +- .../edu/hm/hafner/coverage/TestCount.java | 2 +- .../java/edu/hm/hafner/coverage/Value.java | 6 + .../hafner/coverage/parser/MetricsParser.java | 245 +++ .../coverage/registry/ParserRegistry.java | 6 +- .../hm/hafner/coverage/IntegerValueTest.java | 6 +- .../edu/hm/hafner/coverage/ValueTest.java | 17 +- .../coverage/parser/MetricsParserTest.java | 25 + .../coverage/registry/ParserRegistryTest.java | 2 + .../hafner/coverage/parser/metrics/empty.xml | 3 + .../coverage/parser/metrics/metrics.xml | 1341 +++++++++++++++++ 14 files changed, 1656 insertions(+), 12 deletions(-) create mode 100644 src/main/java/edu/hm/hafner/coverage/parser/MetricsParser.java create mode 100644 src/test/java/edu/hm/hafner/coverage/parser/MetricsParserTest.java create mode 100644 src/test/resources/edu/hm/hafner/coverage/parser/metrics/empty.xml create mode 100644 src/test/resources/edu/hm/hafner/coverage/parser/metrics/metrics.xml diff --git a/src/main/java/edu/hm/hafner/coverage/CyclomaticComplexity.java b/src/main/java/edu/hm/hafner/coverage/CyclomaticComplexity.java index 5cc50a1a..9565b58b 100644 --- a/src/main/java/edu/hm/hafner/coverage/CyclomaticComplexity.java +++ b/src/main/java/edu/hm/hafner/coverage/CyclomaticComplexity.java @@ -32,7 +32,7 @@ public CyclomaticComplexity(final int complexity, final Metric metric) { } @Override - protected IntegerValue create(final int value) { - return new CyclomaticComplexity(value); + protected IntegerValue create(final int value, final Metric metric) { + return new CyclomaticComplexity(value, metric); } } diff --git a/src/main/java/edu/hm/hafner/coverage/IntegerValue.java b/src/main/java/edu/hm/hafner/coverage/IntegerValue.java index cd5b20a6..b2218ccf 100644 --- a/src/main/java/edu/hm/hafner/coverage/IntegerValue.java +++ b/src/main/java/edu/hm/hafner/coverage/IntegerValue.java @@ -40,10 +40,10 @@ public int getValue() { @Override public IntegerValue add(final Value other) { - return castAndMap(other, o -> create(integer + o.getValue())); + return castAndMap(other, o -> create(integer + o.getValue(), o.getMetric())); } - protected abstract IntegerValue create(int value); + protected abstract IntegerValue create(int value, Metric metric); @Override public IntegerValue max(final Value other) { diff --git a/src/main/java/edu/hm/hafner/coverage/LinesOfCode.java b/src/main/java/edu/hm/hafner/coverage/LinesOfCode.java index 6f4ec67e..d15bfa0c 100644 --- a/src/main/java/edu/hm/hafner/coverage/LinesOfCode.java +++ b/src/main/java/edu/hm/hafner/coverage/LinesOfCode.java @@ -19,7 +19,7 @@ public LinesOfCode(final int loc) { } @Override - protected IntegerValue create(final int value) { + protected IntegerValue create(final int value, final Metric ignored) { return new LinesOfCode(value); } } diff --git a/src/main/java/edu/hm/hafner/coverage/Metric.java b/src/main/java/edu/hm/hafner/coverage/Metric.java index a27f6a4f..f801b95e 100644 --- a/src/main/java/edu/hm/hafner/coverage/Metric.java +++ b/src/main/java/edu/hm/hafner/coverage/Metric.java @@ -41,7 +41,10 @@ public enum Metric { COMPLEXITY_MAXIMUM(new MethodMaxComplexityFinder(), MetricTendency.SMALLER_IS_BETTER), COMPLEXITY_DENSITY(new DensityEvaluator(), MetricTendency.SMALLER_IS_BETTER), LOC(new LocEvaluator(), MetricTendency.SMALLER_IS_BETTER), - TESTS(new ValuesAggregator(), MetricTendency.LARGER_IS_BETTER); + TESTS(new ValuesAggregator(), MetricTendency.LARGER_IS_BETTER), + NCSS(new ValuesAggregator(), MetricTendency.SMALLER_IS_BETTER), + COGNITIVE_COMPLEXITY(new ValuesAggregator(), MetricTendency.SMALLER_IS_BETTER), + NPATH_COMPLEXITY(new ValuesAggregator(), MetricTendency.SMALLER_IS_BETTER); /** * Returns the metric that belongs to the specified tag. diff --git a/src/main/java/edu/hm/hafner/coverage/TestCount.java b/src/main/java/edu/hm/hafner/coverage/TestCount.java index 33bfb188..d809416a 100644 --- a/src/main/java/edu/hm/hafner/coverage/TestCount.java +++ b/src/main/java/edu/hm/hafner/coverage/TestCount.java @@ -19,7 +19,7 @@ public TestCount(final int tests) { } @Override - protected IntegerValue create(final int value) { + protected IntegerValue create(final int value, final Metric ignored) { return new TestCount(value); } } diff --git a/src/main/java/edu/hm/hafner/coverage/Value.java b/src/main/java/edu/hm/hafner/coverage/Value.java index 239ad510..49f787e3 100644 --- a/src/main/java/edu/hm/hafner/coverage/Value.java +++ b/src/main/java/edu/hm/hafner/coverage/Value.java @@ -104,6 +104,12 @@ public static Value valueOf(final String stringRepresentation) { return new LinesOfCode(Integer.parseInt(value)); case TESTS: return new TestCount(Integer.parseInt(value)); + case NCSS: + return new CyclomaticComplexity(Integer.parseInt(value), Metric.NCSS); + case NPATH_COMPLEXITY: + return new CyclomaticComplexity(Integer.parseInt(value), Metric.NPATH_COMPLEXITY); + case COGNITIVE_COMPLEXITY: + return new CyclomaticComplexity(Integer.parseInt(value), Metric.COGNITIVE_COMPLEXITY); } } } diff --git a/src/main/java/edu/hm/hafner/coverage/parser/MetricsParser.java b/src/main/java/edu/hm/hafner/coverage/parser/MetricsParser.java new file mode 100644 index 00000000..45578d4b --- /dev/null +++ b/src/main/java/edu/hm/hafner/coverage/parser/MetricsParser.java @@ -0,0 +1,245 @@ +package edu.hm.hafner.coverage.parser; + +import java.io.Reader; +import java.nio.file.Paths; +import javax.xml.namespace.QName; +import javax.xml.stream.XMLEventReader; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.events.StartElement; +import javax.xml.stream.events.XMLEvent; + +import com.google.errorprone.annotations.CanIgnoreReturnValue; + +import edu.hm.hafner.coverage.ClassNode; +import edu.hm.hafner.coverage.CoverageParser; +import edu.hm.hafner.coverage.CyclomaticComplexity; +import edu.hm.hafner.coverage.FileNode; +import edu.hm.hafner.coverage.MethodNode; +import edu.hm.hafner.coverage.Metric; +import edu.hm.hafner.coverage.ModuleNode; +import edu.hm.hafner.coverage.Node; +import edu.hm.hafner.coverage.PackageNode; +import edu.hm.hafner.util.FilteredLog; +import edu.hm.hafner.util.PathUtil; +import edu.hm.hafner.util.SecureXmlParserFactory; +import edu.hm.hafner.util.SecureXmlParserFactory.ParsingException; +import edu.hm.hafner.util.TreeString; + +/** + * Parses Metrics reports into a hierarchical Java Object Model. + * + * @author Maximilian Waidelich + */ +@SuppressWarnings("PMD.GodClass") +public class MetricsParser extends CoverageParser { + private static final long serialVersionUID = -4461747681863455621L; + + /** XML elements. */ + private static final QName PACKAGE = new QName("package"); + private static final QName CLASS = new QName("class"); + private static final QName METHOD = new QName("method"); + private static final QName METRIC = new QName("metric"); + private static final QName FILE = new QName("file"); + + /** Required attributes of the XML elements. */ + private static final QName FQCN = new QName("fqcn"); + private static final QName NAME = new QName("name"); + private static final QName BEGIN_LINE = new QName("beginline"); + private static final QName VALUE = new QName("value"); + + private static final String CYCLOMATIC_COMPLEXITY = "CyclomaticComplexity"; + private static final String COGNITIVE_COMPLEXITY = "CognitiveComplexity"; + private static final String NCSS = "NCSS"; + private static final String NPATH_COMPLEXITY = "NPathComplexity"; + + private static final PathUtil PATH_UTIL = new PathUtil(); + + /** + * Creates a new instance of {@link MetricsParser}. + */ + public MetricsParser() { + this(ProcessingMode.FAIL_FAST); + } + + /** + * Creates a new instance of {@link MetricsParser}. + * + * @param processingMode + * determines whether to ignore errors + */ + public MetricsParser(final ProcessingMode processingMode) { + super(processingMode); + } + + @Override + protected ModuleNode parseReport(final Reader reader, final String fileName, final FilteredLog log) { + try { + var factory = new SecureXmlParserFactory(); + var eventReader = factory.createXmlEventReader(reader); + + while (eventReader.hasNext()) { + XMLEvent event = eventReader.nextEvent(); + + if (event.isStartElement()) { + var startElement = event.asStartElement(); + var tagName = startElement.getName(); + if (PACKAGE.equals(tagName)) { + var root = new ModuleNode(""); + readPackage(eventReader, root, startElement, fileName); + return root; + } + } + } + handleEmptyResults(fileName, log); + + return new ModuleNode("empty"); + } + catch (XMLStreamException exception) { + throw new ParsingException(exception); + } + } + + @CanIgnoreReturnValue + private PackageNode readPackage(final XMLEventReader reader, final ModuleNode root, + final StartElement startElement, final String fileName) throws XMLStreamException { + var packageName = getValueOf(startElement, FQCN); + var packageNode = root.findOrCreatePackageNode(packageName.replaceAll("\\.", "/")); + while (reader.hasNext()) { + XMLEvent event = reader.nextEvent(); + + if (event.isStartElement()) { + var nextElement = event.asStartElement(); + if (FILE.equals(nextElement.getName())) { + readSourceFile(reader, packageNode, nextElement, fileName); + } + else if (METRIC.equals(nextElement.getName())) { + readValueCounter(packageNode, nextElement); + } + } + else if (event.isEndElement()) { + var endElement = event.asEndElement(); + if (PACKAGE.equals(endElement.getName())) { + return packageNode; + } + } + } + throw createEofException(fileName); + } + + @CanIgnoreReturnValue + private Node readClass(final XMLEventReader reader, final FileNode fileNode, final StartElement startElement, + final String fileName, final PackageNode packageNode) throws XMLStreamException { + ClassNode classNode = fileNode.findOrCreateClassNode(packageNode.getName() + "." + getValueOf(startElement, NAME)); + while (reader.hasNext()) { + XMLEvent event = reader.nextEvent(); + + if (event.isStartElement()) { + var nextElement = event.asStartElement(); + if (METHOD.equals(nextElement.getName())) { + readMethod(reader, classNode, nextElement, fileName); + } + else if (METRIC.equals(nextElement.getName())) { + readValueCounter(classNode, nextElement); + } + } + else if (event.isEndElement()) { + var endElement = event.asEndElement(); + if (CLASS.equals(endElement.getName())) { + return classNode; + } + } + } + throw createEofException(fileName); + } + + private TreeString internPath(final String filePath) { + return getTreeStringBuilder().intern(PATH_UTIL.getRelativePath(Paths.get(filePath))); + } + + @CanIgnoreReturnValue + private Node readSourceFile(final XMLEventReader reader, final PackageNode packageNode, + final StartElement startElement, final String fileName) + throws XMLStreamException { + var sourceFilePath = Paths.get(getValueOf(startElement, NAME)).getFileName(); + String sourceFilefileName; + if (sourceFilePath == null) { + sourceFilefileName = getValueOf(startElement, NAME); + } + else { + sourceFilefileName = sourceFilePath.toString(); + } + var fileNode = packageNode.findOrCreateFileNode(sourceFilefileName, + internPath(getValueOf(startElement, NAME))); + + while (reader.hasNext()) { + XMLEvent event = reader.nextEvent(); + + if (event.isStartElement()) { + var nextElement = event.asStartElement(); + if (CLASS.equals(nextElement.getName())) { + readClass(reader, fileNode, nextElement, fileName, packageNode); + } + else if (METRIC.equals(nextElement.getName())) { + readValueCounter(fileNode, nextElement); + } + } + else if (event.isEndElement()) { + var endElement = event.asEndElement(); + if (FILE.equals(endElement.getName())) { + return fileNode; + } + } + } + throw createEofException(fileName); + } + + @CanIgnoreReturnValue + private Node readMethod(final XMLEventReader reader, final ClassNode classNode, + final StartElement startElement, final String fileName) throws XMLStreamException { + String methodName = getValueOf(startElement, NAME) + "#" + getValueOf(startElement, BEGIN_LINE); + + MethodNode methodNode = createMethod(startElement, methodName); + classNode.addChild(methodNode); + + while (reader.hasNext()) { + XMLEvent event = reader.nextEvent(); + + if (event.isStartElement()) { + var nextElement = event.asStartElement(); + if (METRIC.equals(nextElement.getName())) { + readValueCounter(methodNode, nextElement); + } + } + else if (event.isEndElement()) { + var endElement = event.asEndElement(); + if (METHOD.equals(endElement.getName())) { + return methodNode; + } + } + } + throw createEofException(fileName); + } + + private MethodNode createMethod(final StartElement startElement, final String methodName) { + return new MethodNode(methodName, "", parseInteger(getValueOf(startElement, BEGIN_LINE))); + } + + private void readValueCounter(final Node node, final StartElement startElement) { + String currentType = getValueOf(startElement, NAME); + int value = parseInteger(getValueOf(startElement, VALUE)); + if (!node.isAggregation()) { + if (CYCLOMATIC_COMPLEXITY.equals(currentType)) { + node.addValue(new CyclomaticComplexity(value)); + } + else if (COGNITIVE_COMPLEXITY.equals(currentType)) { + node.addValue(new CyclomaticComplexity(value, Metric.COGNITIVE_COMPLEXITY)); + } + else if (NCSS.equals(currentType)) { + node.addValue(new CyclomaticComplexity(value, Metric.NCSS)); + } + else if (NPATH_COMPLEXITY.equals(currentType)) { + node.addValue(new CyclomaticComplexity(value, Metric.NPATH_COMPLEXITY)); + } + } + } +} diff --git a/src/main/java/edu/hm/hafner/coverage/registry/ParserRegistry.java b/src/main/java/edu/hm/hafner/coverage/registry/ParserRegistry.java index 20c85d46..7b6db756 100644 --- a/src/main/java/edu/hm/hafner/coverage/registry/ParserRegistry.java +++ b/src/main/java/edu/hm/hafner/coverage/registry/ParserRegistry.java @@ -7,6 +7,7 @@ import edu.hm.hafner.coverage.parser.CoberturaParser; import edu.hm.hafner.coverage.parser.JacocoParser; import edu.hm.hafner.coverage.parser.JunitParser; +import edu.hm.hafner.coverage.parser.MetricsParser; import edu.hm.hafner.coverage.parser.NunitParser; import edu.hm.hafner.coverage.parser.OpenCoverParser; import edu.hm.hafner.coverage.parser.PitestParser; @@ -28,7 +29,8 @@ public enum CoverageParserType { PIT, JUNIT, VECTORCAST, - XUNIT + XUNIT, + METRICS } /** @@ -79,6 +81,8 @@ public CoverageParser get(final CoverageParserType parser, final ProcessingMode return new XunitParser(processingMode); case VECTORCAST: return new VectorCastParser(processingMode); + case METRICS: + return new MetricsParser(processingMode); } throw new IllegalArgumentException("Unknown parser type: " + parser); } diff --git a/src/test/java/edu/hm/hafner/coverage/IntegerValueTest.java b/src/test/java/edu/hm/hafner/coverage/IntegerValueTest.java index baab0e22..5ab1a77a 100644 --- a/src/test/java/edu/hm/hafner/coverage/IntegerValueTest.java +++ b/src/test/java/edu/hm/hafner/coverage/IntegerValueTest.java @@ -46,9 +46,9 @@ void shouldCreateValue() { var value = createValue(123); assertThat(value.serialize()).startsWith(value.getMetric().name()).endsWith(": 123"); - assertThat(value.create(100)).hasValue(100); - assertThat(value.create(-100)).hasValue(-100); - assertThat(value.create(0)).hasValue(0); + assertThat(value.create(100, Metric.COMPLEXITY)).hasValue(100); + assertThat(value.create(-100, Metric.COMPLEXITY)).hasValue(-100); + assertThat(value.create(0, Metric.COMPLEXITY)).hasValue(0); } @Test diff --git a/src/test/java/edu/hm/hafner/coverage/ValueTest.java b/src/test/java/edu/hm/hafner/coverage/ValueTest.java index 3f018191..2881b810 100644 --- a/src/test/java/edu/hm/hafner/coverage/ValueTest.java +++ b/src/test/java/edu/hm/hafner/coverage/ValueTest.java @@ -59,6 +59,12 @@ void shouldReturnCorrectValueOfIntegerValues() { .isInstanceOfSatisfying(LinesOfCode.class, value -> assertThat(value).hasValue(2)); assertThat(Value.valueOf("TESTS: 3")) .isInstanceOfSatisfying(TestCount.class, value -> assertThat(value).hasValue(3)); + assertThat(Value.valueOf("NCSS: 4")) + .isInstanceOfSatisfying(CyclomaticComplexity.class, value -> assertThat(value).hasValue(4)); + assertThat(Value.valueOf("NPATH_COMPLEXITY: 5")) + .isInstanceOfSatisfying(CyclomaticComplexity.class, value -> assertThat(value).hasValue(5)); + assertThat(Value.valueOf("COGNITIVE_COMPLEXITY: 6")) + .isInstanceOfSatisfying(CyclomaticComplexity.class, value -> assertThat(value).hasValue(6)); } @Test @@ -88,13 +94,22 @@ void shouldThrowExceptionOnInvalidStringRepresentation() { void shouldGetValue() { var linesOfCode = new LinesOfCode(10); var cyclomaticComplexity = new CyclomaticComplexity(20); + var ncss = new CyclomaticComplexity(30, Metric.NCSS); + var npathComplexity = new CyclomaticComplexity(40, Metric.NPATH_COMPLEXITY); + var coginitiveComplexity = new CyclomaticComplexity(50, Metric.COGNITIVE_COMPLEXITY); - List values = List.of(linesOfCode, cyclomaticComplexity); + List values = List.of(linesOfCode, cyclomaticComplexity, ncss, npathComplexity, coginitiveComplexity); assertThat(Value.getValue(Metric.LOC, values)) .isEqualTo(linesOfCode); assertThat(Value.getValue(Metric.COMPLEXITY, values)) .isEqualTo(cyclomaticComplexity); + assertThat(Value.getValue(Metric.NCSS, values)) + .isEqualTo(ncss); + assertThat(Value.getValue(Metric.NPATH_COMPLEXITY, values)) + .isEqualTo(npathComplexity); + assertThat(Value.getValue(Metric.COGNITIVE_COMPLEXITY, values)) + .isEqualTo(coginitiveComplexity); assertThatExceptionOfType(NoSuchElementException.class) .isThrownBy(() -> Value.getValue(Metric.LINE, values)) .withMessageContaining("No value for metric"); diff --git a/src/test/java/edu/hm/hafner/coverage/parser/MetricsParserTest.java b/src/test/java/edu/hm/hafner/coverage/parser/MetricsParserTest.java new file mode 100644 index 00000000..87aaeced --- /dev/null +++ b/src/test/java/edu/hm/hafner/coverage/parser/MetricsParserTest.java @@ -0,0 +1,25 @@ +package edu.hm.hafner.coverage.parser; + +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.DefaultLocale; + +import edu.hm.hafner.coverage.CoverageParser; +import edu.hm.hafner.coverage.CoverageParser.ProcessingMode; + +@DefaultLocale("en") +class MetricsParserTest extends AbstractParserTest { + @Override + CoverageParser createParser(final ProcessingMode processingMode) { + return new MetricsParser(processingMode); + } + + @Override + protected String getFolder() { + return "metrics"; + } + + @Test + void simpleMetrics() { + readReport("metrics.xml"); + } +} diff --git a/src/test/java/edu/hm/hafner/coverage/registry/ParserRegistryTest.java b/src/test/java/edu/hm/hafner/coverage/registry/ParserRegistryTest.java index 6ff5ed76..11311690 100644 --- a/src/test/java/edu/hm/hafner/coverage/registry/ParserRegistryTest.java +++ b/src/test/java/edu/hm/hafner/coverage/registry/ParserRegistryTest.java @@ -4,6 +4,7 @@ import edu.hm.hafner.coverage.CoverageParser.ProcessingMode; import edu.hm.hafner.coverage.parser.CoberturaParser; +import edu.hm.hafner.coverage.parser.MetricsParser; import edu.hm.hafner.coverage.parser.VectorCastParser; import edu.hm.hafner.coverage.parser.JacocoParser; import edu.hm.hafner.coverage.parser.JunitParser; @@ -33,6 +34,7 @@ void shouldCreateSomeParsers() { assertThat(registry.get(CoverageParserType.XUNIT, ProcessingMode.IGNORE_ERRORS)).isInstanceOf(XunitParser.class); assertThat(registry.get(CoverageParserType.VECTORCAST, ProcessingMode.FAIL_FAST)) .isInstanceOf(VectorCastParser.class); + assertThat(registry.get(CoverageParserType.METRICS, ProcessingMode.IGNORE_ERRORS)).isInstanceOf(MetricsParser.class); } @Test diff --git a/src/test/resources/edu/hm/hafner/coverage/parser/metrics/empty.xml b/src/test/resources/edu/hm/hafner/coverage/parser/metrics/empty.xml new file mode 100644 index 00000000..fc7fe782 --- /dev/null +++ b/src/test/resources/edu/hm/hafner/coverage/parser/metrics/empty.xml @@ -0,0 +1,3 @@ + + + diff --git a/src/test/resources/edu/hm/hafner/coverage/parser/metrics/metrics.xml b/src/test/resources/edu/hm/hafner/coverage/parser/metrics/metrics.xml new file mode 100644 index 00000000..89d91997 --- /dev/null +++ b/src/test/resources/edu/hm/hafner/coverage/parser/metrics/metrics.xml @@ -0,0 +1,1341 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From fb0d2ba4a18e30c9522fb1f987f655d678d9df4b Mon Sep 17 00:00:00 2001 From: Maximilian Waidelich Date: Mon, 12 Aug 2024 12:50:29 +0200 Subject: [PATCH 2/5] Added new Parse to README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 0785b961..b1a4e8f3 100644 --- a/README.md +++ b/README.md @@ -33,5 +33,6 @@ This library consists basically of two separate parts: * [JUnit](https://junit.org/junit5/) test results * [NUnit](https://nunit.org) test results * [XUnit](https://xunit.net) test results + * [PMD](https://pmd.github.io/) Metrics XML report All source code is licensed under the MIT license. Contributions to this library are welcome! From b47b7415c6ddb13a31de4d9add936d3ab263f4bf Mon Sep 17 00:00:00 2001 From: Maximilian Waidelich Date: Mon, 12 Aug 2024 14:37:46 +0200 Subject: [PATCH 3/5] Added tests --- .../hafner/coverage/parser/MetricsParser.java | 24 +- .../coverage/parser/MetricsParserTest.java | 90 +- .../coverage/parser/metrics/metrics.xml | 1302 ----------------- 3 files changed, 100 insertions(+), 1316 deletions(-) diff --git a/src/main/java/edu/hm/hafner/coverage/parser/MetricsParser.java b/src/main/java/edu/hm/hafner/coverage/parser/MetricsParser.java index 45578d4b..9bc0ffcf 100644 --- a/src/main/java/edu/hm/hafner/coverage/parser/MetricsParser.java +++ b/src/main/java/edu/hm/hafner/coverage/parser/MetricsParser.java @@ -227,19 +227,17 @@ private MethodNode createMethod(final StartElement startElement, final String me private void readValueCounter(final Node node, final StartElement startElement) { String currentType = getValueOf(startElement, NAME); int value = parseInteger(getValueOf(startElement, VALUE)); - if (!node.isAggregation()) { - if (CYCLOMATIC_COMPLEXITY.equals(currentType)) { - node.addValue(new CyclomaticComplexity(value)); - } - else if (COGNITIVE_COMPLEXITY.equals(currentType)) { - node.addValue(new CyclomaticComplexity(value, Metric.COGNITIVE_COMPLEXITY)); - } - else if (NCSS.equals(currentType)) { - node.addValue(new CyclomaticComplexity(value, Metric.NCSS)); - } - else if (NPATH_COMPLEXITY.equals(currentType)) { - node.addValue(new CyclomaticComplexity(value, Metric.NPATH_COMPLEXITY)); - } + if (CYCLOMATIC_COMPLEXITY.equals(currentType)) { + node.addValue(new CyclomaticComplexity(value)); + } + else if (COGNITIVE_COMPLEXITY.equals(currentType)) { + node.addValue(new CyclomaticComplexity(value, Metric.COGNITIVE_COMPLEXITY)); + } + else if (NCSS.equals(currentType)) { + node.addValue(new CyclomaticComplexity(value, Metric.NCSS)); + } + else if (NPATH_COMPLEXITY.equals(currentType)) { + node.addValue(new CyclomaticComplexity(value, Metric.NPATH_COMPLEXITY)); } } } diff --git a/src/test/java/edu/hm/hafner/coverage/parser/MetricsParserTest.java b/src/test/java/edu/hm/hafner/coverage/parser/MetricsParserTest.java index 87aaeced..32408a9a 100644 --- a/src/test/java/edu/hm/hafner/coverage/parser/MetricsParserTest.java +++ b/src/test/java/edu/hm/hafner/coverage/parser/MetricsParserTest.java @@ -5,6 +5,16 @@ import edu.hm.hafner.coverage.CoverageParser; import edu.hm.hafner.coverage.CoverageParser.ProcessingMode; +import edu.hm.hafner.coverage.IntegerValue; +import edu.hm.hafner.coverage.Metric; +import edu.hm.hafner.coverage.ModuleNode; +import edu.hm.hafner.coverage.Node; +import edu.hm.hafner.coverage.assertions.Assertions; + +import static edu.hm.hafner.coverage.Metric.*; +import static edu.hm.hafner.coverage.Metric.CLASS; +import static edu.hm.hafner.coverage.Metric.FILE; +import static edu.hm.hafner.coverage.assertions.Assertions.*; @DefaultLocale("en") class MetricsParserTest extends AbstractParserTest { @@ -20,6 +30,84 @@ protected String getFolder() { @Test void simpleMetrics() { - readReport("metrics.xml"); + ModuleNode tree = readReport("metrics.xml"); + + assertThat(tree.getAll(MODULE)).hasSize(1); + assertThat(tree.getAll(PACKAGE)).hasSize(1); + assertThat(tree.getAll(FILE)).hasSize(2); + assertThat(tree.getAll(CLASS)).hasSize(3); + assertThat(tree.getAll(METHOD)).hasSize(6); + + assertThat(tree).hasOnlyMetrics(MODULE, PACKAGE, FILE, CLASS, METHOD, COMPLEXITY, COMPLEXITY_MAXIMUM, + NPATH_COMPLEXITY, NCSS, COGNITIVE_COMPLEXITY); + + assertThat(tree.getChildren()).hasSize(1) + .element(0) + .satisfies(packageNode -> assertThat(packageNode).hasName("edu.hm.hafner.util")) + .satisfies(packageNode -> assertThat(packageNode).hasValueMetrics(NCSS)) + .satisfies(packageNode -> assertThat( + ((IntegerValue) packageNode.getValue(NCSS).get()).getValue()).isEqualTo(1000)) + .satisfies(packageNode -> assertThat(packageNode.getChildren()).hasSize(2)) + .satisfies(packageNode -> assertThat(packageNode.getChildren()).element(0) + .satisfies(fileNode -> assertThat(fileNode).hasName("Ensure.java")) + .satisfies(fileNode -> assertThat(fileNode).hasValueMetrics(NCSS)) + .satisfies(fileNode -> assertThat( + ((IntegerValue) fileNode.getValue(NCSS).get()).getValue()).isEqualTo(500)) + .satisfies(fileNode -> assertThat(fileNode.getChildren()).hasSize(2)) + .satisfies( + fileNode -> assertThat(fileNode.getChildren()).element(0).satisfies(this::checkEnsure)) + .satisfies(fileNode -> assertThat(fileNode.getChildren()).element(1) + .satisfies(this::checkIterableCondition))) + .satisfies(packageNode -> assertThat(packageNode.getChildren()).element(1) + .satisfies(fileNode -> assertThat(fileNode).hasName("FilteredLog.java")) + .satisfies(this::checkFilteredLog)); + } + + private void checkIterableCondition(final Node node) { + assertThat(node).satisfies( + classNode -> Assertions.assertThat(classNode).hasName("edu.hm.hafner.util.IterableCondition")) + .satisfies(classNode -> assertThat(classNode.getChildren()).hasSize(2)) + .satisfies(classNode -> assertThat(classNode.getChildren()).element(0) + .satisfies(methodNode -> Assertions.assertThat(methodNode).hasName("IterableCondition#205")) + .satisfies(methodNode -> Assertions.assertThat(methodNode).hasNoValueMetrics())) + .satisfies(classNode -> assertThat(classNode.getChildren()).element(1) + .satisfies(methodNode -> checkMethod(methodNode, "isNotEmpty#216", COGNITIVE_COMPLEXITY, 0))); + } + + private void checkEnsure(final Node node) { + assertThat(node).satisfies(classNode -> Assertions.assertThat(classNode).hasName("edu.hm.hafner.util.Ensure")) + .satisfies(classNode -> Assertions.assertThat(classNode).hasValueMetrics(NCSS)) + .satisfies( + classNode -> assertThat(((IntegerValue) classNode.getValue(NCSS).get()).getValue()).isEqualTo( + 149)) + .satisfies(classNode -> assertThat(classNode.getChildren()).hasSize(1) + .element(0) + .satisfies(methodNode -> checkMethod(methodNode, "that#47", COGNITIVE_COMPLEXITY, 0)) + .satisfies(methodNode -> checkMethod(methodNode, "that#47", COMPLEXITY, 1)) + .satisfies(methodNode -> checkMethod(methodNode, "that#47", NCSS, 2)) + .satisfies(methodNode -> checkMethod(methodNode, "that#47", NPATH_COMPLEXITY, 1))); + } + + private void checkFilteredLog(final Node fileNode) { + assertThat(fileNode.getChildren()).hasSize(1) + .element(0) + .satisfies(classNode -> Assertions.assertThat(classNode).hasName("edu.hm.hafner.util.FilteredLog")) + .satisfies(classNode -> assertThat(classNode).hasValueMetrics(NCSS)) + .satisfies( + classNode -> assertThat(((IntegerValue) classNode.getValue(NCSS).get()).getValue()).isEqualTo( + 96)) + .satisfies(classNode -> assertThat(classNode.getChildren()).hasSize(3)) + .satisfies(classNode -> assertThat(classNode.getChildren()).element(0) + .satisfies(methodNode -> checkMethod(methodNode, "FilteredLog#41", COMPLEXITY, 1))) + .satisfies(classNode -> assertThat(classNode.getChildren()).element(1) + .satisfies(methodNode -> checkMethod(methodNode, "FilteredLog#51", NCSS, 2))) + .satisfies(classNode -> assertThat(classNode.getChildren()).element(2) + .satisfies(methodNode -> checkMethod(methodNode, "FilteredLog#63", NPATH_COMPLEXITY, 1))); + } + + private void checkMethod(final Node methodNode, final String name, final Metric metric, final int expected) { + assertThat(methodNode).hasName(name); + assertThat(methodNode).hasValueMetrics(metric); + assertThat(((IntegerValue) methodNode.getValue(metric).get()).getValue()).isEqualTo(expected); } } diff --git a/src/test/resources/edu/hm/hafner/coverage/parser/metrics/metrics.xml b/src/test/resources/edu/hm/hafner/coverage/parser/metrics/metrics.xml index 89d91997..b0e95daa 100644 --- a/src/test/resources/edu/hm/hafner/coverage/parser/metrics/metrics.xml +++ b/src/test/resources/edu/hm/hafner/coverage/parser/metrics/metrics.xml @@ -10,296 +10,12 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -308,1032 +24,14 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From 8930d3901157557a8f44ecbcba8042d0272da5e9 Mon Sep 17 00:00:00 2001 From: Maximilian Waidelich Date: Mon, 26 Aug 2024 10:31:45 +0200 Subject: [PATCH 4/5] Implemented requested changes --- README.md | 2 +- .../hafner/coverage/parser/MetricsParser.java | 50 ++++++++++++------- .../coverage/parser/MetricsParserTest.java | 2 + .../coverage/parser/metrics/metrics.xml | 4 +- 4 files changed, 36 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index b1a4e8f3..1f4ba673 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,6 @@ This library consists basically of two separate parts: * [JUnit](https://junit.org/junit5/) test results * [NUnit](https://nunit.org) test results * [XUnit](https://xunit.net) test results - * [PMD](https://pmd.github.io/) Metrics XML report + * Metrics XML report All source code is licensed under the MIT license. Contributions to this library are welcome! diff --git a/src/main/java/edu/hm/hafner/coverage/parser/MetricsParser.java b/src/main/java/edu/hm/hafner/coverage/parser/MetricsParser.java index 9bc0ffcf..d4db32c6 100644 --- a/src/main/java/edu/hm/hafner/coverage/parser/MetricsParser.java +++ b/src/main/java/edu/hm/hafner/coverage/parser/MetricsParser.java @@ -35,14 +35,15 @@ public class MetricsParser extends CoverageParser { private static final long serialVersionUID = -4461747681863455621L; /** XML elements. */ + private static final QName METRICS = new QName("metrics"); private static final QName PACKAGE = new QName("package"); private static final QName CLASS = new QName("class"); private static final QName METHOD = new QName("method"); private static final QName METRIC = new QName("metric"); private static final QName FILE = new QName("file"); - /** Required attributes of the XML elements. */ - private static final QName FQCN = new QName("fqcn"); + /** Attributes of the XML elements. */ + private static final QName PROJECT_NAME = new QName("projectName"); private static final QName NAME = new QName("name"); private static final QName BEGIN_LINE = new QName("beginline"); private static final QName VALUE = new QName("value"); @@ -77,22 +78,29 @@ protected ModuleNode parseReport(final Reader reader, final String fileName, fin var factory = new SecureXmlParserFactory(); var eventReader = factory.createXmlEventReader(reader); + ModuleNode root = new ModuleNode(""); + while (eventReader.hasNext()) { XMLEvent event = eventReader.nextEvent(); if (event.isStartElement()) { var startElement = event.asStartElement(); var tagName = startElement.getName(); - if (PACKAGE.equals(tagName)) { - var root = new ModuleNode(""); + if (METRICS.equals(tagName)) { + root = new ModuleNode(getOptionalValueOf(startElement, PROJECT_NAME).orElse("")); + } + else if (PACKAGE.equals(tagName)) { readPackage(eventReader, root, startElement, fileName); - return root; } } } - handleEmptyResults(fileName, log); - - return new ModuleNode("empty"); + if (root.hasChildren()) { + return root; + } + else { + handleEmptyResults(fileName, log); + return new ModuleNode("empty"); + } } catch (XMLStreamException exception) { throw new ParsingException(exception); @@ -102,8 +110,8 @@ protected ModuleNode parseReport(final Reader reader, final String fileName, fin @CanIgnoreReturnValue private PackageNode readPackage(final XMLEventReader reader, final ModuleNode root, final StartElement startElement, final String fileName) throws XMLStreamException { - var packageName = getValueOf(startElement, FQCN); - var packageNode = root.findOrCreatePackageNode(packageName.replaceAll("\\.", "/")); + var packageName = getValueOf(startElement, NAME); + var packageNode = root.findOrCreatePackageNode(packageName); while (reader.hasNext()) { XMLEvent event = reader.nextEvent(); @@ -160,15 +168,8 @@ private TreeString internPath(final String filePath) { private Node readSourceFile(final XMLEventReader reader, final PackageNode packageNode, final StartElement startElement, final String fileName) throws XMLStreamException { - var sourceFilePath = Paths.get(getValueOf(startElement, NAME)).getFileName(); - String sourceFilefileName; - if (sourceFilePath == null) { - sourceFilefileName = getValueOf(startElement, NAME); - } - else { - sourceFilefileName = sourceFilePath.toString(); - } - var fileNode = packageNode.findOrCreateFileNode(sourceFilefileName, + String sourceFileName = getSourceFileName(startElement); + var fileNode = packageNode.findOrCreateFileNode(sourceFileName, internPath(getValueOf(startElement, NAME))); while (reader.hasNext()) { @@ -193,6 +194,16 @@ else if (event.isEndElement()) { throw createEofException(fileName); } + private String getSourceFileName(final StartElement startSourceFileElement) { + var sourceFilePath = Paths.get(getValueOf(startSourceFileElement, NAME)).getFileName(); + if (sourceFilePath == null) { + return getValueOf(startSourceFileElement, NAME); + } + else { + return sourceFilePath.toString(); + } + } + @CanIgnoreReturnValue private Node readMethod(final XMLEventReader reader, final ClassNode classNode, final StartElement startElement, final String fileName) throws XMLStreamException { @@ -225,6 +236,7 @@ private MethodNode createMethod(final StartElement startElement, final String me } private void readValueCounter(final Node node, final StartElement startElement) { + // FIXME: create Metric Values independent of Metric Name String currentType = getValueOf(startElement, NAME); int value = parseInteger(getValueOf(startElement, VALUE)); if (CYCLOMATIC_COMPLEXITY.equals(currentType)) { diff --git a/src/test/java/edu/hm/hafner/coverage/parser/MetricsParserTest.java b/src/test/java/edu/hm/hafner/coverage/parser/MetricsParserTest.java index 32408a9a..3a7737bc 100644 --- a/src/test/java/edu/hm/hafner/coverage/parser/MetricsParserTest.java +++ b/src/test/java/edu/hm/hafner/coverage/parser/MetricsParserTest.java @@ -41,6 +41,8 @@ void simpleMetrics() { assertThat(tree).hasOnlyMetrics(MODULE, PACKAGE, FILE, CLASS, METHOD, COMPLEXITY, COMPLEXITY_MAXIMUM, NPATH_COMPLEXITY, NCSS, COGNITIVE_COMPLEXITY); + assertThat(tree).hasName("testProject"); + assertThat(tree.getChildren()).hasSize(1) .element(0) .satisfies(packageNode -> assertThat(packageNode).hasName("edu.hm.hafner.util")) diff --git a/src/test/resources/edu/hm/hafner/coverage/parser/metrics/metrics.xml b/src/test/resources/edu/hm/hafner/coverage/parser/metrics/metrics.xml index b0e95daa..4ef72063 100644 --- a/src/test/resources/edu/hm/hafner/coverage/parser/metrics/metrics.xml +++ b/src/test/resources/edu/hm/hafner/coverage/parser/metrics/metrics.xml @@ -1,6 +1,6 @@ - - + + From 8626bcf9216070aa59691d5a231fd8f208e0dce2 Mon Sep 17 00:00:00 2001 From: Maximilian Waidelich Date: Mon, 26 Aug 2024 10:48:13 +0200 Subject: [PATCH 5/5] Fix unused import --- src/main/java/edu/hm/hafner/coverage/parser/MetricsParser.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/edu/hm/hafner/coverage/parser/MetricsParser.java b/src/main/java/edu/hm/hafner/coverage/parser/MetricsParser.java index d4db32c6..c1423731 100644 --- a/src/main/java/edu/hm/hafner/coverage/parser/MetricsParser.java +++ b/src/main/java/edu/hm/hafner/coverage/parser/MetricsParser.java @@ -22,7 +22,6 @@ import edu.hm.hafner.util.FilteredLog; import edu.hm.hafner.util.PathUtil; import edu.hm.hafner.util.SecureXmlParserFactory; -import edu.hm.hafner.util.SecureXmlParserFactory.ParsingException; import edu.hm.hafner.util.TreeString; /**