All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.conqat.engine.sourcecode.coverage.LineCoverageInfo Maven / Gradle / Ivy

/*
 * 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 java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.stream.IntStream;

import org.conqat.lib.commons.assertion.CCSMAssert;
import org.conqat.lib.commons.collections.CompactLines;
import org.conqat.lib.commons.region.LineBasedRegion;
import org.conqat.lib.commons.test.IndexValueClass;

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 CompactLines fullyCoveredLines = new CompactLines();

	/** The line numbers that were partially covered */
	@JsonProperty("partiallyCoveredLines")
	@JsonAlias("partially-covered-lines")
	private final CompactLines partiallyCoveredLines = new CompactLines();

	/** The line numbers that were not covered */
	@JsonProperty("uncoveredLines")
	@JsonAlias("uncovered-lines")
	private final CompactLines uncoveredLines = new CompactLines();

	/**
	 * 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; /** * The timestamp of the code this coverage data refers to. May be -1 if unknown. */ @JsonProperty(TIMESTAMP_PROPERTY) private long timestamp; public LineCoverageInfo(LineCoverageInfo lineCoverage) { timestamp = lineCoverage.timestamp; isMethodAccurate = lineCoverage.isMethodAccurate; fullyCoveredLines.addAll(lineCoverage.fullyCoveredLines); partiallyCoveredLines.addAll(lineCoverage.partiallyCoveredLines); uncoveredLines.addAll(lineCoverage.uncoveredLines); } /** 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; } /** @see #isMethodAccurate */ public boolean isMethodAccurate() { return isMethodAccurate; } /** @see #isMethodAccurate */ public void setMethodAccurate(boolean isMethodAccurate) { 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); if (fullyCoveredLines.contains(line)) { // cannot get any better } else if (partiallyCoveredLines.contains(line)) { if (coverage == ELineCoverage.FULLY_COVERED) { partiallyCoveredLines.remove(line); fullyCoveredLines.add(line); } } else { uncoveredLines.remove(line); setLineCoverage(line, coverage); } } /** * Adds the coverage information for the given lines. * * @see #addLineCoverage(CompactLines, ELineCoverage) */ public void addLineCoverage(Collection lines, ELineCoverage coverage) { addLineCoverage(new CompactLines(lines), coverage); } /** * Adds the coverage information for the given lines. This preserves the "highest" coverage value * i.e., adding {@link ELineCoverage#NOT_COVERED} or {@link ELineCoverage#PARTIALLY_COVERED} to a * line that is already {@link ELineCoverage#FULLY_COVERED} this is a NOP. */ public void addLineCoverage(CompactLines lines, ELineCoverage coverage) { if (Objects.requireNonNull(coverage) == ELineCoverage.FULLY_COVERED) { fullyCoveredLines.addAll(lines); partiallyCoveredLines.removeAll(lines); uncoveredLines.removeAll(lines); } else if (coverage == ELineCoverage.PARTIALLY_COVERED) { partiallyCoveredLines.addAll(lines); partiallyCoveredLines.removeAll(fullyCoveredLines); uncoveredLines.removeAll(lines); } else { uncoveredLines.addAll(lines); uncoveredLines.removeAll(fullyCoveredLines); uncoveredLines.removeAll(partiallyCoveredLines); } } /** 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) { addLineCoverage(coverageInfo.fullyCoveredLines, ELineCoverage.FULLY_COVERED); addLineCoverage(coverageInfo.partiallyCoveredLines, ELineCoverage.PARTIALLY_COVERED); addLineCoverage(coverageInfo.uncoveredLines, 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 CompactLines getFullyCoveredLines() { return fullyCoveredLines; } /** Returns list of partially covered lines (sorted ascending) */ public CompactLines getPartiallyCoveredLines() { return partiallyCoveredLines; } /** Returns list of uncovered lines (sorted ascending) */ public CompactLines getUncoveredLines() { return uncoveredLines; } /** * Returns list of lines that are either fully or partially covered (sorted ascending) */ public CompactLines getCoveredLines() { CompactLines coveredLines = new CompactLines(); coveredLines.addAll(partiallyCoveredLines); coveredLines.addAll(fullyCoveredLines); return coveredLines; } /** Returns the line coverage ratio as a double ([0..1]). */ private 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 CompactLines getAllCoverableLines() { CompactLines coverableLines = new CompactLines(); coverableLines.addAll(fullyCoveredLines); coverableLines.addAll(partiallyCoveredLines); coverableLines.addAll(uncoveredLines); return coverableLines; } /** * Get the highest line number of any coverable line in this coverage info. Returns 0 if there are * no coverable lines. */ public int getHighestLineNumber() { return getAllCoverableLines().getHighestLineNumber().orElse(0); } /** * @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: " + fullyCoveredLines + "; partially covered: " + partiallyCoveredLines + "; uncovered: " + uncoveredLines + "; timestamp: " + timestamp; } /** * Clears {@link #uncoveredLines}, {@link #partiallyCoveredLines} and {@link #fullyCoveredLines}. */ public void clear() { fullyCoveredLines.clear(); partiallyCoveredLines.clear(); uncoveredLines.clear(); } /** * 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(CompactLines 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 by omitting the timestamp * value from the copy. */ public LineCoverageInfo copyWithoutTimestamp() { LineCoverageInfo copy = new LineCoverageInfo(this); copy.setTimestamp(-1); 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); } } } /** * Returns the estimated size of the object in bytes. This is used to determine how much we can keep * in the in-memory cache at once. */ public long getEstimatedSizeBytes() { // We estimate 32bytes per HashNode including the Integer and 60bytes for the // HashSets and timestamp in LineCoverageInfo return 60L + (fullyCoveredLines.size() + partiallyCoveredLines.size() + uncoveredLines.size()) * 32L; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy