Skip to content

Commit 6b6bc30

Browse files
author
Viviane Garèse
committed
Implement first two levels of assertion coverage
This change adds the possibility of computing the coverage of assertions and contracts for levels: * ATC (Assertion True Coverage): an assertion is covered if its whole decision has been evaluated to True at least once. * ATCC (Assertion True Condition Coverage): and assertion is covered if every condition of the decision has been evaluated at least once in the context of an evaluation to True of the whole decision. The relevant assertions and contracts are Assert, Assert_And_Cut, Assume, Check, Loop_Invariant, Type_Invariant, Precondition, Postcondition. The only unsupported cases are Pre/Postcondition and Type_Invariant as pragma statements which are not instrumented. A warning is issued stating as much, and the rest of the code is instrumented as normal. Issue: #7 TN: O212-062
1 parent f64a4b3 commit 6b6bc30

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+1669
-311
lines changed

testsuite/SCOV/internals/cnotes.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,11 @@
126126

127127
# cPartCov : independent effect of condition not demonstrated (=report)
128128

129+
# aNoCov : assertion never evaluated (=report)
130+
# atNoCov : assertion expression outcome TRUE never evaluated (=report)
131+
# acPartCov : assertion condition was never evaluated during an evaluation of
132+
# the decision to True (=report)
133+
129134
# xBlock0 : exempted block, 0 deviations (=report)
130135
# xBlock1 : exempted block, >0 deviations (=report)
131136
# xBlock2 : exempted block, >0 undetermined coverage items (=report)
@@ -168,8 +173,9 @@
168173
XotNoCov, XofNoCov, XoPartCov, XoNoCov,
169174
XcPartCov,
170175
Xr0, Xr0c,
176+
aNoCov, atNoCov, acPartCov,
171177
blockNote,
172-
xBlock0, xBlock1, xBlock2) = range(50)
178+
xBlock0, xBlock1, xBlock2) = range(53)
173179

174180
NK_image = {None: "None",
175181
lNoCode: "lNoCode", lNotCoverable: "lNotCoverable",
@@ -192,7 +198,8 @@
192198
XotNoCov: "XotNoCov", XofNoCov: "XofNoCov", XoPartCov: "XoPartCov",
193199
XoNoCov: "XoNoCov",
194200
XcPartCov: "XcPartCov",
195-
Xr0: "Xr0", Xr0c: "Xr0c"}
201+
Xr0: "Xr0", Xr0c: "Xr0c",
202+
aNoCov: "aNoCov", atNoCov: "atNoCov", acPartCov: "acPartCov"}
196203

197204

198205
# ===============================
@@ -221,6 +228,9 @@
221228
# MCDC violations
222229
cNoteKinds = (etNoCov, efNoCov, ePartCov, eNoCov, cPartCov, eUndetCov)
223230

231+
# Assertion violations
232+
aNoteKinds = (aNoCov, atNoCov, acPartCov)
233+
224234
# Exemption regions
225235
xNoteKinds = (xBlock0, xBlock1, xBlock2)
226236

@@ -247,6 +257,7 @@
247257
# if they could be emitted and report them as unmatched.
248258

249259
erNoteKinds = sNoteKinds+dNoteKinds+cNoteKinds+xNoteKinds+tNoteKinds+XNoteKinds
260+
erNoteKinds += aNoteKinds
250261
xrNoteKinds = erNoteKinds+rAntiKinds+XrAntiKinds
251262

252263

testsuite/SCOV/internals/driver.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -936,6 +936,13 @@ def check_expectations(self):
936936
"stmt+decision": 2,
937937
"stmt+mcdc": 3,
938938
"stmt+uc_mcdc": 3,
939+
"stmt+atc": 1,
940+
"stmt+decision+atc": 2,
941+
"stmt+decision+atcc": 2,
942+
"stmt+mcdc+atc": 3,
943+
"stmt+mcdc+atcc": 3,
944+
"stmt+uc_mcdc+atc": 3,
945+
"stmt+uc_mcdc+atcc": 3,
939946
}
940947

941948
stricter_level = (
@@ -1164,6 +1171,43 @@ def _scofiles_list(self):
11641171
for soi in self.sources_of_interest())
11651172
if scof}
11661173

1174+
def wdbase_for(self, covlevel):
1175+
"""
1176+
Compute a short base prefix for the working directory that will
1177+
contain the output of coverage analysis for level covlevel.
1178+
1179+
Uses the first letter of the highest level ('s' for "stmt" or 'u' for
1180+
"stmt+uc_mcdc") and the full name of the assertion level if assertion
1181+
coverage is enabled.
1182+
"""
1183+
levels = covlevel.split("+")
1184+
1185+
if len(levels) == 1:
1186+
return "s_"
1187+
1188+
wdbase = levels[1][0]
1189+
wdbase += levels[-1] if self.assert_lvl else ""
1190+
1191+
return wdbase + "_"
1192+
1193+
def xcovlevel_for(self, wdname):
1194+
"""
1195+
Compute the source coverage level from the working directory prefix
1196+
by matching the first letter of the highest coverage level plus the
1197+
full name of the assertion level is enabled.
1198+
"""
1199+
res = "stmt"
1200+
wdbase = wdname.split('_')[0]
1201+
1202+
for lvl in ["decision", "mcdc", "uc_mcdc"]:
1203+
if wdbase[0] == lvl[0]:
1204+
res += "+" + lvl
1205+
1206+
if len(wdbase) > 1:
1207+
res += "+" + wdbase[1:]
1208+
1209+
return res
1210+
11671211

11681212
class SCOV_helper_bin_traces(SCOV_helper):
11691213
"""SCOV_helper specialization for the binary execution trace based mode."""

testsuite/SCOV/internals/rnexpanders.py

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@
1111
from .cnotes import (
1212
xBlock0, xBlock1, sNoCov, sPartCov, sNotCoverable, dfAlways, dtAlways,
1313
dfNoCov, dtNoCov, dNoCov, dPartCov, efNoCov, etNoCov, eNoCov, ePartCov,
14-
cPartCov, Enote, KnoteDict, erNoteKinds, sUndetCov, dUndetCov, eUndetCov,
15-
xBlock2, XsNoCov, XsPartCov, XsNotCoverable, XsUndetCov, XotNoCov,
16-
XofNoCov, XoPartCov, XoNoCov, XcPartCov
14+
cPartCov, aNoCov, atNoCov, acPartCov, Enote, KnoteDict, erNoteKinds,
15+
sUndetCov, dUndetCov, eUndetCov, xBlock2, XsNoCov, XsPartCov,
16+
XsNotCoverable, XsUndetCov, XotNoCov, XofNoCov, XoPartCov, XoNoCov,
17+
XcPartCov
1718
)
1819
from .segments import Sloc, Sloc_from_match
1920
from .stags import Stag_from
@@ -79,7 +80,6 @@ def __init__(self, sloc, diag):
7980

8081

8182
def Rdiagline_from(text):
82-
8383
p = re.match(Rdiagline.re, text)
8484

8585
return Rdiagline(sloc=Sloc_from_match(p),
@@ -207,7 +207,6 @@ def __init__(self, re_notes):
207207
self.enotes = []
208208

209209
def try_parse_enote(self, rline):
210-
211210
dline = Rdiagline_from(rline)
212211

213212
# If no match at all, punt.
@@ -570,6 +569,22 @@ def __init__(self):
570569
self.noteblocks.append(
571570
VIOsection(re_start="MCDC COVERAGE", re_notes=mcdc_notes))
572571

572+
# Assertion coverage
573+
574+
atc_notes = {
575+
"contract expression outcome TRUE never": atNoCov,
576+
"contract expression never evaluated": aNoCov,
577+
}
578+
self.noteblocks.append(
579+
VIOsection(re_start="ATC COVERAGE", re_notes=atc_notes))
580+
581+
atcc_notes = {
582+
"condition was never evaluated during an evaluation of the "
583+
"decision to True": acPartCov
584+
}
585+
self.noteblocks.append(
586+
VIOsection(re_start="ATCC COVERAGE", re_notes=atcc_notes))
587+
573588
# Non coverable items
574589

575590
nc_notes = {

testsuite/SCOV/internals/xnexpanders.py

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1203,10 +1203,25 @@ def __decode_note_choice(self, text):
12031203
or
12041204
[('stmt', 'l+'), ('stmt+decision', 'l+')]
12051205
"""
1206-
level_from_char = {"s": "stmt",
1207-
"d": "stmt+decision",
1208-
"m": "stmt+mcdc",
1209-
"u": "stmt+uc_mcdc"}
1206+
level_from_char = {"s": ["stmt"],
1207+
"d": ["stmt+decision"],
1208+
"m": ["stmt+mcdc"],
1209+
"u": ["stmt+uc_mcdc"]}
1210+
1211+
def make_assert_lvl_combinaison(assert_lvl):
1212+
"""
1213+
Add the assertion coverage level assert_lvl to other regular
1214+
coverage level combinaisons defined in level_from_char. Return the
1215+
list of combinaisons.
1216+
"""
1217+
return [level_from_char[c][0] + "+" + assert_lvl
1218+
for c in level_from_char]
1219+
1220+
assert_level_from_char = {"a": make_assert_lvl_combinaison("atc"),
1221+
"c": make_assert_lvl_combinaison("atcc")}
1222+
1223+
level_from_char.update(assert_level_from_char)
1224+
12101225
result = text.split("=>")
12111226

12121227
if len(result) == 1:
@@ -1220,7 +1235,12 @@ def __decode_note_choice(self, text):
12201235
note = result[1].lstrip(' ')
12211236
lev_list = result[0].rstrip(' ')
12221237

1223-
return [(level_from_char[lchar], note) for lchar in lev_list]
1238+
res = []
1239+
for lchar in lev_list:
1240+
for combinaison in level_from_char[lchar]:
1241+
res.append((combinaison, note))
1242+
1243+
return res
12241244

12251245
def __select_lnote(self, text):
12261246
"""Decode text to return the line note for the current

testsuite/SCOV/internals/xnotep.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
oPartCov, ofNoCov, otNoCov, r0, r0c, sNoCov, sNotCoverable, sPartCov,
1919
xBlock0, xBlock1, xBlock2, xNoteKinds, lUndetCov, sUndetCov, dUndetCov,
2020
eUndetCov, XsNoCov, XsPartCov, XsNotCoverable, XsUndetCov, XotNoCov,
21-
XofNoCov, XoPartCov, XoNoCov, XcPartCov, Xr0, Xr0c
21+
XofNoCov, XoPartCov, XoNoCov, XcPartCov, Xr0, Xr0c, aNoCov, atNoCov,
22+
acPartCov
2223
)
2324
from .segments import Line, Section, Segment
2425
from .stags import Stag_from
@@ -181,6 +182,7 @@ class XnoteP:
181182
'e?': eUndetCov,
182183
'oT-': otNoCov, 'oF-': ofNoCov, 'o!': oPartCov, 'o-': oNoCov,
183184
'c!': cPartCov,
185+
'a-': aNoCov, 'aT-': atNoCov, 'ac!': acPartCov,
184186
'x0': xBlock0, 'x+': xBlock1, 'x?': xBlock2,
185187
'0': r0, '0c': r0c,
186188
# Exempted notes

testsuite/SCOV/report.py

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
from e3.fs import ls
1818

19+
from SCOV.internals.driver import SCOV_helper
1920
from SCOV.internals.cnotes import xNoteKinds
2021
from SCOV.internals.tfiles import Tfile
2122
from SUITE.tutils import XCOV, thistest, frame
@@ -139,15 +140,6 @@ def check(self):
139140
"stmt+uc_mcdc": ["STMT", "DECISION", "MCDC"]
140141
}
141142

142-
# Base prefix of the working subdirectory for each xcovlevel. ??? This is
143-
# relying too much on knowledge about how the testuite driver works ...
144-
xcovlevel_from = {
145-
"sc_": "stmt",
146-
"dc_": "stmt+decision",
147-
"mc_": "stmt+mcdc",
148-
"uc_": "stmt+uc_mcdc"
149-
}
150-
151143

152144
class ReportChecker:
153145
"""Whole report checker"""
@@ -301,7 +293,9 @@ def __process_one_test(self, qde):
301293
# applicable xcov-level
302294
self.__setup_expectations(
303295
ntraces=len(qde.drivers),
304-
xcovlevel=xcovlevel_from[os.path.basename(qde.wdir)[0:3]],
296+
xcovlevel=SCOV_helper.xcovlevel_for(self.tc,
297+
os.path.basename
298+
(qde.wdir)),
305299
xregions=xregions)
306300

307301
reports = ls(os.path.join(qde.wdir, "test.rep"))

testsuite/SCOV/tc.py

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ def __drivers_from(self, cspec):
124124
return [drv for drv in self.all_drivers if re.search(drv_expr, drv)]
125125

126126
def __init__(self, extradrivers="", extracargs="", category=CAT.auto,
127-
tolerate_messages=None):
127+
tolerate_messages=None, assert_lvl=None):
128128
# By default, these test cases expect no error from subprocesses (xrun,
129129
# xcov, etc.)
130130
self.expect_failures = False
@@ -171,6 +171,8 @@ def __init__(self, extradrivers="", extracargs="", category=CAT.auto,
171171
if category == CAT.auto else
172172
category)
173173

