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

Added support for namespace packages #1387

Merged
merged 9 commits into from Nov 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions coverage/config.py
Expand Up @@ -200,6 +200,7 @@ def __init__(self):
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[:]
Expand Down Expand Up @@ -375,6 +376,7 @@ def copy(self):
('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'),
Expand Down
1 change: 1 addition & 0 deletions coverage/control.py
Expand Up @@ -530,6 +530,7 @@ def _init_for_start(self):
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
Expand Down
5 changes: 3 additions & 2 deletions coverage/files.py
Expand Up @@ -461,7 +461,7 @@ def map(self, path):
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,
Expand All @@ -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[:]
Expand Down
8 changes: 6 additions & 2 deletions coverage/inorout.py
Expand Up @@ -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
Expand Down Expand Up @@ -565,7 +566,10 @@ def _find_executable_files(self, src_dir):
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):
Expand Down
8 changes: 8 additions & 0 deletions doc/config.rst
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions tests/test_config.py
Expand Up @@ -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
Expand Down Expand Up @@ -589,6 +591,7 @@ def assert_config_settings_are_correct(self, cov):
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=""))
Expand Down
19 changes: 18 additions & 1 deletion tests/test_files.py
Expand Up @@ -585,13 +585,30 @@ def test_find_python_files(self):
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):
Expand Down