Skip to content

Commit 18629f1

Browse files
committed
add support for namespace packages
1 parent f9a74c7 commit 18629f1

File tree

6 files changed

+56
-8
lines changed

6 files changed

+56
-8
lines changed

coverage/cmdline.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,10 @@ class Opts:
111111
"Accepts shell-style wildcards, which must be quoted."
112112
),
113113
)
114+
include_namespace_packages = optparse.make_option(
115+
'', '--include_namespace_packages', action='store_true',
116+
help="Include folders without and __init__.py in the Coverage.",
117+
)
114118
pylib = optparse.make_option(
115119
'-L', '--pylib', action='store_true',
116120
help=(
@@ -223,6 +227,9 @@ class Opts:
223227
)
224228

225229

230+
231+
232+
226233
class CoverageOptionParser(optparse.OptionParser):
227234
"""Base OptionParser for coverage.py.
228235
@@ -248,6 +255,7 @@ def __init__(self, *args, **kwargs):
248255
help=None,
249256
ignore_errors=None,
250257
include=None,
258+
include_namespace_packages=False,
251259
keep=None,
252260
module=None,
253261
omit=None,
@@ -360,6 +368,7 @@ def get_prog_name(self):
360368
Opts.input_datafile,
361369
Opts.ignore_errors,
362370
Opts.include,
371+
Opts.include_namespace_packages,
363372
Opts.omit,
364373
] + GLOBAL_ARGS,
365374
usage="[options] [modules]",
@@ -426,6 +435,7 @@ def get_prog_name(self):
426435
Opts.fail_under,
427436
Opts.ignore_errors,
428437
Opts.include,
438+
Opts.include_namespace_packages,
429439
Opts.omit,
430440
Opts.precision,
431441
Opts.quiet,
@@ -451,6 +461,7 @@ def get_prog_name(self):
451461
Opts.fail_under,
452462
Opts.ignore_errors,
453463
Opts.include,
464+
Opts.include_namespace_packages,
454465
Opts.omit,
455466
Opts.output_json,
456467
Opts.json_pretty_print,
@@ -468,6 +479,7 @@ def get_prog_name(self):
468479
Opts.fail_under,
469480
Opts.ignore_errors,
470481
Opts.include,
482+
Opts.include_namespace_packages,
471483
Opts.output_lcov,
472484
Opts.omit,
473485
Opts.quiet,
@@ -484,6 +496,7 @@ def get_prog_name(self):
484496
Opts.fail_under,
485497
Opts.ignore_errors,
486498
Opts.include,
499+
Opts.include_namespace_packages,
487500
Opts.omit,
488501
Opts.precision,
489502
Opts.sort,
@@ -505,6 +518,7 @@ def get_prog_name(self):
505518
Opts.context,
506519
Opts.output_datafile,
507520
Opts.include,
521+
Opts.include_namespace_packages,
508522
Opts.module,
509523
Opts.omit,
510524
Opts.pylib,
@@ -523,6 +537,7 @@ def get_prog_name(self):
523537
Opts.fail_under,
524538
Opts.ignore_errors,
525539
Opts.include,
540+
Opts.include_namespace_packages,
526541
Opts.omit,
527542
Opts.output_xml,
528543
Opts.quiet,
@@ -641,6 +656,7 @@ def command_line(self, argv):
641656
source=source,
642657
omit=omit,
643658
include=include,
659+
include_namespace_packages=options.include_namespace_packages,
644660
debug=debug,
645661
concurrency=concurrency,
646662
check_preimported=True,

coverage/control.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ def __init__(
109109
auto_data=False, timid=None, branch=None, config_file=True,
110110
source=None, source_pkgs=None, omit=None, include=None, debug=None,
111111
concurrency=None, check_preimported=False, context=None,
112-
messages=False,
112+
messages=False, include_namespace_packages=False
113113
): # pylint: disable=too-many-arguments
114114
"""
115115
Many of these arguments duplicate and override values that can be
@@ -183,6 +183,10 @@ def __init__(
183183
If `messages` is true, some messages will be printed to stdout
184184
indicating what is happening.
185185
186+
If `include_namespace_packages` is true folders without an
187+
__init__.py file will be included in the coverage
188+
189+
186190
.. versionadded:: 4.0
187191
The `concurrency` parameter.
188192
@@ -198,6 +202,9 @@ def __init__(
198202
.. versionadded:: 6.0
199203
The `messages` parameter.
200204
205+
.. versionadded:: 6.4
206+
The `include_namespace_packages` parameter.
207+
201208
"""
202209
# data_file=None means no disk file at all. data_file missing means
203210
# use the value from the config file.
@@ -212,6 +219,7 @@ def __init__(
212219

213220
self._auto_load = self._auto_save = auto_data
214221
self._data_suffix_specified = data_suffix
222+
self._include_namespace_packages = include_namespace_packages
215223

216224
# Is it ok for no data to be collected?
217225
self._warn_no_data = True
@@ -526,6 +534,7 @@ def _init_for_start(self):
526534
self._inorout = InOrOut(
527535
warn=self._warn,
528536
debug=(self._debug if self._debug.should('trace') else None),
537+
include_namespace_packages=self._include_namespace_packages
529538
)
530539
self._inorout.configure(self.config)
531540
self._inorout.plugins = self._plugins

coverage/files.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -395,7 +395,7 @@ def map(self, path):
395395
return path
396396

397397

398-
def find_python_files(dirname):
398+
def find_python_files(dirname, include_namespace_packages):
399399
"""Yield all of the importable Python files in `dirname`, recursively.
400400
401401
To be importable, the files have to be in a directory with a __init__.py,
@@ -406,7 +406,8 @@ def find_python_files(dirname):
406406
407407
"""
408408
for i, (dirpath, dirnames, filenames) in enumerate(os.walk(dirname)):
409-
if i > 0 and '__init__.py' not in filenames:
409+
if (i > 0 and '__init__.py' not in filenames
410+
and not include_namespace_packages):
410411
# If a directory doesn't have __init__.py, then it isn't
411412
# importable and neither are its files
412413
del dirnames[:]

coverage/inorout.py

Lines changed: 6 additions & 2 deletions
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):

tests/test_cmdline.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,9 @@ class BaseCmdLineTest(CoverageTest):
6161
_defaults.Coverage(
6262
data_file=DEFAULT_DATAFILE,
6363
cover_pylib=None, data_suffix=None, timid=None, branch=None,
64-
config_file=True, source=None, include=None, omit=None, debug=None,
65-
concurrency=None, check_preimported=True, context=None, messages=True,
64+
config_file=True, source=None, include=None, include_namespace_packages=False,
65+
omit=None, debug=None, concurrency=None, check_preimported=True, context=None,
66+
messages=True,
6667
)
6768

6869
DEFAULT_KWARGS = {name: kw for name, _, kw in _defaults.mock_calls}

tests/test_files.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -413,13 +413,30 @@ def test_find_python_files(self):
413413
self.make_file("sub/ssub/~s.py") # nope: editor effluvia
414414
self.make_file("sub/lab/exp.py") # nope: no __init__.py
415415
self.make_file("sub/windows.pyw")
416-
py_files = set(find_python_files("sub"))
416+
py_files = set(find_python_files("sub", False))
417417
self.assert_same_files(py_files, [
418418
"sub/a.py", "sub/b.py",
419419
"sub/ssub/__init__.py", "sub/ssub/s.py",
420420
"sub/windows.pyw",
421421
])
422422

423+
def test_find_python_files_include_namespace_packages(self):
424+
self.make_file("sub/a.py")
425+
self.make_file("sub/b.py")
426+
self.make_file("sub/x.c") # nope: not .py
427+
self.make_file("sub/ssub/__init__.py")
428+
self.make_file("sub/ssub/s.py")
429+
self.make_file("sub/ssub/~s.py") # nope: editor effluvia
430+
self.make_file("sub/lab/exp.py")
431+
self.make_file("sub/windows.pyw")
432+
py_files = set(find_python_files("sub", True))
433+
self.assert_same_files(py_files, [
434+
"sub/a.py", "sub/b.py",
435+
"sub/ssub/__init__.py", "sub/ssub/s.py",
436+
"sub/lab/exp.py",
437+
"sub/windows.pyw",
438+
])
439+
423440

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

0 commit comments

Comments
 (0)