coverage tools fail on relative source path

Issue #320 new
Dima Tisnek
created an issue
d = coverage.data.CoverageData(".coverage")
d.lines[FILENAME] = {7: None, 8:None, 11:None, 12: None}
d.write()

then coverage reportran from . where both .coverage and source.py are present, depending on FILENAME above, observed

  • source_that_doesnt_exist.py -- complains that source cannot be found, ok
  • /full/path/to/source.py -- no error, coverage 25%, ok
  • ./source.py -- no error, coverage 0%, why?
  • source.py -- no error, coverage 0%, why?

It appears that error reporting is not consistent with coverage reporting.

same for coverage html

Comments (10)

  1. Ned Batchelder repo owner

    I don't understand why you are writing into the data object. coverage.py should fill in that data. Can you tell me more about what you are trying to do? Do you have a use case you can describe for me?

  2. Dima Tisnek reporter

    Above was the short example.

    Full example was to instrument pyinstaller-built executable, run it, turn coverage on and off on demand, load thus produced .converage.xxx into data object, unmangle file names in .lines and .arcs and then save it back.

    Reason is that pyinstaller pretends that e.g. module cherrypy.static resolves to file name /full/path/to/exe/out.pyz/cherrypy.static (note, it has slashes but doesn't end with .py).open()` of such names is intercepted in C level (?) and pyinstaller runtime returns data from the packed executable (itself). Thus coverage sees something that is not quite source path or module name.

  3. Ned Batchelder repo owner

    @Dima Tisnek Can you possibly create an example of this scenario that I can run? It will make it easier to understand what is going on. I'd like to know what filenames are being reported to the trace function, for example.

  4. Dima Tisnek reporter

    Ned, I was trying to work around pyinstaller's name mangling.

    up-to-date pyinstaller is a little easier to work work, even if it doesn't work out of the box, it should be doable using coveragerc file.

    # ctest.py
    #!/usr/bin/env python
    import os.path
    import lib.foo
    
    
    def xx():
        os.path.exists("/")
        lib.foo.bar()
    
    
    if __name__ == "__main__":
        import coverage
        c = coverage.coverage(cover_pylib=True)  # coverage can't tell user modules from system in pyinstaller build
        c.start()
        xx()
        c.stop()
        c.save()
    
    (plc)air:~ dima$ ./ctest.py
    (plc)air:~ dima$ coverage report
    Name                                                                          Stmts   Miss  Cover
    -------------------------------------------------------------------------------------------------
    /Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/genericpath      50     47     6%
    /Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/threading       587    586     1%
    ctest                                                                            12     10    17%
    lib/foo                                                                           4      2    50%
    -------------------------------------------------------------------------------------------------
    TOTAL                                                                           653    645     1%
    
    (plc)air:~ dima$ pyinstaller -y ctest.py
    [snip]
    (plc)air:~ dima$ ./dist/ctest/ctest
    (plc)air:~ dima$ coverage report
    Name                     Stmts   Miss  Cover
    --------------------------------------------
    dist/ctest/genericpath   NoSource: No source for code: '/Users/dima/dist/ctest/genericpath.py'
    dist/ctest/lib/foo   NoSource: No source for code: '/Users/dima/dist/ctest/lib/foo.py'
    dist/ctest/threading   NoSource: No source for code: '/Users/dima/dist/ctest/threading.py'
    
    (plc)air:~ dima$ python -c 'import coverage; d = coverage.data.CoverageData(".coverage"); d.read(); print d.lines.keys()'
    ['/Users/dima/dist/ctest/lib/foo.py', '/Users/dima/dist/ctest/genericpath.py', '/Users/dima/dist/ctest/threading.py']
    

    Obviously there are no *.py files in ./dist/ctest, there's only a binary and a bunch of .so's.

    So I figured I'd go inside coverage file and tweak the data myself -- remove system modules, rename local modules.

    when I did that, I ran into the bug that relative paths to source are unsupported, yet don't raise an error.

  5. Loic Dachary

    The problem is that pyinstaller make it so the coverage tracer function is called with threading.py instead of /usr/lib/python2.7/threading.py as frame.f_code.co_filename when the event is call. I don't think coverage has any automated way to find the source and a bug should be filed against pyinstaller to fix that problem.

  6. Log in to comment