Coverage for pyEDAA/Reports/CodeCoverage/AldecUCDB.py: 89%

748 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# Artur Porebski (Aldec Inc.) # 

12# Michal Pacula (Aldec Inc.) # 

13# # 

14# License: # 

15# ==================================================================================================================== # 

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

17# # 

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

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

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

21# # 

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

23# # 

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

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

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

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

28# limitations under the License. # 

29# # 

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

31# ==================================================================================================================== # 

32# 

33""" 

34Aldec's UCDB XML code coverage format: a model of the format, read from a report and converted to the common model. 

35 

36Aldec's simulators Riviera-PRO and Active-HDL keep code coverage in a coverage database (ACDB), which the tool 

37``acdb2xml`` exports as XML: ``acdb2xml -i aggregate.acdb -o ucdb.xml``. The XML names its namespace ``www.aldec.com`` 

38and its root ``<ux:ucdb>``; it is a dump of the database's UCIS data model - scopes, coverage items (bins) and history 

39nodes -, not the XML interchange format of the UCIS standard. A report states no format version, only the tool's name 

40and version, e.g. ``Riviera-PRO 2022.04``; it is validated against :file:`Aldec-UCDB.xsd` (:data:`SCHEMA`), 

41reverse-engineered from reports of Riviera-PRO. 

42 

43The format's model keeps what the report states: a :class:`Document` holds its attributes, :class:`HistoryNode` 

44elements - nested, a merge holds the tests it merged -, :class:`Scope` elements - nested, e.g. design units, their 

45instances, processes and branching statements - with their :class:`Bin` elements, and :class:`Command` elements. Each 

46element's constructor takes typed values, so the model can be built by hand: an element below the report names its 

47parent with the keyword parameter ``parent`` and is added to it. Its class method ``Parse`` reads the element's XML 

48element. 

49 

50* The scopes are the design units and their instances; the bins are the coverage items - e.g. statements and 

51 branches - with their counts, and where they are in the sources (:class:`SourceLocation`). 

52* Every element may state attributes (:class:`AttributesMixin`); the named elements derive from :class:`Base`. 

53* The kinds of scopes (:class:`ScopeType`), bins (:class:`CoverType`) and source languages (:class:`Language`) are 

54 named as the UCIS standard names them in its C API, without the prefix ``UCIS_``, e.g. ``DU_MODULE`` for 

55 ``UCIS_DU_MODULE``. 

56 

57:meth:`Document.ToCoverageSummary` converts the statement coverage to the common model of 

58:mod:`pyEDAA.Reports.CodeCoverage`: 

59 

60* The statements are the statement bins (``STMTBIN``) of the instances: the bins of the scopes below each top-level 

61 scope, which isn't a design unit. A statement is identified by its file, its line and its index in the line 

62 (attribute ``#SINDEX#``); with ``mergeInstances``, the statements of all instances of a design unit are merged into 

63 one, which ran as often as in all instances together (:meth:`Report.IterateStatements`). 

64* A file's path is the one a statement bin's source location states, relative to the directory the tool ran in; these 

65 directories are the summary's source directories. 

66* A line ran as often as its least often run statement: it is covered, if all its statements ran. A line, whose 

67 statements are all excluded, is excluded; an excluded statement doesn't count for its line. 

68* The branches, the blocks and the other kinds of coverage aren't converted, and the format names no units. 

69 

70.. rubric:: Example 

71 

72.. code-block:: Python 

73 

74 from pathlib import Path 

75 from pyEDAA.Reports.CodeCoverage.AldecUCDB import Document 

76 

77 report = Document(Path("ucdb.xml"), analyzeAndConvert=True) 

78 summary = report.ToCoverageSummary(mergeInstances=True) 

79 for file in summary.IterateFiles(): 

80 print(f"{file.Path}: {file.LineCoverage:.1%}") 

81""" 

82from __future__ import annotations 

83 

84from enum import IntEnum, IntFlag 

85from io import BytesIO 

86from pathlib import Path 

87from typing import Generator, Generic, Iterable, Mapping, Optional as Nullable, Self, TypeVar 

88 

89from lxml.etree import XMLParser, XMLSchema, XMLSchemaParseError, XMLSyntaxError 

90from lxml.etree import _Element, _ElementTree, parse 

91from pyTooling.Common import getFullyQualifiedName, getResourceFile, StringEnum 

92from pyTooling.Decorators import export, readonly 

93from pyTooling.Exceptions import ToolingException 

94from pyTooling.MetaClasses import ExtendedType, abstractclass 

95from pyTooling.Stopwatch import Stopwatch 

96from pyTooling.Versioning import CalendarVersion 

97 

98from pyEDAA.Reports import Resources 

99from pyEDAA.Reports.CodeCoverage import CodeCoverageError, CoverageSummary, Document as cc_Document 

100from pyEDAA.Reports.CodeCoverage import Line as cc_Line, LineCoverageStatus 

101 

102 

103__all__ = ["SCHEMA", "NAMESPACE", "NAMESPACES", "HistoryNodeParentType", "ScopeParentType"] 

104 

105SCHEMA = "Aldec-UCDB.xsd" #: The XML schema a report is validated against. 

106 

107NAMESPACE = "www.aldec.com" #: The XML namespace of the format's elements. 

108NAMESPACES = {"ux": NAMESPACE} #: The XML namespaces of the format by their prefix, for finding elements. 

109 

110HistoryNodeParentType = TypeVar("HistoryNodeParentType", bound="Report | HistoryNode") 

111"""A type variable for the parent of a :class:`HistoryNode`: a :class:`Report` 

112or a :class:`HistoryNode`.""" 

113 

114ScopeParentType = TypeVar("ScopeParentType", bound="Report | Scope") 

115"""A type variable for the parent of a :class:`Scope`: a :class:`Report` or a 

116:class:`Scope`.""" 

117 

118 

119@export 

120class ScopeType(StringEnum): 

121 """ 

122 Kind of a ``<ux:scope>``, as its ``type`` states: a design unit, an instance of one, or a scope of a coverage kind. 

123 """ 

124 

125 Toggle = "TOGGLE" #: Toggle coverage of a signal. 

126 Branch = "BRANCH" #: Branch coverage of a branching statement. 

127 Expression = "EXPR" #: Expression coverage. 

128 Condition = "COND" #: Condition coverage. 

129 Instance = "INSTANCE" #: An instance of a design unit, e.g. of a Verilog module. 

130 Process = "PROCESS" #: A process, e.g. a Verilog ``always`` block. 

131 Block = "BLOCK" #: A block, e.g. a VHDL ``block`` statement. 

132 Function = "FUNCTION" #: A function. 

133 ForkJoin = "FORKJOIN" #: A Verilog ``fork ... join`` block. 

134 Generate = "GENERATE" #: A generate block. 

135 Generic = "GENERIC" #: A scope of a generic kind. 

136 Class = "CLASS" #: A SystemVerilog class. 

137 CoverGroup = "COVERGROUP" #: A SystemVerilog covergroup type. 

138 CoverInstance = "COVERINSTANCE" #: An instance of a SystemVerilog covergroup. 

139 CoverPoint = "COVERPOINT" #: A SystemVerilog coverpoint. 

140 Cross = "CROSS" #: A SystemVerilog cross coverage. 

141 Cover = "COVER" #: A cover directive. 

142 Assert = "ASSERT" #: An assertion. 

143 Program = "PROGRAM" #: An instance of a SystemVerilog program. 

144 Package = "PACKAGE" #: An instance of a package. 

145 Task = "TASK" #: A Verilog task. 

146 Interface = "INTERFACE" #: An instance of a SystemVerilog interface. 

147 FSM = "FSM" #: A finite state machine. 

148 DesignUnitModule = "DU_MODULE" #: Design unit: a Verilog module. 

149 DesignUnitArchitecture = "DU_ARCH" #: Design unit: a VHDL architecture. 

150 DesignUnitPackage = "DU_PACKAGE" #: Design unit: a package. 

151 DesignUnitProgram = "DU_PROGRAM" #: Design unit: a SystemVerilog program. 

152 DesignUnitInterface = "DU_INTERFACE" #: Design unit: a SystemVerilog interface. 

153 FSMStates = "FSM_STATES" #: The states of a finite state machine. 

154 FSMTransitions = "FSM_TRANS" #: The transitions of a finite state machine. 

155 CoverBlock = "COVBLOCK" #: A block of coverage items. 

156 CoverGroupBinScope = "CVGBINSCOPE" #: A scope of covergroup bins. 

157 IllegalBinScope = "ILLEGALBINSCOPE" #: A scope of illegal covergroup bins. 

158 IgnoreBinScope = "IGNOREBINSCOPE" #: A scope of ignored covergroup bins. 

159 

160 

161@export 

162class CoverType(StringEnum): 

163 """ 

164 Kind of a ``<ux:bin>``, as its ``type`` states. 

165 

166 A kind Aldec's export has no name for is stated by its value in hexadecimal, as the UCIS C API defines it. 

167 """ 

168 

169 CoverGroupBin = "CVGBIN" #: A bin of a SystemVerilog covergroup. 

170 CoverBin = "COVERBIN" #: A cover directive's pass. 

171 AssertBin = "ASSERTBIN" #: An assertion's failure. 

172 StatementBin = "STMTBIN" #: A statement. 

173 BranchBin = "BRANCHBIN" #: A branch of a branching statement. 

174 ExpressionBin = "EXPRBIN" #: A row of an expression's truth table. 

175 ConditionBin = "CONDBIN" #: A row of a condition's truth table. 

176 ToggleBin = "TOGGLEBIN" #: A transition of a signal's toggle coverage. 

177 PassBin = "PASSBIN" #: An assertion's pass. 

178 FSMBin = "FSMBIN" #: A state or transition of a finite state machine. 

179 UserBin = "USERBIN" #: A bin defined by the user. 

180 Count = "COUNT" #: A counter. 

181 FailBin = "FAILBIN" #: A cover directive's failure. 

182 VacuousBin = "VACUOUSBIN" #: An assertion's vacuous pass. 

183 DisabledBin = "DISABLEDBIN" #: An assertion's or cover directive's disabled attempt. 

184 AttemptBin = "ATTEMPTBIN" #: An assertion's or cover directive's attempt. 

185 ActiveBin = "ACTIVEBIN" #: An assertion's or cover directive's active thread. 

186 IgnoreBin = "IGNOREBIN" #: An ignored bin of a SystemVerilog covergroup. 

187 IllegalBin = "ILLEGALBIN" #: An illegal bin of a SystemVerilog covergroup. 

188 DefaultBin = "DEFAULTBIN" #: The default bin of a SystemVerilog covergroup. 

189 PeakActiveBin = "PEAKACTIVEBIN" #: The peak number of an assertion's or cover directive's active threads. 

190 BlockBin = "1000000" #: A block of statements, stated by its value ``UCIS_BLOCKBIN``: ``0x1000000``. 

191 

192 

193@export 

194class Language(StringEnum): 

195 """ 

196 Language of the source of a ``<ux:scope>``, as its ``lang`` states. 

197 """ 

198 

199 VHDL = "VHDL" #: VHDL. 

200 Verilog = "VLOG" #: Verilog. 

201 SystemVerilog = "SV" #: SystemVerilog. 

202 SystemC = "SYSTEMC" #: SystemC. 

203 PSLInVHDL = "PSL_VHDL" #: PSL embedded in VHDL. 

204 PSLInVerilog = "PSL_VLOG" #: PSL embedded in Verilog. 

205 PSLInSystemVerilog = "PSL_SV" #: PSL embedded in SystemVerilog. 

206 PSLInSystemC = "PSL_SYSTEMC" #: PSL embedded in SystemC. 

207 E = "E" #: The verification language *e*. 

208 Vera = "VERA" #: The verification language OpenVera. 

209 NoLanguage = "NONE" #: No source language. 

210 Other = "OTHER" #: Another source language. 

211 

212 

213# A class with a property named like a class - ``Language`` - can't name that class in the annotation of a field: the 

214# class body's namespace, where annotations are evaluated, binds the name to the property. 

215_Language = Language 

216 

217 

218@export 

219class HistoryNodeKind(IntEnum): 

220 """ 

221 Kind of a ``<ux:hnode>``, as its ``type`` states. 

222 """ 

223 

224 Test = 1 #: A test: a simulation run, which wrote a coverage database. 

225 Merge = 2 #: A merge of coverage databases. 

226 

227 

228@export 

229class BinFlags(IntFlag): 

230 """ 

231 Flags of a ``<ux:bin>``, as its ``flags`` state in hexadecimal; a flag not named here is kept. 

232 """ 

233 

234 Is32Bit = 0x00000001 #: The count is a 32-bit number. 

235 Is64Bit = 0x00000002 #: The count is a 64-bit number. 

236 IsVector = 0x00000004 #: The bin is a vector of counts. 

237 HasGoal = 0x00000008 #: The bin states a goal. 

238 HasWeight = 0x00000010 #: The bin states a weight. 

239 ExcludePragma = 0x00000020 #: Excluded by a pragma in the source. 

240 ExcludeFile = 0x00000040 #: Excluded by an exclusion file. 

241 ExcludeInstance = 0x00000080 #: Excluded in this instance. 

242 ExcludeAuto = 0x00000100 #: Excluded automatically by the tool. 

243 HasLimit = 0x00000400 #: The count saturates at a limit. 

244 HasCount = 0x00000800 #: The bin states a count. 

245 IsCovered = 0x00001000 #: The bin reached its goal. 

246 

247 Excluded = 0x000001E0 #: Any of the exclusion flags. 

248 

249 

250@export 

251class AttributesMixin(metaclass=ExtendedType, mixin=True): 

252 """ 

253 A mixin-class adding the attributes an element states: its ``<ux:attr>`` elements, each a key and a typed value. 

254 

255 An attribute's type is its value's type: ``int`` is :class:`int`, ``double`` is :class:`float`, ``str`` is 

256 :class:`str`. 

257 """ 

258 

259 _attributes: dict[str, int | float | str] #: The attributes, by key. 

260 

261 def __init__(self, attributes: Nullable[Mapping[str, int | float | str]] = None) -> None: 

262 """ 

263 Initialize the attributes. 

264 

265 :param attributes: Optional, the attributes by key, e.g. ``{"#SINDEX#": 1}``. Default: ``None``. 

266 :raises TypeError: If parameter ``attributes`` isn't a mapping. 

267 :raises TypeError: If parameter ``attributes`` contains a key not of type :class:`str`. 

268 :raises ValueError: If parameter ``attributes`` contains an empty key. 

269 :raises TypeError: If parameter ``attributes`` contains a value not of type :class:`int`, :class:`float` or 

270 :class:`str`. 

271 """ 

272 self._attributes = {} 

273 

274 if attributes is not None: 

275 if not isinstance(attributes, Mapping): 

276 ex = TypeError("Parameter 'attributes' is not a mapping.") 

277 ex.add_note(f"Got type '{getFullyQualifiedName(attributes)}'.") 

278 raise ex 

279 

280 for key, value in attributes.items(): 

281 if not isinstance(key, str): 

282 ex = TypeError("Parameter 'attributes' contains a key not of type 'str'.") 

283 ex.add_note(f"Got type '{getFullyQualifiedName(key)}'.") 

284 raise ex 

285 elif key == "": 

286 raise ValueError("Parameter 'attributes' contains an empty key.") 

287 

288 if not isinstance(value, (int, float, str)): 

289 ex = TypeError("Parameter 'attributes' contains a value not of type 'int', 'float' or 'str'.") 

290 ex.add_note(f"Got type '{getFullyQualifiedName(value)}' for key '{key}'.") 

291 raise ex 

292 

293 self._attributes[key] = value 

294 

295 @staticmethod 

296 def _ParseAttributes(element: _Element) -> dict[str, int | float | str]: 

297 """ 

298 Parse the attributes an element states, from its ``<ux:attr>`` elements. 

299 

300 :param element: The element stating the attributes. 

301 :returns: The attributes by key, each value of the type the attribute states. 

302 :raises CodeCoverageError: If an attribute's value isn't of the type the attribute states. 

303 """ 

304 attributes: dict[str, int | float | str] = {} 

305 for attributeElement in element.iterfind("ux:attr", NAMESPACES): 

306 key = attributeElement.attrib["key"] 

307 attributeType = attributeElement.attrib["type"] 

308 text = attributeElement.text if attributeElement.text is not None else "" 

309 try: 

310 if attributeType == "int": 

311 attributes[key] = int(text) 

312 elif attributeType == "double": 

313 attributes[key] = float(text) 

314 else: 

315 attributes[key] = text 

316 except ValueError as cause: 

317 ex = CodeCoverageError(f"UCDB attribute '{key}' states a value not of type '{attributeType}'.") 

318 ex.add_note(f"Got value '{text}'.") 

319 raise ex from cause 

320 

321 return attributes 

322 

323 @readonly 

324 def Attributes(self) -> dict[str, int | float | str]: 

325 """ 

326 Read-only property to access the attributes (:attr:`_attributes`). 

327 

328 :returns: The attributes by key, e.g. ``#SINDEX#`` - a statement's index in its line - or ``UCIS:tool``. 

329 """ 

330 return self._attributes 

331 

332 

333@export 

334@abstractclass 

335class Base(metaclass=ExtendedType, slots=True): 

336 """ 

337 Base-class of the named elements below the report. 

338 

339 Scopes and bins are named elements. 

340 """ 

341 

342 _name: str #: Name of the element. 

343 

344 def __init__(self, name: str) -> None: 

345 """ 

346 Initialize the name of the element. 

347 

348 :param name: Name of the element. 

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

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

351 """ 

352 if name is None: 

353 raise ValueError("Parameter 'name' is None.") 

354 elif not isinstance(name, str): 

355 ex = TypeError("Parameter 'name' is not of type 'str'.") 

356 ex.add_note(f"Got type '{getFullyQualifiedName(name)}'.") 

357 raise ex 

358 

359 self._name = name 

360 

361 @readonly 

362 def Name(self) -> str: 

363 """ 

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

365 

366 :returns: The name. 

367 """ 

368 return self._name 

369 

370 

371@export 

372class SourceLocation(metaclass=ExtendedType, slots=True): 

373 """ 

374 A ``<ux:src>`` of a scope or bin: the source file, the directory its name is relative to, the line and the token. 

375 

376 It is a value, as a path is: it names no parent. 

377 """ 

378 

379 _file: Path #: Path of the source file, relative to :attr:`_workDirectory`, if not absolute. 

380 _workDirectory: Path #: The directory the tool ran in. 

381 _lineNumber: int #: Line number, counted from 1; ``0`` for a scope without a line. 

382 _token: int #: Number of the token in the line. 

383 

384 def __init__(self, file: Path, workDirectory: Path, lineNumber: int, token: int) -> None: 

385 """ 

386 Initialize the source location. 

387 

388 :param file: Path of the source file, e.g. ``src/counter.sv``. 

389 :param workDirectory: The directory the tool ran in. 

390 :param lineNumber: Line number, counted from 1; ``0`` for a scope without a line. 

391 :param token: Number of the token in the line. 

392 :raises ValueError: If parameter ``file`` is ``None``. 

393 :raises TypeError: If parameter ``file`` isn't of type :class:`~pathlib.Path`. 

394 :raises ValueError: If parameter ``workDirectory`` is ``None``. 

395 :raises TypeError: If parameter ``workDirectory`` isn't of type :class:`~pathlib.Path`. 

396 :raises ValueError: If parameter ``lineNumber`` is ``None``. 

397 :raises TypeError: If parameter ``lineNumber`` isn't of type :class:`int`. 

398 :raises ValueError: If parameter ``lineNumber`` is negative. 

399 :raises ValueError: If parameter ``token`` is ``None``. 

400 :raises TypeError: If parameter ``token`` isn't of type :class:`int`. 

401 :raises ValueError: If parameter ``token`` is negative. 

402 """ 

403 if file is None: 

404 raise ValueError("Parameter 'file' is None.") 

405 elif not isinstance(file, Path): 

406 ex = TypeError("Parameter 'file' is not of type 'Path'.") 

407 ex.add_note(f"Got type '{getFullyQualifiedName(file)}'.") 

408 raise ex 

409 

410 if workDirectory is None: 410 ↛ 411line 410 didn't jump to line 411 because the condition on line 410 was never true

411 raise ValueError("Parameter 'workDirectory' is None.") 

412 elif not isinstance(workDirectory, Path): 

413 ex = TypeError("Parameter 'workDirectory' is not of type 'Path'.") 

414 ex.add_note(f"Got type '{getFullyQualifiedName(workDirectory)}'.") 

415 raise ex 

416 

417 if lineNumber is None: 417 ↛ 418line 417 didn't jump to line 418 because the condition on line 417 was never true

418 raise ValueError("Parameter 'lineNumber' is None.") 

419 elif not isinstance(lineNumber, int): 419 ↛ 420line 419 didn't jump to line 420 because the condition on line 419 was never true

420 ex = TypeError("Parameter 'lineNumber' is not of type 'int'.") 

421 ex.add_note(f"Got type '{getFullyQualifiedName(lineNumber)}'.") 

422 raise ex 

423 elif lineNumber < 0: 

424 ex = ValueError("Parameter 'lineNumber' is negative.") 

425 ex.add_note(f"Got value '{lineNumber}'.") 

426 raise ex 

427 

428 if token is None: 

429 raise ValueError("Parameter 'token' is None.") 

430 elif not isinstance(token, int): 430 ↛ 431line 430 didn't jump to line 431 because the condition on line 430 was never true

431 ex = TypeError("Parameter 'token' is not of type 'int'.") 

432 ex.add_note(f"Got type '{getFullyQualifiedName(token)}'.") 

433 raise ex 

434 elif token < 0: 434 ↛ 435line 434 didn't jump to line 435 because the condition on line 434 was never true

435 ex = ValueError("Parameter 'token' is negative.") 

436 ex.add_note(f"Got value '{token}'.") 

437 raise ex 

438 

439 self._file = file 

440 self._workDirectory = workDirectory 

441 self._lineNumber = lineNumber 

442 self._token = token 

443 

444 @classmethod 

445 def Parse(cls, element: _Element) -> Self: 

446 """ 

447 Parse a source location from its ``<ux:src>`` element. 

448 

449 :param element: The ``<ux:src>`` element. 

450 :returns: The source location. 

451 """ 

452 return cls( 

453 Path(element.attrib["file"]), 

454 Path(element.attrib["workdir"]), 

455 int(element.attrib["line"]), 

456 int(element.attrib["token"]) 

457 ) 

458 

459 @readonly 

460 def File(self) -> Path: 

461 """ 

462 Read-only property to access the path of the source file (:attr:`_file`). 

463 

464 :returns: The path, relative to :attr:`WorkDirectory`, if not absolute. 

465 """ 

466 return self._file 

467 

468 @readonly 

469 def WorkDirectory(self) -> Path: 

470 """ 

471 Read-only property to access the directory the tool ran in (:attr:`_workDirectory`). 

472 

473 It is a path on the machine the coverage was measured on, so it may not exist where the report is read. 

474 

475 :returns: The directory. 

476 """ 

477 return self._workDirectory 

478 

479 @readonly 

480 def LineNumber(self) -> int: 

481 """ 

482 Read-only property to access the line number (:attr:`_lineNumber`). 

483 

484 :returns: The line number, counted from 1; ``0`` for a scope without a line. 

485 """ 

486 return self._lineNumber 

487 

488 @readonly 

489 def Token(self) -> int: 

490 """ 

491 Read-only property to access the number of the token in the line (:attr:`_token`). 

492 

493 :returns: The token's number. 

494 """ 

495 return self._token 

496 

497 

498@export 

499class Bin(Base, AttributesMixin): 

500 """ 

501 A ``<ux:bin>`` of a scope: a coverage item, how often it was hit, its flags, and where it is in the sources. 

502 

503 Statements and branches are coverage items. 

504 """ 

505 

506 _parent: Nullable[Scope] #: The scope the bin belongs to. 

507 _type: CoverType #: Kind of the bin. 

508 _flags: BinFlags #: Flags of the bin, e.g. whether it is excluded. 

509 _count: int #: How often the coverage item was hit. 

510 _source: SourceLocation #: Where the coverage item is in the sources. 

511 

512 def __init__( 

513 self, 

514 name: str, 

515 coverType: CoverType, 

516 flags: BinFlags, 

517 count: int, 

518 source: SourceLocation, 

519 attributes: Nullable[Mapping[str, int | float | str]] = None, 

520 *, 

521 parent: Nullable[Scope] = None 

522 ) -> None: 

523 """ 

524 Initialize the bin, and append it to the bins of its scope. 

525 

526 :param name: Name of the bin, e.g. ``true_branch``; empty for a statement. 

527 :param coverType: Kind of the bin. 

528 :param flags: Flags of the bin. 

529 :param count: How often the coverage item was hit. 

530 :param source: Where the coverage item is in the sources. 

531 :param attributes: Optional, the attributes by key, e.g. ``{"#SINDEX#": 1}``. Default: ``None``. 

532 :param parent: Optional, the scope the bin belongs to; the bin is appended to its bins. Default: ``None``. 

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

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

535 :raises TypeError: If parameter ``attributes`` isn't a mapping. 

536 :raises TypeError: If parameter ``attributes`` contains a key not of type :class:`str`. 

537 :raises ValueError: If parameter ``attributes`` contains an empty key. 

538 :raises TypeError: If parameter ``attributes`` contains a value not of type :class:`int`, :class:`float` or 

539 :class:`str`. 

540 :raises ValueError: If parameter ``coverType`` is ``None``. 

541 :raises TypeError: If parameter ``coverType`` isn't of type 

542 :class:`CoverType`. 

543 :raises ValueError: If parameter ``flags`` is ``None``. 

544 :raises TypeError: If parameter ``flags`` isn't of type :class:`BinFlags`. 

545 :raises ValueError: If parameter ``count`` is ``None``. 

546 :raises TypeError: If parameter ``count`` isn't of type :class:`int`. 

547 :raises ValueError: If parameter ``count`` is negative. 

548 :raises ValueError: If parameter ``source`` is ``None``. 

549 :raises TypeError: If parameter ``source`` isn't of type :class:`SourceLocation`. 

550 :raises TypeError: If parameter ``parent`` isn't of type :class:`Scope`. 

551 """ 

552 super().__init__(name) 

553 AttributesMixin.__init__(self, attributes) 

554 

555 if coverType is None: 555 ↛ 556line 555 didn't jump to line 556 because the condition on line 555 was never true

556 raise ValueError("Parameter 'coverType' is None.") 

557 elif not isinstance(coverType, CoverType): 

558 ex = TypeError("Parameter 'coverType' is not of type 'CoverType'.") 

559 ex.add_note(f"Got type '{getFullyQualifiedName(coverType)}'.") 

560 raise ex 

561 

562 if flags is None: 562 ↛ 563line 562 didn't jump to line 563 because the condition on line 562 was never true

563 raise ValueError("Parameter 'flags' is None.") 

564 elif not isinstance(flags, BinFlags): 

565 ex = TypeError("Parameter 'flags' is not of type 'BinFlags'.") 

566 ex.add_note(f"Got type '{getFullyQualifiedName(flags)}'.") 

567 raise ex 

568 

569 if count is None: 569 ↛ 570line 569 didn't jump to line 570 because the condition on line 569 was never true

570 raise ValueError("Parameter 'count' is None.") 

571 elif not isinstance(count, int): 571 ↛ 572line 571 didn't jump to line 572 because the condition on line 571 was never true

572 ex = TypeError("Parameter 'count' is not of type 'int'.") 

573 ex.add_note(f"Got type '{getFullyQualifiedName(count)}'.") 

574 raise ex 

575 elif count < 0: 

576 ex = ValueError("Parameter 'count' is negative.") 

577 ex.add_note(f"Got value '{count}'.") 

578 raise ex 

579 

580 if source is None: 580 ↛ 581line 580 didn't jump to line 581 because the condition on line 580 was never true

581 raise ValueError("Parameter 'source' is None.") 

582 elif not isinstance(source, SourceLocation): 

583 ex = TypeError("Parameter 'source' is not of type 'SourceLocation'.") 

584 ex.add_note(f"Got type '{getFullyQualifiedName(source)}'.") 

585 raise ex 

586 

587 if parent is not None and not isinstance(parent, Scope): 

588 ex = TypeError("Parameter 'parent' is not of type 'Scope'.") 

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

590 raise ex 

591 

592 self._parent = parent 

593 self._type = coverType 

594 self._flags = flags 

595 self._count = count 

596 self._source = source 

597 

598 if parent is not None: 

599 parent._bins.append(self) 

600 

601 @classmethod 

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

603 """ 

604 Parse a bin, its count, source location and attributes from its ``<ux:bin>`` element. 

605 

606 :param element: The ``<ux:bin>`` element. 

607 :param parent: Optional, the scope the bin belongs to. Default: ``None``. 

608 :returns: The bin. 

609 :raises CodeCoverageError: If an attribute's value isn't of the type the attribute states. 

610 """ 

611 return cls( 

612 element.attrib["name"], 

613 CoverType(element.attrib["type"]), 

614 BinFlags(int(element.attrib["flags"], 16)), 

615 int(element.find("ux:count", NAMESPACES).text), 

616 SourceLocation.Parse(element.find("ux:src", NAMESPACES)), 

617 cls._ParseAttributes(element), 

618 parent=parent 

619 ) 

620 

621 @readonly 

622 def Parent(self) -> Nullable[Scope]: 

623 """ 

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

625 

626 :returns: The scope; ``None`` if the bin belongs to no scope. 

627 """ 

628 return self._parent 

629 

630 @readonly 

631 def Type(self) -> CoverType: 

632 """ 

633 Read-only property to access the kind of the bin (:attr:`_type`). 

634 

635 :returns: The kind, e.g. a statement. 

636 """ 

637 return self._type 

638 

639 @readonly 

640 def Flags(self) -> BinFlags: 

641 """ 

642 Read-only property to access the flags of the bin (:attr:`_flags`). 

643 

644 :returns: The flags. 

645 """ 

646 return self._flags 

647 

648 @readonly 

649 def IsExcluded(self) -> bool: 

650 """ 

651 Read-only property to return whether the bin is excluded from the measurement. 

652 

653 :returns: ``True``, if :attr:`Flags` has one of the exclusion flags (:attr:`BinFlags.Excluded`). 

654 """ 

655 return (self._flags & BinFlags.Excluded) != 0 

656 

657 @readonly 

658 def Count(self) -> int: 

659 """ 

660 Read-only property to access how often the coverage item was hit (:attr:`_count`). 

661 

662 :returns: The count, e.g. how often a statement ran. 

663 """ 

664 return self._count 

665 

666 @readonly 

667 def Source(self) -> SourceLocation: 

668 """ 

669 Read-only property to access where the coverage item is in the sources (:attr:`_source`). 

670 

671 :returns: The source location. 

672 """ 

673 return self._source 

674 

675 

676@export 

677class Scope(Base, AttributesMixin, Generic[ScopeParentType]): 

678 """ 

679 A ``<ux:scope>`` of the report or of a scope: its bins and the scopes below it. 

680 

681 Design units, their instances, processes and branching statements are scopes. 

682 """ 

683 

684 _parent: Nullable[ScopeParentType] #: The report or scope the scope belongs to. 

685 _type: ScopeType #: Kind of the scope. 

686 _language: _Language #: Language of the scope's source. 

687 _weight: int #: Weight of the scope. 

688 _flags: int #: Flags of the scope. 

689 _source: SourceLocation #: Where the scope is in the sources. 

690 _designUnit: Nullable[str] #: Name of the design unit an instance instantiates. 

691 _scopes: list[Scope[Scope]] #: The scopes below this one. 

692 _bins: list[Bin] #: The bins of this scope. 

693 

694 def __init__( 

695 self, 

696 name: str, 

697 scopeType: ScopeType, 

698 language: _Language, 

699 weight: int, 

700 flags: int, 

701 source: SourceLocation, 

702 designUnit: Nullable[str] = None, 

703 attributes: Nullable[Mapping[str, int | float | str]] = None, 

704 *, 

705 parent: Nullable[ScopeParentType] = None 

706 ) -> None: 

707 """ 

708 Initialize the scope, and append it to the scopes of its report or scope. 

709 

710 Its scopes and bins are added by creating them with this scope as their parent. 

711 

712 :param name: Name of the scope, e.g. ``work.counter`` for a design unit or ``uut`` for an instance. 

713 :param scopeType: Kind of the scope. 

714 :param language: Language of the scope's source. 

715 :param weight: Weight of the scope. 

716 :param flags: Flags of the scope. 

717 :param source: Where the scope is in the sources. 

718 :param designUnit: Optional, name of the design unit an instance instantiates, e.g. ``work.counter``. 

719 Default: ``None``. 

720 :param attributes: Optional, the attributes by key, e.g. ``{"#GOAL#": 100}``. Default: ``None``. 

721 :param parent: Optional, the report or scope the scope belongs to; the scope is appended to its scopes. 

722 Default: ``None``. 

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

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

725 :raises TypeError: If parameter ``attributes`` isn't a mapping. 

726 :raises TypeError: If parameter ``attributes`` contains a key not of type :class:`str`. 

727 :raises ValueError: If parameter ``attributes`` contains an empty key. 

728 :raises TypeError: If parameter ``attributes`` contains a value not of type :class:`int`, :class:`float` or 

729 :class:`str`. 

730 :raises ValueError: If parameter ``name`` is empty. 

731 :raises ValueError: If parameter ``scopeType`` is ``None``. 

732 :raises TypeError: If parameter ``scopeType`` isn't of type 

733 :class:`ScopeType`. 

734 :raises ValueError: If parameter ``language`` is ``None``. 

735 :raises TypeError: If parameter ``language`` isn't of type 

736 :class:`Language`. 

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

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

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

740 :raises ValueError: If parameter ``flags`` is ``None``. 

741 :raises TypeError: If parameter ``flags`` isn't of type :class:`int`. 

742 :raises ValueError: If parameter ``flags`` is negative. 

743 :raises ValueError: If parameter ``source`` is ``None``. 

744 :raises TypeError: If parameter ``source`` isn't of type :class:`SourceLocation`. 

745 :raises TypeError: If parameter ``designUnit`` isn't of type :class:`str`. 

746 :raises ValueError: If parameter ``designUnit`` is empty. 

747 :raises TypeError: If parameter ``parent`` isn't of type :class:`Report` 

748 or :class:`Scope`. 

749 """ 

750 super().__init__(name) 

751 AttributesMixin.__init__(self, attributes) 

752 

753 if name == "": 

754 raise ValueError("Parameter 'name' is empty.") 

755 

756 if scopeType is None: 756 ↛ 757line 756 didn't jump to line 757 because the condition on line 756 was never true

757 raise ValueError("Parameter 'scopeType' is None.") 

758 elif not isinstance(scopeType, ScopeType): 

759 ex = TypeError("Parameter 'scopeType' is not of type 'ScopeType'.") 

760 ex.add_note(f"Got type '{getFullyQualifiedName(scopeType)}'.") 

761 raise ex 

762 

763 if language is None: 

764 raise ValueError("Parameter 'language' is None.") 

765 elif not isinstance(language, Language): 765 ↛ 766line 765 didn't jump to line 766 because the condition on line 765 was never true

766 ex = TypeError("Parameter 'language' is not of type 'Language'.") 

767 ex.add_note(f"Got type '{getFullyQualifiedName(language)}'.") 

768 raise ex 

769 

770 if weight is None: 770 ↛ 771line 770 didn't jump to line 771 because the condition on line 770 was never true

771 raise ValueError("Parameter 'weight' is None.") 

772 elif not isinstance(weight, int): 772 ↛ 773line 772 didn't jump to line 773 because the condition on line 772 was never true

773 ex = TypeError("Parameter 'weight' is not of type 'int'.") 

774 ex.add_note(f"Got type '{getFullyQualifiedName(weight)}'.") 

775 raise ex 

776 elif weight < 0: 

777 ex = ValueError("Parameter 'weight' is negative.") 

778 ex.add_note(f"Got value '{weight}'.") 

779 raise ex 

780 

781 if flags is None: 781 ↛ 782line 781 didn't jump to line 782 because the condition on line 781 was never true

782 raise ValueError("Parameter 'flags' is None.") 

783 elif not isinstance(flags, int): 

784 ex = TypeError("Parameter 'flags' is not of type 'int'.") 

785 ex.add_note(f"Got type '{getFullyQualifiedName(flags)}'.") 

786 raise ex 

787 elif flags < 0: 787 ↛ 788line 787 didn't jump to line 788 because the condition on line 787 was never true

788 ex = ValueError("Parameter 'flags' is negative.") 

789 ex.add_note(f"Got value '{flags}'.") 

790 raise ex 

791 

792 if source is None: 

793 raise ValueError("Parameter 'source' is None.") 

794 elif not isinstance(source, SourceLocation): 794 ↛ 795line 794 didn't jump to line 795 because the condition on line 794 was never true

795 ex = TypeError("Parameter 'source' is not of type 'SourceLocation'.") 

796 ex.add_note(f"Got type '{getFullyQualifiedName(source)}'.") 

797 raise ex 

798 

799 if designUnit is not None: 

