├── demo.sh └── setup.py /demo.sh: -------------------------------------------------------------------------------- 1 | rm -rf tmp 2 | mkdir tmp 3 | cd tmp 4 | set -ex 5 | 6 | virtualenv tmpenv 7 | . tmpenv/bin/activate 8 | pip install coverage-enable-subprocess --extra-index-url https://testpypi.python.org/pypi 9 | touch .coveragerc 10 | export COVERAGE_PROCESS_START=$PWD/.coveragerc 11 | echo 'print("oh, hi!")' > ohhi.py 12 | python ohhi.py 13 | coverage report 14 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """ 2 | This package installs a pth file that enables the coveragepy process_startup 3 | feature in this python prefix/virtualenv in subsequent runs. 4 | 5 | See: http://nedbatchelder.com/code/coverage/subprocess.html 6 | 7 | 8 | Demo:: 9 | 10 | $ virtualenv tmpenv 11 | $ . tmpenv/bin/activate 12 | $ pip install coverage-enable-subprocess 13 | $ touch .coveragerc 14 | $ export COVERAGE_PROCESS_START=$PWD/.coveragerc 15 | $ echo 'print("oh, hi!")' > ohhi.py 16 | $ python ohhi.py 17 | oh, hi! 18 | 19 | $ coverage report 20 | Name Stmts Miss Cover 21 | ----------------------------------------------------- 22 | /etc/python2.6/sitecustomize.py 5 1 80% 23 | ohhi.py 1 0 100% 24 | tmpenv/lib/python2.6/site.py 433 392 9% 25 | ----------------------------------------------------- 26 | TOTAL 439 393 10% 27 | 28 | 29 | For projects that need to cd during their test runs, and run many processes in parallel, 30 | I ensure a ``$TOP`` variable is exported, and I use this .coveragerc:: 31 | 32 | [run] 33 | parallel = True 34 | branch = True 35 | data_file = $TOP/.coverage 36 | 37 | [report] 38 | exclude_lines = 39 | # Have to re-enable the standard pragma 40 | \\#.*pragma:\\s*no.?cover 41 | 42 | # we can't get coverage for functions that don't return: 43 | \\#.*never returns 44 | \\#.*doesn't return 45 | 46 | # Don't complain if tests don't hit defensive assertion code: 47 | ^\\s*raise Impossible\\b 48 | ^\\s*raise AssertionError\\b 49 | ^\\s*raise NotImplementedError\\b 50 | ^\\s*return NotImplemented\\b 51 | 52 | # Don't complain if tests don't hit re-raise of unexpected errors: 53 | ^\\s*raise$ 54 | 55 | # if main is covered, we're good: 56 | ^\\s*exit\\(main\\(\\)\\)$ 57 | show_missing = True 58 | 59 | [html] 60 | directory = $TOP/coverage-html 61 | 62 | # vim:ft=dosini 63 | """ 64 | from __future__ import absolute_import 65 | from __future__ import print_function 66 | from __future__ import unicode_literals 67 | 68 | from distutils import log 69 | 70 | from setuptools import setup 71 | from setuptools.command.install import install as orig_install 72 | 73 | PTH = '''\ 74 | try: 75 | import coverage 76 | # coverage throws OSError when $PWD does not exist 77 | except (ImportError, OSError): 78 | pass 79 | else: 80 | coverage.process_startup() 81 | ''' 82 | 83 | DOC = __doc__ 84 | 85 | 86 | class Install(orig_install): 87 | """ 88 | default semantics for install.extra_path cause all installed modules to go 89 | into a directory whose name is equal to the contents of the .pth file. 90 | 91 | All that was necessary was to remove that one behavior to get what you'd 92 | generally want. 93 | """ 94 | # pylint:disable=no-member,attribute-defined-outside-init,access-member-before-definition 95 | 96 | def initialize_options(self): 97 | orig_install.initialize_options(self) 98 | name = self.distribution.metadata.name 99 | 100 | contents = 'import sys; exec(%r)\n' % PTH 101 | self.extra_path = (name, contents) 102 | 103 | def finalize_options(self): 104 | orig_install.finalize_options(self) 105 | 106 | from os.path import relpath, join 107 | install_suffix = relpath(self.install_lib, self.install_libbase) 108 | if install_suffix == '.': 109 | log.info('skipping install of .pth during easy-install') 110 | elif install_suffix == self.extra_path[1]: 111 | self.install_lib = self.install_libbase 112 | log.info( 113 | "will install .pth to '%s.pth'", 114 | join(self.install_lib, self.extra_path[0]), 115 | ) 116 | else: 117 | raise AssertionError( 118 | 'unexpected install_suffix', 119 | self.install_lib, self.install_libbase, install_suffix, 120 | ) 121 | 122 | 123 | def main(): 124 | """the entry point""" 125 | setup( 126 | name=str('coverage_enable_subprocess'), 127 | version='1.0', 128 | url="https://github.com/bukzor/coverage_enable_subprocess", 129 | license="MIT", 130 | author="Buck Evan", 131 | author_email="buck.2019@gmail.com", 132 | description="enable python coverage for subprocesses", 133 | long_description=DOC, 134 | zip_safe=False, 135 | classifiers=[ 136 | 'Programming Language :: Python :: 2.6', 137 | 'Programming Language :: Python :: 2.7', 138 | 'Programming Language :: Python :: 3.3', 139 | 'Programming Language :: Python :: 3.4', 140 | 'License :: OSI Approved :: MIT License', 141 | ], 142 | install_requires=[ 143 | 'coverage', 144 | ], 145 | cmdclass={ 146 | 'install': Install, 147 | }, 148 | options={ 149 | 'bdist_wheel': { 150 | 'universal': 1, 151 | }, 152 | }, 153 | ) 154 | 155 | 156 | if __name__ == '__main__': 157 | exit(main()) 158 | --------------------------------------------------------------------------------