Coverage for pyEDAA/Reports/CodeCoverage/UCIS/Crosses.py: 70%

139 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 crosses of the covergroup coverage of the UCIS XML interchange format: their options and bins, each a combination 

33of the crossed coverpoints' bins. 

34""" 

35from __future__ import annotations 

36 

37from typing import TYPE_CHECKING, Iterable, Optional as Nullable, Self 

38 

39from lxml.etree import _Element 

40from pyTooling.Common import getFullyQualifiedName 

41from pyTooling.Decorators import export, readonly 

42 

43from pyEDAA.Reports.CodeCoverage.UCIS.Bins import BinContents 

44from pyEDAA.Reports.CodeCoverage.UCIS.CoverOptions import CrossOptions 

45from pyEDAA.Reports.CodeCoverage.UCIS.Coverpoints import BinBase 

46from pyEDAA.Reports.CodeCoverage.UCIS.Elements import Base, UserAttribute 

47 

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

49 from pyEDAA.Reports.CodeCoverage.UCIS.Covergroups import CovergroupInstance 

50 

51 

52@export 

53class Cross(Base): 

54 """ 

55 A ``<cross>`` of a covergroup instance: the expressions - coverpoints - it crosses, its options and bins. 

56 """ 

57 

58 _parent: Nullable[CovergroupInstance] #: The covergroup instance the cross belongs to. 

59 _name: str #: Name of the cross. 

60 _key: str #: UCIS key of the cross. 

61 _options: CrossOptions #: The options of the cross. 

62 _alias: Nullable[str] #: An alias of the cross' name. 

63 _expressions: list[str] #: The crossed expressions, e.g. coverpoints' names. 

64 _bins: list[CrossBin] #: The bins. 

65 

66 def __init__( 

67 self, 

68 name: str, 

69 key: str, 

70 options: CrossOptions, 

71 expressions: Nullable[Iterable[str]] = None, 

72 alias: Nullable[str] = None, 

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

74 *, 

75 parent: Nullable[CovergroupInstance] = None 

76 ) -> None: 

77 """ 

78 Initialize the cross, and append it to the crosses of its covergroup instance. 

79 

80 Its bins are added by creating them with this cross as their parent. 

81 

82 :param name: Name of the cross, e.g. ``xab``. 

83 :param key: UCIS key of the cross. 

84 :param options: The options of the cross. 

85 :param expressions: Optional, the crossed expressions, e.g. ``["cvpt_a", "cvpt_b"]``. Default: ``None``. 

86 :param alias: Optional, an alias of the cross' name. Default: ``None``. 

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

88 :param parent: Optional, the covergroup instance the cross belongs to. Default: ``None``. 

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

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

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

92 :raises ValueError: If parameter ``name`` or ``key`` is ``None``. 

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

94 :raises ValueError: If parameter ``options`` is ``None``. 

95 :raises TypeError: If parameter ``options`` isn't of type 

96 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.CoverOptions.CrossOptions`. 

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

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

99 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Covergroups.CovergroupInstance`. 

100 :raises TypeError: If parameter ``expressions`` isn't iterable. 

101 :raises TypeError: If parameter ``expressions`` contains an element not of type :class:`str`. 

102 """ 

103 from pyEDAA.Reports.CodeCoverage.UCIS.Covergroups import CovergroupInstance 

104 

105 super().__init__(userAttributes) 

106 

107 for parameterName, value in ( 

108 ("name", name), 

109 ("key", key) 

110 ): 

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

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

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

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

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

116 raise ex 

117 

118 if options is None: 118 ↛ 119line 118 didn't jump to line 119 because the condition on line 118 was never true

119 raise ValueError("Parameter 'options' is None.") 

120 elif not isinstance(options, CrossOptions): 120 ↛ 121line 120 didn't jump to line 121 because the condition on line 120 was never true

121 ex = TypeError("Parameter 'options' is not of type 'CrossOptions'.") 

122 ex.add_note(f"Got type '{getFullyQualifiedName(options)}'.") 

123 raise ex 

124 

125 if alias is not None and not isinstance(alias, str): 125 ↛ 126line 125 didn't jump to line 126 because the condition on line 125 was never true

126 ex = TypeError("Parameter 'alias' is not of type 'str'.") 

127 ex.add_note(f"Got type '{getFullyQualifiedName(alias)}'.") 

128 raise ex 

129 

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

131 ex = TypeError("Parameter 'parent' is not of type 'CovergroupInstance'.") 

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

133 raise ex 

134 

135 self._parent = parent 

136 self._name = name 

137 self._key = key 

138 self._options = options 

139 self._alias = alias 

140 self._expressions = [] 

141 self._bins = [] 

142 

143 if expressions is not None: 143 ↛ 157line 143 didn't jump to line 157 because the condition on line 143 was always true

144 if not isinstance(expressions, Iterable): 144 ↛ 145line 144 didn't jump to line 145 because the condition on line 144 was never true

145 ex = TypeError("Parameter 'expressions' is not iterable.") 

146 ex.add_note(f"Got type '{getFullyQualifiedName(expressions)}'.") 

147 raise ex 

148 

149 for expression in expressions: 

150 if not isinstance(expression, str): 150 ↛ 151line 150 didn't jump to line 151 because the condition on line 150 was never true

151 ex = TypeError("Parameter 'expressions' contains an element not of type 'str'.") 

152 ex.add_note(f"Got type '{getFullyQualifiedName(expression)}'.") 

153 raise ex 

154 

155 self._expressions.append(expression) 

156 

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

158 parent._crosses.append(self) 

159 

160 @classmethod 

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

162 """ 