800 if not isinstance(designUnit, str): 800 ↛ 801line 800 didn't jump to line 801 because the condition on line 800 was never true

801 ex = TypeError("Parameter 'designUnit' is not of type 'str'.") 

802 ex.add_note(f"Got type '{getFullyQualifiedName(designUnit)}'.") 

803 raise ex 

804 elif designUnit == "": 

805 raise ValueError("Parameter 'designUnit' is empty.") 

806 

807 if parent is not None and not isinstance(parent, (Report, Scope)): 

808 ex = TypeError("Parameter 'parent' is not of type 'Report' or 'Scope'.") 

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

810 raise ex 

811 

812 self._parent = parent 

813 self._type = scopeType 

814 self._language = language 

815 self._weight = weight 

816 self._flags = flags 

817 self._source = source 

818 self._designUnit = designUnit 

819 self._scopes = [] 

820 self._bins = [] 

821 

822 if parent is not None: 

823 parent._scopes.append(self) 

824 

825 @classmethod 

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

827 """ 

828 Parse a scope, its attributes, bins and the scopes below it from its ``<ux:scope>`` element. 

829 

830 :param element: The ``<ux:scope>`` element. 

831 :param parent: Optional, the report or scope the scope belongs to. Default: ``None``. 

832 :returns: The scope. 

833 :raises CodeCoverageError: If an attribute's value isn't of the type the attribute states. 

834 """ 

835 scope = cls( 

836 element.attrib["name"], 

837 ScopeType(element.attrib["type"]), 

838 Language(element.attrib["lang"]), 

839 int(element.attrib["weight"]), 

840 int(element.attrib["flags"], 16), 

841 SourceLocation.Parse(element.find("ux:src", NAMESPACES)), 

842 element.attrib.get("du"), 

843 cls._ParseAttributes(element), 

844 parent=parent 

845 ) 

846 

847 for binElement in element.iterfind("ux:bin", NAMESPACES): 

848 Bin.Parse(binElement, parent=scope) 

849 

850 for scopeElement in element.iterfind("ux:scope", NAMESPACES): 

851 Scope.Parse(scopeElement, parent=scope) 

852 

853 return scope 

854 

855 def IterateBins(self) -> Generator[Bin, None, None]: 

856 """ 

857 Iterate the bins of this scope, then those of the scopes below it, depth-first. 

858 

859 :returns: A generator of the bins. 

860 """ 

861 yield from self._bins 

862 for scope in self._scopes: 

863 yield from scope.IterateBins() 

864 

865 @readonly 

866 def Parent(self) -> Nullable[ScopeParentType]: 

867 """ 

868 Read-only property to access the report or scope the scope belongs to (:attr:`_parent`). 

869 

870 :returns: The :class:`Report` or :class:`Scope`; ``None`` if the scope 

871 belongs to none. 

872 """ 

873 return self._parent 

874 

875 @readonly 

876 def Type(self) -> ScopeType: 

877 """ 

878 Read-only property to access the kind of the scope (:attr:`_type`). 

879 

880 :returns: The kind, e.g. a design unit or an instance. 

881 """ 

882 return self._type 

883 

884 @readonly 

885 def Language(self) -> _Language: 

886 """ 

887 Read-only property to access the language of the scope's source (:attr:`_language`). 

888 

889 :returns: The language. 

890 """ 

891 return self._language 

892 

893 @readonly 

894 def Weight(self) -> int: 

895 """ 

896 Read-only property to access the weight of the scope (:attr:`_weight`). 

897 

898 :returns: The weight. 

899 """ 

900 return self._weight 

901 

902 @readonly 

903 def Flags(self) -> int: 

904 """ 

905 Read-only property to access the flags of the scope (:attr:`_flags`). 

906 

907 :returns: The flags, as stated. 

908 """ 

909 return self._flags 

910 

911 @readonly 

912 def Source(self) -> SourceLocation: 

913 """ 

914 Read-only property to access where the scope is in the sources (:attr:`_source`). 

915 

916 :returns: The source location. 

917 """ 

918 return self._source 

919 

920 @readonly 

921 def DesignUnit(self) -> Nullable[str]: 

922 """ 

923 Read-only property to access the name of the design unit an instance instantiates (:attr:`_designUnit`). 

924 

925 :returns: The design unit's name, e.g. ``work.counter``; ``None`` for a scope, which isn't an instance. 

926 """ 

927 return self._designUnit 

928 

929 @readonly 

930 def Scopes(self) -> list[Scope[Scope]]: 

931 """ 

932 Read-only property to access the scopes below this one (:attr:`_scopes`). 

933 

934 :returns: The scopes, in the order the report lists them. 

935 """ 

936 return self._scopes 

937 

938 @readonly 

939 def Bins(self) -> list[Bin]: 

940 """ 

941 Read-only property to access the bins of this scope (:attr:`_bins`). 

942 

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

944 """ 

945 return self._bins 

946 

947 

948@export 

949class HistoryNode(Base, AttributesMixin, Generic[HistoryNodeParentType]): 

950 """ 

951 A ``<ux:hnode>`` of the report or of a merge: a test or a merge of databases, its attributes and merged nodes. 

952 

953 A test wrote a coverage database, a merge merged databases. The attributes state e.g. the tool, the date and the 

954 command line. 

955 """ 

956 

957 _parent: Nullable[HistoryNodeParentType] #: The report or merge the history node belongs to. 

958 _physicalName: Path #: Path of the coverage database. 

959 _kind: HistoryNodeKind #: Kind of the history node. 

960 _historyNodes: list[HistoryNode[HistoryNode]] #: The history nodes merged by this one. 

961 

962 def __init__( 

963 self, 

964 name: str, 

965 physicalName: Path, 

966 kind: HistoryNodeKind, 

967 attributes: Nullable[Mapping[str, int | float | str]] = None, 

968 *, 

969 parent: Nullable[HistoryNodeParentType] = None 

970 ) -> None: 

971 """ 

972 Initialize the history node, and append it to the history nodes of its report or merge. 

973 

974 The history nodes it merged are added by creating them with this history node as their parent. 

975 

976 :param name: Logical name of the history node, e.g. of the test. 

977 :param physicalName: Path of the coverage database, e.g. ``cov/aggregate/aggregate.acdb``. 

978 :param kind: Kind of the history node. 

979 :param attributes: Optional, the attributes by key, e.g. ``{"UCIS:tool": "Riviera-PRO"}``. Default: ``None``. 

980 :param parent: Optional, the report or merge the history node belongs to; the history node is appended 

981 to its history nodes. Default: ``None``. 

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

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

984 :raises TypeError: If parameter ``attributes`` isn't a mapping. 

985 :raises TypeError: If parameter ``attributes`` contains a key not of type :class:`str`. 

986 :raises ValueError: If parameter ``attributes`` contains an empty key. 

987 :raises TypeError: If parameter ``attributes`` contains a value not of type :class:`int`, :class:`float` or 

988 :class:`str`. 

989 :raises ValueError: If parameter ``name`` is empty. 

990 :raises ValueError: If parameter ``physicalName`` is ``None``. 

991 :raises TypeError: If parameter ``physicalName`` isn't of type :class:`~pathlib.Path`. 

992 :raises ValueError: If parameter ``kind`` is ``None``. 

993 :raises TypeError: If parameter ``kind`` isn't of type :class:`HistoryNodeKind`. 

994 :raises TypeError: If parameter ``parent`` isn't of type :class:`Report` 

995 or :class:`HistoryNode`. 

996 """ 

997 super().__init__(name) 

998 AttributesMixin.__init__(self, attributes) 

999 

1000 if name == "": 

1001 raise ValueError("Parameter 'name' is empty.") 

1002 

1003 if physicalName is None: 1003 ↛ 1004line 1003 didn't jump to line 1004 because the condition on line 1003 was never true

1004 raise ValueError("Parameter 'physicalName' is None.") 

1005 elif not isinstance(physicalName, Path): 

1006 ex = TypeError("Parameter 'physicalName' is not of type 'Path'.") 

1007 ex.add_note(f"Got type '{getFullyQualifiedName(physicalName)}'.") 

1008 raise ex 

1009 

1010 if kind is None: 1010 ↛ 1011line 1010 didn't jump to line 1011 because the condition on line 1010 was never true

1011 raise ValueError("Parameter 'kind' is None.") 

1012 elif not isinstance(kind, HistoryNodeKind): 

1013 ex = TypeError("Parameter 'kind' is not of type 'HistoryNodeKind'.") 

1014 ex.add_note(f"Got type '{getFullyQualifiedName(kind)}'.") 

1015 raise ex 

1016 

1017 if parent is not None and not isinstance(parent, (Report, HistoryNode)): 

1018 ex = TypeError("Parameter 'parent' is not of type 'Report' or 'HistoryNode'.") 

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

1020 raise ex 

1021 

1022 self._parent = parent 

1023 self._physicalName = physicalName 

1024 self._kind = kind 

1025 self._historyNodes = [] 

1026 

1027 if parent is not None: 

1028 parent._historyNodes.append(self) 

1029 

1030 @classmethod 

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

1032 """ 

1033 Parse a history node, its attributes and the history nodes it merged from its ``<ux:hnode>`` element. 

1034 

1035 :param element: The ``<ux:hnode>`` element. 

1036 :param parent: Optional, the report or merge the history node belongs to. Default: ``None``. 

1037 :returns: The history node. 

1038 :raises CodeCoverageError: If an attribute's value isn't of the type the attribute states. 

1039 """ 

