Key metrics for code coverage include:
Condition coverage offers a more fine-grained evaluation of branchcoverage. It requires that each individual boolean subexpression(condition) within a compound expression be evaluated to both true andfalse. For example, in the boolean expressionif (a>0 && f(b) && c==0)
, each ofa>0
, f(b)
, and c==0
, conditioncoverage would require tests that:
a>0
to true and falsef(b)
to true and falsec==0
to true and falseA condition combination refers to a specific set of boolean valuesassigned to individual conditions within a boolean expression. Multiplecondition coverage ensures that all possible condition combinations aretested. In the example above, with three conditions, there would be 2³ =8 possible condition combinations.
While multiple condition coverage may not be practical, Modifiedcondition/decision coverage (MC/DC) offers a more cost-effectiveapproach. Introduced in 1992 by DO-178B (latersuperseded by DO-178C), MC/DC became mandatory for Level A software inthe aviation industry. Its popularity has since extended tosafety-critical applications in automotive (ISO 26262) and otherdomains. Notably, SQLite boasts 100% MC/DC coverage link to SQLitetesting page: https://sqlite.org/testing.html#mcdc.
Consider a boolean expression like(A && B) || (C && D)
. This has fourconditions (A, B, C, and D), each potentially a subexpression likex>0
. Tests evaluate condition combinations (ABCD) andtheir corresponding outcomes.
Multiple flavors of MC/DC exist, with Unique-Cause MC/DC representingthe strongest variant. When demonstrating the independence of A in theboolean expression (A && B) || (C && D)
,Unique-Cause MC/DC requires two tests with different outcomes and:
The two tests form an independence pair for A. Acoverage set comprises tests offering such independence pairsfor each condition. However, achieving this set may be impossible in thepresence of strongly coupled conditions.
Coupling examples:
x==0 && x!=0
arestrongly coupled: changing one automatically changes theother.x==0 || x==1 || x==3
exhibits weakly coupledconditions: changing x from 0 to 2 alters only the first condition,while changing it to 1 affects the first two.Masking involves setting one operand of a boolean operatorto a value that renders the other operand's influence on the outcomeirrelevant. Examples:
&&
withA && false
(outcome is always false, unaffected byA).&&
withfalse && B
(outcome is always false, unaffected byB).||
with A || true
(outcome is always true, unaffected by A).||
with true || B
(outcome is always true, unaffected by B).Masking MC/DC demonstrates condition independence by showingthe condition in question affects the outcome and keeping otherconditions masked. For example, to provde the independence of A in theboolean expression (A && B) || (C && D)
, Cand D can change values as long as C && D
remainsfalse. In this way, each condition allows more independence pairs thanUnique-Cause MC/DC.
In 2001, masking MC/DC has been considered an acceptable method formeeting objective 5 of Table A-7 in DO-178B.
Unique-Cause + Masking MC/DC is weaker than Unique-Cause MC/DC butstronger than Masking MC/DC, allowing masking only for strongly coupledconditions.
If an expression has N unique conditions, both Unique-Cause MC/DC andUnique-Cause Masking MC/DC require a minimum of N+1 tests. It is notclear whether this is an exact bound. WhenN<=4, it is always possible to get Unique-Cause MC/DC with N+1tests. Masking MC/DC requires a minimum of ceil(2*sqrt(N))
.See An Investigation of Three Forms of the Modified ConditionDecision Coverage (MCDC) Criterion for detail.
Binary decision diagram (BDD) is a data structure that is used torepresent a boolean function. Boolean expressions with&&
and ||
compile to reduced orderedBDDs.
There is another coverage metric called object branch coverage, whichdetermines whether each branch is taken at least once and is also nottaken at least once. Object branch coverage does not guarantee MC/DC,but does when the reduced ordered BDD is a tree.
(B && C) || A
is a non-tree example thatachieving object branch coverage requires 3 tests, which areinsufficient to guarantee MC/DC. If the expression is rewritten toA || (B && C)
, then the reduced ordered BDD willbecome a tree, making object branch coverage guarantee MC/DC.
Efficient Test Coverage Measurement for MC/DC describes acode instrumentation technique to determine masking MC/DC. For a booleanexpression with N conditions, only 2*N bits are needed.
Jørgen Kvalsvik posted the first MC/DC patch to gcov in March 2022and PATCHv9 in December 2023. With this patch, we compile source files usinggcc --coverage -fcondition-coverage
and pass--conditions
to gcov. The output should look like:1
2
3
4
5
6
7
8
9
10
11 3: 17:void fn (int a, int b, int c, int d) {
3: 18: if ((a && (b || c)) && d)
conditions covered 3/8
condition 0 not covered (true false)
condition 1 not covered (true)
condition 2 not covered (true)
condition 3 not covered (true)
1: 19: x = 1;
-: 20: else
2: 21: x = 2;
3: 22:}
Since GCC 3.4, GCC has employed .gcno
and.gcda
files to store control-flow graph information and arcexecution counts, respectively. This format has undergone enhancementsbut remains structurally consistent. .gcno
files containfunction records describeing basic blocks, arcs between them, and lineswithin each basic block. Column information is only available forfunctions. .gcda
files store arc execution counts.
gcov identifies basic blocks on a particular line (usually one) andlocates successor basic blocks to infer branches. When -b
is specified, gcov prints branch probabilities, though the output may beunclear since .gcno
does not encode what true and falsebranches are.
1 | cat > a.c <<e |
The output 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 -: 0:Source:a.c
-: 0:Graph:a.gcno
-: 0:Data:a.gcda
-: 0:Runs:1
function test called 3 returned 100% blocks executed 100%
3: 1:int test(int a, int b) {
3: 2: if (a > 0 && b > 0)
branch 0 taken 67% (fallthrough)
branch 1 taken 33%
branch 2 taken 50% (fallthrough)
branch 3 taken 50%
1: 3: return 1;
2: 4: return 0;
-: 5:}
-: 6:
function main called 1 returned 100% blocks executed 100%
1: 7:int main() {
1: 8: test(0, 1);
call 0 returned 100%
1: 9: test(1, 0);
call 0 returned 100%
1: 10: test(1, 1);
call 0 returned 100%
-: 11:}
Clang offers a sophisticated approach to code coverage called Source-basedCode Coverage. Unlike gcov's line-oriented method, Clang utilizescoverage mapping, a format capable of encoding:
ExpansionRegion
)In January 2021, the framework has been enhanced with branchcoverage. This addition:
&&
and ||
operators.The primary data structure changes are the additions of the secondcounter (CountedRegion::FalseExecutionCount
andCounterMappingRegion::FalseCount
) and a newCounterMappingRegion::RegionKind
namedBranchRegion
.
1 | x = x > 0 && y > 0; // 2 extra counters |
The presentation "Branch Coverage: Squeezing more out of LLVMSource-based Code Coverage, 2020" elaborates on the design.
1 | clang -fprofile-instr-generate -fcoverage-mapping a.c -o a |
1 | 1| 3|int test(int a, int b) { |
A Rust feature request was since then filed:https://github.com/rust-lang/rust/issues/79649
In December 2023, Clang's Source-based Code Coverage got maskingMC/DC capability.
-fcoverage-mcdc
tells Clang to instrument&&
and ||
expressions to record thecondition combinations and outcomes, and store the reduced ordered BDDsinto the coverage mapping section. The bitmap is stored in the__llvm_prf_bits
section in a rawprofile.llvm-profdata merge
merges bitmaps from multiple rawprofiles and stores the merged bitmap into an indexed profile.--show-mcdc
to llvm-cov show
,llvm-cov
reads a profdata file, retrieves the bitmap,computes independence pairs, and print the information.Clang adopts a distinct approach to Masking MC/DC compared to thepaper "Efficient Test Coverage Measurement for MC/DC". Insteadof complex masking value computation, it uses a "boring algorithm":
[0, 2**N)
.For example, 1
2if (a && b || c)
return 1;
Let's say in one execution path a=c=1
andb=0
. the condition combination (0b101) leads to an index of5. The instrumentation locates the relevant word in the bitmap and setthe bit 5.
The approach is described in detail in "MC/DC: Enablingeasy-to-use safety-critical code coverage analysis with LLVM" in2022 LLVM Developers' Meeting.
While this method requires more memory (2**N
vs.2*N
), it simplifies instrumentation with just one bitwiseOR instruction for each condition, instead of possibly two (bitwise ANDplus bitwise OR). Determining independent pairs involves a brute-forcealgorithm in llvm-cov, which has a high time complexity but acceptabledue to the limited condition count.