From 94079059648b93bc3c39ddbf0f044a75dc418053 Mon Sep 17 00:00:00 2001 From: jtreichel Date: Sat, 16 Sep 2023 17:32:48 +0200 Subject: [PATCH 1/3] Added additional tests to improve overall code-coverage --- .../hafner/coverage/CoverageParserTest.java | 27 +++ .../edu/hm/hafner/coverage/CoverageTest.java | 130 ++++++++++- .../edu/hm/hafner/coverage/FileNodeTest.java | 210 ++++++++++++++++++ .../hm/hafner/coverage/IntegerValueTest.java | 34 +++ .../edu/hm/hafner/coverage/MetricTest.java | 65 ++++++ .../hafner/coverage/MutationStatusTest.java | 23 ++ .../edu/hm/hafner/coverage/MutationTest.java | 111 +++++++++ .../java/edu/hm/hafner/coverage/NodeTest.java | 208 +++++++++++++++++ .../hm/hafner/coverage/SafeFractionTest.java | 17 ++ .../edu/hm/hafner/coverage/ValueTest.java | 108 +++++++++ 10 files changed, 930 insertions(+), 3 deletions(-) create mode 100644 src/test/java/edu/hm/hafner/coverage/CoverageParserTest.java create mode 100644 src/test/java/edu/hm/hafner/coverage/IntegerValueTest.java create mode 100644 src/test/java/edu/hm/hafner/coverage/MutationStatusTest.java create mode 100644 src/test/java/edu/hm/hafner/coverage/MutationTest.java create mode 100644 src/test/java/edu/hm/hafner/coverage/ValueTest.java diff --git a/src/test/java/edu/hm/hafner/coverage/CoverageParserTest.java b/src/test/java/edu/hm/hafner/coverage/CoverageParserTest.java new file mode 100644 index 00000000..880f964d --- /dev/null +++ b/src/test/java/edu/hm/hafner/coverage/CoverageParserTest.java @@ -0,0 +1,27 @@ +package edu.hm.hafner.coverage; +import org.junit.jupiter.api.Test; +import edu.hm.hafner.util.SecureXmlParserFactory.ParsingException; +import static edu.hm.hafner.coverage.assertions.Assertions.*; +/** + * Test-class to provide tests for protected (static) methods of abstract class + * {@link CoverageParser}. + * + * @author Jannik Treichel + */ +class CoverageParserTest { + + @Test + void shouldCreateEofException() { + var parsingException = CoverageParser.createEofException(); + + assertThat(parsingException) + .isInstanceOf(ParsingException.class) + .hasMessage("Unexpected end of file"); + } + + @Test + void shouldReturnZeroOnInvalidStringParsing() { + assertThat(CoverageParser.parseInteger("NO_NUMBER")) + .isEqualTo(0); + } +} diff --git a/src/test/java/edu/hm/hafner/coverage/CoverageTest.java b/src/test/java/edu/hm/hafner/coverage/CoverageTest.java index a8839a8c..adbac43c 100644 --- a/src/test/java/edu/hm/hafner/coverage/CoverageTest.java +++ b/src/test/java/edu/hm/hafner/coverage/CoverageTest.java @@ -103,11 +103,18 @@ void shouldThrowExceptionWithIncompatibleValue() { var builder = new CoverageBuilder().setMetric(Metric.LINE); Coverage coverage = builder.setCovered(1).setMissed(2).build(); + Coverage wrongMetric = builder.setMetric(Metric.LOC).build(); var loc = new LinesOfCode(1); - assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> coverage.add(loc)); - assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> coverage.max(loc)); - assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> coverage.delta(loc)); + assertThatIllegalArgumentException().isThrownBy(() -> coverage.add(loc)); + assertThatIllegalArgumentException().isThrownBy(() -> coverage.max(loc)); + assertThatIllegalArgumentException().isThrownBy(() -> coverage.delta(loc)); + assertThatIllegalArgumentException().isThrownBy(() -> coverage.add(wrongMetric)); + assertThatIllegalArgumentException().isThrownBy(() -> coverage.max(wrongMetric)); + assertThatIllegalArgumentException().isThrownBy(() -> coverage.delta(wrongMetric)); + assertThatIllegalArgumentException().isThrownBy(() -> wrongMetric.add(loc)); + assertThatIllegalArgumentException().isThrownBy(() -> wrongMetric.max(loc)); + assertThatIllegalArgumentException().isThrownBy(() -> wrongMetric.delta(loc)); } @Test @@ -176,4 +183,121 @@ void shouldCreateCoverage(final int covered, final int missed, final String toSt void shouldAdhereToEquals() { EqualsVerifier.forClass(Coverage.class).withRedefinedSuperclass().verify(); } + + @Test + void shouldSerializeToString() { + var builder = new CoverageBuilder().setMetric(Metric.LINE); + + Coverage coverage = builder.setCovered(10).setMissed(10).build(); + + assertThat(coverage.serialize()).isEqualTo("LINE: 10/20"); + } + + @Test + void shouldSetMetricCoveredMissedByString() { + var builder = new CoverageBuilder().setMetric("LINE"); + + Coverage coverage = builder.setCovered("10").setMissed("16").build(); + + assertThat(coverage) + .hasMetric(Metric.LINE) + .hasCovered(10) + .hasMissed(16); + } + + @Test + void shouldCreateCoverageBasedOnStringRepresentation() { + Coverage coverage = valueOf(Metric.LINE, "16/20"); + + assertThat(coverage) + .hasMetric(Metric.LINE) + .hasCovered(16) + .hasMissed(4) + .hasTotal(20); + } + + @Test + void shouldThrowExceptionOnBadStringRepresentation() { + String invalidSeparator = "10-20"; + String totalSmallerThanCovered = "20/10"; + String noNumber = "NO/NUMBER"; + + assertThatIllegalArgumentException() + .isThrownBy(() -> valueOf(Metric.LINE, invalidSeparator)) + .withMessageContaining(invalidSeparator); + assertThatIllegalArgumentException() + .isThrownBy(() -> valueOf(Metric.LINE, totalSmallerThanCovered)) + .withMessageContaining(totalSmallerThanCovered); + assertThatIllegalArgumentException() + .isThrownBy(() -> valueOf(Metric.LINE, noNumber)) + .withMessageContaining(noNumber); + } + + @Test + void shouldCreateCoverageBasedOnFullStringRepresentation() { + Value value = Value.valueOf("LINE:10/20"); + assertThat(value).isInstanceOf(Coverage.class); + + Coverage coverage = (Coverage) value; + assertThat(coverage) + .hasMetric(Metric.LINE) + .hasCovered(10) + .hasMissed(10) + .hasTotal(20); + } + + @Test + void shouldCalculateThirdValueOnBuilder() { + Coverage coveredTotal = new CoverageBuilder().setMetric(Metric.LINE).setCovered(15).setTotal(40).build(); + Coverage coveredMissed = new CoverageBuilder().setMetric(Metric.LINE).setCovered(16).setMissed(16).build(); + Coverage totalMissed = new CoverageBuilder().setMetric(Metric.LINE).setTotal(40).setMissed(15).build(); + + assertThat(coveredTotal).hasTotal(40).hasMissed(25).hasCovered(15); + assertThat(coveredMissed).hasTotal(32).hasMissed(16).hasCovered(16); + assertThat(totalMissed).hasTotal(40).hasMissed(15).hasCovered(25); + } + + @Test + void shouldThrowExceptionWhenSettingNoneOnBuilder() { + var coverageBuilder = new CoverageBuilder(); + + assertThatIllegalArgumentException() + .isThrownBy(coverageBuilder::build) + .withMessageContaining("Exactly two properties have to be set."); + } + + @Test + void shouldThrowExceptionWhenSettingOneOnBuilder() { + CoverageBuilder onlyTotal = new CoverageBuilder().setTotal(20); + CoverageBuilder onlyMissed = new CoverageBuilder().setMissed(20); + CoverageBuilder onlyCovered = new CoverageBuilder().setCovered(20); + + assertThatIllegalArgumentException() + .isThrownBy(onlyTotal::build) + .withMessageContaining("Exactly two properties have to be set."); + assertThatIllegalArgumentException() + .isThrownBy(onlyMissed::build) + .withMessageContaining("Exactly two properties have to be set."); + assertThatIllegalArgumentException() + .isThrownBy(onlyCovered::build) + .withMessageContaining("Exactly two properties have to be set."); + } + + @Test + void shouldThrowExceptionWhenSettingThreeOnBuilder() { + var coverageBuilder = new CoverageBuilder().setCovered(10).setMissed(10).setTotal(20); + + assertThatIllegalArgumentException() + .isThrownBy(coverageBuilder::build) + .withMessageContaining("Setting all three values covered, missed, and total is not allowed"); + } + + @Test + void shouldThrowExceptionIfNoMetricDefinedOnBuilder() { + var coverageBuilder = new CoverageBuilder().setMissed(10).setTotal(20); + + assertThatIllegalArgumentException() + .isThrownBy(coverageBuilder::build) + .withMessageContaining("No metric defined."); + } } diff --git a/src/test/java/edu/hm/hafner/coverage/FileNodeTest.java b/src/test/java/edu/hm/hafner/coverage/FileNodeTest.java index 00e72567..34338a36 100644 --- a/src/test/java/edu/hm/hafner/coverage/FileNodeTest.java +++ b/src/test/java/edu/hm/hafner/coverage/FileNodeTest.java @@ -1,13 +1,17 @@ package edu.hm.hafner.coverage; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.math.Fraction; import org.junit.jupiter.api.Test; import edu.hm.hafner.util.TreeString; +import edu.hm.hafner.coverage.Mutation.MutationBuilder; import nl.jqno.equalsverifier.Warning; import nl.jqno.equalsverifier.api.EqualsVerifierApi; +import java.util.NavigableMap; + import static edu.hm.hafner.coverage.assertions.Assertions.*; class FileNodeTest extends AbstractNodeTest { @@ -75,4 +79,210 @@ void shouldReadOldVersion() { serializable.setRelativePath(TreeString.valueOf(StringUtils.EMPTY)); assertThatRestoredInstanceEqualsOriginalInstance(serializable, restore(restored)); } + + @Test + void shouldComputeDelta() { + var builder = new Coverage.CoverageBuilder(); + + var fileA = new FileNode("FileA.java", "."); + var fileALineCoverage = builder.setMetric(Metric.LINE).setCovered(10).setMissed(10).build(); + fileA.addValue(fileALineCoverage); + + var fileB = new FileNode("FileB.java", "."); + var fileBLineCoverage = builder.setMetric(Metric.LINE).setCovered(20).setMissed(0).build(); + var fileABranchCoverage = builder.setMetric(Metric.BRANCH).setCovered(10).setMissed(5).build(); + fileB.addValue(fileBLineCoverage); + fileB.addValue(fileABranchCoverage); + + fileB.computeDelta(fileA); + + assertThat(fileB.hasDelta(Metric.LINE)).isTrue(); + assertThat(fileB.hasDelta(Metric.BRANCH)).isFalse(); + assertThat(fileB.getDelta(Metric.LINE)) + .isEqualTo(Fraction.getFraction(20 - 10, 20).reduce()); + } + + @Test + void shouldGetCounters() { + var fileA = new FileNode("FileA.java", "."); + fileA.addCounters(10, 2, 0); + fileA.addCounters(15, 3, 1); + fileA.addCounters(28, 0, 5); + + NavigableMap counters = fileA.getCounters(); + + assertThat(counters) + .containsKeys(10, 15, 28) + .containsValues(2, 3, 0) + .hasSize(3); + assertThat(fileA).hasLinesWithCoverage(10, 15, 28); + assertThat(fileA.hasCoverageForLine(20)).isFalse(); + } + + @Test + void shouldAddModifiedLines() { + FileNode noModifiedLines = new FileNode("NoModified.java", "."); + FileNode modifiedLines = new FileNode("Modified.java", "."); + + modifiedLines.addModifiedLines(1, 3, 5); + + assertThat(noModifiedLines.hasModifiedLines()).isFalse(); + assertThat(modifiedLines.hasModifiedLines()).isTrue(); + assertThat(modifiedLines).hasOnlyModifiedLines(1, 3, 5); + assertThat(modifiedLines.hasModifiedLine(4)).isFalse(); + } + + @Test + void shouldGetPartiallyCoveredLines() { + var fileNode = new FileNode("NoModified.java", "."); + + fileNode.addCounters(1, 2, 1); + fileNode.addCounters(2, 1, 0); + fileNode.addCounters(3, 0, 1); + fileNode.addCounters(4, 4, 3); + + assertThat(fileNode.getPartiallyCoveredLines()) + .containsOnlyKeys(1, 4) + .containsValues(1, 3); + } + + @Test + void shouldThrowExceptionOnFilterTreeByModifiedLinesIfCoverageTotalIsZero() { + var fileNode = new FileNode("file.java", "."); + + fileNode.addCounters(2, 0, 0); + fileNode.addModifiedLines(2); + + assertThatIllegalArgumentException() + .isThrownBy(fileNode::filterTreeByModifiedLines) + .withMessageContaining("No coverage for line"); + } + + @Test + void shouldFilterTreeByModifiedLinesWithMutation() { + var mutationBuilder = new MutationBuilder(); + var fileNode = new FileNode("file.java", "."); + var mutation = mutationBuilder.setLine(2).build(); + var otherMutation = mutationBuilder.setLine(3).build(); + + fileNode.addCounters(2, 1, 0); + fileNode.addModifiedLines(2); + fileNode.addMutation(mutation); + fileNode.addMutation(otherMutation); + + fileNode = (FileNode) fileNode.filterTreeByModifiedLines().get(); + + assertThat(fileNode).hasOnlyMutations(mutation); + } + + @Test + void shouldFilterTreeByModifiedLinesWithNoMutations() { + var fileNode = new FileNode("file.java", "."); + + fileNode.addCounters(2, 1, 0); + fileNode.addModifiedLines(2); + + fileNode = (FileNode) fileNode.filterTreeByModifiedLines().get(); + + assertThat(fileNode).hasNoMutations(); + } + + @Test + void shouldFilterTreeByIndirectChangesPositiveDelta() { + FileNode lineCoverage = new FileNode("lineCoverage.java", "."); + FileNode branchCoverage = new FileNode("branchCoverage.java", "."); + FileNode lineAndBranchCoverage = new FileNode("lineAndBranchCoverage.java", "."); + + lineCoverage.addCounters(1, 1, 0); + lineCoverage.addIndirectCoverageChange(1, 1); + branchCoverage.addCounters(1, 2, 0); + branchCoverage.addIndirectCoverageChange(1, 3); + lineAndBranchCoverage.addCounters(1, 2, 0); + lineAndBranchCoverage.addCounters(2, 1, 0); + lineAndBranchCoverage.addIndirectCoverageChange(1, 2); + + Node filteredLineCoverage = lineCoverage.filterTreeByIndirectChanges().get(); + Node filteredBranchCoverage = branchCoverage.filterTreeByIndirectChanges().get(); + Node filteredLineAndBranchCoverage = lineAndBranchCoverage.filterTreeByIndirectChanges().get(); + + assertThat(filteredLineCoverage) + .hasOnlyValueMetrics(Metric.LINE); + assertThat((Coverage) filteredLineCoverage.getValue(Metric.LINE).get()) + .hasCovered(1) + .hasMissed(0); + assertThat(filteredBranchCoverage) + .hasOnlyValueMetrics(Metric.BRANCH); + assertThat((Coverage) filteredBranchCoverage.getValue(Metric.BRANCH).get()) + .hasCovered(3) + .hasMissed(0); + assertThat(filteredLineAndBranchCoverage) + .hasOnlyValueMetrics(Metric.LINE, Metric.BRANCH); + assertThat((Coverage) filteredLineAndBranchCoverage.getValue(Metric.LINE).get()) + .hasCovered(1) + .hasMissed(0); + assertThat((Coverage) filteredLineAndBranchCoverage.getValue(Metric.BRANCH).get()) + .hasCovered(2) + .hasMissed(0); + } + + @Test + void shouldFilterTreeByIndirectChangesNegativeDelta() { + FileNode lineCoverage = new FileNode("lineCoverage.java", "."); + FileNode branchCoverage = new FileNode("branchCoverage.java", "."); + FileNode lineAndBranchCoverage = new FileNode("lineAndBranchCoverage.java", "."); + + lineCoverage.addCounters(1, 1, 0); + lineCoverage.addIndirectCoverageChange(1, -1); + branchCoverage.addCounters(1, 2, 0); + branchCoverage.addIndirectCoverageChange(1, -3); + lineAndBranchCoverage.addCounters(1, 2, 0); + lineAndBranchCoverage.addCounters(2, 1, 0); + lineAndBranchCoverage.addIndirectCoverageChange(1, -2); + + Node filteredLineCoverage = lineCoverage.filterTreeByIndirectChanges().get(); + Node filteredBranchCoverage = branchCoverage.filterTreeByIndirectChanges().get(); + Node filteredLineAndBranchCoverage = lineAndBranchCoverage.filterTreeByIndirectChanges().get(); + + assertThat(filteredLineCoverage) + .hasNoValueMetrics(); + assertThat(filteredBranchCoverage) + .hasOnlyValueMetrics(Metric.BRANCH); + assertThat((Coverage) filteredBranchCoverage.getValue(Metric.BRANCH).get()) + .hasCovered(0) + .hasMissed(3); + assertThat(filteredLineAndBranchCoverage) + .hasOnlyValueMetrics(Metric.BRANCH); + assertThat((Coverage) filteredLineAndBranchCoverage.getValue(Metric.BRANCH).get()) + .hasCovered(0) + .hasMissed(2); + } + + @Test + void shouldFilterTreeByIndirectChangesNoDelta() { + var fileNode = new FileNode("file.java", "."); + + fileNode.addCounters(1, 2, 0); + fileNode.addCounters(2, 0, 1); + fileNode.addIndirectCoverageChange(1, 0); + fileNode.addIndirectCoverageChange(2, 0); + + var filteredFileNode = fileNode.filterTreeByIndirectChanges().get(); + + assertThat(filteredFileNode) + .hasNoValueMetrics(); + } + + @Test + void shouldFilterTreeByIndirectChangeWhenDeltaForNonCoveredLine() { + var fileNode = new FileNode("file.java", "."); + + fileNode.addCounters(1, 2, 0); + fileNode.addCounters(2, 1, 0); + fileNode.addIndirectCoverageChange(3, 1); + + var filteredFileNode = fileNode.filterTreeByIndirectChanges().get(); + + assertThat(filteredFileNode) + .hasNoValueMetrics(); + } } diff --git a/src/test/java/edu/hm/hafner/coverage/IntegerValueTest.java b/src/test/java/edu/hm/hafner/coverage/IntegerValueTest.java new file mode 100644 index 00000000..fc04ad4f --- /dev/null +++ b/src/test/java/edu/hm/hafner/coverage/IntegerValueTest.java @@ -0,0 +1,34 @@ +package edu.hm.hafner.coverage; + +import org.apache.commons.lang3.math.Fraction; +import org.junit.jupiter.api.Test; + +import static edu.hm.hafner.coverage.assertions.Assertions.*; + +class IntegerValueTest { + + @Test + void shouldThrowExceptionOnInvalidDeltaParameter() { + var linesOfCode = new LinesOfCode(20); + var cyclomaticComplexity = new CyclomaticComplexity(10); + var fractionValue = new FractionValue(Metric.LOC, Fraction.getFraction(1, 1)); + + assertThatIllegalArgumentException() + .isThrownBy(() -> linesOfCode.delta(cyclomaticComplexity)) + .withMessageContaining("Cannot cast incompatible types"); + assertThatIllegalArgumentException() + .isThrownBy(() -> linesOfCode.delta(fractionValue)) + .withMessageContaining("Cannot cast incompatible types"); + } + + @Test + void shouldSerialize() { + var linesOfCode = new LinesOfCode(20); + var cyclomaticComplexity = new CyclomaticComplexity(10); + + assertThat(linesOfCode.serialize()) + .isEqualTo("LOC: 20"); + assertThat(cyclomaticComplexity.serialize()) + .isEqualTo("COMPLEXITY: 10"); + } +} diff --git a/src/test/java/edu/hm/hafner/coverage/MetricTest.java b/src/test/java/edu/hm/hafner/coverage/MetricTest.java index 902aa8c3..e1a4bfc5 100644 --- a/src/test/java/edu/hm/hafner/coverage/MetricTest.java +++ b/src/test/java/edu/hm/hafner/coverage/MetricTest.java @@ -1,8 +1,12 @@ package edu.hm.hafner.coverage; +import org.apache.commons.lang3.math.Fraction; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; +import java.util.NavigableSet; + import static edu.hm.hafner.coverage.assertions.Assertions.*; /** @@ -20,4 +24,65 @@ void shouldConvertToTags(final Metric metric) { var converted = Metric.fromTag(tag); assertThat(converted).isSameAs(metric); } + + @Test + void shouldGetCoverageMetrics() { + NavigableSet metrics = Metric.getCoverageMetrics(); + + assertThat(metrics).containsExactly( + Metric.CONTAINER, + Metric.MODULE, + Metric.PACKAGE, + Metric.FILE, + Metric.CLASS, + Metric.METHOD, + Metric.LINE, + Metric.BRANCH, + Metric.INSTRUCTION); + } + + /** + * Tests if the object in the evaluator-attribute of class {@link Metric} + * correctly overrides its isAggregatingChildren-method. + */ + @Test + void shouldCorrectlyImplementIsContainer() { + assertThat(Metric.MODULE.isContainer()).isTrue(); + assertThat(Metric.LINE.isContainer()).isFalse(); + assertThat(Metric.COMPLEXITY_DENSITY.isContainer()).isFalse(); + assertThat(Metric.LOC.isContainer()).isFalse(); + } + + @Test + void shouldCorrectlyComputeDensityEvaluator() { + var node = new PackageNode("package"); + node.addValue(new Coverage.CoverageBuilder().setMetric(Metric.LINE).setCovered(5).setMissed(5).build()); + node.addValue(new CyclomaticComplexity(10)); + + Value complexityDensity = Metric.COMPLEXITY_DENSITY.getValueFor(node).get(); + + assertThat(complexityDensity) + .isInstanceOf(FractionValue.class); + assertThat((FractionValue) complexityDensity) + .hasFraction(Fraction.getFraction(10, 10)); + } + + @Test + void shouldReturnEmptyOptionalOnComputeDensityEvaluator() { + Coverage zeroLines = new Coverage.CoverageBuilder().setMetric(Metric.LINE).setCovered(0).setMissed(0).build(); + Coverage tenLines = new Coverage.CoverageBuilder().setMetric(Metric.LINE).setCovered(5).setMissed(5).build(); + CyclomaticComplexity cyclomaticComplexity = new CyclomaticComplexity(10); + + var onlyLinesOfCode = new PackageNode("package"); + onlyLinesOfCode.addValue(tenLines); + var onlyCyclomaticComplexity = new PackageNode("package"); + onlyCyclomaticComplexity.addValue(cyclomaticComplexity); + var zeroLinesOfCodeWithComplexity = new PackageNode("package"); + zeroLinesOfCodeWithComplexity.addValue(zeroLines); + zeroLinesOfCodeWithComplexity.addValue(cyclomaticComplexity); + + assertThat(Metric.COMPLEXITY_DENSITY.getValueFor(onlyLinesOfCode)).isEmpty(); + assertThat(Metric.COMPLEXITY_DENSITY.getValueFor(onlyCyclomaticComplexity)).isEmpty(); + assertThat(Metric.COMPLEXITY_DENSITY.getValueFor(zeroLinesOfCodeWithComplexity)).isEmpty(); + } } diff --git a/src/test/java/edu/hm/hafner/coverage/MutationStatusTest.java b/src/test/java/edu/hm/hafner/coverage/MutationStatusTest.java new file mode 100644 index 00000000..45df464e --- /dev/null +++ b/src/test/java/edu/hm/hafner/coverage/MutationStatusTest.java @@ -0,0 +1,23 @@ +package edu.hm.hafner.coverage; + +import org.junit.jupiter.api.Test; + +import static edu.hm.hafner.coverage.assertions.Assertions.*; + +class MutationStatusTest { + + @Test + void shouldReturnCorrectIsDetected() { + // Use method-calls instead of custom-assertions to invoke correct methods + assertThat(MutationStatus.KILLED.isDetected()).isTrue(); + assertThat(MutationStatus.SURVIVED.isDetected()).isFalse(); + } + + @Test + void shouldReturnCorrectIsNotDetected() { + // Use method-calls instead of custom-assertions to invoke correct methods + assertThat(MutationStatus.KILLED.isNotDetected()).isFalse(); + assertThat(MutationStatus.SURVIVED.isNotDetected()).isTrue(); + assertThat(MutationStatus.NO_COVERAGE.isNotDetected()).isTrue(); + } +} \ No newline at end of file diff --git a/src/test/java/edu/hm/hafner/coverage/MutationTest.java b/src/test/java/edu/hm/hafner/coverage/MutationTest.java new file mode 100644 index 00000000..322cb0c4 --- /dev/null +++ b/src/test/java/edu/hm/hafner/coverage/MutationTest.java @@ -0,0 +1,111 @@ +package edu.hm.hafner.coverage; + +import edu.hm.hafner.util.TreeStringBuilder; +import org.junit.jupiter.api.Test; + +import nl.jqno.equalsverifier.EqualsVerifier; + +import static edu.hm.hafner.coverage.assertions.Assertions.*; + +/** + * Additional test-cases for interacting with objects of + * class {@link Mutation} and its attributes. + * These might also be tested by adding additional asserts + * in the tests for the {@link Node} class --> {@link NodeTest}. + */ +class MutationTest { + + private Mutation createDummyMutation(final String identifier, final boolean detected, + final MutationStatus status) { + return new Mutation.MutationBuilder() + .setMutatedClass(String.format("Class%s.class", identifier)) + .setSourceFile(String.format("Class%s.java", identifier)) + .setMutatedMethod(String.format("Method%s", identifier)) + .setLine("1") + .setStatus(status) + .setIsDetected(detected) + .setMutator(String.format("Mutator%s", identifier)) + .setKillingTest(String.format("Test%s", identifier)) + .setMutatedMethodSignature(String.format("Signature%s", identifier)) + .setDescription(String.format("Description%s", identifier)) + .build(); + } + + @Test + void shouldReturnCorrectAttributeValues() { + var mutationA = createDummyMutation("A", false, MutationStatus.NO_COVERAGE); + var mutationB = createDummyMutation("B", true, MutationStatus.KILLED); + var mutationC = createDummyMutation("C", false, MutationStatus.RUN_ERROR); + + assertThat(mutationA) + .hasMutatedClass("ClassA.class") + .hasMethod("MethodA") + .hasLine(1) + .hasStatus(MutationStatus.NO_COVERAGE) + .isNotDetected() + .hasMutator("MutatorA") + .hasKillingTest("TestA") + .hasSignature("SignatureA") + .hasDescription("DescriptionA") + .isValid() + .isNotKilled(); + assertThat(mutationB) + .hasMutatedClass("ClassB.class") + .hasMethod("MethodB") + .hasLine(1) + .hasStatus(MutationStatus.KILLED) + .isDetected() + .hasMutator("MutatorB") + .hasKillingTest("TestB") + .hasSignature("SignatureB") + .hasDescription("DescriptionB") + .isValid() + .isKilled(); + assertThat(mutationC) + .hasMutatedClass("ClassC.class") + .hasMethod("MethodC") + .hasLine(1) + .hasStatus(MutationStatus.RUN_ERROR) + .isNotDetected() + .hasMutator("MutatorC") + .hasKillingTest("TestC") + .hasSignature("SignatureC") + .hasDescription("DescriptionC") + .isNotValid() + .isNotKilled(); + assertThat(mutationA.getStatus()) + .isNotDetected(); + assertThat(mutationB.getStatus()) + .isDetected(); + } + + @Test + void shouldBuildAndAddToModule() { + var mutationBuilder = new Mutation.MutationBuilder() + .setMutatedClass("module.package.file.Class") + .setSourceFile("Class.java") + .setMutatedMethod("Method") + .setLine("1") + .setStatus(MutationStatus.NO_COVERAGE) + .setIsDetected(false) + .setMutator("Mutator") + .setKillingTest("Test") + .setMutatedMethodSignature("Signature") + .setDescription("Description"); + var moduleNode = new ModuleNode("module"); + + mutationBuilder.buildAndAddToModule(moduleNode, new TreeStringBuilder()); + + assertThat(moduleNode.getAllFileNodes()) + .hasSize(1); + assertThat(moduleNode.getAllFileNodes().get(0)) + .hasName("Class.java") + .hasMutations(mutationBuilder.build()); + + } + + @Test + void shouldAdhereToEquals() { + EqualsVerifier.forClass(Mutation.class).verify(); + } +} \ No newline at end of file diff --git a/src/test/java/edu/hm/hafner/coverage/NodeTest.java b/src/test/java/edu/hm/hafner/coverage/NodeTest.java index 5b0bf1e7..f69da6f8 100644 --- a/src/test/java/edu/hm/hafner/coverage/NodeTest.java +++ b/src/test/java/edu/hm/hafner/coverage/NodeTest.java @@ -1,8 +1,11 @@ package edu.hm.hafner.coverage; +import java.util.Arrays; import java.util.List; +import java.util.NavigableMap; import java.util.NoSuchElementException; +import org.apache.commons.lang3.math.Fraction; import org.assertj.core.api.ThrowingConsumer; import org.junit.jupiter.api.Test; import org.junitpioneer.jupiter.DefaultLocale; @@ -748,4 +751,209 @@ private Node createTreeWithoutCoverage() { return moduleNode; } + + + @Test + void shouldCreateEmptyNodes() { + Node fullyEmpty = new PackageNode("Empty Node"); + + assertThat(fullyEmpty).isEmpty(); + } + + @Test + void shouldCreateNonEmptyNodes() { + Node noChildrenButValues = new PackageNode("No Children"); + noChildrenButValues.addValue(new CoverageBuilder().setMetric(LINE).setCovered(10).setMissed(0).build()); + Node noValuesButChildren = new PackageNode("No Values"); + noValuesButChildren.addChild(new FileNode("child", ".")); + + assertThat(noChildrenButValues).isNotEmpty(); + assertThat(noValuesButChildren).isNotEmpty(); + } + + @Test + void shouldMergeSingleNodeInList() { + Node parent = new PackageNode("package"); + Node child = new FileNode("file", "."); + parent.addChild(child); + + Node merged = Node.merge(List.of(parent)); + + assertThat(merged).isEqualTo(parent); + } + + @Test + void shouldThrowExceptionOnMergeWithEmptyList() { + assertThatIllegalArgumentException() + .isThrownBy(() -> Node.merge(List.of())) + .withMessageContaining("Cannot merge an empty list of nodes"); + } + + @Test + void shouldMergeMultipleNodesWithSameNameInList() { + Node parentA = new PackageNode("package"); + Node childA = new FileNode("fileA", "."); + parentA.addChild(childA); + Node parentB = new PackageNode("package"); + Node childB = new FileNode("fileB", "."); + parentB.addChild(childB); + Node parentC = new PackageNode("package"); + Node childC = new FileNode("fileC", "."); + parentC.addChild(childC); + + Node merged = Node.merge(List.of(parentA, parentB, parentC)); + + assertThat(merged).hasOnlyChildren(childA, childB, childC); + } + + @Test + void shouldMergeMultipleNodesWithDifferentNameInList() { + Node parentA = new PackageNode("packageA"); + Node childA = new FileNode("fileA", "."); + parentA.addChild(childA); + Node parentB = new PackageNode("packageB"); + Node childB = new FileNode("fileB", "."); + parentB.addChild(childB); + Node parentC = new PackageNode("packageC"); + Node childC = new FileNode("fileC", "."); + parentC.addChild(childC); + + Node merged = Node.merge(List.of(parentA, parentB, parentC)); + + assertThat(merged) + .hasName("Container") + .hasMetric(CONTAINER) + .hasOnlyChildren(parentA, parentB, parentC); + } + + @Test + void shouldMergeMultipleNodesWithDifferentMetricInList() { + Node parentA = new ModuleNode("NodeA"); + Node parentB = new PackageNode("NodeA"); + Node parentC = new FileNode("NodeA", "."); + + Node merged = Node.merge(List.of(parentA, parentB, parentC)); + + assertThat(merged) + .hasName("Container") + .hasMetric(CONTAINER) + .hasOnlyChildren(parentA, parentB, parentC); + } + + @Test + void shouldMergeNodesWithValues() { + var coverageBuilder = new CoverageBuilder(); + Node parentA = new PackageNode("package"); + parentA.addValue(coverageBuilder.setMetric(LINE).setCovered(0).setMissed(10).build()); + Node parentB = new PackageNode("package"); + parentB.addValue(coverageBuilder.setMetric(LINE).setCovered(10).setMissed(0).build()); + parentB.addValue(coverageBuilder.setMetric(BRANCH).setCovered(2).setMissed(0).build()); + + Node merged = Node.merge(List.of(parentA, parentB)); + + assertThat(merged) + .hasName("package") + .hasOnlyValueMetrics(LINE); + assertThat((Coverage) merged.getValue(LINE).get()) + .hasCovered(10) + .hasMissed(0); + } + + @Test + void shouldGetAllNodesOfTypeInTree() { + Node tree = createTreeWithoutCoverage(); + FileNode coveredFile = tree.findFile("Covered.java").get(); + FileNode missedFile = tree.findFile("Missed.java").get(); + MethodNode coveredMethod = new MethodNode("coveredMethod", "signature"); + MethodNode missedMethod = new MethodNode("missedMethod", "signature"); + + tree.findClass("CoveredClass.class").get().addChild(coveredMethod); + tree.findClass("MissedClass.class").get().addChild(missedMethod); + + assertThat(tree.getAllMethodNodes()).containsExactlyInAnyOrder(coveredMethod, missedMethod); + assertThat(tree.getAllFileNodes()).containsExactlyInAnyOrder(coveredFile, missedFile); + } + + @Test + void shouldComputeDelta() { + var coverageBuilder = new CoverageBuilder(); + Node fileA = new FileNode("FileA.java", "."); + fileA.addAllValues(Arrays.asList( + coverageBuilder.setMetric(LINE).setCovered(10).setMissed(0).build(), + coverageBuilder.setMetric(BRANCH).setCovered(2).setMissed(0).build(), + coverageBuilder.setMetric(MUTATION).setCovered(2).setMissed(0).build() + )); + Node fileB = new FileNode("FileB.java", "."); + fileB.addAllValues(Arrays.asList( + coverageBuilder.setMetric(LINE).setCovered(0).setMissed(10).build(), + coverageBuilder.setMetric(BRANCH).setCovered(1).setMissed(1).build() + )); + + NavigableMap delta = fileA.computeDelta(fileB); + + assertThat(delta) + .containsKeys(FILE, LINE, BRANCH) + .doesNotContainKey(MUTATION); + assertThat(delta.getOrDefault(LINE, Fraction.ZERO)).isEqualTo(Fraction.getFraction(10, 10)); + assertThat(delta.getOrDefault(BRANCH, Fraction.ZERO)).isEqualTo(Fraction.getFraction(1, 2)); + } + + @Test + void shouldGetAllValueMetrics() { + CoverageBuilder coverageBuilder = new CoverageBuilder(); + Node fileA = new FileNode("FileA.java", "."); + fileA.addChild(new ClassNode("ClassA.java")); + fileA.addAllValues(Arrays.asList( + coverageBuilder.setMetric(LINE).setCovered(10).setMissed(0).build(), + coverageBuilder.setMetric(BRANCH).setCovered(2).setMissed(0).build(), + coverageBuilder.setMetric(MUTATION).setCovered(2).setMissed(0).build() + )); + assertThat(fileA.getValueMetrics()) + .containsExactlyInAnyOrder(LINE, BRANCH, MUTATION); + } + + @Test + void shouldContainMetric() { + Node fileA = new FileNode("FileA.java", "."); + fileA.addChild(new ClassNode("ClassA.java")); + fileA.addValue(new CoverageBuilder().setMetric(LINE).setCovered(10).setMissed(0).build()); + + assertThat(fileA.containsMetric(CLASS)).isTrue(); + assertThat(fileA.containsMetric(LINE)).isTrue(); + assertThat(fileA.containsMetric(BRANCH)).isFalse(); + } + + @Test + void shouldGetCoverageValueByMetricWithDefault() { + CoverageBuilder coverageBuilder = new CoverageBuilder(); + Coverage fileACoverage = coverageBuilder.setMetric(LINE).setCovered(10).setMissed(0).build(); + Coverage defaultCoverage = coverageBuilder.setMetric(BRANCH).setCovered(1).setMissed(0).build(); + Node fileA = new FileNode("FileA.java", "."); + fileA.addValue(fileACoverage); + + assertThat(fileA.getTypedValue(LINE, defaultCoverage)).isEqualTo(fileACoverage); + assertThat(fileA.getTypedValue(BRANCH, defaultCoverage)).isEqualTo(defaultCoverage); + } + + @Test + void shouldThrowExceptionWhenTryingToRemoveNodeThatIsNotAChild() { + Node moduleNode = new ModuleNode("module"); + Node packageNode = new PackageNode("package"); + + assertThatExceptionOfType(AssertionError.class) + .isThrownBy(() -> moduleNode.removeChild(packageNode)) + .withMessageContaining(String.format("The node %s is not a child of this node %s", packageNode, moduleNode)); + } + + @Test + void shouldReturnCorrectHasModifiedLines() { + Node packageNode = new PackageNode("package"); + FileNode fileNode = new FileNode("file", "."); + packageNode.addChild(fileNode); + + assertThat(packageNode).doesNotHaveModifiedLines(); + + fileNode.addModifiedLines(1); + assertThat(packageNode).hasModifiedLines(); + } } diff --git a/src/test/java/edu/hm/hafner/coverage/SafeFractionTest.java b/src/test/java/edu/hm/hafner/coverage/SafeFractionTest.java index 7eb76f9f..33316273 100644 --- a/src/test/java/edu/hm/hafner/coverage/SafeFractionTest.java +++ b/src/test/java/edu/hm/hafner/coverage/SafeFractionTest.java @@ -40,4 +40,21 @@ void shouldHandleOverflowForAdd() { var safeFraction = new SafeFraction(fraction); assertThat(safeFraction.add(Fraction.getFraction("100.0")).doubleValue()).isEqualTo(101.0); } + + + @Test + void shouldThrowExceptionOnInstanceWithDenominatorZero() { + assertThatIllegalArgumentException() + .isThrownBy(() -> new SafeFraction(1, 0)) + .withMessageContaining(Percentage.TOTALS_ZERO_MESSAGE); + } + + @Test + void shouldThrowExceptionOnSubtractWithDenominatorZero() { + var safeFraction = new SafeFraction(1, 1); + + assertThatIllegalArgumentException() + .isThrownBy(() -> safeFraction.subtract(1, 0)) + .withMessageContaining(Percentage.TOTALS_ZERO_MESSAGE); + } } diff --git a/src/test/java/edu/hm/hafner/coverage/ValueTest.java b/src/test/java/edu/hm/hafner/coverage/ValueTest.java new file mode 100644 index 00000000..aa90097c --- /dev/null +++ b/src/test/java/edu/hm/hafner/coverage/ValueTest.java @@ -0,0 +1,108 @@ +package edu.hm.hafner.coverage; + +import java.util.List; +import java.util.NoSuchElementException; + +import org.apache.commons.lang3.math.Fraction; +import org.junit.jupiter.api.Test; + +import nl.jqno.equalsverifier.EqualsVerifier; + +import static edu.hm.hafner.coverage.assertions.Assertions.*; + +class ValueTest { + + @Test + void shouldReturnCorrectValueOfCoverage() { + Value container = Value.valueOf("CONTAINER: 1/1"); + + assertThat(container) + .isInstanceOf(Coverage.class); + assertThat(Value.valueOf("MODULE: 1/1")) + .isInstanceOf(Coverage.class); + assertThat(Value.valueOf("PACKAGE: 1/1")) + .isInstanceOf(Coverage.class); + assertThat(Value.valueOf("FILE: 1/1")) + .isInstanceOf(Coverage.class); + assertThat(Value.valueOf("CLASS: 1/1")) + .isInstanceOf(Coverage.class); + assertThat(Value.valueOf("METHOD: 1/1")) + .isInstanceOf(Coverage.class); + assertThat(Value.valueOf("LINE: 1/1")) + .isInstanceOf(Coverage.class); + assertThat(Value.valueOf("INSTRUCTION: 1/1")) + .isInstanceOf(Coverage.class); + assertThat(Value.valueOf("BRANCH: 1/1")) + .isInstanceOf(Coverage.class); + assertThat(Value.valueOf("MUTATION: 1/1")) + .isInstanceOf(Coverage.class); + + assertThat((Coverage) container) + .hasCovered(1) + .hasMissed(0); + } + + @Test + void shouldReturnCorrectValueOfFractionValue() { + Value fractionValue = Value.valueOf("COMPLEXITY_DENSITY: 1/1"); + + assertThat(fractionValue) + .isInstanceOf(FractionValue.class); + assertThat((FractionValue) fractionValue) + .hasFraction(Fraction.getFraction(1, 1)); + } + + @Test + void shouldReturnCorrectValueOfCyclomaticComplexity() { + Value cyclomaticComplexity = Value.valueOf("COMPLEXITY: 1"); + + assertThat(cyclomaticComplexity) + .isInstanceOf(CyclomaticComplexity.class); + assertThat((CyclomaticComplexity) cyclomaticComplexity) + .hasValue(1); + } + + @Test + void shouldReturnCorrectValueOfLinesOfCode() { + Value linesOfCode = Value.valueOf("LOC: 1"); + + assertThat(linesOfCode) + .isInstanceOf(LinesOfCode.class); + assertThat((LinesOfCode) linesOfCode) + .hasValue(1); + } + + @Test + void shouldThrowExceptionOnInvalidStringRepresentation() { + String badRepresentation = "Bad representation"; + String badNumber = "COMPLEXITY: BadNumber"; + + assertThatIllegalArgumentException() + .isThrownBy(() -> Value.valueOf(badRepresentation)) + .withMessageContaining(String.format("Cannot convert '%s' to a valid Value instance.", badRepresentation)); + assertThatIllegalArgumentException() + .isThrownBy(() -> Value.valueOf(badNumber)) + .withMessageContaining(String.format("Cannot convert '%s' to a valid Value instance.", badNumber)); + } + + @Test + void shouldGetValue() { + var linesOfCode = new LinesOfCode(10); + var cyclomaticComplexity = new CyclomaticComplexity(20); + + List values = List.of(linesOfCode, cyclomaticComplexity); + + assertThat(Value.getValue(Metric.LOC, values)) + .isEqualTo(linesOfCode); + assertThat(Value.getValue(Metric.COMPLEXITY, values)) + .isEqualTo(cyclomaticComplexity); + assertThatExceptionOfType(NoSuchElementException.class) + .isThrownBy(() -> Value.getValue(Metric.LINE, values)) + .withMessageContaining("No value for metric"); + } + + @Test + void shouldAdhereToEquals() { + EqualsVerifier.simple().forClass(Value.class).verify(); + } +} \ No newline at end of file From a179a34f67fb24929f8d6f4d599302adb08d89d6 Mon Sep 17 00:00:00 2001 From: jtreichel Date: Sat, 16 Sep 2023 17:37:42 +0200 Subject: [PATCH 2/3] Implemented LineRange-class (including tests) --- .../java/edu/hm/hafner/coverage/FileNode.java | 10 ++ .../edu/hm/hafner/coverage/LineRange.java | 123 ++++++++++++++++++ .../edu/hm/hafner/coverage/FileNodeTest.java | 15 +++ .../edu/hm/hafner/coverage/LineRangeTest.java | 97 ++++++++++++++ 4 files changed, 245 insertions(+) create mode 100644 src/main/java/edu/hm/hafner/coverage/LineRange.java create mode 100644 src/test/java/edu/hm/hafner/coverage/LineRangeTest.java diff --git a/src/main/java/edu/hm/hafner/coverage/FileNode.java b/src/main/java/edu/hm/hafner/coverage/FileNode.java index 0ecbcd2a..fda0fc7b 100644 --- a/src/main/java/edu/hm/hafner/coverage/FileNode.java +++ b/src/main/java/edu/hm/hafner/coverage/FileNode.java @@ -465,6 +465,16 @@ public NavigableSet getMissedLines() { .collect(Collectors.toCollection(TreeSet::new)); } + /** + * Returns the lines that have no line coverage grouped in LineRanges. + * E.g., the lines 1, 2, 3 will be grouped in one {@link LineRange} instance. + * + * @return the aggregated LineRanges that have no line coverage + */ + public NavigableSet getMissedLineRanges() { + return LineRange.getRangesFromSortedLines(getMissedLines()); + } + /** * Returns the lines that contain survived mutations. The returned map contains the line number as the key and a * list of survived mutations as value. diff --git a/src/main/java/edu/hm/hafner/coverage/LineRange.java b/src/main/java/edu/hm/hafner/coverage/LineRange.java new file mode 100644 index 00000000..b00e7013 --- /dev/null +++ b/src/main/java/edu/hm/hafner/coverage/LineRange.java @@ -0,0 +1,123 @@ +package edu.hm.hafner.coverage; + +import java.util.NavigableSet; +import java.util.Objects; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; + +/** + * Represents a range of consecutive lines within a file ({@link FileNode}). + * + * @author Jannik Treichel + */ +public class LineRange implements Comparable { + + private final int start; + private final int end; + + /** + * Returns a set of LineRanges based on a given sorted set of line numbers. + * + * @param lines the set of line numbers to generate ranges from + * @return a navigable set of LineRanges + */ + public static NavigableSet getRangesFromSortedLines(final SortedSet lines) { + NavigableSet lineRanges = new TreeSet<>(); + + if (lines.isEmpty()) { + return lineRanges; + } + + int currentStart = lines.first(); + int currentEnd = lines.first(); + + for (int line : lines) { + if (line - currentEnd > 1) { + lineRanges.add(new LineRange(currentStart, currentEnd)); + currentStart = line; + } + currentEnd = line; + } + lineRanges.add(new LineRange(currentStart, currentEnd)); + + return lineRanges; + } + + /** + * Returns a set of LineRanges based on a given unsorted set of line numbers. + * + * @param lines the set of line numbers to generate ranges from + * @return a navigable set of LineRanges + */ + public static NavigableSet getRangesFromUnsortedLines(final Set lines) { + return getRangesFromSortedLines(new TreeSet<>(lines)); + } + + /** + * Constructs a new LineRange instance where the start- and end- + * values are equal. + * + * @param startAndEnd the start- and end-value of the range + */ + public LineRange(final int startAndEnd) { + this(startAndEnd, startAndEnd); + } + + /** + * Constructs a new LineRange instance with a specified start- + * and end-values. + * + * @param start the first line in the range + * @param end the last line in the range + * @throws IllegalArgumentException if start is less than or equal + * to zero, or if end is less than start + */ + public LineRange(final int start, final int end) { + if (start <= 0) { + throw new IllegalArgumentException( + String.format("A LineRange can only contain positive values! Start-value <%s> is not possible.", start)); + } + if (end < start) { + throw new IllegalArgumentException( + String.format("The end <%s> of the LineRange cannot be smaller than the start <%s>!", end, start)); + } + this.start = start; + this.end = end; + } + + public int getStart() { + return start; + } + + public int getEnd() { + return end; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + LineRange lineRange = (LineRange) o; + return start == lineRange.start && end == lineRange.end; + } + + @Override + public int hashCode() { + return Objects.hash(start, end); + } + + @Override + public int compareTo(final LineRange o) { + return Integer.compare(this.start, o.start); + } + + @Override + public String toString() { + return String.format("", start, end); + } +} \ No newline at end of file diff --git a/src/test/java/edu/hm/hafner/coverage/FileNodeTest.java b/src/test/java/edu/hm/hafner/coverage/FileNodeTest.java index 34338a36..d34ca68b 100644 --- a/src/test/java/edu/hm/hafner/coverage/FileNodeTest.java +++ b/src/test/java/edu/hm/hafner/coverage/FileNodeTest.java @@ -285,4 +285,19 @@ void shouldFilterTreeByIndirectChangeWhenDeltaForNonCoveredLine() { assertThat(filteredFileNode) .hasNoValueMetrics(); } + + @Test + void shouldReturnMissedLineRanges() { + var fileNode = new FileNode("file.java", "."); + + assertThat(fileNode.getMissedLineRanges()).isEmpty(); + + fileNode.addCounters(1, 1, 0); + fileNode.addCounters(2, 0, 1); + fileNode.addCounters(3, 0, 1); + fileNode.addCounters(5, 0, 1); + + assertThat(fileNode.getMissedLineRanges()) + .containsExactly(new LineRange(2, 3), new LineRange(5)); + } } diff --git a/src/test/java/edu/hm/hafner/coverage/LineRangeTest.java b/src/test/java/edu/hm/hafner/coverage/LineRangeTest.java new file mode 100644 index 00000000..19abca1d --- /dev/null +++ b/src/test/java/edu/hm/hafner/coverage/LineRangeTest.java @@ -0,0 +1,97 @@ +package edu.hm.hafner.coverage; + +import java.util.HashSet; +import java.util.List; +import java.util.TreeSet; + +import org.junit.jupiter.api.Test; + +import nl.jqno.equalsverifier.EqualsVerifier; + +import static edu.hm.hafner.coverage.assertions.Assertions.*; + +class LineRangeTest { + @Test + void shouldCreateLineRangeWithSameStartAndEnd() { + var lineRange = new LineRange(10); + + assertThat(lineRange.getStart()).isEqualTo(10); + assertThat(lineRange.getEnd()).isEqualTo(10); + } + + @Test + void shouldCreateLineRangeWithDifferentStartAndEnd() { + var lineRange = new LineRange(10, 20); + + assertThat(lineRange.getStart()).isEqualTo(10); + assertThat(lineRange.getEnd()).isEqualTo(20); + } + + @Test + void shouldThrowExceptionWhenCreatingSmallerThanZeroLineRange() { + assertThatIllegalArgumentException() + .isThrownBy(() -> new LineRange(0)) + .withMessageContainingAll("A LineRange can only contain positive values!", "0"); + assertThatIllegalArgumentException() + .isThrownBy(() -> new LineRange(-1)) + .withMessageContainingAll("A LineRange can only contain positive values!", "-1"); + assertThatIllegalArgumentException() + .isThrownBy(() -> new LineRange(0, 10)) + .withMessageContainingAll("A LineRange can only contain positive values!", "0"); + assertThatIllegalArgumentException() + .isThrownBy(() -> new LineRange(-1, 10)) + .withMessageContainingAll("A LineRange can only contain positive values!", "-1"); + } + + @Test + void shouldThrowExceptionWhenCreatingLineRangeWithSmallerEndThanStartValue() { + assertThatIllegalArgumentException() + .isThrownBy(() -> new LineRange(10, 5)) + .withMessageContainingAll("5", "cannot be smaller than", "10"); + } + + @Test + void shouldCreateLineRangeNavigableSetFromSortedLinesSortedSet() { + var lineRangesA = LineRange.getRangesFromSortedLines(new TreeSet<>(List.of(1, 2, 3, 4))); + var lineRangesB = LineRange.getRangesFromSortedLines(new TreeSet<>(List.of(1, 2, 4))); + + assertThat(lineRangesA).hasSize(1); + assertThat(lineRangesA).containsExactly(new LineRange(1, 4)); + assertThat(lineRangesB).hasSize(2); + assertThat(lineRangesB).containsExactly(new LineRange(1, 2), new LineRange(4)); + } + + @Test + void shouldCreateLineRangeNavigableSetFromUnsortedLinesSet() { + var lineRangesA = LineRange.getRangesFromUnsortedLines(new HashSet<>(List.of(1, 3, 4, 2))); + var lineRangesB = LineRange.getRangesFromUnsortedLines(new HashSet<>(List.of(1, 4, 2))); + + assertThat(lineRangesA).hasSize(1); + assertThat(lineRangesA).containsExactly(new LineRange(1, 4)); + assertThat(lineRangesB).hasSize(2); + assertThat(lineRangesB).containsExactly(new LineRange(1, 2), new LineRange(4)); + } + + @Test + void shouldReturnEmptySetOfLineRangesForNoLines() { + var lineRangesA = LineRange.getRangesFromSortedLines(new TreeSet<>()); + var lineRangesB = LineRange.getRangesFromUnsortedLines(new HashSet<>()); + + assertThat(lineRangesA).isEmpty(); + assertThat(lineRangesB).isEmpty(); + } + + @Test + void shouldCompareCorrectly() { + var lineRangeSmaller = new LineRange(1, 4); + var lineRangeBigger = new LineRange(5, 6); + + assertThat(lineRangeSmaller.compareTo(lineRangeBigger)).isLessThan(0); + assertThat(lineRangeBigger.compareTo(lineRangeSmaller)).isGreaterThan(0); + } + + @Test + void shouldAdhereToEquals() { + EqualsVerifier.simple().forClass(LineRange.class).verify(); + } +} \ No newline at end of file From 03c135d943fff4dfa738eecbf649d600f5e5b25c Mon Sep 17 00:00:00 2001 From: jtreichel Date: Sat, 16 Sep 2023 17:45:31 +0200 Subject: [PATCH 3/3] Replaced Optional.get() with Optional.orElseThrow() (should be fine for test-cases as the get()-calls for the artificially constructed objects will always return a value) --- .../edu/hm/hafner/coverage/FileNodeTest.java | 32 +++++++++---------- .../java/edu/hm/hafner/coverage/NodeTest.java | 24 +++++++------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/test/java/edu/hm/hafner/coverage/FileNodeTest.java b/src/test/java/edu/hm/hafner/coverage/FileNodeTest.java index d34ca68b..e6a0d7de 100644 --- a/src/test/java/edu/hm/hafner/coverage/FileNodeTest.java +++ b/src/test/java/edu/hm/hafner/coverage/FileNodeTest.java @@ -170,7 +170,7 @@ void shouldFilterTreeByModifiedLinesWithMutation() { fileNode.addMutation(mutation); fileNode.addMutation(otherMutation); - fileNode = (FileNode) fileNode.filterTreeByModifiedLines().get(); + fileNode = (FileNode) fileNode.filterTreeByModifiedLines().orElseThrow(); assertThat(fileNode).hasOnlyMutations(mutation); } @@ -182,7 +182,7 @@ void shouldFilterTreeByModifiedLinesWithNoMutations() { fileNode.addCounters(2, 1, 0); fileNode.addModifiedLines(2); - fileNode = (FileNode) fileNode.filterTreeByModifiedLines().get(); + fileNode = (FileNode) fileNode.filterTreeByModifiedLines().orElseThrow(); assertThat(fileNode).hasNoMutations(); } @@ -201,26 +201,26 @@ void shouldFilterTreeByIndirectChangesPositiveDelta() { lineAndBranchCoverage.addCounters(2, 1, 0); lineAndBranchCoverage.addIndirectCoverageChange(1, 2); - Node filteredLineCoverage = lineCoverage.filterTreeByIndirectChanges().get(); - Node filteredBranchCoverage = branchCoverage.filterTreeByIndirectChanges().get(); - Node filteredLineAndBranchCoverage = lineAndBranchCoverage.filterTreeByIndirectChanges().get(); + Node filteredLineCoverage = lineCoverage.filterTreeByIndirectChanges().orElseThrow(); + Node filteredBranchCoverage = branchCoverage.filterTreeByIndirectChanges().orElseThrow(); + Node filteredLineAndBranchCoverage = lineAndBranchCoverage.filterTreeByIndirectChanges().orElseThrow(); assertThat(filteredLineCoverage) .hasOnlyValueMetrics(Metric.LINE); - assertThat((Coverage) filteredLineCoverage.getValue(Metric.LINE).get()) + assertThat((Coverage) filteredLineCoverage.getValue(Metric.LINE).orElseThrow()) .hasCovered(1) .hasMissed(0); assertThat(filteredBranchCoverage) .hasOnlyValueMetrics(Metric.BRANCH); - assertThat((Coverage) filteredBranchCoverage.getValue(Metric.BRANCH).get()) + assertThat((Coverage) filteredBranchCoverage.getValue(Metric.BRANCH).orElseThrow()) .hasCovered(3) .hasMissed(0); assertThat(filteredLineAndBranchCoverage) .hasOnlyValueMetrics(Metric.LINE, Metric.BRANCH); - assertThat((Coverage) filteredLineAndBranchCoverage.getValue(Metric.LINE).get()) + assertThat((Coverage) filteredLineAndBranchCoverage.getValue(Metric.LINE).orElseThrow()) .hasCovered(1) .hasMissed(0); - assertThat((Coverage) filteredLineAndBranchCoverage.getValue(Metric.BRANCH).get()) + assertThat((Coverage) filteredLineAndBranchCoverage.getValue(Metric.BRANCH).orElseThrow()) .hasCovered(2) .hasMissed(0); } @@ -239,20 +239,20 @@ void shouldFilterTreeByIndirectChangesNegativeDelta() { lineAndBranchCoverage.addCounters(2, 1, 0); lineAndBranchCoverage.addIndirectCoverageChange(1, -2); - Node filteredLineCoverage = lineCoverage.filterTreeByIndirectChanges().get(); - Node filteredBranchCoverage = branchCoverage.filterTreeByIndirectChanges().get(); - Node filteredLineAndBranchCoverage = lineAndBranchCoverage.filterTreeByIndirectChanges().get(); + Node filteredLineCoverage = lineCoverage.filterTreeByIndirectChanges().orElseThrow(); + Node filteredBranchCoverage = branchCoverage.filterTreeByIndirectChanges().orElseThrow(); + Node filteredLineAndBranchCoverage = lineAndBranchCoverage.filterTreeByIndirectChanges().orElseThrow(); assertThat(filteredLineCoverage) .hasNoValueMetrics(); assertThat(filteredBranchCoverage) .hasOnlyValueMetrics(Metric.BRANCH); - assertThat((Coverage) filteredBranchCoverage.getValue(Metric.BRANCH).get()) + assertThat((Coverage) filteredBranchCoverage.getValue(Metric.BRANCH).orElseThrow()) .hasCovered(0) .hasMissed(3); assertThat(filteredLineAndBranchCoverage) .hasOnlyValueMetrics(Metric.BRANCH); - assertThat((Coverage) filteredLineAndBranchCoverage.getValue(Metric.BRANCH).get()) + assertThat((Coverage) filteredLineAndBranchCoverage.getValue(Metric.BRANCH).orElseThrow()) .hasCovered(0) .hasMissed(2); } @@ -266,7 +266,7 @@ void shouldFilterTreeByIndirectChangesNoDelta() { fileNode.addIndirectCoverageChange(1, 0); fileNode.addIndirectCoverageChange(2, 0); - var filteredFileNode = fileNode.filterTreeByIndirectChanges().get(); + var filteredFileNode = fileNode.filterTreeByIndirectChanges().orElseThrow(); assertThat(filteredFileNode) .hasNoValueMetrics(); @@ -280,7 +280,7 @@ void shouldFilterTreeByIndirectChangeWhenDeltaForNonCoveredLine() { fileNode.addCounters(2, 1, 0); fileNode.addIndirectCoverageChange(3, 1); - var filteredFileNode = fileNode.filterTreeByIndirectChanges().get(); + var filteredFileNode = fileNode.filterTreeByIndirectChanges().orElseThrow(); assertThat(filteredFileNode) .hasNoValueMetrics(); diff --git a/src/test/java/edu/hm/hafner/coverage/NodeTest.java b/src/test/java/edu/hm/hafner/coverage/NodeTest.java index f69da6f8..1796dd03 100644 --- a/src/test/java/edu/hm/hafner/coverage/NodeTest.java +++ b/src/test/java/edu/hm/hafner/coverage/NodeTest.java @@ -153,7 +153,7 @@ void shouldReturnAllNodesOfSpecificMetricType() { } private static Coverage getCoverage(final Node node, final Metric metric) { - return (Coverage) node.getValue(metric).get(); + return (Coverage) node.getValue(metric).orElseThrow(); } @Test @@ -492,9 +492,9 @@ void shouldUseDeepCopiedNodesInCombineWithInRelatedProjects() { sameProject.addChild(autogradingPkg); Node combinedReport = project.merge(sameProject); - assertThat(combinedReport.find(coveragePkg.getMetric(), coveragePkg.getName()).get()) + assertThat(combinedReport.find(coveragePkg.getMetric(), coveragePkg.getName()).orElseThrow()) .isNotSameAs(coveragePkg); - assertThat(combinedReport.find(autogradingPkg.getMetric(), autogradingPkg.getName()).get()) + assertThat(combinedReport.find(autogradingPkg.getMetric(), autogradingPkg.getName()).orElseThrow()) .isNotSameAs(autogradingPkg); } @@ -508,9 +508,9 @@ void shouldAlsoHandleReportsThatStopAtHigherLevelThanMethod() { pkg.addChild(file); Node otherReport = report.copyTree(); - otherReport.find(FILE, file.getName()).get().addValue( + otherReport.find(FILE, file.getName()).orElseThrow().addValue( new CoverageBuilder().setMetric(LINE).setCovered(90).setMissed(10).build()); - report.find(FILE, file.getName()).get().addValue( + report.find(FILE, file.getName()).orElseThrow().addValue( new CoverageBuilder().setMetric(LINE).setCovered(80).setMissed(20).build()); Node combined = report.merge(otherReport); @@ -526,9 +526,9 @@ void shouldAlsoHandleReportsThatStopAtHigherLevelAndOtherReportHasHigherCoverage report.addChild(pkg); pkg.addChild(file); Node otherReport = report.copyTree(); - otherReport.find(FILE, file.getName()).get().addValue( + otherReport.find(FILE, file.getName()).orElseThrow().addValue( new CoverageBuilder().setMetric(LINE).setCovered(70).setMissed(30).build()); - report.find(FILE, file.getName()).get().addValue( + report.find(FILE, file.getName()).orElseThrow().addValue( new CoverageBuilder().setMetric(LINE).setCovered(80).setMissed(20).build()); Node combined = report.merge(otherReport); @@ -854,7 +854,7 @@ void shouldMergeNodesWithValues() { assertThat(merged) .hasName("package") .hasOnlyValueMetrics(LINE); - assertThat((Coverage) merged.getValue(LINE).get()) + assertThat((Coverage) merged.getValue(LINE).orElseThrow()) .hasCovered(10) .hasMissed(0); } @@ -862,13 +862,13 @@ void shouldMergeNodesWithValues() { @Test void shouldGetAllNodesOfTypeInTree() { Node tree = createTreeWithoutCoverage(); - FileNode coveredFile = tree.findFile("Covered.java").get(); - FileNode missedFile = tree.findFile("Missed.java").get(); + FileNode coveredFile = tree.findFile("Covered.java").orElseThrow(); + FileNode missedFile = tree.findFile("Missed.java").orElseThrow(); MethodNode coveredMethod = new MethodNode("coveredMethod", "signature"); MethodNode missedMethod = new MethodNode("missedMethod", "signature"); - tree.findClass("CoveredClass.class").get().addChild(coveredMethod); - tree.findClass("MissedClass.class").get().addChild(missedMethod); + tree.findClass("CoveredClass.class").orElseThrow().addChild(coveredMethod); + tree.findClass("MissedClass.class").orElseThrow().addChild(missedMethod); assertThat(tree.getAllMethodNodes()).containsExactlyInAnyOrder(coveredMethod, missedMethod); assertThat(tree.getAllFileNodes()).containsExactlyInAnyOrder(coveredFile, missedFile);