1040 historyNode = cls( 

1041 element.attrib["logical_name"], 

1042 Path(element.attrib["physical_name"]), 

1043 HistoryNodeKind(int(element.attrib["type"])), 

1044 cls._ParseAttributes(element), 

1045 parent=parent 

1046 ) 

1047 

1048 for historyNodeElement in element.iterfind("ux:hnode", NAMESPACES): 

1049 HistoryNode.Parse(historyNodeElement, parent=historyNode) 

1050 

1051 return historyNode 

1052 

1053 @readonly 

1054 def Parent(self) -> Nullable[HistoryNodeParentType]: 

1055 """ 

1056 Read-only property to access the report or merge the history node belongs to (:attr:`_parent`). 

1057 

1058 :returns: The :class:`Report` or :class:`HistoryNode`; ``None`` if the 

1059 history node belongs to none. 

1060 """ 

1061 return self._parent 

1062 

1063 @readonly 

1064 def PhysicalName(self) -> Path: 

1065 """ 

1066 Read-only property to access the path of the coverage database (:attr:`_physicalName`). 

1067 

1068 :returns: The path, as the tool stated it, e.g. relative to the directory it ran in. 

1069 """ 

1070 return self._physicalName 

1071 

1072 @readonly 

1073 def Kind(self) -> HistoryNodeKind: 

1074 """ 

1075 Read-only property to access the kind of the history node (:attr:`_kind`). 

1076 

1077 :returns: The kind: a test or a merge. 

1078 """ 

1079 return self._kind 

1080 

1081 @readonly 

1082 def HistoryNodes(self) -> list[HistoryNode[HistoryNode]]: 

1083 """ 

1084 Read-only property to access the history nodes merged by this one (:attr:`_historyNodes`). 

1085 

1086 :returns: The history nodes, in the order the report lists them; empty for a test. 

1087 """ 

1088 return self._historyNodes 

1089 

1090 

1091@export 

1092class Command(metaclass=ExtendedType, slots=True): 

1093 """ 

1094 A ``<ux:command>`` of the report: a record of a command applied to the coverage database. 

1095 

1096 An exclusion is such a command. Its fields are kept as stated, not interpreted. 

1097 """ 

1098 

1099 _parent: Nullable[Report] #: The report the command belongs to. 

1100 _type: int #: Kind of the command. 

1101 _flags: int #: Flags of the command. 

1102 _data: str #: Data of the command, e.g. a list of values separated by ``;``. 

1103 _index: int #: Index of the command. 

1104 

1105 def __init__(self, commandType: int, flags: int, data: str, index: int, *, parent: Nullable[Report] = None) -> None: 

1106 """ 

1107 Initialize the command, and append it to the commands of its report. 

1108 

1109 :param commandType: Kind of the command. 

1110 :param flags: Flags of the command. 

1111 :param data: Data of the command. 

1112 :param index: Index of the command. 

1113 :param parent: Optional, the report the command belongs to; the command is appended to its commands. 

1114 Default: ``None``. 

1115 :raises ValueError: If parameter ``commandType`` is ``None``. 

1116 :raises TypeError: If parameter ``commandType`` isn't of type :class:`int`. 

1117 :raises ValueError: If parameter ``flags`` is ``None``. 

1118 :raises TypeError: If parameter ``flags`` isn't of type :class:`int`. 

1119 :raises ValueError: If parameter ``flags`` is negative. 

1120 :raises ValueError: If parameter ``data`` is ``None``. 

1121 :raises TypeError: If parameter ``data`` isn't of type :class:`str`. 

1122 :raises ValueError: If parameter ``index`` is ``None``. 

1123 :raises TypeError: If parameter ``index`` isn't of type :class:`int`. 

1124 :raises ValueError: If parameter ``index`` is negative. 

1125 :raises TypeError: If parameter ``parent`` isn't of type :class:`Report`. 

1126 """ 

1127 if commandType is None: 1127 ↛ 1128line 1127 didn't jump to line 1128 because the condition on line 1127 was never true

1128 raise ValueError("Parameter 'commandType' is None.") 

1129 elif not isinstance(commandType, int): 

1130 ex = TypeError("Parameter 'commandType' is not of type 'int'.") 

1131 ex.add_note(f"Got type '{getFullyQualifiedName(commandType)}'.") 

1132 raise ex 

1133 

1134 if flags is None: 1134 ↛ 1135line 1134 didn't jump to line 1135 because the condition on line 1134 was never true

1135 raise ValueError("Parameter 'flags' is None.") 

1136 elif not isinstance(flags, int): 1136 ↛ 1137line 1136 didn't jump to line 1137 because the condition on line 1136 was never true

1137 ex = TypeError("Parameter 'flags' is not of type 'int'.") 

1138 ex.add_note(f"Got type '{getFullyQualifiedName(flags)}'.") 

1139 raise ex 

1140 elif flags < 0: 

1141 ex = ValueError("Parameter 'flags' is negative.") 

1142 ex.add_note(f"Got value '{flags}'.") 

1143 raise ex 

1144 

1145 if data is None: 

1146 raise ValueError("Parameter 'data' is None.") 

1147 elif not isinstance(data, str): 1147 ↛ 1148line 1147 didn't jump to line 1148 because the condition on line 1147 was never true

1148 ex = TypeError("Parameter 'data' is not of type 'str'.") 

1149 ex.add_note(f"Got type '{getFullyQualifiedName(data)}'.") 

1150 raise ex 

1151 

1152 if index is None: 1152 ↛ 1153line 1152 didn't jump to line 1153 because the condition on line 1152 was never true

1153 raise ValueError("Parameter 'index' is None.") 

1154 elif not isinstance(index, int): 1154 ↛ 1155line 1154 didn't jump to line 1155 because the condition on line 1154 was never true

1155 ex = TypeError("Parameter 'index' is not of type 'int'.") 

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

1157 raise ex 

1158 elif index < 0: 

1159 ex = ValueError("Parameter 'index' is negative.") 

1160 ex.add_note(f"Got value '{index}'.") 

1161 raise ex 

1162 

1163 if parent is not None and not isinstance(parent, Report): 

1164 ex = TypeError("Parameter 'parent' is not of type 'Report'.") 

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

1166 raise ex 

1167 

1168 self._parent = parent 

1169 self._type = commandType 

1170 self._flags = flags 

1171 self._data = data 

1172 self._index = index 

1173 

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

1175 parent._commands.append(self) 

1176 

1177 @classmethod 

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

1179 """ 

1180 Parse a command from its ``<ux:command>`` element. 

1181 

1182 :param element: The ``<ux:command>`` element. 

1183 :param parent: Optional, the report the command belongs to. Default: ``None``. 

1184 :returns: The command. 

1185 """ 

1186 return cls( 

1187 int(element.attrib["type"]), 

1188 int(element.attrib["flags"]), 

1189 element.attrib["data"], 

1190 int(element.attrib["index"]), 

1191 parent=parent 

1192 ) 

1193 

1194 @readonly 

1195 def Parent(self) -> Nullable[Report]: 

1196 """ 

1197 Read-only property to access the report the command belongs to (:attr:`_parent`). 

1198 

1199 :returns: The report; ``None`` if the command belongs to no report. 

1200 """ 

1201 return self._parent 

1202 

1203 @readonly 

1204 def Type(self) -> int: 

1205 """ 

1206 Read-only property to access the kind of the command (:attr:`_type`). 

1207 

1208 :returns: The kind, as stated. 

1209 """ 

1210 return self._type 

1211 

1212 @readonly 

1213 def Flags(self) -> int: 

1214 """ 

1215 Read-only property to access the flags of the command (:attr:`_flags`). 

1216 

1217 :returns: The flags, as stated. 

1218 """ 

1219 return self._flags 

1220 

1221 @readonly 

1222 def Data(self) -> str: 

1223 """ 

1224 Read-only property to access the data of the command (:attr:`_data`). 

1225 

1226 :returns: The data, as stated, e.g. ``3;1;10;dut.sv;63;/project;0;0;``. 

1227 """ 

1228 return self._data 

1229 

1230 @readonly 

1231 def Index(self) -> int: 

1232 """ 

1233 Read-only property to access the index of the command (:attr:`_index`). 

1234 

1235 :returns: The index, as stated. 

1236 """ 

1237 return self._index 

1238 

1239 

1240@export 

1241class Statement(metaclass=ExtendedType, slots=True): 

1242 """ 

1243 A statement of a source line: its statement bins - of one instance, or merged, of all instances -, and how often it 

1244 ran. 

1245 

1246 A statement isn't an element of the format: :meth:`Report.IterateStatements` computes it from the bins. 

1247 """ 

1248 

1249 _file: Path #: Path of the source file. 

1250 _lineNumber: int #: Line number, counted from 1. 

1251 _index: int #: Index of the statement in its line. 

1252 _bins: list[Bin] #: The statement bins of the statement. 

1253 _count: int #: How often the statement ran: the sum of its bins' counts, without the excluded ones. 

1254 _isExcluded: bool #: Whether all the statement's bins are excluded. 

1255 

1256 def __init__(self, file: Path, lineNumber: int, index: int, bins: Iterable[Bin]) -> None: 