174+
self.assert_lvl = assert_lvl
175+
174176
# - extra compilation arguments, added to what --cargs was provided to
175177
# the testsuite command line:
176178
self.extracargs = extracargs
@@ -191,19 +193,24 @@ def __xcovlevels(self):
191193
if thistest.options.xcov_level:
192194
return [thistest.options.xcov_level]
193195

196+
assert not thistest.options.qualif_level
197+
194198
default_xcovlevels_for = {
195199
# Tests without categories should be ready for anything.
196200
# Exercise with the strictest possible mode:
197-
None: ["stmt+mcdc"],
201+
None: ["stmt+mcdc"],
198202

199-
CAT.stmt: ["stmt"],
203+
CAT.stmt: ["stmt"],
200204
CAT.decision: ["stmt+decision"],
201-
CAT.mcdc: ["stmt+mcdc", "stmt+uc_mcdc"]}
205+
CAT.mcdc: ["stmt+mcdc", "stmt+uc_mcdc"],
206+
}
202207

203-
defaults = default_xcovlevels_for[self.category]
204-
return ([defaults[0]]
205-
if thistest.options.qualif_level else
206-
defaults)
208+
# Add a "+" before the name of the assertion coverage level in order
209+
# to append it at the end of the the other specified coverage levels
210+
# passed to gnatcov.
211+
alvl = ("+" + self.assert_lvl) if self.assert_lvl else ""
212+
213+
return [d + alvl for d in default_xcovlevels_for[self.category]]
207214

208215
def __register_qde_for(self, drvo):
209216
"""
@@ -215,13 +222,6 @@ def __register_qde_for(self, drvo):
215222
drivers=drvo.drivers, xrnotes=drvo.xrnotes,
216223
wdir=os.path.normpath(drvo.awdir())))
217224

218-
# Base prefix for Working directories, per --level. Shared across
219-
# runs for multiples levels.
220-
_wdbase_for = {"stmt": "st_",
221-
"stmt+decision": "dc_",
222-
"stmt+mcdc": "mc_",
223-
"stmt+uc_mcdc": "uc_"}
224-
225225
def __run_one_covlevel(self, covlevel, covcontrol, subdirhint):
226226
"""
227227
Run this testcase individual drivers and consolidation tests with
@@ -236,7 +236,7 @@ def __run_one_covlevel(self, covlevel, covcontrol, subdirhint):
236236

237237
# Compute the Working directory base for this level, then run the test
238238
# for each indivdual driver.
239-
this_wdbase = self._wdbase_for[covlevel]
239+
this_wdbase = this_scov_helper.wdbase_for(self, covlevel)
240240

241241
wdctl = WdirControl(wdbase=this_wdbase, bdbase=self._available_bdbase,
242242
subdirhint=subdirhint)
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
pragma Assertion_Policy (Check);
2+
pragma Ada_2012;
3+
4+
package body Assertions is
5+
6+
---------
7+
-- Foo --
8+
---------
9+
10+
function Foo (I : Integer) return Integer
11+
is
12+
High : Integer := 3; -- # foo_hi_decl
13+
begin
14+
for J in 1 .. High loop -- # foo_loop_1
15+
pragma Loop_Invariant (J = J + 1 - 1 and then J < 4); -- # foo_inv_1
16+
end loop;
17+
18+
for J in 1 .. High loop -- # foo_loop_2
19+
pragma Loop_Invariant (J = J + 1 - 1 or else J < 4); -- # foo_inv_2
20+
end loop;
21+
return I; -- # foo_return
22+
end Foo;
23+
24+
----------
25+
-- Same --
26+
----------
27+
28+
function Same (A, B : Boolean) return Boolean
29+
is
30+
-- Subprogram body with aspect without prior declaration
31+
32+
function Id (C : Boolean) return Boolean
33+
with Pre => C or else (C or else not C), -- # id_pre
34+
Post => Id'Result = C -- # id_post
35+
is
36+
begin
37+
return C; -- # id_ret
38+
end Id;
39+
40+
begin
41+
-- Decision nested as parameter of a function call within an assertion.
42+
-- The function parameter should not be seen as a condition for the
43+
-- assertion decision.
44+
45+
-- Last two conditions are not evaluated
46+
47+
pragma Assert (Id (A or else B) or else A or else B); -- # nested_1
48+
49+
-- Last condition is not evaluated
50+
51+
pragma Assert (B or else A or else Id (A or else B)); -- # nested_2
52+
53+
-- All conditions are evaluated
54+
55+
pragma Assert (B or else Id (A or else B)); -- # nested_3
56+
return B; -- # same_ret
57+
end Same;
58+
59+
end Assertions;

0 commit comments

Comments
 (0)