OpenCppCoverage / OpenCppCoverage

OpenCppCoverage is an open source code coverage tool for C++ under Windows.
GNU General Public License v3.0
899 stars 150 forks source link

Piece of template code from shared library marked as uncovered while it is definitely covered #144

Open jpo38 opened 3 years ago

jpo38 commented 3 years ago

To Reproduce Create a shared library:

coverage.h:

#pragma once

#ifdef COVERAGE_EXPORTS
#define COVERAGE_API __declspec(dllexport)
#else
#define COVERAGE_API __declspec(dllimport)
#endif

#include <iostream>

class COVERAGE_API MyClass
{
public:
    template <typename T>
    static void templatefunc(T u)
    {
        if ( u == 4 )
            std::cout << "OK" << std::endl;
        else
            std::cout << "KO" << std::endl;
    }

    static void testfunc()
    {
        templatefunc<uint8_t>( 4 );
    }
};

coverage.cpp:

#include "coverage.h"

Then, your main test program:

#include "coverage.h"

int main( int argc, char* argv[] )
{
    MyClass::testfunc();
    MyClass::templatefunc<uint8_t>( 7 );
    return 0;
}

I then compile in Debug (using Visual Studio 2019), run OpenCppCoverage 0.9.9 with no particular arguments (but export_type or sources/exclude_sources).

Output it:

[info] Start Program:
Path:"C:\\dev\\vobs_sde\\build/lib_coverage_only/win64/stg/Debug\\test_cppunit_coverage_utl.exe"
Arguments: 
Working directory: "C:\\dev\\vobs_sde\\build/lib_coverage_only/win64/stg/Debug"
Modules: Selected: * Excluded: test_cppunit_* 
Sources: Selected: C:\dev\vobs_sde Excluded: C:\dev\vobs_sde\sde\3rdparty C:\dev\vobs_sde\public\tst     C:\dev\vobs_sde\sde\code\tst C:\dev\vobs_sde\private\clinatec\tst C:\dev\vobs_sde\private\le2s\tst C:\dev\vobs_sde\private\sdetests\tst C:\dev\vobs_sde\private\valkyrie_lite\tst 
Log Level: Normal
Cover Children: 0
Aggregate by file: 1
Continue after C++ exception: 0
Optimized build support: 0
Export: html C:\dev\vobs_sde\build\lib_coverage_only\win64\stg\Debug\OpenCppCoverage\test_cppunit_coverage_utl binary C:\dev\vobs_sde\build\lib_coverage_only\win64\stg\Debug\OpenCppCoverage\test_cppunit_coverage_utl/opencpp.bin 
Input coverage: 
Unified diff: 
Excluded line regular expressions: 
Substitute pdb source paths: 
[info] Module: C:\Windows\System32\ntdll.dll is selected because it matches selected pattern: *
[info] Module: C:\Windows\System32\kernel32.dll is selected because it matches selected pattern: *
[info] Module: C:\Windows\System32\KernelBase.dll is selected because it matches selected pattern: *
[info] Module: C:\Windows\System32\sysfer.dll is selected because it matches selected pattern: *
[info] Module: C:\Windows\System32\msvcp140d.dll is selected because it matches selected pattern: *
[info] Module: C:\Windows\System32\vcruntime140_1d.dll is selected because it matches selected pattern: *
[info] Module: C:\Windows\System32\ucrtbased.dll is selected because it matches selected pattern: *
[info] Module: C:\Windows\System32\vcruntime140d.dll is selected because it matches selected pattern: *
[info] Module: C:\dev\vobs_sde\build\lib_coverage_only\win64\stg\Debug\coverage.dll is selected because it matches selected pattern: *
[info] Module: C:\Windows\System32\kernel.appcore.dll is selected because it matches selected pattern: *
[info] Module: C:\Windows\System32\msvcrt.dll is selected because it matches selected pattern: *
[info] Module: C:\Windows\System32\rpcrt4.dll is selected because it matches selected pattern: *
[info] ----------------------------------------------------
[info] Coverage generated in Folder C:\dev\vobs_sde\build\lib_coverage_only\win64\stg\Debug\OpenCppCoverage\test_cppunit_coverage_utl
[info] ----------------------------------------------------
[info] ----------------------------------------------------
[info] Coverage binary generated in file: C:\dev\vobs_sde\build\lib_coverage_only\win64\stg\Debug\OpenCppCoverage\test_cppunit_coverage_utl/opencpp.bin
[info] ----------------------------------------------------
[info] The code coverage report is not what you expect? See the FAQ https://github.com/OpenCppCoverage/OpenCppCoverage/wiki/FAQ.

Expected behavior MyClass::templatefunc should be fully covered (if statement covered as called by testfunc() called by main, else statement covered as called directly by main). However, std::cout << "KO" << std::endl; is marked as uncovered (while program output shows OK and KO, so it's definitely covered).

Desktop (please complete the following information):

jpo38 commented 3 years ago

I realize I have the test program ( main) be part of excluded_modules list.

Looks like coverage is reported correctly if the test program is not in excluded_modules (that's due to templates being compiled on the fly). But then, test program itself is part of the report, which is not expected, I want the report for my library code only.

Is there a way to have this work as expected? (have only the shared library code in the report and have it be marked as fully covered).

SteveGilham commented 3 years ago

The abstract template code in the shared library is not executed; it's only the instantiations that are, so what you have observed is the expected behaviour. One instantiation takes one path, the other is excluded from coverage so the fact that it takes the second path is not observed.

One approach to getting coverage of an instantiation, when the defining library has none, would be to have an essentially trivial shim library, from which coverage is gathered, between the unit tests and the defining one, which just does the necessary instantiation, like

class TemplateShims
{
  public:
    static void MyClass_templatefunc_uint8_t(uint8_t n)
    {
        MyClass::templatefunc<uint8_t>(n);
    }
}

and call that from the test, like

#include "coverage.h"

int main( int argc, char* argv[] )
{
    MyClass::testfunc();
    TemplateShims::MyClass_templatefunc_uint8_t( 7 );
    return 0;
}
jpo38 commented 3 years ago

OK, thank you for the explanation,

Jean

retroandchill commented 1 month ago

I'm observing something similar to this. The calling code for the template is showing up in the included modules, but for some reason, it still marks it as not covered, despite me knowing it was called.