1257 """ 

1258 Initialize the statement, its count and whether it is excluded. 

1259 

1260 :param file: Path of the source file, as the bins' source locations state it. 

1261 :param lineNumber: Line number, counted from 1. 

1262 :param index: Index of the statement in its line, counted from 1. 

1263 :param bins: The statement bins of the statement: one, or of all instances, if merged. 

1264 :raises ValueError: If parameter ``file`` is ``None``. 

1265 :raises TypeError: If parameter ``file`` isn't of type :class:`~pathlib.Path`. 

1266 :raises ValueError: If parameter ``lineNumber`` is ``None``. 

1267 :raises TypeError: If parameter ``lineNumber`` isn't of type :class:`int`. 

1268 :raises ValueError: If parameter ``lineNumber`` is less than 1. 

1269 :raises ValueError: If parameter ``index`` is ``None``. 

1270 :raises TypeError: If parameter ``index`` isn't of type :class:`int`. 

1271 :raises ValueError: If parameter ``index`` is negative. 

1272 :raises ValueError: If parameter ``bins`` is ``None``. 

1273 :raises TypeError: If parameter ``bins`` isn't iterable. 

1274 :raises TypeError: If parameter ``bins`` contains an element not of type 

1275 :class:`Bin`. 

1276 :raises ValueError: If parameter ``bins`` is empty. 

1277 """ 

1278 if file is None: 1278 ↛ 1279line 1278 didn't jump to line 1279 because the condition on line 1278 was never true

1279 raise ValueError("Parameter 'file' is None.") 

1280 elif not isinstance(file, Path): 1280 ↛ 1281line 1280 didn't jump to line 1281 because the condition on line 1280 was never true

1281 ex = TypeError("Parameter 'file' is not of type 'Path'.") 

1282 ex.add_note(f"Got type '{getFullyQualifiedName(file)}'.") 

1283 raise ex 

1284 

1285 if lineNumber is None: 1285 ↛ 1286line 1285 didn't jump to line 1286 because the condition on line 1285 was never true

1286 raise ValueError("Parameter 'lineNumber' is None.") 

1287 elif not isinstance(lineNumber, int): 1287 ↛ 1288line 1287 didn't jump to line 1288 because the condition on line 1287 was never true

1288 ex = TypeError("Parameter 'lineNumber' is not of type 'int'.") 

1289 ex.add_note(f"Got type '{getFullyQualifiedName(lineNumber)}'.") 

1290 raise ex 

1291 elif lineNumber < 1: 

1292 ex = ValueError("Parameter 'lineNumber' is less than 1.") 

1293 ex.add_note(f"Got value '{lineNumber}'.") 

1294 raise ex 

1295 

1296 if index is None: 1296 ↛ 1297line 1296 didn't jump to line 1297 because the condition on line 1296 was never true

1297 raise ValueError("Parameter 'index' is None.") 

1298 elif not isinstance(index, int): 1298 ↛ 1299line 1298 didn't jump to line 1299 because the condition on line 1298 was never true

1299 ex = TypeError("Parameter 'index' is not of type 'int'.") 

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

1301 raise ex 

1302 elif index < 0: 1302 ↛ 1303line 1302 didn't jump to line 1303 because the condition on line 1302 was never true

1303 ex = ValueError("Parameter 'index' is negative.") 

1304 ex.add_note(f"Got value '{index}'.") 

1305 raise ex 

1306 

1307 if bins is None: 

1308 raise ValueError("Parameter 'bins' is None.") 

1309 elif not isinstance(bins, Iterable): 

1310 ex = TypeError("Parameter 'bins' is not iterable.") 

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

1312 raise ex 

1313 

1314 self._file = file 

1315 self._lineNumber = lineNumber 

1316 self._index = index 

1317 self._bins = [] 

1318 self._count = 0 

1319 self._isExcluded = True 

1320 

1321 for coverBin in bins: 

1322 if not isinstance(coverBin, Bin): 

1323 ex = TypeError("Parameter 'bins' contains an element not of type 'Bin'.") 

1324 ex.add_note(f"Got type '{getFullyQualifiedName(coverBin)}'.") 

1325 raise ex 

1326 

1327 self._bins.append(coverBin) 

1328 if not coverBin.IsExcluded: 

1329 self._count += coverBin._count 

1330 self._isExcluded = False 

1331 

1332 if len(self._bins) == 0: 

1333 raise ValueError("Parameter 'bins' is empty.") 

1334 

1335 @readonly 

1336 def File(self) -> Path: 

1337 """ 

1338 Read-only property to access the path of the source file (:attr:`_file`). 

1339 

1340 :returns: The path, as the bins' source locations state it. 

1341 """ 

1342 return self._file 

1343 

1344 @readonly 

1345 def LineNumber(self) -> int: 

1346 """ 

1347 Read-only property to access the line number (:attr:`_lineNumber`). 

1348 

1349 :returns: The line number, counted from 1. 

1350 """ 

1351 return self._lineNumber 

1352 

1353 @readonly 

1354 def Index(self) -> int: 

1355 """ 

1356 Read-only property to access the index of the statement in its line (:attr:`_index`). 

1357 

1358 :returns: The index, counted from 1. 

1359 """ 

1360 return self._index 

1361 

1362 @readonly 

1363 def Bins(self) -> list[Bin]: 

1364 """ 

1365 Read-only property to access the statement bins of the statement (:attr:`_bins`). 

1366 

1367 :returns: The bins: one, or of all instances, if merged. 

1368 """ 

1369 return self._bins 

1370 

1371 @readonly 

1372 def Count(self) -> int: 

1373 """ 

1374 Read-only property to access how often the statement ran (:attr:`_count`). 

1375 

1376 :returns: The sum of the counts of the bins, which aren't excluded. 

1377 """ 

1378 return self._count 

1379 

1380 @readonly 

1381 def IsExcluded(self) -> bool: 

1382 """ 

1383 Read-only property to access whether the statement is excluded from the measurement (:attr:`_isExcluded`). 

1384 

1385 :returns: ``True``, if all its bins are excluded. 

1386 """ 

1387 return self._isExcluded 

1388 

1389 

1390@export 

1391class Report(AttributesMixin, mixin=True): 

1392 """ 

1393 The root element ``<ux:ucdb>``: the tool, which wrote it, its attributes, the history nodes, the scopes and the 

1394 commands. 

1395 

1396 The root of the format's model: a history node, a scope or a command names it as its parent. 

1397 """ 

1398 

1399 _tool: Nullable[str] #: Name of the tool, which wrote the report. 

1400 _toolVersion: Nullable[CalendarVersion] #: Version of the tool, which wrote the report. 

1401 _historyNodes: list[HistoryNode[Report]] #: The history nodes. 

1402 _scopes: list[Scope[Report]] #: The top-level scopes. 

1403 _commands: list[Command] #: The commands. 

1404 

1405 def __init__(self) -> None: 

1406 """ 

1407 Initialize an empty report. 

1408 """ 

1409 AttributesMixin.__init__(self) 

1410 

1411 self._tool = None 

1412 self._toolVersion = None 

1413 self._historyNodes = [] 

1414 self._scopes = [] 

1415 self._commands = [] 

1416 

1417 @readonly 

1418 def Tool(self) -> Nullable[str]: 

1419 """ 

1420 Read-only property to access the name of the tool, which wrote the report (:attr:`_tool`). 

1421 

1422 :returns: The tool's name, e.g. ``Riviera-PRO``; ``None`` before the report was converted. 

1423 """ 

1424 return self._tool 

1425 

1426 @readonly 

1427 def ToolVersion(self) -> Nullable[CalendarVersion]: 

1428 """ 

1429 Read-only property to access the version of the tool, which wrote the report (:attr:`_toolVersion`). 

1430 

1431 :returns: The tool's version, e.g. ``2022.04``; ``None`` before the report was converted. 

1432 """ 

1433 return self._toolVersion 

1434 

1435 @readonly 

1436 def HistoryNodes(self) -> list[HistoryNode[Report]]: 

1437 """ 

1438 Read-only property to access the history nodes (:attr:`_historyNodes`). 

1439 

1440 :returns: The history nodes, in the order the report lists them: the tests, or the merge of the tests. 

1441 """ 

1442 return self._historyNodes 

1443 

1444 @readonly 

1445 def Scopes(self) -> list[Scope[Report]]: 

1446 """ 

1447 Read-only property to access the top-level scopes (:attr:`_scopes`). 

1448 

1449 :returns: The scopes, in the order the report lists them: e.g. the design units and the top-level instances. 

1450 """ 

1451 return self._scopes 

1452 

1453 @readonly 

1454 def Commands(self) -> list[Command]: 

1455 """ 

1456 Read-only property to access the commands (:attr:`_commands`). 

1457 

1458 :returns: The commands, in the order the report lists them. 

1459 """ 

1460 return self._commands 

1461 

1462 def IterateStatements(self, mergeInstances: bool = False) -> Generator[Statement, None, None]: 

1463 """ 

1464 Iterate the statements: the statement bins of the instances. 

1465 

1466 The statement bins are those of the scopes below each top-level scope, which isn't a design unit. A statement is 

1467 identified by its file, its line and its index in the line - attribute ``#SINDEX#`` of its bin. 

1468 

1469 :param mergeInstances: Optional, if true, merge the bins of all instances, which identify the same statement, 

1470 into one statement; otherwise each bin is a statement. Default: ``False``. 

1471 :returns: A generator of the statements, in the order of their first bin. 

1472 :raises ValueError: If parameter ``mergeInstances`` is ``None``. 

1473 :raises TypeError: If parameter ``mergeInstances`` isn't of type :class:`bool`. 

1474 :raises CodeCoverageError: If a statement bin states no statement index. 

1475 """ 

1476 if mergeInstances is None: 

