From 6f562fba60cd035508726b037f72fdcc75e94e88 Mon Sep 17 00:00:00 2001 From: David Steele Date: Fri, 31 May 2024 14:52:07 +1000 Subject: [PATCH] Migrate coverage testing to C and remove dependency on lcov. lcov does not seem to be very well maintained and is often not compatible with the version of gcc it ships with until a few months after a new distro is released. In any case, lcov is that not useful for us because it generates reports on all coverage while we are mainly interested in missing coverage during development. Instead use the JSON output generated by gcov to generate our minimal coverage report and metrics for the documentation. There are some slight differences in the metrics. The difference in the common module was due to a bug in the old code -- build/common was being added into common as well as being reported separately. The source of the two additional branches in the backup module is unknown but almost certainly down to how exclusions are processed with regular expressions. Since there is additional coverage rather than coverage missing this seems fine. Since this was pretty much a rewrite it was also a good time to migrate to C. --- CONTRIBUTING.md | 4 +- doc/src/meson.build | 2 +- doc/xml/auto/metric-coverage-report.auto.xml | 16 +- doc/xml/contributing.xml | 4 +- src/build/common/json.c | 21 + src/build/common/json.h | 15 + test/Dockerfile | 2 +- test/Vagrantfile | 4 +- test/ci.pl | 6 - test/code-count/file-type.yaml | 24 +- test/container.yaml | 21 +- test/define.yaml | 10 +- .../pgBackRestTest/Common/CodeCountTest.pm | 1 - .../pgBackRestTest/Common/ContainerTest.pm | 20 - .../lib/pgBackRestTest/Common/CoverageTest.pm | 1070 --------------- test/lib/pgBackRestTest/Common/JobTest.pm | 15 +- test/lib/pgBackRestTest/Common/VmTest.pm | 2 - test/src/build/config/config.yaml | 8 +- test/src/build/help/help.xml | 10 + test/src/command/test/coverage.c | 1161 +++++++++++++++++ test/src/command/test/coverage.h | 24 + test/src/main.c | 26 +- test/src/meson.build | 3 +- test/src/module/common/typeJsonTest.c | 2 + test/src/module/test/coverageTest.c | 524 ++++++++ test/test.pl | 43 +- 26 files changed, 1875 insertions(+), 1163 deletions(-) create mode 100644 src/build/common/json.c create mode 100644 src/build/common/json.h delete mode 100644 test/lib/pgBackRestTest/Common/CoverageTest.pm create mode 100644 test/src/command/test/coverage.c create mode 100644 test/src/command/test/coverage.h create mode 100644 test/src/module/test/coverageTest.c diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fd74cf06a..40936a7cd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -30,7 +30,7 @@ This example is based on Ubuntu 20.04, but it should work on many versions of De pgbackrest-dev => Install development tools ``` -sudo apt-get install rsync git devscripts build-essential valgrind lcov autoconf \ +sudo apt-get install rsync git devscripts build-essential valgrind autoconf \ autoconf-archive libssl-dev zlib1g-dev libxml2-dev libpq-dev pkg-config \ libxml-checker-perl libyaml-perl libdbd-pg-perl liblz4-dev liblz4-tool \ zstd libzstd-dev bzip2 libbz2-dev libyaml-dev ccache python3-distutils meson @@ -532,7 +532,7 @@ pgbackrest/test/test.pl --vm-out --module=command --test=check --vm=u20 ``` > **NOTE:** Not all systems perform at the same speed, so if a test is timing out, try rerunning with another vm. -Because a test run has not been specified, a coverage report will be generated and written to the local file system under the pgBackRest directory `test/result/coverage/lcov/index.html` and a file with only the highlighted code that has not been covered will be written to `test/result/coverage/coverage.html`. +A coverage report will be generated and written to the local file system under the pgBackRest repository in `test/result/coverage.html`. If 100 percent code coverage has not been achieved, an error message will be displayed, for example: `ERROR: [125]: c module command/check/check is not fully covered` diff --git a/doc/src/meson.build b/doc/src/meson.build index bcddc6427..5ad5c9f87 100644 --- a/doc/src/meson.build +++ b/doc/src/meson.build @@ -19,6 +19,7 @@ subdir('command/help') # test target #################################################################################################################################### src_doc = [ + '../../src/build/common/json.c', '../../src/build/common/render.c', '../../src/build/common/string.c', '../../src/build/common/xml.c', @@ -38,7 +39,6 @@ src_doc = [ '../../src/common/io/fdWrite.c', '../../src/common/lock.c', '../../src/common/stat.c', - '../../src/common/type/json.c', '../../src/config/config.c', '../../src/config/parse.c', 'command/build/build.c', diff --git a/doc/xml/auto/metric-coverage-report.auto.xml b/doc/xml/auto/metric-coverage-report.auto.xml index 158184c30..692eba546 100644 --- a/doc/xml/auto/metric-coverage-report.auto.xml +++ b/doc/xml/auto/metric-coverage-report.auto.xml @@ -71,7 +71,7 @@ command/backup 50/50 (100.0%) - 790/790 (100.0%) + 792/792 (100.0%) 1896/1896 (100.0%) @@ -161,9 +161,9 @@ common - 149/149 (100.0%) - 634/634 (100.0%) - 1853/1853 (100.0%) + 147/147 (100.0%) + 630/630 (100.0%) + 1829/1829 (100.0%) @@ -357,7 +357,7 @@ TOTAL - 1651/1651 (100.0%) - 10409/10410 (99.99%) - 31143/31143 (100.0%) - \ No newline at end of file + 1649/1649 (100.0%) + 10407/10408 (99.99%) + 31119/31119 (100.0%) + diff --git a/doc/xml/contributing.xml b/doc/xml/contributing.xml index 4a7aa685b..f33e86ae3 100644 --- a/doc/xml/contributing.xml +++ b/doc/xml/contributing.xml @@ -91,7 +91,7 @@ - apt-get install rsync git devscripts build-essential valgrind lcov autoconf + apt-get install rsync git devscripts build-essential valgrind autoconf autoconf-archive libssl-dev zlib1g-dev libxml2-dev libpq-dev pkg-config libxml-checker-perl libyaml-perl libdbd-pg-perl liblz4-dev liblz4-tool zstd libzstd-dev bzip2 libbz2-dev libyaml-dev ccache python3-distutils meson @@ -597,7 +597,7 @@ pgbackrest/test/test.pl --vm-out --module=command --test=check --vm=u20 Not all systems perform at the same speed, so if a test is timing out, try rerunning with another vm. -

Because a test run has not been specified, a coverage report will be generated and written to the local file system under the directory test/result/coverage/lcov/index.html and a file with only the highlighted code that has not been covered will be written to test/result/coverage/coverage.html.

+

A coverage report will be generated and written to the local file system under the repository in test/result/coverage.html.

If 100 percent code coverage has not been achieved, an error message will be displayed, for example: ERROR: [125]: c module command/check/check is not fully covered

diff --git a/src/build/common/json.c b/src/build/common/json.c new file mode 100644 index 000000000..eaeed52ff --- /dev/null +++ b/src/build/common/json.c @@ -0,0 +1,21 @@ +/*********************************************************************************************************************************** +JSON Extensions +***********************************************************************************************************************************/ +// Include core module +#include "common/type/json.c" + +#include "build/common/json.h" + +/**********************************************************************************************************************************/ +bool +jsonReadUntil(JsonRead *const this, const JsonType type) +{ + FUNCTION_TEST_BEGIN(); + FUNCTION_TEST_PARAM(JSON_READ, this); + FUNCTION_TEST_PARAM(ENUM, type); + FUNCTION_TEST_END(); + + ASSERT(this != NULL); + + FUNCTION_TEST_RETURN(BOOL, jsonReadTypeNextIgnoreComma(this) != type); +} diff --git a/src/build/common/json.h b/src/build/common/json.h new file mode 100644 index 000000000..0a02733a8 --- /dev/null +++ b/src/build/common/json.h @@ -0,0 +1,15 @@ +/*********************************************************************************************************************************** +JSON Extensions +***********************************************************************************************************************************/ +#ifndef BUILD_COMMON_JSON_H +#define BUILD_COMMON_JSON_H + +#include "common/type/json.h" + +/*********************************************************************************************************************************** +Functions +***********************************************************************************************************************************/ +// Return true unless specified type found +bool jsonReadUntil(JsonRead *this, JsonType type); + +#endif diff --git a/test/Dockerfile b/test/Dockerfile index e88871a78..37414a408 100644 --- a/test/Dockerfile +++ b/test/Dockerfile @@ -12,7 +12,7 @@ RUN DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y \ sudo vim htop jq rsync sysstat curl \ libdbd-pg-perl libxml-checker-perl libyaml-perl \ devscripts build-essential lintian git cloc txt2man debhelper libssl-dev zlib1g-dev libperl-dev libxml2-dev liblz4-dev \ - liblz4-tool libpq-dev lcov autoconf-archive zstd libzstd-dev bzip2 libbz2-dev pkg-config libyaml-dev libc6-dbg wget meson \ + liblz4-tool libpq-dev autoconf-archive zstd libzstd-dev bzip2 libbz2-dev pkg-config libyaml-dev libc6-dbg wget meson \ ccache valgrind tzdata uncrustify libssh2-1-dev # Install Docker diff --git a/test/Vagrantfile b/test/Vagrantfile index a5568426a..ada11b1c7 100644 --- a/test/Vagrantfile +++ b/test/Vagrantfile @@ -75,7 +75,7 @@ Vagrant.configure(2) do |config| #----------------------------------------------------------------------------------------------------------------------- echo 'Install Build Tools' && date apt-get install -y devscripts build-essential lintian git cloc txt2man debhelper libssl-dev zlib1g-dev libperl-dev \ - libxml2-dev liblz4-dev liblz4-tool libpq-dev lcov autoconf-archive zstd libzstd-dev bzip2 libbz2-dev pkg-config \ + libxml2-dev liblz4-dev liblz4-tool libpq-dev autoconf-archive zstd libzstd-dev bzip2 libbz2-dev pkg-config \ libyaml-dev libc6-dbg valgrind meson ccache uncrustify libssh2-1-dev #----------------------------------------------------------------------------------------------------------------------- @@ -144,7 +144,7 @@ Vagrant.configure(2) do |config| # Basic environment to build/test pgBackRest using homebrew installed in the local user account. #------------------------------------------------------------------------------------------------------------------------------- # git clone --depth=1 https://github.com/Homebrew/brew ~/homebrew - # ~/homebrew/bin/brew install -q pkg-config openssl@1.1 libpq libxml2 libyaml cpanm lcov meson + # ~/homebrew/bin/brew install -q pkg-config openssl@1.1 libpq libxml2 libyaml cpanm meson # ~/homebrew/bin/cpanm --force --local-lib=~/homebrew/perl5 install YAML::XS XML::Checker::Parser # # export PATH="${HOME?}/homebrew/bin:$PATH" diff --git a/test/ci.pl b/test/ci.pl index 491b91c3f..e302848f4 100755 --- a/test/ci.pl +++ b/test/ci.pl @@ -185,12 +185,6 @@ eval "gcc ccache python3-distutils git rsync zlib1g-dev libssl-dev libxml2-dev libpq-dev libyaml-dev pkg-config uncrustify" . " libssh2-1-dev valgrind"; - # Add lcov when testing coverage - if (vmCoverageC($strVm)) - { - $strPackage .= " lcov"; - } - # Extra packages required when testing without containers if ($strVm eq VM_NONE) { diff --git a/test/code-count/file-type.yaml b/test/code-count/file-type.yaml index ec40d7d3a..14df7934b 100644 --- a/test/code-count/file-type.yaml +++ b/test/code-count/file-type.yaml @@ -679,6 +679,14 @@ src/build/common/exec.h: class: build type: c/h +src/build/common/json.c: + class: build + type: c + +src/build/common/json.h: + class: build + type: c/h + src/build/common/regExp.c: class: build type: c @@ -2507,10 +2515,6 @@ test/lib/pgBackRestTest/Common/ContainerTest.pm: class: test/harness type: perl -test/lib/pgBackRestTest/Common/CoverageTest.pm: - class: test/harness - type: perl - test/lib/pgBackRestTest/Common/DbVersion.pm: class: test/harness type: perl @@ -2599,6 +2603,14 @@ test/src/command/test/build.h: class: test/harness type: c/h +test/src/command/test/coverage.c: + class: test/harness + type: c + +test/src/command/test/coverage.h: + class: test/harness + type: c/h + test/src/command/test/define.c: class: test/harness type: c @@ -3207,6 +3219,10 @@ test/src/module/storage/sftpTest.c: class: test/module type: c +test/src/module/test/coverageTest.c: + class: test/module + type: c + test/src/module/test/testTest.c: class: test/module type: c diff --git a/test/container.yaml b/test/container.yaml index 677b17ade..9a0e968d1 100644 --- a/test/container.yaml +++ b/test/container.yaml @@ -12,19 +12,10 @@ # - docker login -u pgbackrest # - VM=XXX;DATE=YYYYMMDDX;BASE=pgbackrest/test:${VM?}-base;docker tag ${BASE?} ${BASE?}-${DATE?} && docker push ${BASE?}-${DATE?} # ********************************************************************************************************************************** -20240524A: +20240530A: x86_64: - u22: eab8001bbbe7c610453ce06adb31ebd971c61592 - -20240518A: - x86_64: - f40: 5173d773cfff925d4d41bd34029e55775be23c51 - -20240425A: - x86_64: - d10: fb03907abefd68fe16557b759e7e110e99eda748 - u20: c3fc7cc1956c5eb10995119deed7a21b92dd07a7 - -20240423A: - x86_64: - rh7: 3ba01dc5bbc96eed48287b8e4f52054d4d7030a5 + d10: 890b52c1c95ba3dc2d6a6535b8d9e8b273db275d + f40: c52b4f20349d8037d74a4a4543d2e33cd52bb3fa + u20: 9ac8fc879f2a7934b31d9e9df56a6a617be6cbed + u22: 3dc2ae79283af572ebe9d79c8c6a1086f9537233 + rh7: bf93465740557a07914137ddc8a2156efde2646b diff --git a/test/define.yaml b/test/define.yaml index 365e3918a..ad7e488e5 100644 --- a/test/define.yaml +++ b/test/define.yaml @@ -254,7 +254,8 @@ unit: total: 2 coverage: - - common/type/json + - build/common/json + - common/type/json: included # ---------------------------------------------------------------------------------------------------------------------------- - name: type-key-value @@ -715,6 +716,13 @@ unit: - test/command/test/define - test/command/test/test + # ---------------------------------------------------------------------------------------------------------------------------- + - name: coverage + total: 1 + + coverage: + - test/command/test/coverage + # ******************************************************************************************************************************** - name: info diff --git a/test/lib/pgBackRestTest/Common/CodeCountTest.pm b/test/lib/pgBackRestTest/Common/CodeCountTest.pm index e3f5b0f50..df1b14827 100644 --- a/test/lib/pgBackRestTest/Common/CodeCountTest.pm +++ b/test/lib/pgBackRestTest/Common/CodeCountTest.pm @@ -66,7 +66,6 @@ sub codeCountScan $strFile =~ '^test/result/' || $strFile =~ '^test/scratch' || $strFile =~ '^test/src/valgrind\.suppress\.' || - $strFile eq 'test/src/lcov.conf' || $strFile eq 'test/uncrustify.cfg'); # Classify the source file diff --git a/test/lib/pgBackRestTest/Common/ContainerTest.pm b/test/lib/pgBackRestTest/Common/ContainerTest.pm index 08d7c57a0..6a5ecdf9e 100644 --- a/test/lib/pgBackRestTest/Common/ContainerTest.pm +++ b/test/lib/pgBackRestTest/Common/ContainerTest.pm @@ -439,12 +439,6 @@ sub containerBuild } } - # If no specific version of lcov is requested then install the default package - if (!defined($oVm->{$strOS}{&VMDEF_LCOV_VERSION})) - { - $strScript .= ' lcov'; - } - #--------------------------------------------------------------------------------------------------------------------------- $strScript .= sectionHeader() . "# Regenerate SSH keys\n" . @@ -476,20 +470,6 @@ sub containerBuild " rm -rf /root/${strValgrind}"; } - #--------------------------------------------------------------------------------------------------------------------------- - if (defined($oVm->{$strOS}{&VMDEF_LCOV_VERSION})) - { - my $strLCovVersion = $oVm->{$strOS}{&VMDEF_LCOV_VERSION}; - my $strLCovPath = "/root/lcov-${strLCovVersion}"; - - $strScript .= sectionHeader() . - "# Build lcov ${strLCovVersion}\n" . - " wget -q -O - https://github.com/linux-test-project/lcov/releases/download/v${strLCovVersion}/" . - "lcov-${strLCovVersion}.tar.gz | tar zx -C /root && \\\n" . - " make -C ${strLCovPath} install && \\\n" . - " rm -rf ${strLCovPath}"; - } - #--------------------------------------------------------------------------------------------------------------------------- if (!$bDeprecated) { diff --git a/test/lib/pgBackRestTest/Common/CoverageTest.pm b/test/lib/pgBackRestTest/Common/CoverageTest.pm deleted file mode 100644 index 2f4aa93aa..000000000 --- a/test/lib/pgBackRestTest/Common/CoverageTest.pm +++ /dev/null @@ -1,1070 +0,0 @@ -#################################################################################################################################### -# 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; - -#################################################################################################################################### -# testRunName -# -# Create module/test names by upper-casing the first letter and then inserting capitals after each -. -#################################################################################################################################### -sub testRunName -{ - my $strName = shift; - my $bInitCapFirst = shift; - - $bInitCapFirst = defined($bInitCapFirst) ? $bInitCapFirst : true; - my $bFirst = true; - - my @stryName = split('\-', $strName); - $strName = undef; - - foreach my $strPart (@stryName) - { - $strName .= ($bFirst && $bInitCapFirst) || !$bFirst ? ucfirst($strPart) : $strPart; - $bFirst = false; - } - - return $strName; -} - -#################################################################################################################################### -# Generate an lcov configuration file -#################################################################################################################################### -sub coverageLCovConfigGenerate -{ - my $oStorage = shift; - my $strOutFile = shift; - my $bContainer = shift; - my $bCoverageSummary = shift; - - my $strBranchFilter = - 'OBJECT_DEFINE_[A-Z0-9_]+\(|\s{4}[A-Z][A-Z0-9_]+\([^\?]*\)|\s{4}(ASSERT|CHECK|CHECK_FMT|assert|switch\s)\(|\{\+{0,1}' . - ($bCoverageSummary ? 'uncoverable_branch' : 'uncover(ed|able)_branch'); - my $strLineFilter = - '\{\+{0,1}' . ($bCoverageSummary ? 'uncoverable' : '(uncover(ed|able)' . ($bContainer ? '' : '|vm_covered') . ')') . '[^_]'; - - 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/(|CHECK/(|assert\( - exclude asserts/checks 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 $bContainer = shift; - my $bSummary = shift; - my $strContainerImage = shift; - my $strWorkPath = shift; - my $strWorkTmpPath = shift; - my $strWorkUnitPath = shift; - my $strTestResultCoveragePath = shift . '/coverage'; - - # Coverage summary must be run in a container - if ($bSummary && !$bContainer) - { - confess &log(ERROR, "coverage summary must be run on containers for full 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, $bContainer, $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} 2>&1"); - - # Generate coverage report for each module - foreach my $strCoveredModule (@stryCoveredModule) - { - my $strModuleName = testRunName($strCoveredModule, false); - - if ($strModuleName =~ /^test/mg) - { - $strModuleName =~ s/^test/src/mg; - } - elsif ($strModuleName =~ /^doc/mg) - { - $strModuleName =~ s/^doc/doc\/src/mg; - } - - my $strModuleOutName = $strModuleName; - my $bTest = false; - - if ($strModuleOutName =~ /^module/mg) - { - $strModuleOutName =~ s/^module/test/mg; - $bTest = true; - } - - # Generate lcov reports - my $strModulePath = "${strWorkPath}/repo/"; - - if (${strModuleOutName} =~ /^src\//) - { - $strModulePath .= 'test/src/' . substr(${strModuleOutName}, 4); - } - elsif (${strModuleOutName} =~ /^test\//) - { - $strModulePath .= 'test/src/module/' . substr(${strModuleOutName}, 5); - } - elsif (${strModuleOutName} =~ /^doc\//) - { - $strModulePath .= "${strModuleOutName}"; - } - else - { - $strModulePath .= "src/${strModuleOutName}"; - } - - my $strLCovFile = "${strTestResultCoveragePath}/raw/${strModuleOutName}.lcov"; - my $strLCovTotal = "${strWorkTmpPath}/all.lcov"; - my $bInc = $strModuleName =~ '\.vendor$' || $strModuleName =~ '\.auto$'; - my $strModuleSourceFile = $strModulePath . '.c' . ($bInc ? '.inc' : ''); - - executeTest( - "${strLCovExe}" . ($bTest ? ' --rc lcov_branch_coverage=0' : '') . " --extract=${strLCovOut} *${strModuleName}.c" . - ($bInc ? '.inc' : '') . " --o=${strLCovOutTmp}"); - - # Combine with prior run if there was one - if ($oStorage->exists($strLCovFile)) - { - my $strCoverage = ${$oStorage->get($strLCovOutTmp)}; - $strCoverage =~ s/^SF\:.*$/SF:$strModuleSourceFile/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; - } - - # Report coverage if this is not a test or if the test does not have complete coverage - if (!$bTest || $iTotalLines != $iCoveredLines || $iTotalBranches != $iCoveredBranches) - { - # Fix source file name - $strCoverage =~ s/^SF\:.*$/SF:$strModuleSourceFile/mg; - - $oStorage->put($oStorage->openWrite($strLCovFile, {bPathCreate => true}), $strCoverage); - - if ($oStorage->exists($strLCovTotal)) - { - executeTest("${strLCovExe} --add-tracefile=${strLCovFile} --add-tracefile=${strLCovTotal} --o=${strLCovTotal}"); - } - else - { - $oStorage->copy($strLCovFile, $strLCovTotal) - } - } - else - { - $oStorage->remove($strLCovFile); - } - } - else - { - if ($hTestCoverage->{$strCoveredModule} ne TESTDEF_COVERAGE_NOCODE) - { - confess &log(ERROR, "module '${strCoveredModule}' is marked 'code' but has no code"); - } - } - } -} - -push @EXPORT, qw(coverageExtract); - -#################################################################################################################################### -# Validate converage and generate reports -#################################################################################################################################### -sub coverageValidateAndGenerate -{ - my $oyTestRun = shift; - my $oStorage = shift; - my $bCoverageReport = shift; - my $bCoverageSummary = shift; - my $strWorkPath = shift; - my $strWorkTmpPath = shift; - my $strTestResultCoveragePath = shift . '/coverage'; - my $strTestResultSummaryPath = shift; - - my $result = 0; - - # Determine which modules were covered (only check coverage if all tests were successful) - #----------------------------------------------------------------------------------------------------------------------- - my $hModuleTest; # Everything that was run - - # Build a hash of all modules, tests, and runs that were executed - foreach my $hTestRun (@{$oyTestRun}) - { - # Get coverage for the module - my $strModule = $hTestRun->{&TEST_MODULE}; - my $hModule = testDefModule($strModule); - - # Get coverage for the test - my $strTest = $hTestRun->{&TEST_NAME}; - my $hTest = testDefModuleTest($strModule, $strTest); - - # If no tests are listed it means all of them were run - if (@{$hTestRun->{&TEST_RUN}} == 0) - { - $hModuleTest->{$strModule}{$strTest} = true; - } - } - - # Now compare against code modules that should have full coverage - my $hCoverageList = testDefCoverageList(); - my $hCoverageType = testDefCoverageType(); - my $hCoverageActual; - - foreach my $strCodeModule (sort(keys(%{$hCoverageList}))) - { - if (@{$hCoverageList->{$strCodeModule}} > 0) - { - my $iCoverageTotal = 0; - - foreach my $hTest (@{$hCoverageList->{$strCodeModule}}) - { - if (!defined($hModuleTest->{$hTest->{strModule}}{$hTest->{strTest}})) - { - next; - } - - $iCoverageTotal++; - } - - if (@{$hCoverageList->{$strCodeModule}} == $iCoverageTotal) - { - $hCoverageActual->{testRunName($strCodeModule, false)} = $hCoverageType->{$strCodeModule}; - } - } - } - - if (keys(%{$hCoverageActual}) == 0) - { - &log(INFO, 'no code modules had all tests run required for coverage'); - } - - # Generate C coverage report - #--------------------------------------------------------------------------------------------------------------------------- - my $strLCovFile = "${strWorkTmpPath}/all.lcov"; - - if ($oStorage->exists($strLCovFile)) - { - foreach my $strCodeModule (sort(keys(%{$hCoverageActual}))) - { - my $strCoverageFile = $strCodeModule; - $strCoverageFile =~ s/^test/src/mg; - $strCoverageFile =~ s/^doc/doc\/src/mg; - $strCoverageFile =~ s/^module/test/mg; - $strCoverageFile = "${strTestResultCoveragePath}/raw/${strCoverageFile}.lcov"; - - my $strCoverage = $oStorage->get($oStorage->openRead($strCoverageFile, {bIgnoreMissing => true})); - - if (defined($strCoverage) && defined($$strCoverage)) - { - 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; - } - - # Generate detail if there is missing coverage - my $strDetail = undef; - - if ($iCoveredLines != $iTotalLines) - { - $strDetail .= "$iCoveredLines/$iTotalLines lines"; - } - - if ($iTotalBranches != $iCoveredBranches) - { - $strDetail .= (defined($strDetail) ? ', ' : '') . "$iCoveredBranches/$iTotalBranches branches"; - } - - if (defined($strDetail)) - { - &log(ERROR, "c module ${strCodeModule} is not fully covered ($strDetail)"); - $result++; - } - } - } - - if ($result == 0) - { - &log(INFO, "tested modules have full coverage"); - } - - # Always generate unified coverage report if there was missing coverage. This is useful for CI. - if ($bCoverageReport || $result != 0) - { - &log(INFO, 'writing C coverage report'); - - if ($bCoverageReport) - { - executeTest( - "genhtml ${strLCovFile} --config-file=${strTestResultCoveragePath}/raw/lcov.conf" . - " --prefix=${strWorkPath}/repo" . - " --output-directory=${strTestResultCoveragePath}/lcov"); - } - - coverageGenerate( - $oStorage, "${strWorkPath}/repo", "${strTestResultCoveragePath}/raw", "${strTestResultCoveragePath}/coverage.html", - $bCoverageReport); - } - # Else output report status in the HTML - else - { - $oStorage->put( - "${strTestResultCoveragePath}/coverage.html", - "
[ " . ($result == 0 ? "Coverage Complete" : "No Coverage Report") . " ]
"); - } - - if ($bCoverageSummary) - { - &log(INFO, 'writing C coverage summary report'); - - coverageDocSummaryGenerate( - $oStorage, "${strTestResultCoveragePath}/raw", "${strTestResultSummaryPath}/metric-coverage-report.auto.xml"); - } - } - - return $result; -} - -push @EXPORT, qw(coverageValidateAndGenerate); - -#################################################################################################################################### -# Generate a C coverage report -#################################################################################################################################### -# Helper to get the function for the current line -sub coverageGenerateFunction -{ - my $rhFile = shift; - my $iCurrentLine = shift; - - my $rhFunction; - - foreach my $iFunctionLine (sort(keys(%{$rhFile->{function}}))) - { - last if $iCurrentLine < $iFunctionLine; - - $rhFunction = $rhFile->{function}{$iFunctionLine}; - } - - if (!defined($rhFunction)) - { - confess &log(ERROR, "function not found at line ${iCurrentLine}"); - } - - return $rhFunction; -} - -sub coverageGenerate -{ - my $oStorage = shift; - my $strBasePath = shift; - my $strCoveragePath = shift; - my $strOutFile = shift; - my $bCoverageReport = shift; - - # Track missing coverage - my $rhCoverage = {}; - - # Find all lcov files in the coverage path - my $rhManifest = $oStorage->manifest($strCoveragePath); - - foreach my $strFileCov (sort(keys(%{$rhManifest}))) - { - # If a coverage report was not requested then skip coverage of test modules. If we are here it means there was missing - # coverage on CI and we want to keep the report as small as possible. - next if !$bCoverageReport && $strFileCov =~ /Test\.lcov$/; - - if ($strFileCov =~ /\.lcov$/) - { - my $strCoverage = ${$oStorage->get("${strCoveragePath}/${strFileCov}")}; - - # Show that the file is part of the coverage report even if there is no missing coverage - my $strFile; - - my $iBranchLine = -1; - my $iBranch = undef; - my $iBranchIdx = -1; - my $iBranchPart = undef; - - foreach my $strLine (split("\n", $strCoverage)) - { - # Get source file name - if ($strLine =~ /^SF\:/) - { - $strFile = substr($strLine, 3); - $rhCoverage->{$strFile} = undef; - - # Generate a random anchor so new reports will not show links as already followed. This is also an easy way to - # create valid, disambiguos links. - $rhCoverage->{$strFile}{anchor} = sha1_hex(rand(16)); - } - # Mark functions as initially covered - if ($strLine =~ /^FN\:/) - { - my ($strLineBegin) = split("\,", substr($strLine, 3)); - $rhCoverage->{$strFile}{function}{sprintf("%09d", $strLineBegin - 1)}{covered} = true; - } - # Check branch coverage - elsif ($strLine =~ /^BRDA\:/) - { - my @stryData = split("\,", substr($strLine, 5)); - - if (@stryData < 4) - { - confess &log(ERROR, "'${strLine}' should have four fields"); - } - - my $strBranchLine = sprintf("%09d", $stryData[0]); - - if ($iBranchLine != $stryData[0]) - { - $iBranchLine = $stryData[0] + 0; - $iBranch = $stryData[1] + 0; - $iBranchIdx = 0; - $iBranchPart = 0; - } - elsif ($iBranch != $stryData[1]) - { - if ($iBranchPart != 1) - { - confess &log(ERROR, "line ${iBranchLine}, branch ${iBranch} does not have at least two parts"); - } - - $iBranch = $stryData[1] + 0; - $iBranchIdx++; - $iBranchPart = 0; - } - else - { - $iBranchPart++; - } - - $rhCoverage->{$strFile}{line}{$strBranchLine}{branch}{$iBranchIdx}{$iBranchPart} = - $stryData[3] eq '-' || $stryData[3] eq '0' ? false : true; - - # If the branch is uncovered then the function is uncovered - if (!$rhCoverage->{$strFile}{line}{$strBranchLine}{branch}{$iBranchIdx}{$iBranchPart}) - { - coverageGenerateFunction($rhCoverage->{$strFile}, $strBranchLine)->{covered} = false; - } - } - - # Check line coverage - if ($strLine =~ /^DA\:/) - { - my @stryData = split("\,", substr($strLine, 3)); - - if (@stryData < 2) - { - confess &log(ERROR, "'${strLine}' should have two fields"); - } - - my $strStatementLine = sprintf("%09d", $stryData[0]); - - # If the statement is uncovered then the function is uncovered - if ($stryData[1] eq '0') - { - $rhCoverage->{$strFile}{line}{$strStatementLine}{statement} = 0; - coverageGenerateFunction($rhCoverage->{$strFile}, $strStatementLine)->{covered} = false; - } - } - } - } - } - - # Report on the entire function if any branches/lines in the function are uncovered - foreach my $strFile (sort(keys(%{$rhCoverage}))) - { - my $bFileCovered = true; - - # Proceed if there is some coverage data - if (defined($rhCoverage->{$strFile}{line})) - { - my @stryC = split("\n", ${$oStorage->get($strFile)}); - my $bInUncoveredFunction = false; - - # Iterate every line in the C file - for (my $iLineIdx = 0; $iLineIdx < @stryC; $iLineIdx++) - { - my $iLine = sprintf("%09d", $iLineIdx + 1); - - # If not in an uncovered function see if this line is the start of an uncovered function - if (!$bInUncoveredFunction) - { - $bInUncoveredFunction = - defined($rhCoverage->{$strFile}{function}{$iLine}) && !$rhCoverage->{$strFile}{function}{$iLine}{covered}; - - # If any function is uncovered then the file is uncovered - if ($bInUncoveredFunction) - { - $bFileCovered = false; - } - } - - # If not in an uncovered function remove coverage - if (!$bInUncoveredFunction) - { - delete($rhCoverage->{$strFile}{line}{$iLine}); - } - # Else in an uncovered function - else - { - # If there is no coverage for this line define it so it will show up on the report - if (!defined($rhCoverage->{$strFile}{line}{$iLine})) - { - $rhCoverage->{$strFile}{line}{$iLine} = undef; - } - - # Stop processing at the function end brace. This depends on the file being formated correctly, but worst case - # is we run on a display the entire file rather than just uncovered functions. - if ($stryC[$iLineIdx] =~ '^\}') - { - $bInUncoveredFunction = false; - } - } - } - } - - # Remove coverage info when file is fully covered - if ($bFileCovered) - { - delete($rhCoverage->{$strFile}{line}); - } - } - - # Build html - my $strTitle = PROJECT_NAME . ' Coverage Report'; - my $strDarkRed = '#580000'; - my $strGray = '#555555'; - my $strDarkGray = '#333333'; - - my $oHtml = new pgBackRestDoc::Html::DocHtmlBuilder( - PROJECT_NAME, $strTitle, - undef, undef, undef, - true, true, - - "html\n" . - "{\n" . - " background-color: ${strGray};\n" . - " font-family: Avenir, Corbel, sans-serif;\n" . - " color: white;\n" . - " font-size: 12pt;\n" . - " margin-top: 8px;\n" . - " margin-left: 1\%;\n" . - " margin-right: 1\%;\n" . - " width: 98\%;\n" . - "}\n" . - "\n" . - "body\n" . - "{\n" . - " margin: 0px auto;\n" . - " padding: 0px;\n" . - " width: 100\%;\n" . - " text-align: justify;\n" . - "}\n" . - - ".title\n" . - "{\n" . - " width: 100\%;\n" . - " text-align: center;\n" . - " font-size: 200\%;\n" . - "}\n" . - - "\n" . - ".list-table\n" . - "{\n" . - " width: 100\%;\n" . - "}\n" . - - "\n" . - ".list-table-caption\n" . - "{\n" . - " margin-top: 1em;\n" . - " font-size: 130\%;\n" . - " margin-bottom: .25em;\n" . - "}\n" . - - "\n" . - ".list-table-caption::after\n" . - "{\n" . - " content: \"Modules Tested for Coverage:\";\n" . - "}\n" . - - "\n" . - ".list-table-header-file\n" . - "{\n" . - " padding-left: .5em;\n" . - " padding-right: .5em;\n" . - " background-color: ${strDarkGray};\n" . - " width: 100\%;\n" . - "}\n" . - - "\n" . - ".list-table-row-uncovered\n" . - "{\n" . - " background-color: ${strDarkRed};\n" . - " color: white;\n" . - " width: 100\%;\n" . - "}\n" . - - "\n" . - ".list-table-row-file\n" . - "{\n" . - " padding-left: .5em;\n" . - " padding-right: .5em;\n" . - "}\n" . - - "\n" . - ".report-table\n" . - "{\n" . - " width: 100\%;\n" . - "}\n" . - - "\n" . - ".report-table-caption\n" . - "{\n" . - " margin-top: 1em;\n" . - " font-size: 130\%;\n" . - " margin-bottom: .25em;\n" . - "}\n" . - - "\n" . - ".report-table-caption::after\n" . - "{\n" . - " content: \" report:\";\n" . - "}\n" . - - "\n" . - ".report-table-header\n" . - "{\n" . - "}\n" . - - "\n" . - ".report-table-header-line, .report-table-header-branch, .report-table-header-code\n" . - "{\n" . - " padding-left: .5em;\n" . - " padding-right: .5em;\n" . - " background-color: ${strDarkGray};\n" . - "}\n" . - - "\n" . - ".report-table-header-code\n" . - "{\n" . - " width: 100\%;\n" . - "}\n" . - - "\n" . - ".report-table-row-dot-tr, .report-table-row\n" . - "{\n" . - " font-family: \"Courier New\", Courier, monospace;\n" . - "}\n" . - - "\n" . - ".report-table-row-dot-skip\n" . - "{\n" . - " height: 1em;\n" . - " padding-top: .25em;\n" . - " padding-bottom: .25em;\n" . - " text-align: center;\n" . - "}\n" . - - "\n" . - ".report-table-row-line, .report-table-row-branch, .report-table-row-branch-uncovered," . - " .report-table-row-code, .report-table-row-code-uncovered\n" . - "{\n" . - " padding-left: .5em;\n" . - " padding-right: .5em;\n" . - "}\n" . - - "\n" . - ".report-table-row-line\n" . - "{\n" . - " text-align: right;\n" . - "}\n" . - - "\n" . - ".report-table-row-branch, .report-table-row-branch-uncovered\n" . - "{\n" . - " text-align: right;\n" . - " white-space: nowrap;\n" . - "}\n" . - - "\n" . - ".report-table-row-branch-uncovered\n" . - "{\n" . - " background-color: ${strDarkRed};\n" . - " color: white;\n" . - "}\n" . - - "\n" . - ".report-table-row-code, .report-table-row-code-uncovered\n" . - "{\n" . - " white-space: pre;\n" . - "}\n" . - - "\n" . - ".report-table-row-code-uncovered\n" . - "{\n" . - " background-color: ${strDarkRed};\n" . - " color: white;\n" . - "}\n"); - - # File list title - $oHtml->bodyGet()->addNew(HTML_DIV, 'title', {strContent => $strTitle}); - - # Build the file list table - $oHtml->bodyGet()->addNew(HTML_DIV, 'list-table-caption'); - - my $oTable = $oHtml->bodyGet()->addNew(HTML_TABLE, 'list-table'); - - my $oHeader = $oTable->addNew(HTML_TR, 'list-table-header'); - $oHeader->addNew(HTML_TH, 'list-table-header-file', {strContent => 'FILE'}); - - foreach my $strFile (sort(keys(%{$rhCoverage}))) - { - my $oRow = $oTable->addNew(HTML_TR, 'list-table-row-' . (defined($rhCoverage->{$strFile}{line}) ? 'uncovered' : 'covered')); - my $strFileDisplay = substr($strFile, length($strBasePath) + 1); - - # Link only created when file is uncovered - if (defined($rhCoverage->{$strFile}{line})) - { - $oRow->addNew(HTML_TD, 'list-table-row-file')->addNew( - HTML_A, undef, {strContent => $strFileDisplay, strRef => '#' . $rhCoverage->{$strFile}{anchor}}); - } - # Else just show the file name - else - { - $oRow->addNew(HTML_TD, 'list-table-row-file', {strContent => $strFileDisplay}); - } - } - - # Report on files that are missing coverage - foreach my $strFile (sort(keys(%{$rhCoverage}))) - { - my $strFileDisplay = substr($strFile, length($strBasePath) + 1); - - if (defined($rhCoverage->{$strFile}{line})) - { - # Anchor only created when file is uncovered - $oHtml->bodyGet()->addNew(HTML_A, undef, {strId => $rhCoverage->{$strFile}{anchor}}); - - # Report table caption, i.e. the uncovered file name - $oHtml->bodyGet()->addNew(HTML_DIV, 'report-table-caption', {strContent => $strFileDisplay}); - - # Build the file report table - $oTable = $oHtml->bodyGet()->addNew(HTML_TABLE, 'report-table'); - - $oHeader = $oTable->addNew(HTML_TR, 'report-table-header'); - $oHeader->addNew(HTML_TH, 'report-table-header-line', {strContent => 'LINE'}); - $oHeader->addNew(HTML_TH, 'report-table-header-branch', {strContent => 'BRANCH'}); - $oHeader->addNew(HTML_TH, 'report-table-header-code', {strContent => 'CODE'}); - - my $strC = ${$oStorage->get($strFile)}; - my @stryC = split("\n", $strC); - my $iLastLine = undef; - - foreach my $strLine (sort(keys(%{$rhCoverage->{$strFile}{line}}))) - { - if (defined($iLastLine) && $strLine != $iLastLine + 1) - { - my $oRow = $oTable->addNew(HTML_TR, 'report-table-row-dot'); - $oRow->addNew(HTML_TD, 'report-table-row-dot-skip', {strExtra => 'colspan="3"'}); - } - - $iLastLine = $strLine; - my $iLine = int($strLine); - - my $oRow = $oTable->addNew(HTML_TR, 'report-table-row'); - $oRow->addNew(HTML_TD, 'report-table-row-line', {strContent => $iLine}); - my $strBranch; - - # Show missing branch coverage - my $bBranchCovered = true; - - if (defined($rhCoverage->{$strFile}{line}{$strLine}{branch})) - { - foreach my $iBranch (sort(keys(%{$rhCoverage->{$strFile}{line}{$strLine}{branch}}))) - { - $strBranch .= '['; - - my $bBranchPartFirst = true; - - foreach my $iBranchPart (sort(keys(%{$rhCoverage->{$strFile}{line}{$strLine}{branch}{$iBranch}}))) - { - if (!$bBranchPartFirst) - { - $strBranch .= ' '; - } - - if ($rhCoverage->{$strFile}{line}{$strLine}{branch}{$iBranch}{$iBranchPart}) - { - $strBranch .= '+'; - } - else - { - $strBranch .= '-'; - $bBranchCovered = false; - } - - $bBranchPartFirst = false; - } - - $strBranch .= ']'; - } - } - - $oRow->addNew( - HTML_TD, 'report-table-row-branch' . (!$bBranchCovered ? '-uncovered' : ''), {strContent => $strBranch}); - - # Color code based on coverage - $oRow->addNew( - HTML_TD, - 'report-table-row-code' . (defined($rhCoverage->{$strFile}{line}{$strLine}{statement}) ? '-uncovered' : ''), - {bPre => true, strContent => $stryC[$strLine - 1]}); - } - } - } - - # Write coverage report - $oStorage->put($strOutFile, $oHtml->htmlGet()); -} - -push @EXPORT, qw(coverageGenerate); - -#################################################################################################################################### -# Generate a C coverage summary for the documentation -#################################################################################################################################### -sub coverageDocSummaryGenerateValue -{ - my $iHit = shift; - my $iFound = shift; - - if (!defined($iFound) || !defined($iHit) || $iFound == 0) - { - return "---"; - } - - my $fPercent = $iHit * 100 / $iFound; - my $strPercent; - - if ($fPercent == 100) - { - $strPercent = '100.0'; - } - elsif ($fPercent > 99.99) - { - $strPercent = '99.99'; - } - else - { - $strPercent = sprintf("%.2f", $fPercent); - } - - return "${iHit}/${iFound} (${strPercent}%)"; -} - -sub coverageDocSummaryGenerate -{ - my $oStorage = shift; - my $strCoveragePath = shift; - my $strOutFile = shift; - - # Track coverage summary - my $rhSummary; - - # Find all lcov files in the coverage path - my $rhManifest = $oStorage->manifest($strCoveragePath); - - foreach my $strFileCov (sort(keys(%{$rhManifest}))) - { - # Skip doc/test modules (this includes modules that start with src/ since src/ is stripped from core modules) - next if $strFileCov =~ /^doc\// || $strFileCov =~ /^test\// || $strFileCov =~ /^src\//; - - if ($strFileCov =~ /\.lcov$/) - { - my $strCoverage = ${$oStorage->get("${strCoveragePath}/${strFileCov}")}; - my $strModule = dirname($strFileCov); - - foreach my $strLine (split("\n", $strCoverage)) - { - # Get Line Coverage - if ($strLine =~ /^LF\:/) - { - $rhSummary->{$strModule}{line}{found} += substr($strLine, 3) + 0; - $rhSummary->{zzztotal}{line}{found} += substr($strLine, 3) + 0; - } - - if ($strLine =~ /^LH\:/) - { - $rhSummary->{$strModule}{line}{hit} += substr($strLine, 3) + 0; - $rhSummary->{zzztotal}{line}{hit} += substr($strLine, 3) + 0; - } - - # Get Function Coverage - if ($strLine =~ /^FNF\:/) - { - $rhSummary->{$strModule}{function}{found} += substr($strLine, 4) + 0; - $rhSummary->{zzztotal}{function}{found} += substr($strLine, 4) + 0; - } - - if ($strLine =~ /^FNH\:/) - { - $rhSummary->{$strModule}{function}{hit} += substr($strLine, 4) + 0; - $rhSummary->{zzztotal}{function}{hit} += substr($strLine, 4) + 0; - } - - # Get Branch Coverage - if ($strLine =~ /^BRF\:/) - { - $rhSummary->{$strModule}{branch}{found} += substr($strLine, 4) + 0; - $rhSummary->{zzztotal}{branch}{found} += substr($strLine, 4) + 0; - } - - if ($strLine =~ /^BRH\:/) - { - $rhSummary->{$strModule}{branch}{hit} += substr($strLine, 4) + 0; - $rhSummary->{zzztotal}{branch}{hit} += substr($strLine, 4) + 0; - } - } - } - } - - # use Data::Dumper;confess Dumper($rhSummary); - - my $strSummary; - - foreach my $strModule (sort(keys(%{$rhSummary}))) - { - my $rhModuleData = $rhSummary->{$strModule}; - - $strSummary .= - (defined($strSummary) ? "\n\n" : '') . - "\n" . - " " . ($strModule eq 'zzztotal' ? 'TOTAL' : $strModule) . "\n" . - " " . - coverageDocSummaryGenerateValue($rhModuleData->{function}{hit}, $rhModuleData->{function}{found}) . - "\n" . - " " . - coverageDocSummaryGenerateValue($rhModuleData->{branch}{hit}, $rhModuleData->{branch}{found}) . - "\n" . - " " . - coverageDocSummaryGenerateValue($rhModuleData->{line}{hit}, $rhModuleData->{line}{found}) . - "\n" . - ""; - } - - - # Write coverage report - $oStorage->put($strOutFile, $strSummary); -} - -push @EXPORT, qw(coverageDocSummaryGenerate); - -1; diff --git a/test/lib/pgBackRestTest/Common/JobTest.pm b/test/lib/pgBackRestTest/Common/JobTest.pm index 90c27c68f..e98c13d56 100644 --- a/test/lib/pgBackRestTest/Common/JobTest.pm +++ b/test/lib/pgBackRestTest/Common/JobTest.pm @@ -25,7 +25,6 @@ use pgBackRestDoc::ProjectInfo; use pgBackRestTest::Common::BuildTest; use pgBackRestTest::Common::ContainerTest; -use pgBackRestTest::Common::CoverageTest; use pgBackRestTest::Common::DbVersion; use pgBackRestTest::Common::DefineTest; use pgBackRestTest::Common::ExecuteTest; @@ -342,13 +341,13 @@ sub end # If C code generate coverage info if ($iExitStatus == 0 && $self->{oTest}->{&TEST_C} && vmCoverageC($self->{oTest}->{&TEST_VM}) && $self->{bCoverageUnit}) { - coverageExtract( - $self->{oStorageTest}, $self->{oTest}->{&TEST_MODULE}, $self->{oTest}->{&TEST_NAME}, - $self->{oTest}->{&TEST_VM} ne VM_NONE, $self->{bCoverageSummary}, - $self->{oTest}->{&TEST_VM} eq VM_NONE ? undef : $strImage, $self->{strTestPath}, "$self->{strTestPath}/temp", - -e "$self->{strUnitPath}/build/test-unit.p" ? - "$self->{strUnitPath}/build/test-unit.p" : "$self->{strUnitPath}/build/test-unit\@exe", - $self->{strBackRestBase} . '/test/result'); + executeTest( + ($self->{oTest}->{&TEST_VM} ne VM_NONE ? 'docker exec -i -u ' . TEST_USER . " ${strImage} " : '') . + "gcov --json-format --stdout --branch-probabilities " . + (-e "$self->{strUnitPath}/build/test-unit.p" ? + "$self->{strUnitPath}/build/test-unit.p" : "$self->{strUnitPath}/build/test-unit\@exe") . + '/test.c.gcda > ' . $self->{strBackRestBase} . '/test/result/coverage/raw/' . + $self->{oTest}->{&TEST_MODULE} . '-' . $self->{oTest}->{&TEST_NAME} . '.json'); } # Record elapsed time diff --git a/test/lib/pgBackRestTest/Common/VmTest.pm b/test/lib/pgBackRestTest/Common/VmTest.pm index c16b8db59..50cdf7652 100644 --- a/test/lib/pgBackRestTest/Common/VmTest.pm +++ b/test/lib/pgBackRestTest/Common/VmTest.pm @@ -42,8 +42,6 @@ use constant VM_OS_BASE => 'os-base' push @EXPORT, qw(VM_OS_BASE); use constant VMDEF_PGSQL_BIN => 'psql-bin'; push @EXPORT, qw(VMDEF_PGSQL_BIN); -use constant VMDEF_LCOV_VERSION => 'lcov-version'; - push @EXPORT, qw(VMDEF_LCOV_VERSION); use constant VMDEF_WITH_LZ4 => 'with-lz4'; push @EXPORT, qw(VMDEF_WITH_LZ4); use constant VMDEF_WITH_ZST => 'with-zst'; diff --git a/test/src/build/config/config.yaml b/test/src/build/config/config.yaml index 8006823c4..fa1b6f977 100644 --- a/test/src/build/config/config.yaml +++ b/test/src/build/config/config.yaml @@ -59,6 +59,12 @@ option: command: test: {} + coverage-summary: + type: boolean + default: false + command: + test: {} + neutral-umask: type: boolean internal: true @@ -126,8 +132,8 @@ option: vm-id: type: integer + required: false internal: true - default: 0 allow-range: [0, 1024] command: test: {} diff --git a/test/src/build/help/help.xml b/test/src/build/help/help.xml index b9b93b01a..0eba08a14 100644 --- a/test/src/build/help/help.xml +++ b/test/src/build/help/help.xml @@ -54,6 +54,16 @@ n + +