Skip to content

Commit 0947c24

Browse files
mballanceCopilot
andcommitted
Add full XML coverage support: FSM, assertion, block, branch, toggle, userAttr
- xml_writer.py: add write_block_coverage, write_branch_coverage, write_toggle_coverage, write_fsm_coverage, write_assertion_coverage, write_user_attrs; fix writtenBy/writtenTime; emit parentId on history nodes; ctx.warn for condition scopes - xml_reader.py: add readFsmCoverage, readAssertionCoverage, read_user_attrs; call read_user_attrs from all coverage readers; two-pass parentId wiring - db_merger.py: extend _merge_code_coverage for FSM/ASSERT/COVER scope types; extend _merge_code_coverage_items for all assertion bin types; rewrite history node copy with depth sort and parent-at-create-time - ucis_builders.py: add cc7 (FSM), as1 (cover assertion), as2 (assert property), sm6 (parent-child history) builders; ALL_BUILDERS now 15 entries - test_xml_conversion.py: add schema_validate fixture; schema validation in all roundtrip and write tests; test_source_file_ids_consistent; test_read_golden_file parametrized over 5 golden fixtures - test_api_attributes.py: remove xml skip guards for 5 attribute tests - fixtures/xml/: 5 golden XML files (toggle, fsm, assertion, block, branch) - xml_interchange.rst: document all new coverage types, userAttr, history nodes; update Feature Support Matrix Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 7ec63c7 commit 0947c24

12 files changed

Lines changed: 820 additions & 24 deletions

File tree

doc/source/reference/xml_interchange.rst

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,153 @@ Optional Elements
192192
- **cross** (minOccurs="0") - Cross coverage is optional
193193
- **crossBin** (minOccurs="0") - Crosses may have no bins
194194

195+
Code Coverage
196+
=============
197+
198+
PyUCIS supports reading and writing all major UCIS code coverage types within
199+
``instanceCoverages`` elements.
200+
201+
Statement Coverage
202+
------------------
203+
204+
Statement coverage is stored as ``blockCoverage`` → flat ``statement`` elements:
205+
206+
.. code::
207+
208+
<blockCoverage>
209+
<statement id="1" file="1" line="5" inlineCount="1">
210+
<contents coverageCount="3"/>
211+
</statement>
212+
</blockCoverage>
213+
214+
Each ``statement`` records one source location with its hit count.
215+
216+
Branch Coverage
217+
---------------
218+
219+
Branch coverage is stored as ``branchCoverage`` → ``branch`` elements, one per
220+
branching statement (``if``, ``case``, etc.). Each branch has one or more
221+
``branchBin`` arms:
222+
223+
.. code::
224+
225+
<branchCoverage>
226+
<branch id="1" branchType="if" file="1" line="10">
227+
<branchBin alias="taken">
228+
<contents coverageCount="2"/>
229+
</branchBin>
230+
<branchBin alias="not_taken">
231+
<contents coverageCount="0"/>
232+
</branchBin>
233+
</branch>
234+
</branchCoverage>
235+
236+
Toggle Coverage
237+
---------------
238+
239+
Toggle coverage is stored as ``toggleCoverage`` → ``toggleObject`` → ``toggleBit``
240+
elements. Each bit records a 0→1 and a 1→0 transition bin:
241+
242+
.. code::
243+
244+
<toggleCoverage>
245+
<toggleObject name="sig" canonical="sig">
246+
<toggleBit name="sig[0]">
247+
<toggle01Bin>
248+
<contents coverageCount="1"/>
249+
</toggle01Bin>
250+
<toggle10Bin>
251+
<contents coverageCount="1"/>
252+
</toggle10Bin>
253+
</toggleBit>
254+
</toggleObject>
255+
</toggleCoverage>
256+
257+
FSM Coverage
258+
------------
259+
260+
FSM coverage is stored as ``fsmCoverage`` → ``fsm`` elements. State coverage uses
261+
``stateBin`` and transition coverage uses ``transitionBin``:
262+
263+
.. code::
264+
265+
<fsmCoverage>
266+
<fsm name="state_machine">
267+
<state stateName="IDLE">
268+
<stateBin name="IDLE">
269+
<contents coverageCount="5"/>
270+
</stateBin>
271+
</state>
272+
<stateTransition>
273+
<state>IDLE</state>
274+
<state>ACTIVE</state>
275+
<transitionBin name="IDLE->ACTIVE">
276+
<contents coverageCount="2"/>
277+
</transitionBin>
278+
</stateTransition>
279+
</fsm>
280+
</fsmCoverage>
281+
282+
Assertion Coverage
283+
------------------
284+
285+
Assertion coverage is stored as ``assertionCoverage`` → ``assertion`` elements.
286+
The ``assertionKind`` attribute is either ``assert`` or ``cover``:
287+
288+
.. code::
289+
290+
<assertionCoverage>
291+
<assertion name="my_property" assertionKind="assert">
292+
<passBin>
293+
<contents coverageCount="10"/>
294+
</passBin>
295+
<failBin>
296+
<contents coverageCount="0"/>
297+
</failBin>
298+
<attemptBin>
299+
<contents coverageCount="10"/>
300+
</attemptBin>
301+
</assertion>
302+
</assertionCoverage>
303+
304+
Supported bin kinds for ``assert``: ``failBin``, ``passBin``, ``vacuousBin``,
305+
``disabledBin``, ``attemptBin``, ``activeBin``, ``peakActiveBin``.
306+
307+
Supported bin kinds for ``cover``: ``coverBin``, ``failBin``, ``passBin``,
308+
``vacuousBin``, ``disabledBin``, ``attemptBin``, ``activeBin``, ``peakActiveBin``.
309+
310+
User Attributes
311+
===============
312+
313+
PyUCIS supports round-tripping user attributes (set via ``setAttribute`` /
314+
``getAttribute``) through XML ``userAttr`` child elements. These are written as
315+
the last children of each coverage container element:
316+
317+
.. code::
318+
319+
<toggleCoverage>
320+
<!-- ... toggle data ... -->
321+
<userAttr key="tool_name" type="str">my_simulator</userAttr>
322+
<userAttr key="version" type="str">2024.1</userAttr>
323+
</toggleCoverage>
324+
325+
User attributes attached to instance scopes are preserved across XML write/read
326+
round-trips. Tags (set via ``addTag``) are not currently serialized to XML.
327+
328+
History Nodes
329+
=============
330+
331+
History nodes record the provenance of coverage data (test runs, merges). PyUCIS
332+
preserves the parent/child relationships between history nodes using the ``id``
333+
and ``parentId`` attributes:
334+
335+
.. code::
336+
337+
<historyNodes>
338+
<historyNode id="1" logicalName="test_run_1" .../>
339+
<historyNode id="2" logicalName="merge_result" parentId="1" .../>
340+
</historyNodes>
341+
195342
Known Format Limitations
196343
=========================
197344

@@ -243,6 +390,30 @@ Feature Support Matrix
243390
* - Cross Bins
244391
- ✅ Yes
245392
- With index reconstruction
393+
* - Statement Coverage
394+
- ✅ Yes
395+
- Flat statement mode
396+
* - Branch Coverage
397+
- ✅ Yes
398+
- if/case branching statements
399+
* - Toggle Coverage
400+
- ✅ Yes
401+
- Per-bit 0→1 and 1→0 bins
402+
* - FSM Coverage
403+
- ✅ Yes
404+
- State and transition coverage
405+
* - Assertion Coverage
406+
- ✅ Yes
407+
- cover and assert kinds, all bin types
408+
* - User Attributes
409+
- ✅ Yes
410+
- Via ``userAttr`` child elements
411+
* - History Node Hierarchy
412+
- ✅ Yes
413+
- Parent/child via ``parentId``
414+
* - Condition/Expression Coverage
415+
- ❌ No
416+
- Not in Python DM; writer emits ctx.warn
246417
* - Instance Weights
247418
- ❌ No
248419
- Not in XML schema
@@ -255,6 +426,9 @@ Feature Support Matrix
255426
* - Standalone File Handles
256427
- ❌ No
257428
- Requires instanceCoverages
429+
* - Tags
430+
- ❌ No
431+
- No direct XML representation
258432