163 Parse a cross, its options and bins from its ``<cross>`` element. 

164 

165 :param element: The ``<cross>`` element. 

166 :param parent: Optional, the covergroup instance the cross belongs to. Default: ``None``. 

167 :returns: The cross. 

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

169 """ 

170 cross = cls( 

171 element.attrib["name"], 

172 element.attrib["key"], 

173 CrossOptions.Parse(element.find("{*}options")), 

174 [(expressionElement.text or "").strip() for expressionElement in element.iterfind("{*}crossExpr")], 

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

176 cls._ParseUserAttributes(element), 

177 parent=parent 

178 ) 

179 

180 for binElement in element.iterfind("{*}crossBin"): 

181 CrossBin.Parse(binElement, parent=cross) 

182 

183 return cross 

184 

185 @readonly 

186 def Parent(self) -> Nullable[CovergroupInstance]: 

187 """ 

188 Read-only property to access the covergroup instance the cross belongs to (:attr:`_parent`). 

189 

190 :returns: The covergroup instance; ``None``, if the cross belongs to none. 

191 """ 

192 return self._parent 

193 

194 @readonly 

195 def Name(self) -> str: 

196 """ 

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

198 

199 :returns: The name, as declared. 

200 """ 

201 return self._name 

202 

203 @readonly 

204 def Key(self) -> str: 

205 """ 

206 Read-only property to access the UCIS key of the cross (:attr:`_key`). 

207 

208 :returns: The key, as stated. 

209 """ 

210 return self._key 

211 

212 @readonly 

213 def Options(self) -> CrossOptions: 

214 """ 

215 Read-only property to access the options of the cross (:attr:`_options`). 

216 

217 :returns: The options. 

218 """ 

219 return self._options 

220 

221 @readonly 

222 def Alias(self) -> Nullable[str]: 

223 """ 

224 Read-only property to access the alias of the cross' name (:attr:`_alias`). 

225 

226 :returns: The alias; ``None``, if the report states none. 

227 """ 

228 return self._alias 

229 

230 @readonly 

231 def Expressions(self) -> list[str]: 

232 """ 

233 Read-only property to access the crossed expressions (:attr:`_expressions`). 

234 

235 :returns: The expressions, e.g. coverpoints' names, in the order the report lists them. 

236 """ 

237 return self._expressions 

238 

239 @readonly 

240 def Bins(self) -> list[CrossBin]: 

241 """ 

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

243 

244 :returns: The bins, in the order the report lists them. 

245 """ 

246 return self._bins 

247 

248 

249@export 

250class CrossBin(BinBase): 

251 """ 

252 A ``<crossBin>`` of a cross: the indices of the crossed coverpoints' bins it combines, and how often the combination 

253 was hit. 

254 """ 

255 

256 _parent: Nullable[Cross] #: The cross the bin belongs to. 

257 _indices: list[int] #: The indices of the crossed coverpoints' bins, one per coverpoint. 

258 _contents: BinContents #: How often the combination was hit. 

259 

260 def __init__( 

261 self, 

262 indices: Iterable[int], 

263 contents: BinContents, 

264 binType: str = "default", 

265 alias: Nullable[str] = None, 

266 name: Nullable[str] = None, 

267 key: Nullable[str] = None, 

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

269 *, 

270 parent: Nullable[Cross] = None 

271 ) -> None: 

272 """ 

273 Initialize the cross bin, and append it to the bins of its cross. 

274 

275 :param indices: The indices of the crossed coverpoints' bins, one per coverpoint. 

276 :param contents: How often the combination was hit. 

277 :param binType: Optional, kind of the bin, e.g. ``default``, ``ignore`` or ``illegal``. Default: 

