Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,23 @@ java_library(
],
)

java_library(
name = "McdcCoverage",
srcs = ["McdcCoverage.java"],
deps = [
"//third_party:auto_value",
"//third_party:guava",
],
)

java_library(
name = "McdcCoverageData",
srcs = ["McdcCoverageData.java"],
deps = [
":McdcCoverage",
],
)

java_library(
name = "SourceFileCoverage",
srcs = ["SourceFileCoverage.java"],
Expand All @@ -97,6 +114,8 @@ java_library(
":BranchCoverageItem",
":BranchCoverageKey",
":LineCoverage",
":McdcCoverage",
":McdcCoverageData",
"//third_party:guava",
],
)
Expand All @@ -108,6 +127,7 @@ java_library(
":BranchCoverageItem",
":Constants",
":Coverage",
":McdcCoverage",
":SourceFileCoverage",
"//third_party:guava",
],
Expand Down Expand Up @@ -156,6 +176,7 @@ java_library(
deps = [
":BranchCoverageItem",
":Constants",
":McdcCoverage",
":SourceFileCoverage",
],
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ class Constants {
static final String DA_MARKER = "DA:";
static final String LH_MARKER = "LH:";
static final String LF_MARKER = "LF:";
static final String MCDC_MARKER = "MCDC:";
static final String MCF_MARKER = "MCF:";
static final String MCH_MARKER = "MCH:";
static final String END_OF_RECORD_MARKER = "end_of_record";
static final String NEVER_EVALUATED = "-";
static final String TRACEFILE_EXTENSION = ".dat";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@
import static com.google.devtools.coverageoutputgenerator.Constants.FN_MARKER;
import static com.google.devtools.coverageoutputgenerator.Constants.LF_MARKER;
import static com.google.devtools.coverageoutputgenerator.Constants.LH_MARKER;
import static com.google.devtools.coverageoutputgenerator.Constants.MCDC_MARKER;
import static com.google.devtools.coverageoutputgenerator.Constants.MCF_MARKER;
import static com.google.devtools.coverageoutputgenerator.Constants.MCH_MARKER;
import static com.google.devtools.coverageoutputgenerator.Constants.NEVER_EVALUATED;
import static com.google.devtools.coverageoutputgenerator.Constants.SF_MARKER;
import static java.nio.charset.StandardCharsets.UTF_8;
Expand Down Expand Up @@ -139,6 +142,15 @@ private boolean parseLine(String line, List<SourceFileCoverage> allSourceFiles)
reset(allSourceFiles);
return true;
}
if (line.startsWith(MCDC_MARKER)) {
return parseMcdcLine(line);
}
if (line.startsWith(MCF_MARKER)) {
return parseMcfLine(line);
}
if (line.startsWith(MCH_MARKER)) {
return parseMchLine(line);
}
logger.log(Level.WARNING, "Tracefile includes invalid line: " + line);
return false;
}
Expand Down Expand Up @@ -424,4 +436,71 @@ private boolean parseLFLine(String line) {
}
return true;
}

// MCDC:<line_number>,<group_size>,<sense>,<taken>,<index>,<expression>
private boolean parseMcdcLine(String line) {
String lineContent = line.substring(MCDC_MARKER.length());
if (lineContent.isEmpty()) {
logger.log(Level.WARNING, "Tracefile contains invalid MCDC line " + line);
return false;
}

String[] tokens = lineContent.split(DELIMITER, 6);
if (tokens.length < 5) {
logger.log(Level.WARNING, "Tracefile contains invalid MCDC line " + line);
return false;
}

try {
int lineNumber = Integer.parseInt(tokens[0]);
int groupSize = Integer.parseInt(tokens[1]);
char sense = tokens[2].length() > 0 ? tokens[2].charAt(0) : 't';
long taken = NEVER_EVALUATED.equals(tokens[3]) ? 0 : Long.parseLong(tokens[3]);
int index = Integer.parseInt(tokens[4]);
String expression = tokens.length > 5 ? tokens[5] : "";

McdcCoverage record = McdcCoverage.create(lineNumber, groupSize, sense, taken, index, expression);
currentSourceFileCoverage.addMcdc(lineNumber, record);
} catch (NumberFormatException e) {
logger.log(Level.WARNING, "Tracefile contains invalid MCDC line " + line);
return false;
}
return true;
}

// MCF:<conditions found>
private boolean parseMcfLine(String line) {
String lineContent = line.substring(MCF_MARKER.length());
if (lineContent.isEmpty()) {
logger.log(Level.WARNING, "Tracefile contains invalid MCF line " + line);
return false;
}
try {
int conditionsFound = Integer.parseInt(lineContent);
// We compute this from the records, so just log for verification
logger.log(Level.FINE, "MCF line indicates " + conditionsFound + " conditions found");
} catch (NumberFormatException e) {
logger.log(Level.WARNING, "Tracefile contains an invalid number on MCF line " + line);
return false;
}
return true;
}

