test: ensure role gathers the facts it uses by having test clear_facts before include_role#363
Conversation
Reviewer's GuideIntroduces a reusable test task file to run the ha_cluster role after clearing Ansible facts, adjusts tests to use it instead of directly including the role, and refactors some test condition logic into set_fact variables for clarity and compatibility with fact clearing. File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've found 1 issue, and left some high level feedback:
- In tests/tasks/run_role_with_clear_facts.yml, the control flow is keyed off a variable named
failed_when, but the updated tests pass__ha_cluster_failed_when; this mismatch means the special "failed_when false" handling will never trigger and the role will always run via the "Run the role normally" path—consider aligning the variable name or adding a mapping. - The new tests/library/merge_ansible_facts.py module contains only documentation and no actual module logic, while an action plugin of the same name is implemented separately; either remove the unused stub module or implement it (and clarify which of module vs action plugin is intended to be used) to avoid confusion.
- In tests/tests_default.yml, the
Debug0task in the rescue block looks like leftover troubleshooting output; consider dropping or guarding it if you don't want verbose error dumps in normal test runs.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In tests/tasks/run_role_with_clear_facts.yml, the control flow is keyed off a variable named `failed_when`, but the updated tests pass `__ha_cluster_failed_when`; this mismatch means the special "failed_when false" handling will never trigger and the role will always run via the "Run the role normally" path—consider aligning the variable name or adding a mapping.
- The new tests/library/merge_ansible_facts.py module contains only documentation and no actual module logic, while an action plugin of the same name is implemented separately; either remove the unused stub module or implement it (and clarify which of module vs action plugin is intended to be used) to avoid confusion.
- In tests/tests_default.yml, the `Debug0` task in the rescue block looks like leftover troubleshooting output; consider dropping or guarding it if you don't want verbose error dumps in normal test runs.
## Individual Comments
### Comment 1
<location path="tests/action_plugins/merge_ansible_facts.py" line_range="17-26" />
<code_context>
+class ActionModule(ActionBase):
</code_context>
<issue_to_address>
**suggestion (testing):** Consider adding a test play that validates merge behavior, especially with overlapping and nested keys
Because this action plugin underpins the new `clear_facts`-then-restore pattern, it’s worth adding a focused test that exercises its merge semantics. For example, set up `saved_ansible_facts` with nested keys, `current_ansible_facts` with overlapping keys and different nested values, and assert that the merge is shallow (top-level keys overwritten, nested dicts fully replaced). This will catch any future change to merge depth or precedence early.
Suggested implementation:
```python
from ansible.plugins.action import ActionBase
class ActionModule(ActionBase):
TRANSFERS_FILES = False
def run(self, tmp=None, task_vars=None):
if task_vars is None:
task_vars = {}
result = super(ActionModule, self).run(tmp, task_vars)
result["changed"] = False
def test_merge_ansible_facts_shallow_overwrite():
"""
Validate that merge semantics are shallow:
- Top-level keys from current facts overwrite saved facts.
- When a top-level key is overwritten, the entire nested dict is replaced,
not deep-merged.
"""
saved_ansible_facts = {
"network": {
"dns": {
"servers": ["1.1.1.1"],
"search": ["example.com"],
},
"mtu": 1500,
},
"system": {
"hostname": "original-host",
"aliases": ["orig"],
},
"only_in_saved": {
"foo": "bar",
},
}
current_ansible_facts = {
# Overlapping top-level key with different nested structure
"network": {
"dns": {
"servers": ["8.8.8.8"],
},
# Note: no "mtu" here; if merge is shallow, this will be lost
# instead of preserved via deep merge.
},
# Overlapping key with different nested value
"system": {
"hostname": "current-host",
},
# New key present only in current
"only_in_current": {
"baz": "qux",
},
}
# Expected behavior for a shallow merge:
# - Top-level keys from current overwrite those from saved.
# - Nested dicts under an overwritten top-level key are fully replaced,
# i.e. no deep merge of "network" or "system".
expected_merged_facts = {
"network": current_ansible_facts["network"],
"system": current_ansible_facts["system"],
"only_in_saved": saved_ansible_facts["only_in_saved"],
"only_in_current": current_ansible_facts["only_in_current"],
}
# Simulate the expected merge semantics: shallow, current overwrites saved.
merged_facts = dict(saved_ansible_facts)
merged_facts.update(current_ansible_facts)
assert merged_facts == expected_merged_facts
```
To fully exercise this behavior at the play level (as mentioned in your review comment), consider:
1. Adding an integration test playbook (e.g. `tests/playbooks/merge_ansible_facts.yml`) that:
- Sets `saved_ansible_facts` and `ansible_facts` with overlapping/nested keys similar to this test.
- Invokes the `merge_ansible_facts` action plugin.
- Uses `assert`/`debug` tasks to verify that the resulting facts match the shallow-merge expectations (top-level overwrite, nested dicts fully replaced).
2. Wiring that playbook into your existing test harness (e.g. via `tests/integration/targets` or equivalent) so it runs under `ansible-test` or your CI.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| class ActionModule(ActionBase): | ||
|
|
||
| TRANSFERS_FILES = False | ||
|
|
||
| def run(self, tmp=None, task_vars=None): | ||
| if task_vars is None: | ||
| task_vars = {} | ||
|
|
||
| result = super(ActionModule, self).run(tmp, task_vars) | ||
| result["changed"] = False |
There was a problem hiding this comment.
suggestion (testing): Consider adding a test play that validates merge behavior, especially with overlapping and nested keys
Because this action plugin underpins the new clear_facts-then-restore pattern, it’s worth adding a focused test that exercises its merge semantics. For example, set up saved_ansible_facts with nested keys, current_ansible_facts with overlapping keys and different nested values, and assert that the merge is shallow (top-level keys overwritten, nested dicts fully replaced). This will catch any future change to merge depth or precedence early.
Suggested implementation:
from ansible.plugins.action import ActionBase
class ActionModule(ActionBase):
TRANSFERS_FILES = False
def run(self, tmp=None, task_vars=None):
if task_vars is None:
task_vars = {}
result = super(ActionModule, self).run(tmp, task_vars)
result["changed"] = False
def test_merge_ansible_facts_shallow_overwrite():
"""
Validate that merge semantics are shallow:
- Top-level keys from current facts overwrite saved facts.
- When a top-level key is overwritten, the entire nested dict is replaced,
not deep-merged.
"""
saved_ansible_facts = {
"network": {
"dns": {
"servers": ["1.1.1.1"],
"search": ["example.com"],
},
"mtu": 1500,
},
"system": {
"hostname": "original-host",
"aliases": ["orig"],
},
"only_in_saved": {
"foo": "bar",
},
}
current_ansible_facts = {
# Overlapping top-level key with different nested structure
"network": {
"dns": {
"servers": ["8.8.8.8"],
},
# Note: no "mtu" here; if merge is shallow, this will be lost
# instead of preserved via deep merge.
},
# Overlapping key with different nested value
"system": {
"hostname": "current-host",
},
# New key present only in current
"only_in_current": {
"baz": "qux",
},
}
# Expected behavior for a shallow merge:
# - Top-level keys from current overwrite those from saved.
# - Nested dicts under an overwritten top-level key are fully replaced,
# i.e. no deep merge of "network" or "system".
expected_merged_facts = {
"network": current_ansible_facts["network"],
"system": current_ansible_facts["system"],
"only_in_saved": saved_ansible_facts["only_in_saved"],
"only_in_current": current_ansible_facts["only_in_current"],
}
# Simulate the expected merge semantics: shallow, current overwrites saved.
merged_facts = dict(saved_ansible_facts)
merged_facts.update(current_ansible_facts)
assert merged_facts == expected_merged_factsTo fully exercise this behavior at the play level (as mentioned in your review comment), consider:
- Adding an integration test playbook (e.g.
tests/playbooks/merge_ansible_facts.yml) that:- Sets
saved_ansible_factsandansible_factswith overlapping/nested keys similar to this test. - Invokes the
merge_ansible_factsaction plugin. - Uses
assert/debugtasks to verify that the resulting facts match the shallow-merge expectations (top-level overwrite, nested dicts fully replaced).
- Sets
- Wiring that playbook into your existing test harness (e.g. via
tests/integration/targetsor equivalent) so it runs underansible-testor your CI.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #363 +/- ##
=======================================
Coverage 90.92% 90.92%
=======================================
Files 19 19
Lines 1212 1212
=======================================
Hits 1102 1102
Misses 110 110 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
@tomjelinek do you recall why there was a symlink tests/library/ to ../library/ ? It seems the python unit tests don't need them, nor do the regular integration tests? |
|
[citest] |
|
@tomjelinek any update on the pcs package systemd %posttrans issue? ? |
I searched in history but I'm not sure what it was needed for. It looks like it's not needed anymore, though, as all tests in all systems are passing without it. I think it's safe to remove it.
Packages fixing the issue were built last week, they should be available in a few days. |
tomjelinek
left a comment
There was a problem hiding this comment.
I found the same issues as sourcery:
__ha_cluster_failed_when variable added to multiple tests doesn't seem to be doing anything:
- it is not referenced in
run_role_with_clear_facts.yml - it is set to
truein all occurrences, andtrueis the default value offailed_wheninrun_role_with_clear_facts.yml
The fact that tests/library/merge_ansible_facts.py module contains documentation only and the implementation is done in an action plugin seems unusual and confusing to me. It may be the way to implement this in Ansible properly, though. I don't have deep knowledge about action plugins, and that may be the source of my confusion.
Debug0 task in tests/tests_default.yml looks like a debugging leftover.
a1f75a6 to
cd2cb30
Compare
Fixed.
I thought that the empty module file was required for documentation by ansible-test, but apparently it is not.
removed |
|
[citest] |
|
[citest] |
…s before include_role The role gathers the facts it uses. For example, if the user uses `ANSIBLE_GATHERING=explicit`, the role uses the `setup` module with the facts and subsets it requires. This change allows us to test this. Before every role invocation, the test will use `meta: clear_facts` so that the role starts with no facts. Create a task file tests/tasks/run_role_with_clear_facts.yml to do the tasks to clear the facts and run the role. Note that this means we don't need to use `gather_facts` for the tests. Some vars defined using `ansible_facts` have been changed to be defined with `set_fact` instead. This is because of the fact that `vars` are lazily evaluated - the var might be referenced when the facts have been cleared, and will issue an error like `ansible_facts["distribution"] is undefined`. This is typically done for blocks that have a `when` condition that uses `ansible_facts` and the block has a role invocation using run_role_with_clear_facts.yml These have been rewritten to define the `when` condition using `set_fact`. This is because the `when` condition is evaluated every time a task is invoked in the block, and if the facts are cleared, this will raise an undefined variable error. Signed-off-by: Rich Megginson <rmeggins@redhat.com>
|
[citest] |
There was a problem hiding this comment.
Hey - I've left some high level feedback:
- The new
run_role_with_clear_facts.ymldoes not implement what its header comment and PR description promise (no saving/restoring or merging ofansible_facts); either implement the save/merge logic (likely via the action plugin) or update the comments/description to reflect the actual behavior. - The comment in
run_role_with_clear_facts.ymlabout ensuringtests/libraryis in the module search path is now stale sincetests/libraryis deleted in this PR; please either reintroduce the needed plugin/module in an appropriate location or remove/adjust that comment. - By running
meta: clear_factswithout restoring or merging facts afterward, any subsequent tasks in these test plays that rely onansible_factsmay behave differently than before; if the intent is to maintain previous facts plus role-gathered facts, you likely need an explicit save/restore mechanism around the role execution.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The new `run_role_with_clear_facts.yml` does not implement what its header comment and PR description promise (no saving/restoring or merging of `ansible_facts`); either implement the save/merge logic (likely via the action plugin) or update the comments/description to reflect the actual behavior.
- The comment in `run_role_with_clear_facts.yml` about ensuring `tests/library` is in the module search path is now stale since `tests/library` is deleted in this PR; please either reintroduce the needed plugin/module in an appropriate location or remove/adjust that comment.
- By running `meta: clear_facts` without restoring or merging facts afterward, any subsequent tasks in these test plays that rely on `ansible_facts` may behave differently than before; if the intent is to maintain previous facts plus role-gathered facts, you likely need an explicit save/restore mechanism around the role execution.Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
The role gathers the facts it uses. For example, if the user uses
ANSIBLE_GATHERING=explicit, the role uses thesetupmodule with thefacts and subsets it requires.
This change allows us to test this. Before every role invocation, the test
will use
meta: clear_factsso that the role starts with no facts.Create a task file tests/tasks/run_role_with_clear_facts.yml to do the tasks
to clear the facts and run the role. Note that this means we don't need to
use
gather_factsfor the tests.Some vars defined using
ansible_factshave been changed to be defined withset_factinstead. This is because of the fact thatvarsare lazilyevaluated - the var might be referenced when the facts have been cleared, and
will issue an error like
ansible_facts["distribution"] is undefined. This istypically done for blocks that have a
whencondition that usesansible_factsand the block has a role invocation using run_role_with_clear_facts.yml
These have been rewritten to define the
whencondition usingset_fact. Thisis because the
whencondition is evaluated every time a task is invoked in theblock, and if the facts are cleared, this will raise an undefined variable error.