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

94 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-10-21 23:49 +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 ``userAttributes`` isn't iterable. 

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

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

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

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

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

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

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 TypeError: If parameter ``userAttributes`` isn't iterable. 

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

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

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

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

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

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

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

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

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

187 :raises ValueError: If parameter ``name`` or ``assertionKind`` is ``None``. 

188 :raises TypeError: If parameter ``name`` or ``assertionKind`` isn't of type :class:`str`. 

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

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

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

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

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

194 """ 

195 super().__init__(userAttributes) 

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

197 

198 for parameterName, value in ( 

199 ("name", name), 

200 ("assertionKind", assertionKind) 

201 ): 

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

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

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

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

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

207 raise ex 

208 

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

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

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

212 raise ex 

213 

214 self._parent = parent 

215 self._name = name 

216 self._assertionKind = assertionKind 

217 self._bins = {} 

218 

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

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

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

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

223 raise ex 

224 

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

226 if not isinstance(binKind, AssertionBinKind): 

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

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

229 raise ex 

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

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

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

233 raise ex 

234 

235 coverBin._parent = self 

236 self._bins[binKind] = coverBin 

237 

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

239 parent._assertions.append(self) 

240 

241 @classmethod 

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

243 """ 

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

245 

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

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

248 :returns: The assertion. 

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

250 """ 

251 bins = { 

252 binKind: Bin.Parse(binElement) 

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

254 } 

255 

256 return cls( 

257 element.attrib["name"], 

258 element.attrib["assertionKind"], 

259 bins, 

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

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

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

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

264 cls._ParseUserAttributes(element), 

265 parent=parent 

266 ) 

267 

268 @readonly 

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

270 """ 

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

272 

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

274 """ 

275 return self._parent 

276 

277 @readonly 

278 def Name(self) -> str: 

279 """ 

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

281 

282 :returns: The name, as declared. 

283 """ 

284 return self._name 

285 

286 @readonly 

287 def AssertionKind(self) -> str: 

288 """ 

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

290 

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

292 """ 

293 return self._assertionKind 

294 

295 @readonly 

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

297 """ 

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

299 

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

301 """ 

302 return self._bins