11import contextlib
22import copy
3+ import itertools
34import json
45import os
56import re
1415from .util import get_commands , get_config
1516from .util import run as _run
1617
18+ LCOV_OUTPUT_FILE = Path ("build" , "meson-logs" , "coveragereport" )
19+
1720
1821class GcovReportFormat (str , Enum ):
1922 html = "html"
@@ -231,6 +234,48 @@ def _check_coverage_tool_installation(coverage_type: GcovReportFormat, build_dir
231234 )
232235
233236
237+ def _get_coverage_files ():
238+ cwd = Path .cwd ()
239+ build_dir = Path (cwd , "build" )
240+ return itertools .chain (build_dir .rglob ("*.gcda" ), build_dir .rglob ("*.da" ))
241+
242+
243+ def _gcov_reset_counters ():
244+ click .secho ("Removing previous GCOV .gcda files..." , fg = "yellow" )
245+ for filename in _get_coverage_files ():
246+ Path .unlink (filename )
247+
248+
249+ def _generate_lcov_coverage_html ():
250+ if next (_get_coverage_files (), None ) is None :
251+ raise click .ClickException (
252+ "GCOV files missing... Cannot generate coverage reports. "
253+ "Coverage reports can be generated by `spin test --coverage --gcov`"
254+ )
255+
256+ click .secho (
257+ "Deleting old HTML coverage report..." ,
258+ fg = "yellow" ,
259+ )
260+ shutil .rmtree (LCOV_OUTPUT_FILE , ignore_errors = True )
261+
262+ click .secho (
263+ "Generating HTML coverage report..." ,
264+ fg = "blue" ,
265+ )
266+ cmd = ["ninja" , "coverage-html" , "-C" , "build" ]
267+ p = _run (
268+ cmd ,
269+ echo = True ,
270+ output = False ,
271+ )
272+ coverage_folder = re .search (r"file://(.*)" , p .stdout .decode ("utf-8" )).group (1 )
273+ click .secho (
274+ f"Coverage report generated successfully and written to { coverage_folder } " ,
275+ fg = "green" ,
276+ )
277+
278+
234279if sys .platform .startswith ("win" ):
235280 DEFAULT_PREFIX = "C:/"
236281else :
@@ -447,6 +492,13 @@ def _get_configured_command(command_name):
447492 default = "html" ,
448493 help = f"Format of the gcov report. Can be one of { ', ' .join (e .value for e in GcovReportFormat )} ." ,
449494)
495+ @click .option (
496+ "--generate-lcov-html" ,
497+ is_flag = True ,
498+ help = "produce HTML for C code coverage information "
499+ "from a previous run with --gcov. "
500+ "HTML output goes to `build/meson-logs/coveragereport/`" ,
501+ )
450502@build_dir_option
451503@click .pass_context
452504def test (
@@ -459,6 +511,7 @@ def test(
459511 coverage = False ,
460512 gcov = None ,
461513 gcov_format = None ,
514+ generate_lcov_html = None ,
462515 build_dir = None ,
463516):
464517 """🔧 Run tests
@@ -503,6 +556,10 @@ def test(
503556 distname = cfg .get ("project.name" , None )
504557 pytest_args = pytest_args or ()
505558
559+ if generate_lcov_html :
560+ _generate_lcov_coverage_html ()
561+ sys .exit (0 )
562+
506563 # User specified tests without -t flag
507564 # Rewrite arguments as though they specified using -t and proceed
508565 if (len (pytest_args ) == 1 ) and (not tests ):
@@ -547,6 +604,7 @@ def test(
547604 "Invoking `build` prior to running tests:" , bold = True , fg = "bright_green"
548605 )
549606 if gcov is not None :
607+ _gcov_reset_counters ()
550608 ctx .invoke (build_cmd , build_dir = build_dir , gcov = bool (gcov ))
551609 else :
552610 ctx .invoke (build_cmd , build_dir = build_dir )
0 commit comments