1477 raise ValueError("Parameter 'mergeInstances' is None.") 

1478 elif not isinstance(mergeInstances, bool): 

1479 ex = TypeError("Parameter 'mergeInstances' is not of type 'bool'.") 

1480 ex.add_note(f"Got type '{getFullyQualifiedName(mergeInstances)}'.") 

1481 raise ex 

1482 

1483 designUnits = ( 

1484 ScopeType.DesignUnitModule, ScopeType.DesignUnitArchitecture, ScopeType.DesignUnitPackage, 

1485 ScopeType.DesignUnitProgram, ScopeType.DesignUnitInterface 

1486 ) 

1487 mergedBins: dict[tuple[Path, int, int], list[Bin]] = {} 

1488 for scope in self._scopes: 

1489 if scope._type in designUnits: 

1490 continue 

1491 

1492 for coverBin in scope.IterateBins(): 

1493 if coverBin._type is not CoverType.StatementBin: 

1494 continue 

1495 

1496 source = coverBin._source 

1497 if not isinstance(index := coverBin._attributes.get("#SINDEX#"), int): 

1498 ex = CodeCoverageError( 

1499 f"UCDB statement bin at '{source._file.as_posix()}:{source._lineNumber}' states no statement index." 

1500 ) 

1501 ex.add_note("A statement bin states its index in the line by attribute '#SINDEX#' of type 'int'.") 

1502 raise ex 

1503 

1504 if mergeInstances: 

1505 mergedBins.setdefault((source._file, source._lineNumber, index), []).append(coverBin) 

1506 else: 

1507 yield Statement(source._file, source._lineNumber, index, (coverBin, )) 

1508 

1509 for (file, lineNumber, index), bins in mergedBins.items(): 

1510 yield Statement(file, lineNumber, index, bins) 

1511 

1512 

1513@export 

1514class Document(cc_Document, Report): 

1515 """ 

1516 An Aldec UCDB XML code coverage report: read into the format's model, and converted to the common model. 

1517 """ 

1518 

1519 _xmlDocument: Nullable[_ElementTree] #: The parsed and validated XML document, after :meth:`Analyze`. 

1520 

1521 def __init__(self, xmlReportFile: Path, analyzeAndConvert: bool = False) -> None: 

1522 """ 

1523 Initialize the report, and optionally read it. 

1524 

1525 :param xmlReportFile: Path to the UCDB XML file. 

1526 :param analyzeAndConvert: Optional, if true, analyze the file and convert its content. Default: ``False``. 

1527 """ 

1528 super().__init__(xmlReportFile) 

1529 Report.__init__(self) 

1530 

1531 self._xmlDocument = None 

1532 

1533 if analyzeAndConvert: 

1534 self.Analyze() 

1535 self.Convert() 

1536 

1537 def Analyze(self) -> None: 

1538 """ 

1539 Parse the XML file, check its root element, and validate the file against the XML schema (:data:`SCHEMA`). 

1540 

1541 :raises CodeCoverageError: If the file doesn't exist. 

1542 :raises CodeCoverageError: If the file can't be read. 

1543 :raises CodeCoverageError: If the file isn't well-formed XML. 

1544 :raises CodeCoverageError: If the root element isn't ``<ux:ucdb>`` of namespace ``www.aldec.com``. 

1545 :raises CodeCoverageError: If the XML schema can't be located or parsed. 

1546 :raises CodeCoverageError: If the file isn't valid according to the XML schema. 

1547 """ 

1548 if not self._path.exists(): 

1549 raise CodeCoverageError(f"Aldec UCDB file '{self._path}' does not exist.") \ 

1550 from FileNotFoundError(f"File '{self._path}' not found.") 

1551 

1552 with Stopwatch() as sw: 

1553 try: 

1554 content = self._path.read_bytes() 

1555 except OSError as ex: 

1556 raise CodeCoverageError(f"Couldn't read Aldec UCDB file '{self._path}'.") from ex 

1557 

1558 try: 

1559 xmlDocument = parse(BytesIO(content), XMLParser(ns_clean=True), base_url=str(self._path)) 

1560 except XMLSyntaxError as ex: 

1561 raise CodeCoverageError(f"XML syntax error in Aldec UCDB file '{self._path}'.") from ex 

1562 

1563 rootElement: _Element = xmlDocument.getroot() 

1564 if rootElement.tag != f"{{{NAMESPACE}}}ucdb": 

1565 ex = CodeCoverageError(f"Root element of '{self._path}' is not '<ux:ucdb>' of namespace '{NAMESPACE}'.") 

1566 ex.add_note(f"Got root element '<{rootElement.tag}>'.") 

1567 raise ex 

1568 

1569 try: 

1570 schemaResourceFile = getResourceFile(Resources, SCHEMA) 

1571 except ToolingException as ex: 

1572 raise CodeCoverageError(f"Couldn't locate XML Schema '{SCHEMA}' in package resources.") from ex 

1573 

1574 try: 

1575 xmlSchema = XMLSchema(parse(schemaResourceFile, XMLParser(ns_clean=True))) 

1576 except (XMLSyntaxError, XMLSchemaParseError) as ex: 

1577 raise CodeCoverageError(f"Error while parsing XML Schema '{SCHEMA}'.") from ex 

1578 

1579 if not xmlSchema.validate(xmlDocument): 

1580 ex = CodeCoverageError(f"Validation error for '{self._path}' using XSD schema '{SCHEMA}'.") 

1581 for logEntry in xmlSchema.error_log: 

1582 ex.add_note(str(logEntry)) 

1583 raise ex 

1584 

1585 self._xmlDocument = xmlDocument 

1586 

1587 self._analysisDuration = sw.Duration 

1588 

1589 def Convert(self) -> None: 

1590 """ 

1591 Convert the parsed and validated XML document to the format's model. 

1592 

1593 :raises CodeCoverageError: If the XML file was not analyzed before. |br| 

1594 Call 'Document.Analyze()' or create the document using 

1595 'Document(path, analyzeAndConvert=True)'. 

1596 :raises CodeCoverageError: If an attribute's value isn't of the type the attribute states. 

1597 """ 

1598 if self._xmlDocument is None: 

1599 ex = CodeCoverageError(f"Aldec UCDB file '{self._path}' needs to be read and analyzed by an XML parser.") 

1600 ex.add_note("Call 'Document.Analyze()' or create the document using 'Document(path, analyzeAndConvert=True)'.") 

1601 raise ex 

1602 

1603 with Stopwatch() as sw: 

1604 root: _Element = self._xmlDocument.getroot() 

1605 tool, _, version = root.attrib["version"].rpartition(" ") 

1606 self._tool = tool 

1607 self._toolVersion = CalendarVersion.Parse(version) 

1608 self._attributes = self._ParseAttributes(root) 

1609 

1610 for historyNodeElement in root.iterfind("ux:hnode", NAMESPACES): 

1611 HistoryNode.Parse(historyNodeElement, parent=self) 

1612 

1613 for scopeElement in root.iterfind("ux:scope", NAMESPACES): 

1614 Scope.Parse(scopeElement, parent=self) 

1615 

1616 for commandElement in root.iterfind("ux:command", NAMESPACES): 

1617 Command.Parse(commandElement, parent=self) 

1618 

1619 self._conversionDuration = sw.Duration 

1620 

1621 def ToCoverageSummary(self, mergeInstances: bool = False) -> CoverageSummary: 

1622 """ 

1623 Convert the statement coverage of the format's model to the common model, and aggregate it. 

1624 

1625 A line ran as often as its least often run statement (:meth:`~Report.IterateStatements`): it is covered, if all its 

1626 statements ran, otherwise uncovered. A line, whose statements are all excluded, is excluded. 

1627 

1628 :param mergeInstances: Optional, if true, merge the statements of all instances of a design unit. Default: 

1629 ``False``. 

1630 :returns: The report's root of the common model, named after the report file. Its source 

1631 directories are the directories the tool ran in. 

1632 :raises ValueError: If parameter ``mergeInstances`` is ``None``. 

1633 :raises TypeError: If parameter ``mergeInstances`` isn't of type :class:`bool`. 

1634 :raises CodeCoverageError: If a statement bin states no statement index. 

1635 :raises CodeCoverageError: If a file's path runs through another file. 

1636 """ 

1637 statementsPerFile: dict[Path, dict[int, list[Statement]]] = {} 

1638 sourceDirectories: dict[Path, None] = {} 

1639 for statement in self.IterateStatements(mergeInstances): 

1640 statementsPerFile.setdefault(statement._file, {}).setdefault(statement._lineNumber, []).append(statement) 

1641 for coverBin in statement._bins: 

1642 sourceDirectories[coverBin._source._workDirectory] = None 

1643 

1644 summary = CoverageSummary(self._path.stem, sourceDirectories=sourceDirectories) 

1645 for path, statementsPerLine in statementsPerFile.items(): 

1646 file = summary.GetOrAddFile(path) 

1647 for lineNumber in sorted(statementsPerLine): 

1648 counts = [statement._count for statement in statementsPerLine[lineNumber] if not statement._isExcluded] 

1649 if len(counts) == 0: 

1650 cc_Line(lineNumber, LineCoverageStatus.Excluded, parent=file) 

1651 else: 

1652 count = min(counts) 

1653 status = LineCoverageStatus.Covered if count > 0 else LineCoverageStatus.Uncovered 

1654 cc_Line(lineNumber, status, count, parent=file) 

1655 

1656 summary.Aggregate() 

1657 return summary