Skip to content

Commit 53f7f6b

Browse files
golowanowkartben
authored andcommitted
twister: coverage: Merge gcovr reports
In `--coverage-per-instance` mode for 'gcovr' merge individual coverage.json reports into the aggregate code coverage report instead of processing .gcda data files again. Signed-off-by: Dmitrii Golovanov <[email protected]>
1 parent f93f82f commit 53f7f6b

File tree

1 file changed

+57
-21
lines changed

1 file changed

+57
-21
lines changed

scripts/pylib/twister/twisterlib/coverage.py

Lines changed: 57 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ def __init__(self):
3333
self.output_formats = None
3434
self.coverage_capture = True
3535
self.coverage_report = True
36+
self.coverage_per_instance = False
37+
self.instances = {}
3638

3739
@staticmethod
3840
def factory(tool, jobs=None):
@@ -156,9 +158,12 @@ def generate(self, outdir):
156158
coverage_completed = self.capture_data(outdir) if self.coverage_capture else True
157159
if not coverage_completed or not self.coverage_report:
158160
return coverage_completed, {}
161+
build_dirs = None
162+
if not self.coverage_capture and self.coverage_report and self.coverage_per_instance:
163+
build_dirs = [instance.build_dir for instance in self.instances.values()]
159164
reports = {}
160165
with open(os.path.join(outdir, "coverage.log"), "a") as coveragelog:
161-
ret, reports = self._generate(outdir, coveragelog)
166+
ret, reports = self._generate(outdir, coveragelog, build_dirs)
162167
if ret == 0:
163168
report_log = {
164169
"html": "HTML report generated: {}".format(
@@ -269,7 +274,8 @@ def run_lcov(self, args, coveragelog):
269274
] + parallel + args
270275
return self.run_command(cmd, coveragelog)
271276

272-
def _generate(self, outdir, coveragelog):
277+
278+
def _generate(self, outdir, coveragelog, build_dirs=None):
273279
coveragefile = os.path.join(outdir, "coverage.info")
274280
ztestfile = os.path.join(outdir, "ztest.info")
275281

@@ -317,6 +323,10 @@ def __init__(self):
317323
self.ignore_branch_patterns = []
318324
self.output_formats = "html"
319325
self.version = self.get_version()
326+
# Different ifdef-ed implementations of the same function should not be
327+
# in conflict treated by GCOVR as separate objects for coverage statistics.
328+
self.options = ["-v", "--merge-mode-functions=separate"]
329+
320330

321331
def get_version(self):
322332
try:
@@ -355,37 +365,29 @@ def _interleave_list(prefix, list):
355365
def _flatten_list(list):
356366
return [a for b in list for a in b]
357367

358-
def _generate(self, outdir, coveragelog):
359-
coverage_file = os.path.join(outdir, "coverage.json")
360-
coverage_summary = os.path.join(outdir, "coverage_summary.json")
361-
ztest_file = os.path.join(outdir, "ztest.json")
362-
368+
def collect_coverage(self, outdir, coverage_file, ztest_file, coveragelog):
363369
excludes = Gcovr._interleave_list("-e", self.ignores)
364370
if len(self.ignore_branch_patterns) > 0:
365371
# Last pattern overrides previous values, so merge all patterns together
366372
merged_regex = "|".join([f"({p})" for p in self.ignore_branch_patterns])
367373
excludes += ["--exclude-branches-by-pattern", merged_regex]
368374

369-
# Different ifdef-ed implementations of the same function should not be
370-
# in conflict treated by GCOVR as separate objects for coverage statistics.
371-
mode_options = ["--merge-mode-functions=separate"]
372-
373375
# We want to remove tests/* and tests/ztest/test/* but save tests/ztest
374-
cmd = ["gcovr", "-v", "-r", self.base_dir,
376+
cmd = ["gcovr", "-r", self.base_dir,
375377
"--gcov-ignore-parse-errors=negative_hits.warn_once_per_file",
376378
"--gcov-executable", self.gcov_tool,
377379
"-e", "tests/*"]
378-
cmd += excludes + mode_options + ["--json", "-o", coverage_file, outdir]
380+
cmd += excludes + self.options + ["--json", "-o", coverage_file, outdir]
379381
cmd_str = " ".join(cmd)
380382
logger.debug(f"Running: {cmd_str}")
381383
coveragelog.write(f"Running: {cmd_str}\n")
382384
coveragelog.flush()
383385
ret = subprocess.call(cmd, stdout=coveragelog, stderr=coveragelog)
384386
if ret:
385387
logger.error(f"GCOVR failed with {ret}")
386-
return ret, {}
388+
return ret, []
387389

388-
cmd = ["gcovr", "-v", "-r", self.base_dir] + mode_options
390+
cmd = ["gcovr", "-r", self.base_dir] + self.options
389391
cmd += ["--gcov-executable", self.gcov_tool,
390392
"-f", "tests/ztest", "-e", "tests/ztest/test/*",
391393
"--json", "-o", ztest_file, outdir]
@@ -396,12 +398,40 @@ def _generate(self, outdir, coveragelog):
396398
ret = subprocess.call(cmd, stdout=coveragelog, stderr=coveragelog)
397399
if ret:
398400
logger.error(f"GCOVR ztest stage failed with {ret}")
399-
return ret, {}
401+
return ret, []
402+
403+
return ret, [file_ for file_ in [coverage_file, ztest_file]
404+
if os.path.exists(file_) and os.path.getsize(file_) > 0]
405+
400406

401-
if os.path.exists(ztest_file) and os.path.getsize(ztest_file) > 0:
402-
files = [coverage_file, ztest_file]
407+
def _generate(self, outdir, coveragelog, build_dirs=None):
408+
coverage_file = os.path.join(outdir, "coverage.json")
409+
coverage_summary = os.path.join(outdir, "coverage_summary.json")
410+
ztest_file = os.path.join(outdir, "ztest.json")
411+
412+
ret = 0
413+
cmd_ = []
414+
files = []
415+
if build_dirs:
416+
for dir_ in build_dirs:
417+
files_ = [fname for fname in
418+
[os.path.join(dir_, "coverage.json"),
419+
os.path.join(dir_, "ztest.json")]
420+
if os.path.exists(fname)]
421+
if not files_:
422+
logger.debug(f"Coverage merge no files in: {dir_}")
423+
continue
424+
files += files_
425+
logger.debug(f"Coverage merge {len(files)} reports in {outdir}")
426+
ztest_file = None
427+
cmd_ = ["--json-pretty", "--json", coverage_file]
403428
else:
404-
files = [coverage_file]
429+
ret, files = self.collect_coverage(outdir, coverage_file, ztest_file, coveragelog)
430+
logger.debug(f"Coverage collected {len(files)} reports from: {outdir}")
431+
432+
if not files:
433+
logger.warning(f"No coverage files to compose report for {outdir}")
434+
return ret, {}
405435

406436
subdir = os.path.join(outdir, "coverage")
407437
os.makedirs(subdir, exist_ok=True)
@@ -422,7 +452,8 @@ def _generate(self, outdir, coveragelog):
422452
[report_options[r] for r in self.output_formats.split(',')]
423453
)
424454

425-
cmd = ["gcovr", "-v", "-r", self.base_dir] + mode_options + gcovr_options + tracefiles
455+
cmd = ["gcovr", "-r", self.base_dir] + self.options + gcovr_options + tracefiles
456+
cmd += cmd_
426457
cmd += ["--json-summary-pretty", "--json-summary", coverage_summary]
427458
cmd_str = " ".join(cmd)
428459
logger.debug(f"Running: {cmd_str}")
@@ -468,14 +499,17 @@ def choose_gcov_tool(options, is_system_gcov):
468499
return gcov_tool
469500

470501

471-
def run_coverage_tool(options, outdir, is_system_gcov, coverage_capture, coverage_report):
502+
def run_coverage_tool(options, outdir, is_system_gcov, instances,
503+
coverage_capture, coverage_report):
472504
coverage_tool = CoverageTool.factory(options.coverage_tool, jobs=options.jobs)
473505
if not coverage_tool:
474506
return False, {}
475507

476508
coverage_tool.gcov_tool = str(choose_gcov_tool(options, is_system_gcov))
477509
logger.debug(f"Using gcov tool: {coverage_tool.gcov_tool}")
478510

511+
coverage_tool.instances = instances
512+
coverage_tool.coverage_per_instance = options.coverage_per_instance
479513
coverage_tool.coverage_capture = coverage_capture
480514
coverage_tool.coverage_report = coverage_report
481515
coverage_tool.base_dir = os.path.abspath(options.coverage_basedir)
@@ -509,6 +543,7 @@ def run_coverage(options, testplan):
509543
break
510544

511545
return run_coverage_tool(options, options.outdir, is_system_gcov,
546+
instances=testplan.instances,
512547
coverage_capture=False,
513548
coverage_report=True)
514549

@@ -518,5 +553,6 @@ def run_coverage_instance(options, instance):
518553
"""
519554
is_system_gcov = has_system_gcov(instance.platform)
520555
return run_coverage_tool(options, instance.build_dir, is_system_gcov,
556+
instances={instance.name: instance},
521557
coverage_capture=True,
522558
coverage_report=options.coverage_per_instance)

0 commit comments

Comments
 (0)