Skip to content

Commit 22ec721

Browse files
committed
* Updated to keep in syhc with Jenkins integration
* Encoding issues * Using @contextlib for file open * Adding support to work with BitBucket Pipelines
1 parent ace4eea commit 22ec721

22 files changed

+1469
-665
lines changed

README.md

Lines changed: 49 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,13 @@ This integration allows the user to build, execute, generate reports, and genera
1212
[VectorCAST](http://vector.com/vectorcast) Projects in various CI/CD tools
1313

1414
Results can be published in the following formats
15-
* Coverage results to **_GitLab_**, **_SonarQube_**: **_Cobertura_** (xml_data/cobertura) and **_Extended Cobertura_**
15+
* Coverage results
16+
* To **_GitLab Pipeline_ **, **_Microsoft Azure_**, **_GitHub Actions_**, **_SonarQube_**: **_Cobertura_** (xml_data/cobertura)
17+
* To **_BitBucket Pipelines_**: **_Cobertura_** sent via REST API
18+
* To **_Jenkins_**: **_Extended Cobertura_** for Jenkins Coverage Plugin support
19+
1620
* Test Results
17-
* To **_GitLab_**: **_JUnit_** (xml_data/junit)
21+
* To _GitLab Pipeline_ **, **_Microsoft Azure_**, **_GitHub Actions_**, **_BitBucket Pipelines_**, and **_Jenkins_** : **_JUnit_** (xml_data/junit)
1822
* To **_SonarQube_**: **_CppUnit_** (xml_data/sonarqube)
1923
* Static Analysis to **_GitLab_**: **_CodeClimate_** (pclp/gl-code-quality-report.json)
2024

@@ -27,16 +31,17 @@ The python scrip `vcast_exec.py` is the main driver for build/execute VectorCAST
2731
The api for vcast_exec.py follows:
2832

2933
```
30-
vpython vcast_exec.py --help
31-
usage: vcast_exec.py [-h] [--build-execute] [--build | --incremental] [--output_dir OUTPUT_DIR]
32-
[--source_root SOURCE_ROOT] [--html_base_dir HTML_BASE_DIR]
33-
[--cobertura] [--cobertura_extended] [--lcov] [--junit]
34-
[--export_rgw] [--sonarqube] [--pclp_input PCLP_INPUT]
35-
[--pclp_output_html PCLP_OUTPUT_HTML]
36-
[--exit_with_failed_count [EXIT_WITH_FAILED_COUNT]]
37-
[--aggregate] [--metrics] [--fullstatus] [--utfull] [--tcmr] [--index]
38-
[--jobs JOBS] [--ci] [--level LEVEL] [--environment ENVIRONMENT]
39-
[--gitlab | --azure] [--print_exc] [--timing] [--version]
34+
usage: vcast_exec.py [-h] [--build-execute] [--build | --incremental]
35+
[--output_dir OUTPUT_DIR] [--source_root SOURCE_ROOT]
36+
[--html_base_dir HTML_BASE_DIR] [--cobertura]
37+
[--cobertura_extended] [--lcov] [--junit] [--export_rgw]
38+
[--sonarqube] [--pclp_input PCLP_INPUT]
39+
[--pclp_output_html PCLP_OUTPUT_HTML]
40+
[--exit_with_failed_count [EXIT_WITH_FAILED_COUNT]]
41+
[--aggregate] [--metrics] [--fullstatus] [--utfull]
42+
[--tcmr] [--index] [--jobs JOBS] [--ci] [-l LEVEL]
43+
[-e ENVIRONMENT] [--gitlab | --azure] [--print_exc]
44+
[--timing] [-v] [--version]
4045
[ManageProject]
4146
4247
positional arguments:
@@ -56,41 +61,54 @@ The api for vcast_exec.py follows:
5661
Options generating metrics
5762
5863
--output_dir OUTPUT_DIR
59-
Set the base directory of the xml_data directory. Default is the workspace directory
64+
Set the base directory of the xml_data directory.
65+
Default is the workspace directory
6066
--source_root SOURCE_ROOT
61-
Set the absolute path for the source file in coverage reporting
67+
Set the absolute path for the source file in coverage
68+
reporting
6269
--html_base_dir HTML_BASE_DIR
63-
Set the base directory of the html_reports directory. The default is the workspace directory
70+
Set the base directory of the html_reports directory.
71+
The default is the workspace directory
6472
--cobertura Generate coverage results in Cobertura xml format
65-
--cobertura_extended Generate coverage results in extended Cobertura xml format
73+
--cobertura_extended Generate coverage results in extended Cobertura xml
74+
format
6675
--lcov Generate coverage results in an LCOV format
6776
--junit Generate test results in Junit xml format
6877
--export_rgw Export RGW data
69-
--sonarqube Generate test results in SonarQube Generic test execution report format (CppUnit)
78+
--sonarqube Generate test results in SonarQube Generic test
79+
execution report format (CppUnit)
7080
--pclp_input PCLP_INPUT
71-
Generate static analysis results from PC-lint Plus XML file to generic static analysis format (codequality)
81+
Generate static analysis results from PC-lint Plus XML
82+
file to generic static analysis format (codequality)
7283
--pclp_output_html PCLP_OUTPUT_HTML
73-
Generate static analysis results from PC-lint Plus XML file to an HTML output
84+
Generate static analysis results from PC-lint Plus XML
85+
file to an HTML output
7486
--exit_with_failed_count [EXIT_WITH_FAILED_COUNT]
75-
Returns failed test case count as script exit. Set a value to indicate a percentage above which the job will be marked as failed
87+
Returns failed test case count as script exit. Set a
88+
value to indicate a percentage above which the job
89+
will be marked as failed
7690
7791
Report Selection:
7892
VectorCAST Manage reports that can be generated
7993
8094
--aggregate Generate aggregate coverage report VectorCAST Project
8195
--metrics Generate metrics reports for VectorCAST Project
8296
--fullstatus Generate full status reports for VectorCAST Project
83-
--utfull Generate Full Reports for each VectorCAST environment in project
84-
--tcmr Generate Test Cases Management Reports for each VectorCAST environment in project
85-
--index Generate an index.html report that ties all the other HTML reports together
97+
--utfull Generate Full Reports for each VectorCAST environment
98+
in project
99+
--tcmr Generate Test Cases Management Reports for each
100+
VectorCAST environment in project
101+
--index Generate an index.html report that ties all the other
102+
HTML reports together
86103
87104
Build/Execution Options:
88105
Options that effect build/execute operation
89106
90107
--jobs JOBS Number of concurrent jobs (default = 1)
91108
--ci Use Continuous Integration Licenses
92109
-l LEVEL, --level LEVEL
93-
Environment Name if only doing single environment. Should be in the form of compiler/testsuite
110+
Environment Name if only doing single environment.
111+
Should be in the form of compiler/testsuite
94112
-e ENVIRONMENT, --environment ENVIRONMENT
95113
Environment Name if only doing single environment.
96114
--gitlab Build using GitLab CI (default)
@@ -103,9 +121,16 @@ The api for vcast_exec.py follows:
103121
--timing Prints timing information for metrics generation
104122
-v, --verbose Enable verbose output
105123
--version Displays the version information
124+
106125
```
107126

108127
# Change log
128+
10/2025
129+
* Updated to keep in syhc with Jenkins integration
130+
* Encoding issues
131+
* Using @contextlib for file open
132+
* Adding support to work with BitBucket Pipelines
133+
109134
8/2025
110135
* Add unit test Full Report creation
111136
* Updated index.html to be more readable

__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,4 @@
2121
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
2222
# THE SOFTWARE.
2323
#
24+

cobertura.py

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,16 @@
3737
from collections import defaultdict
3838
from pprint import pprint
3939
import argparse
40+
try:
41+
from safe_open import open
42+
except:
43+
pass
4044

4145
fileList = []
4246

43-
from vcast_utils import dump, checkVectorCASTVersion
47+
from vcast_utils import dump, checkVectorCASTVersion, getVectorCASTEncoding
48+
49+
encFmt = getVectorCASTEncoding()
4450

4551
def write_xml(x, name, verbose = False):
4652

@@ -52,7 +58,8 @@ def write_xml(x, name, verbose = False):
5258

5359
xml_str += etree.tostring(x,pretty_print=True).decode()
5460

55-
open(name + ".xml", "w").write(xml_str)
61+
with open(name + ".xml", "wb") as fd:
62+
fd.write(xml_str.encode(encFmt,"replace"))
5663

5764
def getCoveredFunctionCount(source):
5865
if len(source.functions) == 0:
@@ -86,7 +93,12 @@ def getFileXML(testXml, coverAPI, verbose = False, extended = False, source_root
8693

8794

8895
fname = coverAPI.display_name
89-
fpath = os.path.relpath(coverAPI.display_path,prj_dir).replace("\\","/")
96+
fpath = coverAPI.display_path
97+
try:
98+
fpath = os.path.relpath(fpath,prj_dir).replace("\\","/")
99+
except:
100+
fpath = fpath.replace("\\","/")
101+
pass
90102

91103
branch_totals = float(coverAPI.metrics.branches + coverAPI.metrics.mcdc_branches)
92104
branch_covered = float(coverAPI.metrics.max_covered_branches + coverAPI.metrics.max_covered_mcdc_branches)
@@ -335,11 +347,13 @@ def procesCoverage(coverXML, coverApi, extended = False, source_root = ""):
335347

336348
if extended:
337349
for func in coverApi.functions:
338-
350+
351+
if isinstance(func.instrumented_functions[0].parameterized_name, bool):
352+
continue
353+
339354
method = etree.SubElement(methods, "method")
340-
341355
method.attrib['name'] = func.name
342-
method.attrib['signature'] = func.instrumented_functions[0].parameterized_name.replace(func.name,"",1)
356+
method.attrib['signature'] = func.instrumented_functions[0].parameterized_name.replace(func.name,"",1)
343357
method.attrib['line-rate'] = str(func.metrics.max_covered_statements_pct/100.0)
344358

345359
statementPercentStr = "{:.2f}".format(func.metrics.max_covered_statements_pct) + "% (" + str(func.metrics.max_covered_statements) + "/" + str(func.metrics.statements) + ")"
@@ -375,11 +389,13 @@ def procesCoverage(coverXML, coverApi, extended = False, source_root = ""):
375389
return processStatementBranchMCDC(coverApi, lines, extended)
376390

377391
def runCoverageResultsMP(packages, mpFile, verbose = False, extended=False, source_root = ""):
378-
379-
vcproj = VCProjectApi(mpFile)
380-
api = vcproj.project.cover_api
381392

382-
return runCoberturaResults(packages, api, verbose = False, extended = extended, source_root = source_root)
393+
with VCProjectApi(mpFile) as vcproj:
394+
api = vcproj.project.cover_api
395+
396+
results = runCoberturaResults(packages, api, verbose = False, extended = extended, source_root = source_root)
397+
398+
return results
383399

384400
def runCoberturaResults(packages, api, verbose = False, extended = False, source_root = ""):
385401

@@ -437,8 +453,7 @@ def runCoberturaResults(packages, api, verbose = False, extended = False, source
437453
except:
438454
prj_dir = os.getcwd().replace("\\","/") + "/"
439455

440-
# get a sorted listed of all the files with the proj directory stripped off
441-
456+
# get a sorted listed of all the files with the proj directory stripped off
442457
for file in api.SourceFile.all():
443458
if file.display_name == "":
444459
continue
@@ -447,7 +462,11 @@ def runCoberturaResults(packages, api, verbose = False, extended = False, source
447462

448463
fname = file.display_name
449464
fpath = file.display_path.rsplit('.',1)[0]
450-
fpath = os.path.relpath(fpath,prj_dir).replace("\\","/")
465+
try:
466+
fpath = os.path.relpath(fpath,prj_dir).replace("\\","/")
467+
except:
468+
fpath = fpath.replace("\\","/")
469+
pass
451470

452471
# print("*", file.name, file.display_name, fpath)
453472

@@ -717,8 +736,8 @@ def generateCoverageResults(inFile, azure = False, xml_data_dir = "xml_data", ve
717736
coverages.attrib['timestamp'] = str(datetime.now())
718737

719738
tool_version = os.path.join(os.environ['VECTORCAST_DIR'], "DATA", "tool_version.txt")
720-
with open(tool_version,"r") as fd:
721-
ver = fd.read()
739+
with open(tool_version,"rb") as fd:
740+
ver = fd.read().decode(encFmt,"replace")
722741

723742
coverages.attrib['version'] = "VectorCAST " + ver.rstrip()
724743

@@ -736,7 +755,7 @@ def generateCoverageResults(inFile, azure = False, xml_data_dir = "xml_data", ve
736755
if MCDC_rate != -1.0: print ("mcdc pairs: {:.2f}% ({:d} out of {:d})".format(MCDC_rate*100.0, cov_mcdc, total_mcdc))
737756

738757
if statement_rate != -1.0: print ("coverage: {:.2f}% of statements".format(statement_rate*100.0))
739-
print ("complexity: {:d}".format(complexity))
758+
if complexity != -1.0: print ("complexity: {:d}".format(complexity))
740759
source = etree.SubElement(sources, "source")
741760
source.text = "./"
742761

copy_build_dir.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -237,9 +237,8 @@ def run(ManageProjectName, Level, BaseName, Env, workspace, vCastProjectWorkspac
237237

238238
def getVcastProjectWorkspace(args):
239239

240-
vc_api = VCProjectApi(args.ManageProject)
241-
vCastProjectWorkspace = vc_api.project.workspace
242-
vc_api.close()
240+
with VCProjectApi(args.ManageProject) as vcproj:
241+
vCastProjectWorkspace = vcproj.project.workspace
243242

244243
return vCastProjectWorkspace
245244

create_index_html.py

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
import argparse
44
import glob
55

6+
from vcast_utils import dump, checkVectorCASTVersion, getVectorCASTEncoding
7+
8+
encFmt = getVectorCASTEncoding()
69

710
class cd:
811
"""Context manager for changing the current working directory"""
@@ -17,8 +20,9 @@ def __exit__(self, etype, value, traceback):
1720
os.chdir(self.savedPath)
1821

1922
def searchKeyword(search_string, filename):
20-
with open(filename, "r") as f:
21-
for line_number, line in enumerate(f, start=1):
23+
with open(filename, "rb") as fd:
24+
for line_number, line in enumerate(fd, start=1):
25+
line = line.decode(encFmt, "replace")
2226
if search_string in line:
2327
start_idx = line.find(search_string)
2428
if start_idx != -1:
@@ -100,26 +104,24 @@ def create_index_html(mpName, isGitLab = False, output_dir = ""):
100104
global baseOutputDir
101105
baseOutputDir = output_dir
102106

103-
api = VCProjectApi(mpName)
104-
# Set custom report directory to the where this script was
105-
# found. Must contain sections/index_section.py
106-
rep_path = pathlib.Path(__file__).parent.resolve()
107+
with VCProjectApi(mpName) as vcproj:
108+
# Set custom report directory to the where this script was
109+
# found. Must contain sections/index_section.py
110+
rep_path = pathlib.Path(__file__).parent.resolve()
107111

108-
if usingGitLabCI:
109-
output_file=os.path.join(baseOutputDir,"index.html")
110-
else:
111-
output_file=os.path.join(baseOutputDir,"index.html")
112-
113-
CustomReport.report_from_api(
114-
api=api,
115-
title="HTML Reports",
116-
report_type="INDEX_FILE",
117-
formats=["HTML"],
118-
output_file=output_file,
119-
sections=['CUSTOM_HEADER', 'REPORT_TITLE', 'TABLE_OF_CONTENTS','INDEX_SECTION', 'CUSTOM_FOOTER'],
120-
customization_dir=rep_path)
121-
122-
api.close()
112+
if usingGitLabCI:
113+
output_file=os.path.join(baseOutputDir,"index.html")
114+
else:
115+
output_file=os.path.join(baseOutputDir,"index.html")
116+
117+
CustomReport.report_from_api(
118+
api=vcproj,
119+
title="HTML Reports",
120+
report_type="INDEX_FILE",
121+
formats=["HTML"],
122+
output_file=output_file,
123+
sections=['CUSTOM_HEADER', 'REPORT_TITLE', 'TABLE_OF_CONTENTS','INDEX_SECTION', 'CUSTOM_FOOTER'],
124+
customization_dir=rep_path)
123125

124126
def create_index_html_body ():
125127

0 commit comments

Comments
 (0)