Coverage for pyEDAA/Reports/CodeCoverage/UCIS/Assertions.py: 81%

94 statements  

« prev     ^ index     » next       coverage.py v7.17.3, created at 2026-11-14 02:26 +0000

1# ==================================================================================================================== # 

2# _____ ____ _ _ ____ _ # 

3# _ __ _ _| ____| _ \ / \ / \ | _ \ ___ _ __ ___ _ __| |_ ___ # 

4# | '_ \| | | | _| | | | |/ _ \ / _ \ | |_) / _ \ '_ \ / _ \| '__| __/ __| # 

5# | |_) | |_| | |___| |_| / ___ \ / ___ \ _| _ < __/ |_) | (_) | | | |_\__ \ # 

6# | .__/ \__, |_____|____/_/ \_\/_/ \_(_)_| \_\___| .__/ \___/|_| \__|___/ # 

7# |_| |___/ |_| # 

8# ==================================================================================================================== # 

9# Authors: # 

10# Patrick Lehmann # 

11# # 

12# License: # 

13# ==================================================================================================================== # 

14# Copyright 2026-2026 Electronic Design Automation Abstraction (EDA²) # 

15# # 

16# Licensed under the Apache License, Version 2.0 (the "License"); # 

17# you may not use this file except in compliance with the License. # 

18# You may obtain a copy of the License at # 

19# # 

20# http://www.apache.org/licenses/LICENSE-2.0 # 

21# # 

22# Unless required by applicable law or agreed to in writing, software # 

23# distributed under the License is distributed on an "AS IS" BASIS, # 

24# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # 

25# See the License for the specific language governing permissions and # 

26# limitations under the License. # 

27# # 

28# SPDX-License-Identifier: Apache-2.0 # 

29# ==================================================================================================================== # 

30# 

31""" 

32The assertion coverage of the UCIS XML interchange format: an instance's ``<assertionCoverage>`` and its assertions. 

33 

34An assertion - e.g. an ``assert``, ``assume`` or ``cover`` directive - has a bin per outcome. 

35""" 

36from __future__ import annotations 

37 

38from typing import TYPE_CHECKING, Iterable, Mapping, Optional as Nullable, Self 

39 

40from lxml.etree import _Element 

41from pyTooling.Common import getFullyQualifiedName, StringEnum 

42from pyTooling.Decorators import export, readonly 

43 

44from pyEDAA.Reports.CodeCoverage.UCIS.Bins import Bin 

45from pyEDAA.Reports.CodeCoverage.UCIS.Elements import Base, MetricCoverage, ObjectAttributesMixin, UserAttribute 

46 

47if TYPE_CHECKING: 47 ↛ 48line 47 didn't jump to line 48 because the condition on line 47 was never true

48 from pyEDAA.Reports.CodeCoverage.UCIS.Instances import InstanceCoverage 

49 

50 

51@export 

52class AssertionBinKind(StringEnum): 

53 """ 

54 Kind of an assertion's bin: the outcome it counts, named as the bin's element. 

55 """ 

56 

57 Cover = "coverBin" #: The cover directive's sequence matched. 

58 Pass = "passBin" #: The assertion passed. 

59 Fail = "failBin" #: The assertion failed. 

60 Vacuous = "vacuousBin" #: The assertion passed vacuously. 

61 Disabled = "disabledBin" #: The assertion was disabled. 

62 Attempt = "attemptBin" #: The assertion was attempted. 

63 Active = "activeBin" #: The assertion's threads were active. 

64 PeakActive = "peakActiveBin" #: The peak number of the assertion's active threads. 

65 

66 

67@export 

68class AssertionCoverage(MetricCoverage): 

69 """ 

70 An ``<assertionCoverage>`` of an instance: its assertions. 

71 """ 

72 

73 _assertions: list[Assertion] #: The assertions. 

74 

75 def __init__( 

76 self, 

77 metricMode: Nullable[str] = None, 

78 weight: int = 1, 

79 userAttributes: Nullable[Iterable[UserAttribute]] = None, 

80 *, 

81 parent: Nullable[InstanceCoverage] = None 

82 ) -> None: 

83 """ 

84 Initialize the assertion coverage, and append it to the assertion coverages of its instance. 

85 

86 Its assertions are added by creating them with this coverage as their parent. 

87 

88 :param metricMode: Optional, the mode in which the coverage was measured. Default: ``None``. 

89 :param weight: Optional, weight of the mode in computing the coverage. Default: ``1``. 

90 :param userAttributes: Optional, the user-defined attributes. Default: ``None``. 

91 :param parent: Optional, the instance the coverage belongs to. Default: ``None``. 

92 :raises TypeError: If parameter ``metricMode`` isn't of type :class:`str`. 

93 :raises ValueError: If parameter ``weight`` is ``None``. 

94 :raises TypeError: If parameter ``weight`` isn't of type :class:`int`. 

95 :raises ValueError: If parameter ``weight`` is negative. 

96 :raises TypeError: If parameter ``userAttributes`` isn't iterable. 

97 :raises TypeError: If parameter ``userAttributes`` contains an element not of type 

98 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Elements.UserAttribute`. 

99 :raises TypeError: If parameter ``parent`` isn't of type 

100 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Instances.InstanceCoverage`. 

101 """ 

102 super().__init__(metricMode, weight, userAttributes, parent=parent) 

103 

104 self._assertions = [] 

105 

106 if parent is not None: 

107 parent._assertionCoverages.append(self) 

108 

109 @classmethod 

110 def Parse(cls, element: _Element, *, parent: Nullable[InstanceCoverage] = None) -> Self: 

111 """ 

112 Parse an assertion coverage and its assertions from its ``<assertionCoverage>`` element. 

113 

114 :param element: The ``<assertionCoverage>`` element. 

115 :param parent: Optional, the instance the coverage belongs to. Default: ``None``. 

116 :returns: The assertion coverage. 

117 :raises CodeCoverageError: If a user-defined attribute's value isn't of the type the attribute states. 

118 """ 

119 assertionCoverage = cls( 

120 element.attrib.get("metricMode"), 

121 int(element.attrib.get("weight", "1")), 

122 cls._ParseUserAttributes(element), 

123 parent=parent 

124 ) 

125 

126 for assertionElement in element.iterfind("{*}assertion"): 

127 Assertion.Parse(assertionElement, parent=assertionCoverage) 

128 

129 return assertionCoverage 

130 

131 @readonly 

132 def Assertions(self) -> list[Assertion]: 

133 """ 

134 Read-only property to access the assertions (:attr:`_assertions`). 

135 

136 :returns: The assertions, in the order the report lists them. 

137 """ 

138 return self._assertions 

139 

140 

141@export 

142class Assertion(Base, ObjectAttributesMixin): 

143 """ 

144 An ``<assertion>`` of an assertion coverage: its name, its kind and a bin per outcome the report counts. 

145 """ 

146 

147 _parent: Nullable[AssertionCoverage] #: The assertion coverage the assertion belongs to. 

148 _name: str #: Name of the assertion. 

149 _assertionKind: str #: Kind of the assertion, e.g. ``assert`` or ``cover``. 

150 _bins: dict[AssertionBinKind, Bin] #: The bins, by the outcome they count. 

151 

152 def __init__( 

153 self, 

154 name: str, 

155 assertionKind: str, 

156 bins: Nullable[Mapping[AssertionBinKind, Bin]] = None, 

157 alias: Nullable[str] = None, 

158 excluded: bool = False, 

159 excludedReason: Nullable[str] = None, 

160 weight: int = 1, 

161 userAttributes: Nullable[Iterable[UserAttribute]] = None, 

162 *, 

163 parent: Nullable[AssertionCoverage] = None 

164 ) -> None: 

165 """ 

166 Initialize the assertion, become its bins' parent, and append it to the assertions of its assertion coverage. 

167 

168 :param name: Name of the assertion, e.g. ``a1``. 

169 :param assertionKind: Kind of the assertion, e.g. ``assert`` or ``cover``. 

170 :param bins: Optional, the bins, by the outcome they count. Default: ``None``. 

171 :param alias: Optional, an alias of the assertion's name. Default: ``None``. 

172 :param excluded: Optional, whether the assertion is excluded from the coverage. Default: ``False``. 

173 :param excludedReason: Optional, why the assertion is excluded. Default: ``None``. 

174 :param weight: Optional, weight of the assertion in computing the coverage. Default: ``1``. 

175 :param userAttributes: Optional, the user-defined attributes. Default: ``None``. 

176 :param parent: Optional, the assertion coverage the assertion belongs to. Default: ``None``. 

177 :raises ValueError: If parameter ``name`` is ``None``. 

178 :raises TypeError: If parameter ``name`` isn't of type :class:`str`. 

179 :raises ValueError: If parameter ``assertionKind`` is ``None``. 

180 :raises TypeError: If parameter ``assertionKind`` isn't of type :class:`str`. 

181 :raises TypeError: If parameter ``bins`` isn't a mapping. 

182 :raises TypeError: If parameter ``bins`` contains a key not of type :class:`AssertionBinKind`. 

183 :raises TypeError: If parameter ``bins`` contains a value not of type 

184 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Bins.Bin`. 

185 :raises TypeError: If parameter ``alias`` isn't of type :class:`str`. 

186 :raises ValueError: If parameter ``excluded`` is ``None``. 

187 :raises TypeError: If parameter ``excluded`` isn't of type :class:`bool`. 

188 :raises TypeError: If parameter ``excludedReason`` isn't of type :class:`str`. 

189 :raises ValueError: If parameter ``weight`` is ``None``. 

190 :raises TypeError: If parameter ``weight`` isn't of type :class:`int`. 

191 :raises ValueError: If parameter ``weight`` is negative. 

192 :raises TypeError: If parameter ``userAttributes`` isn't iterable. 

193 :raises TypeError: If parameter ``userAttributes`` contains an element not of type 

194 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Elements.UserAttribute`. 

195 :raises TypeError: If parameter ``parent`` isn't of type :class:`AssertionCoverage`. 

196 """ 

