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

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

The newest version!
package org.conqat.engine.sourcecode.coverage;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.stream.Collectors;

import org.conqat.lib.commons.collections.CompactLines;
import org.conqat.lib.commons.test.IndexValueClass;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.teamscale.commons.utils.StringPool;

/**
 * Holds line range coverage information for a test covering multiple files.
 */
@IndexValueClass(containedInBackup = true)
public class MultiFileRangeCoverageInfo implements Serializable {

	private static final long serialVersionUID = 1L;

	/**
	 * Mapping from normalized uniform path to a list of line numbers. The list may contain duplicates
	 * and may be unsorted.
	 */
	@JsonProperty("uniformPathToRanges")
	private Map uniformPathToRanges;

	/**
	 * The timestamp of the code this coverage data refers to. May be -1 if unknown.
	 */
	@JsonProperty("timestamp")
	private long timestamp;

	/**
	 * Coverable lines as provided by an external coverage tool. In case none provided,
	 * Teamscale-internal heuristic is used.
	 */
	@JsonProperty("coverableLines")
	private final Map uniformPathToCoverableLineRanges;

	/** Constructor with a default timestamp. */
	@JsonCreator
	public MultiFileRangeCoverageInfo() {
		this(-1);
	}

	public MultiFileRangeCoverageInfo(long timestamp) {
		this.timestamp = timestamp;
		uniformPathToRanges = new HashMap<>();
		uniformPathToCoverableLineRanges = new HashMap<>();
	}

	/**
	 * Merges all coverage contained in newCoverage into this coverage. Coverage from the same test case
	 * replaces existing coverage.
	 */
	public void addLineCoverage(MultiFileRangeCoverageInfo newCoverage) {
		Set> newTestCoverage = newCoverage.uniformPathToRanges.entrySet();
		for (Entry testCoverage : newTestCoverage) {
			uniformPathToRanges.put(testCoverage.getKey(), testCoverage.getValue());
		}
	}

	/** Adds the coverage information for the given lines. */
	public void addLineCoverage(String uniformPath, CompactLines lines) {
		uniformPathToRanges.computeIfAbsent(uniformPath, k -> new CompactLines()).addAll(lines);
	}

	/** Adds the coverable lines for the given uniform path. */
	public void addCoverableLines(String uniformPath, CompactLines lines) {
		uniformPathToCoverableLineRanges.computeIfAbsent(uniformPath, k -> new CompactLines()).addAll(lines);
	}

	/** @see #timestamp */
	public long getTimestamp() {
		return timestamp;
	}

	/** @see #timestamp */
	public void setTimestamp(long timestamp) {
		this.timestamp = timestamp;
	}

	/** Returns the coverage represented by this object in raw form. */
	public Map getCoverage() {
		return uniformPathToRanges;
	}

	/** Returns the coverage lines represented by this object in raw form. */
	public Map getCoverableLines() {
		if (uniformPathToCoverableLineRanges == null) {
			// might be null when the object is deserialized from the Teamscale backup
			return new HashMap<>();
		}
		return uniformPathToCoverableLineRanges;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public String toString() {
		return "Covered: "
				+ uniformPathToRanges.entrySet().stream().sorted(Entry.comparingByKey())
						.map(pair -> pair.getKey() + " " + pair.getValue()).collect(Collectors.joining(", "))
				+ "; timestamp: " + timestamp;
	}

	/**
	 * If the test contains coverage from the given file, the uniform path is adjusted to match the path
	 * used inside Teamscale.
	 */
	public void changeFilePath(String reportPath, String pathInTeamscale) {
		if (uniformPathToRanges.containsKey(reportPath) && !reportPath.equals(pathInTeamscale)) {
			CompactLines ranges = uniformPathToRanges.remove(reportPath);
			uniformPathToRanges.put(pathInTeamscale, ranges);
		}
	}

	/**
	 * We use a custom de-serialization here so that we can intern the paths directly after they have
	 * been read and therefore avoid high peaks in RAM usage because the same paths end up multiple
	 * times in memory otherwise, because readUTF does not use Java's internal string deduplication.
	 */

	private void readObject(ObjectInputStream inputStream) throws ClassNotFoundException, IOException {
		timestamp = inputStream.readLong();
		uniformPathToRanges = new HashMap<>();
		int entries = inputStream.readInt();
		for (int i = 0; i < entries; i++) {
			String path = StringPool.intern(inputStream.readUTF());
			CompactLines list = (CompactLines) inputStream.readObject();
			uniformPathToRanges.put(path, list);
		}
	}

	private void writeObject(ObjectOutputStream outputStream) throws IOException {
		outputStream.writeLong(timestamp);
		outputStream.writeInt(uniformPathToRanges.size());
		for (Entry entry : uniformPathToRanges.entrySet()) {
			outputStream.writeUTF(entry.getKey());
			outputStream.writeObject(entry.getValue());
		}
	}

	/**
	 * 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() {
		return uniformPathToRanges.entrySet().stream()
				.mapToLong(entry -> entry.getKey().length() + entry.getValue().size() * 4L).sum();
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy