blob: 8c94f6bb867639886ed025271a78fe54f9d1e1ef [file]
// Copyright 2016 The Bazel Authors. All Rights Reserved.
//
// 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 com.google.testing.coverage;
import static com.google.common.collect.ImmutableSortedSet.toImmutableSortedSet;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Maps;
import com.google.common.collect.Ordering;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.NoSuchElementException;
/** Coverage data from the analysis of either a class or a source file. */
public final class CoverageData {
private final ImmutableMap<Integer, BranchData> branches; // map of line-number to BranchData
private final BitField linesExecuted;
private final BitField linesInstrumented;
private final ImmutableMap<String, Integer> methodStartLines;
private final ImmutableMap<String, Boolean> methodExecuted;
private static final BranchData NULL_BRANCH_DATA = BranchData.builder().build();
private CoverageData(
ImmutableMap<Integer, BranchData> branches,
BitField linesExecuted,
BitField linesInstrumented,
ImmutableMap<String, Integer> methodStartLines,
ImmutableMap<String, Boolean> methodExecuted) {
this.branches = branches;
this.linesExecuted = linesExecuted;
this.linesInstrumented = linesInstrumented;
this.methodStartLines = methodStartLines;
this.methodExecuted = methodExecuted;
}
public static Builder builder() {
return new Builder();
}
/** Returns true if there is no coverage data. */
public boolean isEmpty() {
return branches.isEmpty()
&& linesInstrumented.isEmpty()
&& methodStartLines.isEmpty()
&& methodExecuted.isEmpty();
}
/** Returns true if the line has branches. */
public boolean hasBranches(Integer line) {
return branches.containsKey(line);
}
/** Gets BranchData for this line */
public BranchData getBranches(Integer line) {
if (!hasBranches(line)) {
return NULL_BRANCH_DATA;
}
return branches.get(line);
}
/** Returns line numbers where more than one branch is present. */
public ImmutableSortedSet<Integer> linesWithBranches() {
return branches.entrySet().stream()
// Omit branches that are not actually branches (unconditional jumps)
.filter(entry -> entry.getValue().size() > 1)
.map(Map.Entry::getKey)
.collect(toImmutableSortedSet(Ordering.natural()));
}
/** Returns the names of all methods in this coverage data. */
public ImmutableList<String> getMethods() {
return ImmutableList.copyOf(methodStartLines.keySet());
}
/** Returns the line number for the first line of the given method. */
public int getMethodLine(String method) {
return methodStartLines.get(method);
}
/** Returns if the given method was executed. */
public boolean isMethodExecuted(String method) {
return methodExecuted.get(method);
}
/** Returns if the given line was executed. */
public boolean isLineExecuted(int line) {
return linesExecuted.isBitSet(line);
}
/** Were any branches on this line executed. */
public boolean isBranchExecuted(int line) {
return branches.containsKey(line) && getBranches(line).anyBranchTaken();
}
/** Returns the line number for all instrumented lines. */
public ImmutableList<Integer> getInstrumentedLines() {
return ImmutableList.copyOf(new InstrumentedLinesIterator(linesInstrumented));
}
/** Iterator for instrumented lines. */
private static class InstrumentedLinesIterator implements Iterator<Integer> {
private final BitField linesInstrumented;
private int idx;
private final int maxIdx;
InstrumentedLinesIterator(BitField linesInstrumented) {
this.linesInstrumented = linesInstrumented;
maxIdx = linesInstrumented.sizeInBits();
// Move idx to point to the first populated bit.
idx = -1;
moveToNextPopulated();
}
@Override
public boolean hasNext() {
return idx < maxIdx;
}
@Override
public Integer next() {
// if idx < maxIdx then it points to a valid element
if (idx >= maxIdx) {
throw new NoSuchElementException();
}
int v = idx;
moveToNextPopulated();
return v;
}
private void moveToNextPopulated() {
do {
idx++;
} while (idx < maxIdx && !linesInstrumented.isBitSet(idx));
}
}
/** Builder for CoverageData. */
public static final class Builder {
private final Map<Integer, BranchData.Builder> branches = new HashMap<>();
private final BitField linesExecuted = new BitField();
private final BitField linesInstrumented = new BitField();
private final Map<String, Integer> methodStartLines = new LinkedHashMap<>();
private final Map<String, Boolean> methodExecuted = new LinkedHashMap<>();
/**
* Adds a method to the coverage data.
*
* <p>This overwrites any values for an existing method name.
*/
@CanIgnoreReturnValue
public Builder addMethod(String method, int line, boolean executed) {
methodStartLines.put(method, line);
methodExecuted.put(method, executed);
return this;
}
/**
* Adds a line to the coverage data.
*
* <p>This overwrites any values for an existing line.
*/
@CanIgnoreReturnValue
public Builder addLine(int line, boolean executed) {
linesInstrumented.setBit(line);
if (executed) {
linesExecuted.setBit(line);
}
return this;
}
/**
* Adds a branch to the coverage data for the given line.
*
* <p>Branches for a line are stored in the order they were added by this method.
*/
@CanIgnoreReturnValue
public Builder addBranch(int line, boolean taken) {
BranchData.Builder branchBuilder =
branches.computeIfAbsent(line, unused -> BranchData.builder());
branchBuilder.addBranch(taken);
return this;
}
/** Returns true if no coverage data has been added. */
public boolean isEmpty() {
return branches.isEmpty()
&& linesInstrumented.isEmpty()
&& methodStartLines.isEmpty()
&& methodExecuted.isEmpty();
}
/** Returns a new CoverageData from this builder. */
public CoverageData build() {
return new CoverageData(
ImmutableMap.copyOf(Maps.transformValues(branches, BranchData.Builder::build)),
linesExecuted,
linesInstrumented,
ImmutableMap.copyOf(methodStartLines),
ImmutableMap.copyOf(methodExecuted));
}
}
/** Data for a set of branches on a single line. */
public static final class BranchData {
private final BitField branchBits;
private final int count;
private BranchData(BitField branchBits, int count) {
this.branchBits = branchBits;
this.count = count;
}
/** Creates a new Builder for BranchData. */
public static Builder builder() {
return new Builder();
}
/** Returns true if the branch at the given index was taken. */
public boolean isBranchTaken(int idx) {
if (idx >= count) {
throw new IndexOutOfBoundsException(
String.format("Branch index %d is out of bounds [0, %d)", idx, count));
}
return branchBits.isBitSet(idx);
}
/** Returns true if any branch was taken. */
public boolean anyBranchTaken() {
return branchBits.any();
}
/** Returns the coverage for each branch on this line. */
public ImmutableList<Boolean> getBranchValues() {
ImmutableList.Builder<Boolean> result = ImmutableList.builder();
for (int i = 0; i < count; i++) {
result.add(branchBits.isBitSet(i));
}
return result.build();
}
/** Returns the number of branches on this line. */
public int size() {
return count;
}
/** Returns true if there are no branches on this line. */
public boolean isEmpty() {
return count == 0;
}
/** Builder for BranchData. */
public static class Builder {
private final BitField branchBits;
private int count;
public Builder() {
branchBits = new BitField();
}
/**
* Adds a branch to the BranchData.
*
* <p>Branches are stored in the order they were added by this method.
*/
@CanIgnoreReturnValue
public Builder addBranch(boolean taken) {
if (taken) {
branchBits.setBit(count);
}
count++;
return this;
}
/** Returns a new BranchData from this builder. */
public BranchData build() {
return new BranchData(branchBits, count);
}
}
}
}