Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
e257c5a
Initial C Cov commit
Den1552 Dec 10, 2025
5d86103
Switched code to utilities
Den1552 Dec 10, 2025
4063e02
Corrected initial mcdc line fetch
Den1552 Dec 10, 2025
ddd2e43
Removed commented code
Den1552 Dec 11, 2025
ddce4e0
Refactoring
Den1552 Dec 11, 2025
d8e12fe
Refactoring 2
Den1552 Dec 11, 2025
a6065e5
Removed all buttons from vcp nodes
Den1552 Dec 11, 2025
f66035d
C Coverage Tests
Den1552 Dec 18, 2025
f41f026
c cov test correction
Den1552 Dec 18, 2025
a684c8d
c cov test correction 2
Den1552 Dec 18, 2025
569e73d
Added enc file
Den1552 Dec 18, 2025
5fa514c
var name change
Den1552 Dec 18, 2025
5f7238d
Adapted tests
Den1552 Dec 19, 2025
6ad76d0
Trying to add pause after file selection
Den1552 Dec 19, 2025
0358495
Removed setting change
Den1552 Dec 19, 2025
1102efb
Added debug log
Den1552 Dec 19, 2025
3763f98
Adapted c tests
Den1552 Jan 13, 2026
9b57208
Merge branch 'main' into vcp_coverage
Den1552 Jan 13, 2026
f1ba3fe
added debugging logs
Den1552 Jan 13, 2026
3a28f5f
Changed debug log location
Den1552 Jan 13, 2026
d21375c
Debugging logs for manage tests
Den1552 Jan 13, 2026
c7866f0
Additional debugging logs
Den1552 Jan 14, 2026
1c29dff
Maximizing bottomBar
Den1552 Jan 14, 2026
06a4491
Restoring bottom bar
Den1552 Jan 14, 2026
5c2cc2f
Removed debug logs
Den1552 Jan 14, 2026
c446e82
Removed last debugging logs
Den1552 Jan 14, 2026
b23a5c4
Sonar
Den1552 Jan 14, 2026
356bffc
update
Den1552 Feb 6, 2026
9dca76a
Added additional coverage
Den1552 Feb 9, 2026
fa6644d
Fixed coverage for cover and for .h
Den1552 Feb 10, 2026
2719d45
MCDC Report for h files 1
Den1552 Feb 10, 2026
890fe14
MCDC Report for h files 2
Den1552 Feb 10, 2026
02a704b
Add support for generating TIC-style MCDC reports
aytey Feb 10, 2026
a88ff61
Fixed MCDC
Den1552 Feb 13, 2026
f3a3c4b
Fixed mcdc report for headers
Den1552 Feb 13, 2026
9799ad0
Fixed issue for not appearing icons if only partially covered
Den1552 Feb 16, 2026
512ba65
Coverage on empty function not at the end anymore
Den1552 Feb 16, 2026
abe557a
Added first Cover Buttons
Den1552 Feb 17, 2026
89bbeb9
Merge main into vcp_coverage
Den1552 Feb 17, 2026
a8f5b19
Enabled to open files in cover project
Den1552 Feb 17, 2026
89a2116
Added Buttons for cover projects
Den1552 Feb 18, 2026
3051e59
Added possibility to open cover in vcastqt
Den1552 Feb 18, 2026
475961b
Changed result file retrieval
Den1552 Feb 18, 2026
20efe6d
Initial coverage filter for files (not finished)
Den1552 Mar 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 114 additions & 29 deletions package.json

Large diffs are not rendered by default.

45 changes: 28 additions & 17 deletions python/coverageGutter.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,28 +15,36 @@ def getMCDCLineDic(sourceObject):

unitFile = sourceObject.cover_data.name
unit = unitFile.rsplit(".", 1)[0]
for mcdc in sourceObject.cover_data.mcdc_decisions:

all_decisions = []
covered_mcdc_pair_found = False
decision_has_covered_pairs_for_all_conditions = True
# If we overwrite a function, we need all "overwritten decisions"
for inst_file in sourceObject.instrumented_files:
all_decisions.extend(inst_file.mcdc_decisions)
for mcdc in all_decisions:
# If it s not a mcdc pair --> continue
if not mcdc.num_conditions:
continue

start_line = mcdc.start_line

# Per default, we set the line to be uncovered
temp_line_coverage_dic[start_line] = MCDCLineCoverage.uncovered
mcdc_unit_line_dic[unit] = temp_line_coverage_dic

for condition in mcdc.conditions:
covered_pair = condition.get_covered_pair()

covered_mcdc_found = False
uncovered_mcdc_found = False

for row in mcdc.rows:
if row.has_any_coverage != 0:
covered_mcdc_found = True
# We have at least 1 covered mcdc pair
if(covered_pair == None):
decision_has_covered_pairs_for_all_conditions = False
else:
uncovered_mcdc_found = True
covered_mcdc_pair_found = True

