Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Incorrect paths for header files #271

Open
ZedThree opened this issue Jul 12, 2018 · 14 comments
Open

Incorrect paths for header files #271

ZedThree opened this issue Jul 12, 2018 · 14 comments
Labels

Comments

@ZedThree
Copy link

@ZedThree ZedThree commented Jul 12, 2018

Header files don't seem to be dealt with correctly:

$ gcovr -j8 -r . --html-details -o gcovr-report/coverage.html -dTraceback (most recent call last):
  File "/home/p/.local/bin/gcovr", line 11, in <module>
    sys.exit(main())
  File "/home/p/.local/lib/python3.6/site-packages/gcovr/__main__.py", line 588, in main
    print_html_report(covdata, options)
  File "/home/p/.local/lib/python3.6/site-packages/gcovr/html_generator.py", line 275, in print_html_report
    errors='replace')
FileNotFoundError: [Errno 2] No such file or directory: 'src/array.hxx'

While --html-details throws an error, just running gcovr counts each header separately for each directory containing a source file that includes it, so I get stuff like:

------------------ ...
File               ...
------------------ ...
src/dir1/array.hxx ...
src/dir2/array.hxx ...
...

If I keep the gcov files, I can see they have the correct names, i.e.:

./^#include#array.hxx.gcov
./src/dir1/^#^#include#array.hxx.gcov
./src/dir2/^#^#include#array.hxx.gcov

This is with gcovr version 4.1 and gcc 8.1

@latk
Copy link
Member

@latk latk commented Jul 12, 2018

@latk
Copy link
Member

@latk latk commented Jul 21, 2018

This issue has not seen any progress in a while, so I am closing it for now. If the problem persists, please add further details so it can be re-opened. If someone else is experiencing a similar issue, please create a new issue.

@ZedThree
Copy link
Author

@ZedThree ZedThree commented Jul 24, 2018

Sorry for the late reply, I went on holiday before I got round to updating this! Apologies for the following information dump...

Here is a zipfile containing a complete toy problem:
gcovr-271.zip

Directory layout is as follows:

├── make.config
├── makefile
├── include
│   ├── makefile
│   ├── sys
│   │   └── foo.hxx
│   └── toy.hxx
├── src
│   ├── makefile
│   ├── sys
│   │   ├── foo.cxx
│   │   └── makefile
│   └── toy.cxx
└── test
    ├── makefile
    └── test.cxx

Also included is compile_commands.json.

Running:

make
make check

should print:

thing is : 4
bar
foo

test

Here is the output of gcovr -r . -k -s:

------------------------------------------------------------------------------
                           GCC Code Coverage Report
Directory: .
------------------------------------------------------------------------------
File                                       Lines    Exec  Cover   Missing
------------------------------------------------------------------------------
src/foo.hxx                                    2       0     0%   9-10
src/sys/foo.cxx                                9       9   100%   
src/toy.cxx                                   10      10   100%   
src/toy.hxx                                    4       4   100%   
test/foo.hxx                                   2       2   100%   
test/test.cxx                                  6       6   100%   
------------------------------------------------------------------------------
TOTAL                                         33      31    93%
------------------------------------------------------------------------------
lines: 93.9% (31 out of 33)
branches: 52.6% (20 out of 38)

Notice that foo.hxx is included twice, once with 0% coverage. Neither header file (foo.hxx, toy.hxx) is under include/. The makefile has a code-coverage-capture target that uses lcov, which reports everything correctly, so I don't think it's something fundamentally wrong with our setup. Unfortunately, lcov automatically cleans up the gcov files it generates, with no option to keep them, so it's not possible to directly compare them. I have a suspicion it's something to do with our recursive-make build system and needing to run gcov in the correct locations, but I'm not certain.

Finally, here is the output of find . -type f -name "*.gcov" | xargs head (after getting rid of the files from system headers):


==> ./src/sys/foo.cxx.gcov <==
        -:    0:Source:foo.cxx
        -:    0:Graph:/home/user/gcovr-271/src/sys/foo.gcno
        -:    0:Data:/home/user/gcovr-271/src/sys/foo.gcda
        -:    0:Runs:1
        -:    0:Programs:1
        -:    1:#include "sys/foo.hxx"
        -:    2:
function _ZN3Foo4pushENSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE called 3 returned 100% blocks executed 100%
        3:    3:void Foo::push(std::string str) {
        3:    4:  stack.push_back(std::move(str));

==> ./src/toy.cxx.gcov <==
        -:    0:Source:toy.cxx
        -:    0:Graph:/home/user/gcovr-271/src/toy.gcno
        -:    0:Data:/home/user/gcovr-271/src/toy.gcda
        -:    0:Runs:1
        -:    0:Programs:1
        -:    1:#include "toy.hxx"
        -:    2:#include "sys/foo.hxx"
        -:    3:
        -:    4:#include <iostream>
        -:    5:

==> ./src/^#include#sys#foo.hxx.gcov <==
        -:    0:Source:../include/sys/foo.hxx
        -:    0:Graph:/home/user/gcovr-271/src/toy.gcno
        -:    0:Data:/home/user/gcovr-271/src/toy.gcda
        -:    0:Runs:1
        -:    0:Programs:1
        -:    1:#ifndef __FOO_H__
        -:    2:#define __FOO_H__
        -:    3:
        -:    4:#include <string>
        -:    5:#include <vector>

==> ./src/^#include#toy.hxx.gcov <==
        -:    0:Source:../include/toy.hxx
        -:    0:Graph:/home/user/gcovr-271/src/toy.gcno
        -:    0:Data:/home/user/gcovr-271/src/toy.gcda
        -:    0:Runs:1
        -:    0:Programs:1
        -:    1:#ifndef __TOY_H__
        -:    2:#define __TOY_H__
        -:    3:
        -:    4:#include <iostream>
        -:    5:

==> ./test/test.cxx.gcov <==
        -:    0:Source:test.cxx
        -:    0:Graph:/home/user/gcovr-271/test/test.gcno
        -:    0:Data:/home/user/gcovr-271/test/test.gcda
        -:    0:Runs:1
        -:    0:Programs:1
        -:    1:#include "toy.hxx"
        -:    2:#include "sys/foo.hxx"
        -:    3:
function main called 1 returned 100% blocks executed 67%
        1:    4:int main() {

==> ./test/^#include#sys#foo.hxx.gcov <==
        -:    0:Source:../include/sys/foo.hxx
        -:    0:Graph:/home/user/gcovr-271/test/test.gcno
        -:    0:Data:/home/user/gcovr-271/test/test.gcda
        -:    0:Runs:1
        -:    0:Programs:1
        -:    1:#ifndef __FOO_H__
        -:    2:#define __FOO_H__
        -:    3:
        -:    4:#include <string>
        -:    5:#include <vector>

==> ./test.cxx.gcov <==
        -:    0:Source:test.cxx
        -:    0:Graph:/home/user/gcovr-271/test/test.gcno
        -:    0:Data:/home/user/gcovr-271/test/test.gcda
        -:    0:Runs:1
        -:    0:Programs:1

==> ./^#include#sys#foo.hxx.gcov <==
        -:    0:Source:../include/sys/foo.hxx
        -:    0:Graph:/home/user/gcovr-271/src/toy.gcno
        -:    0:Data:/home/user/gcovr-271/src/toy.gcda
        -:    0:Runs:1
        -:    0:Programs:1

==> ./toy.cxx.gcov <==
        -:    0:Source:toy.cxx
        -:    0:Graph:/home/user/gcovr-271/src/toy.gcno
        -:    0:Data:/home/user/gcovr-271/src/toy.gcda
        -:    0:Runs:1
        -:    0:Programs:1

==> ./^#include#toy.hxx.gcov <==
        -:    0:Source:../include/toy.hxx
        -:    0:Graph:/home/user/gcovr-271/src/toy.gcno
        -:    0:Data:/home/user/gcovr-271/src/toy.gcda
        -:    0:Runs:1
        -:    0:Programs:1

==> ./foo.cxx.gcov <==
        -:    0:Source:foo.cxx
        -:    0:Graph:/home/user/gcovr-271/src/sys/foo.gcno
        -:    0:Data:/home/user/gcovr-271/src/sys/foo.gcda
        -:    0:Runs:1
        -:    0:Programs:1

@latk
Copy link
Member

@latk latk commented Jul 24, 2018

@latk latk reopened this Jul 24, 2018
@lisongmin
Copy link
Contributor

@lisongmin lisongmin commented Aug 4, 2018

It may cause by -I../include option, and we should try real source file relative to the source directory.

  currdir      /tmp/abc
  gcov_fname   /tmp/tmpu47t14wr/^#include#toy.hxx.gcov
               ['        -', '    0', 'Source', '../include/toy.hxx\n']
  source_fname /tmp/abc/src/toy.gcda
  root         /tmp/abc
  fname        /tmp/abc/src/toy.hxx

now fname is dirname(source_fname) + basename(gcov_fname)

  • fname /tmp/abc/src/toy.hxx

which should be realpath(dirname(source_fname) + gcov_fname)

  • fname /tmp/abc/include/toy.hxx

without realpath test/../include/toy.hxx and src/../include/toy.hxx may consider as two different files.

@@ -196,8 +198,13 @@ def guess_source_file_name_heuristics(
     if os.path.exists(fname):
         return fname

-    # 3. Try using the path to the gcda file as the source directory
+    # 3. Try using the path relative to source directory
     source_fname_dir = os.path.dirname(source_fname)
+    fname = os.path.join(source_fname_dir, gcovname)
+    if os.path.exists(fname):
+        return os.path.realpath(fname)
+
+    # 4. Try using the path to the gcda file as the source directory
     fname = os.path.join(source_fname_dir, os.path.basename(gcovname))
     return fname
------------------------------------------------------------------------------
                           GCC Code Coverage Report
Directory: .
------------------------------------------------------------------------------
File                                       Lines    Exec  Cover   Missing
------------------------------------------------------------------------------
include/sys/foo.hxx                            2       2   100%
include/toy.hxx                                4       4   100%
src/sys/foo.cxx                                9       9   100%
src/toy.cxx                                   10      10   100%
test/test.cxx                                  6       6   100%
------------------------------------------------------------------------------
TOTAL                                         31      31   100%
------------------------------------------------------------------------------

@latk
Copy link
Member

@latk latk commented Aug 5, 2018

Thank you @lisongmin, you are absolutely right that we should use realpath() here – in fact, we should use realpath() for all the heuristics in that function.

I am not quite sure whether it is correct to resolve the source code path relative to the gcda file (confusingly called source_fname in the code). I think the source code paths should be interpreted relative to the original GCC working directory. In this particular example they are the same because of an in-source build, but not in general. Gcovr's guess for the GCC working directory is called chdir in run_gcov_and_process_files().

During the last few weeks I've been spending some time on gcovr's path heuristics. There's a lot of weird code around there and I hope to clean some of it up soon. Gcovr tends to make some assumptions about the build process that are not always a good fit in the real world, e.g. either GCC is invoked somewhere in the build directory or from the --root. This recursive-Makefile approach combined with a separate build directory would probably break the heuristics (haven't tested it yet).

I am super thankful for this issue because it exposes some of these bad assumptions!

@lisongmin
Copy link
Contributor

@lisongmin lisongmin commented Aug 6, 2018

Be aware symlink may changed via realpath(). This may affect to filter in some case.

@darkmattercoder
Copy link

@darkmattercoder darkmattercoder commented Dec 3, 2018

Hi,

is there anything that I can help with? I also experience those things. Shall I post outputs similar to the above?

@dquist
Copy link

@dquist dquist commented Dec 5, 2018

Also experiencing the same error

Traceback (most recent call last):
  File "/usr/local/bin/gcovr", line 11, in <module>
    sys.exit(main())
  File "/usr/local/lib/python2.7/dist-packages/gcovr/__main__.py", line 588, in main
    print_html_report(covdata, options)
  File "/usr/local/lib/python2.7/dist-packages/gcovr/html_generator.py", line 275, in print_html_report
    errors='replace')
IOError: [Errno 2] No such file or directory: 'test/cmake/../..Utility/Flags.h'

Any other info I can give?

Edit: Everything works fine when using standard gcov, but I get the error above when using
--gcov-executable 'llvm-cov gcov' option

Edit 2: At least in my instance, this exception only seems to appear when I am including a file via a compiler include path, not relative to the file being covered.

ie

// Include relative path
#include "../../mylib/include/my-header.h" // <-- Works

// Include using predefined compiler include path
#include "my-header.h"  // <-- Exception

Edit 3: Yeah interestingly the xml report works fine, it's just the html generator that throws an error. The xml report does report the same file multiple times, each with a different path, so this does seem to be a path issue. Interestingly I only get this issue when using clang/llvm-cov. Everything works fine when building with gcc and gcov.

@latk
Copy link
Member

@latk latk commented Dec 6, 2018

I now have a bit of time to look at issues again since I completed an important project today.

The core problem is that I don't understand what gcov's relative Source paths mean precisely: what directory are they relative to? The gcda file? The source file? The compiler invocation directory? The gcov invocation directory? Could this be solved if gcovr is also given any -I options?

I'll try looking at the gcov source code and at how lcov handles this, sometime over the next few weeks. If someone is quicker than me and can post an explanation here (with sources/references), that would also be most welcome. I'll also try turning @ZedThree's excellent example into an xfail test case.

Since the HTML details report has to open the source files in order to display the details, it is the only report that will die due to this issue.
Unfortunately the tests currently only run with GCC 5, not more recent versions (#206) and not LLVM (#134). So it's possible that small incompatibilities have been missed.

@dquist
Copy link

@dquist dquist commented Dec 7, 2018

Thank you for looking into this @latk!

Judging from the fact I can hardly find any online resources using gcovr with llvm, I must assume it's not a well used feature. I'm not surprised something got broken by mistake.

@latk latk mentioned this issue Dec 13, 2018
@thefifo
Copy link

@thefifo thefifo commented May 10, 2019

+1 Seeing the issue still with GCC 8.2
I'd also appreciate a hint regarding a workaround for this issue.

@z3bu
Copy link

@z3bu z3bu commented May 13, 2019

Same issue with gcc 7.4.0 (gcov alone works)

$ gcc --version
gcc.exe (Rev1, Built by MSYS2 project) 7.4.0

ensky added a commit to ensky/gcovr that referenced this issue Mar 17, 2020
ensky added a commit to ensky/gcovr that referenced this issue Mar 17, 2020
@kevinlrak
Copy link

@kevinlrak kevinlrak commented Dec 2, 2020

I'm seeing this issue currently with gcc 10.2.0.

Recommend changing issue title to "Incorrect paths for Include Path files" since this does not apply to all headers, nor is it restricted to only headers (I'm writing some odd unit tests which are including a .cpp file).


Edit
I have a working solution!

First, some background on my environment. This is (the interesting part of) my directory structure:

/workspaces/ci-baseline$ tree
.
|-- CMakeLists.txt
|-- build
|   |-- CMakeFiles
|   |   |-- ci-baseline.sample1.test.dir
|   |   |   `-- sample1
|   |   |       `-- test
|   |   |           |-- sample1_unittest.cpp.gcda
|   |   |           |-- sample1_unittest.cpp.gcno
|   |   |           `-- sample1_unittest.cpp.o
|   |-- bin
|   |   `-- ci-baseline.sample1.test
`-- sample1
    |-- include
    |   `-- sample1.h
    |-- src
    |   `-- sample1.cpp
    `-- test
        `-- sample1_unittest.cpp

A note of something unusual I'm doing: my sample1_unittest.cpp actually includes sample1.cpp directly instead of sample1.h. That isn't really relevant to this discussion, but I just wanted to provide some clarity since it is unusual and may confuse readers of this comment.

# sample1_unittest.cpp excerpt
// NOTE: Since our test unit contains an `int main` method, we can only test it by putting that into a namesapce.
// In order to do that, we have to include the source file directly, rather than any related header.
// This also ensures that the source is compiled with appropriate debugging symbols, optimizatopms, and gcov support.
namespace sample1 {
  #include "sample1.cpp"
}

The compile command (generated from CMake, with comments added for clarity) is:

# PWD = /workspaces/ci-baseline/build
/bin/g++-10 \
  -I../sample1/test         `# Location of this target's .cpp files` \
  -I../sample1/test/include `# Location of this target's .h files` \
  -I../sample1/src          `# Location of the unit-under-test's .cpp files` \
  -I../sample1/include      `# Location of the unit-under-test's .h files` \
  -isystem /home/conan/.conan/data/gtest/1.10.0/_/_/package/6f74be7aa0880ac3a032c53631ce97b6e7ac1ed3/include `# Location of system header files` \
  -fPIE         `# position independent executable` \
  -Wall         `# enables all the warnings about constructions that some users consider questionable, and that are easy to avoid` \
  -Wextra       `# enables some extra warning flags that are not enabled by -Wall` \
  -g            `# Produce debugging information` \
  -O0           `# Optimization: Reduce compilation time and make debugging produce the expected results.` \
  --coverage    `# compile and link code instrumented for coverage analysis.` \
  -std=gnu++17  `# Determine the language standard` \
  -MD           `# generate a rule suitable for make describing the dependencies of the main source file (stored at -MF)` \
  -MT CMakeFiles/ci-baseline.sample1.test.dir/sample1/test/sample1_unittest.cpp.o    `# change the target name` \
  -MF CMakeFiles/ci-baseline.sample1.test.dir/sample1/test/sample1_unittest.cpp.o.d  `# set the target dependency file name`\
  -o  CMakeFiles/ci-baseline.sample1.test.dir/sample1/test/sample1_unittest.cpp.o    `# set the output file` \
  -c            `# Compile or assemble the source files, but do not link` \
  `#input file` ../sample1/test/sample1_unittest.cpp
# Link command is next, but isn't interesting.

The simplest demonstration of the problem for me is:

# PWD=/workspaces/ci-baseline
$ gcovr
------------------------------------------------------------------------------
                           GCC Code Coverage Report
Directory: .
------------------------------------------------------------------------------
File                                       Lines    Exec  Cover   Missing
------------------------------------------------------------------------------
build/CMakeFiles/ci-baseline.sample1.test.dir/sample1/test/sample1.cpp
                                              15       0     0%   35-38,41,45,47,50,55,57,61,65,69-71
build/CMakeFiles/ci-baseline.sample1.test.dir/sample1/test/sample1_unittest.cpp
                                              39       3     7%   83,86-88,107-108,112-116,123,126-128,132-136,140-145,150-151,153-154,156-157,159-160,165,169
------------------------------------------------------------------------------
TOTAL                                         54       3     5%
------------------------------------------------------------------------------

There is no error thrown here, but you can see that gcovr has detected invalid file names which do not exist in the filesystem. This is an indication that the --html-detailed parameter would encounter an error.

The simplest solution is to set gcovr's root (either by the working directory where you execute the command, or the --root parameter) to the location where gcc was invoked, and then add the --filter parameter to specify the top-level directory containing all of your source code, since it may be (and in this case is) outside of the root.

For example:

# PWD=doesn't matter since we're using the `--root` parameter.
$ gcovr \
  --root   "/workspaces/ci-baseline/build" `# The directory where gcc was invoked` \
  --filter "/workspaces/ci-baseline"       `# The directory which contains our source code since it is not a child of root`;
------------------------------------------------------------------------------
                           GCC Code Coverage Report
Directory: /workspaces/ci-baseline/build
------------------------------------------------------------------------------
File                                       Lines    Exec  Cover   Missing
------------------------------------------------------------------------------
/workspaces/ci-baseline/sample1/src/sample1.cpp
                                              15       0     0%   35-38,41,45,47,50,55,57,61,65,69-71
/workspaces/ci-baseline/sample1/test/sample1_unittest.cpp
                                              39       3     7%   83,86-88,107-108,112-116,123,126-128,132-136,140-145,150-151,153-154,156-157,159-160,165,169
------------------------------------------------------------------------------
TOTAL                                         54       3     5%
------------------------------------------------------------------------------

Here you can see the filenames are correct. With those values for --root and --filter, the --html-details parameter now works.

ensky added a commit to ensky/gcovr that referenced this issue Dec 24, 2020
ensky added a commit to ensky/gcovr that referenced this issue Dec 24, 2020
ensky added a commit to ensky/gcovr that referenced this issue Dec 24, 2020
ensky added a commit to ensky/gcovr that referenced this issue Dec 24, 2020
ensky added a commit to ensky/gcovr that referenced this issue Dec 26, 2020
ensky added a commit to ensky/gcovr that referenced this issue Jan 3, 2021
zhajkun added a commit to zhajkun/gcovr that referenced this issue Jul 26, 2021
fix issue gcovr#271, make gcovr to search for the files in all directory.
@zhajkun zhajkun mentioned this issue Jul 26, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
8 participants