From e257c5a3dc476d7bf837827fd8f33bcfbfce122f Mon Sep 17 00:00:00 2001 From: Den1552 Date: Wed, 10 Dec 2025 16:29:02 +0100 Subject: [PATCH 01/42] Initial C Cov commit --- package.json | 3 +- python/mcdcReport.py | 37 ++++++++++++++++++++++--- python/vTestInterface.py | 54 ++++++++++++++++++++++++++++++++++-- src/editorDecorator.ts | 6 +++- src/extension.ts | 11 ++++++-- src/testData.ts | 1 + src/testPane.ts | 58 +++++++++++++++++++++++++++++++++++---- src/utilities.ts | 4 ++- src/vcastTestInterface.ts | 5 +++- src/vcastUtilities.ts | 9 +++--- 10 files changed, 165 insertions(+), 23 deletions(-) diff --git a/package.json b/package.json index cee14e70..ae6b7c26 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,8 @@ "main": "out/extension.js", "activationEvents": [ "workspaceContains:**/UNITDATA.VCD", - "workspaceContains:**/*.vcm" + "workspaceContains:**/*.vcm", + "workspaceContains:**/*.vcp" ], "contributes": { "viewsWelcome": [ diff --git a/python/mcdcReport.py b/python/mcdcReport.py index 3c632695..6df02d13 100644 --- a/python/mcdcReport.py +++ b/python/mcdcReport.py @@ -1,8 +1,10 @@ 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 @@ -29,11 +31,35 @@ def parse_args(): return parser.parse_args() +def get_api_context(env_path): + """ + Determines if the environment is a Cover Project or a standard Unit Test env. + Returns: (ApiClass, collection_name, path_to_use) + """ + clean_path = os.path.normpath(env_path) + + # CASE 1: The user provided the path to the .vcp file directly + if clean_path.lower().endswith(".vcp") and os.path.isfile(clean_path): + return CoverApi, "File" + + # CASE 2: The user provided a folder (e.g. /path/to/env) + # but the VCP file is adjacent (/path/to/env.vcp) + sibling_vcp = clean_path + ".vcp" + if os.path.isfile(sibling_vcp): + return CoverApi, "File" + + # CASE 3: Standard Unit Test Environment (The folder itself) + return UnitTestApi, "Unit" + + def get_mcdc_lines(env): all_lines_with_data = {} - with UnitTestApi(env) as api: - for unit in api.Unit.filter(): + ApiClass, collection_name = get_api_context(env) + + with ApiClass(env) as api: + collection = getattr(api, collection_name) + for unit in collection.filter(): for mcdc_dec in unit.cover_data.mcdc_decisions: if not mcdc_dec.num_conditions: continue @@ -63,11 +89,14 @@ 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) + ApiClass, collection_name = get_api_context(env) + # Open-up the unit test API - with UnitTestApi(env) as api: + with ApiClass(env) as api: + collection = getattr(api, collection_name) # Find and check for our unit unit_found = False - for unit in api.Unit.filter(name=unit_filter): + for unit in collection.filter(name=unit_filter): unit_found = True # Spin through all MCDC decisions looking for the one on our line diff --git a/python/vTestInterface.py b/python/vTestInterface.py index c9b54c0c..10670de7 100644 --- a/python/vTestInterface.py +++ b/python/vTestInterface.py @@ -36,6 +36,7 @@ from vector.apps.DataAPI.manage_api import VCProjectApi from vector.apps.DataAPI.vcproject_models import EnvironmentType from vector.apps.DataAPI.unit_test_api import UnitTestApi +from vector.apps.DataAPI.cover_api import CoverApi from vector.lib.core.system import cd from vector.enums import COVERAGE_TYPE_TYPE_T @@ -413,6 +414,9 @@ def getCoverageData(sourceObject): checksum = sourceObject.checksum coverageKind = getCoverageKind(sourceObject) + # print("----------------") + # print(coverageKind) + # print("----------------") mcdc_line_dic = coverageGutter.getMCDCLineDic(sourceObject) # iterate_coverage crashes if the file path doesn't exist if os.path.exists(sourceObject.path): @@ -735,6 +739,21 @@ def scan_dir(path): return vce_files +def find_vcp_files(root_dir): + vce_files = [] + + def scan_dir(path): + with os.scandir(path) as entries: + for entry in entries: + if entry.is_file() and entry.name.endswith(".vcp"): + vce_files.append(entry.path) + elif entry.is_dir(follow_symlinks=False): + scan_dir(entry.path) + + scan_dir(root_dir) + return vce_files + + def processCommandLogic(mode, clicast, pathToUse, testString="", options=""): """ This function does the actual work of processing a vTestInterface command, @@ -780,10 +799,12 @@ def processCommandLogic(mode, clicast, pathToUse, testString="", options=""): elif mode == "getWorkspaceEnviroData": enviro_list = [] + vcp_list = [] errors = [] topLevel = {} - vce_files = find_vce_files(pathToUse) + # 1. Process VCE (Unit Test) Files + vce_files = find_vce_files(pathToUse) for vce_path in vce_files: try: api = UnitTestApi(vce_path) @@ -800,13 +821,42 @@ def processCommandLogic(mode, clicast, pathToUse, testString="", options=""): "mockingSupport": mocking_support, } ) - except Exception as err: errors.append(f"{vce_path}: {str(err)}") + # 2. Process VCP (Cover) Files + vcp_files = find_vcp_files(pathToUse) + for vcp_path in vcp_files: + try: + api = CoverApi(vcp_path) + test_data = [] # Cover projects do not have test data + unit_data = getUnitData(api) + mocking_support = False # Cover projects do not support mocking + api.close() + + vcp_list.append( + { + "vcpPath": normalize_path(vcp_path), + "testData": test_data, + "unitData": unit_data, + "mockingSupport": mocking_support, + } + ) + except Exception as err: + errors.append(f"{vcp_path}: {str(err)}") + + # 3. Construct Top Level Object + # Defaulting top-level data to the first VCE environment found (if any) topLevel["testData"] = enviro_list[0]["testData"] if enviro_list else [] topLevel["unitData"] = enviro_list[0]["unitData"] if enviro_list else [] + + # If no VCEs exist but VCPs do, you might want to fallback to VCP unitData: + if not topLevel["unitData"] and vcp_list: + topLevel["unitData"] = vcp_list[0]["unitData"] + topLevel["enviro"] = enviro_list + topLevel["vcp"] = vcp_list + if errors: topLevel["errors"] = errors diff --git a/src/editorDecorator.ts b/src/editorDecorator.ts index 9719e6a0..094d45ba 100644 --- a/src/editorDecorator.ts +++ b/src/editorDecorator.ts @@ -37,7 +37,11 @@ export async function updateCurrentActiveUnitMCDCLines() { // Get the unit name based on the file name without extension const fullPath = activeEditor.document.fileName; - const unitName = path.basename(fullPath, path.extname(fullPath)); + let unitName = path.basename(fullPath, path.extname(fullPath)); + + if (enviroPath?.endsWith(".vcp")) { + unitName = unitName + path.extname(fullPath); + } // Get all mcdc lines for every unit and parse it into JSON if (enviroPath) { diff --git a/src/extension.ts b/src/extension.ts index 1caa308f..fa25450a 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1221,8 +1221,15 @@ function configureExtension(context: vscode.ExtensionContext) { const filePath = activeEditor ? activeEditor.document.uri.fsPath : fileFromUri; - const enviroPath = getEnvPathForFilePath(filePath); - const fileName = path.parse(filePath).name; + let enviroPath = getEnvPathForFilePath(filePath); + let fileName = path.parse(filePath).name; + + if (enviroPath?.endsWith(".vcp")) { + fileName = fileName + path.extname(filePath); + const parsed = path.parse(enviroPath); + enviroPath = path.join(parsed.dir, parsed.name); + } + if (enviroPath) { viewMCDCReport(enviroPath, fileName, args.lineNumber); } else { diff --git a/src/testData.ts b/src/testData.ts index d1d01b1d..178e93a0 100644 --- a/src/testData.ts +++ b/src/testData.ts @@ -1,6 +1,7 @@ import { normalizePath, quote } from "./utilities"; export interface environmentNodeDataType { + isVcp: boolean; projectPath: string; buildDirectory: string; isBuilt: boolean; diff --git a/src/testPane.ts b/src/testPane.ts index 2fe7dfaf..bc40fe11 100644 --- a/src/testPane.ts +++ b/src/testPane.ts @@ -135,6 +135,7 @@ type UnitData = { type EnviroData = { vcePath: string; + vcpPath: string; testData: FileTestData[]; unitData: UnitData[]; mockingSupport: boolean; @@ -144,6 +145,7 @@ type CachedWorkspaceData = { testData: FileTestData[]; unitData: UnitData[]; enviro: EnviroData[]; + vcp: EnviroData[]; errors?: string[]; }; @@ -630,6 +632,35 @@ export function addFreeEnvironments( } } +/** + * Adds VCP (VectorCAST Cover Project) files found in the workspace cache to the environment list. + * These are treated as environments but will have no test children. + * @param environmentList A list to push environment data. + * @param workspaceRoot The workspace root directory path. + */ +export function addVcpEnvironments( + environmentList: any[], + workspaceRoot: string +): void { + // Check if the cached data exists and has the 'vcp' key we added in Python + if (cachedWorkspaceEnvData && cachedWorkspaceEnvData.vcp) { + for (const vcpData of cachedWorkspaceEnvData.vcp) { + // vcpData contains: { vcpPath, testData (empty), unitData, mockingSupport } + const normalizedPath = normalizePath(vcpData.vcpPath); + const displayName = path.basename(normalizedPath); + + environmentList.push({ + projectPath: "", // VCPs are usually standalone or treated differently than managed envs + buildDirectory: normalizedPath, // We use the VCP file path as the identifier + isBuilt: true, // VCPs are considered "non-built" / accessible + displayName: displayName, + workspaceRoot: workspaceRoot, + isVcp: true, // Flag to identify this as a VCP node if needed downstream + }); + } + } +} + /** * Checks if the given path is an environment of interest. * @param candidatePath - The path to check @@ -739,17 +770,29 @@ async function loadEnviroData( comingFromRefresh: boolean ): Promise { let buildDirDerivedFromVCEPath: string = ""; + let buildDirDerivedFromVCPPath: string = ""; let buildPathDir: string = enviroData.buildDirectory; if (comingFromRefresh) { // If we've already fetched the full workspace data, reuse it if (cachedWorkspaceEnvData) { - const enviroList = cachedWorkspaceEnvData["enviro"]; - vectorMessage(`Processing environment data for: ${buildPathDir}`); - for (const envAPIData of enviroList) { - buildDirDerivedFromVCEPath = envAPIData.vcePath.split(".vce")[0]; - if (buildDirDerivedFromVCEPath === buildPathDir) { - return envAPIData; + if (enviroData.isVcp) { + const vcpListList = cachedWorkspaceEnvData["vcp"]; + vectorMessage(`Processing coverage project data for: ${buildPathDir}`); + for (const vcpAPIData of vcpListList) { + buildDirDerivedFromVCPPath = vcpAPIData.vcpPath; + if (buildDirDerivedFromVCPPath === buildPathDir) { + return vcpAPIData; + } + } + } else { + const enviroList = cachedWorkspaceEnvData["enviro"]; + vectorMessage(`Processing environment data for: ${buildPathDir}`); + for (const envAPIData of enviroList) { + buildDirDerivedFromVCEPath = envAPIData.vcePath.split(".vce")[0]; + if (buildDirDerivedFromVCEPath === buildPathDir) { + return envAPIData; + } } } } else { @@ -980,6 +1023,9 @@ async function loadAllVCTests( ignoreEnvsInProject, environmentList ); + + // Add VCP (Cover Project) files + addVcpEnvironments(environmentList, workspaceRoot); } if (environmentList.length > 0) { diff --git a/src/utilities.ts b/src/utilities.ts index 16d2ab66..2eca5593 100644 --- a/src/utilities.ts +++ b/src/utilities.ts @@ -406,7 +406,9 @@ export async function updateCoverageAndRebuildEnv() { } // Now rebuild every env so that the coverage is updated for (let enviroPath of envArray) { - await rebuildEnvironment(enviroPath, rebuildEnvironmentCallback); + if (!enviroPath.endsWith(".vcp")) { + await rebuildEnvironment(enviroPath, rebuildEnvironmentCallback); + } } } diff --git a/src/vcastTestInterface.ts b/src/vcastTestInterface.ts index bf107ad5..0eb9b215 100644 --- a/src/vcastTestInterface.ts +++ b/src/vcastTestInterface.ts @@ -189,6 +189,7 @@ export function clearTestDataFromStatusArray(): void { // List of source file from all local environments interface coverageDataType { crc32Checksum: number; + isVCP: boolean; covered: number[]; uncovered: number[]; partiallyCovered: number[]; @@ -248,7 +249,7 @@ export function getCoverageDataForFile(filePath: string): coverageSummaryType { let uncoveredList: number[] = []; let partiallyCoveredList: number[] = []; for (const enviroData of dataForThisFile.enviroList.values()) { - if (enviroData.crc32Checksum == checksum) { + if (enviroData.crc32Checksum == checksum || enviroData.isVCP) { coveredList = coveredList.concat(enviroData.covered); uncoveredList = uncoveredList.concat(enviroData.uncovered); partiallyCoveredList = partiallyCoveredList.concat( @@ -338,7 +339,9 @@ export function updateGlobalDataForFile(enviroPath: string, fileList: any[]) { .map(Number); const checksum = fileList[fileIndex].cmcChecksum; + let coverageData: coverageDataType = { + isVCP: enviroPath.endsWith(".vcp"), crc32Checksum: checksum, covered: coveredList, uncovered: uncoveredList, diff --git a/src/vcastUtilities.ts b/src/vcastUtilities.ts index d19f83b0..43fc4a6a 100644 --- a/src/vcastUtilities.ts +++ b/src/vcastUtilities.ts @@ -444,11 +444,10 @@ export function getVcastInterfaceCommandForMCDC( let optionsDict: { [command: string]: string | number } = {}; optionsDict["unitName"] = unitName; optionsDict["lineNumber"] = lineNumber; - const jsonOptions: string = JSON.stringify(optionsDict).replaceAll( - '"', - '\\"' - ); - const testArgument = `--options="${jsonOptions}"`; + + const jsonOptions: string = JSON.stringify(optionsDict); + const testArgument = `--options='${jsonOptions}'`; + return `${commandToRun} ${testArgument}`; } From 5d86103b137d959515af2bcf68bd23d46c1abd87 Mon Sep 17 00:00:00 2001 From: Den1552 Date: Wed, 10 Dec 2025 17:18:31 +0100 Subject: [PATCH 02/42] Switched code to utilities --- python/mcdcReport.py | 27 +++++---------------------- python/pythonUtilities.py | 17 +++++++++++++++++ 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/python/mcdcReport.py b/python/mcdcReport.py index 6df02d13..09b29b1a 100644 --- a/python/mcdcReport.py +++ b/python/mcdcReport.py @@ -6,7 +6,7 @@ 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(): @@ -31,30 +31,11 @@ def parse_args(): return parser.parse_args() -def get_api_context(env_path): - """ - Determines if the environment is a Cover Project or a standard Unit Test env. - Returns: (ApiClass, collection_name, path_to_use) - """ - clean_path = os.path.normpath(env_path) - - # CASE 1: The user provided the path to the .vcp file directly - if clean_path.lower().endswith(".vcp") and os.path.isfile(clean_path): - return CoverApi, "File" - - # CASE 2: The user provided a folder (e.g. /path/to/env) - # but the VCP file is adjacent (/path/to/env.vcp) - sibling_vcp = clean_path + ".vcp" - if os.path.isfile(sibling_vcp): - return CoverApi, "File" - - # CASE 3: Standard Unit Test Environment (The folder itself) - return UnitTestApi, "Unit" - - def get_mcdc_lines(env): all_lines_with_data = {} + # 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, collection_name = get_api_context(env) with ApiClass(env) as api: @@ -89,6 +70,8 @@ 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) + # 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, collection_name = get_api_context(env) # Open-up the unit test API diff --git a/python/pythonUtilities.py b/python/pythonUtilities.py index cc70ce89..4c5d1e23 100644 --- a/python/pythonUtilities.py +++ b/python/pythonUtilities.py @@ -233,3 +233,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" From 4063e02e1280e398ef3d4368a5ad4db64e452066 Mon Sep 17 00:00:00 2001 From: Den1552 Date: Wed, 10 Dec 2025 18:19:37 +0100 Subject: [PATCH 03/42] Corrected initial mcdc line fetch --- python/pythonUtilities.py | 4 ++++ src/editorDecorator.ts | 5 ++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/python/pythonUtilities.py b/python/pythonUtilities.py index 4c5d1e23..7999ea7e 100644 --- a/python/pythonUtilities.py +++ b/python/pythonUtilities.py @@ -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 = "" diff --git a/src/editorDecorator.ts b/src/editorDecorator.ts index 094d45ba..af1ec921 100644 --- a/src/editorDecorator.ts +++ b/src/editorDecorator.ts @@ -33,14 +33,17 @@ export async function updateCurrentActiveUnitMCDCLines() { if (activeEditor) { // First we need to get the env name from the active file const filePath = activeEditor.document.uri.fsPath; - const enviroPath = getEnvPathForFilePath(filePath); + let enviroPath = getEnvPathForFilePath(filePath); // Get the unit name based on the file name without extension const fullPath = activeEditor.document.fileName; let unitName = path.basename(fullPath, path.extname(fullPath)); + // If the file is in a cover project, we need adapt the paths if (enviroPath?.endsWith(".vcp")) { unitName = unitName + path.extname(fullPath); + const parsed = path.parse(enviroPath); + enviroPath = path.join(parsed.dir, parsed.name); } // Get all mcdc lines for every unit and parse it into JSON From ddd2e43e3afb0f6f62b07567708183381f93e463 Mon Sep 17 00:00:00 2001 From: Den1552 Date: Thu, 11 Dec 2025 09:19:03 +0100 Subject: [PATCH 04/42] Removed commented code --- python/vTestInterface.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/python/vTestInterface.py b/python/vTestInterface.py index 10670de7..bd21ea1e 100644 --- a/python/vTestInterface.py +++ b/python/vTestInterface.py @@ -414,9 +414,6 @@ def getCoverageData(sourceObject): checksum = sourceObject.checksum coverageKind = getCoverageKind(sourceObject) - # print("----------------") - # print(coverageKind) - # print("----------------") mcdc_line_dic = coverageGutter.getMCDCLineDic(sourceObject) # iterate_coverage crashes if the file path doesn't exist if os.path.exists(sourceObject.path): From ddce4e078336366aecf0040047a4ee9df3adc1d6 Mon Sep 17 00:00:00 2001 From: Den1552 Date: Thu, 11 Dec 2025 15:27:50 +0100 Subject: [PATCH 05/42] Refactoring --- package.json | 3 ++- python/mcdcReport.py | 17 ++++++++-------- python/vTestInterface.py | 44 +++++++++++++++++----------------------- src/testPane.ts | 4 ++-- 4 files changed, 31 insertions(+), 37 deletions(-) diff --git a/package.json b/package.json index ae6b7c26..6e16d53c 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,8 @@ "activationEvents": [ "workspaceContains:**/UNITDATA.VCD", "workspaceContains:**/*.vcm", - "workspaceContains:**/*.vcp" + "workspaceContains:**/*.vcp", + "workspaceContains:**/*.env" ], "contributes": { "viewsWelcome": [ diff --git a/python/mcdcReport.py b/python/mcdcReport.py index 09b29b1a..1bf364e7 100644 --- a/python/mcdcReport.py +++ b/python/mcdcReport.py @@ -34,13 +34,13 @@ def parse_args(): def get_mcdc_lines(env): all_lines_with_data = {} - # 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, collection_name = get_api_context(env) + ApiClass, entity_attr = get_api_context(env) with ApiClass(env) as api: - collection = getattr(api, collection_name) - for unit in collection.filter(): + # Access the API property (api.Unit or api.File) dynamically + source_modules = getattr(api, entity_attr) + + for unit in source_modules.filter(): for mcdc_dec in unit.cover_data.mcdc_decisions: if not mcdc_dec.num_conditions: continue @@ -72,14 +72,13 @@ def generate_mcdc_report(env, unit_filter, line_filter, output): # 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, collection_name = get_api_context(env) + ApiClass, entity_attr = get_api_context(env) - # Open-up the unit test API with ApiClass(env) as api: - collection = getattr(api, collection_name) + source_modules = getattr(api, entity_attr) # Find and check for our unit unit_found = False - for unit in collection.filter(name=unit_filter): + for unit in source_modules.filter(name=unit_filter): unit_found = True # Spin through all MCDC decisions looking for the one on our line diff --git a/python/vTestInterface.py b/python/vTestInterface.py index bd21ea1e..3943a0e9 100644 --- a/python/vTestInterface.py +++ b/python/vTestInterface.py @@ -721,34 +721,28 @@ def getProjectCompilerData(api): return compilerList -def find_vce_files(root_dir): - vce_files = [] - - def scan_dir(path): - with os.scandir(path) as entries: - for entry in entries: - if entry.is_file() and entry.name.endswith(".vce"): - vce_files.append(entry.path) - elif entry.is_dir(follow_symlinks=False): - scan_dir(entry.path) - - scan_dir(root_dir) - return vce_files - - -def find_vcp_files(root_dir): +def find_environment_files(root_dir): + """ + Scans the directory tree once and returns two lists: + 1. vce_files: Paths to vce files for Unit Test environments + 2. vcp_files: Paths to vcp files for Cover Projects + """ vce_files = [] + vcp_files = [] def scan_dir(path): with os.scandir(path) as entries: for entry in entries: - if entry.is_file() and entry.name.endswith(".vcp"): - vce_files.append(entry.path) + if entry.is_file(): + if entry.name.endswith(".vce"): + vce_files.append(entry.path) + elif entry.name.endswith(".vcp"): + vcp_files.append(entry.path) elif entry.is_dir(follow_symlinks=False): scan_dir(entry.path) scan_dir(root_dir) - return vce_files + return vce_files, vcp_files def processCommandLogic(mode, clicast, pathToUse, testString="", options=""): @@ -800,8 +794,10 @@ def processCommandLogic(mode, clicast, pathToUse, testString="", options=""): errors = [] topLevel = {} - # 1. Process VCE (Unit Test) Files - vce_files = find_vce_files(pathToUse) + # Scan directory for both file types + vce_files, vcp_files = find_environment_files(pathToUse) + + # Process VCE Files for vce_path in vce_files: try: api = UnitTestApi(vce_path) @@ -821,8 +817,7 @@ def processCommandLogic(mode, clicast, pathToUse, testString="", options=""): except Exception as err: errors.append(f"{vce_path}: {str(err)}") - # 2. Process VCP (Cover) Files - vcp_files = find_vcp_files(pathToUse) + # Process VCP Files for vcp_path in vcp_files: try: api = CoverApi(vcp_path) @@ -842,12 +837,11 @@ def processCommandLogic(mode, clicast, pathToUse, testString="", options=""): except Exception as err: errors.append(f"{vcp_path}: {str(err)}") - # 3. Construct Top Level Object + # Construct Top Level Object # Defaulting top-level data to the first VCE environment found (if any) topLevel["testData"] = enviro_list[0]["testData"] if enviro_list else [] topLevel["unitData"] = enviro_list[0]["unitData"] if enviro_list else [] - # If no VCEs exist but VCPs do, you might want to fallback to VCP unitData: if not topLevel["unitData"] and vcp_list: topLevel["unitData"] = vcp_list[0]["unitData"] diff --git a/src/testPane.ts b/src/testPane.ts index bc40fe11..e7519e47 100644 --- a/src/testPane.ts +++ b/src/testPane.ts @@ -777,9 +777,9 @@ async function loadEnviroData( // If we've already fetched the full workspace data, reuse it if (cachedWorkspaceEnvData) { if (enviroData.isVcp) { - const vcpListList = cachedWorkspaceEnvData["vcp"]; + const vcpList = cachedWorkspaceEnvData["vcp"]; vectorMessage(`Processing coverage project data for: ${buildPathDir}`); - for (const vcpAPIData of vcpListList) { + for (const vcpAPIData of vcpList) { buildDirDerivedFromVCPPath = vcpAPIData.vcpPath; if (buildDirDerivedFromVCPPath === buildPathDir) { return vcpAPIData; From d8e12fee07e8a15403fe534e112909cc60df478d Mon Sep 17 00:00:00 2001 From: Den1552 Date: Thu, 11 Dec 2025 15:40:44 +0100 Subject: [PATCH 06/42] Refactoring 2 --- src/editorDecorator.ts | 16 ++++++++++------ src/extension.ts | 16 +++++++++------- src/utilities.ts | 22 ++++++++++++++++++++++ 3 files changed, 41 insertions(+), 13 deletions(-) diff --git a/src/editorDecorator.ts b/src/editorDecorator.ts index af1ec921..275e18bb 100644 --- a/src/editorDecorator.ts +++ b/src/editorDecorator.ts @@ -3,7 +3,11 @@ import { DecorationRenderOptions, TextEditorDecorationType } from "vscode"; import { testNodeType } from "./testData"; -import { getEnvPathForFilePath, getRangeOption } from "./utilities"; +import { + getEnvPathForFilePath, + getRangeOption, + resolveVcpPaths, +} from "./utilities"; import { checksumMatchesEnvironment } from "./vcastTestInterface"; import { getMCDCCoverageLines } from "./vcastAdapter"; @@ -40,11 +44,11 @@ export async function updateCurrentActiveUnitMCDCLines() { let unitName = path.basename(fullPath, path.extname(fullPath)); // If the file is in a cover project, we need adapt the paths - if (enviroPath?.endsWith(".vcp")) { - unitName = unitName + path.extname(fullPath); - const parsed = path.parse(enviroPath); - enviroPath = path.join(parsed.dir, parsed.name); - } + ({ enviroPath, unitName } = resolveVcpPaths( + enviroPath, + unitName, + fullPath + )); // Get all mcdc lines for every unit and parse it into JSON if (enviroPath) { diff --git a/src/extension.ts b/src/extension.ts index fa25450a..99a2ba60 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -79,6 +79,7 @@ import { forceLowerCaseDriveLetter, decodeVar, getFullEnvReport, + resolveVcpPaths, } from "./utilities"; import { @@ -1222,16 +1223,17 @@ function configureExtension(context: vscode.ExtensionContext) { ? activeEditor.document.uri.fsPath : fileFromUri; let enviroPath = getEnvPathForFilePath(filePath); - let fileName = path.parse(filePath).name; + let unitName = path.parse(filePath).name; - if (enviroPath?.endsWith(".vcp")) { - fileName = fileName + path.extname(filePath); - const parsed = path.parse(enviroPath); - enviroPath = path.join(parsed.dir, parsed.name); - } + // If the file is in a cover project, we need adapt the paths + ({ enviroPath, unitName } = resolveVcpPaths( + enviroPath, + unitName, + filePath + )); if (enviroPath) { - viewMCDCReport(enviroPath, fileName, args.lineNumber); + viewMCDCReport(enviroPath, unitName, args.lineNumber); } else { vscode.window.showErrorMessage( `Did not find environment name ${enviroPath} or path for file: ${filePath}` diff --git a/src/utilities.ts b/src/utilities.ts index 2eca5593..aa825fbc 100644 --- a/src/utilities.ts +++ b/src/utilities.ts @@ -459,3 +459,25 @@ export async function getFullEnvReport( // Return the generated HTML file path return htmlReportPath; } + +/** + * Checks if the environment is a Cover Project (.vcp). + * If so, it adjusts the environment path (removing .vcp) and appends the + * file extension to the unit name, as required by the Cover API. + */ +export function resolveVcpPaths( + enviroPath: string | null, + unitName: string, + fullFilePath: string +): { enviroPath: string | null; unitName: string } { + if (enviroPath?.endsWith(".vcp")) { + // Cover projects require the full filename (e.g. "manager.c" instead of "manager") + unitName = unitName + path.extname(fullFilePath); + + // The build directory for VCP is the path without the .vcp extension + const parsed = path.parse(enviroPath); + enviroPath = path.join(parsed.dir, parsed.name); + } + + return { enviroPath, unitName }; +} From a6065e5bb9afff331c97ca7e96dc5444c5809101 Mon Sep 17 00:00:00 2001 From: Den1552 Date: Thu, 11 Dec 2025 17:12:12 +0100 Subject: [PATCH 07/42] Removed all buttons from vcp nodes --- package.json | 52 ++++++++++++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/package.json b/package.json index 6e16d53c..d5e2244c 100644 --- a/package.json +++ b/package.json @@ -967,97 +967,97 @@ { "command": "vectorcastTestExplorer.updateProjectEnvironment", "group": "vcast.project", - "when": "(testId =~ /^vcast:.*$/ && (testId in vectorcastTestExplorer.vcastUnbuiltEnviroList || testId in vectorcastTestExplorer.vcastEnviroList) && vectorcastTestExplorer.globalProjectIsOpenedChecker) && !config.vectorcastTestExplorer.automaticallyUpdateManageProject" + "when": "(testId =~ /^vcast:.*$/ && (testId in vectorcastTestExplorer.vcastUnbuiltEnviroList || testId in vectorcastTestExplorer.vcastEnviroList) && vectorcastTestExplorer.globalProjectIsOpenedChecker) && !config.vectorcastTestExplorer.automaticallyUpdateManageProject && !(testId =~ /\\.vcp$/)" }, { "command": "vectorcastTestExplorer.buildProjectEnviro", "group": "vcast.build", - "when": "testId =~ /^vcast:.*$/ && testId in vectorcastTestExplorer.vcastUnbuiltEnviroList" + "when": "testId =~ /^vcast:.*$/ && testId in vectorcastTestExplorer.vcastUnbuiltEnviroList && !(testId =~ /\\.vcp$/)" }, { "command": "vectorcastTestExplorer.getEnvFullReport", "group": "vcast.build", - "when": "testId =~ /^vcast:.*$/ && testId in vectorcastTestExplorer.vcastEnviroList" + "when": "testId =~ /^vcast:.*$/ && testId in vectorcastTestExplorer.vcastEnviroList && !(testId =~ /\\.vcp$/)" }, { "command": "vectorcastTestExplorer.buildExecuteIncremental", "group": "vcast.build", - "when": "vectorcastTestExplorer.globalProjectIsOpenedChecker && ( testId =~ /\\.vcm$/ || testId in vectorcastTestExplorer.globalProjectCompilers || testId in vectorcastTestExplorer.globalProjectTestsuites || (testId =~ /^vcast:.*$/ && ( testId in vectorcastTestExplorer.vcastEnviroList || testId in vectorcastTestExplorer.vcastUnbuiltEnviroList)))" + "when": "vectorcastTestExplorer.globalProjectIsOpenedChecker && ( testId =~ /\\.vcm$/ || testId in vectorcastTestExplorer.globalProjectCompilers || testId in vectorcastTestExplorer.globalProjectTestsuites || (testId =~ /^vcast:.*$/ && ( testId in vectorcastTestExplorer.vcastEnviroList || testId in vectorcastTestExplorer.vcastUnbuiltEnviroList))) && !(testId =~ /\\.vcp$/)" }, { "command": "vectorcastTestExplorer.rebuildEnviro", "group": "vcast.build", - "when": "testId =~ /^vcast:.*$/ && testId in vectorcastTestExplorer.vcastEnviroList && testId not in vectorcastTestExplorer.vcastUnbuiltEnviroList" + "when": "testId =~ /^vcast:.*$/ && testId in vectorcastTestExplorer.vcastEnviroList && testId not in vectorcastTestExplorer.vcastUnbuiltEnviroList && !(testId =~ /\\.vcp$/)" }, { "command": "vectorcastTestExplorer.cleanEnviro", "group": "vcast.build", - "when": "testId =~ /^vcast:.*$/ && testId in vectorcastTestExplorer.vcastEnviroList && vectorcastTestExplorer.globalProjectIsOpenedChecker && testId not in vectorcastTestExplorer.vcastUnbuiltEnviroList" + "when": "testId =~ /^vcast:.*$/ && testId in vectorcastTestExplorer.vcastEnviroList && vectorcastTestExplorer.globalProjectIsOpenedChecker && testId not in vectorcastTestExplorer.vcastUnbuiltEnviroList && !(testId =~ /\\.vcp$/)" }, { "command": "vectorcastTestExplorer.openVCAST", "group": "vcast.enviroManagement", - "when": "testId =~ /^vcast:.*$/ && testId in vectorcastTestExplorer.vcastEnviroList && testId not in vectorcastTestExplorer.vcastUnbuiltEnviroList" + "when": "testId =~ /^vcast:.*$/ && testId in vectorcastTestExplorer.vcastEnviroList && testId not in vectorcastTestExplorer.vcastUnbuiltEnviroList && !(testId =~ /\\.vcp$/)" }, { "command": "vectorcastTestExplorer.deleteEnviro", "group": "vcast.enviroManagement", - "when": "testId =~ /^vcast:.*$/ && testId in vectorcastTestExplorer.vcastEnviroList && !vectorcastTestExplorer.globalProjectIsOpenedChecker" + "when": "testId =~ /^vcast:.*$/ && testId in vectorcastTestExplorer.vcastEnviroList && !vectorcastTestExplorer.globalProjectIsOpenedChecker && !(testId =~ /\\.vcp$/)" }, { "command": "vectorcastTestExplorer.deleteEnviroFromProject", "group": "vcast.enviroManagement", - "when": "testId =~ /^vcast:.*$/ && (testId in vectorcastTestExplorer.vcastUnbuiltEnviroList || testId in vectorcastTestExplorer.vcastEnviroList) && vectorcastTestExplorer.globalProjectIsOpenedChecker" + "when": "testId =~ /^vcast:.*$/ && (testId in vectorcastTestExplorer.vcastUnbuiltEnviroList || testId in vectorcastTestExplorer.vcastEnviroList) && vectorcastTestExplorer.globalProjectIsOpenedChecker && !(testId =~ /\\.vcp$/)" }, { "command": "vectorcastTestExplorer.removeTestsuite", "group": "vcast.enviroManagement", - "when": "testId =~ /^vcast:.*$/ && (testId in vectorcastTestExplorer.vcastUnbuiltEnviroList || testId in vectorcastTestExplorer.vcastEnviroList) && vectorcastTestExplorer.globalProjectIsOpenedChecker" + "when": "testId =~ /^vcast:.*$/ && (testId in vectorcastTestExplorer.vcastUnbuiltEnviroList || testId in vectorcastTestExplorer.vcastEnviroList) && vectorcastTestExplorer.globalProjectIsOpenedChecker && !(testId =~ /\\.vcp$/)" }, { "command": "vectorcastTestExplorer.editTestScript", "group": "vcast.testScript", - "when": "testId =~ /^vcast:.*$/ && !(testId =~ /.*coded_tests_driver.*/) && testId not in vectorcastTestExplorer.vcastUnbuiltEnviroList" + "when": "testId =~ /^vcast:.*$/ && !(testId =~ /.*coded_tests_driver.*/) && testId not in vectorcastTestExplorer.vcastUnbuiltEnviroList && !(testId =~ /\\.vcp$/)" }, { "command": "vectorcastTestExplorer.createTestScript", "group": "vcast.testScript", - "when": "testId =~ /^vcast:.*$/ && testId not in vectorcastTestExplorer.vcastEnviroList && testId not in vectorcastTestExplorer.vcastUnbuiltEnviroList && !(testId =~ /.*coded_tests_driver.*/ )" + "when": "testId =~ /^vcast:.*$/ && testId not in vectorcastTestExplorer.vcastEnviroList && testId not in vectorcastTestExplorer.vcastUnbuiltEnviroList && !(testId =~ /.*coded_tests_driver.*/ ) && !(testId =~ /\\.vcp$/)" }, { "command": "vectorcastTestExplorer.editCodedTest", "group": "vcast.testScript", - "when": "testId =~ /^vcast:.*$/ && testId not in vectorcastTestExplorer.vcastEnviroList && testId not in vectorcastTestExplorer.vcastUnbuiltEnviroList && testId =~ /.*coded_tests_driver.+/" + "when": "testId =~ /^vcast:.*$/ && testId not in vectorcastTestExplorer.vcastEnviroList && testId not in vectorcastTestExplorer.vcastUnbuiltEnviroList && testId =~ /.*coded_tests_driver.+/ && !(testId =~ /\\.vcp$/)" }, { "command": "vectorcastTestExplorer.addCodedTests", "group": "vcast.testScript", - "when": "testId =~ /^vcast:.*$/ && testId =~ /.*coded_tests_driver$/ && testId not in vectorcastTestExplorer.vcastHasCodedTestsList" + "when": "testId =~ /^vcast:.*$/ && testId =~ /.*coded_tests_driver$/ && testId not in vectorcastTestExplorer.vcastHasCodedTestsList && !(testId =~ /\\.vcp$/)" }, { "command": "vectorcastTestExplorer.generateCodedTests", "group": "vcast.testScript", - "when": "testId =~ /^vcast:.*$/ && testId =~ /.*coded_tests_driver$/ && testId not in vectorcastTestExplorer.vcastHasCodedTestsList" + "when": "testId =~ /^vcast:.*$/ && testId =~ /.*coded_tests_driver$/ && testId not in vectorcastTestExplorer.vcastHasCodedTestsList && !(testId =~ /\\.vcp$/)" }, { "command": "vectorcastTestExplorer.removeCodedTests", "group": "vcast.testScript", - "when": "testId =~ /^vcast:.*$/ && testId =~ /.*coded_tests_driver$/ && testId in vectorcastTestExplorer.vcastHasCodedTestsList" + "when": "testId =~ /^vcast:.*$/ && testId =~ /.*coded_tests_driver$/ && testId in vectorcastTestExplorer.vcastHasCodedTestsList && !(testId =~ /\\.vcp$/)" }, { "command": "vectorcastTestExplorer.insertBasisPathTests", "group": "vcast.testGeneration", - "when": "testId =~ /^vcast:.*$/ && !(testId =~ /^.*<>.*$/) && !(testId =~ /^.*<>.*$/) && !(testId =~ /.*coded_tests_driver.*/) && testId not in vectorcastTestExplorer.vcastUnbuiltEnviroList" + "when": "testId =~ /^vcast:.*$/ && !(testId =~ /^.*<>.*$/) && !(testId =~ /^.*<>.*$/) && !(testId =~ /.*coded_tests_driver.*/) && testId not in vectorcastTestExplorer.vcastUnbuiltEnviroList && !(testId =~ /\\.vcp$/)" }, { "command": "vectorcastTestExplorer.insertATGTests", "group": "vcast.testGeneration", - "when": "testId =~ /^vcast:.*$/ && vectorcastTestExplorer.atgAvailable && !(testId =~ /^.*<>.*$/) && !(testId =~ /^.*<>.*$/) && !(testId =~ /.*coded_tests_driver.*/) && testId not in vectorcastTestExplorer.vcastUnbuiltEnviroList" + "when": "testId =~ /^vcast:.*$/ && vectorcastTestExplorer.atgAvailable && !(testId =~ /^.*<>.*$/) && !(testId =~ /^.*<>.*$/) && !(testId =~ /.*coded_tests_driver.*/) && testId not in vectorcastTestExplorer.vcastUnbuiltEnviroList && !(testId =~ /\\.vcp$/)" }, { "command": "vectorcastTestExplorer.deleteTest", "group": "vcast.delete", - "when": "testId =~ /^vcast:.*$/ && !(testId =~ /.*coded_tests_driver.*/) && testId not in vectorcastTestExplorer.vcastUnbuiltEnviroList" + "when": "testId =~ /^vcast:.*$/ && !(testId =~ /.*coded_tests_driver.*/) && testId not in vectorcastTestExplorer.vcastUnbuiltEnviroList && !(testId =~ /\\.vcp$/)" }, { "command": "vectorcastTestExplorer.deleteTestsuite", @@ -1072,37 +1072,37 @@ { "command": "vectorcastTestExplorer.importRequirementsFromGateway", "group": "vcast@8", - "when": "testId =~ /^vcast:.*$/ && testId in vectorcastTestExplorer.vcastEnviroList && vectorcastTestExplorer.reqs2xFeatureEnabled && testId not in vectorcastTestExplorer.vcastRequirementsAvailable && vectorcastTestExplorer.generateRequirementsEnabled" + "when": "testId =~ /^vcast:.*$/ && testId in vectorcastTestExplorer.vcastEnviroList && vectorcastTestExplorer.reqs2xFeatureEnabled && testId not in vectorcastTestExplorer.vcastRequirementsAvailable && vectorcastTestExplorer.generateRequirementsEnabled && !(testId =~ /\\.vcp$/)" }, { "command": "vectorcastTestExplorer.generateRequirements", "group": "vcast@8", - "when": "testId =~ /^vcast:.*$/ && testId in vectorcastTestExplorer.vcastEnviroList && vectorcastTestExplorer.reqs2xFeatureEnabled && testId not in vectorcastTestExplorer.vcastRequirementsAvailable && vectorcastTestExplorer.generateRequirementsEnabled" + "when": "testId =~ /^vcast:.*$/ && testId in vectorcastTestExplorer.vcastEnviroList && vectorcastTestExplorer.reqs2xFeatureEnabled && testId not in vectorcastTestExplorer.vcastRequirementsAvailable && vectorcastTestExplorer.generateRequirementsEnabled && !(testId =~ /\\.vcp$/)" }, { "command": "vectorcastTestExplorer.showRequirements", "group": "vcast@9", - "when": "testId =~ /^vcast:.*$/ && testId in vectorcastTestExplorer.vcastEnviroList && vectorcastTestExplorer.reqs2xFeatureEnabled && testId in vectorcastTestExplorer.vcastRequirementsAvailable && vectorcastTestExplorer.generateRequirementsEnabled" + "when": "testId =~ /^vcast:.*$/ && testId in vectorcastTestExplorer.vcastEnviroList && vectorcastTestExplorer.reqs2xFeatureEnabled && testId in vectorcastTestExplorer.vcastRequirementsAvailable && vectorcastTestExplorer.generateRequirementsEnabled && !(testId =~ /\\.vcp$/)" }, { "command": "vectorcastTestExplorer.generateTestsFromRequirements", "group": "vcast@10", - "when": "testId =~ /^vcast:.*$/ && vectorcastTestExplorer.reqs2xFeatureEnabled" + "when": "testId =~ /^vcast:.*$/ && vectorcastTestExplorer.reqs2xFeatureEnabled && !(testId =~ /\\.vcp$/)" }, { "command": "vectorcastTestExplorer.removeRequirements", "group": "vcast@9", - "when": "testId =~ /^vcast:.*$/ && vectorcastTestExplorer.reqs2xFeatureEnabled && testId in vectorcastTestExplorer.vcastRequirementsAvailable && vectorcastTestExplorer.generateRequirementsEnabled" + "when": "testId =~ /^vcast:.*$/ && vectorcastTestExplorer.reqs2xFeatureEnabled && testId in vectorcastTestExplorer.vcastRequirementsAvailable && vectorcastTestExplorer.generateRequirementsEnabled && !(testId =~ /\\.vcp$/)" }, { "command": "vectorcastTestExplorer.populateRequirementsGateway", "group": "vcast@9", - "when": "testId =~ /^vcast:.*$/ && vectorcastTestExplorer.reqs2xFeatureEnabled && testId in vectorcastTestExplorer.vcastRequirementsAvailable && vectorcastTestExplorer.generateRequirementsEnabled" + "when": "testId =~ /^vcast:.*$/ && vectorcastTestExplorer.reqs2xFeatureEnabled && testId in vectorcastTestExplorer.vcastRequirementsAvailable && vectorcastTestExplorer.generateRequirementsEnabled && !(testId =~ /\\.vcp$/)" }, { "command": "vectorcastTestExplorer.viewResults", "group": "vcast.results", - "when": "testId =~ /^vcast:.*$/ && testId not in vectorcastTestExplorer.vcastEnviroList && testId not in vectorcastTestExplorer.vcastUnbuiltEnviroList" + "when": "testId =~ /^vcast:.*$/ && testId not in vectorcastTestExplorer.vcastEnviroList && testId not in vectorcastTestExplorer.vcastUnbuiltEnviroList && !(testId =~ /\\.vcp$/)" } ] }, From f66035d560f1f28ef6852bb021fc1efaeb226dd8 Mon Sep 17 00:00:00 2001 From: Den1552 Date: Thu, 18 Dec 2025 14:46:05 +0100 Subject: [PATCH 08/42] C Coverage Tests --- package.json | 3 +- src/editorDecorator.ts | 3 +- .../e2e/test/cCoverage/c_cov_example.sh | 55 ++++++ .../e2e/test/specs/vcast_c_tests.test.ts | 186 ++++++++++++++++++ tests/internal/e2e/test/specs_config.ts | 8 + tests/internal/e2e/test/wdio.conf.ts | 26 +++ 6 files changed, 278 insertions(+), 3 deletions(-) create mode 100644 tests/internal/e2e/test/cCoverage/c_cov_example.sh create mode 100644 tests/internal/e2e/test/specs/vcast_c_tests.test.ts diff --git a/package.json b/package.json index d5e2244c..e511b868 100644 --- a/package.json +++ b/package.json @@ -34,8 +34,7 @@ "activationEvents": [ "workspaceContains:**/UNITDATA.VCD", "workspaceContains:**/*.vcm", - "workspaceContains:**/*.vcp", - "workspaceContains:**/*.env" + "workspaceContains:**/*.vcp" ], "contributes": { "viewsWelcome": [ diff --git a/src/editorDecorator.ts b/src/editorDecorator.ts index 275e18bb..679bf24b 100644 --- a/src/editorDecorator.ts +++ b/src/editorDecorator.ts @@ -6,6 +6,7 @@ import { testNodeType } from "./testData"; import { getEnvPathForFilePath, getRangeOption, + normalizePath, resolveVcpPaths, } from "./utilities"; @@ -36,7 +37,7 @@ export async function updateCurrentActiveUnitMCDCLines() { let activeEditor = vscode.window.activeTextEditor; if (activeEditor) { // First we need to get the env name from the active file - const filePath = activeEditor.document.uri.fsPath; + const filePath = normalizePath(activeEditor.document.uri.fsPath); let enviroPath = getEnvPathForFilePath(filePath); // Get the unit name based on the file name without extension diff --git a/tests/internal/e2e/test/cCoverage/c_cov_example.sh b/tests/internal/e2e/test/cCoverage/c_cov_example.sh new file mode 100644 index 00000000..b51cd3d0 --- /dev/null +++ b/tests/internal/e2e/test/cCoverage/c_cov_example.sh @@ -0,0 +1,55 @@ +#!/bin/sh -ex + +# Always start with a clean working directory +rm -fr work +mkdir work +cd work +export WORK_DIRECTORY=$PWD +# export VECTORCAST_DIR=/home/JOBDATA/VectorCAST/vc20__86806_vcwrap__87490_inst_mod/deliver/linux64/debug + +# Download lua 5.3.5 for instrumentation +time wget https://www.lua.org/ftp/lua-5.4.0.tar.gz +time tar -zxvf lua-5.4.0.tar.gz + + +# Configuration VectorCAST, including the compiler +$VECTORCAST_DIR/clicast template GNU_CPP11_X +$VECTORCAST_DIR/clicast option VCAST_COVERAGE_FOR_HEADERS TRUE +$VECTORCAST_DIR/clicast option VCAST_COVERAGE_FOR_AGGREGATE_INIT TRUE + + +# Create the cover environment +time $VECTORCAST_DIR/clicast cover environment script_run ../env.enc + + +$VECTORCAST_DIR/clicast -e env cover env disable_instrumentation + + +# These commands will be put into a single clicast build command + + # Single step instrument and build + cd lua-5.4.0/src + + # The command that instruments and builds + time make generic -j16 -B + + # Add the instrumentation data into the cover environment + cd $WORK_DIRECTORY + +# Run the tests +mkdir lua_tests +cd lua_tests +time wget https://www.lua.org/tests/lua-5.4.0-tests.tar.gz +time tar -zxvf lua-5.4.0-tests.tar.gz +cd lua-5.4.0-tests +set +e +../../lua-5.4.0/src/lua all.lua +mv TESTINSS.DAT TESTINSS.DAT.all.lua +../../lua-5.4.0/src/lua api.lua +mv TESTINSS.DAT TESTINSS.DAT.api.lua +set -e + +# Add the test suite result to the cover environment +cd $WORK_DIRECTORY +time $VECTORCAST_DIR/clicast -e env cover result add lua_tests/lua-5.4.0-tests/TESTINSS.DAT.all.lua all.lua +time $VECTORCAST_DIR/clicast -e env cover result add lua_tests/lua-5.4.0-tests/TESTINSS.DAT.api.lua api.lua \ No newline at end of file diff --git a/tests/internal/e2e/test/specs/vcast_c_tests.test.ts b/tests/internal/e2e/test/specs/vcast_c_tests.test.ts new file mode 100644 index 00000000..22df4eac --- /dev/null +++ b/tests/internal/e2e/test/specs/vcast_c_tests.test.ts @@ -0,0 +1,186 @@ +// Test/specs/vcast.test.ts +import { + TextEditor, + type BottomBarPanel, + type Workbench, +} from "wdio-vscode-service"; +import { Key } from "webdriverio"; +import { + releaseCtrl, + executeCtrlClickOn, + expandWorkspaceFolderSectionInExplorer, + updateTestID, + checkIfRequestInLogs, + checkElementExistsInHTML, +} from "../test_utils/vcast_utils"; +import { TIMEOUT } from "../test_utils/vcast_utils"; +import { checkForServerRunnability } from "../../../../unit/getToolversion"; + +describe("vTypeCheck VS Code Extension", () => { + let bottomBar: BottomBarPanel; + let workbench: Workbench; + let useDataServer: boolean = true; + before(async () => { + workbench = await browser.getWorkbench(); + // Opening bottom bar and problems view before running any tests + bottomBar = workbench.getBottomBar(); + await bottomBar.toggle(true); + process.env.E2E_TEST_ID = "0"; + let releaseIsSuitableForServer = await checkForServerRunnability(); + if (process.env.VCAST_USE_PYTHON || !releaseIsSuitableForServer) { + useDataServer = false; + } + }); + + it("test 1: should be able to load VS Code", async () => { + await updateTestID(); + expect(await workbench.getTitleBar().getTitle()).toBe( + "[Extension Development Host] vcastTutorial - Visual Studio Code" + ); + }); + + it("should activate vcastAdapter", async () => { + await updateTestID(); + + await browser.keys([Key.Control, Key.Shift, "p"]); + // Typing Vector in the quick input box + // This brings up VectorCAST Test Explorer: Configure + // so just need to hit Enter to activate + for (const character of "vector") { + await browser.keys(character); + } + + await browser.keys(Key.Enter); + + const activityBar = workbench.getActivityBar(); + const viewControls = await activityBar.getViewControls(); + for (const viewControl of viewControls) { + console.log(await viewControl.getTitle()); + } + + await bottomBar.toggle(true); + const outputView = await bottomBar.openOutputView(); + + console.log("Waiting for VectorCAST activation"); + await $("aria/VectorCAST Test Pane Initialization"); + console.log("WAITING FOR TESTING"); + await browser.waitUntil( + async () => (await activityBar.getViewControl("Testing")) !== undefined, + { timeout: TIMEOUT } + ); + console.log("WAITING FOR TEST EXPLORER"); + browser.pause(10000); + await browser.waitUntil(async () => + (await outputView.getChannelNames()) + .toString() + .includes("VectorCAST Test Explorer") + ); + await outputView.selectChannel("VectorCAST Test Explorer"); + console.log("Channel selected"); + console.log("WAITING FOR LANGUAGE SERVER"); + await browser.waitUntil( + async () => + (await outputView.getText()) + .toString() + .includes("Starting the language server"), + { timeout: TIMEOUT } + ); + + const testingView = await activityBar.getViewControl("Testing"); + await testingView?.openView(); + }); + + it("should check for server starting logs if in server mode", async () => { + const outputView = await bottomBar.openOutputView(); + + // Check if server started + if (useDataServer) { + // Check message pane for expected message + await browser.waitUntil( + async () => + (await outputView.getText()) + .toString() + .includes("Started VectorCAST Data Server"), + { timeout: TIMEOUT } + ); + + // Check server logs + const logs = await checkIfRequestInLogs(3, ["port:", "clicast"]); + expect(logs).toBe(true); + } + }); + + it("should check for c coverage", async () => { + workbench = await browser.getWorkbench(); + bottomBar = workbench.getBottomBar(); + const outputView = await bottomBar.openOutputView(); + const workspaceFolderSection = + await expandWorkspaceFolderSectionInExplorer("vcastTutorial"); + const workFolder = workspaceFolderSection.findItem("work"); + await (await workFolder).select(); + const luaFolder = workspaceFolderSection.findItem("lua-5.4.0"); + await (await luaFolder).select(); + const srcFolder = workspaceFolderSection.findItem("src"); + await (await srcFolder).select(); + const file = workspaceFolderSection.findItem("lua.c"); + await (await file).select(); + + // Check if the file is already open in the editor + const editorView = workbench.getEditorView(); + // List of open editor titles + const openEditors = await editorView.getOpenEditorTitles(); + const isFileOpen = openEditors.includes("lua.c"); + if (!isFileOpen) { + // Select file from the explorer if not already open + await (await file).select(); + } + const icon = "no-cover-icon-with-mcdc"; + const tab = (await editorView.openEditor("lua.c")) as TextEditor; + const lineNumberElement = await $(`.line-numbers=80`); + const flaskElement = await ( + await lineNumberElement.parentElement() + ).$(".cgmr.codicon"); + const backgroundImageCSS = + await flaskElement.getCSSProperty("background-image"); + const backgroundImageURL = backgroundImageCSS.value; + const BEAKER = `/${icon}`; + expect(backgroundImageURL.includes(BEAKER)).toBe(true); + + await flaskElement.click({ button: 2 }); + await (await $("aria/VectorCAST MC/DC Report")).click(); + await browser.waitUntil( + async () => + (await outputView.getText()) + .toString() + .includes("Report file path is:"), + { timeout: TIMEOUT } + ); + await browser.waitUntil( + async () => (await workbench.getAllWebviews()).length > 0, + { timeout: TIMEOUT } + ); + const webviews = await workbench.getAllWebviews(); + expect(webviews).toHaveLength(1); + const webview = webviews[0]; + + await webview.open(); + + // Retrieve the HTML and count the number of div.report-block + const reportBlockCount = await browser.execute(() => { + // Use querySelectorAll to count how many
elements are in the document + return document.querySelectorAll("div.report-block").length; + }); + + expect(reportBlockCount).toEqual(1); + + // Some important lines we want to check for in the report + await expect(await checkElementExistsInHTML("lua.c")).toBe(true); + await expect(await checkElementExistsInHTML("80")).toBe(true); + await expect( + await checkElementExistsInHTML("Pairs satisfied: 0 of 1 ( 0% )") + ).toBe(true); + + await webview.close(); + await editorView.closeEditor("VectorCAST Report", 1); + }); +}); diff --git a/tests/internal/e2e/test/specs_config.ts b/tests/internal/e2e/test/specs_config.ts index 251b82e4..f5f95449 100755 --- a/tests/internal/e2e/test/specs_config.ts +++ b/tests/internal/e2e/test/specs_config.ts @@ -170,6 +170,14 @@ export function getSpecGroups(useVcast24: boolean) { }, params: {}, }; + specGroups["c_coverage"] = { + specs: ["./**/**/vcast_c_tests.test.ts"], + env: { + VCAST_USE_PYTHON: "True", + C_COVERAGE: "True", + }, + params: {}, + }; specGroups["basic_user_interactions_server"] = { specs: [ diff --git a/tests/internal/e2e/test/wdio.conf.ts b/tests/internal/e2e/test/wdio.conf.ts index 18af6977..92add166 100644 --- a/tests/internal/e2e/test/wdio.conf.ts +++ b/tests/internal/e2e/test/wdio.conf.ts @@ -335,6 +335,7 @@ export const config: Options.Testrunner = { async () => await buildEnvsWithSpecificReleases(initialWorkdir), ], ["MANAGE_TEST", async () => await testManage(initialWorkdir)], + ["C_COVERAGE", async () => await testCCoverage(initialWorkdir)], ]); // Determine the environment key @@ -392,6 +393,31 @@ export const config: Options.Testrunner = { * ================================================================================================ */ + async function testCCoverage(initialWorkdir: string) { + const testInputCCoverage = path.join(initialWorkdir, "test", "cCoverage"); + const workFolder = path.join(testInputCCoverage, "work"); + + await checkVPython(); + clicastExecutablePath = await checkClicast(); + process.env.CLICAST_PATH = clicastExecutablePath; + + // Execute the coverage build script + const build_coverage = `cd ${testInputCCoverage} && bash ./c_cov_example.sh`; + await executeCommand(build_coverage); + + await prepareConfig(initialWorkdir, clicastExecutablePath); + + // Copy the work folder contents to vcastTutorial + const destFolder = path.join(initialWorkdir, "test", "vcastTutorial"); + if (process.platform === "win32") { + await executeCommand( + `xcopy /s /i /y ${workFolder} ${destFolder} > NUL 2> NUL` + ); + } else { + await executeCommand(`cp -r ${workFolder}/* ${destFolder}/`); + } + } + async function testManage(initialWorkdir: string) { const testInputManage = path.join(initialWorkdir, "test", "manage"); const build_demo = `cd ${testInputManage} && ./demo_build.sh`; From f41f026191d29d409dc449107f5ca24268a06da2 Mon Sep 17 00:00:00 2001 From: Den1552 Date: Thu, 18 Dec 2025 14:58:35 +0100 Subject: [PATCH 09/42] c cov test correction --- tests/internal/e2e/test/specs/vcast_c_tests.test.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/internal/e2e/test/specs/vcast_c_tests.test.ts b/tests/internal/e2e/test/specs/vcast_c_tests.test.ts index 22df4eac..dfa05251 100644 --- a/tests/internal/e2e/test/specs/vcast_c_tests.test.ts +++ b/tests/internal/e2e/test/specs/vcast_c_tests.test.ts @@ -116,8 +116,6 @@ describe("vTypeCheck VS Code Extension", () => { const outputView = await bottomBar.openOutputView(); const workspaceFolderSection = await expandWorkspaceFolderSectionInExplorer("vcastTutorial"); - const workFolder = workspaceFolderSection.findItem("work"); - await (await workFolder).select(); const luaFolder = workspaceFolderSection.findItem("lua-5.4.0"); await (await luaFolder).select(); const srcFolder = workspaceFolderSection.findItem("src"); From a684c8d1b57e26842e4596b6bce999fffbc5ceea Mon Sep 17 00:00:00 2001 From: Den1552 Date: Thu, 18 Dec 2025 15:23:01 +0100 Subject: [PATCH 10/42] c cov test correction 2 --- .../e2e/test/specs/vcast_c_tests.test.ts | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/tests/internal/e2e/test/specs/vcast_c_tests.test.ts b/tests/internal/e2e/test/specs/vcast_c_tests.test.ts index dfa05251..aac29954 100644 --- a/tests/internal/e2e/test/specs/vcast_c_tests.test.ts +++ b/tests/internal/e2e/test/specs/vcast_c_tests.test.ts @@ -12,6 +12,7 @@ import { updateTestID, checkIfRequestInLogs, checkElementExistsInHTML, + findTreeNodeAtLevel, } from "../test_utils/vcast_utils"; import { TIMEOUT } from "../test_utils/vcast_utils"; import { checkForServerRunnability } from "../../../../unit/getToolversion"; @@ -110,9 +111,21 @@ describe("vTypeCheck VS Code Extension", () => { } }); + it("should check for vcp node", async () => { + const outputView = await bottomBar.openOutputView(); + const activityBar = workbench.getActivityBar(); + const testingView = await activityBar.getViewControl("Testing"); + await testingView?.openView(); + const vcpNode = await findTreeNodeAtLevel(0, "env.vcp"); + expect(compilerNode).toBeDefined(); + }); + it("should check for c coverage", async () => { workbench = await browser.getWorkbench(); bottomBar = workbench.getBottomBar(); + const activityBar = workbench.getActivityBar(); + const testingView = await activityBar.getViewControl("Explorer"); + await testingView?.openView(); const outputView = await bottomBar.openOutputView(); const workspaceFolderSection = await expandWorkspaceFolderSectionInExplorer("vcastTutorial"); @@ -134,7 +147,9 @@ describe("vTypeCheck VS Code Extension", () => { } const icon = "no-cover-icon-with-mcdc"; const tab = (await editorView.openEditor("lua.c")) as TextEditor; - const lineNumberElement = await $(`.line-numbers=80`); + + const lineNumber = 65; + const lineNumberElement = await $(`.line-numbers=${lineNumber}`); const flaskElement = await ( await lineNumberElement.parentElement() ).$(".cgmr.codicon"); @@ -173,9 +188,9 @@ describe("vTypeCheck VS Code Extension", () => { // Some important lines we want to check for in the report await expect(await checkElementExistsInHTML("lua.c")).toBe(true); - await expect(await checkElementExistsInHTML("80")).toBe(true); + await expect(await checkElementExistsInHTML("65")).toBe(true); await expect( - await checkElementExistsInHTML("Pairs satisfied: 0 of 1 ( 0% )") + await checkElementExistsInHTML("Pairs satisfied: 0 of 2 ( 0% )") ).toBe(true); await webview.close(); From 569e73df99882f7f38df68e974a2608bbe331148 Mon Sep 17 00:00:00 2001 From: Den1552 Date: Thu, 18 Dec 2025 16:47:20 +0100 Subject: [PATCH 11/42] Added enc file --- tests/internal/e2e/test/cCoverage/env.enc | 41 +++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 tests/internal/e2e/test/cCoverage/env.enc diff --git a/tests/internal/e2e/test/cCoverage/env.enc b/tests/internal/e2e/test/cCoverage/env.enc new file mode 100644 index 00000000..818093db --- /dev/null +++ b/tests/internal/e2e/test/cCoverage/env.enc @@ -0,0 +1,41 @@ + + + 2 + + env + STATEMENT_MCDC + + + SourceRoot + ./lua-5.4.0 + src + + + PROBE_ID: 2 + PROBE_UNIT: luac.c + PROBE_FUNCTION: main + PROBE_LINE: return 0; + PROBE_CONTEXT_BEFORE: + if (lua_pcallk(L, (2), (0), (0), 0, ((void *)0))!=0) fatal(lua_tolstring(L, (-1), ((void *)0))); + lua_close(L); + END_PROBE_CONTEXT_BEFORE: + PROBE_CODE: + VCAST_DUMP_COVERAGE_DATA(); + END_PROBE_CODE: + + + + SourceRoot + src/lua.c + + true + + + + SourceRoot + src/luac.c + + true + + + From 5fa514c5adb31b8863fdf8b5b47cc6526addb2a0 Mon Sep 17 00:00:00 2001 From: Den1552 Date: Thu, 18 Dec 2025 16:58:14 +0100 Subject: [PATCH 12/42] var name change --- tests/internal/e2e/test/specs/vcast_c_tests.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/internal/e2e/test/specs/vcast_c_tests.test.ts b/tests/internal/e2e/test/specs/vcast_c_tests.test.ts index aac29954..811db69e 100644 --- a/tests/internal/e2e/test/specs/vcast_c_tests.test.ts +++ b/tests/internal/e2e/test/specs/vcast_c_tests.test.ts @@ -117,7 +117,7 @@ describe("vTypeCheck VS Code Extension", () => { const testingView = await activityBar.getViewControl("Testing"); await testingView?.openView(); const vcpNode = await findTreeNodeAtLevel(0, "env.vcp"); - expect(compilerNode).toBeDefined(); + expect(vcpNode).toBeDefined(); }); it("should check for c coverage", async () => { From 5f7238db9bae4d94cf23c623f1ecbcc17913d583 Mon Sep 17 00:00:00 2001 From: Den1552 Date: Fri, 19 Dec 2025 09:04:32 +0100 Subject: [PATCH 13/42] Adapted tests --- tests/internal/e2e/test/specs/vcast_c_tests.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/internal/e2e/test/specs/vcast_c_tests.test.ts b/tests/internal/e2e/test/specs/vcast_c_tests.test.ts index 811db69e..c5f59bd0 100644 --- a/tests/internal/e2e/test/specs/vcast_c_tests.test.ts +++ b/tests/internal/e2e/test/specs/vcast_c_tests.test.ts @@ -149,6 +149,7 @@ describe("vTypeCheck VS Code Extension", () => { const tab = (await editorView.openEditor("lua.c")) as TextEditor; const lineNumber = 65; + await tab.moveCursor(lineNumber, 1); const lineNumberElement = await $(`.line-numbers=${lineNumber}`); const flaskElement = await ( await lineNumberElement.parentElement() From 6ad76d0fb49d1612b87f30c055b421e65b5cf6d3 Mon Sep 17 00:00:00 2001 From: Den1552 Date: Fri, 19 Dec 2025 14:13:19 +0100 Subject: [PATCH 14/42] Trying to add pause after file selection --- .../internal/e2e/test/specs/vcast_c_tests.test.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/internal/e2e/test/specs/vcast_c_tests.test.ts b/tests/internal/e2e/test/specs/vcast_c_tests.test.ts index c5f59bd0..23f8f2b1 100644 --- a/tests/internal/e2e/test/specs/vcast_c_tests.test.ts +++ b/tests/internal/e2e/test/specs/vcast_c_tests.test.ts @@ -111,6 +111,17 @@ describe("vTypeCheck VS Code Extension", () => { } }); + it("should change coverageKind and Rebuild env", async () => { + // We need to change the covarage kind to Statement+MCDC in order to get the MCDC lines + let settingsEditor = await workbench.openSettings(); + const coverageKindSetting = await settingsEditor.findSetting( + "Coverage Kind", + "Vectorcast Test Explorer", + "Build" + ); + await coverageKindSetting.setValue("Statement+MCDC"); + }); + it("should check for vcp node", async () => { const outputView = await bottomBar.openOutputView(); const activityBar = workbench.getActivityBar(); @@ -145,6 +156,9 @@ describe("vTypeCheck VS Code Extension", () => { // Select file from the explorer if not already open await (await file).select(); } + + await browser.pause(30000); + const icon = "no-cover-icon-with-mcdc"; const tab = (await editorView.openEditor("lua.c")) as TextEditor; From 035849500b51510b511bbe4f5550511f490769ea Mon Sep 17 00:00:00 2001 From: Den1552 Date: Fri, 19 Dec 2025 14:22:21 +0100 Subject: [PATCH 15/42] Removed setting change --- tests/internal/e2e/test/specs/vcast_c_tests.test.ts | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/tests/internal/e2e/test/specs/vcast_c_tests.test.ts b/tests/internal/e2e/test/specs/vcast_c_tests.test.ts index 23f8f2b1..79be543d 100644 --- a/tests/internal/e2e/test/specs/vcast_c_tests.test.ts +++ b/tests/internal/e2e/test/specs/vcast_c_tests.test.ts @@ -111,17 +111,6 @@ describe("vTypeCheck VS Code Extension", () => { } }); - it("should change coverageKind and Rebuild env", async () => { - // We need to change the covarage kind to Statement+MCDC in order to get the MCDC lines - let settingsEditor = await workbench.openSettings(); - const coverageKindSetting = await settingsEditor.findSetting( - "Coverage Kind", - "Vectorcast Test Explorer", - "Build" - ); - await coverageKindSetting.setValue("Statement+MCDC"); - }); - it("should check for vcp node", async () => { const outputView = await bottomBar.openOutputView(); const activityBar = workbench.getActivityBar(); From 1102efb66f7a811e46daa9c5bbe569065a3773f2 Mon Sep 17 00:00:00 2001 From: Den1552 Date: Fri, 19 Dec 2025 14:47:11 +0100 Subject: [PATCH 16/42] Added debug log --- src/editorDecorator.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/editorDecorator.ts b/src/editorDecorator.ts index 679bf24b..155c1406 100644 --- a/src/editorDecorator.ts +++ b/src/editorDecorator.ts @@ -66,6 +66,8 @@ export async function updateCurrentActiveUnitMCDCLines() { const mcdcLinesForUnit = mcdcUnitCoverageLines[unitName]; // Check if there are no MCDC lines for the unit --> for example when the env is build with only Statement coverage // and the user changes it to Statement+MCDC in the settings + vectorMessage(`MCDC LINES FOR UNIT: ${unitName}`); + vectorMessage(mcdcLinesForUnit.toString()); if (mcdcLinesForUnit) { currentActiveUnitMCDCLines = mcdcUnitCoverageLines[unitName]; } else { From 3763f9824e2d7a1b5230eba90dac838c05de8e50 Mon Sep 17 00:00:00 2001 From: Denis Moslavac Date: Tue, 13 Jan 2026 09:11:46 +0100 Subject: [PATCH 17/42] Adapted c tests --- .../e2e/test/specs/vcast_c_tests.test.ts | 49 ++++++++++++++----- tests/internal/e2e/test/wdio.conf.ts | 31 +++++++----- 2 files changed, 56 insertions(+), 24 deletions(-) diff --git a/tests/internal/e2e/test/specs/vcast_c_tests.test.ts b/tests/internal/e2e/test/specs/vcast_c_tests.test.ts index 79be543d..1ccb090c 100644 --- a/tests/internal/e2e/test/specs/vcast_c_tests.test.ts +++ b/tests/internal/e2e/test/specs/vcast_c_tests.test.ts @@ -126,45 +126,65 @@ describe("vTypeCheck VS Code Extension", () => { const activityBar = workbench.getActivityBar(); const testingView = await activityBar.getViewControl("Explorer"); await testingView?.openView(); - const outputView = await bottomBar.openOutputView(); + + await bottomBar.toggle(false); + const workspaceFolderSection = await expandWorkspaceFolderSectionInExplorer("vcastTutorial"); + + const workFolder = workspaceFolderSection.findItem("work"); + await (await workFolder).select(); + const luaFolder = workspaceFolderSection.findItem("lua-5.4.0"); await (await luaFolder).select(); + const srcFolder = workspaceFolderSection.findItem("src"); await (await srcFolder).select(); + const file = workspaceFolderSection.findItem("lua.c"); await (await file).select(); // Check if the file is already open in the editor const editorView = workbench.getEditorView(); - // List of open editor titles const openEditors = await editorView.getOpenEditorTitles(); const isFileOpen = openEditors.includes("lua.c"); + if (!isFileOpen) { - // Select file from the explorer if not already open await (await file).select(); } - await browser.pause(30000); + // Give VS Code some time to settle (language server, decorations, etc.) + await browser.pause(30_000); const icon = "no-cover-icon-with-mcdc"; - const tab = (await editorView.openEditor("lua.c")) as TextEditor; - const lineNumber = 65; + + const tab = (await editorView.openEditor("lua.c")) as TextEditor; await tab.moveCursor(lineNumber, 1); + + // Use the EXACT same pattern as the working test const lineNumberElement = await $(`.line-numbers=${lineNumber}`); const flaskElement = await ( await lineNumberElement.parentElement() ).$(".cgmr.codicon"); + + // Verify the icon const backgroundImageCSS = await flaskElement.getCSSProperty("background-image"); - const backgroundImageURL = backgroundImageCSS.value; - const BEAKER = `/${icon}`; - expect(backgroundImageURL.includes(BEAKER)).toBe(true); + expect(backgroundImageCSS.value.includes(`/${icon}`)).toBe(true); + // Close bottom bar before context menu + await bottomBar.toggle(false); + await browser.pause(1000); + + // Open context menu await flaskElement.click({ button: 2 }); + await browser.pause(2000); await (await $("aria/VectorCAST MC/DC Report")).click(); + + const outputView = await bottomBar.openOutputView(); + + // Wait for report generation await browser.waitUntil( async () => (await outputView.getText()) @@ -172,31 +192,34 @@ describe("vTypeCheck VS Code Extension", () => { .includes("Report file path is:"), { timeout: TIMEOUT } ); + + // Wait for webview to open await browser.waitUntil( async () => (await workbench.getAllWebviews()).length > 0, { timeout: TIMEOUT } ); + const webviews = await workbench.getAllWebviews(); expect(webviews).toHaveLength(1); - const webview = webviews[0]; + const webview = webviews[0]; await webview.open(); - // Retrieve the HTML and count the number of div.report-block + // Count report blocks in the HTML const reportBlockCount = await browser.execute(() => { - // Use querySelectorAll to count how many
elements are in the document return document.querySelectorAll("div.report-block").length; }); expect(reportBlockCount).toEqual(1); - // Some important lines we want to check for in the report + // Validate report content await expect(await checkElementExistsInHTML("lua.c")).toBe(true); await expect(await checkElementExistsInHTML("65")).toBe(true); await expect( await checkElementExistsInHTML("Pairs satisfied: 0 of 2 ( 0% )") ).toBe(true); + // Cleanup await webview.close(); await editorView.closeEditor("VectorCAST Report", 1); }); diff --git a/tests/internal/e2e/test/wdio.conf.ts b/tests/internal/e2e/test/wdio.conf.ts index 92add166..f82620b8 100644 --- a/tests/internal/e2e/test/wdio.conf.ts +++ b/tests/internal/e2e/test/wdio.conf.ts @@ -394,28 +394,37 @@ export const config: Options.Testrunner = { */ async function testCCoverage(initialWorkdir: string) { + const workFolder = path.join(initialWorkdir, "test", "vcastTutorial"); const testInputCCoverage = path.join(initialWorkdir, "test", "cCoverage"); - const workFolder = path.join(testInputCCoverage, "work"); await checkVPython(); clicastExecutablePath = await checkClicast(); process.env.CLICAST_PATH = clicastExecutablePath; - // Execute the coverage build script - const build_coverage = `cd ${testInputCCoverage} && bash ./c_cov_example.sh`; - await executeCommand(build_coverage); - await prepareConfig(initialWorkdir, clicastExecutablePath); - // Copy the work folder contents to vcastTutorial - const destFolder = path.join(initialWorkdir, "test", "vcastTutorial"); + // Create vcastTutorial directory + await mkdir(workFolder, { recursive: true }); + + // Copy the build script to vcastTutorial + const scriptSource = path.join(testInputCCoverage, "c_cov_example.sh"); + const scriptDest = path.join(workFolder, "c_cov_example.sh"); + const envSource = path.join(testInputCCoverage, "env.enc"); + const envDest = path.join(workFolder, "env.enc"); + if (process.platform === "win32") { - await executeCommand( - `xcopy /s /i /y ${workFolder} ${destFolder} > NUL 2> NUL` - ); + await executeCommand(`copy /y ${scriptSource} ${scriptDest}`); + await executeCommand(`copy /y ${envSource} ${envDest}`); } else { - await executeCommand(`cp -r ${workFolder}/* ${destFolder}/`); + await executeCommand(`cp ${scriptSource} ${scriptDest}`); + await executeCommand(`cp ${envSource} ${envDest}`); } + + // Execute the coverage build script in vcastTutorial + const build_coverage = `cd ${workFolder} && bash ./c_cov_example.sh`; + await executeCommand(build_coverage); + + // No copy needed - everything is already in the right place! } async function testManage(initialWorkdir: string) { From f1ba3fe30fc1ce2ce0fcdc63a6593f3120ace831 Mon Sep 17 00:00:00 2001 From: Denis Moslavac Date: Tue, 13 Jan 2026 13:41:20 +0100 Subject: [PATCH 18/42] added debugging logs --- src/editorDecorator.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/editorDecorator.ts b/src/editorDecorator.ts index 155c1406..212602c2 100644 --- a/src/editorDecorator.ts +++ b/src/editorDecorator.ts @@ -38,8 +38,9 @@ export async function updateCurrentActiveUnitMCDCLines() { if (activeEditor) { // First we need to get the env name from the active file const filePath = normalizePath(activeEditor.document.uri.fsPath); + vectorMessage(`filepath: ${filePath}`); let enviroPath = getEnvPathForFilePath(filePath); - + vectorMessage(`enviroPath: ${enviroPath}`); // Get the unit name based on the file name without extension const fullPath = activeEditor.document.fileName; let unitName = path.basename(fullPath, path.extname(fullPath)); @@ -51,6 +52,7 @@ export async function updateCurrentActiveUnitMCDCLines() { fullPath )); + vectorMessage(`enviroPath: ${enviroPath}, unitName: ${unitName}`); // Get all mcdc lines for every unit and parse it into JSON if (enviroPath) { // Replace single quotes with double quotes to make it a valid JSON string From 3a28f5f1a3d3b6ae19414fe01f683c7c22f6c537 Mon Sep 17 00:00:00 2001 From: Denis Moslavac Date: Tue, 13 Jan 2026 14:11:20 +0100 Subject: [PATCH 19/42] Changed debug log location --- src/editorDecorator.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/editorDecorator.ts b/src/editorDecorator.ts index 212602c2..e324d566 100644 --- a/src/editorDecorator.ts +++ b/src/editorDecorator.ts @@ -69,8 +69,8 @@ export async function updateCurrentActiveUnitMCDCLines() { // Check if there are no MCDC lines for the unit --> for example when the env is build with only Statement coverage // and the user changes it to Statement+MCDC in the settings vectorMessage(`MCDC LINES FOR UNIT: ${unitName}`); - vectorMessage(mcdcLinesForUnit.toString()); if (mcdcLinesForUnit) { + vectorMessage(mcdcLinesForUnit.toString()); currentActiveUnitMCDCLines = mcdcUnitCoverageLines[unitName]; } else { currentActiveUnitMCDCLines = []; From d21375c936862666b736cc1db63a2c61f5ff584f Mon Sep 17 00:00:00 2001 From: Denis Moslavac Date: Tue, 13 Jan 2026 17:41:05 +0100 Subject: [PATCH 20/42] Debugging logs for manage tests --- src/testPane.ts | 3 +++ src/vcastTestInterface.ts | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/testPane.ts b/src/testPane.ts index e7519e47..ef2c57d1 100644 --- a/src/testPane.ts +++ b/src/testPane.ts @@ -1835,8 +1835,11 @@ export async function refreshAllExtensionData() { // - we fall back from server mode // resetCoverageData(); + vectorMessage("-------------3---------------"); await buildTestPaneContents(); + vectorMessage("-------------4---------------"); await updateCOVdecorations(); + vectorMessage("-------------5---------------"); // Global varibale to see if we have a manage Project opened or just an Environment setGlobalProjectIsOpenedChecker(); setGlobalCompilerAndTestsuites(); diff --git a/src/vcastTestInterface.ts b/src/vcastTestInterface.ts index 0eb9b215..e512024d 100644 --- a/src/vcastTestInterface.ts +++ b/src/vcastTestInterface.ts @@ -907,7 +907,7 @@ export async function newEnvironment( // file in the list will be a C/C++ file but we need to filter // for the multi-select case. // - + vectorMessage("-------------0---------------"); let fileList: string[] = []; for (let index = 0; index < URIlist.length; index++) { const filePath = URIlist[index].fsPath; @@ -938,12 +938,14 @@ export async function newEnvironment( ); return; } + vectorMessage("-------------1---------------"); await configureWorkspaceAndBuildEnviro( fileList, tempEnvPath, projectEnvParameters ); } else { + vectorMessage("-------------1.5---------------"); let unitTestLocation = getUnitTestLocationForPath( path.dirname(fileList[0]) ); @@ -956,6 +958,7 @@ export async function newEnvironment( "]" ); } + vectorMessage("-------------2---------------"); await refreshAllExtensionData(); } From c7866f0b9be01726a86f2184a43854fda420d78a Mon Sep 17 00:00:00 2001 From: Denis Moslavac Date: Wed, 14 Jan 2026 09:20:35 +0100 Subject: [PATCH 21/42] Additional debugging logs --- src/vcastTestInterface.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/vcastTestInterface.ts b/src/vcastTestInterface.ts index e512024d..50d29cff 100644 --- a/src/vcastTestInterface.ts +++ b/src/vcastTestInterface.ts @@ -755,8 +755,11 @@ async function configureWorkspaceAndBuildEnviro( // If we have project params, we want to create an env within a project if (projectEnvParameters) { // Create the environment using the provided file list + vectorMessage("############## 0 ##############"); + vectorMessage(`envLocation: ${envLocation}`); + vectorMessage(`fileList: ${fileList.toString()}`); await commonEnvironmentSetup(fileList, envLocation, false); - + vectorMessage("############## 1 ##############"); const envName = createEnvNameFromFiles(fileList); const envFilePath = path.join(envLocation, `${envName}.env`); const testSuites = projectEnvParameters.testsuiteArgs; @@ -858,7 +861,8 @@ async function commonEnvironmentSetup( // Derive the environment name and path let enviroName = createEnvNameFromFiles(fileList)!.toUpperCase(); let enviroPath = path.join(envLocation, enviroName); - + vectorMessage(`enviroName: ${enviroName}`); + vectorMessage(`enviroPath 2: ${enviroPath}`); // If the directory already exists, prompt for a new name if (fs.existsSync(enviroPath)) { const response = await vscode.window.showInputBox({ @@ -885,7 +889,7 @@ async function commonEnvironmentSetup( return; } } - + vectorMessage("############## 2 ##############"); // Build the environment with the valid name await buildEnvironmentVCAST( fileList, From 1c29dff0e00bda2d4aece84c7ded9a9674a0c45a Mon Sep 17 00:00:00 2001 From: Denis Moslavac Date: Wed, 14 Jan 2026 09:52:43 +0100 Subject: [PATCH 22/42] Maximizing bottomBar --- src/vcastTestInterface.ts | 1 + tests/internal/e2e/test/specs/vcast_manage.test.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/vcastTestInterface.ts b/src/vcastTestInterface.ts index 50d29cff..414c2bd7 100644 --- a/src/vcastTestInterface.ts +++ b/src/vcastTestInterface.ts @@ -920,6 +920,7 @@ export async function newEnvironment( fileList.push(filePath); } } + vectorMessage(`FILELIST: ${fileList.toString()}`); if (fileList.length > 0) { if (projectEnvParameters) { // Get the workspace root folder. diff --git a/tests/internal/e2e/test/specs/vcast_manage.test.ts b/tests/internal/e2e/test/specs/vcast_manage.test.ts index 0b462eec..95a7b822 100644 --- a/tests/internal/e2e/test/specs/vcast_manage.test.ts +++ b/tests/internal/e2e/test/specs/vcast_manage.test.ts @@ -814,6 +814,7 @@ describe("vTypeCheck VS Code Extension", () => { const button = await $(`aria/Import OK`); await button.click(); console.log("Checking for Output logs if Environment creation is finished"); + await bottomBar.maximize(); await browser.waitUntil( async () => (await outputView.getText()) From 06a44917ca8908e03c38437f7c655ead139802ca Mon Sep 17 00:00:00 2001 From: Denis Moslavac Date: Wed, 14 Jan 2026 10:50:27 +0100 Subject: [PATCH 23/42] Restoring bottom bar --- tests/internal/e2e/test/specs/vcast_manage.test.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/internal/e2e/test/specs/vcast_manage.test.ts b/tests/internal/e2e/test/specs/vcast_manage.test.ts index 95a7b822..0260a42f 100644 --- a/tests/internal/e2e/test/specs/vcast_manage.test.ts +++ b/tests/internal/e2e/test/specs/vcast_manage.test.ts @@ -847,6 +847,8 @@ describe("vTypeCheck VS Code Extension", () => { const testsuiteNode = await findTreeNodeAtLevel(3, "DATABASE-MANAGER"); expect(testsuiteNode).toBeDefined(); + await bottomBar.restore(); + // Closing all current notifications for the next test const notificationsCenter = await workbench.openNotificationsCenter(); await notificationsCenter.clearAllNotifications(); From 5c2cc2f77117309db6071ba2f15fe7a80593cb69 Mon Sep 17 00:00:00 2001 From: Denis Moslavac Date: Wed, 14 Jan 2026 11:33:19 +0100 Subject: [PATCH 24/42] Removed debug logs --- src/testPane.ts | 3 --- src/vcastTestInterface.ts | 9 --------- 2 files changed, 12 deletions(-) diff --git a/src/testPane.ts b/src/testPane.ts index ef2c57d1..e7519e47 100644 --- a/src/testPane.ts +++ b/src/testPane.ts @@ -1835,11 +1835,8 @@ export async function refreshAllExtensionData() { // - we fall back from server mode // resetCoverageData(); - vectorMessage("-------------3---------------"); await buildTestPaneContents(); - vectorMessage("-------------4---------------"); await updateCOVdecorations(); - vectorMessage("-------------5---------------"); // Global varibale to see if we have a manage Project opened or just an Environment setGlobalProjectIsOpenedChecker(); setGlobalCompilerAndTestsuites(); diff --git a/src/vcastTestInterface.ts b/src/vcastTestInterface.ts index 414c2bd7..c8c3d31f 100644 --- a/src/vcastTestInterface.ts +++ b/src/vcastTestInterface.ts @@ -755,11 +755,7 @@ async function configureWorkspaceAndBuildEnviro( // If we have project params, we want to create an env within a project if (projectEnvParameters) { // Create the environment using the provided file list - vectorMessage("############## 0 ##############"); - vectorMessage(`envLocation: ${envLocation}`); - vectorMessage(`fileList: ${fileList.toString()}`); await commonEnvironmentSetup(fileList, envLocation, false); - vectorMessage("############## 1 ##############"); const envName = createEnvNameFromFiles(fileList); const envFilePath = path.join(envLocation, `${envName}.env`); const testSuites = projectEnvParameters.testsuiteArgs; @@ -889,7 +885,6 @@ async function commonEnvironmentSetup( return; } } - vectorMessage("############## 2 ##############"); // Build the environment with the valid name await buildEnvironmentVCAST( fileList, @@ -911,7 +906,6 @@ export async function newEnvironment( // file in the list will be a C/C++ file but we need to filter // for the multi-select case. // - vectorMessage("-------------0---------------"); let fileList: string[] = []; for (let index = 0; index < URIlist.length; index++) { const filePath = URIlist[index].fsPath; @@ -943,14 +937,12 @@ export async function newEnvironment( ); return; } - vectorMessage("-------------1---------------"); await configureWorkspaceAndBuildEnviro( fileList, tempEnvPath, projectEnvParameters ); } else { - vectorMessage("-------------1.5---------------"); let unitTestLocation = getUnitTestLocationForPath( path.dirname(fileList[0]) ); @@ -963,7 +955,6 @@ export async function newEnvironment( "]" ); } - vectorMessage("-------------2---------------"); await refreshAllExtensionData(); } From c446e82ca0cfce6641f07a302a87415b9d689c56 Mon Sep 17 00:00:00 2001 From: Denis Moslavac Date: Wed, 14 Jan 2026 13:34:09 +0100 Subject: [PATCH 25/42] Removed last debugging logs --- src/editorDecorator.ts | 5 ----- src/testPane.ts | 4 ++-- src/vcastTestInterface.ts | 4 +--- tests/internal/e2e/test/wdio.conf.ts | 2 +- 4 files changed, 4 insertions(+), 11 deletions(-) diff --git a/src/editorDecorator.ts b/src/editorDecorator.ts index e324d566..20ede5d5 100644 --- a/src/editorDecorator.ts +++ b/src/editorDecorator.ts @@ -38,9 +38,7 @@ export async function updateCurrentActiveUnitMCDCLines() { if (activeEditor) { // First we need to get the env name from the active file const filePath = normalizePath(activeEditor.document.uri.fsPath); - vectorMessage(`filepath: ${filePath}`); let enviroPath = getEnvPathForFilePath(filePath); - vectorMessage(`enviroPath: ${enviroPath}`); // Get the unit name based on the file name without extension const fullPath = activeEditor.document.fileName; let unitName = path.basename(fullPath, path.extname(fullPath)); @@ -52,7 +50,6 @@ export async function updateCurrentActiveUnitMCDCLines() { fullPath )); - vectorMessage(`enviroPath: ${enviroPath}, unitName: ${unitName}`); // Get all mcdc lines for every unit and parse it into JSON if (enviroPath) { // Replace single quotes with double quotes to make it a valid JSON string @@ -68,9 +65,7 @@ export async function updateCurrentActiveUnitMCDCLines() { const mcdcLinesForUnit = mcdcUnitCoverageLines[unitName]; // Check if there are no MCDC lines for the unit --> for example when the env is build with only Statement coverage // and the user changes it to Statement+MCDC in the settings - vectorMessage(`MCDC LINES FOR UNIT: ${unitName}`); if (mcdcLinesForUnit) { - vectorMessage(mcdcLinesForUnit.toString()); currentActiveUnitMCDCLines = mcdcUnitCoverageLines[unitName]; } else { currentActiveUnitMCDCLines = []; diff --git a/src/testPane.ts b/src/testPane.ts index e7519e47..6aaacff1 100644 --- a/src/testPane.ts +++ b/src/testPane.ts @@ -652,10 +652,10 @@ export function addVcpEnvironments( environmentList.push({ projectPath: "", // VCPs are usually standalone or treated differently than managed envs buildDirectory: normalizedPath, // We use the VCP file path as the identifier - isBuilt: true, // VCPs are considered "non-built" / accessible + isBuilt: true, displayName: displayName, workspaceRoot: workspaceRoot, - isVcp: true, // Flag to identify this as a VCP node if needed downstream + isVcp: true, }); } } diff --git a/src/vcastTestInterface.ts b/src/vcastTestInterface.ts index c8c3d31f..1a977553 100644 --- a/src/vcastTestInterface.ts +++ b/src/vcastTestInterface.ts @@ -857,8 +857,7 @@ async function commonEnvironmentSetup( // Derive the environment name and path let enviroName = createEnvNameFromFiles(fileList)!.toUpperCase(); let enviroPath = path.join(envLocation, enviroName); - vectorMessage(`enviroName: ${enviroName}`); - vectorMessage(`enviroPath 2: ${enviroPath}`); + // If the directory already exists, prompt for a new name if (fs.existsSync(enviroPath)) { const response = await vscode.window.showInputBox({ @@ -914,7 +913,6 @@ export async function newEnvironment( fileList.push(filePath); } } - vectorMessage(`FILELIST: ${fileList.toString()}`); if (fileList.length > 0) { if (projectEnvParameters) { // Get the workspace root folder. diff --git a/tests/internal/e2e/test/wdio.conf.ts b/tests/internal/e2e/test/wdio.conf.ts index f82620b8..8799dc3e 100644 --- a/tests/internal/e2e/test/wdio.conf.ts +++ b/tests/internal/e2e/test/wdio.conf.ts @@ -424,7 +424,7 @@ export const config: Options.Testrunner = { const build_coverage = `cd ${workFolder} && bash ./c_cov_example.sh`; await executeCommand(build_coverage); - // No copy needed - everything is already in the right place! + // No copy needed - everything is put in place by the sh script } async function testManage(initialWorkdir: string) { From b23a5c4994a4d40c2fc183bece9f60d585fd6618 Mon Sep 17 00:00:00 2001 From: Denis Moslavac Date: Wed, 14 Jan 2026 13:38:53 +0100 Subject: [PATCH 26/42] Sonar --- src/testPane.ts | 2 +- tests/internal/e2e/test/specs/vcast_c_tests.test.ts | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/testPane.ts b/src/testPane.ts index 6aaacff1..df2cbc6a 100644 --- a/src/testPane.ts +++ b/src/testPane.ts @@ -643,7 +643,7 @@ export function addVcpEnvironments( workspaceRoot: string ): void { // Check if the cached data exists and has the 'vcp' key we added in Python - if (cachedWorkspaceEnvData && cachedWorkspaceEnvData.vcp) { + if (cachedWorkspaceEnvData?.vcp) { for (const vcpData of cachedWorkspaceEnvData.vcp) { // vcpData contains: { vcpPath, testData (empty), unitData, mockingSupport } const normalizedPath = normalizePath(vcpData.vcpPath); diff --git a/tests/internal/e2e/test/specs/vcast_c_tests.test.ts b/tests/internal/e2e/test/specs/vcast_c_tests.test.ts index 1ccb090c..ac901074 100644 --- a/tests/internal/e2e/test/specs/vcast_c_tests.test.ts +++ b/tests/internal/e2e/test/specs/vcast_c_tests.test.ts @@ -6,15 +6,13 @@ import { } from "wdio-vscode-service"; import { Key } from "webdriverio"; import { - releaseCtrl, - executeCtrlClickOn, expandWorkspaceFolderSectionInExplorer, updateTestID, checkIfRequestInLogs, checkElementExistsInHTML, findTreeNodeAtLevel, + TIMEOUT, } from "../test_utils/vcast_utils"; -import { TIMEOUT } from "../test_utils/vcast_utils"; import { checkForServerRunnability } from "../../../../unit/getToolversion"; describe("vTypeCheck VS Code Extension", () => { @@ -112,7 +110,6 @@ describe("vTypeCheck VS Code Extension", () => { }); it("should check for vcp node", async () => { - const outputView = await bottomBar.openOutputView(); const activityBar = workbench.getActivityBar(); const testingView = await activityBar.getViewControl("Testing"); await testingView?.openView(); From 356bffcf15b526fb451184cb7463195faa22d326 Mon Sep 17 00:00:00 2001 From: Denis Moslavac Date: Fri, 6 Feb 2026 17:04:58 +0100 Subject: [PATCH 27/42] update --- python/vTestInterface.py | 33 +++++++++- src/testPane.ts | 131 +++++++++++++++++++++++++++++++++++---- src/vcastUtilities.ts | 14 ++++- 3 files changed, 161 insertions(+), 17 deletions(-) diff --git a/python/vTestInterface.py b/python/vTestInterface.py index 3943a0e9..b518003f 100644 --- a/python/vTestInterface.py +++ b/python/vTestInterface.py @@ -821,7 +821,7 @@ def processCommandLogic(mode, clicast, pathToUse, testString="", options=""): for vcp_path in vcp_files: try: api = CoverApi(vcp_path) - test_data = [] # Cover projects do not have test data + test_data = getVCPResultsList(vcp_path) unit_data = getUnitData(api) mocking_support = False # Cover projects do not support mocking api.close() @@ -931,6 +931,37 @@ def processCommandLogic(mode, clicast, pathToUse, testString="", options=""): return returnCode, returnObject +def getVCPResultsList(vcp_path): + """ + Recursively searches for all.lua and api.lua result files in the VCP root directory + """ + import os + import glob + + resultsList = [] + + try: + # Get the directory containing the .vcp file + vcp_dir = os.path.dirname(vcp_path) + + # Recursively search for all.lua files + all_lua_pattern = os.path.join(vcp_dir, "**", "all.lua") + all_lua_files = glob.glob(all_lua_pattern, recursive=True) + + # Recursively search for api.lua files + api_lua_pattern = os.path.join(vcp_dir, "**", "api.lua") + api_lua_files = glob.glob(api_lua_pattern, recursive=True) + + # Combine and add full paths to the results list + for lua_file in all_lua_files + api_lua_files: + resultsList.append(os.path.abspath(lua_file)) + + except Exception as e: + print(f"Error searching for .lua files in {vcp_path}: {e}") + + return resultsList + + def processCommand(mode, clicast, pathToUse, testString="", options=""): """ This is a wrapper for process command logic, so that we can process diff --git a/src/testPane.ts b/src/testPane.ts index df2cbc6a..aebd7304 100644 --- a/src/testPane.ts +++ b/src/testPane.ts @@ -634,7 +634,7 @@ export function addFreeEnvironments( /** * Adds VCP (VectorCAST Cover Project) files found in the workspace cache to the environment list. - * These are treated as environments but will have no test children. + * VCP Nodes are different as they only have a "Files" and "Results" as children. * @param environmentList A list to push environment data. * @param workspaceRoot The workspace root directory path. */ @@ -645,13 +645,11 @@ export function addVcpEnvironments( // Check if the cached data exists and has the 'vcp' key we added in Python if (cachedWorkspaceEnvData?.vcp) { for (const vcpData of cachedWorkspaceEnvData.vcp) { - // vcpData contains: { vcpPath, testData (empty), unitData, mockingSupport } const normalizedPath = normalizePath(vcpData.vcpPath); const displayName = path.basename(normalizedPath); - environmentList.push({ - projectPath: "", // VCPs are usually standalone or treated differently than managed envs - buildDirectory: normalizedPath, // We use the VCP file path as the identifier + projectPath: normalizedPath, // VCP path + buildDirectory: normalizedPath, isBuilt: true, displayName: displayName, workspaceRoot: workspaceRoot, @@ -735,7 +733,7 @@ let vcastHasCodedTestsList: string[] = []; // Global cache for workspace-wide env data // Used to avoid redundant API calls during refresh -let cachedWorkspaceEnvData: CachedWorkspaceData | null = null; +export let cachedWorkspaceEnvData: CachedWorkspaceData | null = null; export function clearCachedWorkspaceEnvData(): void { cachedWorkspaceEnvData = null; @@ -750,13 +748,16 @@ export async function updateTestsForEnvironment( enviroData: environmentNodeDataType, comingFromRefresh: boolean = false ) { - let jsonData: any; + // Handle VCP files specially + if (enviroData.isVcp && parentNode) { + await createVcpChildNodes(parentNode, enviroData); + return; + } - // In case we are refreshing the extension, we do not want to call the API n times (n=|envs|) - // Instead we get the entire env data at once and save us time. + // Regular environment handling + let jsonData: any; jsonData = await loadEnviroData(enviroData, comingFromRefresh); if (!jsonData) return; - await processSingleEnvData(parentNode, enviroData, jsonData); } @@ -1868,7 +1869,23 @@ function getParentNodeForEnvironment( return null; } - // Managed project branch. + // VCP Cover Project + if (enviroData.isVcp) { + let projectNode = globalProjectMap.get(enviroData.projectPath); + if (!projectNode) { + const projectDisplayName = path.basename(enviroData.projectPath); + projectNode = globalController.createTestItem( + enviroData.projectPath, + projectDisplayName + ) as vcastTestItem; + projectNode.nodeKind = nodeKind.vcpProject; + globalController.items.add(projectNode); + globalProjectMap.set(enviroData.projectPath, projectNode); + } + return projectNode; + } + + // Managed project let projectNode = globalProjectMap.get(enviroData.projectPath); if (!projectNode) { // If project node doesn't exist, create a new one. @@ -1881,9 +1898,7 @@ function getParentNodeForEnvironment( globalController.items.add(projectNode); globalProjectMap.set(enviroData.projectPath, projectNode); } - let currentParent = createHierarchy(pathParts, projectNode); - return currentParent; } @@ -2113,7 +2128,9 @@ export async function updateCodedTestCases(editor: any) { } // special is for compound and init + export enum nodeKind { + vcpProject, projectGroup, project, environmentGroup, @@ -2124,6 +2141,9 @@ export enum nodeKind { test, compiler, testsuite, + vcpFiles, + vcpResults, + vcpSourceFile, } export interface vcastTestItem extends vscode.TestItem { // this is a simple wrapper that allows us to add additional @@ -2154,3 +2174,88 @@ function checkWorkspaceEnvDataForErrors() { } } } + +/** + * Creates "Files" and "Results" child nodes under a VCP project node + */ +async function createVcpChildNodes( + vcpProjectNode: vcastTestItem, + enviroData: environmentNodeDataType +): Promise { + // Get the cached VCP data to access unitData and testData + const vcpData = getVcpDataFromCache(enviroData.buildDirectory); + + // Create "Files" node + const filesNodeId = `${enviroData.buildDirectory}::files`; + const filesNode = globalController.createTestItem( + filesNodeId, + "Files" + ) as vcastTestItem; + filesNode.nodeKind = nodeKind.vcpFiles; + vcpProjectNode.children.add(filesNode); + + // Add source files as children under Files node + if (vcpData?.unitData && Array.isArray(vcpData.unitData)) { + for (const unit of vcpData.unitData) { + if (unit.path) { + const unitFileName = path.basename(unit.path); + const unitNodeId = `${filesNodeId}::${unit.path}`; + + const unitNode = globalController.createTestItem( + unitNodeId, + unitFileName, + vscode.Uri.file(unit.path) + ) as vcastTestItem; + + unitNode.nodeKind = nodeKind.vcpSourceFile; + unitNode.canResolveChildren = false; + + filesNode.children.add(unitNode); + } + } + } + + // Create "Results" node + const resultsNodeId = `${enviroData.buildDirectory}::results`; + const resultsNode = globalController.createTestItem( + resultsNodeId, + "Results" + ) as vcastTestItem; + resultsNode.nodeKind = nodeKind.vcpResults; + vcpProjectNode.children.add(resultsNode); + + // Add .lua result files as children under Results node + if (vcpData?.testData && Array.isArray(vcpData.testData)) { + for (const resultFile of vcpData.testData) { + // resultFile should be the full path to all.lua or api.lua + if (resultFile && typeof resultFile === "string") { + const resultFileName = path.basename(resultFile); + const resultNodeId = `${resultsNodeId}::${resultFile}`; + + const resultNode = globalController.createTestItem( + resultNodeId, + resultFileName, + vscode.Uri.file(resultFile) + ) as vcastTestItem; + + resultNode.nodeKind = nodeKind.vcpResults; + resultNode.canResolveChildren = false; + + resultsNode.children.add(resultNode); + } + } + } +} + +/** + * Helper function to get VCP data from the workspace cache + */ +function getVcpDataFromCache(vcpPath: string): any { + if (cachedWorkspaceEnvData?.vcp) { + const normalizedPath = normalizePath(vcpPath); + return cachedWorkspaceEnvData.vcp.find( + (vcp: any) => normalizePath(vcp.vcpPath) === normalizedPath + ); + } + return null; +} diff --git a/src/vcastUtilities.ts b/src/vcastUtilities.ts index 43fc4a6a..d3fc26e8 100644 --- a/src/vcastUtilities.ts +++ b/src/vcastUtilities.ts @@ -590,19 +590,27 @@ export function checkIfAnyProjectsAreOpened() { * @param fullPath Full Path to the Project File */ export function getVcmRoot(fullPath: string) { + // Remove vcast: scheme if present + const cleanPath = fullPath.replace(/^vcast:/, ""); + // pre-compile the regex once const vcmRe = /(.*\/)([^/]+\.vcm)(?:\/.*)?$/; + const vcpRe = /(.*\/)([^/]+\.vcp)(?:\/.*)?$/; // use exec() instead of match() (sonarcloud) - const match = vcmRe.exec(fullPath); + let match: RegExpExecArray | null; + if (cleanPath.endsWith(".vcm")) { + match = vcmRe.exec(cleanPath); + } else { + match = vcpRe.exec(cleanPath); + } if (match) { - // match[1] is the directory (with trailing slash), match[2] is the .vcm name + // match[1] is the directory (with trailing slash), match[2] is the .vcm/.vcp name const rootPath = match[1].replace(/\/$/, ""); const vcmName = match[2]; return { rootPath, vcmName }; } - return null; } /** From 9dca76a19516a4499a65963a3d3a244460be3ce5 Mon Sep 17 00:00:00 2001 From: Den1552 Date: Mon, 9 Feb 2026 09:36:54 +0100 Subject: [PATCH 28/42] Added additional coverage --- python/vTestInterface.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/python/vTestInterface.py b/python/vTestInterface.py index b518003f..c181acf7 100644 --- a/python/vTestInterface.py +++ b/python/vTestInterface.py @@ -353,16 +353,19 @@ class CoverageKind: COVERAGE_TYPE_TYPE_T.STATEMENT, COVERAGE_TYPE_TYPE_T.STATEMENT_FUNCTION_CALL, COVERAGE_TYPE_TYPE_T.STATEMENT_BRANCH_FUNCTION_CALL, + COVERAGE_TYPE_TYPE_T.STATEMENT_BRANCH_FUNCTION_FUNCTION_CALL, ] mcdcCoverageList = [ COVERAGE_TYPE_TYPE_T.STATEMENT_MCDC, COVERAGE_TYPE_TYPE_T.STATEMENT_MCDC_FUNCTION_CALL, + COVERAGE_TYPE_TYPE_T.STATEMENT_MCDC_FUNCTION_FUNCTION_CALL ] branchCoverageList = [ COVERAGE_TYPE_TYPE_T.STATEMENT_BRANCH, COVERAGE_TYPE_TYPE_T.STATEMENT_BRANCH_FUNCTION_CALL, + COVERAGE_TYPE_TYPE_T.STATEMENT_BRANCH_FUNCTION_FUNCTION_CALL, ] From fa6644da061ce5bb8d245fac3bee6e3d52e232b5 Mon Sep 17 00:00:00 2001 From: Den1552 Date: Tue, 10 Feb 2026 13:48:56 +0100 Subject: [PATCH 29/42] Fixed coverage for cover and for .h --- python/mcdcReport.py | 15 ++++++++------- src/testPane.ts | 2 ++ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/python/mcdcReport.py b/python/mcdcReport.py index 1bf364e7..28385a3e 100644 --- a/python/mcdcReport.py +++ b/python/mcdcReport.py @@ -39,15 +39,16 @@ def get_mcdc_lines(env): with ApiClass(env) as api: # Access the API property (api.Unit or api.File) dynamically source_modules = getattr(api, entity_attr) - - for unit in source_modules.filter(): - for mcdc_dec in unit.cover_data.mcdc_decisions: + sourceObjects = api.SourceFile.all() + for sourceObject in sourceObjects: + unit = sourceObject.cover_data.name + for mcdc_dec in sourceObject.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) + 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 diff --git a/src/testPane.ts b/src/testPane.ts index aebd7304..49f5b435 100644 --- a/src/testPane.ts +++ b/src/testPane.ts @@ -2184,6 +2184,8 @@ async function createVcpChildNodes( ): Promise { // Get the cached VCP data to access unitData and testData const vcpData = getVcpDataFromCache(enviroData.buildDirectory); + saveEnviroNodeData(enviroData.buildDirectory, enviroData); + updateGlobalDataForFile(enviroData.buildDirectory, vcpData.unitData); // Create "Files" node const filesNodeId = `${enviroData.buildDirectory}::files`; From 2719d45429a3312c7a274665d73a92b7275a6798 Mon Sep 17 00:00:00 2001 From: Den1552 Date: Tue, 10 Feb 2026 14:49:19 +0100 Subject: [PATCH 30/42] MCDC Report for h files 1 --- python/mcdcReport.py | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/python/mcdcReport.py b/python/mcdcReport.py index 28385a3e..00ad6a1d 100644 --- a/python/mcdcReport.py +++ b/python/mcdcReport.py @@ -76,15 +76,24 @@ def generate_mcdc_report(env, unit_filter, line_filter, output): ApiClass, entity_attr = get_api_context(env) with ApiClass(env) as api: - source_modules = getattr(api, entity_attr) + # Use api.SourceFile.all() to get all source objects including .h files + sourceObjects = api.SourceFile.all() + # Find and check for our unit unit_found = False - for unit in source_modules.filter(name=unit_filter): + for sourceObject in sourceObjects: + # Get unit name from source object + unit_name = sourceObject.cover_data.name + + # Check if this is the unit we're looking for + 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? @@ -114,14 +123,17 @@ 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 we found the unit, break out of the outer loop + 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)" + f"Could not find unit {unit_filter} (units should not have extensions)" ) From 890fe148a663f9eef77a9efc1eb0e70dfd38a4a0 Mon Sep 17 00:00:00 2001 From: Den1552 Date: Tue, 10 Feb 2026 15:24:02 +0100 Subject: [PATCH 31/42] MCDC Report for h files 2 --- python/mcdcReport.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/python/mcdcReport.py b/python/mcdcReport.py index 00ad6a1d..b571ef56 100644 --- a/python/mcdcReport.py +++ b/python/mcdcReport.py @@ -34,11 +34,10 @@ def parse_args(): def get_mcdc_lines(env): all_lines_with_data = {} + # Check if normal env or Cover --> Different API ApiClass, entity_attr = get_api_context(env) with ApiClass(env) as api: - # Access the API property (api.Unit or api.File) dynamically - source_modules = getattr(api, entity_attr) sourceObjects = api.SourceFile.all() for sourceObject in sourceObjects: unit = sourceObject.cover_data.name @@ -76,16 +75,13 @@ def generate_mcdc_report(env, unit_filter, line_filter, output): ApiClass, entity_attr = get_api_context(env) with ApiClass(env) as api: - # Use api.SourceFile.all() to get all source objects including .h files sourceObjects = api.SourceFile.all() - # Find and check for our unit unit_found = False for sourceObject in sourceObjects: - # Get unit name from source object + # Find and check for our unit unit_name = sourceObject.cover_data.name - - # Check if this is the unit we're looking for + if unit_name != unit_filter: continue @@ -123,7 +119,6 @@ def generate_mcdc_report(env, unit_filter, line_filter, output): ) break - # If we found the unit, break out of the outer loop if unit_found: # If we don't find our line, report an error if not line_found: From 02a704bf4e22e4d59875a5b1775c7ab3f6a7f754 Mon Sep 17 00:00:00 2001 From: "Andrew V. Teylu" Date: Tue, 10 Feb 2026 16:13:18 +0000 Subject: [PATCH 32/42] Add support for generating TIC-style MCDC reports --- python/custom/sections/per_line_mcdc.py | 21 ++++++++++++++----- .../templates/PerLineMcdc/main.html.template | 8 +++++-- python/mcdcReport.py | 16 +++++++++----- python/vTestInterface.py | 2 +- 4 files changed, 34 insertions(+), 13 deletions(-) diff --git a/python/custom/sections/per_line_mcdc.py b/python/custom/sections/per_line_mcdc.py index 7552d8e3..649f4850 100644 --- a/python/custom/sections/per_line_mcdc.py +++ b/python/custom/sections/per_line_mcdc.py @@ -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 @@ -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 diff --git a/python/custom/templates/PerLineMcdc/main.html.template b/python/custom/templates/PerLineMcdc/main.html.template index cba0f61b..cfc3c55e 100644 --- a/python/custom/templates/PerLineMcdc/main.html.template +++ b/python/custom/templates/PerLineMcdc/main.html.template @@ -8,8 +8,6 @@ {%- endif %} {%- for function in obj.functions -%} - {% if function.mcdcs %} - {% endif %} {%- for mcdc in function.mcdcs %} {% if is_sfp -%}
@@ -26,6 +24,12 @@ {% trans %}File{% endtrans %} {{mcdc.unit_name|e}} + {%- if show_subprogram %} + + {% trans %}Subprogram{% endtrans %} + {{function.name|e}} + + {%- endif %} {% if is_sfp -%}
{% trans %}Source line{% endtrans %} diff --git a/python/mcdcReport.py b/python/mcdcReport.py index b571ef56..e78a2e6a 100644 --- a/python/mcdcReport.py +++ b/python/mcdcReport.py @@ -106,9 +106,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( @@ -125,10 +132,9 @@ def generate_mcdc_report(env, unit_filter, line_filter, output): 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_filter} (units should not have extensions)" + f"Could not find unit {unit_filter}" ) diff --git a/python/vTestInterface.py b/python/vTestInterface.py index c181acf7..38389076 100644 --- a/python/vTestInterface.py +++ b/python/vTestInterface.py @@ -359,7 +359,7 @@ class CoverageKind: mcdcCoverageList = [ COVERAGE_TYPE_TYPE_T.STATEMENT_MCDC, COVERAGE_TYPE_TYPE_T.STATEMENT_MCDC_FUNCTION_CALL, - COVERAGE_TYPE_TYPE_T.STATEMENT_MCDC_FUNCTION_FUNCTION_CALL + COVERAGE_TYPE_TYPE_T.STATEMENT_MCDC_FUNCTION_FUNCTION_CALL, ] branchCoverageList = [ From a88ff6129aa21bd146fc8e8dcdc90550c7b23be6 Mon Sep 17 00:00:00 2001 From: Den1552 Date: Fri, 13 Feb 2026 15:29:20 +0100 Subject: [PATCH 33/42] Fixed MCDC --- python/coverageGutter.py | 46 +++++++++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/python/coverageGutter.py b/python/coverageGutter.py index 35e84057..0834c537 100644 --- a/python/coverageGutter.py +++ b/python/coverageGutter.py @@ -15,28 +15,35 @@ 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 @@ -85,7 +92,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: @@ -96,13 +102,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: @@ -148,6 +154,8 @@ def handleStatementMcdcCoverage( if use_mcdc else metrics.max_covered_branches + metrics.max_annotations_branches ) + #print(f"line_number: {line_number}") + #print(f"{metrics.mcdc_branches} = {metrics.covered_mcdc_branches} + {metrics.uncovered_mcdc_branches}") # Determine statement coverage covered_statements = ( @@ -156,17 +164,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 From f3a3c4bce71f9c36a661e3d1fa2b5fe62407bc64 Mon Sep 17 00:00:00 2001 From: Den1552 Date: Fri, 13 Feb 2026 15:30:05 +0100 Subject: [PATCH 34/42] Fixed mcdc report for headers --- python/mcdcReport.py | 33 +++++++++++++++++---------------- src/editorDecorator.ts | 6 +----- src/extension.ts | 6 +----- src/utilities.ts | 4 ++-- 4 files changed, 21 insertions(+), 28 deletions(-) diff --git a/python/mcdcReport.py b/python/mcdcReport.py index e78a2e6a..ba8b3957 100644 --- a/python/mcdcReport.py +++ b/python/mcdcReport.py @@ -36,18 +36,19 @@ def get_mcdc_lines(env): # 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 = sourceObject.cover_data.name - 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) + 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 @@ -76,17 +77,17 @@ def generate_mcdc_report(env, unit_filter, line_filter, output): with ApiClass(env) as api: sourceObjects = api.SourceFile.all() - unit_found = False for sourceObject in sourceObjects: # Find and check for our unit - unit_name = sourceObject.cover_data.name + 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 sourceObject.cover_data.mcdc_decisions: @@ -132,10 +133,10 @@ def generate_mcdc_report(env, unit_filter, line_filter, output): raise RuntimeError(f"Could not find line {line_filter} in unit {unit_filter}") break - if not unit_found: - raise RuntimeError( - f"Could not find unit {unit_filter}" - ) + if not unit_found: + raise RuntimeError( + f"Could not find unit {unit_filter}" + ) def main(): diff --git a/src/editorDecorator.ts b/src/editorDecorator.ts index 20ede5d5..6167294d 100644 --- a/src/editorDecorator.ts +++ b/src/editorDecorator.ts @@ -44,11 +44,7 @@ export async function updateCurrentActiveUnitMCDCLines() { let unitName = path.basename(fullPath, path.extname(fullPath)); // If the file is in a cover project, we need adapt the paths - ({ enviroPath, unitName } = resolveVcpPaths( - enviroPath, - unitName, - fullPath - )); + enviroPath = resolveVcpPaths(enviroPath, unitName, fullPath); // Get all mcdc lines for every unit and parse it into JSON if (enviroPath) { diff --git a/src/extension.ts b/src/extension.ts index 99a2ba60..41c7175f 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1226,11 +1226,7 @@ function configureExtension(context: vscode.ExtensionContext) { let unitName = path.parse(filePath).name; // If the file is in a cover project, we need adapt the paths - ({ enviroPath, unitName } = resolveVcpPaths( - enviroPath, - unitName, - filePath - )); + enviroPath = resolveVcpPaths(enviroPath, unitName, filePath); if (enviroPath) { viewMCDCReport(enviroPath, unitName, args.lineNumber); diff --git a/src/utilities.ts b/src/utilities.ts index aa825fbc..6c9adbae 100644 --- a/src/utilities.ts +++ b/src/utilities.ts @@ -469,7 +469,7 @@ export function resolveVcpPaths( enviroPath: string | null, unitName: string, fullFilePath: string -): { enviroPath: string | null; unitName: string } { +) { if (enviroPath?.endsWith(".vcp")) { // Cover projects require the full filename (e.g. "manager.c" instead of "manager") unitName = unitName + path.extname(fullFilePath); @@ -479,5 +479,5 @@ export function resolveVcpPaths( enviroPath = path.join(parsed.dir, parsed.name); } - return { enviroPath, unitName }; + return enviroPath; } From 9799ad0bbe7790e162dff8f0fd71f7e4c7b8c5cd Mon Sep 17 00:00:00 2001 From: Den1552 Date: Mon, 16 Feb 2026 17:05:37 +0100 Subject: [PATCH 35/42] Fixed issue for not appearing icons if only partially covered --- src/vcastTestInterface.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/vcastTestInterface.ts b/src/vcastTestInterface.ts index 1a977553..2099e6f7 100644 --- a/src/vcastTestInterface.ts +++ b/src/vcastTestInterface.ts @@ -258,7 +258,11 @@ export function getCoverageDataForFile(filePath: string): coverageSummaryType { } } - if (coveredList.length == 0 && uncoveredList.length == 0) { + if ( + coveredList.length == 0 && + uncoveredList.length == 0 && + partiallyCoveredList.length == 0 + ) { // This status is for files that have changed since // they were last instrumented returnData.statusString = "Coverage Out of Date"; From 512ba6593ed31fef34b847a5d2402123af22f092 Mon Sep 17 00:00:00 2001 From: Den1552 Date: Mon, 16 Feb 2026 17:08:11 +0100 Subject: [PATCH 36/42] Coverage on empty function not at the end anymore --- python/coverageGutter.py | 3 +- python/vTestInterface.py | 114 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 114 insertions(+), 3 deletions(-) diff --git a/python/coverageGutter.py b/python/coverageGutter.py index 0834c537..f3c6581d 100644 --- a/python/coverageGutter.py +++ b/python/coverageGutter.py @@ -28,6 +28,7 @@ def getMCDCLineDic(sourceObject): 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 @@ -154,8 +155,6 @@ def handleStatementMcdcCoverage( if use_mcdc else metrics.max_covered_branches + metrics.max_annotations_branches ) - #print(f"line_number: {line_number}") - #print(f"{metrics.mcdc_branches} = {metrics.covered_mcdc_branches} + {metrics.uncovered_mcdc_branches}") # Determine statement coverage covered_statements = ( diff --git a/python/vTestInterface.py b/python/vTestInterface.py index 38389076..ad531a8f 100644 --- a/python/vTestInterface.py +++ b/python/vTestInterface.py @@ -393,7 +393,6 @@ def getCoverageKind(sourceObject): else: return CoverageKind.ignore - def getCoverageData(sourceObject): """ This function will use the data interface to @@ -486,8 +485,121 @@ def getCoverageData(sourceObject): uncoveredString = uncoveredString[:-1] partiallyCoveredString = partiallyCoveredString[:-1] + # Remap coverage from closing brace to function start for empty functions + coveredString, partiallyCoveredString, uncoveredString = ( + remapEmptyFunctionCoverage( + sourceObject, + coveredString, + partiallyCoveredString, + uncoveredString, + ) + ) + return coveredString, uncoveredString, partiallyCoveredString, checksum +def lineInString(line_number, coverage_string): + """ + Exact token match in a comma-separated coverage string. + Avoids false positives like '8' matching inside '18'. + """ + if not coverage_string: + return False + + all_lines = coverage_string.split(",") + return str(line_number) in all_lines + + +def removeLine(line_number, coverage_string): + """ + Removes a line number from a comma-separated coverage string. + """ + if not coverage_string: + return coverage_string + + line_number_str = str(line_number) + all_lines = coverage_string.split(",") + + filtered_lines = [] + for entry in all_lines: + if entry != line_number_str: + filtered_lines.append(entry) + + return ",".join(filtered_lines) + + +def addLine(line_number, coverage_string): + """ + Appends a line number to a comma-separated coverage string (no duplicates). + """ + if not coverage_string: + return str(line_number) + + line_number_str = str(line_number) + all_lines = coverage_string.split(",") + already_present = line_number_str in all_lines + + if not already_present: + all_lines.append(line_number_str) + + return ",".join(all_lines) + + +def remapEmptyFunctionCoverage( + sourceObject, + coveredString, + partiallyCoveredString, + uncoveredString, +): + """ + VectorCAST only instruments the closing '}' of an empty/stub function, + so the coverage icon would appear on the brace instead of the function signature. + + For each function where: + - start_line has NO coverage of any kind, AND + - end_line DOES have coverage (in any List) + + move that entry from end_line to start_line, keeping whichever + state it was in: covered / partially-covered / uncovered. + """ + for function in sourceObject.cover_data.functions: + start_line = function.start_line + end_line = getattr(function, "end_line", None) + + # Nothing to remap if identical or unknown + if end_line is None or end_line == start_line: + continue + + # Skip if start_line already has coverage in any List + start_has_coverage = ( + lineInString(start_line, coveredString) + or lineInString(start_line, partiallyCoveredString) + or lineInString(start_line, uncoveredString) + ) + if start_has_coverage: + continue + + # Determine which List end_line is in + end_is_covered = lineInString(end_line, coveredString) + end_is_partial = lineInString(end_line, partiallyCoveredString) + end_is_uncovered = lineInString(end_line, uncoveredString) + + if not (end_is_covered or end_is_partial or end_is_uncovered): + continue + + # Move end_line -> start_line in the correct List + if end_is_covered: + coveredString = removeLine(end_line, coveredString) + coveredString = addLine(start_line, coveredString) + + elif end_is_partial: + partiallyCoveredString = removeLine(end_line, partiallyCoveredString) + partiallyCoveredString = addLine(start_line, partiallyCoveredString) + + elif end_is_uncovered: + uncoveredString = removeLine(end_line, uncoveredString) + uncoveredString = addLine(start_line, uncoveredString) + + return coveredString, partiallyCoveredString, uncoveredString def executeVCtest(enviroPath, testIDObject): with cd(os.path.dirname(enviroPath)): From abe557a628c3a71098ee12781bfd27f82cfa4805 Mon Sep 17 00:00:00 2001 From: Den1552 Date: Tue, 17 Feb 2026 10:23:59 +0100 Subject: [PATCH 37/42] Added first Cover Buttons --- package.json | 42 ++++++++++++++++++++++++++++++++ src/extension.ts | 30 +++++++++++++++++++++++ src/testData.ts | 63 ++++++++++++++++++++++++++++++++++++++++++++++++ src/testPane.ts | 21 ++++++++++++++++ 4 files changed, 156 insertions(+) diff --git a/package.json b/package.json index 869f2ddb..4ef9f83e 100644 --- a/package.json +++ b/package.json @@ -95,6 +95,21 @@ "category": "VectorCAST Test Explorer", "title": "Re-Build Environment" }, + { + "command": "vectorcastTestExplorer.coverRemoveAllResults", + "category": "VectorCAST Test Explorer", + "title": "Cover: Remove All Results" + }, + { + "command": "vectorcastTestExplorer.coverRemoveResult", + "category": "VectorCAST Test Explorer", + "title": "Cover: Remove Result File" + }, + { + "command": "vectorcastTestExplorer.coverAddResult", + "category": "VectorCAST Test Explorer", + "title": "Cover: Add Result File" + }, { "command": "vectorcastTestExplorer.buildProjectEnviro", "category": "VectorCAST Test Explorer", @@ -680,6 +695,18 @@ "command": "vectorcastTestExplorer.importEnviroToProject", "when": "never" }, + { + "command": "vectorcastTestExplorer.coverRemoveAllResults", + "when": "never" + }, + { + "command": "vectorcastTestExplorer.coverRemoveResult", + "when": "never" + }, + { + "command": "vectorcastTestExplorer.coverAddResult", + "when": "never" + }, { "command": "vectorcastTestExplorer.updateProjectEnvironment", "when": "never" @@ -992,6 +1019,21 @@ "group": "vcast.project", "when": "(testId =~ /^vcast:.*$/ && (testId in vectorcastTestExplorer.vcastUnbuiltEnviroList || testId in vectorcastTestExplorer.vcastEnviroList) && vectorcastTestExplorer.globalProjectIsOpenedChecker) && !config.vectorcastTestExplorer.automaticallyUpdateManageProject && !(testId =~ /\\.vcp$/)" }, + { + "command": "vectorcastTestExplorer.coverRemoveAllResults", + "group": "vcast.project", + "when": "testId =~ /::results$/" + }, + { + "command": "vectorcastTestExplorer.coverRemoveResult", + "group": "vcast.project", + "when": "testId in vectorcastTestExplorer.coverResultNodeIDList" + }, + { + "command": "vectorcastTestExplorer.coverAddResult", + "group": "vcast.project", + "when": "testId =~ /::results$/" + }, { "command": "vectorcastTestExplorer.buildProjectEnviro", "group": "vcast.build", diff --git a/src/extension.ts b/src/extension.ts index 41c7175f..7c85d088 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -469,6 +469,36 @@ function configureExtension(context: vscode.ExtensionContext) { ); context.subscriptions.push(insertATGTestsCommand); + // Command: vectorcastTestExplorer.coverRemoveAllResults//////////////////////////////////////////////////////// + let coverRemoveAllResultsCommand = vscode.commands.registerCommand( + "vectorcastTestExplorer.coverRemoveAllResults", + (args: any) => { + // TODO: implement remove all results + vectorMessage(`${args.id}`); + } + ); + context.subscriptions.push(coverRemoveAllResultsCommand); + + // Command: vectorcastTestExplorer.coverRemoveResult//////////////////////////////////////////////////////// + let coverRemoveResultCommand = vscode.commands.registerCommand( + "vectorcastTestExplorer.coverRemoveResult", + (args: any) => { + // TODO: implement remove result + vectorMessage(`${args.id}`); + } + ); + context.subscriptions.push(coverRemoveResultCommand); + + // Command: vectorcastTestExplorer.coverAddResult//////////////////////////////////////////////////////// + let coverAddResultCommand = vscode.commands.registerCommand( + "vectorcastTestExplorer.coverAddResult", + (args: any) => { + // TODO: implement add result + vectorMessage(`${args.id}`); + } + ); + context.subscriptions.push(coverAddResultCommand); + // Command: vectorcastTestExplorer.insertATGTestsFromEditor//////////////////////////////////////////////////////// let insertATGTestsFromEditorCommand = vscode.commands.registerCommand( "vectorcastTestExplorer.insertATGTestsFromEditor", diff --git a/src/testData.ts b/src/testData.ts index 178e93a0..564dc6ed 100644 --- a/src/testData.ts +++ b/src/testData.ts @@ -1,4 +1,5 @@ import { normalizePath, quote } from "./utilities"; +import * as vscode from "vscode"; export interface environmentNodeDataType { isVcp: boolean; @@ -31,8 +32,70 @@ export function clearEnviroDataCache() { environmentDataCache.clear(); } +export let coverFilesNodeIDList: string[] = []; +export let coverResultNodeIDList: string[] = []; + export const compoundOnlyString = " [compound only]"; +export interface vcpNodeType { + vcpNodeID: string; + projectPath: string; + projectName: string; + isVcpFile: boolean; + isVcpResult: boolean; + projectNodeID: string; +} + +export const vcpNodeCache = new Map(); + +export function createVcpNodeInCache( + vcpNodeID: string, + projectPath: string, + projectName: string, + projectNodeID: string, + isVcpFile: boolean, + isVcpResult: boolean +) { + let vcpNode: vcpNodeType = { + vcpNodeID: vcpNodeID, + projectPath: projectPath, + projectName: projectName, + projectNodeID: projectNodeID, + isVcpFile: isVcpFile, + isVcpResult: isVcpResult, + }; + + vcpNodeCache.set(vcpNodeID, vcpNode); + if (vcpNode.isVcpFile && !coverFilesNodeIDList.includes(vcpNodeID)) { + coverFilesNodeIDList.push(vcpNodeID); + } else if (vcpNode.isVcpResult && !coverFilesNodeIDList.includes(vcpNodeID)) { + coverResultNodeIDList.push(vcpNodeID); + } + + setCoverProjectContext(); +} + +export function removeVcpNodeFromCache(nodeID: string) { + vcpNodeCache.delete(nodeID); +} + +export function getVcpTestNode(nodeID: string): testNodeType { + return vcpNodeCache.get(nodeID); +} + +export function setCoverProjectContext() { + vscode.commands.executeCommand( + "setContext", + "vectorcastTestExplorer.coverFilesNodeIDList", + coverFilesNodeIDList + ); + vscode.commands.executeCommand( + "setContext", + "vectorcastTestExplorer.coverResultNodeIDList", + coverResultNodeIDList + ); +} + export interface testNodeType { enviroNodeID: string; enviroPath: string; // the full path including the enviro directory diff --git a/src/testPane.ts b/src/testPane.ts index 49f5b435..7197da6e 100644 --- a/src/testPane.ts +++ b/src/testPane.ts @@ -41,6 +41,7 @@ import { saveEnviroNodeData, testNodeType, testNodeCache, + createVcpNodeInCache, } from "./testData"; import { @@ -2187,6 +2188,8 @@ async function createVcpChildNodes( saveEnviroNodeData(enviroData.buildDirectory, enviroData); updateGlobalDataForFile(enviroData.buildDirectory, vcpData.unitData); + const projectNodeID = vcpProjectNode.id; + // Create "Files" node const filesNodeId = `${enviroData.buildDirectory}::files`; const filesNode = globalController.createTestItem( @@ -2195,6 +2198,7 @@ async function createVcpChildNodes( ) as vcastTestItem; filesNode.nodeKind = nodeKind.vcpFiles; vcpProjectNode.children.add(filesNode); + const projectName = path.basename(enviroData.projectPath); // Add source files as children under Files node if (vcpData?.unitData && Array.isArray(vcpData.unitData)) { @@ -2213,6 +2217,15 @@ async function createVcpChildNodes( unitNode.canResolveChildren = false; filesNode.children.add(unitNode); + + createVcpNodeInCache( + filesNodeId, + enviroData.projectPath, + projectName, + projectNodeID, + true, + false + ); } } } @@ -2244,6 +2257,14 @@ async function createVcpChildNodes( resultNode.canResolveChildren = false; resultsNode.children.add(resultNode); + createVcpNodeInCache( + filesNodeId, + enviroData.projectPath, + projectName, + projectNodeID, + false, + true + ); } } } From a8f5b199f99e34ce4965740da7510e49af1bcb10 Mon Sep 17 00:00:00 2001 From: Den1552 Date: Tue, 17 Feb 2026 14:05:52 +0100 Subject: [PATCH 38/42] Enabled to open files in cover project --- package.json | 2 +- src/extension.ts | 111 ++++++++++++++++++++++------------------------- src/testData.ts | 5 ++- src/testPane.ts | 11 +++-- src/utilities.ts | 21 +++++++++ 5 files changed, 86 insertions(+), 64 deletions(-) diff --git a/package.json b/package.json index 61c43af7..87d4bfea 100644 --- a/package.json +++ b/package.json @@ -1121,7 +1121,7 @@ { "command": "vectorcastTestExplorer.openSourceFileFromTestpaneCommand", "group": "vcast.enviroManagement", - "when": "testId =~ /^vcast:.*$/ && !(testId =~ /^.*<>.*$/) && !(testId =~ /^.*<>.*$/) && !(testId =~ /.*coded_tests_driver.*/) && testId not in vectorcastTestExplorer.vcastUnbuiltEnviroList && testId not in vectorcastTestExplorer.vcastEnviroList" + "when": "( testId =~ /^vcast:.*$/ || testId =~ /::file$/) && !(testId =~ /^.*<>.*$/) && !(testId =~ /^.*<>.*$/) && !(testId =~ /.*coded_tests_driver.*/) && testId not in vectorcastTestExplorer.vcastUnbuiltEnviroList && testId not in vectorcastTestExplorer.vcastEnviroList" }, { "command": "vectorcastTestExplorer.insertBasisPathTests", diff --git a/src/extension.ts b/src/extension.ts index 4c4d7be7..3b88a3e9 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -49,7 +49,9 @@ import { getEnviroNodeData, getEnviroPathFromID, getTestNode, + getVcpTestNode, testNodeType, + vcpNodeType, } from "./testData"; import { @@ -80,6 +82,7 @@ import { decodeVar, getFullEnvReport, resolveVcpPaths, + openFileAtLine, } from "./utilities"; import { @@ -1275,69 +1278,61 @@ function configureExtension(context: vscode.ExtensionContext) { let openSourceFileFromTestpaneCommand = vscode.commands.registerCommand( "vectorcastTestExplorer.openSourceFileFromTestpaneCommand", async (args: any) => { - if (args) { - const testNode: testNodeType = getTestNode(args.id); - if (testNode) { - const enviroPath = testNode.enviroPath; - const unitName = testNode.unitName; - const functionName = testNode.functionName; - const envData = await getEnvironmentData(enviroPath); - - if (envData.unitData) { - for (const unitInfo of envData.unitData) { - // Extract unit name from path to match against unitName - const pathBasename = path.basename( - unitInfo.path, - path.extname(unitInfo.path) - ); - - if (pathBasename === unitName) { - const sourcePath = unitInfo.path; - const uri = vscode.Uri.file(sourcePath); - - // Determine the line number to open at (0 = top default) - let lineNumber = 0; - - // If functionName is defined, try to find it in the function list - if (functionName && unitInfo.functionList) { - for (const func of unitInfo.functionList) { - if ( - func.name === functionName && - func.startLine !== undefined - ) { - lineNumber = func.startLine; - break; - } - } - } - - // Open the document at the specified line - const document = await vscode.workspace.openTextDocument(uri); - const position = new vscode.Position( - Math.max(0, lineNumber - 1), - 0 - ); - const selection = new vscode.Range(position, position); + if (!args) return; + + const fileID: string = args.id; + const isVcpFile = fileID.endsWith("::file"); + let testNode: testNodeType | vcpNodeType; + if (isVcpFile) { + testNode = getVcpTestNode(fileID); + if (!testNode) { + vscode.window.showErrorMessage( + `Unable to open Source File for Node: ${fileID}` + ); + return; + } + await openFileAtLine(testNode.sourceFilePath, 0); + return; + } - await vscode.window.showTextDocument(document, { - preview: false, // open as a real tab - preserveFocus: false, - selection: selection, - }); + testNode = getTestNode(fileID); + if (!testNode) { + vscode.window.showErrorMessage( + `Unable to open Source File for Node: ${fileID}` + ); + return; + } - break; - } + const envData = await getEnvironmentData(testNode.enviroPath); + if (!envData.unitData) { + vscode.window.showErrorMessage( + `Could not find environment data for: ${testNode.enviroPath}` + ); + return; + } + + for (const unitInfo of envData.unitData) { + const pathBasename = path.basename( + unitInfo.path, + path.extname(unitInfo.path) + ); + if (pathBasename !== testNode.unitName) continue; + + let lineNumber = 0; + if (testNode.functionName && unitInfo.functionList) { + for (const func of unitInfo.functionList) { + if ( + func.name === testNode.functionName && + func.startLine !== undefined + ) { + lineNumber = func.startLine; + break; } - } else { - vscode.window.showErrorMessage( - `Could not find environment data for: ${enviroPath}` - ); } - } else { - vscode.window.showErrorMessage( - `Unable to open Source File for Node: ${args.id}` - ); } + + await openFileAtLine(unitInfo.path, lineNumber); + break; } } ); diff --git a/src/testData.ts b/src/testData.ts index 564dc6ed..7ed65924 100644 --- a/src/testData.ts +++ b/src/testData.ts @@ -39,6 +39,7 @@ export const compoundOnlyString = " [compound only]"; export interface vcpNodeType { vcpNodeID: string; + sourceFilePath: string; projectPath: string; projectName: string; isVcpFile: boolean; @@ -50,6 +51,7 @@ export const vcpNodeCache = new Map(); export function createVcpNodeInCache( vcpNodeID: string, + sourceFilePath: string, projectPath: string, projectName: string, projectNodeID: string, @@ -58,6 +60,7 @@ export function createVcpNodeInCache( ) { let vcpNode: vcpNodeType = { vcpNodeID: vcpNodeID, + sourceFilePath: sourceFilePath, projectPath: projectPath, projectName: projectName, projectNodeID: projectNodeID, @@ -79,7 +82,7 @@ export function removeVcpNodeFromCache(nodeID: string) { vcpNodeCache.delete(nodeID); } -export function getVcpTestNode(nodeID: string): testNodeType { +export function getVcpTestNode(nodeID: string): vcpNodeType { return vcpNodeCache.get(nodeID); } diff --git a/src/testPane.ts b/src/testPane.ts index ab3cffe4..551b792a 100644 --- a/src/testPane.ts +++ b/src/testPane.ts @@ -2210,7 +2210,8 @@ async function createVcpChildNodes( for (const unit of vcpData.unitData) { if (unit.path) { const unitFileName = path.basename(unit.path); - const unitNodeId = `${filesNodeId}::${unit.path}`; + const sourceFilePath = unit.path; + const unitNodeId = `${sourceFilePath}::file`; const unitNode = globalController.createTestItem( unitNodeId, @@ -2224,7 +2225,8 @@ async function createVcpChildNodes( filesNode.children.add(unitNode); createVcpNodeInCache( - filesNodeId, + unitNodeId, + sourceFilePath, enviroData.projectPath, projectName, projectNodeID, @@ -2250,7 +2252,7 @@ async function createVcpChildNodes( // resultFile should be the full path to all.lua or api.lua if (resultFile && typeof resultFile === "string") { const resultFileName = path.basename(resultFile); - const resultNodeId = `${resultsNodeId}::${resultFile}`; + const resultNodeId = `${resultFile}::result`; const resultNode = globalController.createTestItem( resultNodeId, @@ -2263,7 +2265,8 @@ async function createVcpChildNodes( resultsNode.children.add(resultNode); createVcpNodeInCache( - filesNodeId, + resultNodeId, + resultFile, enviroData.projectPath, projectName, projectNodeID, diff --git a/src/utilities.ts b/src/utilities.ts index 7652321d..b77e5ed3 100644 --- a/src/utilities.ts +++ b/src/utilities.ts @@ -430,11 +430,15 @@ export async function mergeWorkspaceEnvResponses( ): Promise { const allErrors: string[] = []; const allEnvs: EnviroData[] = []; + const allVcp: EnviroData[] = []; for (const resp of responses) { if (resp.errors) { allErrors.push(...resp.errors); } + if (resp.vcp) { + allVcp.push(...resp.vcp); + } if (resp.enviro) { allEnvs.push(...resp.enviro); } @@ -442,6 +446,7 @@ export async function mergeWorkspaceEnvResponses( return { enviro: allEnvs, + vcp: allVcp, errors: allErrors.length ? allErrors : undefined, }; } @@ -504,3 +509,19 @@ export function resolveVcpPaths( return enviroPath; } + +export async function openFileAtLine( + filePath: string, + lineNumber: number +): Promise { + const uri = vscode.Uri.file(filePath); + const document = await vscode.workspace.openTextDocument(uri); + const position = new vscode.Position(Math.max(0, lineNumber - 1), 0); + const selection = new vscode.Range(position, position); + + await vscode.window.showTextDocument(document, { + preview: false, + preserveFocus: false, + selection: selection, + }); +} From 89a21166279c45c04046d12693b8ad1e1bea9bf7 Mon Sep 17 00:00:00 2001 From: Den1552 Date: Wed, 18 Feb 2026 17:00:37 +0100 Subject: [PATCH 39/42] Added Buttons for cover projects --- package.json | 28 +++++++ python/vTestInterface.py | 3 +- src/coverage.ts | 8 +- src/extension.ts | 149 +++++++++++++++++++++++++++++++++++--- src/testData.ts | 27 ++++++- src/testPane.ts | 41 ++++++++--- src/utilities.ts | 28 +++++++ src/vcastCommandRunner.ts | 81 +++++++++++++++++++++ 8 files changed, 338 insertions(+), 27 deletions(-) diff --git a/package.json b/package.json index 87d4bfea..45893a00 100644 --- a/package.json +++ b/package.json @@ -110,6 +110,16 @@ "category": "VectorCAST Test Explorer", "title": "Cover: Add Result File" }, + { + "command": "vectorcastTestExplorer.coverEnableInstrumentation", + "category": "VectorCAST Test Explorer", + "title": "Cover: Enable Instrumentation" + }, + { + "command": "vectorcastTestExplorer.coverDisableInstrumentation", + "category": "VectorCAST Test Explorer", + "title": "Cover: Disable Instrumentation" + }, { "command": "vectorcastTestExplorer.buildProjectEnviro", "category": "VectorCAST Test Explorer", @@ -712,6 +722,14 @@ "command": "vectorcastTestExplorer.coverAddResult", "when": "never" }, + { + "command": "vectorcastTestExplorer.coverEnableInstrumentation", + "when": "never" + }, + { + "command": "vectorcastTestExplorer.coverDisableInstrumentation", + "when": "never" + }, { "command": "vectorcastTestExplorer.updateProjectEnvironment", "when": "never" @@ -1043,6 +1061,16 @@ "group": "vcast.project", "when": "testId =~ /::results$/" }, + { + "command": "vectorcastTestExplorer.coverEnableInstrumentation", + "group": "vcast.project", + "when": "testId =~ /\\.vcp$/ && testId not in vectorcastTestExplorer.coverInPlaceList" + }, + { + "command": "vectorcastTestExplorer.coverDisableInstrumentation", + "group": "vcast.project", + "when": "testId =~ /\\.vcp$/ && testId in vectorcastTestExplorer.coverInPlaceList" + }, { "command": "vectorcastTestExplorer.buildProjectEnviro", "group": "vcast.build", diff --git a/python/vTestInterface.py b/python/vTestInterface.py index ad531a8f..463e6eb4 100644 --- a/python/vTestInterface.py +++ b/python/vTestInterface.py @@ -939,7 +939,7 @@ def processCommandLogic(mode, clicast, pathToUse, testString="", options=""): test_data = getVCPResultsList(vcp_path) unit_data = getUnitData(api) mocking_support = False # Cover projects do not support mocking - api.close() + in_place = api.environment.instrumenting_in_place vcp_list.append( { @@ -947,6 +947,7 @@ def processCommandLogic(mode, clicast, pathToUse, testString="", options=""): "testData": test_data, "unitData": unit_data, "mockingSupport": mocking_support, + "inPlace": in_place } ) except Exception as err: diff --git a/src/coverage.ts b/src/coverage.ts index e4618054..bde3dd72 100644 --- a/src/coverage.ts +++ b/src/coverage.ts @@ -9,7 +9,7 @@ import { getListOfFilesWithCoverage, } from "./vcastTestInterface"; -import { getRangeOption } from "./utilities"; +import { fileIsVCPAndInPlace, getRangeOption } from "./utilities"; import { fileDecorator } from "./fileDecorator"; import { @@ -183,10 +183,14 @@ export async function updateCOVdecorations() { ) { const filePath = url.fileURLToPath(activeEditor.document.uri.toString()); + // We have to check if the source file is part of a Cover project AND + // whether it is instrumented in_place. If so, we do not want to show coverage. + const fileIsPartOfVCPAndInPlace = fileIsVCPAndInPlace(filePath); + // this returns the cached coverage data for this file const coverageData = getCoverageDataForFile(filePath); - if (coverageData.hasCoverageData) { + if (coverageData.hasCoverageData && !fileIsPartOfVCPAndInPlace) { // there is coverage data and it matches the file checksum // Reset the global decoration arrays resetGlobalDecorations(); diff --git a/src/extension.ts b/src/extension.ts index 3b88a3e9..524315f9 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -70,6 +70,7 @@ import { setGlobalProjectIsOpenedChecker, setGlobalCompilerAndTestsuites, loadTestScriptButton, + getVcpDataFromCache, } from "./testPane"; import { @@ -126,6 +127,7 @@ import { launchFile, globalPathToSupportFiles, initializeInstallerFiles, + clicastCommandToUse, } from "./vcastInstallation"; import { @@ -173,6 +175,7 @@ import { resolveWebviewBase, setCompilerList, } from "./manage/manageSrc/manageUtils"; +import { executeWithRealTimeEchoNoCallback } from "./vcastCommandRunner"; const path = require("path"); @@ -476,9 +479,26 @@ function configureExtension(context: vscode.ExtensionContext) { // Command: vectorcastTestExplorer.coverRemoveAllResults//////////////////////////////////////////////////////// let coverRemoveAllResultsCommand = vscode.commands.registerCommand( "vectorcastTestExplorer.coverRemoveAllResults", - (args: any) => { - // TODO: implement remove all results - vectorMessage(`${args.id}`); + async (args: any) => { + const resultNode = getVcpTestNode(args.id); + const coverProjectName = resultNode.projectName.split(".vcp")[0]; + const cwd = path.dirname(resultNode.projectPath); + const commandArgs = [ + "-e", + coverProjectName, + "cover", + "RESult", + "REMove", + "all", + ]; + const vscodeInfoMEssage = `Removing all Results from ${cwd}`; + await executeWithRealTimeEchoNoCallback( + clicastCommandToUse, + commandArgs, + cwd, + vscodeInfoMEssage + ); + await refreshAllExtensionData(); } ); context.subscriptions.push(coverRemoveAllResultsCommand); @@ -486,9 +506,28 @@ function configureExtension(context: vscode.ExtensionContext) { // Command: vectorcastTestExplorer.coverRemoveResult//////////////////////////////////////////////////////// let coverRemoveResultCommand = vscode.commands.registerCommand( "vectorcastTestExplorer.coverRemoveResult", - (args: any) => { - // TODO: implement remove result - vectorMessage(`${args.id}`); + async (args: any) => { + const singleResultNode = getVcpTestNode(args.id); + const resultFilePath = singleResultNode.sourceFilePath; + const resultFileName = path.basename(resultFilePath); + const coverProjectName = singleResultNode.projectName.split(".vcp")[0]; + const cwd = path.dirname(singleResultNode.projectPath); + const commandArgs = [ + "-e", + coverProjectName, + "cover", + "RESult", + "REMove", + resultFileName, + ]; + const vscodeInfoMEssage = `Removing Result ${resultFileName} from ${cwd}`; + await executeWithRealTimeEchoNoCallback( + clicastCommandToUse, + commandArgs, + cwd, + vscodeInfoMEssage + ); + await refreshAllExtensionData(); } ); context.subscriptions.push(coverRemoveResultCommand); @@ -496,13 +535,98 @@ function configureExtension(context: vscode.ExtensionContext) { // Command: vectorcastTestExplorer.coverAddResult//////////////////////////////////////////////////////// let coverAddResultCommand = vscode.commands.registerCommand( "vectorcastTestExplorer.coverAddResult", - (args: any) => { - // TODO: implement add result - vectorMessage(`${args.id}`); + async (args: any) => { + const resultNode = getVcpTestNode(args.id); + const coverProjectName = resultNode.projectName.split(".vcp")[0]; + const cwd = path.dirname(resultNode.projectPath); + + const selectedFiles = await vscode.window.showOpenDialog({ + canSelectMany: true, + canSelectFolders: false, + filters: { "Result Files": ["lua"] }, + title: "Select Result File(s) to Add", + }); + + if (!selectedFiles || selectedFiles.length === 0) return; + + for (const file of selectedFiles) { + const resultFileName = path.basename(file.fsPath); + const commandArgs = [ + "-e", + coverProjectName, + "cover", + "RESult", + "ADD", + resultFileName, + ]; + const vscodeInfoMessage = `Adding result ${resultFileName} to ${cwd}`; + await executeWithRealTimeEchoNoCallback( + clicastCommandToUse, + commandArgs, + cwd, + vscodeInfoMessage + ); + } + + await refreshAllExtensionData(); } ); context.subscriptions.push(coverAddResultCommand); + // Command: vectorcastTestExplorer.coverEnableInstrumentation//////////////////////////////////////////////////////// + let coverEnableInstrumentationCommand = vscode.commands.registerCommand( + "vectorcastTestExplorer.coverEnableInstrumentation", + async (args: any) => { + const vcpNode = getVcpDataFromCache(args.id); + const projectPath = vcpNode.vcpPath.split(".vcp")[0]; + const coverProjectName = path.basename(projectPath); + const cwd = path.dirname(projectPath); + const commandArgs = [ + "-e", + coverProjectName, + "cover", + "environment", + "enable_instrumentation", + ]; + const vscodeInfoMessage = `Enabling instrumentation for ${coverProjectName}`; + await executeWithRealTimeEchoNoCallback( + clicastCommandToUse, + commandArgs, + cwd, + vscodeInfoMessage + ); + await refreshAllExtensionData(); + } + ); + context.subscriptions.push(coverEnableInstrumentationCommand); + + // Command: vectorcastTestExplorer.coverDisableInstrumentation//////////////////////////////////////////////////////// + let coverDisableInstrumentationCommand = vscode.commands.registerCommand( + "vectorcastTestExplorer.coverDisableInstrumentation", + async (args: any) => { + const vcpNode = getVcpDataFromCache(args.id); + const projectPath = vcpNode.vcpPath.split(".vcp")[0]; + const coverProjectName = path.basename(projectPath); + const cwd = path.dirname(projectPath); + const commandArgs = [ + "-e", + coverProjectName, + "cover", + "environment", + "disable_instrumentation", + ]; + const vscodeInfoMessage = `Disabling instrumentation for ${coverProjectName}`; + await executeWithRealTimeEchoNoCallback( + clicastCommandToUse, + commandArgs, + cwd, + vscodeInfoMessage + ); + await refreshAllExtensionData(); + } + ); + context.subscriptions.push(coverDisableInstrumentationCommand); + // Command: vectorcastTestExplorer.insertATGTestsFromEditor//////////////////////////////////////////////////////// let insertATGTestsFromEditorCommand = vscode.commands.registerCommand( "vectorcastTestExplorer.insertATGTestsFromEditor", @@ -1283,18 +1407,21 @@ function configureExtension(context: vscode.ExtensionContext) { const fileID: string = args.id; const isVcpFile = fileID.endsWith("::file"); let testNode: testNodeType | vcpNodeType; + // If it's a vcp file, we do not have additional info like function or unit, so we just open it up at line 0 if (isVcpFile) { testNode = getVcpTestNode(fileID); - if (!testNode) { + const sourceFilePath = testNode.sourceFilePath; + if (!testNode || !sourceFilePath) { vscode.window.showErrorMessage( `Unable to open Source File for Node: ${fileID}` ); return; } - await openFileAtLine(testNode.sourceFilePath, 0); + await openFileAtLine(sourceFilePath, 0); return; } + // If it's a unit test node, we can open up the source file more specifically testNode = getTestNode(fileID); if (!testNode) { vscode.window.showErrorMessage( diff --git a/src/testData.ts b/src/testData.ts index 7ed65924..d13ea69d 100644 --- a/src/testData.ts +++ b/src/testData.ts @@ -34,40 +34,45 @@ export function clearEnviroDataCache() { export let coverFilesNodeIDList: string[] = []; export let coverResultNodeIDList: string[] = []; +export let coverInPlaceList: string[] = []; export const compoundOnlyString = " [compound only]"; export interface vcpNodeType { vcpNodeID: string; - sourceFilePath: string; projectPath: string; projectName: string; isVcpFile: boolean; isVcpResult: boolean; projectNodeID: string; + inPlace: boolean; + sourceFilePath?: string; } export const vcpNodeCache = new Map(); export function createVcpNodeInCache( vcpNodeID: string, - sourceFilePath: string, projectPath: string, projectName: string, projectNodeID: string, isVcpFile: boolean, - isVcpResult: boolean + isVcpResult: boolean, + inPlace: boolean, + sourceFilePath?: string ) { let vcpNode: vcpNodeType = { vcpNodeID: vcpNodeID, - sourceFilePath: sourceFilePath, projectPath: projectPath, projectName: projectName, projectNodeID: projectNodeID, isVcpFile: isVcpFile, isVcpResult: isVcpResult, + inPlace: inPlace, + sourceFilePath: sourceFilePath, }; + // Prepare the lists for the package.json --> 1) All Files, 2) All Results, 3) All VCPs in place for coverage vcpNodeCache.set(vcpNodeID, vcpNode); if (vcpNode.isVcpFile && !coverFilesNodeIDList.includes(vcpNodeID)) { coverFilesNodeIDList.push(vcpNodeID); @@ -75,6 +80,15 @@ export function createVcpNodeInCache( coverResultNodeIDList.push(vcpNodeID); } + if (inPlace) { + if (!coverInPlaceList.includes(projectNodeID)) { + coverInPlaceList.push(projectNodeID); + } + } else { + // Remove it from the list if it was there before + coverInPlaceList = coverInPlaceList.filter((id) => id !== projectNodeID); + } + setCoverProjectContext(); } @@ -97,6 +111,11 @@ export function setCoverProjectContext() { "vectorcastTestExplorer.coverResultNodeIDList", coverResultNodeIDList ); + vscode.commands.executeCommand( + "setContext", + "vectorcastTestExplorer.coverInPlaceList", + coverInPlaceList + ); } export interface testNodeType { diff --git a/src/testPane.ts b/src/testPane.ts index 551b792a..3f844174 100644 --- a/src/testPane.ts +++ b/src/testPane.ts @@ -141,6 +141,7 @@ export type EnviroData = { testData: FileTestData[]; unitData: UnitData[]; mockingSupport: boolean; + inPlace: boolean; }; export type CachedWorkspaceData = { @@ -654,6 +655,7 @@ export function addVcpEnvironments( displayName: displayName, workspaceRoot: workspaceRoot, isVcp: true, + inPlace: vcpData.inPlace, }); } } @@ -2192,18 +2194,27 @@ async function createVcpChildNodes( const vcpData = getVcpDataFromCache(enviroData.buildDirectory); saveEnviroNodeData(enviroData.buildDirectory, enviroData); updateGlobalDataForFile(enviroData.buildDirectory, vcpData.unitData); - + const inPlace = vcpData.inPlace; const projectNodeID = vcpProjectNode.id; // Create "Files" node const filesNodeId = `${enviroData.buildDirectory}::files`; + const projectName = path.basename(enviroData.projectPath); const filesNode = globalController.createTestItem( filesNodeId, "Files" ) as vcastTestItem; + createVcpNodeInCache( + filesNodeId, + normalizePath(enviroData.projectPath), + projectName, + projectNodeID, + false, + false, + inPlace + ); filesNode.nodeKind = nodeKind.vcpFiles; vcpProjectNode.children.add(filesNode); - const projectName = path.basename(enviroData.projectPath); // Add source files as children under Files node if (vcpData?.unitData && Array.isArray(vcpData.unitData)) { @@ -2226,12 +2237,13 @@ async function createVcpChildNodes( createVcpNodeInCache( unitNodeId, - sourceFilePath, - enviroData.projectPath, + normalizePath(enviroData.projectPath), projectName, projectNodeID, true, - false + false, + inPlace, + normalizePath(sourceFilePath) ); } } @@ -2246,6 +2258,16 @@ async function createVcpChildNodes( resultsNode.nodeKind = nodeKind.vcpResults; vcpProjectNode.children.add(resultsNode); + createVcpNodeInCache( + resultsNodeId, + normalizePath(enviroData.projectPath), + projectName, + projectNodeID, + false, + false, + inPlace + ); + // Add .lua result files as children under Results node if (vcpData?.testData && Array.isArray(vcpData.testData)) { for (const resultFile of vcpData.testData) { @@ -2266,12 +2288,13 @@ async function createVcpChildNodes( resultsNode.children.add(resultNode); createVcpNodeInCache( resultNodeId, - resultFile, - enviroData.projectPath, + normalizePath(enviroData.projectPath), projectName, projectNodeID, false, - true + true, + inPlace, + normalizePath(resultFile) ); } } @@ -2281,7 +2304,7 @@ async function createVcpChildNodes( /** * Helper function to get VCP data from the workspace cache */ -function getVcpDataFromCache(vcpPath: string): any { +export function getVcpDataFromCache(vcpPath: string): any { if (cachedWorkspaceEnvData?.vcp) { const normalizedPath = normalizePath(vcpPath); return cachedWorkspaceEnvData.vcp.find( diff --git a/src/utilities.ts b/src/utilities.ts index b77e5ed3..d060ba2e 100644 --- a/src/utilities.ts +++ b/src/utilities.ts @@ -12,6 +12,7 @@ import { rebuildEnvironmentCallback } from "./callbacks"; import { CachedWorkspaceData, EnviroData } from "./testPane"; import { executeWithRealTimeEchoWithProgress } from "./vcastCommandRunner"; import { getVectorCastInstallationLocation } from "./vcastInstallation"; +import { vcpNodeCache, vcpNodeType } from "./testData"; const fs = require("fs"); const glob = require("glob"); @@ -525,3 +526,30 @@ export async function openFileAtLine( selection: selection, }); } + +/** + * Returns true if the given file path is part of a vcp project and is in_place + */ +export function fileIsVCPAndInPlace(filePath: string) { + let vcpNode = getVcpNodeBySourceFilePath(filePath); + + if (vcpNode?.inPlace) { + return true; + } else { + return false; + } +} + +/** + * Returns vcp node for a given source file path + */ +export function getVcpNodeBySourceFilePath( + sourceFilePath: string +): vcpNodeType | undefined { + for (const node of vcpNodeCache.values()) { + if (node.sourceFilePath === sourceFilePath) { + return node; + } + } + return undefined; +} diff --git a/src/vcastCommandRunner.ts b/src/vcastCommandRunner.ts index 28eade48..bd943b4a 100644 --- a/src/vcastCommandRunner.ts +++ b/src/vcastCommandRunner.ts @@ -616,3 +616,84 @@ export async function executeClicastCommandUsingServer( } return commandStatus; } + +export function executeWithRealTimeEchoNoCallback( + command: string, + argList: string[], + CWD: string, + vscodeMessage: string +) { + return vscode.window.withProgress( + { + location: vscode.ProgressLocation.Notification, + title: `${vscodeMessage}`, + cancellable: true, + }, + async (progress, token) => { + progress.report({ increment: 10 }); + + let processHandle = spawn(command, argList, { + cwd: CWD, + windowsHide: true, + }); + vectorMessage("-".repeat(100)); + vectorMessage("-".repeat(100)); + let messageFragment: string = ""; + + let progressValue = 10; + const progressInterval = setInterval(() => { + if (progressValue < 90) { + progressValue += 15; + progress.report({ increment: 10 }); + } + }, 3000); + + token.onCancellationRequested(() => { + if (processHandle) { + processHandle.kill(); + vectorMessage(`User cancelled the operation.`); + } + clearInterval(progressInterval); + }); + + await new Promise((resolve) => { + processHandle.stdout.on("data", function (data: any) { + const rawString = data.toString(); + const lineArray = rawString.split(/[\n\r?]/); + + if (messageFragment.length > 0) { + lineArray[0] = messageFragment + lineArray[0]; + messageFragment = ""; + } + + if (!rawString.endsWith("\n") && !rawString.endsWith("\r")) { + messageFragment = lineArray.pop(); + } + + for (const line of lineArray) { + if (line.length > 0) { + vectorMessage(line.replace(/\n/g, "")); + } + } + }); + + processHandle.on("exit", async function (code: any) { + clearInterval(progressInterval); + progress.report({ increment: 100 }); + vectorMessage("-".repeat(100)); + vectorMessage( + `${path.basename(command)}: '${argList.join(" ")}' returned exit code: ${code.toString()}` + ); + vectorMessage("-".repeat(100)); + resolve(); + }); + + processHandle.on("error", (error) => { + clearInterval(progressInterval); + vectorMessage(`Error occurred: ${error.message}`); + resolve(); + }); + }); + } + ); +} From 3051e5943182bdfe6d162bfedee0426749846abd Mon Sep 17 00:00:00 2001 From: Den1552 Date: Wed, 18 Feb 2026 17:26:16 +0100 Subject: [PATCH 40/42] Added possibility to open cover in vcastqt --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 45893a00..4fb95df1 100644 --- a/package.json +++ b/package.json @@ -1019,7 +1019,7 @@ { "command": "vectorcastTestExplorer.openProjectInVectorCAST", "group": "vcast.project", - "when": "testId =~ /\\.vcm$/ || testId in vectorcastTestExplorer.globalProjectCompilers || testId in vectorcastTestExplorer.globalProjectTestsuites" + "when": "testId =~ /\\.vcm$/ || testId =~ /\\.vcp$/ || testId in vectorcastTestExplorer.globalProjectCompilers || testId in vectorcastTestExplorer.globalProjectTestsuites" }, { "command": "vectorcastTestExplorer.addCompilerToProject", From 475961b97717cff8458e0c642b3a98e7b13b0845 Mon Sep 17 00:00:00 2001 From: Den1552 Date: Wed, 18 Feb 2026 18:07:39 +0100 Subject: [PATCH 41/42] Changed result file retrieval --- python/vTestInterface.py | 30 ++++++++---------------------- src/extension.ts | 1 - 2 files changed, 8 insertions(+), 23 deletions(-) diff --git a/python/vTestInterface.py b/python/vTestInterface.py index 463e6eb4..af801567 100644 --- a/python/vTestInterface.py +++ b/python/vTestInterface.py @@ -936,7 +936,7 @@ def processCommandLogic(mode, clicast, pathToUse, testString="", options=""): for vcp_path in vcp_files: try: api = CoverApi(vcp_path) - test_data = getVCPResultsList(vcp_path) + test_data = getVCPResultsList(api) unit_data = getUnitData(api) mocking_support = False # Cover projects do not support mocking in_place = api.environment.instrumenting_in_place @@ -1047,37 +1047,23 @@ def processCommandLogic(mode, clicast, pathToUse, testString="", options=""): return returnCode, returnObject -def getVCPResultsList(vcp_path): +def getVCPResultsList(api): """ - Recursively searches for all.lua and api.lua result files in the VCP root directory + Returns a list of all result file paths in the VCP project """ - import os - import glob - resultsList = [] try: - # Get the directory containing the .vcp file - vcp_dir = os.path.dirname(vcp_path) - - # Recursively search for all.lua files - all_lua_pattern = os.path.join(vcp_dir, "**", "all.lua") - all_lua_files = glob.glob(all_lua_pattern, recursive=True) - - # Recursively search for api.lua files - api_lua_pattern = os.path.join(vcp_dir, "**", "api.lua") - api_lua_files = glob.glob(api_lua_pattern, recursive=True) - - # Combine and add full paths to the results list - for lua_file in all_lua_files + api_lua_files: - resultsList.append(os.path.abspath(lua_file)) + for result in api.Result.all(): + result_path = result.absolute_path + if result_path: + resultsList.append(result_path) except Exception as e: - print(f"Error searching for .lua files in {vcp_path}: {e}") + print(f"Error retrieving results from VCP: {e}") return resultsList - def processCommand(mode, clicast, pathToUse, testString="", options=""): """ This is a wrapper for process command logic, so that we can process diff --git a/src/extension.ts b/src/extension.ts index 524315f9..50277803 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -543,7 +543,6 @@ function configureExtension(context: vscode.ExtensionContext) { const selectedFiles = await vscode.window.showOpenDialog({ canSelectMany: true, canSelectFolders: false, - filters: { "Result Files": ["lua"] }, title: "Select Result File(s) to Add", }); From 20efe6d7678faf981d0450fb8822f73e0dd08662 Mon Sep 17 00:00:00 2001 From: Den1552 Date: Tue, 3 Mar 2026 17:12:33 +0100 Subject: [PATCH 42/42] Initial coverage filter for files (not finished) --- package.json | 14 ++ src/coverage.ts | 140 ++++++++++++++++ src/extension.ts | 149 ++++++++++++++++++ src/manage/webviews/css/coverageFilter.css | 135 ++++++++++++++++ src/manage/webviews/html/coverageFilter.html | 29 ++++ .../webviews/webviewScripts/coverageFilter.js | 90 +++++++++++ src/utilities.ts | 2 +- src/vcastTestInterface.ts | 29 +++- 8 files changed, 585 insertions(+), 3 deletions(-) create mode 100644 src/manage/webviews/css/coverageFilter.css create mode 100644 src/manage/webviews/html/coverageFilter.html create mode 100644 src/manage/webviews/webviewScripts/coverageFilter.js diff --git a/package.json b/package.json index 4fb95df1..c7b4602f 100644 --- a/package.json +++ b/package.json @@ -120,6 +120,11 @@ "category": "VectorCAST Test Explorer", "title": "Cover: Disable Instrumentation" }, + { + "command": "vectorcastTestExplorer.configureCoverageFilter", + "title": "Configure Coverage Filter", + "category": "VectorCAST Text Explorer" + }, { "command": "vectorcastTestExplorer.buildProjectEnviro", "category": "VectorCAST Test Explorer", @@ -690,6 +695,10 @@ "command": "vectorcastTestExplorer.buildExecuteIncremental", "when": "never" }, + { + "command": "vectorcastTestExplorer.configureCoverageFilter", + "when": "never" + }, { "command": "vectorcastTestExplorer.buildProjectEnviro", "when": "never" @@ -930,6 +939,11 @@ "when": "(resourceLangId == cpp || resourceLangId == c) && vectorcastTestExplorer.globalProjectIsOpenedChecker", "group": "2_workspace" }, + { + "command": "vectorcastTestExplorer.configureCoverageFilter", + "group": "2_workspace", + "when": "resourceLangId == cpp || resourceLangId == c" + }, { "command": "vectorcastTestExplorer.addLaunchConfiguration", "when": "vectorcastTestExplorer.configured && resourceFilename==launch.json", diff --git a/src/coverage.ts b/src/coverage.ts index bde3dd72..a4a6adef 100644 --- a/src/coverage.ts +++ b/src/coverage.ts @@ -1,4 +1,6 @@ import * as vscode from "vscode"; +import * as fs from "fs"; +import path = require("node:path"); import { DecorationRenderOptions, TextEditorDecorationType, @@ -83,6 +85,8 @@ export function initializeCodeCoverageFeatures( //fontWeight: "bold", gutterIconPath: context.asAbsolutePath("./images/light/cover-icon.svg"), }; + + initCoverageFilterStatusBarItem(context); } // global decoration arrays @@ -277,6 +281,7 @@ export async function updateCOVdecorations() { } else { // we get here for non-C/C++ files coverageStatusBarObject.hide(); + hideCoverageFilterStatusBar(); } } @@ -335,3 +340,138 @@ export async function toggleCoverageAction() { export async function updateDisplayedCoverage() { if (coverageOn) await updateCOVdecorations(); } + +// ── Types ──────────────────────────────────────────────────── + +/** + * Shape of coverageFilter.json on disk: + * { "": ["", ...], ... } + * + * A source file that is present in the JSON has an explicit list of + * enabled enviros. A source file that is absent is treated as + * "all enviros enabled" (the default). + */ +type CoverageFilterJson = Record; + +// ── File-path helper ───────────────────────────────────────── + +/** + * Returns the path to the coverageFilter.json file for a given workspace root. + */ +export function getCoverageFilterJsonPath(workspaceRoot: string): string { + return path.join(workspaceRoot, ".vscode", "coverageFilter.json"); +} + +// ── Read / Write helpers ───────────────────────────────────── + +/** + * Reads and parses coverageFilter.json for the given workspace root. + * Returns an empty object if the file does not exist or is corrupt. + */ +export function readCoverageFilterFile( + workspaceRoot: string +): CoverageFilterJson { + const filePath = getCoverageFilterJsonPath(workspaceRoot); + if (!fs.existsSync(filePath)) return {}; + try { + return JSON.parse(fs.readFileSync(filePath, "utf8")) as CoverageFilterJson; + } catch { + // Corrupt JSON – start fresh + return {}; + } +} + +/** + * Writes data back to coverageFilter.json for the given workspace root. + * Creates .vscode/ if it does not exist yet. + */ +export function writeCoverageFilterFile( + workspaceRoot: string, + data: CoverageFilterJson +): void { + const vscodeDir = path.join(workspaceRoot, ".vscode"); + if (!fs.existsSync(vscodeDir)) { + fs.mkdirSync(vscodeDir, { recursive: true }); + } + fs.writeFileSync( + getCoverageFilterJsonPath(workspaceRoot), + JSON.stringify(data, null, 2), + "utf8" + ); +} + +// ── Public state accessor used by getCoverageDataForFile ───── + +/** + * Returns the set of enabled enviro paths for a given source file by reading + * the coverageFilter.json that belongs to that file's workspace folder. + * + * Returns undefined when no filter entry exists for this file, which means + * all enviros are enabled (the default behaviour). + */ +export function getEnabledEnvirosForFile( + filePath: string +): Set | undefined { + const workspaceFolder = vscode.workspace.getWorkspaceFolder( + vscode.Uri.file(filePath) + ); + if (!workspaceFolder) return undefined; + + const data = readCoverageFilterFile(workspaceFolder.uri.fsPath); + if (!(filePath in data)) { + // No entry yet – all enviros are enabled + return undefined; + } + return new Set(data[filePath]); +} + +let coverageFilterStatusBarItem: vscode.StatusBarItem; + +export function initCoverageFilterStatusBarItem( + context: vscode.ExtensionContext +): void { + coverageFilterStatusBarItem = vscode.window.createStatusBarItem( + vscode.StatusBarAlignment.Right, + // Adjust the priority number so the item sits where you want it relative + // to other status bar entries (higher = further right). + 99 + ); + coverageFilterStatusBarItem.color = new vscode.ThemeColor("charts.yellow"); + coverageFilterStatusBarItem.tooltip = + "Not all environments are included in the displayed coverage.\n" + + "Click to configure the coverage filter."; + coverageFilterStatusBarItem.command = + "vectorcastTestExplorer.configureCoverageFilter"; + context.subscriptions.push(coverageFilterStatusBarItem); +} + +export function updateCoverageFilterStatusBar( + totalEnviros: number, + enabledEnviros: Set | undefined +): void { + if (enabledEnviros === undefined) { + // No filter set for this file – all enviros are shown, nothing to warn about + coverageFilterStatusBarItem.hide(); + return; + } + + // Count only enabled enviros that still exist (guards against stale JSON entries + // left over after an enviro has been deleted from the project) + const enabledCount = enabledEnviros.size; + + if (enabledCount >= totalEnviros) { + // Every enviro is enabled – hide the warning + hideCoverageFilterStatusBar(); + return; + } + + // At least one enviro is filtered out – show the yellow warning + coverageFilterStatusBarItem.text = `$(warning) File Coverage: (${enabledCount}/${totalEnviros}) Environments / Projects`; + coverageFilterStatusBarItem.show(); +} + +export function hideCoverageFilterStatusBar() { + if (coverageFilterStatusBarItem) { + coverageFilterStatusBarItem.hide(); + } +} diff --git a/src/extension.ts b/src/extension.ts index 50277803..baaa806f 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -20,6 +20,9 @@ import { toggleCoverageAction, updateDisplayedCoverage, updateCOVdecorations, + readCoverageFilterFile, + writeCoverageFilterFile, + getCoverageFilterJsonPath, } from "./coverage"; import { @@ -155,6 +158,7 @@ import { newTestScript, openCodedTest, ProjectEnvParameters, + globalCoverageData, } from "./vcastTestInterface"; import { @@ -178,6 +182,7 @@ import { import { executeWithRealTimeEchoNoCallback } from "./vcastCommandRunner"; const path = require("path"); +const url = require("url"); /** * Decodes a Base64-encoded variable name. @@ -626,6 +631,150 @@ function configureExtension(context: vscode.ExtensionContext) { ); context.subscriptions.push(coverDisableInstrumentationCommand); + let configureCoverageFilterCommand = vscode.commands.registerCommand( + "vectorcastTestExplorer.configureCoverageFilter", + async (fileUri?: vscode.Uri) => { + // Resolve the target file path – prefer a URI passed by a menu + // contribution, fall back to the currently active editor. + let filePath: string | undefined; + if (fileUri) { + filePath = fileUri.fsPath; + } else { + const editor = vscode.window.activeTextEditor; + if (editor) { + filePath = url.fileURLToPath(editor.document.uri.toString()); + } + } + + if (!filePath) { + vscode.window.showErrorMessage("No active C/C++ file found."); + return; + } + + const ext = path.extname(filePath).toLowerCase(); + if (![".c", ".cpp", ".h"].includes(ext)) { + vscode.window.showErrorMessage( + "Coverage filter is only available for .c, .cpp, and .h files." + ); + return; + } + + const fileData = globalCoverageData.get(filePath); + // The keys of enviroList are the enviro paths – these are the tickable items + const enviros: string[] = fileData ? [...fileData.enviroList.keys()] : []; + + if (enviros.length === 0) { + vscode.window.showInformationMessage( + "No environments are associated with this file." + ); + return; + } + + // Read the persisted state from disk for this file's workspace. + // If the file is not yet in the JSON, default to all enviros enabled. + const workspaceFolder = + vscode.workspace.getWorkspaceFolder(vscode.Uri.file(filePath)) ?? + vscode.workspace.workspaceFolders?.[0]; + + const workspaceRoot = workspaceFolder!.uri.fsPath; + + // Ensure the file exists the first time (all enviros ticked by default) + const vscodeDir = path.join(workspaceRoot, ".vscode"); + const filterFilePath = getCoverageFilterJsonPath(workspaceRoot); + if (!fs.existsSync(filterFilePath)) { + if (!fs.existsSync(vscodeDir)) { + fs.mkdirSync(vscodeDir, { recursive: true }); + } + fs.writeFileSync(filterFilePath, "{}\n", "utf8"); + } + + const filterData = readCoverageFilterFile(workspaceRoot); + // If no entry exists yet, treat all enviros as enabled + const enabledList: string[] = + filePath in filterData ? filterData[filePath] : [...enviros]; + + const baseDir = resolveWebviewBase(context); + const panel = vscode.window.createWebviewPanel( + "coverageFilter", + "Coverage Filter", + vscode.ViewColumn.Active, + { + enableScripts: true, + retainContextWhenHidden: true, + localResourceRoots: [vscode.Uri.file(baseDir)], + } + ); + + // ── Build webview HTML (same pattern as getNewProjectWebviewContent) ── + const cssOnDisk = vscode.Uri.file( + path.join(baseDir, "css", "coverageFilter.css") + ); + const scriptOnDisk = vscode.Uri.file( + path.join(baseDir, "webviewScripts", "coverageFilter.js") + ); + const htmlPath = path.join(baseDir, "html", "coverageFilter.html"); + const cssUri = panel.webview.asWebviewUri(cssOnDisk); + const scriptUri = panel.webview.asWebviewUri(scriptOnDisk); + const nonce = getNonce(); + + let html = fs.readFileSync(htmlPath, "utf8"); + html = html.replace( + //, + ` + + ` + ); + html = html.replace("{{ cssUri }}", cssUri.toString()); + html = html.replace( + "{{ scriptUri }}", + `` + ); + panel.webview.html = html; + + panel.webview.onDidReceiveMessage( + async (msg: { + command: string; + filePath?: string; + enabledEnviros?: string[]; + }) => { + switch (msg.command) { + case "apply": { + if (msg.filePath && msg.enabledEnviros) { + // Write the updated selection back to disk + const updated = readCoverageFilterFile(workspaceRoot); + updated[msg.filePath] = msg.enabledEnviros; + writeCoverageFilterFile(workspaceRoot, updated); + + vscode.window.showInformationMessage( + `Coverage filter updated: ${msg.enabledEnviros.length}/${enviros.length} environment(s) enabled.` + ); + + // Redraw coverage decorations immediately + await updateDisplayedCoverage(); + } + panel.dispose(); + break; + } + + case "cancel": + panel.dispose(); + break; + } + }, + undefined, + context.subscriptions + ); + } + ); + + context.subscriptions.push(configureCoverageFilterCommand); + // Command: vectorcastTestExplorer.insertATGTestsFromEditor//////////////////////////////////////////////////////// let insertATGTestsFromEditorCommand = vscode.commands.registerCommand( "vectorcastTestExplorer.insertATGTestsFromEditor", diff --git a/src/manage/webviews/css/coverageFilter.css b/src/manage/webviews/css/coverageFilter.css new file mode 100644 index 00000000..f1a0833b --- /dev/null +++ b/src/manage/webviews/css/coverageFilter.css @@ -0,0 +1,135 @@ +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + background-color: #1e1e1e; + color: #d4d4d4; + display: flex; + align-items: flex-start; + justify-content: center; + padding: 30px 20px; + min-height: 100vh; +} + +.modal { + width: 750px; + background-color: #252526; + padding: 25px; + border-radius: 8px; + box-shadow: 0 0 10px rgba(0,0,0,0.3); +} + +.modal h2 { + text-align: center; + color: #ffffff; + font-size: 22px; + margin-bottom: 8px; +} + +.subtitle { + text-align: center; + color: #9cdcfe; + font-size: 12px; + margin-bottom: 18px; + word-break: break-all; +} + +.enviro-list { + display: flex; + flex-direction: column; + gap: 6px; + max-height: 55vh; + overflow-y: auto; + margin-bottom: 18px; + border: 1px solid #3c3c3c; + border-radius: 4px; + padding: 10px; + background: #1e1e1e; +} + +.enviro-item { + display: flex; + align-items: center; + gap: 10px; + padding: 8px 10px; + border-radius: 4px; + background: #2d2d2d; + border: 1px solid #3c3c3c; + cursor: pointer; + transition: background 0.15s; +} + +.enviro-item:hover { + background: #37373d; +} + +.enviro-item input[type="checkbox"] { + width: 16px; + height: 16px; + cursor: pointer; + accent-color: #007acc; + flex-shrink: 0; +} + +.enviro-item label { + font-size: 13px; + color: #d4d4d4; + cursor: pointer; + word-break: break-all; + flex: 1; + margin: 0; +} + +.enviro-item.checked { + border-color: #007acc44; + background: #1a3a50; +} + +.empty-msg { + color: #888; + font-size: 13px; + text-align: center; + padding: 20px 0; +} + +.button-container { + display: flex; + align-items: center; + gap: 10px; + margin-top: 4px; +} + +.primary-button, +.cancel-button, +.select-button { + padding: 8px 18px; + font-size: 14px; + border: none; + border-radius: 4px; + cursor: pointer; + transition: background-color 0.2s; +} + +.primary-button { + background-color: #007acc; + color: white; +} +.primary-button:hover { background-color: #005f99; } + +.cancel-button { + background-color: #cc4444; + color: white; +} +.cancel-button:hover { background-color: #992222; } + +.select-button { + background-color: #3c3c3c; + color: #d4d4d4; + border: 1px solid #555; +} +.select-button:hover { background-color: #505050; } +.select-button.deselect { } diff --git a/src/manage/webviews/html/coverageFilter.html b/src/manage/webviews/html/coverageFilter.html new file mode 100644 index 00000000..0358d80f --- /dev/null +++ b/src/manage/webviews/html/coverageFilter.html @@ -0,0 +1,29 @@ + + + + + + Coverage Filter + + + + + + {{ scriptUri }} + + diff --git a/src/manage/webviews/webviewScripts/coverageFilter.js b/src/manage/webviews/webviewScripts/coverageFilter.js new file mode 100644 index 00000000..7b955d50 --- /dev/null +++ b/src/manage/webviews/webviewScripts/coverageFilter.js @@ -0,0 +1,90 @@ +const vscode = acquireVsCodeApi(); + +window.addEventListener("DOMContentLoaded", () => { + /** @type {string[]} */ + const enviros = window.enviroData || []; + /** @type {string[]} */ + const enabledEnviros = window.enabledEnviros || []; + /** @type {string} */ + const filePath = window.filePath || ""; + + document.getElementById("fileLabel").textContent = filePath; + + const listEl = document.getElementById("enviroList"); + + // Track state: map enviroPath -> checked boolean + /** @type {Map} */ + const state = new Map(); + for (const e of enviros) { + // enabled if present in enabledEnviros (or enabledEnviros is empty → all enabled) + const isEnabled = + enabledEnviros.length === 0 ? true : enabledEnviros.includes(e); + state.set(e, isEnabled); + } + + function renderList() { + listEl.innerHTML = ""; + if (enviros.length === 0) { + const msg = document.createElement("div"); + msg.className = "empty-msg"; + msg.textContent = "No environments found for this file."; + listEl.appendChild(msg); + return; + } + + for (const envPath of enviros) { + const checked = state.get(envPath) ?? true; + + const item = document.createElement("div"); + item.className = "enviro-item" + (checked ? " checked" : ""); + + const cb = document.createElement("input"); + cb.type = "checkbox"; + cb.id = "cb_" + envPath; + cb.checked = checked; + + const lbl = document.createElement("label"); + lbl.htmlFor = cb.id; + lbl.textContent = envPath; + + cb.addEventListener("change", () => { + state.set(envPath, cb.checked); + item.classList.toggle("checked", cb.checked); + }); + + // clicking the row also toggles + item.addEventListener("click", (e) => { + if (e.target === cb || e.target === lbl) return; + cb.checked = !cb.checked; + cb.dispatchEvent(new Event("change")); + }); + + item.appendChild(cb); + item.appendChild(lbl); + listEl.appendChild(item); + } + } + + renderList(); + + document.getElementById("btnSelectAll").addEventListener("click", () => { + for (const k of state.keys()) state.set(k, true); + renderList(); + }); + + document.getElementById("btnDeselectAll").addEventListener("click", () => { + for (const k of state.keys()) state.set(k, false); + renderList(); + }); + + document.getElementById("btnApply").addEventListener("click", () => { + const enabled = [...state.entries()] + .filter(([, v]) => v) + .map(([k]) => k); + vscode.postMessage({ command: "apply", filePath, enabledEnviros: enabled }); + }); + + document.getElementById("btnCancel").addEventListener("click", () => { + vscode.postMessage({ command: "cancel" }); + }); +}); diff --git a/src/utilities.ts b/src/utilities.ts index d060ba2e..64849f57 100644 --- a/src/utilities.ts +++ b/src/utilities.ts @@ -541,7 +541,7 @@ export function fileIsVCPAndInPlace(filePath: string) { } /** - * Returns vcp node for a given source file path + * Returns vcp node for a given source file path */ export function getVcpNodeBySourceFilePath( sourceFilePath: string diff --git a/src/vcastTestInterface.ts b/src/vcastTestInterface.ts index 2099e6f7..3dda4a45 100644 --- a/src/vcastTestInterface.ts +++ b/src/vcastTestInterface.ts @@ -71,6 +71,11 @@ import { closeConnection, globalEnviroDataServerActive, } from "../src-common/vcastServer"; +import { + getEnabledEnvirosForFile, + hideCoverageFilterStatusBar, + updateCoverageFilterStatusBar, +} from "./coverage"; const fs = require("fs"); const path = require("path"); @@ -201,7 +206,7 @@ interface fileCoverageType { } // key is filePath -let globalCoverageData = new Map(); +export let globalCoverageData = new Map(); ///////////////////////////////////////////////////////////////////// export function resetCoverageData() { @@ -245,10 +250,22 @@ export function getCoverageDataForFile(filePath: string): coverageSummaryType { // if there is coverage data, create the x/y status bar message if (dataForThisFile.hasCoverage && dataForThisFile.enviroList.size > 0) { const checksum: number = getChecksum(filePath); + // undefined = all enviros enabled (no filter entry exists for this file) + const enabledEnviros = getEnabledEnvirosForFile(filePath); + let coveredList: number[] = []; let uncoveredList: number[] = []; let partiallyCoveredList: number[] = []; - for (const enviroData of dataForThisFile.enviroList.values()) { + + for (const [ + enviroPath, + enviroData, + ] of dataForThisFile.enviroList.entries()) { + // Skip enviros the user has deselected in the coverage filter webview + if (enabledEnviros !== undefined && !enabledEnviros.has(enviroPath)) { + continue; + } + if (enviroData.crc32Checksum == checksum || enviroData.isVCP) { coveredList = coveredList.concat(enviroData.covered); uncoveredList = uncoveredList.concat(enviroData.uncovered); @@ -266,17 +283,25 @@ export function getCoverageDataForFile(filePath: string): coverageSummaryType { // This status is for files that have changed since // they were last instrumented returnData.statusString = "Coverage Out of Date"; + hideCoverageFilterStatusBar(); } else { returnData.hasCoverageData = true; // remove duplicates returnData.covered = [...new Set(coveredList)]; returnData.uncovered = [...new Set(uncoveredList)]; returnData.partiallyCovered = [...new Set(partiallyCoveredList)]; + + // update status bar filter indicator + updateCoverageFilterStatusBar( + dataForThisFile.enviroList.size, + enabledEnviros + ); } } else { // This status is for files that are part of // and environment but not instrumented returnData.statusString = "No Coverage Data"; + hideCoverageFilterStatusBar(); } }