diff options
Diffstat (limited to 'test/rules')
-rw-r--r-- | test/rules/__init__.py | 1 | ||||
-rw-r--r-- | test/rules/fixtures/__init__.py | 3 | ||||
-rw-r--r-- | test/rules/fixtures/ematcher.py | 15 | ||||
-rw-r--r-- | test/rules/fixtures/raw_task.md | 3 | ||||
-rw-r--r-- | test/rules/fixtures/raw_task.py | 30 | ||||
-rw-r--r-- | test/rules/fixtures/unset_variable_matcher.py | 15 | ||||
-rw-r--r-- | test/rules/test_deprecated_module.py | 27 | ||||
-rw-r--r-- | test/rules/test_inline_env_var.py | 90 | ||||
-rw-r--r-- | test/rules/test_no_changed_when.py | 23 | ||||
-rw-r--r-- | test/rules/test_package_latest.py | 23 | ||||
-rw-r--r-- | test/rules/test_role_names.py | 91 | ||||
-rw-r--r-- | test/rules/test_syntax_check.py | 70 |
12 files changed, 391 insertions, 0 deletions
diff --git a/test/rules/__init__.py b/test/rules/__init__.py new file mode 100644 index 0000000..28b581d --- /dev/null +++ b/test/rules/__init__.py @@ -0,0 +1 @@ +"""Tests for specific rules.""" diff --git a/test/rules/fixtures/__init__.py b/test/rules/fixtures/__init__.py new file mode 100644 index 0000000..d049bf0 --- /dev/null +++ b/test/rules/fixtures/__init__.py @@ -0,0 +1,3 @@ +"""Test rules resources.""" + +__all__ = ["ematcher", "raw_task", "unset_variable_matcher"] diff --git a/test/rules/fixtures/ematcher.py b/test/rules/fixtures/ematcher.py new file mode 100644 index 0000000..1b04b6b --- /dev/null +++ b/test/rules/fixtures/ematcher.py @@ -0,0 +1,15 @@ +"""Custom rule used as fixture.""" +from ansiblelint.rules import AnsibleLintRule + + +class EMatcherRule(AnsibleLintRule): + """BANNED string found.""" + + id = "TEST0001" + description = ( + "This is a test custom rule that looks for lines containing BANNED string" + ) + tags = ["fake", "dummy", "test1"] + + def match(self, line: str) -> bool: + return "BANNED" in line diff --git a/test/rules/fixtures/raw_task.md b/test/rules/fixtures/raw_task.md new file mode 100644 index 0000000..2aa6d22 --- /dev/null +++ b/test/rules/fixtures/raw_task.md @@ -0,0 +1,3 @@ +# raw-task + +This is a test rule that looks in a raw task to flag raw action params. diff --git a/test/rules/fixtures/raw_task.py b/test/rules/fixtures/raw_task.py new file mode 100644 index 0000000..0d5b023 --- /dev/null +++ b/test/rules/fixtures/raw_task.py @@ -0,0 +1,30 @@ +"""Test Rule that needs_raw_task.""" +from __future__ import annotations + +from typing import TYPE_CHECKING + +from ansiblelint.rules import AnsibleLintRule + +if TYPE_CHECKING: + from ansiblelint.file_utils import Lintable + from ansiblelint.utils import Task + + +class RawTaskRule(AnsibleLintRule): + """Test rule that inspects the raw task.""" + + id = "raw-task" + shortdesc = "Test rule that inspects the raw task" + tags = ["fake", "dummy", "test3"] + needs_raw_task = True + + def matchtask( + self, + task: Task, + file: Lintable | None = None, + ) -> bool | str: + """Match a task using __raw_task__ to inspect the module params type.""" + raw_task = task["__raw_task__"] + module = task["action"]["__ansible_module_original__"] + found_raw_task_params = not isinstance(raw_task[module], dict) + return found_raw_task_params diff --git a/test/rules/fixtures/unset_variable_matcher.py b/test/rules/fixtures/unset_variable_matcher.py new file mode 100644 index 0000000..8486009 --- /dev/null +++ b/test/rules/fixtures/unset_variable_matcher.py @@ -0,0 +1,15 @@ +"""Custom linting rule used as test fixture.""" +from ansiblelint.rules import AnsibleLintRule + + +class UnsetVariableMatcherRule(AnsibleLintRule): + """Line contains untemplated variable.""" + + id = "TEST0002" + description = ( + "This is a test rule that looks for lines post templating that still contain {{" + ) + tags = ["fake", "dummy", "test2"] + + def match(self, line: str) -> bool: + return "{{" in line diff --git a/test/rules/test_deprecated_module.py b/test/rules/test_deprecated_module.py new file mode 100644 index 0000000..a57d8db --- /dev/null +++ b/test/rules/test_deprecated_module.py @@ -0,0 +1,27 @@ +"""Tests for deprecated-module rule.""" +from pathlib import Path + +from ansiblelint.rules import RulesCollection +from ansiblelint.rules.deprecated_module import DeprecatedModuleRule +from ansiblelint.testing import RunFromText + +MODULE_DEPRECATED = """ +- name: Task example + docker: + debug: test +""" + + +def test_module_deprecated(tmp_path: Path) -> None: + """Test for deprecated-module.""" + collection = RulesCollection() + collection.register(DeprecatedModuleRule()) + runner = RunFromText(collection) + results = runner.run_role_tasks_main(MODULE_DEPRECATED, tmp_path=tmp_path) + assert len(results) == 1 + # based on version and blend of ansible being used, we may + # get a missing module, so we future proof the test + assert ( + "couldn't resolve module" not in results[0].message + or "Deprecated module" not in results[0].message + ) diff --git a/test/rules/test_inline_env_var.py b/test/rules/test_inline_env_var.py new file mode 100644 index 0000000..98f337e --- /dev/null +++ b/test/rules/test_inline_env_var.py @@ -0,0 +1,90 @@ +"""Tests for inline-env-var rule.""" +from ansiblelint.rules import RulesCollection +from ansiblelint.rules.inline_env_var import EnvVarsInCommandRule +from ansiblelint.testing import RunFromText + +SUCCESS_PLAY_TASKS = """ +- hosts: localhost + + tasks: + - name: Actual use of environment + shell: echo $HELLO + environment: + HELLO: hello + + - name: Use some key-value pairs + command: chdir=/tmp creates=/tmp/bobbins warn=no touch bobbins + + - name: Commands can have flags + command: abc --xyz=def blah + + - name: Commands can have equals in them + command: echo "===========" + + - name: Commands with cmd + command: + cmd: + echo "-------" + + - name: Command with stdin (ansible > 2.4) + command: /bin/cat + args: + stdin: "Hello, world!" + + - name: Use argv to send the command as a list + command: + argv: + - /bin/echo + - Hello + - World + + - name: Another use of argv + command: + args: + argv: + - echo + - testing + + - name: Environment variable with shell + shell: HELLO=hello echo $HELLO + + - name: Command with stdin_add_newline (ansible > 2.8) + command: /bin/cat + args: + stdin: "Hello, world!" + stdin_add_newline: false + + - name: Command with strip_empty_ends (ansible > 2.8) + command: echo + args: + strip_empty_ends: false +""" + +FAIL_PLAY_TASKS = """ +- hosts: localhost + + tasks: + - name: Environment variable with command + command: HELLO=hello echo $HELLO + + - name: Typo some stuff + command: cerates=/tmp/blah warn=no touch /tmp/blah +""" + + +def test_success() -> None: + """Positive test for inline-env-var.""" + collection = RulesCollection() + collection.register(EnvVarsInCommandRule()) + runner = RunFromText(collection) + results = runner.run_playbook(SUCCESS_PLAY_TASKS) + assert len(results) == 0 + + +def test_fail() -> None: + """Negative test for inline-env-var.""" + collection = RulesCollection() + collection.register(EnvVarsInCommandRule()) + runner = RunFromText(collection) + results = runner.run_playbook(FAIL_PLAY_TASKS) + assert len(results) == 2 diff --git a/test/rules/test_no_changed_when.py b/test/rules/test_no_changed_when.py new file mode 100644 index 0000000..c89d8f4 --- /dev/null +++ b/test/rules/test_no_changed_when.py @@ -0,0 +1,23 @@ +"""Tests for no-change-when rule.""" +from ansiblelint.rules import RulesCollection +from ansiblelint.rules.no_changed_when import CommandHasChangesCheckRule +from ansiblelint.runner import Runner + + +def test_command_changes_positive() -> None: + """Positive test for no-changed-when.""" + collection = RulesCollection() + collection.register(CommandHasChangesCheckRule()) + success = "examples/playbooks/command-check-success.yml" + good_runner = Runner(success, rules=collection) + assert [] == good_runner.run() + + +def test_command_changes_negative() -> None: + """Negative test for no-changed-when.""" + collection = RulesCollection() + collection.register(CommandHasChangesCheckRule()) + failure = "examples/playbooks/command-check-failure.yml" + bad_runner = Runner(failure, rules=collection) + errs = bad_runner.run() + assert len(errs) == 2 diff --git a/test/rules/test_package_latest.py b/test/rules/test_package_latest.py new file mode 100644 index 0000000..5631f02 --- /dev/null +++ b/test/rules/test_package_latest.py @@ -0,0 +1,23 @@ +"""Tests for package-latest rule.""" +from ansiblelint.rules import RulesCollection +from ansiblelint.rules.package_latest import PackageIsNotLatestRule +from ansiblelint.runner import Runner + + +def test_package_not_latest_positive() -> None: + """Positive test for package-latest.""" + collection = RulesCollection() + collection.register(PackageIsNotLatestRule()) + success = "examples/playbooks/package-check-success.yml" + good_runner = Runner(success, rules=collection) + assert [] == good_runner.run() + + +def test_package_not_latest_negative() -> None: + """Negative test for package-latest.""" + collection = RulesCollection() + collection.register(PackageIsNotLatestRule()) + failure = "examples/playbooks/package-check-failure.yml" + bad_runner = Runner(failure, rules=collection) + errs = bad_runner.run() + assert len(errs) == 4 diff --git a/test/rules/test_role_names.py b/test/rules/test_role_names.py new file mode 100644 index 0000000..491cf14 --- /dev/null +++ b/test/rules/test_role_names.py @@ -0,0 +1,91 @@ +"""Test the RoleNames rule.""" +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +import pytest + +from ansiblelint.rules import RulesCollection +from ansiblelint.rules.role_name import RoleNames +from ansiblelint.runner import Runner + +if TYPE_CHECKING: + from pathlib import Path + + from _pytest.fixtures import SubRequest + +ROLE_NAME_VALID = "test_role" + +TASK_MINIMAL = """ +- name: Some task + ping: +""" + +ROLE_MINIMAL = {"tasks": {"main.yml": TASK_MINIMAL}} +ROLE_META_EMPTY = {"meta": {"main.yml": ""}} + +ROLE_WITH_EMPTY_META = {**ROLE_MINIMAL, **ROLE_META_EMPTY} + +PLAY_INCLUDE_ROLE = f""" +- hosts: all + roles: + - {ROLE_NAME_VALID} +""" + + +@pytest.fixture(name="test_rules_collection") +def fixture_test_rules_collection() -> RulesCollection: + """Instantiate a roles collection for tests.""" + collection = RulesCollection() + collection.register(RoleNames()) + return collection + + +def dict_to_files(parent_dir: Path, file_dict: dict[str, Any]) -> None: + """Write a nested dict to a file and directory structure below parent_dir.""" + for file, content in file_dict.items(): + if isinstance(content, dict): + directory = parent_dir / file + directory.mkdir() + dict_to_files(directory, content) + else: + (parent_dir / file).write_text(content) + + +@pytest.fixture(name="playbook_path") +def fixture_playbook_path(request: SubRequest, tmp_path: Path) -> str: + """Create a playbook with a role in a temporary directory.""" + playbook_text = request.param[0] + role_name = request.param[1] + role_layout = request.param[2] + role_path = tmp_path / role_name + role_path.mkdir() + dict_to_files(role_path, role_layout) + play_path = tmp_path / "playbook.yml" + play_path.write_text(playbook_text) + return str(play_path) + + +@pytest.mark.parametrize( + ("playbook_path", "messages"), + ( + pytest.param( + (PLAY_INCLUDE_ROLE, ROLE_NAME_VALID, ROLE_WITH_EMPTY_META), + [], + id="ROLE_EMPTY_META", + ), + ), + indirect=("playbook_path",), +) +def test_role_name( + test_rules_collection: RulesCollection, + playbook_path: str, + messages: list[str], +) -> None: + """Lint a playbook and compare the expected messages with the actual messages.""" + runner = Runner(playbook_path, rules=test_rules_collection) + results = runner.run() + assert len(results) == len(messages) + results_text = str(results) + for message in messages: + assert message in results_text diff --git a/test/rules/test_syntax_check.py b/test/rules/test_syntax_check.py new file mode 100644 index 0000000..2fe36a3 --- /dev/null +++ b/test/rules/test_syntax_check.py @@ -0,0 +1,70 @@ +"""Tests for syntax-check rule.""" +from typing import Any + +from ansiblelint.file_utils import Lintable +from ansiblelint.rules import RulesCollection +from ansiblelint.runner import Runner + + +def test_get_ansible_syntax_check_matches( + default_rules_collection: RulesCollection, +) -> None: + """Validate parsing of ansible output.""" + lintable = Lintable( + "examples/playbooks/conflicting_action.yml", + kind="playbook", + ) + + result = Runner(lintable, rules=default_rules_collection).run() + + assert result[0].lineno == 4 + assert result[0].column == 7 + assert ( + result[0].message + == "conflicting action statements: ansible.builtin.debug, ansible.builtin.command" + ) + # We internally convert absolute paths returned by ansible into paths + # relative to current directory. + assert result[0].filename.endswith("/conflicting_action.yml") + assert len(result) == 1 + + +def test_empty_playbook(default_rules_collection: RulesCollection) -> None: + """Validate detection of empty-playbook.""" + lintable = Lintable("examples/playbooks/empty_playbook.yml", kind="playbook") + result = Runner(lintable, rules=default_rules_collection).run() + assert result[0].lineno == 1 + # We internally convert absolute paths returned by ansible into paths + # relative to current directory. + assert result[0].filename.endswith("/empty_playbook.yml") + assert result[0].tag == "syntax-check[empty-playbook]" + assert result[0].message == "Empty playbook, nothing to do" + assert len(result) == 1 + + +def test_extra_vars_passed_to_command( + default_rules_collection: RulesCollection, + config_options: Any, +) -> None: + """Validate `extra-vars` are passed to syntax check command.""" + config_options.extra_vars = { + "foo": "bar", + "complex_variable": ":{;\t$()", + } + lintable = Lintable("examples/playbooks/extra_vars.yml", kind="playbook") + + result = Runner(lintable, rules=default_rules_collection).run() + + assert not result + + +def test_syntax_check_role() -> None: + """Validate syntax check of a broken role.""" + lintable = Lintable("examples/playbooks/roles/invalid_due_syntax", kind="role") + rules = RulesCollection() + result = Runner(lintable, rules=rules).run() + assert len(result) == 1, result + assert result[0].lineno == 2 + assert result[0].filename == "examples/roles/invalid_due_syntax/tasks/main.yml" + assert result[0].tag == "syntax-check[specific]" + assert result[0].message == "no module/action detected in task." |