NAME

Devel::XSCover - XS and C coverage for Devel::Cover, with one command

SYNOPSIS

xs_cover

use Devel::XSCover;
my $cover  = Devel::XSCover->new(dir => '.', report => 'html');
my $status = $cover->run;

DESCRIPTION

Devel::XSCover extends Devel::Cover with coverage of the XS and C in a distribution. The xs_cover command, run in a distribution directory, rebuilds the XS with the compiler's coverage instrumentation, runs the tests under Devel::Cover, reads the counters the compiler wrote, merges them into the same coverage database as the Perl side, and runs cover for the report. One run, one database, one report.

What Devel::Cover's own gcov support gives, and what this adds

cover -test rebuilds the XS with gcov's flags and runs gcov2perl on what gcov writes. That reads statement counts and two-arm branches and nothing more: every switch and every line with two conditions is dropped with a warning, no C function or XSUB has subroutine coverage, a header's inline functions seen from several objects write conflicting data, perl's own headers appear in the report, and the generated C file, the only record that an XSUB was entered, is thrown away.

On a small XS distribution with a switch, an inline function in a header and an XSUB with an alias, cover -test reports

File                           stmt   bran   cond    sub    pod   time  total
...ulti-2level/CORE/inline.h  100.0    n/a    n/a    n/a    n/a    n/a  100.0
...i-2level/CORE/sv_inline.h   80.0   50.0    n/a    n/a    n/a    n/a   71.4
Spike.xs                       88.4   75.0    n/a    n/a    n/a    n/a   85.2
blib/lib/Spike.pm             100.0    n/a    n/a  100.0    n/a  100.0  100.0
src/helper.c                   85.7    n/a    n/a    n/a    n/a    n/a   85.7
xs/more.xs                     50.0    n/a    n/a    n/a    n/a    n/a   50.0

and xs_cover reports

File              stmt   bran   cond    sub    pod   time  total
Spike.xs          88.4   83.3    n/a  100.0    n/a    n/a   88.6
blib/lib/Spike.pm  100.0    n/a    n/a  100.0    n/a  100.0  100.0
include/spike.h  100.0  100.0    n/a  100.0    n/a    n/a  100.0
src/helper.c      85.7   87.5    n/a  100.0    n/a    n/a   87.5
xs/more.xs        50.0    n/a    n/a   50.0    n/a    n/a   50.0

The header is in, perl's are out, every function and XSUB has a subroutine entry under its Perl name (an alias gives it two), and every arm of the switch is a branch. With gcc 14 or later the C files have a condition column too, from the compiler's own condition coverage: for every term of a decision, whether its true and its false outcome each decided the result. Other compilers give gcov nothing of the kind, and the column stays empty for C. A branch inside a macro, as GvSVn or SvTRUE expand to on a line with no decision of its own, is left out, since nothing in the source can take its other arm.

The report

The default report is Devel::Cover::Report::XSCover: a summary page with tiles, a sortable table and a filter, and a page per file with the coverage drawn beside each line, in words where something is missing. Any other report of Devel::Cover can be asked for with -report.

Marking code uncoverable

Devel::Cover's own forms work on C lines: a comment such as /* # uncoverable branch true */ at the end of the line, or the .uncoverable file. Which of a C branch's two arms is "true" is the compiler's choice, so a mark on a C branch may land on the other arm. The report shows the marked arm in its own colour.

Limitations

A process that ends by POSIX::_exit or by a signal writes no C counters, since the compiler's runtime writes them at a normal exit. A test that uses threads dies under Devel::Cover, so every test file that uses threads is found and run without it by itself, keeping its C side, and -plain does the same for any other file. A test that compares a die message against at FILE line N sees a different message under Devel::Cover, where a string eval's location gains its file and line. A module that reads or rewrites the op tree sees Devel::Cover's instrumentation in it and may behave differently under it, and -no_perl runs its whole suite without, for the C side alone. Windows is not supported in this release.

METHODS

new

my $cover = Devel::XSCover->new(%options);

Takes the options of xs_cover as a hash: dir, db, report, gcov, make, test (an array reference of test files), plain (an array reference of test files run without Devel::Cover), ignore (an array reference of patterns), timeout, cover (the cover command), keep, no_delete, no_perl (run the tests without Devel::Cover, for the C side alone), threads_covered, dry and silent.

new_from_argv

my $cover = Devel::XSCover->new_from_argv(@ARGV);

Parses a command line in the form xs_cover takes and returns the object. Prints the usage and exits for -h, prints the version and exits for -v.

run

my $status = $cover->run;

Runs the five steps in order: the instrumented build, the tests under Devel::Cover, the XS map and the gcov reader, the write into the database, and cover for the report. Returns the exit status of the test run, or of the build when that fails. Any counter file left by an earlier run is removed before the tests. Unless keep is set, the counter files are removed and the sources touched again afterwards, so the next plain make rebuilds optimised.

dir

The distribution directory.

db

The coverage database directory, cover_db by default.

report_name

The report passed to cover, XSCover by default.

report

$cover->report($db);

Runs cover on the database with that report, with -nogcov: left to itself cover runs its own gcov pass on any counter file in the tree and writes a second, thinner set of runs over ours. The cover beside the running perl is used, so the report is made by the same Devel::Cover that wrote the runs.

cover_command

That cover as a list of words, or the cover option split on spaces. When cover runs under the same perl it also gets -I for the directory this module was loaded from, so a report module of this distribution is found from a source tree as well as from an installation.

report_inc

That -I, or nothing when the report module is not beside this one.

cover

The cover option.

cleanup

$cover->cleanup($build, $reader, $db);

Removes the counter files and the saved gcov text and touches the sources, and says how many of each.

remove_counters

my $n = $cover->remove_counters($reader, $graphs);

Removes every counter file under the directory, and the graph files too when $graphs is set, and returns how many went. Run after the build and before the tests as well, since a counter file left by a process of an earlier run that ended late belongs to another build, and the compiler's runtime refuses to merge into it and leaves it corrupt.

gcov_dir

my $dir = $cover->gcov_dir($db);

Where the gcov text of each object is kept during a run, and after it with keep: the database's name with .gcov appended, beside it rather than inside, since cover complains of anything it does not know inside the database.

skip_dirs

The directories the steps leave alone: blib, .git and the database.

gcov

The gcov tool, when the detection is to be overridden.

make

The make command.

tests

The test files to run instead of make test, as a list.

plains

The test files to run without Devel::Cover, as a list.

timeout

The seconds a test run may take, zero for no limit.

ignores

The patterns added to the default ignore list, as a list.

keep

True when the counters and instrumented objects are kept after the run.

delete_db

True when the coverage database is removed before the run, which is the default.

no_perl

True when the tests run without Devel::Cover, so the report has the C side alone.

threads_covered

True when test files that use threads are left in the covered run rather than run without Devel::Cover by themselves.

dry

True when every command is printed and nothing is run.

silent

True when only the summary is printed.

SEE ALSO

Devel::Cover, xs_cover, gcov2perl

AUTHOR

LNATION, <email at lnation.org>

LICENSE AND COPYRIGHT

This software is Copyright (c) 2026 by LNATION.

This is free software, licensed under:

The Artistic License 2.0 (GPL Compatible)