NAME

Devel::XSCover::Gcov - the counters the compiler wrote, read through gcov

SYNOPSIS

use Devel::XSCover::Gcov;
my $reader  = Devel::XSCover::Gcov->new(dir => '.', gcov => ['gcov']);
my @objects = $reader->read;

DESCRIPTION

Finds every counter file the instrumented objects wrote, runs gcov on each from the distribution directory, and parses its text into one structure per object that knows nothing of Devel::Cover: the executable lines with their counts, the functions with their first line and call count, and the blocks that end in a jump with the count of each arm. Where the tool can also give the flow graph, the decisions of each source are read from it too, through Devel::XSCover::Flow.

METHODS

new

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

Options: dir, gcov (the tool as a list of words), conditions (ask gcov for condition coverage, gcc 14 and later), flow (llvm or gcc to ask the tool for the flow graph too, as Devel::XSCover::Build detects it, or empty), ignore (patterns on the relative path), generated (the generated C files to keep aside), skip (directories not searched, blib, cover_db and .git by default), save (a directory to keep each object's gcov text in), dry and silent.

find_gcda

Every .gcda under the directory, relative to it, sorted.

run_gcov

my $text = $reader->run_gcov('src/helper.gcda');

Runs the tool from the distribution directory with -abct and the counter file, and returns the text. The source paths gcov records are relative to the directory the object was compiled in, so it must run there.

run_flow

my $graph = $reader->run_flow('src/helper.gcda');

The flow graph of the same object as text: llvm-cov gcov --dump for llvm, which prints it on its error stream, and gcov --json-format for gcc. Nothing when flow is empty, and a warning and nothing when the tool gives nothing, in which case the branches of that object come from gcov's blocks alone. Saved beside the gcov text as NAME.flow.txt or NAME.flow.json.

attach_decisions

my $files_with = $reader->attach_decisions($parsed, $graph);

Reads the decisions of every source in the graph and puts them under decisions in the parsed structure of that file, a list of { line, arms }. A file with that key gets its branches from it, with or without entries, and one without gets them from its blocks.

parse

my $files = $reader->parse($text);

The structure, keyed by source file as gcov names it:

{ 'Spike.xs' => {
    lines     => { 27 => 353062, 37 => 1 },
    functions => [ { name => 'only_from_one', line => 8, count => 2 } ],
    blocks    => [ { line => 36, arms => [2, 1] }, { line => 47, arms => [1, 1, 1, 0, 1] } ],
    conditions => [ { line => 36, covered => 3, total => 4, terms => [ { true => 1, false => 1 }, { true => 1, false => 0 } ] } ],
} }

A count of - is not an executable line. ##### and ===== are zero, a trailing * is dropped. A function's line is the first source line after its summary. A block's arms are the branch lines after it, a never executed arm counting zero, and blocks without arms are dropped. Branch lines without a block of their own make one block for the line. A condition outcomes covered N/M line, which gcc 14's gcov --conditions writes under a line, is a decision of M/2 terms, and the condition K not covered (true) lines after it say which outcome of which term never decided the result. Each term ends up with a true and a false flag.

filter

my ($kept, $aside) = $reader->filter($files);

Drops every file outside the distribution directory, ppport.h, and files matching an ignore pattern. Files in generated are returned apart, as the second value, for the XS map. Absolute paths under the directory become relative.

read

Runs the steps for every counter file, gcov, the flow graph when there is one, the parse and the filter, and returns a list of hashes with object (the counter file), files and generated.

SEE ALSO

Devel::XSCover