1.8. Coverage Exemptions
In some circumstances, there are good and well understood reasons why proper coverage of some source construct is not achievable. The GNATcoverage exemptions facility was designed to allow abstracting these coverage violations away from the genuine defects of a testing campaign.
1.8.1. Defining Exemption Regions
Exemption regions are lexical sections of sources in which coverage violations are expected and can be justified.
For Ada with the GNAT compilers, regions are defined by the insertion of dedicated pragmas in the sources:
pragma Annotate (Xcov, Exempt_On, "justification text");starts a region, providing some justification text that will be recalled in coverage reports.pragma Annotate (Xcov, Exempt_Off);closes the current exemption region.
There may be no overlap between exemption regions.
To illustrate, let us consider a common assertion control procedure in Ada:
procedure Eassert (T : Boolean) is
begin
pragma Annotate (Xcov, Exempt_On, "assert condition never to be False");
if not T then
raise Program_Error;
end if;
pragma Annotate (Xcov, Exempt_Off);
end Eassert;
We expect never to reach here with T False, so we declare an exemption
region to state that coverage violations across this region are expected and
not a testing campaign deficiency.
For C code, exemptions are only supported with source traces and are defined using comment markers to delimit the exempted regions:
Comments matching
GNATCOV_EXEMPT_ONorGNATCOV_EXEMPT_ON("my justification")start a region, the string within the double quotes being used as justification text that will be recalled in coverage reports.Comments matching
GNATCOV_EXEMPT_OFFclose the current region.
The following assert function illustrates the definition of an exemption block:
void
assert (bool x){
// GNATCOV_EXEMPT_ON("assert condition never to be False")
if (!x)
abort();
// GNATCOV_EXEMPT_OFF
}
As in the first example, we never expect to reach this function with x false, so an exemption region is declared to state that all coverage violations within the region are expected.
An exemption comment marker may not intersect any coverage obligation, such as a statement or a decision, as in the following example attempting to exempt a decision only partially:
if(a && /*GNATCOV_EXEMPT_ON("justification")*/ b /*GNATCOV_EXEMPT_OFF*/){
...
}
Such markers are ignored by gnatcov instrument, after emitting a warning.
It is also possible to define exemption regions trough external annotation files, see External source annotations for more information.
1.8.2. Defining Fine Grained Exemptions
Fine Grained Exemptions are annotations to state that violations of precise source coverage obligations are expected, with a justification message. These annotations are similar to the ones that define exemption regions:
-- In Ada
pragma Annotate
(Xcov,
Exemption_Kind, -- Identifier to designate what to exempt
-- Potential exemption kind-dependent details go here
"Message" -- Justification for the exemption);
// In C
// GNATCOV_EXEMPTION_KIND(/* ... details ... */, "Message")
These annotations must appear right before the statement/declaration in which to find the source coverage obligation to exempt:
-- Exemption_1 targets a construct in Statement_Or_Declaration_A
pragma Annotate (Xcov, Exemption_1, "Justification");
Statement_Or_Declaration_A;
-- Exemption_2 .. 4 target a construct in Statement_Or_Declaration_B;
-- Statement_Or_Declaration_C is out of reach.
pragma Annotate (Xcov, Exemption_2, "Justification");
pragma Annotate (Xcov, Exemption_3, "Justification");
pragma Annotate (Xcov, Exemption_4, "Justification");
Statement_Or_Declaration_B;
Statement_Or_Declaration_C;
-- Exemption_5 targets a construct in Statement_Or_Declaration_D
pragma Annotate (Xcov, Exemption_5, "Justification");
Statement_Or_Declaration_D;
1.8.2.1. Decision Outcome Exemptions
For decision coverage, each of the two decision outcomes can be exempted
separately. In Ada, the following will exempt the outcome True of the Debug
decision:
pragma Annotate (Xcov, Exempt_Decision_Outcome, True, "Debug code");
Put_Line ("Content" & (if Debug then " (debug)" else ""));
The equivalent construct in C/C++ would be:
// GNATCOV_EXEMPT_DECISION_OUTCOME(true, "Debug code")
printf ("Content%s", debug ? " (debug)" : "");
When there are multiple decisions in the same statement, it is possible to
specify a “decision offset” N, instructing to skip N decisions after
reaching the one to exempt. For instance:
pragma Annotate (Xcov, Exempt_Decision_Outcome, True, 2, "Debug code");
Procedure_Call
(Arg_1 => (if Flag_1 then 'A' else 'B'),
Arg_2 => (if Flag_2 then 'C' elsif Flag_3 then 'D' else 'E'));
There are 3 decisions here: in source order: Flag_1, Flag_2 and
Flag_3. Without a decision offset, the exemption would target the first
decision (Flag_1), but with the decision offset 2, the exemption targets
Flag_3: 2 decisions are skipped (Flag_1 and Flag_2).
1.8.2.2. Decision Condition Exemptions
For MCDC, the need to demonstrate the independent influence of each condition on the decision outcome can be exempted.
pragma Annotate (Xcov, Exempt_Decision_Condition, 2, "Debug code");
if Message_Requested or else Debug then
Send_Message;
end if;
In this example, 2 is the index of the condition to exempt. For a given
decision, indexes are assigned from left to right starting at 1:
Message_Requested is the condition at index 1, Debug is the condition
at index 2.
With this fine grained exemption, it is no longer necessary to demonstrate the
independent influence of the Debug condition, so the following evaluation
vectors for the decision will be enough to get no violation in the coverage
report:
False or else False(outcome:False)True or else XXX(outcome:True)
whereas without the exemption, an additional False or else True would be
needed to reach full MCDC coverage.
Below is the equivalent example in C/C++:
// GNATCOV_EXEMPT_DECISION_CONDITION(2, "Debug code")
if (message_requested || debug)
send_message ();
Note that for ATCC, the exemption covers the mere evaluation of the designated condition as part of a decision evaluation that reaches its outcome True.
As with decision outcome exemptions, it is possible to specify a decision offset for cases when there are multiple decisions in the statement:
pragma Annotate (Xcov, Exempt_Decision_Condition, 3, 1, "Debug code");
Procedure_Call
(Arg_1 => (if Flag_1 then 'A' else 'B'),
Arg_2 => (if Flag_2
and then Flag_3
and then Flag_4
then 'C'
else 'D'),
Arg_3 => (if Flag_5 then 'E' else 'F'));
Here, the statement that follows the exemption annotation has 3 decisions. The
decision at offset 1 (i.e. Flag_2 and then Flag_3 and then Flag_4) has 3
conditions, so it is Flag_4 that is exempted.
1.8.2.3. Full Decision Exemptions
gnatcov supports the following convenience annotation to exempt all relevant outcomes (i.e. False and True for decision coverage and MCDC, only True for assertion coverage) and conditions for a given decision:
-- Exempt all decisions and conditions for the next decision
pragma Annotate (Xcov, Exempt_Full_Decision, "Justification");
-- Exempt all decisions and conditions for the decision
-- at offset 2.
pragma Annotate (Xcov, Exempt_Full_Decision, 2, "Justification");
// GNATCOV_EXEMPT_FULL_DECISION("Justification")
// GNATCOV_EXEMPT_FULL_DECISION(2, "Justification")
In both examples above, 2 is the optional decision offset.
1.8.2.4. Branch Exemptions
The branch exemption annotation is a convenience helper that can be put first
in an if/elsif/else block (Ada and C/C++): it is expanded into:
a decision outcome exemption for the relevant outcome of the closest controlling decision;
an exemption region for the statements that contain the branch exemption.
For example:
if Debug then
pragma Annotate (Xcov, Exempt_Branch, "Debug code");
Put_Line ("Execution went here");
elsif Profiling then
Increment_Counter;
end if;
Here, the outcome True of the Debug decision is exempted, as well as the
call to Put_Line. However, if the annotation was put before the call to
Increment_Counter, then the outcome True of Profiling and the call to
Increment_Counter would be exempted.
In C/C++, the equivalent example would be:
if (debug)
// GNATCOV_EXEMPT_BRANCH("Debug code")
puts ("Execution went here");
else if (profiling)
increment_counter ();
Specifically in Ada, it is also possible to set a branch exemption in a when
... => clause: in that case, no decision outcome is exempted, but all
statements in the branch are exempted:
case State is
when Uninitialized =>
Initialize;
when Initialized =>
Make_Progress;
when Error =>
pragma Annotate (Xcov, Exempt_Branch, "defensive code");
Log_Unreachable ("State = Error");
raise Program_Error;
end case;
1.8.2.5. Manual Decision Evaluations
While they are not exactly exemptions, decision evaluation vectors are conveyed to gnatcov through the annotation mechanism: coverage report production will then act as if the decision was evaluated with the given condition values.
pragma Annotate
(Xcov, Manual_Decision_Evaluation, False, False, "Tested manually");
pragma Annotate
(Xcov, Manual_Decision_Evaluation, True, False, "Tested manually");
if A and then B then
Do_Something;
end if;
In this example, gnatcov will consider that the A and then B decision was
evaluated as False and then XXX and True and then False, so MCDC will
be fully achieved for this decision as soon as it is evaluated at run time with
True, True.
Note that the in the False, False evaluation vector, the second condition
valuation is not meaningful since, because of the short-circuiting behavior of
and then, B is not evaluated when A evaluates to False: in that
case, the second boolean value passed to Manual_Decision_Evaluation is just
disregarded, yet its presence is necessary in order to have exactly one
valuation per condition.
Below is the equivalent example in C/C++:
// GNATCOV_MANUAL_DECISION_EVALUATION(false, false, "Tested manually")
// GNATCOV_MANUAL_DECISION_EVALUATION(true, false, "Tested manually")
if (a && b)
do_something ();
As with decision outcome exemptions, it is possible to specify a decision offset for cases when there are multiple decisions in the statement:
pragma Annotate
(Xcov, Manual_Decision_Evaluation, False, False, 1, "Debug code");
Procedure_Call
(Arg_1 => (if Flag_1 then 'A' else 'B'),
Arg_2 => (if Flag_2 and then Flag_3 then 'C' else 'D'),
Arg_3 => (if Flag_5 then 'E' else 'F'));
1.8.3. Reporting about coverage exemptions
1.8.3.1. Exemption regions
Exempted regions are reported as blocks in both the annotated source and the
synthetic text reports, for both source and object coverage metrics. In
annotated source reports, a # or * character annotates all the exempted
lines, depending on whether 0 or at least 1 violation was exempted over the
whole section, respectively. For our Eassert example above, a typical
=xcov output for stmt+decision coverage for would
be:
6 .: procedure Eassert (T : Boolean) is
7 .: begin
8 *: pragma Annotate (Xcov, Exempt_On, "assert condition never to be False");
9 *: if not T then
10 *: raise Program_Error;
11 *: end if;
12 *: pragma Annotate (Xcov, Exempt_Off);
13 .: end Eassert;
The whole block is marked with * annotations to indicate that some
violations were actually exempted; 2 in this case: the statement coverage
violation for the raise and the decision coverage violation for the if
control.
In synthetic text reports, a single indication is emitted for each exempted region as a whole, and the indications for all the regions are grouped in a separate Exempted Regions report section, only present if there are exemption regions in the analysis scope. This section lists the exempted regions, displaying for each the source location span, the number of actually exempted violations in the region, the exemption justification text and the observed exempted violations per regions. It also includes a total count of the number of exempted regions and another for exempted violations across all sections at the end.
The corresponding =report excerpt below illustrates this for the
Eassert example:
...
=========================
== 3. EXEMPTED REGIONS ==
=========================
eassert.adb:8:4-12:4: 2 exempted violations, justification:
"assert condition never to be False"
Exempted violations:
eassert.adb:8:4: decision outcome TRUE never exercised
eassert.adb:9:8: statement not executed
1 exempted region, 2 exempted violations.
=========================
== 4. ANALYSIS SUMMARY ==
=========================
No non-exempted STMT violation.
No non-exempted DECISION violation.
1 exempted region, 2 exempted violations.
The Coverage Violations section is renamed to convey that it contains “NON-EXEMPTED” violations only, and the Analysis Summary counters are adjusted in a similar manner. The number of exempted regions is added to the list of counters in this section.
If the executed tests actually trigger an assertion failure, there is no coverage violation to be exempted any more and this translates as visible differences in the reports:
In annotated sources, the region is annotated with # signs instead of
*, as in:
6 .: procedure Eassert (T : Boolean) is
7 .: begin
8 #: pragma Annotate (Xcov, Exempt_On, "assert condition never to be False");
9 #: if not T then
10 #: raise Program_Error;
11 #: end if;
12 #: pragma Annotate (Xcov, Exempt_Off);
13 .: end Eassert;
In synthetic reports, the count of exempted violations is 0, like:
=========================
== 3. EXEMPTED REGIONS ==
=========================
eassert.adb:8:4-12:4: 0 exempted violation, justification:
assert condition never to be False
1 exempted region.
1.8.3.2. Fine grained exemptions
In the annotated source, fine grained exemptions are reported in the line range
that covers the relevant source coverage obligation, with the same signs as
exemption regions (# and *) depending on whether the exempted
obligation had a violation. Note that unexempted violations are reported in
priority over exempted violations:
-- The whole statement was not executed
8 .: pragma Annotate (Xcov, Exempt_Decision_Outcome, True, "Debug code");
9 -: Put_Line ("Content" & (if Debug then " (debug)" else ""));
-- The statement was executed, Debug was evaluated to False
8 .: pragma Annotate (Xcov, Exempt_Decision_Outcome, True, "Debug code");
9 *: Put_Line ("Content" & (if Debug then " (debug)" else ""));
-- The statement was executed and Debug evaluated to both False and True
8 .: pragma Annotate (Xcov, Exempt_Decision_Outcome, True, "Debug code");
9 #: Put_Line ("Content" & (if Debug then " (debug)" else ""));
In synthetic text reports, fine grained exemptions that were triggered (i.e. that prevented the emission of a note in the Coverage Violations section) are listed in the Fine Grained Exempted Violatinos section:
=========================================
== 3. FINE GRAINED EXEMPTED VIOLATIONS ==
=========================================
pkg.adb:8:10: decision outcome TRUE never exercised (exempted: Debug code)
1 fine grained exempted item.
Manual decision evaluations are visible in annotated source reports, but only
when including details (xcov+, html):
8 .: pragma Annotate
9 .: (Xcov, Manual_Decision_Evaluation, False, False, "Tested manually");
10 .: pragma Annotate
11 .: (Xcov, Manual_Decision_Evaluation, True, False, "Tested manually");
12 +: if A and then B then
pkg.adb:12:15: including manual decision evaluation:
F - -> FALSE (justification: Tested manually)
In synthetic text reports, they are also included in the Fine Grained Exempted Violations section:
=========================================
== 3. FINE GRAINED EXEMPTED VIOLATIONS ==
=========================================
pkg.adb:12:15: including manual decision evaluation:
F - -> FALSE (justification: Tested manually)
1 fine grained exempted item.
1.8.3.3. Undetermined Coverage state and Exemptions
For each exemption region in which there are obligations with undetermined coverage state, the synthetic text report will indicate the count of undetermined coverage obligations in the region, in addition to the number of violations.
In the annotated sources, exemption regions with only undetermined coverage
items are annotated with @ signs instead of *. If there are both
undetermined coverage obligations as well as violations in the exemption
region, the corresponding lines is still annotated with #.
1.8.4. Object coverage exemptions
Exemption regions specified via annotations in source files actually apply to both source and object level criteria analyzed over the annotated regions.
In the previous example, we would have used similar exemption annotations to
deal with expected object instruction and branch coverage failures in Eassert,
as the conditional branch used to implement the if statement is expected
to remain partially covered, as well as the sequence of machine instructions
triggering the Ada exception raise.
As for Source Coverage Obligations for source level criteria, information about the declared exemption regions is located in the Library Information files produced by the compiler for every compilation unit. The mechanisms described in chapter Specifying Units Of Interest can then also be used to designate units for which exemptions regions should be accounted for.