From 94a832d575bcd882ec0623cc8bd3aaa30e5ef8bd Mon Sep 17 00:00:00 2001 From: Santosh Mahale Date: Sun, 7 Jun 2026 09:20:29 +0530 Subject: [PATCH 1/6] Add unit tests for filetree lookup exclude handling --- tests/unit/plugins/lookup/test_filetree.py | 81 ++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 tests/unit/plugins/lookup/test_filetree.py diff --git a/tests/unit/plugins/lookup/test_filetree.py b/tests/unit/plugins/lookup/test_filetree.py new file mode 100644 index 0000000000..37b9413b77 --- /dev/null +++ b/tests/unit/plugins/lookup/test_filetree.py @@ -0,0 +1,81 @@ +# Copyright (c) 2026, Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +from __future__ import annotations + +import os +import shutil +import tempfile +import unittest +from unittest.mock import patch + +from ansible.errors import AnsibleLookupError +from ansible.plugins.loader import lookup_loader +from ansible.template import Templar + +from ansible_collections.community.internal_test_tools.tests.unit.mock.loader import DictDataLoader + + +class TestFiletreeLookup(unittest.TestCase): + def setUp(self): + self.loader = DictDataLoader({}) + self.templar = Templar(loader=self.loader, variables={}) + self.lookup = lookup_loader.get( + "community.general.filetree", + loader=self.loader, + templar=self.templar, + ) + self.tmpdir = tempfile.mkdtemp(prefix="ansible_test_filetree_") + self.tree_root = os.path.join(self.tmpdir, "data") + os.makedirs(os.path.join(self.tree_root, ".git")) + os.makedirs(os.path.join(self.tree_root, "subdir")) + with open(os.path.join(self.tree_root, "app.conf"), "w", encoding="utf-8") as handle: + handle.write("content\n") + with open(os.path.join(self.tree_root, "subdir", "nested.conf"), "w", encoding="utf-8") as handle: + handle.write("nested\n") + + def tearDown(self): + shutil.rmtree(self.tmpdir) + + def _run_lookup(self, **kwargs): + with ( + patch.object(self.lookup, "get_basedir", return_value=self.tmpdir), + patch.object(self.loader, "path_dwim_relative", return_value=self.tmpdir), + ): + return self.lookup.run(["data"], {}, **kwargs) + + def test_invalid_exclude_regex_raises_lookup_error(self): + with self.assertRaises(AnsibleLookupError) as ctx: + self._run_lookup(exclude="temp[1") + + self.assertIn("Invalid exclude regular expression", str(ctx.exception)) + self.assertIn("temp[1", str(ctx.exception)) + + def test_valid_exclude_skips_matching_basenames(self): + result = self._run_lookup(exclude=r"^\.git$") + + paths = {entry["path"] for entry in result} + self.assertIn("app.conf", paths) + self.assertIn(os.path.join("subdir", "nested.conf"), paths) + self.assertNotIn(".git", paths) + + def test_lookup_without_exclude_lists_entries(self): + result = self._run_lookup() + + paths = {entry["path"] for entry in result} + self.assertIn("app.conf", paths) + self.assertIn(".git", paths) + self.assertIn("subdir", paths) + self.assertIn(os.path.join("subdir", "nested.conf"), paths) + + def test_file_entry_includes_expected_properties(self): + result = self._run_lookup() + app_conf = next(entry for entry in result if entry["path"] == "app.conf") + + self.assertEqual(app_conf["state"], "file") + self.assertEqual(app_conf["root"], self.tree_root) + self.assertEqual(app_conf["src"], os.path.join(self.tree_root, "app.conf")) + self.assertIn("mode", app_conf) + self.assertIn("owner", app_conf) + self.assertIn("group", app_conf) From 654a63ac4b92a8ff205cbb8066c0f0a96e51fb3e Mon Sep 17 00:00:00 2001 From: Santosh Mahale Date: Sun, 7 Jun 2026 11:47:53 +0530 Subject: [PATCH 2/6] Fix import order in filetree lookup unit tests --- tests/unit/plugins/lookup/test_filetree.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/unit/plugins/lookup/test_filetree.py b/tests/unit/plugins/lookup/test_filetree.py index 37b9413b77..8377b04035 100644 --- a/tests/unit/plugins/lookup/test_filetree.py +++ b/tests/unit/plugins/lookup/test_filetree.py @@ -13,7 +13,6 @@ from unittest.mock import patch from ansible.errors import AnsibleLookupError from ansible.plugins.loader import lookup_loader from ansible.template import Templar - from ansible_collections.community.internal_test_tools.tests.unit.mock.loader import DictDataLoader From c031bb1eb92cad9f2263bd17c52bbf2e7bef2514 Mon Sep 17 00:00:00 2001 From: Santosh Mahale Date: Tue, 9 Jun 2026 21:03:51 +0530 Subject: [PATCH 3/6] Replace filetree unit tests with integration tests --- .../targets/lookup_filetree/aliases | 5 ++ .../targets/lookup_filetree/tasks/main.yml | 69 ++++++++++++++++ tests/unit/plugins/lookup/test_filetree.py | 80 ------------------- 3 files changed, 74 insertions(+), 80 deletions(-) create mode 100644 tests/integration/targets/lookup_filetree/aliases create mode 100644 tests/integration/targets/lookup_filetree/tasks/main.yml delete mode 100644 tests/unit/plugins/lookup/test_filetree.py diff --git a/tests/integration/targets/lookup_filetree/aliases b/tests/integration/targets/lookup_filetree/aliases new file mode 100644 index 0000000000..12d1d6617e --- /dev/null +++ b/tests/integration/targets/lookup_filetree/aliases @@ -0,0 +1,5 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +azp/posix/2 diff --git a/tests/integration/targets/lookup_filetree/tasks/main.yml b/tests/integration/targets/lookup_filetree/tasks/main.yml new file mode 100644 index 0000000000..409da43686 --- /dev/null +++ b/tests/integration/targets/lookup_filetree/tasks/main.yml @@ -0,0 +1,69 @@ +--- +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Create test file tree + ansible.builtin.copy: + content: "{{ item.content }}" + dest: "{{ item.dest }}" + loop: + - dest: files/data/app.conf + content: "content\n" + - dest: files/data/subdir/nested.conf + content: "nested\n" + +- name: Create .git directory for exclude test + ansible.builtin.file: + path: files/data/.git + state: directory + +- name: Lookup filetree without exclude + ansible.builtin.set_fact: + filetree_all: "{{ lookup('community.general.filetree', 'data') }}" + +- name: Verify all entries are listed + ansible.builtin.assert: + that: + - filetree_all | selectattr('path', 'equalto', 'app.conf') | list | length == 1 + - filetree_all | selectattr('path', 'equalto', '.git') | list | length == 1 + - filetree_all | selectattr('path', 'equalto', 'subdir') | list | length == 1 + - filetree_all | selectattr('path', 'equalto', 'subdir/nested.conf') | list | length == 1 + +- name: Lookup filetree with valid exclude regex + ansible.builtin.set_fact: + filetree_filtered: "{{ lookup('community.general.filetree', 'data', exclude='^\\.git$') }}" + +- name: Verify exclude filters matching basenames + ansible.builtin.assert: + that: + - filetree_filtered | selectattr('path', 'equalto', '.git') | list | length == 0 + - filetree_filtered | selectattr('path', 'equalto', 'app.conf') | list | length == 1 + - filetree_filtered | selectattr('path', 'equalto', 'subdir/nested.conf') | list | length == 1 + +- name: Verify file entry properties + ansible.builtin.assert: + that: + - (filetree_all | selectattr('path', 'equalto', 'app.conf') | first).state == 'file' + - (filetree_all | selectattr('path', 'equalto', 'app.conf') | first).src is defined + - (filetree_all | selectattr('path', 'equalto', 'app.conf') | first).mode is defined + - (filetree_all | selectattr('path', 'equalto', 'app.conf') | first).owner is defined + - (filetree_all | selectattr('path', 'equalto', 'app.conf') | first).group is defined + +- name: Lookup filetree with invalid exclude regex + ansible.builtin.debug: + msg: "{{ lookup('community.general.filetree', 'data', exclude='temp[1') }}" + ignore_errors: true + register: invalid_exclude + +- name: Verify invalid exclude raises AnsibleLookupError + ansible.builtin.assert: + that: + - invalid_exclude is failed + - "'Invalid exclude regular expression' in invalid_exclude.msg" + - "'temp[1' in invalid_exclude.msg" diff --git a/tests/unit/plugins/lookup/test_filetree.py b/tests/unit/plugins/lookup/test_filetree.py deleted file mode 100644 index 8377b04035..0000000000 --- a/tests/unit/plugins/lookup/test_filetree.py +++ /dev/null @@ -1,80 +0,0 @@ -# Copyright (c) 2026, Ansible Project -# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) -# SPDX-License-Identifier: GPL-3.0-or-later - -from __future__ import annotations - -import os -import shutil -import tempfile -import unittest -from unittest.mock import patch - -from ansible.errors import AnsibleLookupError -from ansible.plugins.loader import lookup_loader -from ansible.template import Templar -from ansible_collections.community.internal_test_tools.tests.unit.mock.loader import DictDataLoader - - -class TestFiletreeLookup(unittest.TestCase): - def setUp(self): - self.loader = DictDataLoader({}) - self.templar = Templar(loader=self.loader, variables={}) - self.lookup = lookup_loader.get( - "community.general.filetree", - loader=self.loader, - templar=self.templar, - ) - self.tmpdir = tempfile.mkdtemp(prefix="ansible_test_filetree_") - self.tree_root = os.path.join(self.tmpdir, "data") - os.makedirs(os.path.join(self.tree_root, ".git")) - os.makedirs(os.path.join(self.tree_root, "subdir")) - with open(os.path.join(self.tree_root, "app.conf"), "w", encoding="utf-8") as handle: - handle.write("content\n") - with open(os.path.join(self.tree_root, "subdir", "nested.conf"), "w", encoding="utf-8") as handle: - handle.write("nested\n") - - def tearDown(self): - shutil.rmtree(self.tmpdir) - - def _run_lookup(self, **kwargs): - with ( - patch.object(self.lookup, "get_basedir", return_value=self.tmpdir), - patch.object(self.loader, "path_dwim_relative", return_value=self.tmpdir), - ): - return self.lookup.run(["data"], {}, **kwargs) - - def test_invalid_exclude_regex_raises_lookup_error(self): - with self.assertRaises(AnsibleLookupError) as ctx: - self._run_lookup(exclude="temp[1") - - self.assertIn("Invalid exclude regular expression", str(ctx.exception)) - self.assertIn("temp[1", str(ctx.exception)) - - def test_valid_exclude_skips_matching_basenames(self): - result = self._run_lookup(exclude=r"^\.git$") - - paths = {entry["path"] for entry in result} - self.assertIn("app.conf", paths) - self.assertIn(os.path.join("subdir", "nested.conf"), paths) - self.assertNotIn(".git", paths) - - def test_lookup_without_exclude_lists_entries(self): - result = self._run_lookup() - - paths = {entry["path"] for entry in result} - self.assertIn("app.conf", paths) - self.assertIn(".git", paths) - self.assertIn("subdir", paths) - self.assertIn(os.path.join("subdir", "nested.conf"), paths) - - def test_file_entry_includes_expected_properties(self): - result = self._run_lookup() - app_conf = next(entry for entry in result if entry["path"] == "app.conf") - - self.assertEqual(app_conf["state"], "file") - self.assertEqual(app_conf["root"], self.tree_root) - self.assertEqual(app_conf["src"], os.path.join(self.tree_root, "app.conf")) - self.assertIn("mode", app_conf) - self.assertIn("owner", app_conf) - self.assertIn("group", app_conf) From 8cec602a852e980ae8d537a8b13d1b5583f9992e Mon Sep 17 00:00:00 2001 From: Santosh Mahale Date: Tue, 9 Jun 2026 21:14:58 +0530 Subject: [PATCH 4/6] Fix lookup_filetree integration test setup directories --- .../integration/targets/lookup_filetree/tasks/main.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/integration/targets/lookup_filetree/tasks/main.yml b/tests/integration/targets/lookup_filetree/tasks/main.yml index 409da43686..ca301a5c83 100644 --- a/tests/integration/targets/lookup_filetree/tasks/main.yml +++ b/tests/integration/targets/lookup_filetree/tasks/main.yml @@ -8,7 +8,15 @@ # GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) # SPDX-License-Identifier: GPL-3.0-or-later -- name: Create test file tree +- name: Create test file tree directories + ansible.builtin.file: + path: "{{ item }}" + state: directory + loop: + - files/data + - files/data/subdir + +- name: Create test files ansible.builtin.copy: content: "{{ item.content }}" dest: "{{ item.dest }}" From 52e98362e7fe129327faaeb6eeda5974cb192148 Mon Sep 17 00:00:00 2001 From: Santosh Mahale Date: Tue, 9 Jun 2026 21:21:51 +0530 Subject: [PATCH 5/6] Fix lookup_filetree integration test lookup term path --- tests/integration/targets/lookup_filetree/tasks/main.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/integration/targets/lookup_filetree/tasks/main.yml b/tests/integration/targets/lookup_filetree/tasks/main.yml index ca301a5c83..56e7613017 100644 --- a/tests/integration/targets/lookup_filetree/tasks/main.yml +++ b/tests/integration/targets/lookup_filetree/tasks/main.yml @@ -33,7 +33,7 @@ - name: Lookup filetree without exclude ansible.builtin.set_fact: - filetree_all: "{{ lookup('community.general.filetree', 'data') }}" + filetree_all: "{{ lookup('community.general.filetree', 'data/') }}" - name: Verify all entries are listed ansible.builtin.assert: @@ -45,7 +45,7 @@ - name: Lookup filetree with valid exclude regex ansible.builtin.set_fact: - filetree_filtered: "{{ lookup('community.general.filetree', 'data', exclude='^\\.git$') }}" + filetree_filtered: "{{ lookup('community.general.filetree', 'data/', exclude='^\\.git$') }}" - name: Verify exclude filters matching basenames ansible.builtin.assert: @@ -65,7 +65,7 @@ - name: Lookup filetree with invalid exclude regex ansible.builtin.debug: - msg: "{{ lookup('community.general.filetree', 'data', exclude='temp[1') }}" + msg: "{{ lookup('community.general.filetree', 'data/', exclude='temp[1') }}" ignore_errors: true register: invalid_exclude From e2548df3df635e58e476f640f80967af7ff1e11a Mon Sep 17 00:00:00 2001 From: Santosh Mahale Date: Wed, 10 Jun 2026 19:15:50 +0530 Subject: [PATCH 6/6] Use remote_tmp_dir for lookup_filetree integration test files --- .../targets/lookup_filetree/defaults/main.yml | 6 ++++++ .../targets/lookup_filetree/meta/main.yml | 7 +++++++ .../targets/lookup_filetree/tasks/main.yml | 16 ++++++++-------- 3 files changed, 21 insertions(+), 8 deletions(-) create mode 100644 tests/integration/targets/lookup_filetree/defaults/main.yml create mode 100644 tests/integration/targets/lookup_filetree/meta/main.yml diff --git a/tests/integration/targets/lookup_filetree/defaults/main.yml b/tests/integration/targets/lookup_filetree/defaults/main.yml new file mode 100644 index 0000000000..5a4e3371fe --- /dev/null +++ b/tests/integration/targets/lookup_filetree/defaults/main.yml @@ -0,0 +1,6 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +filetree_test_root: "{{ remote_tmp_dir }}/data" diff --git a/tests/integration/targets/lookup_filetree/meta/main.yml b/tests/integration/targets/lookup_filetree/meta/main.yml new file mode 100644 index 0000000000..982de6eb03 --- /dev/null +++ b/tests/integration/targets/lookup_filetree/meta/main.yml @@ -0,0 +1,7 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +dependencies: + - setup_remote_tmp_dir diff --git a/tests/integration/targets/lookup_filetree/tasks/main.yml b/tests/integration/targets/lookup_filetree/tasks/main.yml index 56e7613017..0f9a1302c4 100644 --- a/tests/integration/targets/lookup_filetree/tasks/main.yml +++ b/tests/integration/targets/lookup_filetree/tasks/main.yml @@ -13,27 +13,27 @@ path: "{{ item }}" state: directory loop: - - files/data - - files/data/subdir + - "{{ filetree_test_root }}" + - "{{ filetree_test_root }}/subdir" - name: Create test files ansible.builtin.copy: content: "{{ item.content }}" dest: "{{ item.dest }}" loop: - - dest: files/data/app.conf + - dest: "{{ filetree_test_root }}/app.conf" content: "content\n" - - dest: files/data/subdir/nested.conf + - dest: "{{ filetree_test_root }}/subdir/nested.conf" content: "nested\n" - name: Create .git directory for exclude test ansible.builtin.file: - path: files/data/.git + path: "{{ filetree_test_root }}/.git" state: directory - name: Lookup filetree without exclude ansible.builtin.set_fact: - filetree_all: "{{ lookup('community.general.filetree', 'data/') }}" + filetree_all: "{{ lookup('community.general.filetree', filetree_test_root ~ '/') }}" - name: Verify all entries are listed ansible.builtin.assert: @@ -45,7 +45,7 @@ - name: Lookup filetree with valid exclude regex ansible.builtin.set_fact: - filetree_filtered: "{{ lookup('community.general.filetree', 'data/', exclude='^\\.git$') }}" + filetree_filtered: "{{ lookup('community.general.filetree', filetree_test_root ~ '/', exclude='^\\.git$') }}" - name: Verify exclude filters matching basenames ansible.builtin.assert: @@ -65,7 +65,7 @@ - name: Lookup filetree with invalid exclude regex ansible.builtin.debug: - msg: "{{ lookup('community.general.filetree', 'data/', exclude='temp[1') }}" + msg: "{{ lookup('community.general.filetree', filetree_test_root ~ '/', exclude='temp[1') }}" ignore_errors: true register: invalid_exclude