if covered_mcdc_found == True:
if covered_mcdc_pair_found == True:
# We found covered and uncovered mcdc pairs --> Partially covered
if uncovered_mcdc_found == True:
if decision_has_covered_pairs_for_all_conditions == False:
temp_line_coverage_dic[start_line] = MCDCLineCoverage.partially_covered
else:
# We found only covered mcdc pairs --> Fully covered
Expand Down Expand Up @@ -85,7 +93,6 @@ def handleMcdcCoverage(
if use_mcdc
else metrics.max_covered_branches + metrics.max_annotations_branches
)

has_branch_coverage = covered_branches > 0
# First check for the branch coverage. If it has none, it can not be partially covered / covered
if has_branch_coverage:
Expand All @@ -96,13 +103,13 @@ def handleMcdcCoverage(
# To be fully mcdc covered: All Branches + All MCDC pairs
is_fully_mcdc_covered = (
covered_branches == branch_total
and mcdc_line_coverage == MCDCLineCoverage.covered
and mcdc_line_coverage == MCDCLineCoverage.covered
)
# If it's fully covered --> It's an mcdc line and fully covered --> green
if is_fully_mcdc_covered:
coveredString += f"{line.line_number},"
# Partially covered mcdc line --> orange
elif mcdc_line_coverage == MCDCLineCoverage.partially_covered:
# Not everything is covered but we have at least 1 covered mcdc pair --> Partially covered mcdc line --> orange
elif metrics.covered_mcdc_pairs > 0:
partiallyCoveredString += f"{line.line_number},"
# If it has branches covered but not mcdc pair
else:
Expand Down Expand Up @@ -156,17 +163,21 @@ def handleStatementMcdcCoverage(
total_statements = metrics.statements

if mcdc_line_coverage is not None:

# To be fully mcdc covered: All Statements + All Branches + All MCDC pairs
# and the line either has to be flagged covered or partially covered from getMCDCLineDic
# here we find out if it's really 100% covered or only partially covered
is_fully_mcdc_covered = (
covered_statements == total_statements
and covered_branches == branch_total
and mcdc_line_coverage == MCDCLineCoverage.covered
and mcdc_line_coverage == MCDCLineCoverage.covered
)

# If it's fully covered --> It's an mcdc line and fully covered --> green
if is_fully_mcdc_covered:
coveredString += f"{line_number},"
# Partially covered mcdc line --> orange

# Not everything is covered but we have at least 1 covered mcdc pair --> Partially covered mcdc line --> orange
elif mcdc_line_coverage == MCDCLineCoverage.partially_covered:
partiallyCoveredString += f"{line_number},"
# a mcdc line that has no coverage --> Red
Expand Down
21 changes: 16 additions & 5 deletions python/custom/sections/per_line_mcdc.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,19 @@ def __init__(self, api, orig):

def __get__(self, instance, owner):
data = self.__orig.__get__(instance, owner)
mcdc_filter = self.__api.mcdc_filter
unit_filter = mcdc_filter.get("unit")
line_filter = mcdc_filter["line"]
# Filter the data
new_data = []
for decn in data:
if (
decn.function.instrumented_file.name == self.__api.mcdc_filter["unit"]
and decn.start_line == self.__api.mcdc_filter["line"]
):
new_data.append(decn)
if decn.start_line != line_filter:
continue
# When a unit is specified, also match the instrumented file name.
# When omitted (included-header case), accept all TUs.
if unit_filter and decn.function.instrumented_file.name != unit_filter:
continue
new_data.append(decn)
return new_data


Expand Down Expand Up @@ -62,5 +67,11 @@ def prepare_data(self):
# Older versions only have the public method
super().prepare_data()

# Expose whether this is a template-instantiation report
# (no specific unit → included-header with multiple TUs).
self.section_context["show_subprogram"] = (
"unit" not in self.api.mcdc_filter
)


# EOF
8 changes: 6 additions & 2 deletions python/custom/templates/PerLineMcdc/main.html.template
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@
<div class="return-to-top"><span class="pull-right"><a href="#TableOfContents">Top</a></span></div>
{%- endif %}
{%- for function in obj.functions -%}
{% if function.mcdcs %}
{% endif %}
{%- for mcdc in function.mcdcs %}
{% if is_sfp -%}
<div class="mcdc-condition {{mcdc.data.covered | coverage_status_to_class}}" id="mcdc_index_{{mcdc.data.id|e}}">
Expand All @@ -26,6 +24,12 @@
<th>{% trans %}File{% endtrans %}</th>
<td>{{mcdc.unit_name|e}}</td>
</tr>
{%- if show_subprogram %}
<tr>
<th>{% trans %}Subprogram{% endtrans %}</th>
<td>{{function.name|e}}</td>
</tr>
{%- endif %}
{% if is_sfp -%}
<tr style="cursor: pointer;" onclick="mcdcUpArrow('file{{obj.unit_index}}', {{mcdc.data.source_line}}, {{mcdc.data.decision_index}}, {{mcdc.func_start}})">
<th><div class="up_arrow">↑</div>{% trans %}Source line{% endtrans %}</th>
Expand Down
80 changes: 53 additions & 27 deletions python/mcdcReport.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import argparse
import pathlib
import sys
import os

from vector.apps.DataAPI.unit_test_api import UnitTestApi
from vector.apps.DataAPI.cover_api import CoverApi

from pythonUtilities import monkeypatch_custom_css
from pythonUtilities import monkeypatch_custom_css, get_api_context


def parse_args():
Expand Down Expand Up @@ -32,15 +34,21 @@ def parse_args():
def get_mcdc_lines(env):
all_lines_with_data = {}

with UnitTestApi(env) as api:
for unit in api.Unit.filter():
for mcdc_dec in unit.cover_data.mcdc_decisions:
if not mcdc_dec.num_conditions:
continue
if unit.name not in all_lines_with_data:
all_lines_with_data[unit.name] = []
if mcdc_dec.start_line not in all_lines_with_data[unit.name]:
all_lines_with_data[unit.name].append(mcdc_dec.start_line)
# Check if normal env or Cover --> Different API
ApiClass, entity_attr = get_api_context(env)
with ApiClass(env) as api:
sourceObjects = api.SourceFile.all()
for sourceObject in sourceObjects:
unit_file = sourceObject.cover_data.name
unit = os.path.splitext(unit_file)[0]
if sourceObject.is_instrumented:
for mcdc_dec in sourceObject.cover_data.mcdc_decisions:
if not mcdc_dec.num_conditions:
continue
if unit not in all_lines_with_data:
all_lines_with_data[unit] = []
if mcdc_dec.start_line not in all_lines_with_data[unit]:
all_lines_with_data[unit].append(mcdc_dec.start_line)

return all_lines_with_data

Expand All @@ -63,16 +71,26 @@ def generate_mcdc_report(env, unit_filter, line_filter, output):
# Patch get_option to use our CSS without setting the CFG option
monkeypatch_custom_css(custom_css)

# Open-up the unit test API
with UnitTestApi(env) as api:
# Find and check for our unit
# We have to check whether env is the path to a "Normal" env or to a "Cover Project"
# and therefore use a different API
ApiClass, entity_attr = get_api_context(env)

with ApiClass(env) as api:
sourceObjects = api.SourceFile.all()
unit_found = False
for unit in api.Unit.filter(name=unit_filter):
for sourceObject in sourceObjects:
# Find and check for our unit
unit_file = sourceObject.cover_data.name
unit_name = os.path.splitext(unit_file)[0]

if unit_name != unit_filter:
continue

unit_found = True

# Spin through all MCDC decisions looking for the one on our line
line_found = False
for mcdc_dec in unit.cover_data.mcdc_decisions:
for mcdc_dec in sourceObject.cover_data.mcdc_decisions:
# If it has no conditions, then it generates an empty report
#
# TODO: do we want to just generate an empty MCDC report?
Expand All @@ -89,9 +107,16 @@ def generate_mcdc_report(env, unit_filter, line_filter, output):
# Record in the API instance the line number we're interested
# in
#
# NOTE: custom/sections/mini_mcdc.py reads this attribute to
# know what to filter!
api.mcdc_filter = {"unit": unit_filter, "line": line_filter}
# NOTE: custom/sections/per_line_mcdc.py reads this attribute
# to know what to filter!
#
# If the decision lives in a different instrumented file
# (e.g. template instantiations across TUs), filter by
# line only so all instantiations are included.
if mcdc_dec.function.instrumented_file.name == unit_filter:
api.mcdc_filter = {"unit": unit_filter, "line": line_filter}
else:
api.mcdc_filter = {"line": line_filter}

# Generate our report
api.report(
Expand All @@ -102,15 +127,16 @@ def generate_mcdc_report(env, unit_filter, line_filter, output):
)
break

# If we don't find our line, report an error
if not line_found:
raise RuntimeError(f"Could not find line {line}")
if unit_found:
# If we don't find our line, report an error
if not line_found:
raise RuntimeError(f"Could not find line {line_filter} in unit {unit_filter}")
break

# If we don't find our unit, report an error
if not unit_found:
raise RuntimeError(
f"Could not find unit {unit} (units should not have extensions)"
)
if not unit_found:
raise RuntimeError(
f"Could not find unit {unit_filter}"
)


def main():
Expand Down
21 changes: 21 additions & 0 deletions python/pythonUtilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
import re
from vector.apps.DataAPI.configuration import EnvironmentMixin

from vector.apps.DataAPI.unit_test_api import UnitTestApi
from vector.apps.DataAPI.cover_api import CoverApi


# This contains the clicast command that was used to start the data server
globalClicastCommand = ""

Expand Down Expand Up @@ -233,3 +237,20 @@ def repl(match):
return match.group(0)

return env_var_pattern.sub(repl, path)


def get_api_context(env_path):
"""
Determines if the environment is a Cover Project or a standard Unit Test env.
And returns what API we need to use.
"""
clean_path = os.path.normpath(env_path)

# Check if it's a Cover project by checking if there is a vcp file
# with the same name on the same level like the build dir (env_path)
vcp_file = clean_path + ".vcp"
if os.path.isfile(vcp_file):
return CoverApi, "File"

# If there is no vcp file --> normal env
return UnitTestApi, "Unit"
Loading
Loading