Skip to content

Commit

Permalink
feat: added support for finding unexecuted namespace packages (#1387)
Browse files Browse the repository at this point in the history
* add support for namespace packages

* fixed typo

* update documentation

* fixed lint issues

* changed versionadded

* convert to config setting

* removed pure formatting changes

* code review changes

Co-authored-by: Ned Batchelder <ned@nedbatchelder.com>
  • Loading branch information
felix-ht and nedbat committed Nov 17, 2022
1 parent e76b5c7 commit 2644550
Show file tree
Hide file tree
Showing 7 changed files with 41 additions and 5 deletions.
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

0 comments on commit 2644550

Please sign in to comment.