Skip to content

Commit e263031

Browse files
committed
Add support for coverage 4.2.1+
Includes tests. Fixes #1
1 parent 141e5b1 commit e263031

File tree

10 files changed

+337
-13
lines changed

10 files changed

+337
-13
lines changed

.gitignore

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Files that can appear anywhere in the tree.
2+
*.pyc
3+
*.pyo
4+
*$py.class
5+
*.pyd
6+
*.so
7+
*.bak
8+
.coverage
9+
.coverage.*
10+
coverage.xml
11+
.metacov
12+
.metacov.*
13+
*.swp
14+
15+
# Stuff generated by editors.
16+
.idea/
17+
.vimtags
18+
19+
# Stuff in the root.
20+
build
21+
*.egg-info
22+
dist
23+
htmlcov
24+
MANIFEST
25+
setuptools-*.egg
26+
.tox*
27+
.noseids
28+
.cache
29+
.pytest_cache
30+
.hypothesis
31+
32+
# Stuff in the test directory.
33+
zipmods.zip
34+
35+
# Stuff in the doc directory.
36+
_build
37+
_spell
38+
sample_html_beta
39+
40+
# Stuff in the ci directory.
41+
*.token
42+
43+
# OS junk
44+
.DS_Store

.travis.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
dist: xenial
2+
language: python
3+
4+
cache: pip
5+
python:
6+
- '2.7'
7+
- '3.4'
8+
- '3.5'
9+
- '3.6'
10+
- '3.7'
11+
- 'pypy2.7-6.0'
12+
- 'pypy3.5-6.0'
13+
14+
install:
15+
- pip install tox-travis
16+
script:
17+
- tox

CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
Changes
2+
-------
3+
4+
* v0.3.0
5+
- Support for coverage 4.4.2+
6+
- All logic moved into PluginBase and ConfigReloadPlugin
7+
which is registered as a plugin using either noop or
8+
configurer plugin types.
9+
- Tests for all coverage patch versions 4.0 - 5.0a,
10+
CPython 2.7 and 3.4 - 3.7 and PyPy 6.0
11+
- Add LICENSE to distributions
12+
13+
* v0.2.0
14+
- Python 2 support
15+
16+
* v0.1.0
17+
- Initial version

