Skip to content

Commit

Permalink
add support for namespace packages
Browse files Browse the repository at this point in the history
  • Loading branch information
felix-ht committed May 30, 2022
1 parent f9a74c7 commit 18629f1
Show file tree
Hide file tree
Showing 6 changed files with 56 additions and 8 deletions.
16 changes: 16 additions & 0 deletions coverage/cmdline.py
Expand Up @@ -111,6 +111,10 @@ class Opts:
"Accepts shell-style wildcards, which must be quoted."
),
)
include_namespace_packages = optparse.make_option(
'', '--include_namespace_packages', action='store_true',
help="Include folders without and __init__.py in the Coverage.",
)
pylib = optparse.make_option(
'-L', '--pylib', action='store_true',
help=(
Expand Down Expand Up @@ -223,6 +227,9 @@ class Opts:
)





class CoverageOptionParser(optparse.OptionParser):
"""Base OptionParser for coverage.py.
Expand All @@ -248,6 +255,7 @@ def __init__(self, *args, **kwargs):
help=None,
ignore_errors=None,
include=None,
include_namespace_packages=False,
keep=None,
module=None,
omit=None,
Expand Down Expand Up @@ -360,6 +368,7 @@ def get_prog_name(self):
Opts.input_datafile,
Opts.ignore_errors,
Opts.include,
Opts.include_namespace_packages,
Opts.omit,
] + GLOBAL_ARGS,
usage="[options] [modules]",
Expand Down Expand Up @@ -426,6 +435,7 @@ def get_prog_name(self):
Opts.fail_under,
Opts.ignore_errors,
Opts.include,
Opts.include_namespace_packages,
Opts.omit,
Opts.precision,
Opts.quiet,
Expand All @@ -451,6 +461,7 @@ def get_prog_name(self):
Opts.fail_under,
Opts.ignore_errors,
Opts.include,
Opts.include_namespace_packages,
Opts.omit,
Opts.output_json,
Opts.json_pretty_print,
Expand All @@ -468,6 +479,7 @@ def get_prog_name(self):
Opts.fail_under,
Opts.ignore_errors,
Opts.include,
Opts.include_namespace_packages,
Opts.output_lcov,
Opts.omit,
Opts.quiet,
Expand All @@ -484,6 +496,7 @@ def get_prog_name(self):
Opts.fail_under,
Opts.ignore_errors,
Opts.include,
Opts.include_namespace_packages,
Opts.omit,
Opts.precision,
Opts.sort,
Expand All @@ -505,6 +518,7 @@ def get_prog_name(self):
Opts.context,
Opts.output_datafile,
Opts.include,
Opts.include_namespace_packages,
Opts.module,
Opts.omit,
Opts.pylib,
Expand All @@ -523,6 +537,7 @@ def get_prog_name(self):
Opts.fail_under,
Opts.ignore_errors,
Opts.include,
Opts.include_namespace_packages,
Opts.omit,
Opts.output_xml,
Opts.quiet,
Expand Down Expand Up @@ -641,6 +656,7 @@ def command_line(self, argv):
source=source,
omit=omit,
include=include,
include_namespace_packages=options.include_namespace_packages,
debug=debug,
concurrency=concurrency,
check_preimported=True,
Expand Down
11 changes: 10 additions & 1 deletion coverage/control.py
Expand Up @@ -109,7 +109,7 @@ def __init__(
auto_data=False, timid=None, branch=None, config_file=True,
source=None, source_pkgs=None, omit=None, include=None, debug=None,
concurrency=None, check_preimported=False, context=None,
messages=False,
messages=False, include_namespace_packages=False
): # pylint: disable=too-many-arguments
"""
Many of these arguments duplicate and override values that can be
Expand Down Expand Up @@ -183,6 +183,10 @@ def __init__(
If `messages` is true, some messages will be printed to stdout
indicating what is happening.
If `include_namespace_packages` is true folders without an
__init__.py file will be included in the coverage
.. versionadded:: 4.0
The `concurrency` parameter.
Expand All @@ -198,6 +202,9 @@ def __init__(
.. versionadded:: 6.0
The `messages` parameter.
.. versionadded:: 6.4
The `include_namespace_packages` parameter.
"""
# data_file=None means no disk file at all. data_file missing means
# use the value from the config file.
Expand All @@ -212,6 +219,7 @@ def __init__(

self._auto_load = self._auto_save = auto_data
self._data_suffix_specified = data_suffix
self._include_namespace_packages = include_namespace_packages

# Is it ok for no data to be collected?
self._warn_no_data = True
Expand Down Expand Up @@ -526,6 +534,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._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 @@ -395,7 +395,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 @@ -406,7 +406,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
5 changes: 3 additions & 2 deletions tests/test_cmdline.py
Expand Up @@ -61,8 +61,9 @@ class BaseCmdLineTest(CoverageTest):
_defaults.Coverage(
data_file=DEFAULT_DATAFILE,
cover_pylib=None, data_suffix=None, timid=None, branch=None,
config_file=True, source=None, include=None, omit=None, debug=None,
concurrency=None, check_preimported=True, context=None, messages=True,
config_file=True, source=None, include=None, include_namespace_packages=False,
omit=None, debug=None, concurrency=None, check_preimported=True, context=None,
messages=True,
)

DEFAULT_KWARGS = {name: kw for name, _, kw in _defaults.mock_calls}
Expand Down
19 changes: 18 additions & 1 deletion tests/test_files.py
Expand Up @@ -413,13 +413,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", 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", 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 18629f1

Please sign in to comment.