Skip to content

Commit 2644550

Browse files
felix-htnedbat
andauthored
feat: added support for finding unexecuted namespace packages (#1387)
* 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 <[email protected]>
1 parent e76b5c7 commit 2644550

File tree

7 files changed

+41
-5
lines changed

7 files changed

+41
-5
lines changed

coverage/config.py

+2
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,7 @@ def __init__(self):
200200
self.fail_under = 0.0
201201
self.format = None
202202
self.ignore_errors = False
203+
self.include_namespace_packages = False
203204
self.report_include = None
204205
self.report_omit = None
205206
self.partial_always_list = DEFAULT_PARTIAL_ALWAYS[:]
@@ -375,6 +376,7 @@ def copy(self):
375376
('fail_under', 'report:fail_under', 'float'),
376377
('format', 'report:format', 'boolean'),
377378
('ignore_errors', 'report:ignore_errors', 'boolean'),
379+
('include_namespace_packages', 'report:include_namespace_packages', 'boolean'),
378380
('partial_always_list', 'report:partial_branches_always', 'regexlist'),
379381
('partial_list', 'report:partial_branches', 'regexlist'),
380382
('precision', 'report:precision', 'int'),

coverage/control.py

+1
Original file line numberDiff line numberDiff line change
@@ -530,6 +530,7 @@ def _init_for_start(self):
530530
self._inorout = InOrOut(
531531
warn=self._warn,
532532
debug=(self._debug if self._debug.should('trace') else None),
533+
include_namespace_packages=self.config.include_namespace_packages
533534
)
534535
self._inorout.configure(self.config)
535536
self._inorout.plugins = self._plugins

coverage/files.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -461,7 +461,7 @@ def map(self, path):
461461
return path
462462

463463

464-
def find_python_files(dirname):
464+
def find_python_files(dirname, include_namespace_packages):
465465
"""Yield all of the importable Python files in `dirname`, recursively.
466466
467467
To be importable, the files have to be in a directory with a __init__.py,
@@ -472,7 +472,8 @@ def find_python_files(dirname):
472472
473473
"""
474474
for i, (dirpath, dirnames, filenames) in enumerate(os.walk(dirname)):
475-
if i > 0 and '__init__.py' not in filenames:
475+
if (i > 0 and '__init__.py' not in filenames
476+
and not include_namespace_packages):
476477
# If a directory doesn't have __init__.py, then it isn't
477478
# importable and neither are its files
478479
del dirnames[:]

coverage/inorout.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -189,9 +189,10 @@ def add_coverage_paths(paths):
189189
class InOrOut:
190190
"""Machinery for determining what files to measure."""
191191

192-
def __init__(self, warn, debug):
192+
def __init__(self, warn, debug, include_namespace_packages):
193193
self.warn = warn
194194
self.debug = debug
195+
self.include_namespace_packages = include_namespace_packages
195196

196197
# The matchers for should_trace.
197198
self.source_match = None
@@ -565,7 +566,10 @@ def _find_executable_files(self, src_dir):
565566
Yield the file path, and the plugin name that handles the file.
566567
567568
"""
568-
py_files = ((py_file, None) for py_file in find_python_files(src_dir))
569+
py_files = (
570+
(py_file, None) for py_file in
571+
find_python_files(src_dir, self.include_namespace_packages)
572+
)
569573
plugin_files = self._find_plugin_files(src_dir)
570574

571575
for file_path, plugin_name in itertools.chain(py_files, plugin_files):

doc/config.rst

+8
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,14 @@ warning instead of an exception.
410410
See :ref:`source` for details.
411411

412412

413+
.. _config_include_namespace_packages:
414+
415+
[report] include_namespace_packages
416+
...................................
417+
418+
(boolean, default False) Include folders without an ``__init__.py`` in the
419+
coverage.
420+
413421
.. _config_report_omit:
414422

415423
[report] omit

tests/test_config.py

+3
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,8 @@ class ConfigFileTest(UsingModulesMixin, CoverageTest):
494494
skip_covered = TruE
495495
skip_empty =TruE
496496
497+
include_namespace_packages = TRUE
498+
497499
[{section}html]
498500
499501
directory = c:\\tricky\\dir.somewhere
@@ -589,6 +591,7 @@ def assert_config_settings_are_correct(self, cov):
589591
assert cov.config.get_plugin_options("plugins.another") == {}
590592
assert cov.config.json_show_contexts is True
591593
assert cov.config.json_pretty_print is True
594+
assert cov.config.include_namespace_packages is True
592595

593596
def test_config_file_settings(self):
594597
self.make_file(".coveragerc", self.LOTSA_SETTINGS.format(section=""))

tests/test_files.py

+18-1
Original file line numberDiff line numberDiff line change
@@ -585,13 +585,30 @@ def test_find_python_files(self):
585585
self.make_file("sub/ssub/~s.py") # nope: editor effluvia
586586
self.make_file("sub/lab/exp.py") # nope: no __init__.py
587587
self.make_file("sub/windows.pyw")
588-
py_files = set(find_python_files("sub"))
588+
py_files = set(find_python_files("sub", include_namespace_packages=False))
589589
self.assert_same_files(py_files, [
590590
"sub/a.py", "sub/b.py",
591591
"sub/ssub/__init__.py", "sub/ssub/s.py",
592592
"sub/windows.pyw",
593593
])
594594

595+
def test_find_python_files_include_namespace_packages(self):
596+
self.make_file("sub/a.py")
597+
self.make_file("sub/b.py")
598+
self.make_file("sub/x.c") # nope: not .py
599+
self.make_file("sub/ssub/__init__.py")
600+
self.make_file("sub/ssub/s.py")
601+
self.make_file("sub/ssub/~s.py") # nope: editor effluvia
602+
self.make_file("sub/lab/exp.py")
603+
self.make_file("sub/windows.pyw")
604+
py_files = set(find_python_files("sub", include_namespace_packages=True))
605+
self.assert_same_files(py_files, [
606+
"sub/a.py", "sub/b.py",
607+
"sub/ssub/__init__.py", "sub/ssub/s.py",
608+
"sub/lab/exp.py",
609+
"sub/windows.pyw",
610+
])
611+
595612

596613
@pytest.mark.skipif(not env.WINDOWS, reason="Only need to run Windows tests on Windows.")
597614
class WindowsFileTest(CoverageTest):

0 commit comments

Comments
 (0)