summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatt Davis <nitzmahone@users.noreply.github.com>2020-09-27 22:29:24 -0700
committerGitHub <noreply@github.com>2020-09-28 00:29:24 -0500
commit07a9de12472dde042c670a7e48eeee396207856b (patch)
tree83191ca7887a319cd763628863104df2caa7fbea
parent68278f36fd616293e11440dde9f5c9519cb09b4c (diff)
downloadansible-07a9de12472dde042c670a7e48eeee396207856b.tar.gz
fix coverage output from synthetic packages (#71727) (#71748)
* fix coverage output from synthetic packages * synthetic packages (eg, implicit collection packages without `__init__.py`) were always created at runtime with empty string source, which was compiled to a code object and exec'd during the package load. When run with code coverage, it created a bogus coverage entry (since the `__synthetic__`-suffixed `__file__` entry didn't exist on disk). * modified collection loader `get_code` to preserve the distinction between `None` (eg synthetic package) and empty string (eg empty `__init__.py`) values from `get_source`, and to return `None` when the source is `None`. This allows the package loader to skip `exec`ing things that truly have no source file on disk, thus not creating bogus coverage entries, while preserving behavior and coverage reporting for empty package inits that actually exist. * add unit test (cherry picked from commit e813b0151c3b4baec8368fc0d099e7f19be22144)
-rw-r--r--changelogs/fragments/fix_bogus_coverage.yml2
-rw-r--r--lib/ansible/utils/collection_loader/_collection_finder.py11
-rw-r--r--test/units/utils/collection_loader/test_collection_loader.py16
3 files changed, 26 insertions, 3 deletions
diff --git a/changelogs/fragments/fix_bogus_coverage.yml b/changelogs/fragments/fix_bogus_coverage.yml
new file mode 100644
index 0000000000..c60ada5f6e
--- /dev/null
+++ b/changelogs/fragments/fix_bogus_coverage.yml
@@ -0,0 +1,2 @@
+bugfixes:
+- collection loader - fix bogus code coverage entries for synthetic packages
diff --git a/lib/ansible/utils/collection_loader/_collection_finder.py b/lib/ansible/utils/collection_loader/_collection_finder.py
index bc099b262f..99d5ddc916 100644
--- a/lib/ansible/utils/collection_loader/_collection_finder.py
+++ b/lib/ansible/utils/collection_loader/_collection_finder.py
@@ -355,7 +355,9 @@ class _AnsibleCollectionPkgLoaderBase:
with self._new_or_existing_module(fullname, **module_attrs) as module:
# execute the module's code in its namespace
- exec(self.get_code(fullname), module.__dict__)
+ code_obj = self.get_code(fullname)
+ if code_obj is not None: # things like NS packages that can't have code on disk will return None
+ exec(code_obj, module.__dict__)
return module
@@ -428,8 +430,11 @@ class _AnsibleCollectionPkgLoaderBase:
filename = '<string>'
source_code = self.get_source(fullname)
- if not source_code:
- source_code = ''
+
+ # for things like synthetic modules that really have no source on disk, don't return a code object at all
+ # vs things like an empty package init (which has an empty string source on disk)
+ if source_code is None:
+ return None
self._compiled_code = compile(source=source_code, filename=filename, mode='exec', flags=0, dont_inherit=True)
diff --git a/test/units/utils/collection_loader/test_collection_loader.py b/test/units/utils/collection_loader/test_collection_loader.py
index 496dc5416e..6488188c08 100644
--- a/test/units/utils/collection_loader/test_collection_loader.py
+++ b/test/units/utils/collection_loader/test_collection_loader.py
@@ -594,6 +594,22 @@ def test_bogus_imports():
import_module(bogus_import)
+def test_empty_vs_no_code():
+ finder = get_default_finder()
+ reset_collections_loader_state(finder)
+
+ from ansible_collections.testns import testcoll # synthetic package with no code on disk
+ from ansible_collections.testns.testcoll.plugins import module_utils # real package with empty code file
+
+ # ensure synthetic packages have no code object at all (prevent bogus coverage entries)
+ assert testcoll.__loader__.get_source(testcoll.__name__) is None
+ assert testcoll.__loader__.get_code(testcoll.__name__) is None
+
+ # ensure empty package inits do have a code object
+ assert module_utils.__loader__.get_source(module_utils.__name__) == b''
+ assert module_utils.__loader__.get_code(module_utils.__name__) is not None
+
+
def test_finder_playbook_paths():
finder = get_default_finder()
reset_collections_loader_state(finder)