MANIFEST.in

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
include LICENSE
2+
include tox.ini
3+
include tests/*.py

README.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,14 @@
11
# coverage config reload plugin
22

3+
This is a plugin for coveragepy that forces it to reload its
4+
configuration. Typically this is used when the config file needs to
5+
be dynamic, or the environment which is used when parsing the
6+
config file.
7+
38
Place as the last plugin to be loaded, this plugin will
4-
reloads the configuration files after other plugins have been loaded.
9+
reloads the configuration files after other plugins have been loaded
10+
and performed their changes to the configuration file or environment.
11+
12+
This plugin was developed as a helper for
13+
[coverage-env-plugin](https://github.com/jayvdb/coverage_env_plugin/),
14+
so it does not need worry about the internals of coveragepy.

coverage_config_reload_plugin.py

Lines changed: 116 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,137 @@
1-
"""Coverage Config reload Plugin"""
1+
"""Coverage Config reload Plugin
2+
3+
config.from_file was broken in coveragepy 4.4.1
4+
https://bitbucket.org/ned/coveragepy/issues/616
5+
6+
add_configurer was added in coveragepy 4.5, but also included
7+
a few easter eggs to make this plugin a tiny bit harder.
8+
"""
29
import sys
310

4-
__version__ = '0.2.0'
11+
import coverage
12+
from coverage.backward import configparser
13+
14+
__version__ = '0.3.0'
15+
16+
17+
class PluginBase(coverage.CoveragePlugin):
18+
19+
@classmethod
20+
def _register(cls, reg, options, now=True):
21+
config = get_coverage_config()
22+
plugin = cls(reg, options, config, now)
23+
24+
# Try using coverage 4.5
25+
try:
26+
reg.add_configurer(plugin)
27+
return plugin
28+
except AttributeError:
29+
pass
30+
31+
# Fallback to using noop added v4.0
32+
try:
33+
reg.add_noop(plugin)
34+
except Exception:
35+
reg._add_plugin(plugin, None)
36+
return plugin
37+
38+
def __init__(self, reg, options, config=None, now=False):
39+
self._name = self.__class__.__name__.lower().replace('plugin', '')
40+
self.reg = reg
41+
self.options = options
42+
self.config = config
43+
self.done = False
44+
self.status = None
45+
if now:
46+
self.do()
47+
48+
def configure(self, config):
49+
if isinstance(config, coverage.Coverage):
50+
config = config.config
51+
self.config = config
52+
53+
self.do()
54+
55+
def do(self):
56+
if not self.done:
57+
self._do()
58+
self.done = True
59+
60+
def sys_info(self):
61+
if not self.done:
62+
try:
63+
self.do()
64+
except:
65+
pass
66+
return [(self._name,
67+
str(self.status) if self.status else str(self.done))]
68+
69+
70+
class ConfigReloadPlugin(PluginBase):
71+
72+
def _do(self):
73+
read_config_files(self.config)
574

675

776
def get_coverage_config():
877
"""Get coverage config from stack."""
978
# Stack
1079
# 1. get_coverage_config (i.e. this function)
11-
# 2. coverage_init
12-
# 3. load_plugins
13-
frame = sys._getframe(2)
80+
# 2. PluginBase._register
81+
# 3. coverage_init
82+
# 4. load_plugins
83+
frame = sys._getframe(3)
1484
config = frame.f_locals['config']
1585
return config
1686

1787

88+
def read_config_file(config, filename):
89+
if filename == '.coveragerc':
90+
rc_file = True
91+
elif filename in ('tox.ini', 'setup.cfg'):
92+
rc_file = False
93+
else:
94+
# Very likely all other config files are 'own rc' files.
95+
# However the cost here is minimal for correctness and this
96+
# reduces the chance it can be broken by future childishness :P
97+
parser = configparser.RawConfigParser()
98+
parser.read(filename)
99+
sections = parser.sections()
100+
rc_file = not any(section.startswith('coverage:')
101+
for section in sections)
102+
103+
# Try the old pre 4.4.1 invocation
104+
try:
105+
return config.from_file(
106+
filename,
107+
section_prefix='' if rc_file else 'coverage:'
108+
)
109+
except TypeError:
110+
pass
111+
112+
# coverage 5+
113+
if hasattr(config, 'config_file'):
114+
config.config_file = filename
115+
116+
# coverage 4.4.1+
117+
return config.from_file(filename, our_file=rc_file)
118+
119+
18120
def read_config_files(config):
121+
if not hasattr(config, 'config_files'):
122+
if config.config_file:
123+
rv = read_config_file(config, config.config_file)
124+
assert rv is True
125+
return
126+
19127
config_filenames = config.config_files[:]
20128
for filename in config_filenames:
21-
prefix = '' if filename == '.coveragerc' else 'coverage:'
22-
config.from_file(filename, section_prefix=prefix)
129+
rv = read_config_file(config, filename)
130+
assert rv is True
23131

24132
# restore original as from_file appends to the config_files list
25133
config.config_files = config_filenames
26134

27135

28136
def coverage_init(reg, options):
29-
config = get_coverage_config()
30-
read_config_files(config)
137+
ConfigReloadPlugin._register(reg, options)

setup.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,17 @@
1111
Programming Language :: Python :: 3.4
1212
Programming Language :: Python :: 3.5
1313
Programming Language :: Python :: 3.6
14+
Programming Language :: Python :: 3.7
1415
Programming Language :: Python :: Implementation :: CPython
1516
Programming Language :: Python :: Implementation :: PyPy
1617
Topic :: Software Development :: Quality Assurance
1718
Topic :: Software Development :: Testing
18-
Development Status :: 3 - Alpha
19+
Development Status :: 4 - Beta
1920
"""
2021

2122
setup(
22-
name='coverage_config_reload_plugin',
23-
version='0.2.0',
23+
name='coverage-config-reload-plugin',
24+
version='0.3.0',
2425
description='coverage.py config reload plugin',
2526
author='John Vandenberg',
2627
author_email='[email protected]',
@@ -29,6 +30,8 @@
2930
install_requires=[
3031
'coverage >= 4.0',
3132
],
33+
tests_require=['unittest-mixins'],
34+
test_suite='tests',
3235
license='MIT License',
3336
classifiers=classifiers.splitlines(),
3437
)

tests/__init__.py

Whitespace-only changes.

tests/test_reload.py

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
from distutils.version import LooseVersion
2+
from unittest import TestCase
3+
4+
from unittest_mixins import TempDirMixin
5+
6+
7+
from coverage import __version__ as coverage_version
8+
from coverage.backward import StringIO
9+
from coverage.control import Coverage
10+
11+
12+
class ConfigReloadPluginTest(TempDirMixin, TestCase):
13+
"""Test plugin through the Coverage class."""
14+
15+
def test_no_reload_plugin(self):
16+
debug_out = StringIO()
17+
cov = Coverage(debug=['sys'])
18+
cov._debug_file = debug_out
19+
cov.set_option('run:plugins', [])
20+
cov.start()
21+
cov.stop()
22+
23+
out_lines = [line.strip() for line in debug_out.getvalue().splitlines()]
24+
self.assertIn('plugins.file_tracers: -none-', out_lines)
25+
26+
expected_end = [
27+
'-- end -------------------------------------------------------',
28+
]
29+
self.assertEqual(expected_end, out_lines[-len(expected_end):])
30+
31+
if LooseVersion(coverage_version) >= LooseVersion('4.6'):
32+
self.assertIn('plugins.configurers: -none-', out_lines)
33+
34+
assert cov.config.get_option('report:ignore_errors') is False
35+
36+
def test_plugin_init(self):
37+
self.make_file('coveragerc_test_config', '')
38+
39+
debug_out = StringIO()
40+
cov = Coverage(config_file='coveragerc_test_config', debug=['sys'])
41+
cov._debug_file = debug_out
42+
cov.set_option('run:plugins', ['coverage_config_reload_plugin'])
43+
cov.start()
44+
cov.stop()
45+
46+
out_lines = [line.strip() for line in debug_out.getvalue().splitlines()]
47+
self.assertIn('plugins.file_tracers: -none-', out_lines)
48+
49+
expected_end = [
50+
'-- sys: coverage_config_reload_plugin.ConfigReloadPlugin -----',
51+
'configreload: True',
52+
'-- end -------------------------------------------------------',
53+
]
54+
self.assertEqual(expected_end, out_lines[-len(expected_end):])
55+
56+
if LooseVersion(coverage_version) >= LooseVersion('4.6'):
57+
self.assertIn('plugins.configurers: coverage_config_reload_plugin.ConfigReloadPlugin', out_lines)
58+
59+
def _check_config(self, filename, own_rc):
60+
section = 'report' if own_rc else 'coverage:report'
61+
62+
# Force own rc for pre 4.4.1
63+
if LooseVersion(coverage_version) < LooseVersion('4.4.1'):
64+
self.make_file(filename, """\
65+
[report]
66+
ignore_errors = true
67+
""")
68+
else:
69+
self.make_file(filename, """\
70+
[{}]
71+
ignore_errors = true
72+
""".format(section))
73+
74+
debug_out = StringIO()
75+
cov = Coverage(config_file=filename, debug=['sys'])
76+
assert cov.config.get_option('report:ignore_errors') is True
77+
cov._debug_file = debug_out
78+
79+
self.make_file(filename, """\
80+
[{}]
81+
ignore_errors = off
82+
""".format(section))
83+
84+
cov.set_option('run:plugins', ['coverage_config_reload_plugin'])
85+
cov.start()
86+
cov.stop()
87+
88+
assert cov.config.get_option('report:ignore_errors') is False
89+
90+
def test_reload_config_rc(self):
91+
self._check_config('coveragerc_test_config', True)
92+
93+
def test_reload_config_not_rc(self):
94+
self._check_config('coveragerc_test_config', False)
95+
96+
def test_reload_config_toxini(self):
97+
self._check_config('tox.ini', False)
98+
99+
def test_reload_config_setupcfg(self):
100+
self._check_config('setup.cfg', False)
101+
102+
def test_reload_config_coveragerc(self):
103+
self._check_config('.coveragerc', True)

0 commit comments

Comments
 (0)