Sohcahtoa82
Sohcahtoa82

Reputation: 639

How do I make PyCharm show code coverage of programs that use multiple processes?

Let's say I create this simple module and call it MyModule.py:

import threading
import multiprocessing
import time

def workerThreaded():
    print 'thread working...'
    time.sleep(2)
    print 'thread complete'

def workerProcessed():
    print 'process working...'
    time.sleep(2)
    print 'process complete'

def main():
    workerThread = threading.Thread(target=workerThreaded)
    workerThread.start()
    workerProcess = multiprocessing.Process(target=workerProcessed)
    workerProcess.start()
    workerThread.join()
    workerProcess.join()

if __name__ == '__main__':
    main()

And then I throw this together to unit test it:

import unittest
import MyModule

class MyModuleTester(unittest.TestCase):
    def testMyModule(self):
        MyModule.main()

unittest.main()

(I know this isn't a good unit test because it doesn't actually TEST it, it just runs it, but that's not relevant to my question)

If I run this unit test in PyCharm with code coverage, then it only shows the code inside the workerThreaded() and main() functions as being covered, even though it clearly covers the workerProcessed() function as well.

How do I get PyCharm to include code that was started in a new process process in its code coverage? Also, how can I get it to include the if __name__ == '__main__': block as well?

I'm running PyCharm 2.7.3, as well as Python 2.7.3.

Upvotes: 4

Views: 2170

Answers (2)

avladev
avladev

Reputation: 13

I managed to make it work with subprocesses, not sure if this will work with threads or with python 2.

Create .covergerc file in your project root

[run]
concurrency=multiprocessing

Create sitecustomize.py file in your project root

import atexit
from glob import glob

import os
from functools import partial
from shutil import copyfile
from tempfile import mktemp


def combine_coverage(coverage_pattern, xml_pattern, old_coverage, old_xml):
    from coverage.cmdline import main
    # Find newly created coverage files
    coverage_files = [file for file in glob(coverage_pattern) if file not in old_coverage]
    xml_files = [file for file in glob(xml_pattern) if file not in old_xml]

    if not coverage_files:
        raise Exception("No coverage files generated!")

    if not xml_files:
        raise Exception("No coverage xml file generated!")

    # Combine all coverage files
    main(["combine", *coverage_files])

    # Convert them to xml
    main(["xml"])

    # Copy combined xml file over PyCharm generated one
    copyfile('coverage.xml', xml_files[0])
    os.remove('coverage.xml')


def enable_coverage():
    import coverage
    # Enable subprocess monitoring by providing rc file and enable coverage collecting
    os.environ['COVERAGE_PROCESS_START'] = os.path.join(os.path.dirname(__file__), '.coveragerc')
    coverage.process_startup()

    # Get current coverage files so we can process only newly created ones
    temp_root = os.path.dirname(mktemp())
    coverage_pattern = '%s/pycharm-coverage*.coverage*' % temp_root
    xml_pattern = '%s/pycharm-coverage*.xml' % temp_root
    old_coverage = glob(coverage_pattern)
    old_xml = glob(xml_pattern)

    # Register atexit handler to collect coverage files when python is shutting down
    atexit.register(partial(combine_coverage, coverage_pattern, xml_pattern, old_coverage, old_xml))


if os.getenv('PYCHARM_RUN_COVERAGE'):
    enable_coverage()

This basically detects if the code is running in PyCharm Coverage and collects newly generated coverage files. There are multiple files, one for the main process and one for each subprocess. So we need to combine them with "coverage combine" then convert them to xml with "coverage xml" and copy the resulted file over PyCharm's generated xml file.

Note that if you kill the child process in you tests coverage.py will not write the data file.

It does not require anything else just hit "Run unittests with Coverage" button in PyCharm.

That's it.

Upvotes: 1

Ned Batchelder
Ned Batchelder

Reputation: 375932

Coverage.py can measure code run in subprocesses, details are at http://nedbatchelder.com/code/coverage/subprocess.html

Upvotes: 1

Related Questions