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

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

There is a newer version: 2025.1.0-rc2
Show newest version
/*
 * 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