Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented LineRange-class and improved overall code-coverage #30

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/main/java/edu/hm/hafner/coverage/FileNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,16 @@ public NavigableSet<Integer> 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<LineRange> 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.
Expand Down
123 changes: 123 additions & 0 deletions src/main/java/edu/hm/hafner/coverage/LineRange.java
Original file line number Diff line number Diff line change
@@ -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<LineRange> {

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<LineRange> getRangesFromSortedLines(final SortedSet<Integer> lines) {
NavigableSet<LineRange> 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<LineRange> getRangesFromUnsortedLines(final Set<Integer> 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("<LineRange [%s-%s]>", start, end);
}
}
27 changes: 27 additions & 0 deletions src/test/java/edu/hm/hafner/coverage/CoverageParserTest.java
Original file line number Diff line number Diff line change
@@ -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 {

Check notice

Code scanning / CodeQL

Unused classes and interfaces Note test

Unused class: CoverageParserTest is not referenced within this codebase. If not used as an external API it should be removed.

@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);
}
}
130 changes: 127 additions & 3 deletions src/test/java/edu/hm/hafner/coverage/CoverageTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.");
}
}
Loading
Loading