197 super().__init__(userAttributes) 

198 ObjectAttributesMixin.__init__(self, alias, excluded, excludedReason, weight) 

199 

200 for parameterName, value in ( 

201 ("name", name), 

202 ("assertionKind", assertionKind) 

203 ): 

204 if value is None: 204 ↛ 205line 204 didn't jump to line 205 because the condition on line 204 was never true

205 raise ValueError(f"Parameter '{parameterName}' is None.") 

206 elif not isinstance(value, str): 206 ↛ 207line 206 didn't jump to line 207 because the condition on line 206 was never true

207 ex = TypeError(f"Parameter '{parameterName}' is not of type 'str'.") 

208 ex.add_note(f"Got type '{getFullyQualifiedName(value)}'.") 

209 raise ex 

210 

211 if parent is not None and not isinstance(parent, AssertionCoverage): 211 ↛ 212line 211 didn't jump to line 212 because the condition on line 211 was never true

212 ex = TypeError("Parameter 'parent' is not of type 'AssertionCoverage'.") 

213 ex.add_note(f"Got type '{getFullyQualifiedName(parent)}'.") 

214 raise ex 

215 

216 self._parent = parent 

217 self._name = name 

218 self._assertionKind = assertionKind 

219 self._bins = {} 

220 

221 if bins is not None: 221 ↛ 240line 221 didn't jump to line 240 because the condition on line 221 was always true

222 if not isinstance(bins, Mapping): 222 ↛ 223line 222 didn't jump to line 223 because the condition on line 222 was never true

223 ex = TypeError("Parameter 'bins' is not a mapping.") 

224 ex.add_note(f"Got type '{getFullyQualifiedName(bins)}'.") 

225 raise ex 

226 

227 for binKind, coverBin in bins.items(): 

228 if not isinstance(binKind, AssertionBinKind): 

229 ex = TypeError("Parameter 'bins' contains a key not of type 'AssertionBinKind'.") 

230 ex.add_note(f"Got type '{getFullyQualifiedName(binKind)}'.") 

231 raise ex 

232 elif not isinstance(coverBin, Bin): 232 ↛ 233line 232 didn't jump to line 233 because the condition on line 232 was never true

233 ex = TypeError("Parameter 'bins' contains a value not of type 'Bin'.") 

234 ex.add_note(f"Got type '{getFullyQualifiedName(coverBin)}' for key '{binKind}'.") 

235 raise ex 

236 

237 coverBin._parent = self 

238 self._bins[binKind] = coverBin 

239 

240 if parent is not None: 240 ↛ exitline 240 didn't return from function '__init__' because the condition on line 240 was always true

241 parent._assertions.append(self) 

242 

243 @classmethod 

244 def Parse(cls, element: _Element, *, parent: Nullable[AssertionCoverage] = None) -> Self: 

245 """ 

246 Parse an assertion and its bins from its ``<assertion>`` element. 

247 

248 :param element: The ``<assertion>`` element. 

249 :param parent: Optional, the assertion coverage the assertion belongs to. Default: ``None``. 

250 :returns: The assertion. 

251 :raises CodeCoverageError: If a user-defined attribute's value isn't of the type the attribute states. 

252 """ 

253 bins = { 

254 binKind: Bin.Parse(binElement) 

255 for binKind in AssertionBinKind if (binElement := element.find(f"{{*}}{binKind}")) is not None 

256 } 

257 

258 return cls( 

259 element.attrib["name"], 

260 element.attrib["assertionKind"], 

261 bins, 

262 element.attrib.get("alias"), 

263 cls._ParseBoolean(element.attrib.get("excluded", "false")), 

264 element.attrib.get("excludedReason"), 

265 int(element.attrib.get("weight", "1")), 

266 cls._ParseUserAttributes(element), 

267 parent=parent 

268 ) 

269 

270 @readonly 

271 def Parent(self) -> Nullable[AssertionCoverage]: 

272 """ 

273 Read-only property to access the assertion coverage the assertion belongs to (:attr:`_parent`). 

274 

275 :returns: The assertion coverage; ``None``, if the assertion belongs to none. 

276 """ 

277 return self._parent 

278 

279 @readonly 

280 def Name(self) -> str: 

281 """ 

282 Read-only property to access the name of the assertion (:attr:`_name`). 

283 

284 :returns: The name, as declared. 

285 """ 

286 return self._name 

287 

288 @readonly 

289 def AssertionKind(self) -> str: 

290 """ 

291 Read-only property to access the kind of the assertion (:attr:`_assertionKind`). 

292 

293 :returns: The kind, as stated, e.g. ``assert`` or ``cover``. 

294 """ 

295 return self._assertionKind 

296 

297 @readonly 

298 def Bins(self) -> dict[AssertionBinKind, Bin]: 

299 """ 

300 Read-only property to access the bins (:attr:`_bins`). 

301 

302 :returns: The bins, by the outcome they count, in the order of :class:`AssertionBinKind`. 

303 """ 

304 return self._bins