11import contextlib
22import copy
3+ import itertools
34import json
45import os
56import re
1516from .util import run as _run
1617
1718
19+ LCOV_OUTPUT_FILE = Path ("build" , "meson-logs" , "coveragereport" )
20+
21+
1822class GcovReportFormat (str , Enum ):
1923 html = "html"
2024 xml = "xml"
@@ -231,6 +235,49 @@ def _check_coverage_tool_installation(coverage_type: GcovReportFormat, build_dir
231235 )
232236
233237
238+ def _get_coverage_files ():
239+ cwd = Path .cwd ()
240+ build_dir = Path (cwd , "build" )
241+ return itertools .chain (build_dir .rglob ("*.gcda" ), build_dir .rglob ("*.da" ))
242+
243+
244+ def _gcov_reset_counters ():
245+ click .secho ("Removing previous GCOV .gcda files..." , fg = "yellow" )
246+ build_dir = Path (Path .cwd (), "build" )
247+ for filename in _get_coverage_files ():
248+ Path .unlink (filename )
249+
250+
251+ def _generate_lcov_coverage_html ():
252+ if next (_get_coverage_files (), None ) is None :
253+ raise click .ClickException (
254+ "GCOV files missing... Cannot generate coverage reports. "
255+ "Coverage reports can be generated by `spin test --coverage --gcov`"
256+ )
257+
258+ click .secho (
259+ "Deleting old HTML coverage report..." ,
260+ fg = "yellow" ,
261+ )
262+ shutil .rmtree (LCOV_OUTPUT_FILE , ignore_errors = True )
263+
264+ click .secho (
265+ "Generating HTML coverage report..." ,
266+ fg = "blue" ,
267+ )
268+ cmd = ["ninja" , "coverage-html" , "-C" , "build" ]
269+ p = _run (
270+ cmd ,
271+ echo = True ,
272+ output = False ,
273+ )
274+ coverage_folder = re .search (r"file://(.*)" , p .stdout .decode ("utf-8" )).group (1 )
275+ click .secho (
276+ f"Coverage report generated successfully and written to { coverage_folder } " ,
277+ fg = "green" ,
278+ )
279+
280+
234281if sys .platform .startswith ("win" ):
235282 DEFAULT_PREFIX = "C:/"
236283else :
@@ -447,6 +494,13 @@ def _get_configured_command(command_name):
447494 default = "html" ,
448495 help = f"Format of the gcov report. Can be one of { ', ' .join (e .value for e in GcovReportFormat )} ." ,
449496)
497+ @click .option (
498+ "--generate-lcov-html" ,
499+ is_flag = True ,
500+ help = "produce HTML for C code coverage information "
501+ "from a previous run with --gcov. "
502+ "HTML output goes to `build/meson-logs/coveragereport/`" ,
503+ )
450504@build_dir_option
451505@click .pass_context
452506def test (
@@ -459,6 +513,7 @@ def test(
459513 coverage = False ,
460514 gcov = None ,
461515 gcov_format = None ,
516+ generate_lcov_html = None ,
462517 build_dir = None ,
463518):
464519 """🔧 Run tests
@@ -503,6 +558,10 @@ def test(
503558 distname = cfg .get ("project.name" , None )
504559 pytest_args = pytest_args or ()
505560
561+ if generate_lcov_html :
562+ _generate_lcov_coverage_html ()
563+ sys .exit (0 )
564+
506565 # User specified tests without -t flag
507566 # Rewrite arguments as though they specified using -t and proceed
508567 if (len (pytest_args ) == 1 ) and (not tests ):
@@ -547,6 +606,7 @@ def test(
547606 "Invoking `build` prior to running tests:" , bold = True , fg = "bright_green"
548607 )
549608 if gcov is not None :
609+ _gcov_reset_counters ()
550610 ctx .invoke (build_cmd , build_dir = build_dir , gcov = bool (gcov ))
551611 else :
552612 ctx .invoke (build_cmd , build_dir = build_dir )
0 commit comments