summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--coverage/config.py2
-rw-r--r--coverage/control.py1
-rw-r--r--coverage/files.py5
-rw-r--r--coverage/inorout.py8
-rw-r--r--doc/config.rst8
-rw-r--r--tests/test_config.py3
-rw-r--r--tests/test_files.py19
7 files changed, 41 insertions, 5 deletions
diff --git a/coverage/config.py b/coverage/config.py
index 7b765edf..994154f6 100644
--- a/coverage/config.py
+++ b/coverage/config.py
@@ -200,6 +200,7 @@ class CoverageConfig:
self.fail_under = 0.0
self.format = None
self.ignore_errors = False
+ self.include_namespace_packages = False
self.report_include = None
self.report_omit = None
self.partial_always_list = DEFAULT_PARTIAL_ALWAYS[:]
@@ -375,6 +376,7 @@ class CoverageConfig:
('fail_under', 'report:fail_under', 'float'),
('format', 'report:format', 'boolean'),
('ignore_errors', 'report:ignore_errors', 'boolean'),
+ ('include_namespace_packages', 'report:include_namespace_packages', 'boolean'),
('partial_always_list', 'report:partial_branches_always', 'regexlist'),
('partial_list', 'report:partial_branches', 'regexlist'),
('precision', 'report:precision', 'int'),
diff --git a/coverage/control.py b/coverage/control.py
index 91ad5a78..a955c283 100644
--- a/coverage/control.py
+++ b/coverage/control.py
@@ -530,6 +530,7 @@ class Coverage:
self._inorout = InOrOut(
warn=self._warn,
debug=(self._debug if self._debug.should('trace') else None),
+ include_namespace_packages=self.config.include_namespace_packages
)
self._inorout.configure(self.config)
self._inorout.plugins = self._plugins
diff --git a/coverage/files.py b/coverage/files.py
index bfd808ff..8be292f3 100644
--- a/coverage/files.py
+++ b/coverage/files.py
@@ -461,7 +461,7 @@ class PathAliases:
return path
-def find_python_files(dirname):
+def find_python_files(dirname, include_namespace_packages):
"""Yield all of the importable Python files in `dirname`, recursively.
To be importable, the files have to be in a directory with a __init__.py,
@@ -472,7 +472,8 @@ def find_python_files(dirname):
"""
for i, (dirpath, dirnames, filenames) in enumerate(os.walk(dirname)):
- if i > 0 and '__init__.py' not in filenames:
+ if (i > 0 and '__init__.py' not in filenames
+ and not include_namespace_packages):
# If a directory doesn't have __init__.py, then it isn't
# importable and neither are its files
del dirnames[:]
diff --git a/coverage/inorout.py b/coverage/inorout.py
index 2e534c85..0d3f6d67 100644
--- a/coverage/inorout.py
+++ b/coverage/inorout.py
@@ -189,9 +189,10 @@ def add_coverage_paths(paths):
class InOrOut:
"""Machinery for determining what files to measure."""
- def __init__(self, warn, debug):
+ def __init__(self, warn, debug, include_namespace_packages):
self.warn = warn
self.debug = debug
+ self.include_namespace_packages = include_namespace_packages
# The matchers for should_trace.
self.source_match = None
@@ -565,7 +566,10 @@ class InOrOut:
Yield the file path, and the plugin name that handles the file.
"""
- py_files = ((py_file, None) for py_file in find_python_files(src_dir))
+ py_files = (
+ (py_file, None) for py_file in
+ find_python_files(src_dir, self.include_namespace_packages)
+ )
plugin_files = self._find_plugin_files(src_dir)
for file_path, plugin_name in itertools.chain(py_files, plugin_files):
diff --git a/doc/config.rst b/doc/config.rst
index 83f64a4d..b5112982 100644
--- a/doc/config.rst
+++ b/doc/config.rst
@@ -410,6 +410,14 @@ warning instead of an exception.
See :ref:`source` for details.
+.. _config_include_namespace_packages:
+
+[report] include_namespace_packages
+...................................
+
+(boolean, default False) Include folders without an ``__init__.py`` in the
+coverage.
+
.. _config_report_omit:
[report] omit
diff --git a/tests/test_config.py b/tests/test_config.py
index 444719fd..5f8a0547 100644
--- a/tests/test_config.py
+++ b/tests/test_config.py
@@ -494,6 +494,8 @@ class ConfigFileTest(UsingModulesMixin, CoverageTest):
skip_covered = TruE
skip_empty =TruE
+ include_namespace_packages = TRUE
+
[{section}html]
directory = c:\\tricky\\dir.somewhere
@@ -589,6 +591,7 @@ class ConfigFileTest(UsingModulesMixin, CoverageTest):
assert cov.config.get_plugin_options("plugins.another") == {}
assert cov.config.json_show_contexts is True
assert cov.config.json_pretty_print is True
+ assert cov.config.include_namespace_packages is True
def test_config_file_settings(self):
self.make_file(".coveragerc", self.LOTSA_SETTINGS.format(section=""))
diff --git a/tests/test_files.py b/tests/test_files.py
index 561b961d..a69d1a4b 100644
--- a/tests/test_files.py
+++ b/tests/test_files.py
@@ -585,13 +585,30 @@ class FindPythonFilesTest(CoverageTest):
self.make_file("sub/ssub/~s.py") # nope: editor effluvia
self.make_file("sub/lab/exp.py") # nope: no __init__.py
self.make_file("sub/windows.pyw")
- py_files = set(find_python_files("sub"))
+ py_files = set(find_python_files("sub", include_namespace_packages=False))
self.assert_same_files(py_files, [
"sub/a.py", "sub/b.py",
"sub/ssub/__init__.py", "sub/ssub/s.py",
"sub/windows.pyw",
])
+ def test_find_python_files_include_namespace_packages(self):
+ self.make_file("sub/a.py")
+ self.make_file("sub/b.py")
+ self.make_file("sub/x.c") # nope: not .py
+ self.make_file("sub/ssub/__init__.py")
+ self.make_file("sub/ssub/s.py")
+ self.make_file("sub/ssub/~s.py") # nope: editor effluvia
+ self.make_file("sub/lab/exp.py")
+ self.make_file("sub/windows.pyw")
+ py_files = set(find_python_files("sub", include_namespace_packages=True))
+ self.assert_same_files(py_files, [
+ "sub/a.py", "sub/b.py",
+ "sub/ssub/__init__.py", "sub/ssub/s.py",
+ "sub/lab/exp.py",
+ "sub/windows.pyw",
+ ])
+
@pytest.mark.skipif(not env.WINDOWS, reason="Only need to run Windows tests on Windows.")
class WindowsFileTest(CoverageTest):