org.conqat.engine.sourcecode.coverage.LineCoverageInfo Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of teamscale-commons Show documentation
Show all versions of teamscale-commons Show documentation
Provides common DTOs for Teamscale
/*
* Copyright (c) CQSE GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.conqat.engine.sourcecode.coverage;
import org.conqat.lib.commons.test.IndexValueClass;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.IntStream;
import org.conqat.lib.commons.assertion.CCSMAssert;
import org.conqat.lib.commons.collections.CollectionUtils;
import org.conqat.lib.commons.region.LineBasedRegion;
import org.conqat.lib.commons.string.StringUtils;
import com.fasterxml.jackson.annotation.JsonAlias;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* Holds line coverage information for a file.
*/
@IndexValueClass(containedInBackup = true)
public class LineCoverageInfo implements Serializable {
/** Version for serialization. */
private static final long serialVersionUID = 1L;
/** The name of the JSON property name for {@link #timestamp}. */
private static final String TIMESTAMP_PROPERTY = "timestamp";
/** The name of the JSON property name for {@link #isMethodAccurate}. */
private static final String IS_METHOD_ACCURATE_PROPERTY = "isMethodAccurate";
/** The line numbers that were fully covered */
@JsonProperty("fullyCoveredLines")
@JsonAlias("fully-covered-lines")
private final Set fullyCoveredLines = new HashSet<>();
/** The line numbers that were partially covered */
@JsonProperty("partiallyCoveredLines")
@JsonAlias("partially-covered-lines")
private final Set partiallyCoveredLines = new HashSet<>();
/** The line numbers that were not covered */
@JsonProperty("uncoveredLines")
@JsonAlias("uncovered-lines")
private final Set uncoveredLines = new HashSet<>();
/**
* Determines the accuracy of this coverage info. If false
, the
* info is line-accurate, otherwise it is only method-accurate (i.e. executing
* any statement in the method will mark the entire method as executed). The
* default is line-accurate.
*
* Note that method-accurate coverage should not be used when e.g. calculating
* the line coverage metric.
*/
@JsonProperty(IS_METHOD_ACCURATE_PROPERTY)
private boolean isMethodAccurate;
/**
* @see #isMethodAccurate
*/
public boolean isMethodAccurate() {
return isMethodAccurate;
}
/**
* @see #isMethodAccurate
*/
public void setMethodAccurate(boolean isMethodAccurate) {
this.isMethodAccurate = isMethodAccurate;
}
/**
* The timestamp of the code this coverage data refers to. May be -1 if unknown.
*/
@JsonProperty(TIMESTAMP_PROPERTY)
private long timestamp;
/** Constructor with a default timestamp. */
public LineCoverageInfo(boolean isMethodAccurate) {
this(-1, isMethodAccurate);
}
/** Constructor */
@JsonCreator
public LineCoverageInfo(@JsonProperty(TIMESTAMP_PROPERTY) long timestamp,
@JsonProperty(IS_METHOD_ACCURATE_PROPERTY) boolean isMethodAccurate) {
this.timestamp = timestamp;
this.isMethodAccurate = isMethodAccurate;
}
/**
* Adds the coverage information for the given line. This merges the given
* coverage info if called multiple times for the same line. This is needed to
* allow for overlapping coverage reports.
*/
public void addLineCoverage(int line, ELineCoverage coverage) {
CCSMAssert.isNotNull(coverage);
ELineCoverage existingCoverage = getLineCoverage(line);
if (existingCoverage == null) {
setLineCoverage(line, coverage);
return;
}
switch (existingCoverage) {
case NOT_COVERED:
uncoveredLines.remove(line);
setLineCoverage(line, coverage);
break;
case PARTIALLY_COVERED:
if (coverage == ELineCoverage.FULLY_COVERED) {
partiallyCoveredLines.remove(line);
fullyCoveredLines.add(line);
}
break;
case FULLY_COVERED:
// cannot get any better
break;
default:
throw new IllegalStateException("Unknown line coverage: " + coverage);
}
}
/**
* Adds the coverage information for the given lines.
*
* @see #addLineCoverage(int, ELineCoverage)
*/
public void addLineCoverage(Collection lines, ELineCoverage coverage) {
for (Integer line : lines) {
addLineCoverage(line, coverage);
}
}
/** Adds coverage information from startLine to endLine */
public void addRangeCoverage(int startLine, int endLine, ELineCoverage coverage) {
for (int i = startLine; i <= endLine; ++i) {
addLineCoverage(i, coverage);
}
}
/**
* Adds the coverage information for the given lines.
*
* @see #addLineCoverage(int, ELineCoverage)
*/
public void addLineCoverage(IntStream lines, ELineCoverage coverage) {
lines.forEach(line -> addLineCoverage(line, coverage));
}
/** Removes all previously stored line coverage for the given line. */
public void removeLineCoverageInfo(int line) {
fullyCoveredLines.remove(line);
partiallyCoveredLines.remove(line);
uncoveredLines.remove(line);
}
/**
* Sets the line coverage for the given line. This ignores previously stored
* values.
*/
private void setLineCoverage(int line, ELineCoverage coverage) {
switch (coverage) {
case FULLY_COVERED:
fullyCoveredLines.add(line);
break;
case PARTIALLY_COVERED:
partiallyCoveredLines.add(line);
break;
case NOT_COVERED:
uncoveredLines.add(line);
break;
default:
throw new IllegalStateException("Unknown line coverage: " + coverage);
}
}
/** Adds all coverage information from another {@link LineCoverageInfo}. */
public void addAll(LineCoverageInfo coverageInfo) {
for (int line : coverageInfo.fullyCoveredLines) {
addLineCoverage(line, ELineCoverage.FULLY_COVERED);
}
for (int line : coverageInfo.partiallyCoveredLines) {
addLineCoverage(line, ELineCoverage.PARTIALLY_COVERED);
}
for (int line : coverageInfo.uncoveredLines) {
addLineCoverage(line, ELineCoverage.NOT_COVERED);
}
}
/**
* Returns the line coverage for the given line or null
if none is
* stored.
*/
public ELineCoverage getLineCoverage(int line) {
if (fullyCoveredLines.contains(line)) {
return ELineCoverage.FULLY_COVERED;
}
if (partiallyCoveredLines.contains(line)) {
return ELineCoverage.PARTIALLY_COVERED;
}
if (uncoveredLines.contains(line)) {
return ELineCoverage.NOT_COVERED;
}
return null;
}
/** Returns list of fully covered lines (sorted ascending) */
public List getFullyCoveredLines() {
return CollectionUtils.sort(fullyCoveredLines);
}
/** Returns list of partially covered lines (sorted ascending) */
public List getPartiallyCoveredLines() {
return CollectionUtils.sort(partiallyCoveredLines);
}
/** Returns list of uncovered lines (sorted ascending) */
public List getUncoveredLines() {
return CollectionUtils.sort(uncoveredLines);
}
/**
* Returns list of lines that are either fully or partially covered (sorted
* ascending)
*/
public List getCoveredLines() {
List coveredLines = new ArrayList<>();
coveredLines.addAll(partiallyCoveredLines);
coveredLines.addAll(fullyCoveredLines);
return CollectionUtils.sort(coveredLines);
}
/** Returns the line coverage ratio as a double ([0..1]). */
public double getCoverageRatio() {
int lines = getCoverableLines();
if (lines == 0) {
return 0;
}
return getCoveredLineCount() / lines;
}
/** Returns the number of lines that are covered or partially covered. */
public double getCoveredLineCount() {
return fullyCoveredLines.size() + partiallyCoveredLines.size();
}
/** Returns the number of lines that are coverable. */
public int getCoverableLines() {
return fullyCoveredLines.size() + partiallyCoveredLines.size() + uncoveredLines.size();
}
/** Returns the set of all lines in the coverage report. */
public Set getAllCoverableLines() {
return CollectionUtils.unionSet(fullyCoveredLines, partiallyCoveredLines, uncoveredLines);
}
/**
* Get the highest line number of any coverable line in this coverage info.
* Returns 0 if there are no coverable lines.
*/
public int getHighestLineNumber() {
Set allCoverableLines = getAllCoverableLines();
if (allCoverableLines.isEmpty()) {
return 0;
}
return Collections.max(allCoverableLines);
}
/**
* @see #timestamp
*/
public long getTimestamp() {
return timestamp;
}
/**
* @see #timestamp
*/
public void setTimestamp(long timestamp) {
this.timestamp = timestamp;
}
/** {@inheritDoc} */
@Override
public String toString() {
return String.valueOf(getCoverageRatio());
}
/** Returns a string representation of the covered/uncovered lines. */
public String toLineString() {
return "Fully covered: " + StringUtils.concat(CollectionUtils.sort(fullyCoveredLines), ",")
+ "; partially covered: " + StringUtils.concat(CollectionUtils.sort(partiallyCoveredLines), ",")
+ "; uncovered: " + StringUtils.concat(CollectionUtils.sort(uncoveredLines), ",") + "; timestamp: "
+ timestamp;
}
/**
* Replaces the coverable lines with the given lines. This also adjusts the
* {@link #fullyCoveredLines} and {@link #partiallyCoveredLines} by removing all
* lines that are not coverable.
*/
public void setCoverableLines(Set lines) {
fullyCoveredLines.retainAll(lines);
partiallyCoveredLines.retainAll(lines);
uncoveredLines.clear();
uncoveredLines.addAll(lines);
uncoveredLines.removeAll(fullyCoveredLines);
uncoveredLines.removeAll(partiallyCoveredLines);
}
/**
* Creates a copy of this object that is stable in regard to serialization, as
* it creates the {@link #fullyCoveredLines}, {@link #partiallyCoveredLines} and
* {@link #uncoveredLines} sets by adding the respective entries in a sorted
* manner.
*
* This is somewhat of a hack,as this relies on java sets always turning out the
* same, if the entries are inserted in the same order.
*/
public LineCoverageInfo createStableCopy() {
LineCoverageInfo copy = new LineCoverageInfo(isMethodAccurate);
copy.addLineCoverage(getFullyCoveredLines(), ELineCoverage.FULLY_COVERED);
copy.addLineCoverage(getPartiallyCoveredLines(), ELineCoverage.PARTIALLY_COVERED);
copy.addLineCoverage(getUncoveredLines(), ELineCoverage.NOT_COVERED);
return copy;
}
/**
* Extends coverage to full entities by using the best covered line for all
* lines of an entity.
*/
public void extendCoverageToStatements(List multiLineRegions) {
for (LineBasedRegion region : multiLineRegions) {
int startLine = region.getStart();
int endLine = region.getEnd();
if (IntStream.range(startLine, endLine + 1).anyMatch(fullyCoveredLines::contains)) {
IntStream.range(startLine, endLine + 1).forEach(fullyCoveredLines::add);
IntStream.range(startLine, endLine + 1).forEach(partiallyCoveredLines::remove);
IntStream.range(startLine, endLine + 1).forEach(uncoveredLines::remove);
} else if (IntStream.range(startLine, endLine + 1).anyMatch(partiallyCoveredLines::contains)) {
IntStream.range(startLine, endLine + 1).forEach(partiallyCoveredLines::add);
IntStream.range(startLine, endLine + 1).forEach(uncoveredLines::remove);
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy