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

Coverage missing for QThreads #686

Open
hhslepicka opened this issue Jul 27, 2018 · 4 comments
Open

Coverage missing for QThreads #686

hhslepicka opened this issue Jul 27, 2018 · 4 comments
Labels
exotic Unusual execution environment

Comments

@hhslepicka
Copy link

hhslepicka commented Jul 27, 2018

New Issue to separate the discussion on #582.

Here is a gist file for the test:
https://gist.github.com/hhslepicka/3153c9c2ac27dee4398754dce6a11fea

Environment:

  • macOS High Sierra - 10.13.5
  • Tested with Python 2.7 and 3.6 (Anaconda)
  • Coverage: 4.5.1 with C extension

Dependencies:

  • Python 2.7 or 3.6 (I tested with both)
  • PyQt5 & Qt5... any version...

Some useful information:

(py36) slepicka@public-81-101:~/sandbox/covtest  $ coverage --version
Coverage.py, version 4.5.1 with C extension
Documentation at https://coverage.readthedocs.io
(py36) slepicka@dhcp-swlan-public-81-101:~/sandbox/covtest  $ coverage run test.py
Counter:  56
(py36) slepicka@dhcp-swlan-public-81-101:~/sandbox/covtest  $ coverage report -m
Name      Stmts   Miss  Cover   Missing
---------------------------------------
test.py      24      4    83%   13-15, 18

Documentation for QThread in case it is useful: http://doc.qt.io/qt-5/qthread.html
Just let me know if you need additional info from my system.

@hhslepicka
Copy link
Author

From @nedbat's post in another thread.

Asked on the PyQt list: Any way to run code before each QThread?.

From there: an example of monkey-patching QThread to add debugging support: http://die-offenbachs.homelinux.org:48888/hg/eric/file/87b1626eb49b/DebugClients/Python/ThreadExtension.py#l361

@enkore
Copy link

enkore commented Aug 19, 2018

Even when explicitly registering sys.settrace, it doesn't work. Am I missing something?

https://gist.github.com/enkore/14c53e9af79e9bbaf66478115605633f

karlch added a commit to karlch/vimiv-qt that referenced this issue Nov 8, 2019
* Ignore lines that are clearly run in parallel in Qt from coverage
  See nedbat/coveragepy#686 for the reasoning
* Ignore some lines that are run in a different test env
karlch added a commit to karlch/vimiv-qt that referenced this issue Nov 8, 2019
Running external commands is completely tested in the end2end tests but
not noticed as they run in a QThread. See
nedbat/coveragepy#686
for more details.
@nedbat nedbat added the exotic Unusual execution environment label Jan 15, 2020
@earonesty
Copy link

earonesty commented May 20, 2020

I tried sys.settrace(threading._trace_hook) and it seems to work fine... coverage is reported on all threads.

Important to note for @enkore : your test code will only track coverage of the counter. Coverage starts at the next function call. So your sleep will not be covered (for example). Easiest way to fix this is to have run call self._run.... where the real code is.

Maybe all coveragepy needs is to enshrine this by adding a : coverage.thread_start() function. Then users of exotic threading system can just call that function.

@Dennis-van-Gils
Copy link

Dennis-van-Gils commented May 27, 2020

I solved this by writing a custom decorator to decorate your own methods with that are afflicted by this problem. Disclaimer: I am a Python enthusiast and not an expert, but it seems to work all right for me.

Paste this code to the top of your module:

# Code coverage tools 'coverage' and 'pytest-cov' don't seem to correctly trace 
# code which is inside methods called from within QThreads, see
# https://github.com/nedbat/coveragepy/issues/686
# To mitigate this problem, I use a custom decorator '@coverage_resolve_trace' 
# to be hung onto those method definitions. This will prepend the decorated
# method code with 'sys.settrace(threading._trace_hook)' when a code
# coverage test is detected. When no coverage test is detected, it will just
# pass the original method untouched.
import sys
import threading
from functools import wraps

running_coverage = 'coverage' in sys.modules
if running_coverage: print("\nCode coverage test detected\n")

def coverage_resolve_trace(fn):
    @wraps(fn)
    def wrapped(*args, **kwargs):
        if running_coverage: sys.settrace(threading._trace_hook)
        fn(*args, **kwargs)
    return wrapped    

And remember to decorate your problematic method with it, e.g.

@coverage_resolve_trace
def my_method():
    ....

I hope my solution is of use to others, too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
exotic Unusual execution environment
Projects
None yet
Development

No branches or pull requests

5 participants