278 ``"default"``. 

279 :param alias: Optional, an alias of the bin's name. Default: ``None``. 

280 :param name: Optional, name of the bin, as pyucis writes it. Default: ``None``. 

281 :param key: Optional, UCIS key of the bin, as pyucis writes it. Default: ``None``. 

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

283 :param parent: Optional, the cross the bin belongs to. Default: ``None``. 

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

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

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

287 :raises ValueError: If parameter ``binType`` is ``None``. 

288 :raises TypeError: If parameter ``binType`` isn't of type :class:`str`. 

289 :raises TypeError: If parameter ``alias``, ``name`` or ``key`` isn't of type :class:`str`. 

290 :raises ValueError: If parameter ``contents`` is ``None``. 

291 :raises TypeError: If parameter ``contents`` isn't of type 

292 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Bins.BinContents`. 

293 :raises TypeError: If parameter ``parent`` isn't of type :class:`Cross`. 

294 :raises ValueError: If parameter ``indices`` is ``None``. 

295 :raises TypeError: If parameter ``indices`` isn't iterable. 

296 :raises TypeError: If parameter ``indices`` contains an element not of type :class:`int`. 

297 :raises ValueError: If parameter ``indices`` is empty. 

298 """ 

299 super().__init__(binType, alias, name, key, userAttributes) 

300 

301 if contents is None: 301 ↛ 302line 301 didn't jump to line 302 because the condition on line 301 was never true

302 raise ValueError("Parameter 'contents' is None.") 

303 elif not isinstance(contents, BinContents): 303 ↛ 304line 303 didn't jump to line 304 because the condition on line 303 was never true

304 ex = TypeError("Parameter 'contents' is not of type 'BinContents'.") 

305 ex.add_note(f"Got type '{getFullyQualifiedName(contents)}'.") 

306 raise ex 

307 

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

309 ex = TypeError("Parameter 'parent' is not of type 'Cross'.") 

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

311 raise ex 

312 

313 self._parent = parent 

314 self._indices = [] 

315 self._contents = contents 

316 

317 if indices is None: 317 ↛ 318line 317 didn't jump to line 318 because the condition on line 317 was never true

318 raise ValueError("Parameter 'indices' is None.") 

319 elif not isinstance(indices, Iterable): 319 ↛ 320line 319 didn't jump to line 320 because the condition on line 319 was never true

320 ex = TypeError("Parameter 'indices' is not iterable.") 

321 ex.add_note(f"Got type '{getFullyQualifiedName(indices)}'.") 

322 raise ex 

323 

324 for index in indices: 

325 if not isinstance(index, int) or isinstance(index, bool): 325 ↛ 326line 325 didn't jump to line 326 because the condition on line 325 was never true

326 ex = TypeError("Parameter 'indices' contains an element not of type 'int'.") 

327 ex.add_note(f"Got type '{getFullyQualifiedName(index)}'.") 

328 raise ex 

329 

330 self._indices.append(index) 

331 

332 if len(self._indices) == 0: 

333 raise ValueError("Parameter 'indices' is empty.") 

334 

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

336 parent._bins.append(self) 

337 

338 @classmethod 

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

340 """ 

341 Parse a cross bin from its ``<crossBin>`` element. 

342 

343 :param element: The ``<crossBin>`` element. 

344 :param parent: Optional, the cross the bin belongs to. Default: ``None``. 

345 :returns: The cross bin. 

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

347 """ 

348 return cls( 

349 [int(indexElement.text) for indexElement in element.iterfind("{*}index")], 

350 BinContents.Parse(element.find("{*}contents")), 

351 element.attrib.get("type", "default"), 

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

353 element.attrib.get("name"), 

354 element.attrib.get("key"), 

355 cls._ParseUserAttributes(element), 

356 parent=parent 

357 ) 

358 

359 @readonly 

360 def Parent(self) -> Nullable[Cross]: 

361 """ 

362 Read-only property to access the cross the bin belongs to (:attr:`_parent`). 

363 

364 :returns: The cross; ``None``, if the bin belongs to none. 

365 """ 

366 return self._parent 

367 

368 @readonly 

369 def Indices(self) -> list[int]: 

370 """ 

371 Read-only property to access the indices of the crossed coverpoints' bins (:attr:`_indices`). 

372 

373 :returns: The indices, one per crossed coverpoint. 

374 """ 

375 return self._indices 

376 

377 @readonly 

378 def Contents(self) -> BinContents: 

379 """ 

380 Read-only property to access how often the combination was hit (:attr:`_contents`). 

381 

382 :returns: The contents: the count and the tests. 

383 """ 

384 return self._contents