<commit_before>####################################################################################################################################
# Generate C Coverage Report
####################################################################################################################################
package pgBackRestTest::Common::CoverageTest;

####################################################################################################################################
# Perl includes
####################################################################################################################################
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use English '-no_match_vars';

use Digest::SHA qw(sha1_hex);
use Exporter qw(import);
    our @EXPORT = qw();
use File::Basename qw(dirname);

use pgBackRestDoc::Common::Log;
use pgBackRestDoc::Common::String;
use pgBackRestDoc::Html::DocHtmlBuilder;
use pgBackRestDoc::Html::DocHtmlElement;
use pgBackRestDoc::ProjectInfo;

use pgBackRestTest::Common::ContainerTest;
use pgBackRestTest::Common::DefineTest;
use pgBackRestTest::Common::ExecuteTest;
use pgBackRestTest::Common::ListTest;
use pgBackRestTest::Common::RunTest;

####################################################################################################################################
# Generate an lcov configuration file
####################################################################################################################################
sub coverageLCovConfigGenerate
{
    my $oStorage = shift;
    my $strOutFile = shift;
    my $bCoverageSummary = shift;

    my $strBranchFilter =
        'OBJECT_DEFINE_[A-Z0-9_]+\(|\s{4}[A-Z][A-Z0-9_]+\([^\?]*\)|\s{4}(ASSERT|assert|switch\s)\(|\{\+{0,1}' .
        ($bCoverageSummary ? 'uncoverable_branch' : 'uncover(ed|able)_branch');
    my $strLineFilter = '\{\+{0,1}uncover' . ($bCoverageSummary ? 'able' : '(ed|able)') . '[^_]';

    my $strConfig =
        "# LCOV Settings\n" .
        "\n" .
        "# Specify if branch coverage data should be collected and processed\n" .
        "lcov_branch_coverage=1\n" .
        "\n" .
        "# Specify the regular expression of lines to exclude from branch coverage\n" .
        "#\n" .
        '# OBJECT_DEFINE_[A-Z0-9_]+\( - exclude object definitions' . "\n" .
        '# \s{4}[A-Z][A-Z0-9_]+\([^\?]*\) - exclude macros that do not take a conditional parameter and are not themselves a parameter' . "\n" .
        '# ASSERT/(|assert\( - exclude asserts since it usually not possible to trigger both branches' . "\n" .
        '# switch \( - lcov requires default: to show complete coverage but --Wswitch-enum enforces all enum values be present' . "\n" .
        "lcov_excl_br_line=${strBranchFilter}\n" .
        "\n" .
        "# Specify the regular expression of lines to exclude\n" .
        "lcov_excl_line=${strLineFilter}\n" .
        "\n" .
        "# Coverage rate limits\n" .
        "genhtml_hi_limit = 100\n" .
        "genhtml_med_limit = 90\n" .
        "\n" .
        "# Width of line coverage field in source code view\n" .
        "genhtml_line_field_width = 9\n";

    # Write configuration file
    $oStorage->put($strOutFile, $strConfig);
}

push @EXPORT, qw(coverageLCovConfigGenerate);

####################################################################################################################################
# Extract coverage using gcov
####################################################################################################################################
sub coverageExtract
{
    my $oStorage = shift;
    my $strModule = shift;
    my $strTest = shift;
    my $bSummary = shift;
    my $strContainerImage = shift;
    my $strWorkPath = shift;
    my $strWorkTmpPath = shift;
    my $strWorkUnitPath = shift;
    my $strTestResultCoveragePath = shift . '/coverage';

    # Generate a list of files to cover
    my $hTestCoverage = (testDefModuleTest($strModule, $strTest))->{&TESTDEF_COVERAGE};

    my @stryCoveredModule;

    foreach my $strModule (sort(keys(%{$hTestCoverage})))
    {
        push (@stryCoveredModule, $strModule);
    }

    push(@stryCoveredModule, "module/${strModule}/" . testRunName($strTest, false) . 'Test');

    # Generate coverage reports for the modules
    my $strLCovConf = "${strTestResultCoveragePath}/raw/lcov.conf";
    coverageLCovConfigGenerate($oStorage, $strLCovConf, $bSummary);

    my $strLCovExeBase = "lcov --config-file=${strLCovConf}";
    my $strLCovOut = "${strWorkUnitPath}/test.lcov";
    my $strLCovOutTmp = "${strWorkUnitPath}/test.tmp.lcov";

    executeTest(
        (defined($strContainerImage) ? 'docker exec -i -u ' . TEST_USER . " ${strContainerImage} " : '') .
        "${strLCovExeBase} --capture --directory=${strWorkUnitPath} --o=${strLCovOut}");

    # Generate coverage report for each module
    foreach my $strCoveredModule (@stryCoveredModule)
    {
        my $strModuleName = testRunName($strCoveredModule, false);
        my $strModuleOutName = $strModuleName;
        my $bTest = false;

        if ($strModuleOutName =~ /^module/mg)
        {
            $strModuleOutName =~ s/^module/test/mg;
            $bTest = true;
        }

        # Disable branch coverage for test files
        my $strLCovExe = $strLCovExeBase;

        if ($bTest)
        {
            $strLCovExe .= ' --rc lcov_branch_coverage=0';
        }

        # Generate lcov reports
        my $strModulePath =
            "${strWorkPath}/repo/" .
            (${strModuleOutName} =~ /^test\// ?
                'test/src/module/' . substr(${strModuleOutName}, 5) : "src/${strModuleOutName}");
        my $strLCovFile = "${strTestResultCoveragePath}/raw/${strModuleOutName}.lcov";
        my $strLCovTotal = "${strWorkTmpPath}/all.lcov";

        executeTest(
            "${strLCovExe} --extract=${strLCovOut} */${strModuleName}.c --o=${strLCovOutTmp}");

        # Combine with prior run if there was one
        if ($oStorage->exists($strLCovFile))
        {
            my $strCoverage = ${$oStorage->get($strLCovOutTmp)};
            $strCoverage =~ s/^SF\:.*$/SF:$strModulePath\.c/mg;
            $oStorage->put($strLCovOutTmp, $strCoverage);

            executeTest(
                "${strLCovExe} --add-tracefile=${strLCovOutTmp} --add-tracefile=${strLCovFile} --o=${strLCovOutTmp}");
        }

        # Update source file
        my $strCoverage = ${$oStorage->get($strLCovOutTmp)};

        if (defined($strCoverage))
        {
            if (!$bTest && $hTestCoverage->{$strCoveredModule} eq TESTDEF_COVERAGE_NOCODE)
            {
                confess &log(ERROR, "module '${strCoveredModule}' is marked 'no code' but has code");
            }

            # Get coverage info
            my $iTotalLines = (split(':', ($strCoverage =~ m/^LF:.*$/mg)[0]))[1] + 0;
            my $iCoveredLines = (split(':', ($strCoverage =~ m/^LH:.*$/mg)[0]))[1] + 0;

            my $iTotalBranches = 0;
            my $iCoveredBranches = 0;

            if ($strCoverage =~ /^BRF\:/mg && $strCoverage =~ /^BRH\:/mg)
            {
                # If this isn't here the statements below fail -- huh?
                my @match = $strCoverage =~ m/^BRF\:.*$/mg;

                $iTotalBranches = (split(':', ($strCoverage =~ m/^BRF:.*$/mg)[0]))[1] + 0;
                $iCoveredBranches = (split(':', ($strCoverage =~ m/^BRH:.*$/mg)[0]))[1] + 0;
            }<commit_msg>Fix lcov report when test module missing coverage.<commit_after>####################################################################################################################################
# Generate C Coverage Report
####################################################################################################################################
package pgBackRestTest::Common::CoverageTest;

####################################################################################################################################
# Perl includes
####################################################################################################################################
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use English '-no_match_vars';

use Digest::SHA qw(sha1_hex);
use Exporter qw(import);
    our @EXPORT = qw();
use File::Basename qw(dirname);

use pgBackRestDoc::Common::Log;
use pgBackRestDoc::Common::String;
use pgBackRestDoc::Html::DocHtmlBuilder;
use pgBackRestDoc::Html::DocHtmlElement;
use pgBackRestDoc::ProjectInfo;

use pgBackRestTest::Common::ContainerTest;
use pgBackRestTest::Common::DefineTest;
use pgBackRestTest::Common::ExecuteTest;
use pgBackRestTest::Common::ListTest;
use pgBackRestTest::Common::RunTest;

####################################################################################################################################
# Generate an lcov configuration file
####################################################################################################################################
sub coverageLCovConfigGenerate
{
    my $oStorage = shift;
    my $strOutFile = shift;
    my $bCoverageSummary = shift;

    my $strBranchFilter =
        'OBJECT_DEFINE_[A-Z0-9_]+\(|\s{4}[A-Z][A-Z0-9_]+\([^\?]*\)|\s{4}(ASSERT|assert|switch\s)\(|\{\+{0,1}' .
        ($bCoverageSummary ? 'uncoverable_branch' : 'uncover(ed|able)_branch');
    my $strLineFilter = '\{\+{0,1}uncover' . ($bCoverageSummary ? 'able' : '(ed|able)') . '[^_]';

    my $strConfig =
        "# LCOV Settings\n" .
        "\n" .
        "# Specify if branch coverage data should be collected and processed\n" .
        "lcov_branch_coverage=1\n" .
        "\n" .
        "# Specify the regular expression of lines to exclude from branch coverage\n" .
        "#\n" .
        '# OBJECT_DEFINE_[A-Z0-9_]+\( - exclude object definitions' . "\n" .
        '# \s{4}[A-Z][A-Z0-9_]+\([^\?]*\) - exclude macros that do not take a conditional parameter and are not themselves a parameter' . "\n" .
        '# ASSERT/(|assert\( - exclude asserts since it usually not possible to trigger both branches' . "\n" .
        '# switch \( - lcov requires default: to show complete coverage but --Wswitch-enum enforces all enum values be present' . "\n" .
        "lcov_excl_br_line=${strBranchFilter}\n" .
        "\n" .
        "# Specify the regular expression of lines to exclude\n" .
        "lcov_excl_line=${strLineFilter}\n" .
        "\n" .
        "# Coverage rate limits\n" .
        "genhtml_hi_limit = 100\n" .
        "genhtml_med_limit = 90\n" .
        "\n" .
        "# Width of line coverage field in source code view\n" .
        "genhtml_line_field_width = 9\n";

    # Write configuration file
    $oStorage->put($strOutFile, $strConfig);
}

push @EXPORT, qw(coverageLCovConfigGenerate);

####################################################################################################################################
# Extract coverage using gcov
####################################################################################################################################
sub coverageExtract
{
    my $oStorage = shift;
    my $strModule = shift;
    my $strTest = shift;
    my $bSummary = shift;
    my $strContainerImage = shift;
    my $strWorkPath = shift;
    my $strWorkTmpPath = shift;
    my $strWorkUnitPath = shift;
    my $strTestResultCoveragePath = shift . '/coverage';

    # Generate a list of files to cover
    my $hTestCoverage = (testDefModuleTest($strModule, $strTest))->{&TESTDEF_COVERAGE};

    my @stryCoveredModule;

    foreach my $strModule (sort(keys(%{$hTestCoverage})))
    {
        push (@stryCoveredModule, $strModule);
    }

    push(@stryCoveredModule, "module/${strModule}/" . testRunName($strTest, false) . 'Test');

    # Generate coverage reports for the modules
    my $strLCovConf = "${strTestResultCoveragePath}/raw/lcov.conf";
    coverageLCovConfigGenerate($oStorage, $strLCovConf, $bSummary);

    my $strLCovExe = "lcov --config-file=${strLCovConf}";
    my $strLCovOut = "${strWorkUnitPath}/test.lcov";
    my $strLCovOutTmp = "${strWorkUnitPath}/test.tmp.lcov";

    executeTest(
        (defined($strContainerImage) ? 'docker exec -i -u ' . TEST_USER . " ${strContainerImage} " : '') .
        "${strLCovExe} --capture --directory=${strWorkUnitPath} --o=${strLCovOut}");

    # Generate coverage report for each module
    foreach my $strCoveredModule (@stryCoveredModule)
    {
        my $strModuleName = testRunName($strCoveredModule, false);
        my $strModuleOutName = $strModuleName;
        my $bTest = false;

        if ($strModuleOutName =~ /^module/mg)
        {
            $strModuleOutName =~ s/^module/test/mg;
            $bTest = true;
        }

        # Generate lcov reports
        my $strModulePath =
            "${strWorkPath}/repo/" .
            (${strModuleOutName} =~ /^test\// ?
                'test/src/module/' . substr(${strModuleOutName}, 5) : "src/${strModuleOutName}");
        my $strLCovFile = "${strTestResultCoveragePath}/raw/${strModuleOutName}.lcov";
        my $strLCovTotal = "${strWorkTmpPath}/all.lcov";

        executeTest(
            "${strLCovExe}" . ($bTest ? ' --rc lcov_branch_coverage=0' : '') . " --extract=${strLCovOut} */${strModuleName}.c" .
                " --o=${strLCovOutTmp}");

        # Combine with prior run if there was one
        if ($oStorage->exists($strLCovFile))
        {
            my $strCoverage = ${$oStorage->get($strLCovOutTmp)};
            $strCoverage =~ s/^SF\:.*$/SF:$strModulePath\.c/mg;
            $oStorage->put($strLCovOutTmp, $strCoverage);

            executeTest("${strLCovExe} --add-tracefile=${strLCovOutTmp} --add-tracefile=${strLCovFile} --o=${strLCovOutTmp}");
        }

        # Update source file
        my $strCoverage = ${$oStorage->get($strLCovOutTmp)};

        if (defined($strCoverage))
        {
            if (!$bTest && $hTestCoverage->{$strCoveredModule} eq TESTDEF_COVERAGE_NOCODE)
            {
                confess &log(ERROR, "module '${strCoveredModule}' is marked 'no code' but has code");
            }

            # Get coverage info
            my $iTotalLines = (split(':', ($strCoverage =~ m/^LF:.*$/mg)[0]))[1] + 0;
            my $iCoveredLines = (split(':', ($strCoverage =~ m/^LH:.*$/mg)[0]))[1] + 0;

            my $iTotalBranches = 0;
            my $iCoveredBranches = 0;

            if ($strCoverage =~ /^BRF\:/mg && $strCoverage =~ /^BRH\:/mg)
            {
                # If this isn't here the statements below fail -- huh?
                my @match = $strCoverage =~ m/^BRF\:.*$/mg;

                $iTotalBranches = (split(':', ($strCoverage =~ m/^BRF:.*$/mg)[0]))[1] + 0;
                $iCoveredBranches = (split(':', ($strCoverage =~ m/^BRH:.*$/mg)[0]))[1] + 0;
            }
{'fraction_non_alphanumeric': 0.2781488227754166, 'fraction_numerical': 0.005262092693786683, 'mean_word_length': 3.294322132097335, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 19, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '926295', 'n_tokens_mistral': 4543, 'n_tokens_neox': 4308, 'n_words': 901}