259433
Workarounds
260434
-----------

src/ucis/merge/db_merger.py

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,14 +80,33 @@ def merge(self, dst_db, src_db_l : List[UCIS]):
8080
self._merge_code_coverage(dst_iscope, src_scopes)
8181

8282
# Copy history nodes from all source databases
83+
def _node_key(n):
84+
"""Stable key for a history node regardless of backend."""
85+
return getattr(n, 'history_id', id(n))
86+
8387
for db in src_db_l:
84-
for src_hn in db.historyNodes(HistoryNodeKind.ALL):
88+
src_nodes = list(db.historyNodes(HistoryNodeKind.ALL))
89+
src_to_dst = {} # maps _node_key(src_node) → dst_node
90+
91+
# Sort so parents are created before children
92+
def _sort_key(n):
93+
depth = 0
94+
p = n.getParent()
95+
while p is not None:
96+
depth += 1
97+
p = p.getParent()
98+
return depth
99+
100+
for src_hn in sorted(src_nodes, key=_sort_key):
101+
src_parent = src_hn.getParent()
102+
dst_parent = src_to_dst.get(_node_key(src_parent)) if src_parent is not None else None
85103
dst_hn = dst_db.createHistoryNode(
86-
None,
104+
dst_parent,
87105
src_hn.getLogicalName(),
88106
src_hn.getPhysicalName(),
89107
src_hn.getKind()
90108
)
109+
src_to_dst[_node_key(src_hn)] = dst_hn
91110
dst_hn.setTestStatus(src_hn.getTestStatus())
92111
if src_hn.getSimTime() is not None:
93112
dst_hn.setSimTime(src_hn.getSimTime())
@@ -304,6 +323,13 @@ def _merge_code_coverage(self, dst_scope, src_scopes):
304323

305324
# Merge TOGGLE scopes (toggle coverage)
306325
self._merge_scopes_by_type(dst_scope, src_scopes, ScopeTypeT.TOGGLE)
326+
327+
# Merge FSM scopes (FSM state/transition coverage)
328+
self._merge_scopes_by_type(dst_scope, src_scopes, ScopeTypeT.FSM)
329+
330+
# Merge assertion scopes (assert/cover directives)
331+
self._merge_scopes_by_type(dst_scope, src_scopes, ScopeTypeT.ASSERT)
332+
self._merge_scopes_by_type(dst_scope, src_scopes, ScopeTypeT.COVER)
307333

308334
def _merge_scopes_by_type(self, dst_parent, src_scopes, scope_type):
309335
"""Merge scopes of a specific type.
@@ -362,6 +388,15 @@ def _merge_code_coverage_items(self, dst_scope, src_scopes):
362388
CoverTypeT.EXPRBIN, # Expression coverage
363389
CoverTypeT.CONDBIN, # Condition coverage
364390
CoverTypeT.FSMBIN, # FSM coverage
391+
CoverTypeT.ASSERTBIN, # Assertion directive bins
392+
CoverTypeT.COVERBIN,
393+
CoverTypeT.PASSBIN,
394+
CoverTypeT.FAILBIN,
395+
CoverTypeT.VACUOUSBIN,
396+
CoverTypeT.DISABLEDBIN,
397+
CoverTypeT.ATTEMPTBIN,
398+
CoverTypeT.ACTIVEBIN,
399+
CoverTypeT.PEAKACTIVEBIN,
365400
]
366401

367402
for cvg_type in coverage_types:

0 commit comments

Comments
 (0)