// MCH:<conditions hit>
private boolean parseMchLine(String line) {
String lineContent = line.substring(MCH_MARKER.length());
if (lineContent.isEmpty()) {
logger.log(Level.WARNING, "Tracefile contains invalid MCH line " + line);
return false;
}
try {
int conditionsHit = Integer.parseInt(lineContent);
// We compute this from the records, so just log for verification
logger.log(Level.FINE, "MCH line indicates " + conditionsHit + " conditions hit");
} catch (NumberFormatException e) {
logger.log(Level.WARNING, "Tracefile contains an invalid number on MCH line " + line);
return false;
}
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ void print(SourceFileCoverage sourceFile) throws IOException {
}
printBRFLine(sourceFile);
printBRHLine(sourceFile);
printMcdcLines(sourceFile);
printMcdcSummary(sourceFile);
printDALines(sourceFile);
printLHLine(sourceFile);
printLFLine(sourceFile);
Expand Down Expand Up @@ -216,4 +218,38 @@ private void printEndOfRecordLine() throws IOException {
bufferedWriter.write(Constants.END_OF_RECORD_MARKER);
bufferedWriter.newLine();
}

// MCDC:<line>,<groupSize>,<sense>,<taken>,<index>,<expression>
private void printMcdcLines(SourceFileCoverage sourceFile) throws IOException {
for (McdcCoverage record : sourceFile.getAllMcdc()) {
bufferedWriter.write(Constants.MCDC_MARKER);
bufferedWriter.write(Integer.toString(record.lineNumber()));
bufferedWriter.write(Constants.DELIMITER);
bufferedWriter.write(Integer.toString(record.groupSize()));
bufferedWriter.write(Constants.DELIMITER);
bufferedWriter.write(record.sense());
bufferedWriter.write(Constants.DELIMITER);
bufferedWriter.write(Long.toString(record.taken()));
bufferedWriter.write(Constants.DELIMITER);
bufferedWriter.write(Integer.toString(record.index()));
bufferedWriter.write(Constants.DELIMITER);
bufferedWriter.write(record.expression());
bufferedWriter.newLine();
}
}

// MCF:<conditions found> and MCH:<conditions hit>
private void printMcdcSummary(SourceFileCoverage sourceFile) throws IOException {
if (sourceFile.nrMcdcFound() > 0) {
// MCF:<conditions found>
bufferedWriter.write(Constants.MCF_MARKER);
bufferedWriter.write(Integer.toString(sourceFile.nrMcdcFound()));
bufferedWriter.newLine();

// MCH:<conditions hit>
bufferedWriter.write(Constants.MCH_MARKER);
bufferedWriter.write(Integer.toString(sourceFile.nrMcdcHit()));
bufferedWriter.newLine();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// Copyright 2018 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.devtools.coverageoutputgenerator;

import static com.google.common.base.Verify.verify;

import com.google.auto.value.AutoValue;

/**
* Stores MC/DC (Modified Condition/Decision Coverage) information.
*
* <p>Corresponds to MCDC lines in an lcov report with the form:
*
* <pre>MCDC:[line_number],[group_size],[sense],[taken],[index],[expression]</pre>
*
* where:
* <ul>
* <li>line_number = the line number where the condition appears
* <li>group_size = the total number of conditions in this MC/DC group
* <li>sense = 't' or 'f' indicating whether this condition sensitizes for true or false
* <li>taken = number of times this condition was sensitized (0 if never hit, "-" parsed as 0)
* <li>index = unique index within the MC/DC group for this condition
* <li>expression = textual representation of the Boolean expression (optional)
* </ul>
*/
@AutoValue
abstract class McdcCoverage {

/**
* Creates an instance of McdcCoverage.
*
* @param lineNumber The line number where the condition appears
* @param groupSize The total number of conditions in this MC/DC group
* @param sense The sense character ('t' or 'f') for this condition
* @param taken Number of times this condition was sensitized
* @param index Unique index within the MC/DC group
* @param expression Textual representation of the Boolean expression
*/
static McdcCoverage create(
int lineNumber,
int groupSize,
char sense,
long taken,
int index,
String expression) {
return new AutoValue_McdcCoverage(
lineNumber, groupSize, sense, taken, index, expression);
}

/**
* Checks if two MC/DC records have the same identifying characteristics (can be merged).
*
* <p>Two MC/DC records can be merged if they have the same lineNumber, groupSize, sense,
* index, and expression.
*
* @param first The first MC/DC record
* @param second The second MC/DC record
* @return true if the records can be merged, false otherwise
*/
static boolean canMerge(McdcCoverage first, McdcCoverage second) {
return first.lineNumber() == second.lineNumber()
&& first.groupSize() == second.groupSize()
&& first.sense() == second.sense()
&& first.index() == second.index()
&& first.expression().equals(second.expression());
}

/**
* Merges two given instances of {@link McdcCoverage}.
*
* <p>Calling {@code lineNumber()}, {@code groupSize()}, {@code sense()}, {@code index()},
* and {@code expression()} must return the same values for {@code first} and {@code second}.
*/
static McdcCoverage merge(McdcCoverage first, McdcCoverage second) {
verify(McdcCoverage.canMerge(first, second), "MC/DC records must match");

return create(
first.lineNumber(),
first.groupSize(),
first.sense(),
first.taken() + second.taken(),
first.index(),
first.expression());
}

abstract int lineNumber();

abstract int groupSize();

abstract char sense();

abstract long taken();

abstract int index();

abstract String expression();

boolean wasHit() {
return taken() > 0;
}
}
Loading