diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-14 20:04:50 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-14 20:04:50 +0000 |
commit | 782f8df6e41f29dce2db4970a3ad84aaeb7d8c5f (patch) | |
tree | 3a88a542cd8074743d251881131510157cfc510b /test | |
parent | Initial commit. (diff) | |
download | ansible-lint-782f8df6e41f29dce2db4970a3ad84aaeb7d8c5f.tar.xz ansible-lint-782f8df6e41f29dce2db4970a3ad84aaeb7d8c5f.zip |
Adding upstream version 4.3.7.upstream/4.3.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'test')
180 files changed, 4896 insertions, 0 deletions
diff --git a/test/TestAlwaysRunRule.py b/test/TestAlwaysRunRule.py new file mode 100644 index 0000000..011a258 --- /dev/null +++ b/test/TestAlwaysRunRule.py @@ -0,0 +1,24 @@ +# pylint: disable=preferred-module # FIXME: remove once migrated per GH-725 +import unittest + +from ansiblelint.rules import RulesCollection +from ansiblelint.rules.AlwaysRunRule import AlwaysRunRule +from ansiblelint.runner import Runner + + +class TestAlwaysRun(unittest.TestCase): + collection = RulesCollection() + + def setUp(self): + self.collection.register(AlwaysRunRule()) + + def test_file_positive(self): + success = 'test/always-run-success.yml' + good_runner = Runner(self.collection, success, [], [], []) + self.assertEqual([], good_runner.run()) + + def test_file_negative(self): + failure = 'test/always-run-failure.yml' + bad_runner = Runner(self.collection, failure, [], [], []) + errs = bad_runner.run() + self.assertEqual(1, len(errs)) diff --git a/test/TestAnsibleLintRule.py b/test/TestAnsibleLintRule.py new file mode 100644 index 0000000..f8bc6d1 --- /dev/null +++ b/test/TestAnsibleLintRule.py @@ -0,0 +1,7 @@ +from ansiblelint.rules import AnsibleLintRule + + +def test_unjinja(): + input = "{{ a }} {% b %} {# try to confuse parsing inside a comment { {{}} } #}" + output = "JINJA_EXPRESSION JINJA_STATEMENT JINJA_COMMENT" + assert AnsibleLintRule.unjinja(input) == output diff --git a/test/TestAnsibleSyntax.py b/test/TestAnsibleSyntax.py new file mode 100644 index 0000000..8103b61 --- /dev/null +++ b/test/TestAnsibleSyntax.py @@ -0,0 +1,16 @@ +"""Test Ansible Syntax. + +This module contains tests that validate that linter does not produce errors +when encountering what counts as valid Ansible syntax. +""" + +PB_WITH_NULL_TASKS = ''' +- hosts: all + tasks: +''' + + +def test_null_tasks(default_text_runner): + """Assure we do not fail when encountering null tasks.""" + results = default_text_runner.run_playbook(PB_WITH_NULL_TASKS) + assert not results diff --git a/test/TestBaseFormatter.py b/test/TestBaseFormatter.py new file mode 100644 index 0000000..c6ba7fb --- /dev/null +++ b/test/TestBaseFormatter.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8; -*- +from pathlib import Path + +import pytest + +from ansiblelint.formatters import BaseFormatter + + +@pytest.mark.parametrize(('base_dir', 'relative_path'), ( + (None, True), + ('/whatever', False), + (Path('/whatever'), False), +)) +@pytest.mark.parametrize('path', ('/whatever/string', Path('/whatever/string'))) +def test_base_formatter_when_base_dir(base_dir, relative_path, path): + # Given + base_formatter = BaseFormatter(base_dir, relative_path) + + # When + output_path = base_formatter._format_path(path) + + # Then + assert isinstance(output_path, str) + assert base_formatter._base_dir is None or isinstance(base_formatter._base_dir, str) + assert output_path == str(path) + + +@pytest.mark.parametrize('base_dir', ( + Path('/whatever'), + '/whatever', +)) +@pytest.mark.parametrize('path', ('/whatever/string', Path('/whatever/string'))) +def test_base_formatter_when_base_dir_is_given_and_relative_is_true(path, base_dir): + # Given + base_formatter = BaseFormatter(base_dir, True) + + # When + output_path = base_formatter._format_path(path) + + # Then + assert isinstance(output_path, str) + assert isinstance(base_formatter._base_dir, str) + assert output_path == Path(path).name + + +# vim: et:sw=4:syntax=python:ts=4: diff --git a/test/TestBecomeUserWithoutBecome.py b/test/TestBecomeUserWithoutBecome.py new file mode 100644 index 0000000..066e52f --- /dev/null +++ b/test/TestBecomeUserWithoutBecome.py @@ -0,0 +1,24 @@ +# pylint: disable=preferred-module # FIXME: remove once migrated per GH-725 +import unittest + +from ansiblelint.rules import RulesCollection +from ansiblelint.rules.BecomeUserWithoutBecomeRule import BecomeUserWithoutBecomeRule +from ansiblelint.runner import Runner + + +class TestBecomeUserWithoutBecome(unittest.TestCase): + collection = RulesCollection() + + def setUp(self): + self.collection.register(BecomeUserWithoutBecomeRule()) + + def test_file_positive(self): + success = 'test/become-user-without-become-success.yml' + good_runner = Runner(self.collection, success, [], [], []) + self.assertEqual([], good_runner.run()) + + def test_file_negative(self): + failure = 'test/become-user-without-become-failure.yml' + bad_runner = Runner(self.collection, failure, [], [], []) + errs = bad_runner.run() + self.assertEqual(3, len(errs)) diff --git a/test/TestCliRolePaths.py b/test/TestCliRolePaths.py new file mode 100644 index 0000000..a459df2 --- /dev/null +++ b/test/TestCliRolePaths.py @@ -0,0 +1,134 @@ +# pylint: disable=preferred-module # FIXME: remove once migrated per GH-725 +import os +import unittest +from pathlib import Path + +import pytest + +from ansiblelint.testing import run_ansible_lint + + +class TestCliRolePaths(unittest.TestCase): + def setUp(self): + self.local_test_dir = os.path.dirname(os.path.realpath(__file__)) + + def test_run_single_role_path_no_trailing_slash_module(self): + cwd = self.local_test_dir + role_path = 'test-role' + + result = run_ansible_lint(role_path, cwd=cwd) + self.assertIn('Use shell only when shell functionality is required', + result.stdout) + + def test_run_single_role_path_no_trailing_slash_script(self): + cwd = self.local_test_dir + role_path = 'test-role' + + result = run_ansible_lint(role_path, cwd=cwd, bin="ansible-lint") + self.assertIn('Use shell only when shell functionality is required', + result.stdout) + + def test_run_single_role_path_with_trailing_slash(self): + cwd = self.local_test_dir + role_path = 'test-role/' + + result = run_ansible_lint(role_path, cwd=cwd) + self.assertIn('Use shell only when shell functionality is required', + result.stdout) + + def test_run_multiple_role_path_no_trailing_slash(self): + cwd = self.local_test_dir + role_path = 'roles/test-role' + + result = run_ansible_lint(role_path, cwd=cwd) + self.assertIn('Use shell only when shell functionality is required', + result.stdout) + + def test_run_multiple_role_path_with_trailing_slash(self): + cwd = self.local_test_dir + role_path = 'roles/test-role/' + + result = run_ansible_lint(role_path, cwd=cwd) + self.assertIn('Use shell only when shell functionality is required', + result.stdout) + + def test_run_inside_role_dir(self): + cwd = os.path.join(self.local_test_dir, 'test-role/') + role_path = '.' + + result = run_ansible_lint(role_path, cwd=cwd) + self.assertIn('Use shell only when shell functionality is required', + result.stdout) + + def test_run_role_three_dir_deep(self): + cwd = self.local_test_dir + role_path = 'testproject/roles/test-role' + + result = run_ansible_lint(role_path, cwd=cwd) + self.assertIn('Use shell only when shell functionality is required', + result.stdout) + + def test_run_playbook(self): + """Call ansible-lint the way molecule does.""" + top_src_dir = os.path.dirname(self.local_test_dir) + cwd = os.path.join(top_src_dir, 'test/roles/test-role') + role_path = 'molecule/default/include-import-role.yml' + + env = os.environ.copy() + env['ANSIBLE_ROLES_PATH'] = os.path.dirname(cwd) + + result = run_ansible_lint(role_path, cwd=cwd, env=env) + self.assertIn('Use shell only when shell functionality is required', result.stdout) + + def test_run_role_name_invalid(self): + cwd = self.local_test_dir + role_path = 'roles/invalid-name' + + result = run_ansible_lint(role_path, cwd=cwd) + assert '106 Role name invalid-name does not match' in result.stdout + + def test_run_role_name_with_prefix(self): + cwd = self.local_test_dir + role_path = 'roles/ansible-role-foo' + + result = run_ansible_lint(role_path, cwd=cwd) + assert len(result.stdout) == 0 + assert len(result.stderr) == 0 + assert result.returncode == 0 + + def test_run_role_name_from_meta(self): + cwd = self.local_test_dir + role_path = 'roles/valid-due-to-meta' + + result = run_ansible_lint(role_path, cwd=cwd) + assert len(result.stdout) == 0 + assert len(result.stderr) == 0 + assert result.returncode == 0 + + def test_run_invalid_role_name_from_meta(self): + cwd = self.local_test_dir + role_path = 'roles/invalid_due_to_meta' + + result = run_ansible_lint(role_path, cwd=cwd) + assert '106 Role name invalid-due-to-meta does not match' in result.stdout + + +@pytest.mark.parametrize(('result', 'env'), ( + (True, { + "GITHUB_ACTIONS": "true", + "GITHUB_WORKFLOW": "foo" + }), + (False, None)), + ids=("on", "off")) +def test_run_playbook_github(result, env): + """Call ansible-lint simulating GitHub Actions environment.""" + cwd = str(Path(__file__).parent.parent.resolve()) + role_path = 'examples/example.yml' + + result_gh = run_ansible_lint(role_path, cwd=cwd, env=env) + + expected = ( + '::error file=examples/example.yml,line=47,severity=MEDIUM::[E101] ' + 'Deprecated always_run' + ) + assert (expected in result_gh.stdout) is result diff --git a/test/TestCommandHasChangesCheck.py b/test/TestCommandHasChangesCheck.py new file mode 100644 index 0000000..da0aa08 --- /dev/null +++ b/test/TestCommandHasChangesCheck.py @@ -0,0 +1,24 @@ +# pylint: disable=preferred-module # FIXME: remove once migrated per GH-725 +import unittest + +from ansiblelint.rules import RulesCollection +from ansiblelint.rules.CommandHasChangesCheckRule import CommandHasChangesCheckRule +from ansiblelint.runner import Runner + + +class TestCommandHasChangesCheck(unittest.TestCase): + collection = RulesCollection() + + def setUp(self): + self.collection.register(CommandHasChangesCheckRule()) + + def test_command_changes_positive(self): + success = 'test/command-check-success.yml' + good_runner = Runner(self.collection, success, [], [], []) + self.assertEqual([], good_runner.run()) + + def test_command_changes_negative(self): + failure = 'test/command-check-failure.yml' + bad_runner = Runner(self.collection, failure, [], [], []) + errs = bad_runner.run() + self.assertEqual(2, len(errs)) diff --git a/test/TestCommandLineInvocationSameAsConfig.py b/test/TestCommandLineInvocationSameAsConfig.py new file mode 100644 index 0000000..a5368fa --- /dev/null +++ b/test/TestCommandLineInvocationSameAsConfig.py @@ -0,0 +1,137 @@ +import os +import sys +from pathlib import Path + +import pytest + +from ansiblelint import cli + + +@pytest.fixture +def base_arguments(): + return ['../test/skiptasks.yml'] + + +@pytest.mark.parametrize(('args', 'config'), ( + (["-p"], "test/fixtures/parseable.yml"), + (["-q"], "test/fixtures/quiet.yml"), + (["-r", "test/fixtures/rules/"], + "test/fixtures/rulesdir.yml"), + (["-R", "-r", "test/fixtures/rules/"], + "test/fixtures/rulesdir-defaults.yml"), + (["-t", "skip_ansible_lint"], + "test/fixtures/tags.yml"), + (["-v"], "test/fixtures/verbosity.yml"), + (["-x", "bad_tag"], + "test/fixtures/skip-tags.yml"), + (["--exclude", "test/"], + "test/fixtures/exclude-paths.yml"), + (["--show-relpath"], + "test/fixtures/show-abspath.yml"), + ([], + "test/fixtures/show-relpath.yml"), + )) +def test_ensure_config_are_equal(base_arguments, args, config, monkeypatch): + command = base_arguments + args + cli_parser = cli.get_cli_parser() + + _real_pathlib_resolve = Path.resolve + + def _fake_pathlib_resolve(self): + try: + return _real_pathlib_resolve(self) + except FileNotFoundError: + if self != Path(args[-1]): + raise + return Path.cwd() / self + + with monkeypatch.context() as mp_ctx: + if ( + sys.version_info[:2] < (3, 6) and + args[-2:] == ["-r", "test/fixtures/rules/"] + ): + mp_ctx.setattr(Path, 'resolve', _fake_pathlib_resolve) + options = cli_parser.parse_args(command) + + file_config = cli.load_config(config) + + for key, val in file_config.items(): + if key in {'exclude_paths', 'rulesdir'}: + val = [Path(p) for p in val] + assert val == getattr(options, key) + + +def test_config_can_be_overridden(base_arguments): + no_override = cli.get_config(base_arguments + ["-t", "bad_tag"]) + + overridden = cli.get_config(base_arguments + + ["-t", "bad_tag", + "-c", "test/fixtures/tags.yml"]) + + assert no_override.tags + ["skip_ansible_lint"] == overridden.tags + + +def test_different_config_file(base_arguments): + """Ensures an alternate config_file can be used.""" + diff_config = cli.get_config(base_arguments + + ["-c", "test/fixtures/ansible-config.yml"]) + no_config = cli.get_config(base_arguments + ["-v"]) + + assert diff_config.verbosity == no_config.verbosity + + +def test_expand_path_user_and_vars_config_file(base_arguments): + """Ensure user and vars are expanded when specified as exclude_paths.""" + config1 = cli.get_config(base_arguments + + ["-c", "test/fixtures/exclude-paths-with-expands.yml"]) + config2 = cli.get_config(base_arguments + [ + "--exclude", "~/.ansible/roles", + "--exclude", "$HOME/.ansible/roles" + ]) + + assert str(config1.exclude_paths[0]) == os.path.expanduser("~/.ansible/roles") + assert str(config2.exclude_paths[0]) == os.path.expanduser("~/.ansible/roles") + assert str(config1.exclude_paths[1]) == os.path.expandvars("$HOME/.ansible/roles") + assert str(config2.exclude_paths[1]) == os.path.expandvars("$HOME/.ansible/roles") + + +def test_path_from_config_do_not_depend_on_cwd(monkeypatch): # Issue 572 + config1 = cli.load_config("test/fixtures/config-with-relative-path.yml") + monkeypatch.chdir('test') + config2 = cli.load_config("fixtures/config-with-relative-path.yml") + + assert config1['exclude_paths'].sort() == config2['exclude_paths'].sort() + + +def test_path_from_cli_depend_on_cwd(base_arguments, monkeypatch, tmp_path): + # Issue 572 + arguments = base_arguments + ["--exclude", + "test/fixtures/config-with-relative-path.yml"] + + options1 = cli.get_cli_parser().parse_args(arguments) + assert 'test/test' not in str(options1.exclude_paths[0]) + + test_dir = 'test' + if sys.version_info[:2] < (3, 6): + test_dir = tmp_path / 'test' / 'test' / 'fixtures' + test_dir.mkdir(parents=True) + (test_dir / 'config-with-relative-path.yml').write_text('') + test_dir = test_dir / '..' / '..' + monkeypatch.chdir(test_dir) + options2 = cli.get_cli_parser().parse_args(arguments) + + assert 'test/test' in str(options2.exclude_paths[0]) + + +@pytest.mark.parametrize( + "config_file", + ( + pytest.param("test/fixtures/ansible-config-invalid.yml", id="invalid"), + pytest.param("/dev/null/ansible-config-missing.yml", id="missing") + ), +) +def test_config_failure(base_arguments, config_file): + """Ensures specific config files produce error code 2.""" + with pytest.raises(SystemExit, match="^2$"): + cli.get_config(base_arguments + + ["-c", config_file]) diff --git a/test/TestComparisonToEmptyString.py b/test/TestComparisonToEmptyString.py new file mode 100644 index 0000000..cdbd0fa --- /dev/null +++ b/test/TestComparisonToEmptyString.py @@ -0,0 +1,39 @@ +# pylint: disable=preferred-module # FIXME: remove once migrated per GH-725 +import unittest + +from ansiblelint.rules import RulesCollection +from ansiblelint.rules.ComparisonToEmptyStringRule import ComparisonToEmptyStringRule +from ansiblelint.testing import RunFromText + +SUCCESS_TASKS = ''' +- name: shut down + command: /sbin/shutdown -t now + when: ansible_os_family +''' + +FAIL_TASKS = ''' +- hosts: all + tasks: + - name: shut down + command: /sbin/shutdown -t now + when: ansible_os_family == "" + - name: shut down + command: /sbin/shutdown -t now + when: ansible_os_family !="" +''' + + +class TestComparisonToEmptyStringRule(unittest.TestCase): + collection = RulesCollection() + collection.register(ComparisonToEmptyStringRule()) + + def setUp(self): + self.runner = RunFromText(self.collection) + + def test_success(self): + results = self.runner.run_role_tasks_main(SUCCESS_TASKS) + self.assertEqual(0, len(results)) + + def test_fail(self): + results = self.runner.run_playbook(FAIL_TASKS) + self.assertEqual(2, len(results)) diff --git a/test/TestComparisonToLiteralBool.py b/test/TestComparisonToLiteralBool.py new file mode 100644 index 0000000..041ca1b --- /dev/null +++ b/test/TestComparisonToLiteralBool.py @@ -0,0 +1,69 @@ +# pylint: disable=preferred-module # FIXME: remove once migrated per GH-725 +import unittest + +from ansiblelint.rules import RulesCollection +from ansiblelint.rules.ComparisonToLiteralBoolRule import ComparisonToLiteralBoolRule +from ansiblelint.testing import RunFromText + +PASS_WHEN = ''' +- name: example task + debug: + msg: test + when: my_var +''' + +PASS_WHEN_NOT_FALSE = ''' +- name: example task + debug: + msg: test + when: not my_var +''' + +PASS_WHEN_NOT_NULL = ''' +- name: example task + debug: + msg: test + when: my_var not None +''' + +FAIL_LITERAL_TRUE = ''' +- name: example task + debug: + msg: test + when: my_var == True +''' + +FAIL_LITERAL_FALSE = ''' +- name: example task + debug: + msg: test + when: my_var == false +''' + + +class TestComparisonToLiteralBoolRule(unittest.TestCase): + collection = RulesCollection() + collection.register(ComparisonToLiteralBoolRule()) + + def setUp(self): + self.runner = RunFromText(self.collection) + + def test_when(self): + results = self.runner.run_role_tasks_main(PASS_WHEN) + self.assertEqual(0, len(results)) + + def test_when_not_false(self): + results = self.runner.run_role_tasks_main(PASS_WHEN_NOT_FALSE) + self.assertEqual(0, len(results)) + + def test_when_not_null(self): + results = self.runner.run_role_tasks_main(PASS_WHEN_NOT_NULL) + self.assertEqual(0, len(results)) + + def test_literal_true(self): + results = self.runner.run_role_tasks_main(FAIL_LITERAL_TRUE) + self.assertEqual(1, len(results)) + + def test_literal_false(self): + results = self.runner.run_role_tasks_main(FAIL_LITERAL_FALSE) + self.assertEqual(1, len(results)) diff --git a/test/TestDependenciesInMeta.py b/test/TestDependenciesInMeta.py new file mode 100644 index 0000000..d272120 --- /dev/null +++ b/test/TestDependenciesInMeta.py @@ -0,0 +1,22 @@ +import pytest + +from ansiblelint.runner import Runner + + +@pytest.mark.parametrize( + 'filename', + ( + 'bitbucket', + 'galaxy', + 'github', + 'webserver', + 'gitlab', + ), +) +def test_external_dependency_is_ok(default_rules_collection, filename): + playbook_path = ( + 'test/dependency-in-meta/{filename}.yml'. + format_map(locals()) + ) + good_runner = Runner(default_rules_collection, playbook_path, [], [], []) + assert [] == good_runner.run() diff --git a/test/TestDeprecatedModule.py b/test/TestDeprecatedModule.py new file mode 100644 index 0000000..6741ed4 --- /dev/null +++ b/test/TestDeprecatedModule.py @@ -0,0 +1,32 @@ +# pylint: disable=preferred-module # FIXME: remove once migrated per GH-725 +import unittest + +import pytest + +from ansiblelint.rules import RulesCollection +from ansiblelint.rules.DeprecatedModuleRule import DeprecatedModuleRule +from ansiblelint.testing import ANSIBLE_MAJOR_VERSION, RunFromText + +MODULE_DEPRECATED = ''' +- name: task example + docker: + debug: test +''' + + +class TestDeprecatedModuleRule(unittest.TestCase): + collection = RulesCollection() + collection.register(DeprecatedModuleRule()) + + def setUp(self): + self.runner = RunFromText(self.collection) + + @pytest.mark.xfail( + ANSIBLE_MAJOR_VERSION > (2, 9), + reason='Ansible devel has changed so ansible-lint needs fixing. ' + 'Ref: https://github.com/ansible/ansible-lint/issues/675', + raises=SystemExit, strict=True, + ) + def test_module_deprecated(self): + results = self.runner.run_role_tasks_main(MODULE_DEPRECATED) + self.assertEqual(1, len(results)) diff --git a/test/TestEnvVarsInCommand.py b/test/TestEnvVarsInCommand.py new file mode 100644 index 0000000..e3a1d84 --- /dev/null +++ b/test/TestEnvVarsInCommand.py @@ -0,0 +1,90 @@ +# pylint: disable=preferred-module # FIXME: remove once migrated per GH-725 +import unittest + +from ansiblelint.rules import RulesCollection +from ansiblelint.rules.EnvVarsInCommandRule 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 +''' + + +class TestEnvVarsInCommand(unittest.TestCase): + collection = RulesCollection() + collection.register(EnvVarsInCommandRule()) + + def setUp(self): + self.runner = RunFromText(self.collection) + + def test_success(self): + results = self.runner.run_playbook(SUCCESS_PLAY_TASKS) + self.assertEqual(0, len(results)) + + def test_fail(self): + results = self.runner.run_playbook(FAIL_PLAY_TASKS) + self.assertEqual(2, len(results)) diff --git a/test/TestExamples.py b/test/TestExamples.py new file mode 100644 index 0000000..48a89d3 --- /dev/null +++ b/test/TestExamples.py @@ -0,0 +1,8 @@ +"""Assure samples produced desire outcomes.""" +from ansiblelint.runner import Runner + + +def test_example(default_rules_collection): + """example.yml is expected to have 5 match errors inside.""" + result = Runner(default_rules_collection, 'examples/example.yml', [], [], []).run() + assert len(result) == 5 diff --git a/test/TestFormatter.py b/test/TestFormatter.py new file mode 100644 index 0000000..fd4f122 --- /dev/null +++ b/test/TestFormatter.py @@ -0,0 +1,46 @@ +# Copyright (c) 2016 Will Thames <will@thames.id.au> +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# pylint: disable=preferred-module # FIXME: remove once migrated per GH-725 +import pathlib +import unittest + +from ansiblelint.errors import MatchError +from ansiblelint.formatters import Formatter +from ansiblelint.rules import AnsibleLintRule + + +class TestFormatter(unittest.TestCase): + + def setUp(self): + self.rule = AnsibleLintRule() + self.rule.id = "TCF0001" + self.formatter = Formatter(pathlib.Path.cwd(), True) + + def test_format_coloured_string(self): + match = MatchError("message", 1, "hello", "filename.yml", self.rule) + self.formatter.format(match, True) + + def test_unicode_format_string(self): + match = MatchError(u'\U0001f427', 1, "hello", "filename.yml", self.rule) + self.formatter.format(match, False) + + def test_dict_format_line(self): + match = MatchError("xyz", 1, {'hello': 'world'}, "filename.yml", self.rule,) + self.formatter.format(match, True) diff --git a/test/TestImportIncludeRole.py b/test/TestImportIncludeRole.py new file mode 100644 index 0000000..8b581ff --- /dev/null +++ b/test/TestImportIncludeRole.py @@ -0,0 +1,103 @@ +import pytest + +from ansiblelint.runner import Runner + +ROLE_TASKS_MAIN = ''' +- name: shell instead of command + shell: echo hello world +''' + +ROLE_TASKS_WORLD = ''' +- command: echo this is a task without a name +''' + +PLAY_IMPORT_ROLE = ''' +- hosts: all + + tasks: + - import_role: + name: test-role +''' + +PLAY_IMPORT_ROLE_INCOMPLETE = ''' +- hosts: all + + tasks: + - import_role: + foo: bar +''' + +PLAY_IMPORT_ROLE_INLINE = ''' +- hosts: all + + tasks: + - import_role: name=test-role +''' + +PLAY_INCLUDE_ROLE = ''' +- hosts: all + + tasks: + - include_role: + name: test-role + tasks_from: world +''' + +PLAY_INCLUDE_ROLE_INLINE = ''' +- hosts: all + + tasks: + - include_role: name=test-role tasks_from=world +''' + + +@pytest.fixture +def playbook_path(request, tmp_path): + playbook_text = request.param + role_tasks_dir = tmp_path / 'test-role' / 'tasks' + role_tasks_dir.mkdir(parents=True) + (role_tasks_dir / 'main.yml').write_text(ROLE_TASKS_MAIN) + (role_tasks_dir / 'world.yml').write_text(ROLE_TASKS_WORLD) + 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_IMPORT_ROLE, + ['only when shell functionality is required'], + id='IMPORT_ROLE', + ), + pytest.param(PLAY_IMPORT_ROLE_INLINE, + ['only when shell functionality is require'], + id='IMPORT_ROLE_INLINE', + ), + pytest.param(PLAY_INCLUDE_ROLE, + ['only when shell functionality is require', + 'All tasks should be named'], + id='INCLUDE_ROLE', + ), + pytest.param(PLAY_INCLUDE_ROLE_INLINE, + ['only when shell functionality is require', + 'All tasks should be named'], + id='INCLUDE_ROLE_INLINE', + ), +), indirect=('playbook_path', )) +def test_import_role2(default_rules_collection, playbook_path, messages): + runner = Runner(default_rules_collection, playbook_path, [], [], []) + results = runner.run() + for message in messages: + assert message in str(results) + + +@pytest.mark.parametrize(('playbook_path', 'messages'), ( + pytest.param(PLAY_IMPORT_ROLE_INCOMPLETE, + ["Failed to find required 'name' key in import_role"], + id='IMPORT_ROLE_INCOMPLETE', + ), +), indirect=('playbook_path', )) +def test_invalid_import_role(default_rules_collection, playbook_path, messages): + runner = Runner(default_rules_collection, playbook_path, [], [], []) + results = runner.run() + for message in messages: + assert message in str(results) diff --git a/test/TestImportPlaybook.py b/test/TestImportPlaybook.py new file mode 100644 index 0000000..06492e3 --- /dev/null +++ b/test/TestImportPlaybook.py @@ -0,0 +1,17 @@ +"""Test ability to import playbooks.""" +from ansiblelint.runner import Runner + + +def test_task_hook_import_playbook(default_rules_collection): + """Assures import_playbook includes are recognized.""" + playbook_path = 'test/playbook-import/playbook_parent.yml' + runner = Runner(default_rules_collection, playbook_path, [], [], []) + results = runner.run() + + results_text = str(results) + assert len(runner.playbooks) == 2 + assert len(results) == 2 + # Assures we detected the issues from imported playbook + assert 'Commands should not change things' in results_text + assert '502' in results_text + assert 'All tasks should be named' in results_text diff --git a/test/TestImportWithMalformed.py b/test/TestImportWithMalformed.py new file mode 100644 index 0000000..4f5425c --- /dev/null +++ b/test/TestImportWithMalformed.py @@ -0,0 +1,65 @@ +from collections import namedtuple + +import pytest + +from ansiblelint.runner import Runner + +PlayFile = namedtuple('PlayFile', ['name', 'content']) + + +IMPORT_TASKS_MAIN = PlayFile('import-tasks-main.yml', ''' +- oops this is invalid +''') + +IMPORT_SHELL_PIP = PlayFile('import-tasks-main.yml', ''' +- shell: pip +''') + +PLAY_IMPORT_TASKS = PlayFile('playbook.yml', ''' +- hosts: all + tasks: + - import_tasks: import-tasks-main.yml +''') + + +@pytest.fixture +def play_file_path(tmp_path): + p = tmp_path / 'playbook.yml' + return str(p) + + +@pytest.fixture +def runner(play_file_path, default_rules_collection): + return Runner(default_rules_collection, play_file_path, [], [], []) + + +@pytest.fixture +def _play_files(tmp_path, request): + if request.param is None: + return + for play_file in request.param: + p = tmp_path / play_file.name + p.write_text(play_file.content) + + +@pytest.mark.parametrize( + '_play_files', + ( + pytest.param([IMPORT_SHELL_PIP, PLAY_IMPORT_TASKS], id='Import shell w/ pip'), + pytest.param( + [IMPORT_TASKS_MAIN, PLAY_IMPORT_TASKS], + id='import_tasks w/ malformed import', + marks=pytest.mark.xfail( + reason='Garbage non-tasks sequence is not being ' + 'properly processed. Ref: ' + 'https://github.com/ansible/ansible-lint/issues/707', + raises=AttributeError, + ), + ), + ), + indirect=['_play_files'] +) +@pytest.mark.usefixtures('_play_files') +def test_import_tasks_with_malformed_import(runner): + results = str(runner.run()) + assert 'only when shell functionality is required' in results diff --git a/test/TestIncludeMissFileWithRole.py b/test/TestIncludeMissFileWithRole.py new file mode 100644 index 0000000..126e095 --- /dev/null +++ b/test/TestIncludeMissFileWithRole.py @@ -0,0 +1,122 @@ +import os +from collections import namedtuple + +import pytest + +from ansiblelint.runner import Runner + +PlayFile = namedtuple('PlayFile', ['name', 'content']) + + +PLAY_IN_THE_PLACE = PlayFile('playbook.yml', u''' +- hosts: all + roles: + - include_in_the_place +''') + +PLAY_RELATIVE = PlayFile('playbook.yml', u''' +- hosts: all + roles: + - include_relative +''') + +PLAY_MISS_INCLUDE = PlayFile('playbook.yml', u''' +- hosts: all + roles: + - include_miss +''') + +PLAY_ROLE_INCLUDED_IN_THE_PLACE = PlayFile('roles/include_in_the_place/tasks/main.yml', u''' +--- +- include_tasks: included_file.yml +''') + +PLAY_ROLE_INCLUDED_RELATIVE = PlayFile('roles/include_relative/tasks/main.yml', u''' +--- +- include_tasks: tasks/included_file.yml +''') + +PLAY_ROLE_INCLUDED_MISS = PlayFile('roles/include_miss/tasks/main.yml', u''' +--- +- include_tasks: tasks/noexist_file.yml +''') + +PLAY_INCLUDED_IN_THE_PLACE = PlayFile('roles/include_in_the_place/tasks/included_file.yml', u''' +- debug: + msg: 'was found & included' +''') + +PLAY_INCLUDED_RELATIVE = PlayFile('roles/include_relative/tasks/included_file.yml', u''' +- debug: + msg: 'was found & included' +''') + + +@pytest.fixture +def play_file_path(tmp_path): + p = tmp_path / 'playbook.yml' + return str(p) + + +@pytest.fixture +def runner(play_file_path, default_rules_collection): + return Runner(default_rules_collection, play_file_path, [], [], []) + + +@pytest.fixture +def _play_files(tmp_path, request): + if request.param is None: + return + for play_file in request.param: + print(play_file.name) + p = tmp_path / play_file.name + os.makedirs(os.path.dirname(p), exist_ok=True) + p.write_text(play_file.content) + + +@pytest.mark.parametrize( + '_play_files', + ( + pytest.param([PLAY_MISS_INCLUDE, + PLAY_ROLE_INCLUDED_MISS], + id='no exist file include'), + ), + indirect=['_play_files'] +) +@pytest.mark.usefixtures('_play_files') +def test_cases_warning_message(runner, caplog): + runner.run() + noexist_message_count = 0 + + for record in caplog.records: + print(record) + if "Couldn't open" in str(record): + noexist_message_count += 1 + + assert noexist_message_count == 3 # 3 retries + + +@pytest.mark.parametrize( + '_play_files', + ( + pytest.param([PLAY_IN_THE_PLACE, + PLAY_ROLE_INCLUDED_IN_THE_PLACE, + PLAY_INCLUDED_IN_THE_PLACE], + id='in the place include'), + pytest.param([PLAY_RELATIVE, + PLAY_ROLE_INCLUDED_RELATIVE, + PLAY_INCLUDED_RELATIVE], + id='relative include') + ), + indirect=['_play_files'] +) +@pytest.mark.usefixtures('_play_files') +def test_cases_that_do_not_report(runner, caplog): + runner.run() + noexist_message_count = 0 + + for record in caplog.records: + if "Couldn't open" in str(record): + noexist_message_count += 1 + + assert noexist_message_count == 0 diff --git a/test/TestIncludeMissingFileRule.py b/test/TestIncludeMissingFileRule.py new file mode 100644 index 0000000..7248fdc --- /dev/null +++ b/test/TestIncludeMissingFileRule.py @@ -0,0 +1,88 @@ +from collections import namedtuple + +import pytest + +from ansiblelint.runner import Runner + +PlayFile = namedtuple('PlayFile', ['name', 'content']) + + +PLAY_INCLUDING_PLAIN = PlayFile('playbook.yml', u''' +- hosts: all + tasks: + - include: some_file.yml +''') + +PLAY_INCLUDING_JINJA2 = PlayFile('playbook.yml', u''' +- hosts: all + tasks: + - include: "{{ some_path }}/some_file.yml" +''') + +PLAY_INCLUDING_NOQA = PlayFile('playbook.yml', u''' +- hosts: all + tasks: + - include: some_file.yml # noqa 505 +''') + +PLAY_INCLUDED = PlayFile('some_file.yml', u''' +- debug: + msg: 'was found & included' +''') + +PLAY_HAVING_TASK = PlayFile('playbook.yml', u''' +- name: Play + hosts: all + pre_tasks: + tasks: + - name: Ping + ping: +''') + + +@pytest.fixture +def play_file_path(tmp_path): + p = tmp_path / 'playbook.yml' + return str(p) + + +@pytest.fixture +def runner(play_file_path, default_rules_collection): + return Runner(default_rules_collection, play_file_path, [], [], []) + + +@pytest.fixture +def _play_files(tmp_path, request): + if request.param is None: + return + for play_file in request.param: + p = tmp_path / play_file.name + p.write_text(play_file.content) + + +@pytest.mark.parametrize( + '_play_files', (pytest.param([PLAY_INCLUDING_PLAIN], id='referenced file missing'), ), + indirect=['_play_files'] +) +@pytest.mark.usefixtures('_play_files') +def test_include_file_missing(runner): + results = str(runner.run()) + assert 'referenced missing file in' in results + assert 'playbook.yml' in results + assert 'some_file.yml' in results + + +@pytest.mark.parametrize( + '_play_files', + ( + pytest.param([PLAY_INCLUDING_PLAIN, PLAY_INCLUDED], id='File Exists'), + pytest.param([PLAY_INCLUDING_JINJA2], id='JINJA2 in reference'), + pytest.param([PLAY_INCLUDING_NOQA], id='NOQA was used'), + pytest.param([PLAY_HAVING_TASK], id='Having a task') + ), + indirect=['_play_files'] +) +@pytest.mark.usefixtures('_play_files') +def test_cases_that_do_not_report(runner): + results = str(runner.run()) + assert 'referenced missing file in' not in results diff --git a/test/TestLineNumber.py b/test/TestLineNumber.py new file mode 100644 index 0000000..9355daa --- /dev/null +++ b/test/TestLineNumber.py @@ -0,0 +1,36 @@ +# Copyright (c) 2020 Albin Vass <albin.vass@gmail.com> +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +from ansiblelint.rules.SudoRule import SudoRule + +TEST_TASKLIST = """ +- debug: + msg: test + +- command: echo test + sudo: true +""" + + +def test_rule_linenumber(monkeypatch): + """Check that SudoRule offense contains a line number.""" + rule = SudoRule() + matches = rule.matchyaml(dict(path="", type='tasklist'), TEST_TASKLIST) + assert matches[0].linenumber == 5 diff --git a/test/TestLineTooLong.py b/test/TestLineTooLong.py new file mode 100644 index 0000000..be8c6f7 --- /dev/null +++ b/test/TestLineTooLong.py @@ -0,0 +1,24 @@ +# pylint: disable=preferred-module # FIXME: remove once migrated per GH-725 +import unittest + +from ansiblelint.rules import RulesCollection +from ansiblelint.rules.LineTooLongRule import LineTooLongRule +from ansiblelint.testing import RunFromText + +LONG_LINE = ''' +- name: task example + debug: + msg: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua tempor incididunt ut labore et dolore' +''' # noqa 501 + + +class TestLineTooLongRule(unittest.TestCase): + collection = RulesCollection() + collection.register(LineTooLongRule()) + + def setUp(self): + self.runner = RunFromText(self.collection) + + def test_long_line(self): + results = self.runner.run_role_tasks_main(LONG_LINE) + self.assertEqual(1, len(results)) diff --git a/test/TestLintRule.py b/test/TestLintRule.py new file mode 100644 index 0000000..7377443 --- /dev/null +++ b/test/TestLintRule.py @@ -0,0 +1,45 @@ +# Copyright (c) 2013-2014 Will Thames <will@thames.id.au> +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +# pylint: disable=preferred-module # FIXME: remove once migrated per GH-725 +import unittest + +from .rules import EMatcherRule, UnsetVariableMatcherRule + + +class TestRule(unittest.TestCase): + + def test_rule_matching(self): + text = "" + filename = 'test/ematchtest.yml' + with open(filename) as f: + text = f.read() + ematcher = EMatcherRule.EMatcherRule() + matches = ematcher.matchlines(dict(path=filename, type='playbooks'), text) + self.assertEqual(len(matches), 3) + + def test_rule_postmatching(self): + text = "" + filename = 'test/bracketsmatchtest.yml' + with open(filename) as f: + text = f.read() + rule = UnsetVariableMatcherRule.UnsetVariableMatcherRule() + matches = rule.matchlines(dict(path=filename, type='playbooks'), text) + self.assertEqual(len(matches), 2) diff --git a/test/TestLocalContent.py b/test/TestLocalContent.py new file mode 100644 index 0000000..e78aab4 --- /dev/null +++ b/test/TestLocalContent.py @@ -0,0 +1,42 @@ +"""Test playbooks with local content.""" +import pytest + +from ansiblelint.runner import Runner + + +def test_local_collection(default_rules_collection): + """Assures local collections are found.""" + playbook_path = 'test/local-content/test-collection.yml' + runner = Runner(default_rules_collection, playbook_path, [], [], []) + results = runner.run() + + assert len(runner.playbooks) == 1 + assert len(results) == 0 + + +def test_roles_local_content(default_rules_collection): + """Assures local content in roles is found.""" + playbook_path = 'test/local-content/test-roles-success/test.yml' + runner = Runner(default_rules_collection, playbook_path, [], [], []) + results = runner.run() + + assert len(runner.playbooks) == 4 + assert len(results) == 0 + + +def test_roles_local_content_failure(default_rules_collection): + """Assures local content in roles is found, even if Ansible itself has trouble.""" + playbook_path = 'test/local-content/test-roles-failed/test.yml' + runner = Runner(default_rules_collection, playbook_path, [], [], []) + results = runner.run() + + assert len(runner.playbooks) == 4 + assert len(results) == 0 + + +def test_roles_local_content_failure_complete(default_rules_collection): + """Role with local content that is not found.""" + playbook_path = 'test/local-content/test-roles-failed-complete/test.yml' + runner = Runner(default_rules_collection, playbook_path, [], [], []) + with pytest.raises(SystemExit, match="^3$"): + runner.run() diff --git a/test/TestMatchError.py b/test/TestMatchError.py new file mode 100644 index 0000000..f17d865 --- /dev/null +++ b/test/TestMatchError.py @@ -0,0 +1,178 @@ +"""Tests for MatchError.""" + +import operator + +import pytest + +from ansiblelint.errors import MatchError +from ansiblelint.rules import AnsibleLintRule +from ansiblelint.rules.AlwaysRunRule import AlwaysRunRule +from ansiblelint.rules.BecomeUserWithoutBecomeRule import BecomeUserWithoutBecomeRule + + +class DummyTestObject: + """A dummy object for equality tests.""" + + def __repr__(self): + """Return a dummy object representation for parmetrize.""" + return '{self.__class__.__name__}()'.format(self=self) + + def __eq__(self, other): + """Report the equality check failure with any object.""" + return False + + def __ne__(self, other): + """Report the confirmation of inequality with any object.""" + return True + + +class DummySentinelTestObject: + """A dummy object for equality protocol tests with sentinel.""" + + def __eq__(self, other): + """Return sentinel as result of equality check w/ anything.""" + return 'EQ_SENTINEL' + + def __ne__(self, other): + """Return sentinel as result of inequality check w/ anything.""" + return 'NE_SENTINEL' + + def __lt__(self, other): + """Return sentinel as result of less than check w/ anything.""" + return 'LT_SENTINEL' + + def __gt__(self, other): + """Return sentinel as result of greater than chk w/ anything.""" + return 'GT_SENTINEL' + + +@pytest.mark.parametrize( + ('left_match_error', 'right_match_error'), + ( + (MatchError("foo"), MatchError("foo")), + (MatchError("a", details="foo"), MatchError("a", details="foo")), + ), +) +def test_matcherror_compare(left_match_error, right_match_error): + """Check that MatchError instances with similar attrs are equivalent.""" + assert left_match_error == right_match_error + + +class AnsibleLintRuleWithStringId(AnsibleLintRule): + id = "ANSIBLE200" + + +def test_matcherror_invalid(): + """Ensure that MatchError requires message or rule.""" + expected_err = r"^MatchError\(\) missing a required argument: one of 'message' or 'rule'$" + with pytest.raises(TypeError, match=expected_err): + MatchError() + + +@pytest.mark.parametrize( + ('left_match_error', 'right_match_error'), ( + # sorting by message + (MatchError("z"), MatchError("a")), + # filenames takes priority in sorting + (MatchError("a", filename="b"), MatchError("a", filename="a")), + # rule id 501 > rule id 101 + (MatchError(rule=BecomeUserWithoutBecomeRule), MatchError(rule=AlwaysRunRule)), + # rule id "200" > rule id 101 + (MatchError(rule=AnsibleLintRuleWithStringId), MatchError(rule=AlwaysRunRule)), + # details are taken into account + (MatchError("a", details="foo"), MatchError("a", details="bar")), + )) +class TestMatchErrorCompare: + + def test_match_error_less_than(self, left_match_error, right_match_error): + """Check 'less than' protocol implementation in MatchError.""" + assert right_match_error < left_match_error + + def test_match_error_greater_than(self, left_match_error, right_match_error): + """Check 'greater than' protocol implementation in MatchError.""" + assert left_match_error > right_match_error + + def test_match_error_not_equal(self, left_match_error, right_match_error): + """Check 'not equals' protocol implementation in MatchError.""" + assert left_match_error != right_match_error + + +@pytest.mark.parametrize( + 'other', + ( + None, + "foo", + 42, + Exception("foo"), + ), + ids=repr, +) +@pytest.mark.parametrize( + ('operation', 'operator_char'), + ( + pytest.param(operator.le, '<=', id='<='), + pytest.param(operator.gt, '>', id='>'), + ), +) +def test_matcherror_compare_no_other_fallback(other, operation, operator_char): + """Check that MatchError comparison with other types causes TypeError.""" + expected_error = ( + r'^(' + r'unsupported operand type\(s\) for {operator!s}:|' + r"'{operator!s}' not supported between instances of" + r") 'MatchError' and '{other_type!s}'$". + format(other_type=type(other).__name__, operator=operator_char) + ) + with pytest.raises(TypeError, match=expected_error): + operation(MatchError("foo"), other) + + +@pytest.mark.parametrize( + 'other', + ( + None, + 'foo', + 42, + Exception('foo'), + DummyTestObject(), + ), + ids=repr, +) +@pytest.mark.parametrize( + ('operation', 'expected_value'), + ( + (operator.eq, False), + (operator.ne, True), + ), + ids=('==', '!=') +) +def test_matcherror_compare_with_other_fallback( + other, + operation, + expected_value, +): + """Check that MatchError comparison runs other types fallbacks.""" + assert operation(MatchError("foo"), other) is expected_value + + +@pytest.mark.parametrize( + ('operation', 'expected_value'), + ( + (operator.eq, 'EQ_SENTINEL'), + (operator.ne, 'NE_SENTINEL'), + # NOTE: these are swapped because when we do `x < y`, and `x.__lt__(y)` + # NOTE: returns `NotImplemented`, Python will reverse the check into + # NOTE: `y > x`, and so `y.__gt__(x) is called. + # Ref: https://docs.python.org/3/reference/datamodel.html#object.__lt__ + (operator.lt, 'GT_SENTINEL'), + (operator.gt, 'LT_SENTINEL'), + ), + ids=('==', '!=', '<', '>'), +) +def test_matcherror_compare_with_dummy_sentinel(operation, expected_value): + """Check that MatchError comparison runs other types fallbacks.""" + dummy_obj = DummySentinelTestObject() + # NOTE: This assertion abuses the CPython property to cache short string + # NOTE: objects because the identity check is more presice and we don't + # NOTE: want extra operator protocol methods to influence the test. + assert operation(MatchError("foo"), dummy_obj) is expected_value diff --git a/test/TestMetaChangeFromDefault.py b/test/TestMetaChangeFromDefault.py new file mode 100644 index 0000000..911553a --- /dev/null +++ b/test/TestMetaChangeFromDefault.py @@ -0,0 +1,33 @@ +# pylint: disable=preferred-module # FIXME: remove once migrated per GH-725 +import unittest + +from ansiblelint.rules import RulesCollection +from ansiblelint.rules.MetaChangeFromDefaultRule import MetaChangeFromDefaultRule +from ansiblelint.testing import RunFromText + +DEFAULT_GALAXY_INFO = ''' +galaxy_info: + author: your name + description: your description + company: your company (optional) + license: license (GPLv2, CC-BY, etc) +''' + + +class TestMetaChangeFromDefault(unittest.TestCase): + collection = RulesCollection() + collection.register(MetaChangeFromDefaultRule()) + + def setUp(self): + self.runner = RunFromText(self.collection) + + def test_default_galaxy_info(self): + results = self.runner.run_role_meta_main(DEFAULT_GALAXY_INFO) + self.assertIn("Should change default metadata: author", + str(results)) + self.assertIn("Should change default metadata: description", + str(results)) + self.assertIn("Should change default metadata: company", + str(results)) + self.assertIn("Should change default metadata: license", + str(results)) diff --git a/test/TestMetaMainHasInfo.py b/test/TestMetaMainHasInfo.py new file mode 100644 index 0000000..757a4df --- /dev/null +++ b/test/TestMetaMainHasInfo.py @@ -0,0 +1,94 @@ +# pylint: disable=preferred-module # FIXME: remove once migrated per GH-725 +import unittest + +from ansiblelint.rules import RulesCollection +from ansiblelint.rules.MetaMainHasInfoRule import MetaMainHasInfoRule +from ansiblelint.testing import RunFromText + +NO_GALAXY_INFO = ''' +author: the author +description: this meta/main.yml has no galaxy_info +''' + +MISSING_INFO = ''' +galaxy_info: + # author: the author + description: Testing if meta contains values + company: Not applicable + + license: MIT + + # min_ansible_version: 2.5 + + platforms: + - name: Fedora + versions: + - 25 + - missing_name: No name + versions: + - 25 +''' + +BAD_TYPES = ''' +galaxy_info: + author: 007 + description: ['Testing meta'] + company: Not applicable + + license: MIT + + min_ansible_version: 2.5 + + platforms: Fedora +''' + +PLATFORMS_LIST_OF_STR = ''' +galaxy_info: + author: '007' + description: 'Testing meta' + company: Not applicable + + license: MIT + + min_ansible_version: 2.5 + + platforms: ['Fedora', 'EL'] +''' + + +class TestMetaMainHasInfo(unittest.TestCase): + collection = RulesCollection() + collection.register(MetaMainHasInfoRule()) + + def setUp(self): + self.runner = RunFromText(self.collection) + + def test_no_galaxy_info(self): + results = self.runner.run_role_meta_main(NO_GALAXY_INFO) + assert len(results) == 1 + self.assertIn("No 'galaxy_info' found", + str(results)) + + def test_missing_info(self): + results = self.runner.run_role_meta_main(MISSING_INFO) + assert len(results) == 3 + self.assertIn("Role info should contain author", + str(results)) + self.assertIn("Role info should contain min_ansible_version", + str(results)) + self.assertIn("Platform should contain name", + str(results)) + + def test_bad_types(self): + results = self.runner.run_role_meta_main(BAD_TYPES) + assert len(results) == 3 + self.assertIn("author should be a string", str(results)) + self.assertIn("description should be a string", str(results)) + self.assertIn("Platforms should be a list of dictionaries", + str(results)) + + def test_platform_list_of_str(self): + results = self.runner.run_role_meta_main(PLATFORMS_LIST_OF_STR) + assert len(results) == 1 + self.assertIn("Platforms should be a list of dictionaries", + str(results)) diff --git a/test/TestMetaVideoLinks.py b/test/TestMetaVideoLinks.py new file mode 100644 index 0000000..4d61891 --- /dev/null +++ b/test/TestMetaVideoLinks.py @@ -0,0 +1,35 @@ +# pylint: disable=preferred-module # FIXME: remove once migrated per GH-725 +import unittest + +from ansiblelint.rules import RulesCollection +from ansiblelint.rules.MetaVideoLinksRule import MetaVideoLinksRule +from ansiblelint.testing import RunFromText + +META_VIDEO_LINKS = ''' +galaxy_info: + video_links: + - url: https://youtu.be/aWmRepTSFKs + title: Proper format + - https://youtu.be/this_is_not_a_dictionary + - my_bad_key: https://youtu.be/aWmRepTSFKs + title: This has a bad key + - url: www.myvid.com/vid + title: Bad format of url +''' + + +class TestMetaVideoLinks(unittest.TestCase): + collection = RulesCollection() + collection.register(MetaVideoLinksRule()) + + def setUp(self): + self.runner = RunFromText(self.collection) + + def test_video_links(self): + results = self.runner.run_role_meta_main(META_VIDEO_LINKS) + self.assertIn("Expected item in 'video_links' to be a dictionary", + str(results)) + self.assertIn("'video_links' to contain only keys 'url' and 'title'", + str(results)) + self.assertIn("URL format 'www.myvid.com/vid' is not recognized", + str(results)) diff --git a/test/TestMissingFilePermissionsRule.py b/test/TestMissingFilePermissionsRule.py new file mode 100644 index 0000000..0a67ae1 --- /dev/null +++ b/test/TestMissingFilePermissionsRule.py @@ -0,0 +1,110 @@ +# Copyright (c) 2020 Sorin Sbarnea <sorin.sbarnea@gmail.com> +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +"""MissingFilePermissionsRule tests.""" +import pytest + +from ansiblelint.rules.MissingFilePermissionsRule import MissingFilePermissionsRule + +SUCCESS_TASKS = ''' +--- +- hosts: hosts + tasks: + - name: permissions not missing and numeric + file: + path: foo + mode: 0600 + - name: permissions missing while state is absent is fine + file: + path: foo + state: absent + - name: permissions missing while state is file (default) is fine + file: + path: foo + - name: permissions missing while state is link is fine + file: + path: foo2 + src: foo + state: link + - name: file edit when create is false + lineinfile: + path: foo + create: false + line: some content here + - name: replace should not require mode + replace: + path: foo +''' + +FAIL_TASKS = ''' +--- +- hosts: hosts + tasks: + - name: file does not allow preserve value for mode + file: + path: foo + mode: preserve + - name: permissions missing and might create file + file: + path: foo + state: touch + - name: permissions missing and might create directory + file: + path: foo + state: directory + - name: permissions needed if create is used + ini_file: + path: foo + create: true + - name: lineinfile when create is true + lineinfile: + path: foo + create: true + line: some content here + - name: replace does not allow preserve mode + replace: + path: foo + mode: preserve + - name: ini_file does not accept preserve mode + ini_file: + path: foo + create: true + mode: preserve +''' + + +@pytest.mark.parametrize('rule_runner', (MissingFilePermissionsRule, ), indirect=['rule_runner']) +def test_success(rule_runner): + """Validate that mode presence avoids hitting the rule.""" + results = rule_runner.run_playbook(SUCCESS_TASKS) + assert len(results) == 0 + + +@pytest.mark.parametrize('rule_runner', (MissingFilePermissionsRule, ), indirect=['rule_runner']) +def test_fail(rule_runner): + """Validate that missing mode triggers the rule.""" + results = rule_runner.run_playbook(FAIL_TASKS) + assert len(results) == 7 + assert results[0].linenumber == 5 + assert results[1].linenumber == 9 + assert results[2].linenumber == 13 + assert results[3].linenumber == 17 + assert results[4].linenumber == 21 + assert results[5].linenumber == 26 + assert results[6].linenumber == 30 diff --git a/test/TestNestedJinjaRule.py b/test/TestNestedJinjaRule.py new file mode 100644 index 0000000..f8367b0 --- /dev/null +++ b/test/TestNestedJinjaRule.py @@ -0,0 +1,208 @@ +# -*- coding: utf-8 -*- +# Author: Adrián Tóth <adtoth@redhat.com> +# +# Copyright (c) 2020, Red Hat, Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +from collections import namedtuple + +import pytest + +from ansiblelint.runner import Runner + +PlayFile = namedtuple('PlayFile', ['name', 'content']) + + +FAIL_TASK_1LN = PlayFile('playbook.yml', ''' +- hosts: all + tasks: + - name: one-level nesting + set_fact: + var_one: "2*(1+2) is {{ 2 * {{ 1 + 2 }} }}" +''') + +FAIL_TASK_1LN_M = PlayFile('playbook.yml', ''' +- hosts: all + tasks: + - name: one-level multiline nesting + set_fact: + var_one_ml: > + 2*(1+2) is {{ 2 * + {{ 1 + 2 }} + }} +''') + +FAIL_TASK_2LN = PlayFile('playbook.yml', ''' +- hosts: all + tasks: + - name: two-level nesting + set_fact: + var_two: "2*(1+(3-1)) is {{ 2 * {{ 1 + {{ 3 - 1 }} }} }}" +''') + +FAIL_TASK_2LN_M = PlayFile('playbook.yml', ''' +- hosts: all + tasks: + - name: two-level multiline nesting + set_fact: + var_two_ml: > + 2*(1+(3-1)) is {{ 2 * + {{ 1 + + {{ 3 - 1 }} + }} }} +''') + +FAIL_TASK_W_5LN = PlayFile('playbook.yml', ''' +- hosts: all + tasks: + - name: five-level wild nesting + set_fact: + var_three_wld: "{{ {{ {{ {{ {{ 234 }} }} }} }} }}" +''') + +FAIL_TASK_W_5LN_M = PlayFile('playbook.yml', ''' +- hosts: all + tasks: + - name: five-level wild multiline nesting + set_fact: + var_three_wld_ml: > + {{ + {{ + {{ + {{ + {{ 234 }} + }} + }} + }} + }} +''') + +SUCCESS_TASK_P = PlayFile('playbook.yml', ''' +- hosts: all + tasks: + - name: non-nested example + set_fact: + var_one: "number for 'one' is {{ 2 * 1 }}" +''') + +SUCCESS_TASK_P_M = PlayFile('playbook.yml', ''' +- hosts: all + tasks: + - name: multiline non-nested example + set_fact: + var_one_ml: > + number for 'one' is {{ + 2 * 1 }} +''') + +SUCCESS_TASK_2P = PlayFile('playbook.yml', ''' +- hosts: all + tasks: + - name: nesting far from each other + set_fact: + var_two: "number for 'two' is {{ 2 * 1 }} and number for 'three' is {{ 4 - 1 }}" +''') + +SUCCESS_TASK_2P_M = PlayFile('playbook.yml', ''' +- hosts: all + tasks: + - name: multiline nesting far from each other + set_fact: + var_two_ml: > + number for 'two' is {{ 2 * 1 + }} and number for 'three' is {{ + 4 - 1 }} +''') + +SUCCESS_TASK_C_2P = PlayFile('playbook.yml', ''' +- hosts: all + tasks: + - name: nesting close to each other + set_fact: + var_three: "number for 'ten' is {{ 2 - 1 }}{{ 3 - 3 }}" +''') + +SUCCESS_TASK_C_2P_M = PlayFile('playbook.yml', ''' +- hosts: all + tasks: + - name: multiline nesting close to each other + set_fact: + var_three_ml: > + number for 'ten' is {{ + 2 - 1 + }}{{ 3 - 3 }} +''') + + +@pytest.fixture +def runner(tmp_path, default_rules_collection): + return Runner( + default_rules_collection, + str(tmp_path / 'playbook.yml'), + [], [], [], + ) + + +@pytest.fixture +def _playbook_file(tmp_path, request): + if request.param is None: + return + for play_file in request.param: + p = tmp_path / play_file.name + p.write_text(play_file.content) + + +@pytest.mark.parametrize( + '_playbook_file', + ( + pytest.param([FAIL_TASK_1LN], id='file includes one-level nesting'), + pytest.param([FAIL_TASK_1LN_M], id='file includes one-level multiline nesting'), + pytest.param([FAIL_TASK_2LN], id='file includes two-level nesting'), + pytest.param([FAIL_TASK_2LN_M], id='file includes two-level multiline nesting'), + pytest.param([FAIL_TASK_W_5LN], id='file includes five-level wild nesting'), + pytest.param([FAIL_TASK_W_5LN_M], id='file includes five-level wild multiline nesting'), + ), + indirect=['_playbook_file'], +) +@pytest.mark.usefixtures('_playbook_file') +def test_including_wrong_nested_jinja(runner): + rule_violations = runner.run() + assert rule_violations[0].rule.id == '207' + + +@pytest.mark.parametrize( + '_playbook_file', + ( + pytest.param([SUCCESS_TASK_P], id='file includes non-nested example'), + pytest.param([SUCCESS_TASK_P_M], id='file includes multiline non-nested example'), + pytest.param([SUCCESS_TASK_2P], id='file includes nesting far from each other'), + pytest.param([SUCCESS_TASK_2P_M], id='file includes multiline nesting far from each other'), + pytest.param([SUCCESS_TASK_C_2P], id='file includes nesting close to each other'), + pytest.param( + [SUCCESS_TASK_C_2P_M], + id='file includes multiline nesting close to each other', + ), + ), + indirect=['_playbook_file'], +) +@pytest.mark.usefixtures('_playbook_file') +def test_including_proper_nested_jinja(runner): + rule_violations = runner.run() + assert not rule_violations diff --git a/test/TestNoFormattingInWhenRule.py b/test/TestNoFormattingInWhenRule.py new file mode 100644 index 0000000..f9fde4a --- /dev/null +++ b/test/TestNoFormattingInWhenRule.py @@ -0,0 +1,24 @@ +# pylint: disable=preferred-module # FIXME: remove once migrated per GH-725 +import unittest + +from ansiblelint.rules import RulesCollection +from ansiblelint.rules.NoFormattingInWhenRule import NoFormattingInWhenRule +from ansiblelint.runner import Runner + + +class TestNoFormattingInWhenRule(unittest.TestCase): + collection = RulesCollection() + + def setUp(self): + self.collection.register(NoFormattingInWhenRule()) + + def test_file_positive(self): + success = 'test/jinja2-when-success.yml' + good_runner = Runner(self.collection, success, [], [], []) + self.assertEqual([], good_runner.run()) + + def test_file_negative(self): + failure = 'test/jinja2-when-failure.yml' + bad_runner = Runner(self.collection, failure, [], [], []) + errs = bad_runner.run() + self.assertEqual(2, len(errs)) diff --git a/test/TestOctalPermissions.py b/test/TestOctalPermissions.py new file mode 100644 index 0000000..ed8c79c --- /dev/null +++ b/test/TestOctalPermissions.py @@ -0,0 +1,112 @@ +# pylint: disable=preferred-module # FIXME: remove once migrated per GH-725 +import unittest + +from ansiblelint.rules import RulesCollection +from ansiblelint.rules.OctalPermissionsRule import OctalPermissionsRule +from ansiblelint.testing import RunFromText + +SUCCESS_TASKS = ''' +--- +- hosts: hosts + vars: + varset: varset + tasks: + - name: octal permissions test success (0600) + file: + path: foo + mode: 0600 + + - name: octal permissions test success (0000) + file: + path: foo + mode: 0000 + + - name: octal permissions test success (02000) + file: + path: bar + mode: 02000 + + - name: octal permissions test success (02751) + file: + path: bar + mode: 02751 + + - name: octal permissions test success (0777) + file: path=baz mode=0777 + + - name: octal permissions test success (0711) + file: path=baz mode=0711 + + - name: permissions test success (0777) + file: path=baz mode=u+rwx + + - name: octal permissions test success (777) + file: path=baz mode=777 + + - name: octal permissions test success (733) + file: path=baz mode=733 +''' + +FAIL_TASKS = ''' +--- +- hosts: hosts + vars: + varset: varset + tasks: + - name: octal permissions test fail (600) + file: + path: foo + mode: 600 + + - name: octal permissions test fail (710) + file: + path: foo + mode: 710 + + - name: octal permissions test fail (123) + file: + path: foo + mode: 123 + + - name: octal permissions test fail (2000) + file: + path: bar + mode: 2000 +''' + + +class TestOctalPermissionsRuleWithFile(unittest.TestCase): + + collection = RulesCollection() + VALID_MODES = [0o777, 0o775, 0o770, 0o755, 0o750, 0o711, 0o710, 0o700, + 0o666, 0o664, 0o660, 0o644, 0o640, 0o600, + 0o555, 0o551, 0o550, 0o511, 0o510, 0o500, + 0o444, 0o440, 0o400] + + INVALID_MODES = [777, 775, 770, 755, 750, 711, 710, 700, + 666, 664, 660, 644, 640, 622, 620, 600, + 555, 551, 550, # 511 == 0o777, 510 == 0o776, 500 == 0o764 + 444, 440, 400] + + def setUp(self): + self.rule = OctalPermissionsRule() + self.collection.register(self.rule) + self.runner = RunFromText(self.collection) + + def test_success(self): + results = self.runner.run_playbook(SUCCESS_TASKS) + self.assertEqual(0, len(results)) + + def test_fail(self): + results = self.runner.run_playbook(FAIL_TASKS) + self.assertEqual(4, len(results)) + + def test_valid_modes(self): + for mode in self.VALID_MODES: + self.assertFalse(self.rule.is_invalid_permission(mode), + msg="0o%o should be a valid mode" % mode) + + def test_invalid_modes(self): + for mode in self.INVALID_MODES: + self.assertTrue(self.rule.is_invalid_permission(mode), + msg="%d should be an invalid mode" % mode) diff --git a/test/TestPackageIsNotLatest.py b/test/TestPackageIsNotLatest.py new file mode 100644 index 0000000..91bf24c --- /dev/null +++ b/test/TestPackageIsNotLatest.py @@ -0,0 +1,24 @@ +# pylint: disable=preferred-module # FIXME: remove once migrated per GH-725 +import unittest + +from ansiblelint.rules import RulesCollection +from ansiblelint.rules.PackageIsNotLatestRule import PackageIsNotLatestRule +from ansiblelint.runner import Runner + + +class TestPackageIsNotLatestRule(unittest.TestCase): + collection = RulesCollection() + + def setUp(self): + self.collection.register(PackageIsNotLatestRule()) + + def test_package_not_latest_positive(self): + success = 'test/package-check-success.yml' + good_runner = Runner(self.collection, success, [], [], []) + self.assertEqual([], good_runner.run()) + + def test_package_not_latest_negative(self): + failure = 'test/package-check-failure.yml' + bad_runner = Runner(self.collection, failure, [], [], []) + errs = bad_runner.run() + self.assertEqual(3, len(errs)) diff --git a/test/TestRoleHandlers.py b/test/TestRoleHandlers.py new file mode 100644 index 0000000..9f38320 --- /dev/null +++ b/test/TestRoleHandlers.py @@ -0,0 +1,20 @@ +# pylint: disable=preferred-module # FIXME: remove once migrated per GH-725 +import unittest + +from ansiblelint.rules import RulesCollection +from ansiblelint.rules.UseHandlerRatherThanWhenChangedRule import ( + UseHandlerRatherThanWhenChangedRule, +) +from ansiblelint.runner import Runner + + +class TestRoleHandlers(unittest.TestCase): + collection = RulesCollection() + + def setUp(self): + self.collection.register(UseHandlerRatherThanWhenChangedRule()) + + def test_role_handler_positive(self): + success = 'test/role-with-handler/main.yml' + good_runner = Runner(self.collection, success, [], [], []) + self.assertEqual([], good_runner.run()) diff --git a/test/TestRoleNames.py b/test/TestRoleNames.py new file mode 100644 index 0000000..9441a50 --- /dev/null +++ b/test/TestRoleNames.py @@ -0,0 +1,82 @@ +"""Test the RoleNames rule.""" + +import pytest + +from ansiblelint.rules import RulesCollection +from ansiblelint.rules.RoleNames import RoleNames +from ansiblelint.runner import Runner + +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 +def test_rules_collection(): + """Instantiate a roles collection for tests.""" + collection = RulesCollection() + collection.register(RoleNames()) + return collection + + +def dict_to_files(parent_dir, file_dict): + """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 +def playbook_path(request, tmp_path): + """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, playbook_path, messages): + """Lint a playbook and compare the expected messages with the actual messages.""" + runner = Runner(test_rules_collection, playbook_path, [], [], []) + 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/TestRoleRelativePath.py b/test/TestRoleRelativePath.py new file mode 100644 index 0000000..6c3163b --- /dev/null +++ b/test/TestRoleRelativePath.py @@ -0,0 +1,52 @@ +# pylint: disable=preferred-module # FIXME: remove once migrated per GH-725 +import unittest + +from ansiblelint.rules import RulesCollection +from ansiblelint.rules.RoleRelativePath import RoleRelativePath +from ansiblelint.testing import RunFromText + +FAIL_TASKS = ''' +- name: template example + template: + src: ../templates/foo.j2 + dest: /etc/file.conf +- name: copy example + copy: + src: ../files/foo.conf + dest: /etc/foo.conf +- name: win_template example + win_template: + src: ../win_templates/file.conf.j2 + dest: file.conf +- name: win_copy example + win_copy: + src: ../files/foo.conf + dest: renamed-foo.conf +''' + +SUCCESS_TASKS = ''' +- name: content example with no src + copy: + content: '# This file was moved to /etc/other.conf' + dest: /etc/mine.conf +- name: content example with no src + win_copy: + content: '# This file was moved to /etc/other.conf' + dest: /etc/mine.conf +''' + + +class TestRoleRelativePath(unittest.TestCase): + collection = RulesCollection() + collection.register(RoleRelativePath()) + + def setUp(self): + self.runner = RunFromText(self.collection) + + def test_fail(self): + results = self.runner.run_role_tasks_main(FAIL_TASKS) + self.assertEqual(4, len(results)) + + def test_success(self): + results = self.runner.run_role_tasks_main(SUCCESS_TASKS) + self.assertEqual(0, len(results)) diff --git a/test/TestRuleProperties.py b/test/TestRuleProperties.py new file mode 100644 index 0000000..41f40e0 --- /dev/null +++ b/test/TestRuleProperties.py @@ -0,0 +1,11 @@ +def test_serverity_valid(default_rules_collection): + valid_severity_values = [ + 'VERY_HIGH', + 'HIGH', + 'MEDIUM', + 'LOW', + 'VERY_LOW', + 'INFO', + ] + for rule in default_rules_collection: + assert rule.severity in valid_severity_values diff --git a/test/TestRulesCollection.py b/test/TestRulesCollection.py new file mode 100644 index 0000000..52d7dc6 --- /dev/null +++ b/test/TestRulesCollection.py @@ -0,0 +1,112 @@ +# Copyright (c) 2013-2014 Will Thames <will@thames.id.au> +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +import collections +import os + +import pytest + +from ansiblelint.rules import RulesCollection +from ansiblelint.testing import run_ansible_lint + + +@pytest.fixture +def test_rules_collection(): + return RulesCollection([os.path.abspath('./test/rules')]) + + +@pytest.fixture +def ematchtestfile(): + return dict(path='test/ematchtest.yml', type='playbook') + + +@pytest.fixture +def bracketsmatchtestfile(): + return dict(path='test/bracketsmatchtest.yml', type='playbook') + + +def test_load_collection_from_directory(test_rules_collection): + assert len(test_rules_collection) == 2 + + +def test_run_collection(test_rules_collection, ematchtestfile): + matches = test_rules_collection.run(ematchtestfile) + assert len(matches) == 3 + + +def test_tags(test_rules_collection, ematchtestfile, bracketsmatchtestfile): + matches = test_rules_collection.run(ematchtestfile, tags=['test1']) + assert len(matches) == 3 + matches = test_rules_collection.run(ematchtestfile, tags=['test2']) + assert len(matches) == 0 + matches = test_rules_collection.run(bracketsmatchtestfile, tags=['test1']) + assert len(matches) == 1 + matches = test_rules_collection.run(bracketsmatchtestfile, tags=['test2']) + assert len(matches) == 2 + + +def test_skip_tags(test_rules_collection, ematchtestfile, bracketsmatchtestfile): + matches = test_rules_collection.run(ematchtestfile, skip_list=['test1']) + assert len(matches) == 0 + matches = test_rules_collection.run(ematchtestfile, skip_list=['test2']) + assert len(matches) == 3 + matches = test_rules_collection.run(bracketsmatchtestfile, skip_list=['test1']) + assert len(matches) == 2 + matches = test_rules_collection.run(bracketsmatchtestfile, skip_list=['test2']) + assert len(matches) == 1 + + +def test_skip_id(test_rules_collection, ematchtestfile, bracketsmatchtestfile): + matches = test_rules_collection.run(ematchtestfile, skip_list=['TEST0001']) + assert len(matches) == 0 + matches = test_rules_collection.run(ematchtestfile, skip_list=['TEST0002']) + assert len(matches) == 3 + matches = test_rules_collection.run(bracketsmatchtestfile, skip_list=['TEST0001']) + assert len(matches) == 2 + matches = test_rules_collection.run(bracketsmatchtestfile, skip_list=['TEST0002']) + assert len(matches) == 1 + + +def test_skip_non_existent_id(test_rules_collection, ematchtestfile): + matches = test_rules_collection.run(ematchtestfile, skip_list=['DOESNOTEXIST']) + assert len(matches) == 3 + + +def test_no_duplicate_rule_ids(test_rules_collection): + real_rules = RulesCollection([os.path.abspath('./lib/ansiblelint/rules')]) + rule_ids = [rule.id for rule in real_rules] + assert not any(y > 1 for y in collections.Counter(rule_ids).values()) + + +def test_rich_rule_listing(): + """Test that rich list format output is rendered as a table. + + This check also offers the contract of having rule id, short and long + descriptions in the console output. + """ + rules_path = os.path.abspath('./test/rules') + result = run_ansible_lint("-r", rules_path, "-f", "rich", "-L") + assert result.returncode == 0 + + for rule in RulesCollection([rules_path]): + assert rule.id in result.stdout + assert rule.shortdesc in result.stdout + # description could wrap inside table, so we do not check full length + assert rule.description[:30] in result.stdout diff --git a/test/TestRunner.py b/test/TestRunner.py new file mode 100644 index 0000000..59740bd --- /dev/null +++ b/test/TestRunner.py @@ -0,0 +1,86 @@ +# Copyright (c) 2013-2014 Will Thames <will@thames.id.au> +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +import os + +import pytest + +from ansiblelint import formatters +from ansiblelint.cli import abspath +from ansiblelint.runner import Runner + +LOTS_OF_WARNINGS_PLAYBOOK = abspath('examples/lots_of_warnings.yml', os.getcwd()) + + +@pytest.mark.parametrize(('playbook', 'exclude', 'length'), ( + ('test/nomatchestest.yml', [], 0), + ('test/unicode.yml', [], 1), + (LOTS_OF_WARNINGS_PLAYBOOK, [LOTS_OF_WARNINGS_PLAYBOOK], 0), + ('test/block.yml', [], 0), + ('test/become.yml', [], 0), + ('test/emptytags.yml', [], 0), + ('test/contains_secrets.yml', [], 0), +)) +def test_runner(default_rules_collection, playbook, exclude, length): + runner = Runner(default_rules_collection, playbook, [], [], exclude) + + matches = runner.run() + + assert len(matches) == length + + +@pytest.mark.parametrize(('formatter_cls', 'format_kwargs'), ( + pytest.param(formatters.Formatter, {}, id='Formatter-plain'), + pytest.param(formatters.ParseableFormatter, + {'colored': True}, + id='ParseableFormatter-colored'), + pytest.param(formatters.QuietFormatter, + {'colored': True}, + id='QuietFormatter-colored'), + pytest.param(formatters.Formatter, + {'colored': True}, + id='Formatter-colored'), +)) +def test_runner_unicode_format(default_rules_collection, formatter_cls, format_kwargs): + formatter = formatter_cls(os.getcwd(), True) + runner = Runner(default_rules_collection, 'test/unicode.yml', [], [], []) + + matches = runner.run() + + formatter.format(matches[0], **format_kwargs) + + +@pytest.mark.parametrize('directory_name', ('test/', os.path.abspath('test'))) +def test_runner_with_directory(default_rules_collection, directory_name): + runner = Runner(default_rules_collection, directory_name, [], [], []) + assert list(runner.playbooks)[0][1] == 'role' + + +def test_files_not_scanned_twice(default_rules_collection): + checked_files = set() + + filename = os.path.abspath('test/common-include-1.yml') + runner = Runner(default_rules_collection, filename, [], [], [], 0, checked_files) + run1 = runner.run() + + filename = os.path.abspath('test/common-include-2.yml') + runner = Runner(default_rules_collection, filename, [], [], [], 0, checked_files) + run2 = runner.run() + + assert (len(run1) + len(run2)) == 1 diff --git a/test/TestShellWithoutPipefail.py b/test/TestShellWithoutPipefail.py new file mode 100644 index 0000000..c0c8545 --- /dev/null +++ b/test/TestShellWithoutPipefail.py @@ -0,0 +1,84 @@ +# pylint: disable=preferred-module # FIXME: remove once migrated per GH-725 +import unittest + +from ansiblelint.rules import RulesCollection +from ansiblelint.rules.ShellWithoutPipefail import ShellWithoutPipefail +from ansiblelint.testing import RunFromText + +FAIL_TASKS = ''' +--- +- hosts: localhost + become: no + tasks: + - name: pipeline without pipefail + shell: false | cat + + - name: pipeline with or and pipe, no pipefail + shell: false || true | cat + + - shell: | + df | grep '/dev' +''' + +SUCCESS_TASKS = ''' +--- +- hosts: localhost + become: no + tasks: + - name: pipeline with pipefail + shell: set -o pipefail && false | cat + + - name: pipeline with pipefail, multi-line + shell: | + set -o pipefail + false | cat + + - name: pipeline with pipefail, complex set + shell: | + set -e -x -o pipefail + false | cat + + - name: pipeline with pipefail, complex set + shell: | + set -e -x -o pipefail + false | cat + + - name: pipeline with pipefail, complex set + shell: | + set -eo pipefail + false | cat + + - name: pipeline without pipefail, ignoring errors + shell: false | cat + ignore_errors: true + + - name: non-pipeline without pipefail + shell: "true" + + - name: command without pipefail + command: "true" + + - name: shell with or + shell: + false || true + + - shell: | + set -o pipefail + df | grep '/dev' +''' + + +class TestShellWithoutPipeFail(unittest.TestCase): + collection = RulesCollection() + collection.register(ShellWithoutPipefail()) + + def setUp(self): + self.runner = RunFromText(self.collection) + + def test_fail(self): + results = self.runner.run_playbook(FAIL_TASKS) + self.assertEqual(3, len(results)) + + def test_success(self): + results = self.runner.run_playbook(SUCCESS_TASKS) + self.assertEqual(0, len(results)) diff --git a/test/TestSkipImportPlaybook.py b/test/TestSkipImportPlaybook.py new file mode 100644 index 0000000..66e8520 --- /dev/null +++ b/test/TestSkipImportPlaybook.py @@ -0,0 +1,35 @@ +import pytest + +from ansiblelint.runner import Runner + +IMPORTED_PLAYBOOK = ''' +- hosts: all + tasks: + - name: success + fail: msg="fail" + when: False +''' + +MAIN_PLAYBOOK = ''' +- hosts: all + + tasks: + - name: should be shell # noqa 305 301 + shell: echo lol + +- import_playbook: imported_playbook.yml +''' + + +@pytest.fixture +def playbook(tmp_path): + playbook_path = tmp_path / 'playbook.yml' + playbook_path.write_text(MAIN_PLAYBOOK) + (tmp_path / 'imported_playbook.yml').write_text(IMPORTED_PLAYBOOK) + return str(playbook_path) + + +def test_skip_import_playbook(default_rules_collection, playbook): + runner = Runner(default_rules_collection, playbook, [], [], []) + results = runner.run() + assert len(results) == 0 diff --git a/test/TestSkipInsideYaml.py b/test/TestSkipInsideYaml.py new file mode 100644 index 0000000..a2abde2 --- /dev/null +++ b/test/TestSkipInsideYaml.py @@ -0,0 +1,122 @@ +import pytest + +ROLE_TASKS = ''' +--- +- name: test 303 + command: git log + changed_when: False +- name: test 303 (skipped) + command: git log # noqa 303 + changed_when: False +''' + +ROLE_TASKS_WITH_BLOCK = ''' +--- +- name: bad git 1 # noqa 401 + action: git a=b c=d +- name: bad git 2 + action: git a=b c=d +- name: Block with rescue and always section + block: + - name: bad git 3 # noqa 401 + action: git a=b c=d + - name: bad git 4 + action: git a=b c=d + rescue: + - name: bad git 5 # noqa 401 + action: git a=b c=d + - name: bad git 6 + action: git a=b c=d + always: + - name: bad git 7 # noqa 401 + action: git a=b c=d + - name: bad git 8 + action: git a=b c=d +''' + +PLAYBOOK = ''' +- hosts: all + tasks: + - name: test 402 + action: hg + - name: test 402 (skipped) # noqa 402 + action: hg + + - name: test 401 and 501 + become_user: alice + action: git + - name: test 401 and 501 (skipped) # noqa 401 501 + become_user: alice + action: git + + - name: test 204 and 206 + get_url: + url: http://example.com/really_long_path/really_long_path/really_long_path/really_long_path/really_long_path/really_long_path/really_long_path/really_long_path/file.conf + dest: "{{dest_proj_path}}/foo.conf" + - name: test 204 and 206 (skipped) + get_url: + url: http://example.com/really_long_path/really_long_path/really_long_path/really_long_path/really_long_path/really_long_path/really_long_path/really_long_path/file.conf # noqa 204 + dest: "{{dest_proj_path}}/foo.conf" # noqa 206 + + - name: test 302 + command: creates=B chmod 644 A + - name: test 302 + command: warn=yes creates=B chmod 644 A + - name: test 302 (skipped via no warn) + command: warn=no creates=B chmod 644 A + - name: test 302 (skipped via skip_ansible_lint) + command: creates=B chmod 644 A + tags: + - skip_ansible_lint + + - name: test invalid action (skipped) + foo: bar + tags: + - skip_ansible_lint +''' + +ROLE_META = ''' +galaxy_info: # noqa 701 + author: your name # noqa 703 + description: missing min_ansible_version and platforms. author default not changed + license: MIT +''' + +ROLE_TASKS_WITH_BLOCK_BECOME = ''' +- hosts: + tasks: + - name: foo + become: true + block: + - name: bar + become_user: jonhdaa + command: "/etc/test.sh" +''' + + +def test_role_tasks(default_text_runner): + results = default_text_runner.run_role_tasks_main(ROLE_TASKS) + assert len(results) == 1 + + +def test_role_tasks_with_block(default_text_runner): + results = default_text_runner.run_role_tasks_main(ROLE_TASKS_WITH_BLOCK) + assert len(results) == 4 + + +@pytest.mark.parametrize( + ('playbook_src', 'results_num'), + ( + (PLAYBOOK, 7), + (ROLE_TASKS_WITH_BLOCK_BECOME, 0), + ), + ids=('generic', 'with block become inheritance'), +) +def test_playbook(default_text_runner, playbook_src, results_num): + results = default_text_runner.run_playbook(playbook_src) + assert len(results) == results_num + + +def test_role_meta(default_text_runner): + results = default_text_runner.run_role_meta_main(ROLE_META) + assert len(results) == 0 diff --git a/test/TestSkipPlaybookItems.py b/test/TestSkipPlaybookItems.py new file mode 100644 index 0000000..5564ff2 --- /dev/null +++ b/test/TestSkipPlaybookItems.py @@ -0,0 +1,99 @@ +import pytest + +PLAYBOOK_PRE_TASKS = ''' +- hosts: all + tasks: + - name: bad git 1 # noqa 401 + action: git a=b c=d + - name: bad git 2 + action: git a=b c=d + pre_tasks: + - name: bad git 3 # noqa 401 + action: git a=b c=d + - name: bad git 4 + action: git a=b c=d +''' + +PLAYBOOK_POST_TASKS = ''' +- hosts: all + tasks: + - name: bad git 1 # noqa 401 + action: git a=b c=d + - name: bad git 2 + action: git a=b c=d + post_tasks: + - name: bad git 3 # noqa 401 + action: git a=b c=d + - name: bad git 4 + action: git a=b c=d +''' + +PLAYBOOK_HANDLERS = ''' +- hosts: all + tasks: + - name: bad git 1 # noqa 401 + action: git a=b c=d + - name: bad git 2 + action: git a=b c=d + handlers: + - name: bad git 3 # noqa 401 + action: git a=b c=d + - name: bad git 4 + action: git a=b c=d +''' + +PLAYBOOK_TWO_PLAYS = ''' +- hosts: all + tasks: + - name: bad git 1 # noqa 401 + action: git a=b c=d + - name: bad git 2 + action: git a=b c=d + +- hosts: all + tasks: + - name: bad git 3 # noqa 401 + action: git a=b c=d + - name: bad git 4 + action: git a=b c=d +''' + +PLAYBOOK_WITH_BLOCK = ''' +- hosts: all + tasks: + - name: bad git 1 # noqa 401 + action: git a=b c=d + - name: bad git 2 + action: git a=b c=d + - name: Block with rescue and always section + block: + - name: bad git 3 # noqa 401 + action: git a=b c=d + - name: bad git 4 + action: git a=b c=d + rescue: + - name: bad git 5 # noqa 401 + action: git a=b c=d + - name: bad git 6 + action: git a=b c=d + always: + - name: bad git 7 # noqa 401 + action: git a=b c=d + - name: bad git 8 + action: git a=b c=d +''' + + +@pytest.mark.parametrize(('playbook', 'length'), ( + pytest.param(PLAYBOOK_PRE_TASKS, 2, id='PRE_TASKS'), + pytest.param(PLAYBOOK_POST_TASKS, 2, id='POST_TASKS'), + pytest.param(PLAYBOOK_HANDLERS, 2, id='HANDLERS'), + pytest.param(PLAYBOOK_TWO_PLAYS, 2, id='TWO_PLAYS'), + pytest.param(PLAYBOOK_WITH_BLOCK, 4, id='WITH_BLOCK'), +)) +def test_pre_tasks(default_text_runner, playbook, length): + # When + results = default_text_runner.run_playbook(playbook) + + # Then + assert len(results) == length diff --git a/test/TestSudoRule.py b/test/TestSudoRule.py new file mode 100644 index 0000000..a105854 --- /dev/null +++ b/test/TestSudoRule.py @@ -0,0 +1,67 @@ +# pylint: disable=preferred-module # FIXME: remove once migrated per GH-725 +import unittest + +from ansiblelint.rules import RulesCollection +from ansiblelint.rules.SudoRule import SudoRule +from ansiblelint.testing import RunFromText + +ROLE_2_ERRORS = ''' +- name: test + debug: + msg: 'test message' + sudo: yes + sudo_user: nobody +''' + +ROLE_0_ERRORS = ''' +- name: test + debug: + msg: 'test message' + become: yes + become_user: somebody +''' + +PLAY_4_ERRORS = ''' +- hosts: all + sudo: yes + sudo_user: somebody + tasks: + - name: test + debug: + msg: 'test message' + sudo: yes + sudo_user: nobody +''' + +PLAY_1_ERROR = ''' +- hosts: all + tasks: + - name: test + debug: + msg: 'test message' + sudo: yes +''' + + +class TestSudoRule(unittest.TestCase): + collection = RulesCollection() + collection.register(SudoRule()) + + def setUp(self): + self.runner = RunFromText(self.collection) + + def test_run_role_fail(self): + results = self.runner.run_role_tasks_main(ROLE_2_ERRORS) + self.assertEqual(2, len(results)) + + def test_run_role_pass(self): + results = self.runner.run_role_tasks_main(ROLE_0_ERRORS) + self.assertEqual(0, len(results)) + + def test_play_root_and_task_fail(self): + results = self.runner.run_playbook(PLAY_4_ERRORS) + self.assertEqual(4, len(results)) + + def test_play_task_fail(self): + results = self.runner.run_playbook(PLAY_1_ERROR) + self.assertEqual(1, len(results)) diff --git a/test/TestTaskHasName.py b/test/TestTaskHasName.py new file mode 100644 index 0000000..3a35f29 --- /dev/null +++ b/test/TestTaskHasName.py @@ -0,0 +1,24 @@ +# pylint: disable=preferred-module # FIXME: remove once migrated per GH-725 +import unittest + +from ansiblelint.rules import RulesCollection +from ansiblelint.rules.TaskHasNameRule import TaskHasNameRule +from ansiblelint.runner import Runner + + +class TestTaskHasNameRule(unittest.TestCase): + collection = RulesCollection() + + def setUp(self): + self.collection.register(TaskHasNameRule()) + + def test_file_positive(self): + success = 'test/task-has-name-success.yml' + good_runner = Runner(self.collection, success, [], [], []) + self.assertEqual([], good_runner.run()) + + def test_file_negative(self): + failure = 'test/task-has-name-failure.yml' + bad_runner = Runner(self.collection, failure, [], [], []) + errs = bad_runner.run() + self.assertEqual(2, len(errs)) diff --git a/test/TestTaskIncludes.py b/test/TestTaskIncludes.py new file mode 100644 index 0000000..a3506bd --- /dev/null +++ b/test/TestTaskIncludes.py @@ -0,0 +1,34 @@ +import pytest + +from ansiblelint.runner import Runner + + +@pytest.mark.parametrize( + ('filename', 'playbooks_count'), + ( + pytest.param('blockincludes', 4, id='block included tasks'), + pytest.param( + 'blockincludes2', 4, + id='block included tasks with rescue and always', + ), + pytest.param('taskincludes', 4, id='included tasks'), + pytest.param( + 'taskincludes_2_4_style', 4, + id='include tasks 2.4 style', + ), + pytest.param('taskimports', 4, id='import tasks 2 4 style'), + pytest.param( + 'include-in-block', 3, + id='include tasks with block include', + ), + pytest.param( + 'include-import-tasks-in-role', 4, + id='include tasks in role', + ), + ), +) +def test_included_tasks(default_rules_collection, filename, playbooks_count): + playbook_path = 'test/{filename}.yml'.format(**locals()) + runner = Runner(default_rules_collection, playbook_path, [], [], []) + runner.run() + assert len(runner.playbooks) == playbooks_count diff --git a/test/TestTaskNoLocalAction.py b/test/TestTaskNoLocalAction.py new file mode 100644 index 0000000..74be24c --- /dev/null +++ b/test/TestTaskNoLocalAction.py @@ -0,0 +1,24 @@ +# pylint: disable=preferred-module # FIXME: remove once migrated per GH-725 +import unittest + +from ansiblelint.rules import RulesCollection +from ansiblelint.rules.TaskNoLocalAction import TaskNoLocalAction +from ansiblelint.testing import RunFromText + +TASK_LOCAL_ACTION = ''' +- name: task example + local_action: + module: boto3_facts +''' + + +class TestTaskNoLocalAction(unittest.TestCase): + collection = RulesCollection() + collection.register(TaskNoLocalAction()) + + def setUp(self): + self.runner = RunFromText(self.collection) + + def test_local_action(self): + results = self.runner.run_role_tasks_main(TASK_LOCAL_ACTION) + self.assertEqual(1, len(results)) diff --git a/test/TestUseCommandInsteadOfShell.py b/test/TestUseCommandInsteadOfShell.py new file mode 100644 index 0000000..d7e44a2 --- /dev/null +++ b/test/TestUseCommandInsteadOfShell.py @@ -0,0 +1,24 @@ +# pylint: disable=preferred-module # FIXME: remove once migrated per GH-725 +import unittest + +from ansiblelint.rules import RulesCollection +from ansiblelint.rules.UseCommandInsteadOfShellRule import UseCommandInsteadOfShellRule +from ansiblelint.runner import Runner + + +class TestUseCommandInsteadOfShell(unittest.TestCase): + collection = RulesCollection() + + def setUp(self): + self.collection.register(UseCommandInsteadOfShellRule()) + + def test_file_positive(self): + success = 'test/command-instead-of-shell-success.yml' + good_runner = Runner(self.collection, success, [], [], []) + self.assertEqual([], good_runner.run()) + + def test_file_negative(self): + failure = 'test/command-instead-of-shell-failure.yml' + bad_runner = Runner(self.collection, failure, [], [], []) + errs = bad_runner.run() + self.assertEqual(2, len(errs)) diff --git a/test/TestUseHandlerRatherThanWhenChanged.py b/test/TestUseHandlerRatherThanWhenChanged.py new file mode 100644 index 0000000..5306e2f --- /dev/null +++ b/test/TestUseHandlerRatherThanWhenChanged.py @@ -0,0 +1,88 @@ +# pylint: disable=preferred-module # FIXME: remove once migrated per GH-725 +import unittest + +from ansiblelint.rules import RulesCollection +from ansiblelint.rules.UseHandlerRatherThanWhenChangedRule import ( + UseHandlerRatherThanWhenChangedRule, +) +from ansiblelint.testing import RunFromText + +SUCCESS_TASKS = ''' +- name: print helpful error message + debug: + var: result + when: result.failed + +- name: do something when hello is output + debug: + msg: why isn't this a handler + when: result.stdout == "hello" + +- name: never actually debug + debug: + var: result + when: False + +- name: Dont execute this step + debug: + msg: "debug message" + when: + - false + +- name: check when with a list + debug: + var: result + when: + - conditionA + - conditionB +''' + + +FAIL_TASKS = ''' +- name: execute command + command: echo hello + register: result + +- name: this should be a handler + debug: + msg: why isn't this a handler + when: result.changed + +- name: this should be a handler 2 + debug: + msg: why isn't this a handler + when: result|changed + +- name: this should be a handler 3 + debug: + msg: why isn't this a handler + when: result.changed == true + +- name: this should be a handler 4 + debug: + msg: why isn't this a handler + when: result['changed'] == true + +- name: this should be a handler 5 + debug: + msg: why isn't this a handler + when: + - result['changed'] == true + - another_condition +''' + + +class TestUseHandlerRatherThanWhenChanged(unittest.TestCase): + collection = RulesCollection() + collection.register(UseHandlerRatherThanWhenChangedRule()) + + def setUp(self): + self.runner = RunFromText(self.collection) + + def test_success(self): + results = self.runner.run_role_tasks_main(SUCCESS_TASKS) + self.assertEqual(0, len(results)) + + def test_fail(self): + results = self.runner.run_role_tasks_main(FAIL_TASKS) + self.assertEqual(5, len(results)) diff --git a/test/TestUsingBareVariablesIsDeprecated.py b/test/TestUsingBareVariablesIsDeprecated.py new file mode 100644 index 0000000..42c3b4d --- /dev/null +++ b/test/TestUsingBareVariablesIsDeprecated.py @@ -0,0 +1,24 @@ +# pylint: disable=preferred-module # FIXME: remove once migrated per GH-725 +import unittest + +from ansiblelint.rules import RulesCollection +from ansiblelint.rules.UsingBareVariablesIsDeprecatedRule import UsingBareVariablesIsDeprecatedRule +from ansiblelint.runner import Runner + + +class TestUsingBareVariablesIsDeprecated(unittest.TestCase): + collection = RulesCollection() + + def setUp(self): + self.collection.register(UsingBareVariablesIsDeprecatedRule()) + + def test_file_positive(self): + success = 'test/using-bare-variables-success.yml' + good_runner = Runner(self.collection, success, [], [], []) + self.assertEqual([], good_runner.run()) + + def test_file_negative(self): + failure = 'test/using-bare-variables-failure.yml' + bad_runner = Runner(self.collection, failure, [], [], []) + errs = bad_runner.run() + self.assertEqual(14, len(errs)) diff --git a/test/TestUtils.py b/test/TestUtils.py new file mode 100644 index 0000000..58824b0 --- /dev/null +++ b/test/TestUtils.py @@ -0,0 +1,317 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2013-2014 Will Thames <will@thames.id.au> +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +"""Tests for generic utilitary functions.""" + +import logging +import os +import os.path +import subprocess +import sys +from pathlib import Path + +import pytest + +from ansiblelint import cli, constants, utils +from ansiblelint.__main__ import initialize_logger +from ansiblelint.file_utils import normpath + + +@pytest.mark.parametrize(('string', 'expected_cmd', 'expected_args', 'expected_kwargs'), ( + pytest.param('', '', [], {}, id='blank'), + pytest.param('vars:', 'vars', [], {}, id='single_word'), + pytest.param('hello: a=1', 'hello', [], {'a': '1'}, id='string_module_and_arg'), + pytest.param('action: hello a=1', 'hello', [], {'a': '1'}, id='strips_action'), + pytest.param('action: whatever bobbins x=y z=x c=3', + 'whatever', + ['bobbins', 'x=y', 'z=x', 'c=3'], + {}, + id='more_than_one_arg'), + pytest.param('action: command chdir=wxy creates=zyx tar xzf zyx.tgz', + 'command', + ['tar', 'xzf', 'zyx.tgz'], + {'chdir': 'wxy', 'creates': 'zyx'}, + id='command_with_args'), +)) +def test_tokenize(string, expected_cmd, expected_args, expected_kwargs): + """Test that tokenize works for different input types.""" + (cmd, args, kwargs) = utils.tokenize(string) + assert cmd == expected_cmd + assert args == expected_args + assert kwargs == expected_kwargs + + +@pytest.mark.parametrize(('reference_form', 'alternate_forms'), ( + pytest.param(dict(name='hello', action='command chdir=abc echo hello world'), + (dict(name="hello", command="chdir=abc echo hello world"), ), + id='simple_command'), + pytest.param({'git': {'version': 'abc'}, 'args': {'repo': 'blah', 'dest': 'xyz'}}, + ({'git': {'version': 'abc', 'repo': 'blah', 'dest': 'xyz'}}, + {"git": 'version=abc repo=blah dest=xyz'}, + {"git": None, "args": {'repo': 'blah', 'dest': 'xyz', 'version': 'abc'}}, + ), + id='args') +)) +def test_normalize(reference_form, alternate_forms): + """Test that tasks specified differently are normalized same way.""" + normal_form = utils.normalize_task(reference_form, 'tasks.yml') + + for form in alternate_forms: + assert normal_form == utils.normalize_task(form, 'tasks.yml') + + +def test_normalize_complex_command(): + """Test that tasks specified differently are normalized same way.""" + task1 = dict(name="hello", action={'module': 'pip', + 'name': 'df', + 'editable': 'false'}) + task2 = dict(name="hello", pip={'name': 'df', + 'editable': 'false'}) + task3 = dict(name="hello", pip="name=df editable=false") + task4 = dict(name="hello", action="pip name=df editable=false") + assert utils.normalize_task(task1, 'tasks.yml') == utils.normalize_task(task2, 'tasks.yml') + assert utils.normalize_task(task2, 'tasks.yml') == utils.normalize_task(task3, 'tasks.yml') + assert utils.normalize_task(task3, 'tasks.yml') == utils.normalize_task(task4, 'tasks.yml') + + +def test_extract_from_list(): + """Check that tasks get extracted from blocks if present.""" + block = { + 'block': [{'tasks': {'name': 'hello', 'command': 'whoami'}}], + 'test_none': None, + 'test_string': 'foo', + } + blocks = [block] + + test_list = utils.extract_from_list(blocks, ['block']) + test_none = utils.extract_from_list(blocks, ['test_none']) + + assert list(block['block']) == test_list + assert list() == test_none + with pytest.raises(RuntimeError): + utils.extract_from_list(blocks, ['test_string']) + + +@pytest.mark.parametrize(('template', 'output'), ( + pytest.param('{{ playbook_dir }}', '/a/b/c', id='simple'), + pytest.param("{{ 'hello' | doesnotexist }}", + "{{ 'hello' | doesnotexist }}", + id='unknown_filter'), + pytest.param('{{ hello | to_json }}', + '{{ hello | to_json }}', + id='to_json_filter_on_undefined_variable'), + pytest.param('{{ hello | to_nice_yaml }}', + '{{ hello | to_nice_yaml }}', + id='to_nice_yaml_filter_on_undefined_variable'), +)) +def test_template(template, output): + """Verify that resolvable template vars and filters get rendered.""" + result = utils.template('/base/dir', template, dict(playbook_dir='/a/b/c')) + assert result == output + + +def test_task_to_str_unicode(): + """Ensure that extracting messages from tasks preserves Unicode.""" + task = dict(fail=dict(msg=u"unicode é ô à")) + result = utils.task_to_str(utils.normalize_task(task, 'filename.yml')) + assert result == u"fail msg=unicode é ô à" + + +@pytest.mark.parametrize('path', ( + pytest.param(Path('a/b/../'), id='pathlib.Path'), + pytest.param('a/b/../', id='str'), +)) +def test_normpath_with_path_object(path): + """Ensure that relative parent dirs are normalized in paths.""" + assert normpath(path) == "a" + + +def test_expand_path_vars(monkeypatch): + """Ensure that tilde and env vars are expanded in paths.""" + test_path = '/test/path' + monkeypatch.setenv('TEST_PATH', test_path) + assert utils.expand_path_vars('~') == os.path.expanduser('~') + assert utils.expand_path_vars('$TEST_PATH') == test_path + + +@pytest.mark.parametrize(('test_path', 'expected'), ( + pytest.param(Path('$TEST_PATH'), "/test/path", id='pathlib.Path'), + pytest.param('$TEST_PATH', "/test/path", id='str'), + pytest.param(' $TEST_PATH ', "/test/path", id='stripped-str'), + pytest.param('~', os.path.expanduser('~'), id='home'), +)) +def test_expand_paths_vars(test_path, expected, monkeypatch): + """Ensure that tilde and env vars are expanded in paths lists.""" + monkeypatch.setenv('TEST_PATH', '/test/path') + assert utils.expand_paths_vars([test_path]) == [expected] + + +@pytest.mark.parametrize( + ('reset_env_var', 'message_prefix'), + ( + ('PATH', + "Failed to locate command: "), + ('GIT_DIR', + "Failed to discover yaml files to lint using git: ") + ), + ids=('no Git installed', 'outside Git repository'), +) +def test_get_yaml_files_git_verbose( + reset_env_var, + message_prefix, + monkeypatch, + caplog +): + """Ensure that autodiscovery lookup failures are logged.""" + options = cli.get_config(['-v']) + initialize_logger(options.verbosity) + monkeypatch.setenv(reset_env_var, '') + utils.get_yaml_files(options) + + expected_info = ( + "ansiblelint.utils", + logging.INFO, + 'Discovering files to lint: git ls-files *.yaml *.yml') + + assert expected_info in caplog.record_tuples + assert any(m.startswith(message_prefix) for m in caplog.messages) + + +@pytest.mark.parametrize( + 'is_in_git', + (True, False), + ids=('in Git', 'outside Git'), +) +def test_get_yaml_files_silent(is_in_git, monkeypatch, capsys): + """Verify that no stderr output is displayed while discovering yaml files. + + (when the verbosity is off, regardless of the Git or Git-repo presence) + + Also checks expected number of files are detected. + """ + options = cli.get_config([]) + test_dir = Path(__file__).resolve().parent + lint_path = test_dir / 'roles' / 'test-role' + if not is_in_git: + monkeypatch.setenv('GIT_DIR', '') + + yaml_count = ( + len(list(lint_path.glob('**/*.yml'))) + len(list(lint_path.glob('**/*.yaml'))) + ) + + monkeypatch.chdir(str(lint_path)) + files = utils.get_yaml_files(options) + stderr = capsys.readouterr().err + assert not stderr, 'No stderr output is expected when the verbosity is off' + assert len(files) == yaml_count, ( + "Expected to find {yaml_count} yaml files in {lint_path}".format_map( + locals(), + ) + ) + + +def test_logger_debug(caplog): + """Test that the double verbosity arg causes logger to be DEBUG.""" + options = cli.get_config(['-vv']) + initialize_logger(options.verbosity) + + expected_info = ( + "ansiblelint.__main__", + logging.DEBUG, + 'Logging initialized to level 10', + ) + + assert expected_info in caplog.record_tuples + + +@pytest.mark.xfail +def test_cli_auto_detect(capfd): + """Test that run without arguments it will detect and lint the entire repository.""" + cmd = sys.executable, "-m", "ansiblelint", "-v", "-p", "--nocolor" + result = subprocess.run(cmd, check=False).returncode + + # We de expect to fail on our own repo due to test examples we have + # TODO(ssbarnea) replace it with exact return code once we document them + assert result != 0 + + out, err = capfd.readouterr() + + # Confirmation that it runs in auto-detect mode + assert "Discovering files to lint: git ls-files *.yaml *.yml" in err + # Expected failure to detect file type" + assert "Unknown file type: test/fixtures/unknown-type.yml" in err + # An expected rule match from our examples + assert "examples/roles/bobbins/tasks/main.yml:2: " \ + "[E401] Git checkouts must contain explicit version" in out + # assures that our .ansible-lint exclude was effective in excluding github files + assert "Unknown file type: .github/" not in out + # assures that we can parse playbooks as playbooks + assert "Unknown file type: test/test/always-run-success.yml" not in err + + +@pytest.mark.xfail +def test_is_playbook(): + """Verify that we can detect a playbook as a playbook.""" + assert utils.is_playbook("test/test/always-run-success.yml") + + +def test_auto_detect_exclude(monkeypatch): + """Verify that exclude option can be used to narrow down detection.""" + options = cli.get_config(['--exclude', 'foo']) + + def mockreturn(options): + return ['foo/playbook.yml', 'bar/playbook.yml'] + + monkeypatch.setattr(utils, 'get_yaml_files', mockreturn) + result = utils.get_playbooks_and_roles(options) + assert result == ['bar/playbook.yml'] + + +_DEFAULT_RULEDIRS = [constants.DEFAULT_RULESDIR] +_CUSTOM_RULESDIR = Path(__file__).parent / "custom_rules" +_CUSTOM_RULEDIRS = [ + str(_CUSTOM_RULESDIR / "example_inc"), + str(_CUSTOM_RULESDIR / "example_com") +] + + +@pytest.mark.parametrize(("user_ruledirs", "use_default", "expected"), ( + ([], True, _DEFAULT_RULEDIRS), + ([], False, _DEFAULT_RULEDIRS), + (_CUSTOM_RULEDIRS, True, _CUSTOM_RULEDIRS + _DEFAULT_RULEDIRS), + (_CUSTOM_RULEDIRS, False, _CUSTOM_RULEDIRS) +)) +def test_get_rules_dirs(user_ruledirs, use_default, expected): + """Test it returns expected dir lists.""" + assert utils.get_rules_dirs(user_ruledirs, use_default) == expected + + +@pytest.mark.parametrize(("user_ruledirs", "use_default", "expected"), ( + ([], True, sorted(_CUSTOM_RULEDIRS) + _DEFAULT_RULEDIRS), + ([], False, sorted(_CUSTOM_RULEDIRS) + _DEFAULT_RULEDIRS), + (_CUSTOM_RULEDIRS, True, + _CUSTOM_RULEDIRS + sorted(_CUSTOM_RULEDIRS) + _DEFAULT_RULEDIRS), + (_CUSTOM_RULEDIRS, False, _CUSTOM_RULEDIRS) +)) +def test_get_rules_dirs_with_custom_rules(user_ruledirs, use_default, expected, monkeypatch): + """Test it returns expected dir lists when custom rules exist.""" + monkeypatch.setenv(constants.CUSTOM_RULESDIR_ENVVAR, str(_CUSTOM_RULESDIR)) + assert utils.get_rules_dirs(user_ruledirs, use_default) == expected diff --git a/test/TestVariableHasSpaces.py b/test/TestVariableHasSpaces.py new file mode 100644 index 0000000..01b12c9 --- /dev/null +++ b/test/TestVariableHasSpaces.py @@ -0,0 +1,54 @@ +# pylint: disable=preferred-module # FIXME: remove once migrated per GH-725 +import unittest + +from ansiblelint.rules import RulesCollection +from ansiblelint.rules.VariableHasSpacesRule import VariableHasSpacesRule +from ansiblelint.testing import RunFromText + +TASK_VARIABLES = ''' +- name: good variable format + debug: + msg: "{{ good_format }}" +- name: good variable format + debug: + msg: "Value: {{ good_format }}" +- name: jinja escaping allowed + debug: + msg: "{{ '{{' }}" +- name: jinja escaping allowed + shell: docker info --format '{{ '{{' }}json .Swarm.LocalNodeState{{ '}}' }}' | tr -d '"' +- name: jinja whitespace control allowed + debug: + msg: | + {{ good_format }}/ + {{- good_format }} + {{- good_format -}} +- name: bad variable format + debug: + msg: "{{bad_format}}" +- name: bad variable format + debug: + msg: "Value: {{ bad_format}}" +- name: bad variable format + debug: + msg: "{{bad_format }}" +- name: not a jinja variable + debug: + msg: "test" + example: "data = ${lookup{$local_part}lsearch{/etc/aliases}}" +- name: JSON inside jinja is valid + debug: + msg: "{{ {'test': {'subtest': variable}} }}" +''' + + +class TestVariableHasSpaces(unittest.TestCase): + collection = RulesCollection() + collection.register(VariableHasSpacesRule()) + + def setUp(self): + self.runner = RunFromText(self.collection) + + def test_variable_has_spaces(self): + results = self.runner.run_role_tasks_main(TASK_VARIABLES) + self.assertEqual(3, len(results)) diff --git a/test/TestWithSkipTagId.py b/test/TestWithSkipTagId.py new file mode 100644 index 0000000..129a00d --- /dev/null +++ b/test/TestWithSkipTagId.py @@ -0,0 +1,39 @@ +# pylint: disable=preferred-module # FIXME: remove once migrated per GH-725 +import unittest + +from ansiblelint.rules import RulesCollection +from ansiblelint.rules.TrailingWhitespaceRule import TrailingWhitespaceRule +from ansiblelint.runner import Runner + + +class TestWithSkipTagId(unittest.TestCase): + collection = RulesCollection() + collection.register(TrailingWhitespaceRule()) + file = 'test/with-skip-tag-id.yml' + + def test_negative_no_param(self): + bad_runner = Runner(self.collection, self.file, [], [], []) + errs = bad_runner.run() + self.assertGreater(len(errs), 0) + + def test_negative_with_id(self): + with_id = '201' + bad_runner = Runner(self.collection, self.file, [with_id], [], []) + errs = bad_runner.run() + self.assertGreater(len(errs), 0) + + def test_negative_with_tag(self): + with_tag = 'ANSIBLE0002' + bad_runner = Runner(self.collection, self.file, [with_tag], [], []) + errs = bad_runner.run() + self.assertGreater(len(errs), 0) + + def test_positive_skip_id(self): + skip_id = '201' + good_runner = Runner(self.collection, self.file, [], [skip_id], []) + self.assertEqual([], good_runner.run()) + + def test_positive_skip_tag(self): + skip_tag = 'ANSIBLE0002' + good_runner = Runner(self.collection, self.file, [], [skip_tag], []) + self.assertEqual([], good_runner.run()) diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 0000000..124a757 --- /dev/null +++ b/test/__init__.py @@ -0,0 +1 @@ +"""Use ansiblelint.testing instead for test reusables.""" diff --git a/test/always-run-failure.yml b/test/always-run-failure.yml new file mode 100644 index 0000000..c9ff225 --- /dev/null +++ b/test/always-run-failure.yml @@ -0,0 +1,6 @@ +- hosts: localhost + + tasks: + - name: always_run is deprecated + debug: msg="always_run is deprecated" + always_run: yes diff --git a/test/always-run-success.yml b/test/always-run-success.yml new file mode 100644 index 0000000..30559e0 --- /dev/null +++ b/test/always-run-success.yml @@ -0,0 +1,6 @@ +- hosts: localhost + + tasks: + - name: always_run is deprecated + debug: msg="always_run is deprecated" + check_mode: yes diff --git a/test/bar.txt b/test/bar.txt new file mode 100644 index 0000000..e22f90b --- /dev/null +++ b/test/bar.txt @@ -0,0 +1 @@ +Bar file diff --git a/test/become-user-without-become-failure.yml b/test/become-user-without-become-failure.yml new file mode 100644 index 0000000..a4051f0 --- /dev/null +++ b/test/become-user-without-become-failure.yml @@ -0,0 +1,26 @@ +- hosts: localhost + name: become_user without become play + become_user: root + + tasks: + - debug: + msg: hello + +- hosts: localhost + + tasks: + - name: become_user without become task + command: whoami + become_user: postgres + +- hosts: localhost + + tasks: + - name: a block with become and become_user on different tasks + block: + - name: become + become: true + command: whoami + - name: become_user + become_user: postgres + command: whoami diff --git a/test/become-user-without-become-success.yml b/test/become-user-without-become-success.yml new file mode 100644 index 0000000..a8d64da --- /dev/null +++ b/test/become-user-without-become-success.yml @@ -0,0 +1,30 @@ +- hosts: localhost + become_user: root + become: true + + tasks: + - debug: + msg: hello + +- hosts: localhost + + tasks: + - command: whoami + become_user: postgres + become: true + +- hosts: localhost + become: true + + tasks: + - name: Accepts a become from higher scope + command: whoami + become_user: postgres + +- hosts: localhost + become_user: postgres + + tasks: + - name: Accepts a become from a lower scope + command: whoami + become: true diff --git a/test/become.yml b/test/become.yml new file mode 100644 index 0000000..f2ea524 --- /dev/null +++ b/test/become.yml @@ -0,0 +1,14 @@ +- hosts: all + + tasks: + - name: clone content repository + git: + repo: '{{ archive_services_repo_url }}' + dest: '/home/www' + accept_hostkey: yes + version: master + update: no + become: yes + become_user: nobody + notify: + - restart apache2 diff --git a/test/block.yml b/test/block.yml new file mode 100644 index 0000000..1aac26e --- /dev/null +++ b/test/block.yml @@ -0,0 +1,26 @@ +--- +- hosts: all + + pre_tasks: + - { include: 'doesnotexist.yml' } + + tasks: + - block: + - name: successful debug message + debug: msg='i execute normally' + - name: failure command + command: /bin/false + changed_when: False + - name: never reached debug message + debug: msg='i never execute, cause ERROR!' + rescue: + - name: exception debug message + debug: msg='I caught an error' + - name: another failure command + command: /bin/false + changed_when: False + - name: another missed debug message + debug: msg='I also never execute :-(' + always: + - name: always reached debug message + debug: msg="this always executes" diff --git a/test/blockincludes.yml b/test/blockincludes.yml new file mode 100644 index 0000000..5bea9be --- /dev/null +++ b/test/blockincludes.yml @@ -0,0 +1,13 @@ +--- +- hosts: webservers + vars: + varset: varset + tasks: + - block: + - include: nestedincludes.yml tags=nested + - block: + - include: "{{ varnotset }}.yml" + - block: + - include: "{{ varset }}.yml" + - block: + - include: "directory with spaces/main.yml" diff --git a/test/blockincludes2.yml b/test/blockincludes2.yml new file mode 100644 index 0000000..03d7a75 --- /dev/null +++ b/test/blockincludes2.yml @@ -0,0 +1,13 @@ +--- +- hosts: webservers + vars: + varset: varset + tasks: + - block: + - include: nestedincludes.yml tags=nested + - block: + - include: "{{ varnotset }}.yml" + rescue: + - include: "{{ varset }}.yml" + always: + - include: "directory with spaces/main.yml" diff --git a/test/brackets-do-not-match-test.yml b/test/brackets-do-not-match-test.yml new file mode 100644 index 0000000..dfa5ff8 --- /dev/null +++ b/test/brackets-do-not-match-test.yml @@ -0,0 +1,22 @@ +--- +- hosts: foo + roles: + - ../../../roles/base_os + - ../../../roles/repos + - { + role: ../../../roles/openshift_master, + oo_minion_ips: "{ hostvars['localhost'].oo_minion_ips | default(['']) }}", + oo_bind_ip: "{{ hostvars[inventory_hostname].ansible_eth0.ipv4.address | default(['']) }}" + } + - ../../../roles/pods + +- name: "Set Origin specific facts on localhost (for later use)" + hosts: localhost + gather_facts: no + tasks: + - name: Setting oo_minion_ips fact on localhost + set_fact: + oo_minion_ips: "{{ hostvars + | oo_select_keys(groups['tag_env-host-type-' + oo_env + '-openshift-minion']) + | oo_collect(attribute='ansible_eth0.ipv4.address') }" + when: groups['tag_env-host-type-' + oo_env + '-openshift-minion'] is defined diff --git a/test/bracketsmatchtest.yml b/test/bracketsmatchtest.yml new file mode 100644 index 0000000..46b213d --- /dev/null +++ b/test/bracketsmatchtest.yml @@ -0,0 +1,3 @@ +val1: "{{dest}}" +val2: worry +val3: "{{victory}}" diff --git a/test/command-check-failure.yml b/test/command-check-failure.yml new file mode 100644 index 0000000..d58c09d --- /dev/null +++ b/test/command-check-failure.yml @@ -0,0 +1,11 @@ +- hosts: localhost + tasks: + - name: command without checks + command: echo blah + args: + chdir: X + + - name: shell without checks + shell: echo blah + args: + chdir: X diff --git a/test/command-check-success.yml b/test/command-check-success.yml new file mode 100644 index 0000000..24266ff --- /dev/null +++ b/test/command-check-success.yml @@ -0,0 +1,61 @@ +- hosts: localhost + tasks: + - name: command with creates check + command: echo blah + args: + creates: Z + + - name: command with removes check + command: echo blah + args: + removes: Z + + - name: command with changed_when + command: echo blah + changed_when: False + + - name: command with inline creates + command: creates=Z echo blah + + - name: command with inline removes + command: removes=Z echo blah + + - name: command with cmd + command: + cmd: + echo blah + args: + creates: Z + + - name: shell with creates check + shell: echo blah + args: + creates: Z + + - name: shell with removes check + shell: echo blah + args: + removes: Z + + - name: shell with changed_when + shell: echo blah + changed_when: False + + - name: shell with inline creates + shell: creates=Z echo blah + + - name: shell with inline removes + shell: removes=Z echo blah + + - name: shell with cmd + shell: + cmd: + echo blah + args: + creates: Z + +- hosts: localhost + handlers: + - name: restart something + command: do something + - include: included-handlers.yml diff --git a/test/command-instead-of-shell-failure.yml b/test/command-instead-of-shell-failure.yml new file mode 100644 index 0000000..7b8d829 --- /dev/null +++ b/test/command-instead-of-shell-failure.yml @@ -0,0 +1,8 @@ +--- +- hosts: localhost + tasks: + - name: shell no pipe + shell: echo hello + + - name: shell with jinja filter + shell: echo {{"hello"|upper}} diff --git a/test/command-instead-of-shell-success.yml b/test/command-instead-of-shell-success.yml new file mode 100644 index 0000000..410ff97 --- /dev/null +++ b/test/command-instead-of-shell-success.yml @@ -0,0 +1,37 @@ +- hosts: localhost + tasks: + - name: shell with pipe + shell: echo hello | true + + - name: shell with redirect + shell: echo hello > /tmp/hello + + - name: chain two shell commands + shell: echo hello && echo goodbye + + - name: run commands in succession + shell: echo hello ; echo goodbye + + - name: use variables + shell: echo $HOME $USER + + - name: use * for globbing + shell: ls foo* + + - name: use ? for globbing + shell: ls foo? + + - name: use [] for globbing + shell: ls foo[1,2,3] + + - name: use shell generator + shell: ls foo{.txt,.xml} + + - name: use backticks + shell: ls `ls foo*` + + - name: use shell with cmd + shell: + cmd: | + set -x + ls foo? diff --git a/test/common-include-1.yml b/test/common-include-1.yml new file mode 100644 index 0000000..287df85 --- /dev/null +++ b/test/common-include-1.yml @@ -0,0 +1,4 @@ +--- +- hosts: webservers + tasks: + - include: included-with-lint.yml diff --git a/test/common-include-2.yml b/test/common-include-2.yml new file mode 100644 index 0000000..287df85 --- /dev/null +++ b/test/common-include-2.yml @@ -0,0 +1,4 @@ +--- +- hosts: webservers + tasks: + - include: included-with-lint.yml diff --git a/test/contains_secrets.yml b/test/contains_secrets.yml new file mode 100644 index 0000000..aed5a0e --- /dev/null +++ b/test/contains_secrets.yml @@ -0,0 +1,14 @@ +- hosts: localhost + vars: + plain: hello123 + # just 'hello123' encrypted with 'letmein' for test purposes + secret: !vault | + $ANSIBLE_VAULT;1.1;AES256 + 63346434613163653866303630313238626164313961613935373137323639636333393338386232 + 3735313061316666343839343665383036623237353263310a623639336530383433343833653138 + 30393032393534316164613834393864616566646164363830316664623636643731383164376163 + 3736653037356435310a303533383533353739323834343637366438633766666163656330343631 + 3066 + tasks: + - name: just a debug task + debug: msg="hello world" diff --git a/test/custom_rules/__init__.py b/test/custom_rules/__init__.py new file mode 100644 index 0000000..09a0f04 --- /dev/null +++ b/test/custom_rules/__init__.py @@ -0,0 +1 @@ +"""Dummy test module.""" diff --git a/test/custom_rules/example_com/ExampleComRule.py b/test/custom_rules/example_com/ExampleComRule.py new file mode 100644 index 0000000..0ce9c1c --- /dev/null +++ b/test/custom_rules/example_com/ExampleComRule.py @@ -0,0 +1,28 @@ +# Copyright (c) 2020, Ansible Project +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +"""A dummy custom rule module #2.""" + +import ansiblelint.rules.AlwaysRunRule + + +class ExampleComRule(ansiblelint.rules.AlwaysRunRule.AlwaysRunRule): + """A dummy custom rule class.""" + + id = '100002' diff --git a/test/custom_rules/example_com/__init__.py b/test/custom_rules/example_com/__init__.py new file mode 100644 index 0000000..a633c75 --- /dev/null +++ b/test/custom_rules/example_com/__init__.py @@ -0,0 +1 @@ +"""A dummy test module #2.""" diff --git a/test/custom_rules/example_inc/CustomAlwaysRunRule.py b/test/custom_rules/example_inc/CustomAlwaysRunRule.py new file mode 100644 index 0000000..1bff62d --- /dev/null +++ b/test/custom_rules/example_inc/CustomAlwaysRunRule.py @@ -0,0 +1,28 @@ +# Copyright (c) 2020, Ansible Project +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +"""Dummy custom rule module.""" + +import ansiblelint.rules.AlwaysRunRule + + +class CustomAlwaysRunRule(ansiblelint.rules.AlwaysRunRule.AlwaysRunRule): + """Dummy custom rule class.""" + + id = '100001' diff --git a/test/custom_rules/example_inc/__init__.py b/test/custom_rules/example_inc/__init__.py new file mode 100644 index 0000000..09a0f04 --- /dev/null +++ b/test/custom_rules/example_inc/__init__.py @@ -0,0 +1 @@ +"""Dummy test module.""" diff --git a/test/dependency-in-meta/bitbucket.yml b/test/dependency-in-meta/bitbucket.yml new file mode 100644 index 0000000..b696809 --- /dev/null +++ b/test/dependency-in-meta/bitbucket.yml @@ -0,0 +1,10 @@ +--- + +dependencies: + # from Bitbucket + - src: git+http://bitbucket.org/willthames/git-ansible-galaxy + version: v1.4 + + # from Bitbucket, alternative syntax and caveats + - src: http://bitbucket.org/willthames/hg-ansible-galaxy + scm: hg diff --git a/test/dependency-in-meta/galaxy.yml b/test/dependency-in-meta/galaxy.yml new file mode 100644 index 0000000..7f2c343 --- /dev/null +++ b/test/dependency-in-meta/galaxy.yml @@ -0,0 +1,5 @@ +--- + +dependencies: + # from galaxy + - src: yatesr.timezone diff --git a/test/dependency-in-meta/github.yml b/test/dependency-in-meta/github.yml new file mode 100644 index 0000000..234c85a --- /dev/null +++ b/test/dependency-in-meta/github.yml @@ -0,0 +1,10 @@ +--- + +dependencies: + # from GitHub + - src: https://github.com/bennojoy/nginx + + # from GitHub, overriding the name and specifying a specific tag + - src: https://github.com/bennojoy/nginx + version: master + name: nginx_role diff --git a/test/dependency-in-meta/gitlab.yml b/test/dependency-in-meta/gitlab.yml new file mode 100644 index 0000000..03b741e --- /dev/null +++ b/test/dependency-in-meta/gitlab.yml @@ -0,0 +1,7 @@ +--- + +dependencies: + # from GitLab or other git-based scm + - src: git@gitlab.company.com:mygroup/ansible-base.git + scm: git + version: "0.1" # quoted, so YAML doesn't parse this as a floating-point value diff --git a/test/dependency-in-meta/webserver.yml b/test/dependency-in-meta/webserver.yml new file mode 100644 index 0000000..2209ee2 --- /dev/null +++ b/test/dependency-in-meta/webserver.yml @@ -0,0 +1,6 @@ +--- + +dependencies: + # from a webserver, where the role is packaged in a tar.gz + - src: https://some.webserver.example.com/files/master.tar.gz + name: http-role diff --git a/test/directory with spaces/main.yml b/test/directory with spaces/main.yml new file mode 100644 index 0000000..1969c6e --- /dev/null +++ b/test/directory with spaces/main.yml @@ -0,0 +1 @@ +- debug: msg="tasks in directory with spaces included" diff --git a/test/ematchtest.yml b/test/ematchtest.yml new file mode 100644 index 0000000..333526c --- /dev/null +++ b/test/ematchtest.yml @@ -0,0 +1,5 @@ +hello +nothing +exciting +is +happening diff --git a/test/emptytags.yml b/test/emptytags.yml new file mode 100644 index 0000000..26758dc --- /dev/null +++ b/test/emptytags.yml @@ -0,0 +1,7 @@ +--- +- hosts: all + + tasks: + - name: hello world + debug: msg="hello world" + tags: diff --git a/test/fixtures/ansible-config-invalid.yml b/test/fixtures/ansible-config-invalid.yml new file mode 100644 index 0000000..ca8c431 --- /dev/null +++ b/test/fixtures/ansible-config-invalid.yml @@ -0,0 +1,3 @@ +# invalid .ansible-lint config file +- foo +- bar diff --git a/test/fixtures/ansible-config.yml b/test/fixtures/ansible-config.yml new file mode 100644 index 0000000..4c94267 --- /dev/null +++ b/test/fixtures/ansible-config.yml @@ -0,0 +1,4 @@ +--- +verbosity: 1 + +# vim: et:sw=2:syntax=yaml:ts=2: diff --git a/test/fixtures/config-with-relative-path.yml b/test/fixtures/config-with-relative-path.yml new file mode 100644 index 0000000..51ac404 --- /dev/null +++ b/test/fixtures/config-with-relative-path.yml @@ -0,0 +1,5 @@ +--- +exclude_paths: +- ../test-role/ + +# vim: et:sw=2:syntax=yaml:ts=2: diff --git a/test/fixtures/exclude-paths-with-expands.yml b/test/fixtures/exclude-paths-with-expands.yml new file mode 100644 index 0000000..20c742d --- /dev/null +++ b/test/fixtures/exclude-paths-with-expands.yml @@ -0,0 +1,6 @@ +--- +exclude_paths: +- ~/.ansible/roles +- $HOME/.ansible/roles + +# vim: et:sw=2:syntax=yaml:ts=2: diff --git a/test/fixtures/exclude-paths.yml b/test/fixtures/exclude-paths.yml new file mode 100644 index 0000000..a8bb938 --- /dev/null +++ b/test/fixtures/exclude-paths.yml @@ -0,0 +1,5 @@ +--- +exclude_paths: +- ../ + +# vim: et:sw=2:syntax=yaml:ts=2: diff --git a/test/fixtures/parseable.yml b/test/fixtures/parseable.yml new file mode 100644 index 0000000..4603267 --- /dev/null +++ b/test/fixtures/parseable.yml @@ -0,0 +1,4 @@ +--- +parseable: true + +# vim: et:sw=2:syntax=yaml:ts=2: diff --git a/test/fixtures/quiet.yml b/test/fixtures/quiet.yml new file mode 100644 index 0000000..583556f --- /dev/null +++ b/test/fixtures/quiet.yml @@ -0,0 +1,4 @@ +--- +quiet: true + +# vim: et:sw=2:syntax=yaml:ts=2: diff --git a/test/fixtures/rulesdir-defaults.yml b/test/fixtures/rulesdir-defaults.yml new file mode 100644 index 0000000..9eb30c6 --- /dev/null +++ b/test/fixtures/rulesdir-defaults.yml @@ -0,0 +1,6 @@ +--- +rulesdir: +- ./rules +use_default_rules: true + +# vim: et:sw=2:syntax=yaml:ts=2: diff --git a/test/fixtures/rulesdir.yml b/test/fixtures/rulesdir.yml new file mode 100644 index 0000000..6ecd43d --- /dev/null +++ b/test/fixtures/rulesdir.yml @@ -0,0 +1,5 @@ +--- +rulesdir: +- ./rules + +# vim: et:sw=2:syntax=yaml:ts=2: diff --git a/test/fixtures/show-abspath.yml b/test/fixtures/show-abspath.yml new file mode 100644 index 0000000..1945d2e --- /dev/null +++ b/test/fixtures/show-abspath.yml @@ -0,0 +1,4 @@ +--- +display_relative_path: false + +# vim: et:sw=2:syntax=2:ts=2: diff --git a/test/fixtures/show-relpath.yml b/test/fixtures/show-relpath.yml new file mode 100644 index 0000000..7e7c3e6 --- /dev/null +++ b/test/fixtures/show-relpath.yml @@ -0,0 +1,4 @@ +--- +display_relative_path: true + +# vim: et:sw=2:syntax=2:ts=2: diff --git a/test/fixtures/skip-tags.yml b/test/fixtures/skip-tags.yml new file mode 100644 index 0000000..1f64b00 --- /dev/null +++ b/test/fixtures/skip-tags.yml @@ -0,0 +1,5 @@ +--- +skip_list: +- "bad_tag" + +# vim: et:sw=2:syntax=yaml:ts=2: diff --git a/test/fixtures/tags.yml b/test/fixtures/tags.yml new file mode 100644 index 0000000..39f444a --- /dev/null +++ b/test/fixtures/tags.yml @@ -0,0 +1,5 @@ +--- +tags: + - skip_ansible_lint + +# vim: et:sw=2:syntax=yaml:ts=2: diff --git a/test/fixtures/unknown-type.yml b/test/fixtures/unknown-type.yml new file mode 100644 index 0000000..54c6d2b --- /dev/null +++ b/test/fixtures/unknown-type.yml @@ -0,0 +1,2 @@ +--- +some: map diff --git a/test/fixtures/verbosity.yml b/test/fixtures/verbosity.yml new file mode 100644 index 0000000..4c94267 --- /dev/null +++ b/test/fixtures/verbosity.yml @@ -0,0 +1,4 @@ +--- +verbosity: 1 + +# vim: et:sw=2:syntax=yaml:ts=2: diff --git a/test/foo.txt b/test/foo.txt new file mode 100644 index 0000000..94f8f1a --- /dev/null +++ b/test/foo.txt @@ -0,0 +1 @@ +Foo file diff --git a/test/include-import-role.yml b/test/include-import-role.yml new file mode 100644 index 0000000..7050ed1 --- /dev/null +++ b/test/include-import-role.yml @@ -0,0 +1,17 @@ +- hosts: all + vars: + var_is_set: no + + tasks: + - import_role: + name: test-role + +- hosts: all + vars: + var_is_set: no + + tasks: + - include_role: + name: test-role + tasks_from: world + when: "{{ var_is_set }}" diff --git a/test/include-import-tasks-in-role.yml b/test/include-import-tasks-in-role.yml new file mode 100644 index 0000000..313fc28 --- /dev/null +++ b/test/include-import-tasks-in-role.yml @@ -0,0 +1,3 @@ +- hosts: all + roles: + - role-with-included-imported-tasks diff --git a/test/include-in-block-inner.yml b/test/include-in-block-inner.yml new file mode 100644 index 0000000..be491a1 --- /dev/null +++ b/test/include-in-block-inner.yml @@ -0,0 +1,5 @@ +--- + +- block: + - include: simpletask.yml + tags: ['foo'] diff --git a/test/include-in-block.yml b/test/include-in-block.yml new file mode 100644 index 0000000..74f1f99 --- /dev/null +++ b/test/include-in-block.yml @@ -0,0 +1,5 @@ +--- +- hosts: all + + tasks: + - include: include-in-block-inner.yml diff --git a/test/included-handlers.yml b/test/included-handlers.yml new file mode 100644 index 0000000..322b347 --- /dev/null +++ b/test/included-handlers.yml @@ -0,0 +1,6 @@ +--- +- name: restart xyz + service: name=xyz state=restarted +# see Issue #165 +- name: command handler issue 165 + command: do something diff --git a/test/included-with-lint.yml b/test/included-with-lint.yml new file mode 100644 index 0000000..d92af1a --- /dev/null +++ b/test/included-with-lint.yml @@ -0,0 +1,4 @@ +# missing a task name +- yum: + name: ansible + until: result|success diff --git a/test/includedoesnotexist.yml b/test/includedoesnotexist.yml new file mode 100644 index 0000000..b123339 --- /dev/null +++ b/test/includedoesnotexist.yml @@ -0,0 +1,3 @@ +--- +- pre_tasks: + - include: "doesnotexist.yml" diff --git a/test/jinja2-when-failure.yml b/test/jinja2-when-failure.yml new file mode 100644 index 0000000..aec7269 --- /dev/null +++ b/test/jinja2-when-failure.yml @@ -0,0 +1,10 @@ +- hosts: all + tasks: + - name: test when with jinja2 + debug: msg=text + when: "{{ false }}" + +- hosts: all + roles: + - role: test + when: "{{ '1' = '1' }}" diff --git a/test/jinja2-when-success.yml b/test/jinja2-when-success.yml new file mode 100644 index 0000000..20d3db9 --- /dev/null +++ b/test/jinja2-when-success.yml @@ -0,0 +1,8 @@ +- hosts: all + tasks: + - name: test when + debug: msg=text + when: true + - name: test when 2 + debug: msg=text2 + when: 1 = 1 diff --git a/test/local-content/README.md b/test/local-content/README.md new file mode 100644 index 0000000..2b6322a --- /dev/null +++ b/test/local-content/README.md @@ -0,0 +1,6 @@ +The reason that every roles test gets its own directory is that while they +use the same three roles, the way the tests work makes sure that when the +second one runs, the roles and their local plugins from the first test are +still known to Ansible. For that reason, their names reflect the directory +they are in to make sure that tests don't use modules/plugins found by +other tests. diff --git a/test/local-content/collections/ansible_collections/testns/testcoll/galaxy.yml b/test/local-content/collections/ansible_collections/testns/testcoll/galaxy.yml new file mode 100644 index 0000000..43dd2e9 --- /dev/null +++ b/test/local-content/collections/ansible_collections/testns/testcoll/galaxy.yml @@ -0,0 +1,3 @@ +namespace: testns +name: testcoll +version: 0.1.0 diff --git a/test/local-content/collections/ansible_collections/testns/testcoll/plugins/filter/test_filter.py b/test/local-content/collections/ansible_collections/testns/testcoll/plugins/filter/test_filter.py new file mode 100644 index 0000000..ac9e854 --- /dev/null +++ b/test/local-content/collections/ansible_collections/testns/testcoll/plugins/filter/test_filter.py @@ -0,0 +1,16 @@ +"""A filter plugin.""" + + +def a_test_filter(a, b): + """Return a string containing both a and b.""" + return '{0}:{1}'.format(a, b) + + +class FilterModule(object): + """Filter plugin.""" + + def filters(self): + """Return filters.""" + return { + 'test_filter': a_test_filter + } diff --git a/test/local-content/collections/ansible_collections/testns/testcoll/plugins/modules/test_module_2.py b/test/local-content/collections/ansible_collections/testns/testcoll/plugins/modules/test_module_2.py new file mode 100644 index 0000000..cae1a26 --- /dev/null +++ b/test/local-content/collections/ansible_collections/testns/testcoll/plugins/modules/test_module_2.py @@ -0,0 +1,14 @@ +#!/usr/bin/python +"""A module.""" + +from ansible.module_utils.basic import AnsibleModule + + +def main() -> None: + """Execute module.""" + module = AnsibleModule(dict()) + module.exit_json(msg="Hello 2!") + + +if __name__ == '__main__': + main() diff --git a/test/local-content/test-collection.yml b/test/local-content/test-collection.yml new file mode 100644 index 0000000..bc3ed1b --- /dev/null +++ b/test/local-content/test-collection.yml @@ -0,0 +1,10 @@ +--- +- name: Use module and filter plugin from local collection + hosts: localhost + tasks: + - name: Use module from local collection + testns.testcoll.test_module_2: + - name: Use filter from local collection + assert: + that: + - 1 | testns.testcoll.test_filter(2) == '1:2' diff --git a/test/local-content/test-roles-failed-complete/roles/role1/library/test_module_1_failed_complete.py b/test/local-content/test-roles-failed-complete/roles/role1/library/test_module_1_failed_complete.py new file mode 100644 index 0000000..1c63fdd --- /dev/null +++ b/test/local-content/test-roles-failed-complete/roles/role1/library/test_module_1_failed_complete.py @@ -0,0 +1,14 @@ +#!/usr/bin/python +"""A module.""" + +from ansible.module_utils.basic import AnsibleModule + + +def main() -> None: + """Execute module.""" + module = AnsibleModule(dict()) + module.exit_json(msg="Hello 1!") + + +if __name__ == '__main__': + main() diff --git a/test/local-content/test-roles-failed-complete/roles/role1/tasks/main.yml b/test/local-content/test-roles-failed-complete/roles/role1/tasks/main.yml new file mode 100644 index 0000000..680dcab --- /dev/null +++ b/test/local-content/test-roles-failed-complete/roles/role1/tasks/main.yml @@ -0,0 +1,3 @@ +--- +- name: Use local module 1 + test_module_1_failed_complete: diff --git a/test/local-content/test-roles-failed-complete/roles/role2/tasks/main.yml b/test/local-content/test-roles-failed-complete/roles/role2/tasks/main.yml new file mode 100644 index 0000000..8646f6b --- /dev/null +++ b/test/local-content/test-roles-failed-complete/roles/role2/tasks/main.yml @@ -0,0 +1,11 @@ +--- +- name: Use local module from other role that has been included before this one + # If it has not been included before, loading this role fails! + test_module_1_failed_complete: +- name: Use local module from other role that has been included before this one + # If it has not been included before, loading this role fails! + test_module_3_failed_complete: +- name: Use local test plugin + assert: + that: + - "'2' is b_test_failed_complete '12345'" diff --git a/test/local-content/test-roles-failed-complete/roles/role2/test_plugins/b_failed_complete.py b/test/local-content/test-roles-failed-complete/roles/role2/test_plugins/b_failed_complete.py new file mode 100644 index 0000000..abc1049 --- /dev/null +++ b/test/local-content/test-roles-failed-complete/roles/role2/test_plugins/b_failed_complete.py @@ -0,0 +1,16 @@ +"""A test plugin.""" + + +def compatibility_in_test(a, b): + """Return True when a is contained in b.""" + return a in b + + +class TestModule: + """Test plugin.""" + + def tests(self): + """Return tests.""" + return { + 'b_test_failed_complete': compatibility_in_test, + } diff --git a/test/local-content/test-roles-failed-complete/roles/role3/library/test_module_3_failed_complete.py b/test/local-content/test-roles-failed-complete/roles/role3/library/test_module_3_failed_complete.py new file mode 100644 index 0000000..c7296be --- /dev/null +++ b/test/local-content/test-roles-failed-complete/roles/role3/library/test_module_3_failed_complete.py @@ -0,0 +1,14 @@ +#!/usr/bin/python +"""A module.""" + +from ansible.module_utils.basic import AnsibleModule + + +def main() -> None: + """Execute module.""" + module = AnsibleModule(dict()) + module.exit_json(msg="Hello 3!") + + +if __name__ == '__main__': + main() diff --git a/test/local-content/test-roles-failed-complete/roles/role3/tasks/main.yml b/test/local-content/test-roles-failed-complete/roles/role3/tasks/main.yml new file mode 100644 index 0000000..7a36734 --- /dev/null +++ b/test/local-content/test-roles-failed-complete/roles/role3/tasks/main.yml @@ -0,0 +1,3 @@ +--- +- name: Use local module 3 + test_module_3_failed_complete: diff --git a/test/local-content/test-roles-failed-complete/test.yml b/test/local-content/test-roles-failed-complete/test.yml new file mode 100644 index 0000000..1160bb5 --- /dev/null +++ b/test/local-content/test-roles-failed-complete/test.yml @@ -0,0 +1,5 @@ +--- +- name: Include role which expects module that is local to other role which is not loaded + hosts: localhost + roles: + - role2 diff --git a/test/local-content/test-roles-failed/roles/role1/library/test_module_1_failed.py b/test/local-content/test-roles-failed/roles/role1/library/test_module_1_failed.py new file mode 100644 index 0000000..1c63fdd --- /dev/null +++ b/test/local-content/test-roles-failed/roles/role1/library/test_module_1_failed.py @@ -0,0 +1,14 @@ +#!/usr/bin/python +"""A module.""" + +from ansible.module_utils.basic import AnsibleModule + + +def main() -> None: + """Execute module.""" + module = AnsibleModule(dict()) + module.exit_json(msg="Hello 1!") + + +if __name__ == '__main__': + main() diff --git a/test/local-content/test-roles-failed/roles/role1/tasks/main.yml b/test/local-content/test-roles-failed/roles/role1/tasks/main.yml new file mode 100644 index 0000000..257493a --- /dev/null +++ b/test/local-content/test-roles-failed/roles/role1/tasks/main.yml @@ -0,0 +1,3 @@ +--- +- name: Use local module 1 + test_module_1_failed: diff --git a/test/local-content/test-roles-failed/roles/role2/tasks/main.yml b/test/local-content/test-roles-failed/roles/role2/tasks/main.yml new file mode 100644 index 0000000..48daca6 --- /dev/null +++ b/test/local-content/test-roles-failed/roles/role2/tasks/main.yml @@ -0,0 +1,11 @@ +--- +- name: Use local module from other role that has been included before this one + # If it has not been included before, loading this role fails! + test_module_1_failed: +- name: Use local module from other role that has been included before this one + # If it has not been included before, loading this role fails! + test_module_3_failed: +- name: Use local test plugin + assert: + that: + - "'2' is b_test_failed '12345'" diff --git a/test/local-content/test-roles-failed/roles/role2/test_plugins/b_failed.py b/test/local-content/test-roles-failed/roles/role2/test_plugins/b_failed.py new file mode 100644 index 0000000..09a02a3 --- /dev/null +++ b/test/local-content/test-roles-failed/roles/role2/test_plugins/b_failed.py @@ -0,0 +1,16 @@ +"""A test plugin.""" + + +def compatibility_in_test(a, b): + """Return True when a is contained in b.""" + return a in b + + +class TestModule: + """Test plugin.""" + + def tests(self): + """Return tests.""" + return { + 'b_test_failed': compatibility_in_test, + } diff --git a/test/local-content/test-roles-failed/roles/role3/library/test_module_3_failed.py b/test/local-content/test-roles-failed/roles/role3/library/test_module_3_failed.py new file mode 100644 index 0000000..c7296be --- /dev/null +++ b/test/local-content/test-roles-failed/roles/role3/library/test_module_3_failed.py @@ -0,0 +1,14 @@ +#!/usr/bin/python +"""A module.""" + +from ansible.module_utils.basic import AnsibleModule + + +def main() -> None: + """Execute module.""" + module = AnsibleModule(dict()) + module.exit_json(msg="Hello 3!") + + +if __name__ == '__main__': + main() diff --git a/test/local-content/test-roles-failed/roles/role3/tasks/main.yml b/test/local-content/test-roles-failed/roles/role3/tasks/main.yml new file mode 100644 index 0000000..ad17eb0 --- /dev/null +++ b/test/local-content/test-roles-failed/roles/role3/tasks/main.yml @@ -0,0 +1,3 @@ +--- +- name: Use local module 3 + test_module_3_failed: diff --git a/test/local-content/test-roles-failed/test.yml b/test/local-content/test-roles-failed/test.yml new file mode 100644 index 0000000..08ff0f6 --- /dev/null +++ b/test/local-content/test-roles-failed/test.yml @@ -0,0 +1,7 @@ +--- +- name: Use roles with local module in wrong order, so that Ansible fails + hosts: localhost + roles: + - role2 + - role3 + - role1 diff --git a/test/local-content/test-roles-success/roles/role1/library/test_module_1_success.py b/test/local-content/test-roles-success/roles/role1/library/test_module_1_success.py new file mode 100644 index 0000000..1c63fdd --- /dev/null +++ b/test/local-content/test-roles-success/roles/role1/library/test_module_1_success.py @@ -0,0 +1,14 @@ +#!/usr/bin/python +"""A module.""" + +from ansible.module_utils.basic import AnsibleModule + + +def main() -> None: + """Execute module.""" + module = AnsibleModule(dict()) + module.exit_json(msg="Hello 1!") + + +if __name__ == '__main__': + main() diff --git a/test/local-content/test-roles-success/roles/role1/tasks/main.yml b/test/local-content/test-roles-success/roles/role1/tasks/main.yml new file mode 100644 index 0000000..ba920af --- /dev/null +++ b/test/local-content/test-roles-success/roles/role1/tasks/main.yml @@ -0,0 +1,3 @@ +--- +- name: Use local module 1 + test_module_1_success: diff --git a/test/local-content/test-roles-success/roles/role2/tasks/main.yml b/test/local-content/test-roles-success/roles/role2/tasks/main.yml new file mode 100644 index 0000000..a540cf1 --- /dev/null +++ b/test/local-content/test-roles-success/roles/role2/tasks/main.yml @@ -0,0 +1,11 @@ +--- +- name: Use local module from other role that has been included before this one + # If it has not been included before, loading this role fails! + test_module_1_success: +- name: Use local module from other role that has been included before this one + # If it has not been included before, loading this role fails! + test_module_3_success: +- name: Use local test plugin + assert: + that: + - "'2' is b_test_success '12345'" diff --git a/test/local-content/test-roles-success/roles/role2/test_plugins/b_success.py b/test/local-content/test-roles-success/roles/role2/test_plugins/b_success.py new file mode 100644 index 0000000..bcef377 --- /dev/null +++ b/test/local-content/test-roles-success/roles/role2/test_plugins/b_success.py @@ -0,0 +1,16 @@ +"""A test plugin.""" + + +def compatibility_in_test(a, b): + """Return True when a is contained in b.""" + return a in b + + +class TestModule: + """Test plugin.""" + + def tests(self): + """Return tests.""" + return { + 'b_test_success': compatibility_in_test, + } diff --git a/test/local-content/test-roles-success/roles/role3/library/test_module_3_success.py b/test/local-content/test-roles-success/roles/role3/library/test_module_3_success.py new file mode 100644 index 0000000..c7296be --- /dev/null +++ b/test/local-content/test-roles-success/roles/role3/library/test_module_3_success.py @@ -0,0 +1,14 @@ +#!/usr/bin/python +"""A module.""" + +from ansible.module_utils.basic import AnsibleModule + + +def main() -> None: + """Execute module.""" + module = AnsibleModule(dict()) + module.exit_json(msg="Hello 3!") + + +if __name__ == '__main__': + main() diff --git a/test/local-content/test-roles-success/roles/role3/tasks/main.yml b/test/local-content/test-roles-success/roles/role3/tasks/main.yml new file mode 100644 index 0000000..c77a7c8 --- /dev/null +++ b/test/local-content/test-roles-success/roles/role3/tasks/main.yml @@ -0,0 +1,3 @@ +--- +- name: Use local module 3 + test_module_3_success: diff --git a/test/local-content/test-roles-success/test.yml b/test/local-content/test-roles-success/test.yml new file mode 100644 index 0000000..df17c7d --- /dev/null +++ b/test/local-content/test-roles-success/test.yml @@ -0,0 +1,7 @@ +--- +- name: Use roles with local modules and test plugins + hosts: localhost + roles: + - role1 + - role3 + - role2 diff --git a/test/multiline-brackets-do-not-match-test.yml b/test/multiline-brackets-do-not-match-test.yml new file mode 100644 index 0000000..dfa5ff8 --- /dev/null +++ b/test/multiline-brackets-do-not-match-test.yml @@ -0,0 +1,22 @@ +--- +- hosts: foo + roles: + - ../../../roles/base_os + - ../../../roles/repos + - { + role: ../../../roles/openshift_master, + oo_minion_ips: "{ hostvars['localhost'].oo_minion_ips | default(['']) }}", + oo_bind_ip: "{{ hostvars[inventory_hostname].ansible_eth0.ipv4.address | default(['']) }}" + } + - ../../../roles/pods + +- name: "Set Origin specific facts on localhost (for later use)" + hosts: localhost + gather_facts: no + tasks: + - name: Setting oo_minion_ips fact on localhost + set_fact: + oo_minion_ips: "{{ hostvars + | oo_select_keys(groups['tag_env-host-type-' + oo_env + '-openshift-minion']) + | oo_collect(attribute='ansible_eth0.ipv4.address') }" + when: groups['tag_env-host-type-' + oo_env + '-openshift-minion'] is defined diff --git a/test/multiline-bracketsmatchtest.yml b/test/multiline-bracketsmatchtest.yml new file mode 100644 index 0000000..703f225 --- /dev/null +++ b/test/multiline-bracketsmatchtest.yml @@ -0,0 +1,22 @@ +--- +- hosts: foo + roles: + - ../../../roles/base_os + - ../../../roles/repos + - { + role: ../../../roles/openshift_master, + oo_minion_ips: "{{ hostvars['localhost'].oo_minion_ips | default(['']) }}", + oo_bind_ip: "{{ hostvars[inventory_hostname].ansible_eth0.ipv4.address | default(['']) }}" + } + - ../../../roles/pods + +- name: "Set Origin specific facts on localhost (for later use)" + hosts: localhost + gather_facts: no + tasks: + - name: Setting oo_minion_ips fact on localhost + set_fact: + oo_minion_ips: "{{ hostvars + | oo_select_keys(groups['tag_env-host-type-' + oo_env + '-openshift-minion']) + | oo_collect(attribute='ansible_eth0.ipv4.address') }}" + when: groups['tag_env-host-type-' + oo_env + '-openshift-minion'] is defined diff --git a/test/nestedincludes.yml b/test/nestedincludes.yml new file mode 100644 index 0000000..5985b54 --- /dev/null +++ b/test/nestedincludes.yml @@ -0,0 +1,2 @@ +--- +- include: simpletask.yml tags=nginx diff --git a/test/nomatchestest.yml b/test/nomatchestest.yml new file mode 100644 index 0000000..e48ef92 --- /dev/null +++ b/test/nomatchestest.yml @@ -0,0 +1,9 @@ +--- +- hosts: whatever + + tasks: + - name: hello world + action: debug msg="Hello!" + + - name: this should be fine too + action: file state=touch dest=./wherever mode=0600 diff --git a/test/norole.yml b/test/norole.yml new file mode 100644 index 0000000..3a18e94 --- /dev/null +++ b/test/norole.yml @@ -0,0 +1,5 @@ +--- +- hosts: + - localhost + roles: + - name: node diff --git a/test/norole2.yml b/test/norole2.yml new file mode 100644 index 0000000..2ee7a83 --- /dev/null +++ b/test/norole2.yml @@ -0,0 +1,5 @@ +--- +- hosts: + - localhost + roles: + - name: node diff --git a/test/package-check-failure.yml b/test/package-check-failure.yml new file mode 100644 index 0000000..8c25f3c --- /dev/null +++ b/test/package-check-failure.yml @@ -0,0 +1,14 @@ +- hosts: localhost + tasks: + - name: install ansible + yum: name=ansible state=latest + + - name: install ansible-lint + pip: name=ansible-lint + args: + state: latest + + - name: install some-package + package: + name: some-package + state: latest diff --git a/test/package-check-success.yml b/test/package-check-success.yml new file mode 100644 index 0000000..d649dc7 --- /dev/null +++ b/test/package-check-success.yml @@ -0,0 +1,15 @@ +- hosts: localhost + tasks: + - name: install ansible + yum: name=ansible-2.1.0.0 state=present + + - name: install ansible-lint + pip: name=ansible-lint + args: + state: present + version: 3.1.2 + + - name: install some-package + package: + name: some-package + state: present diff --git a/test/playbook-import/playbook_imported.yml b/test/playbook-import/playbook_imported.yml new file mode 100644 index 0000000..4dc646a --- /dev/null +++ b/test/playbook-import/playbook_imported.yml @@ -0,0 +1,9 @@ +--- +- hosts: localhost + connection: local + gather_facts: no + tasks: + - command: echo "no name" # should generate 502 + - name: Another task + debug: + msg: debug message diff --git a/test/playbook-import/playbook_parent.yml b/test/playbook-import/playbook_parent.yml new file mode 100644 index 0000000..7e8b524 --- /dev/null +++ b/test/playbook-import/playbook_parent.yml @@ -0,0 +1,3 @@ +--- +- name: Importing another playbook + import_playbook: playbook_imported.yml diff --git a/test/role-with-handler/a-role/handlers/main.yml b/test/role-with-handler/a-role/handlers/main.yml new file mode 100644 index 0000000..59ae800 --- /dev/null +++ b/test/role-with-handler/a-role/handlers/main.yml @@ -0,0 +1,5 @@ +--- +- name: do anything + shell: echo merp | cat + when: + - something.changed diff --git a/test/role-with-handler/main.yml b/test/role-with-handler/main.yml new file mode 100644 index 0000000..9b37597 --- /dev/null +++ b/test/role-with-handler/main.yml @@ -0,0 +1,4 @@ +- hosts: localhost + name: foo + roles: + - { role: a-role } diff --git a/test/role-with-included-imported-tasks/tasks/imported_tasks.yml b/test/role-with-included-imported-tasks/tasks/imported_tasks.yml new file mode 100644 index 0000000..32a2b23 --- /dev/null +++ b/test/role-with-included-imported-tasks/tasks/imported_tasks.yml @@ -0,0 +1,2 @@ +- name: This is a task that should be imported + ping: diff --git a/test/role-with-included-imported-tasks/tasks/included_tasks.yml b/test/role-with-included-imported-tasks/tasks/included_tasks.yml new file mode 100644 index 0000000..37b59d2 --- /dev/null +++ b/test/role-with-included-imported-tasks/tasks/included_tasks.yml @@ -0,0 +1,2 @@ +- name: This is a task that should be included + ping: diff --git a/test/role-with-included-imported-tasks/tasks/main.yml b/test/role-with-included-imported-tasks/tasks/main.yml new file mode 100644 index 0000000..81de7cb --- /dev/null +++ b/test/role-with-included-imported-tasks/tasks/main.yml @@ -0,0 +1,6 @@ +- include_tasks: included_tasks.yml +- import_tasks: imported_tasks.yml +- include_tasks: + file: included_tasks.yml + apply: + tags: sometag diff --git a/test/roles/ansible-role-foo/tasks/main.yaml b/test/roles/ansible-role-foo/tasks/main.yaml new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/roles/ansible-role-foo/tasks/main.yaml diff --git a/test/roles/invalid-name/tasks/main.yaml b/test/roles/invalid-name/tasks/main.yaml new file mode 100644 index 0000000..1270837 --- /dev/null +++ b/test/roles/invalid-name/tasks/main.yaml @@ -0,0 +1,4 @@ +--- +- name: foo + debug: + msg: foo diff --git a/test/roles/invalid_due_to_meta/meta/main.yml b/test/roles/invalid_due_to_meta/meta/main.yml new file mode 100644 index 0000000..0d4b0b2 --- /dev/null +++ b/test/roles/invalid_due_to_meta/meta/main.yml @@ -0,0 +1,8 @@ +galaxy_info: + role_name: invalid-due-to-meta + author: foo + description: foo + license: MIT + platforms: + - name: foo + min_ansible_version: 2.7 diff --git a/test/roles/invalid_due_to_meta/tasks/main.yaml b/test/roles/invalid_due_to_meta/tasks/main.yaml new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/roles/invalid_due_to_meta/tasks/main.yaml diff --git a/test/roles/test-role/molecule/default/include-import-role.yml b/test/roles/test-role/molecule/default/include-import-role.yml new file mode 100644 index 0000000..0e1d166 --- /dev/null +++ b/test/roles/test-role/molecule/default/include-import-role.yml @@ -0,0 +1,6 @@ +--- +- name: test + gather_facts: no + hosts: all + roles: + - role: test-role diff --git a/test/roles/test-role/tasks/main.yml b/test/roles/test-role/tasks/main.yml new file mode 100644 index 0000000..53b968b --- /dev/null +++ b/test/roles/test-role/tasks/main.yml @@ -0,0 +1,2 @@ +- name: shell instead of command + shell: echo hello world diff --git a/test/roles/valid-due-to-meta/meta/main.yml b/test/roles/valid-due-to-meta/meta/main.yml new file mode 100644 index 0000000..8b8566b --- /dev/null +++ b/test/roles/valid-due-to-meta/meta/main.yml @@ -0,0 +1,8 @@ +galaxy_info: + role_name: valid_due_to_meta + author: foo + description: foo + license: MIT + platforms: + - name: foo + min_ansible_version: 2.7 diff --git a/test/roles/valid-due-to-meta/tasks/debian/main.yml b/test/roles/valid-due-to-meta/tasks/debian/main.yml new file mode 100644 index 0000000..6fa48c2 --- /dev/null +++ b/test/roles/valid-due-to-meta/tasks/debian/main.yml @@ -0,0 +1,2 @@ +# This empty task file is here to test that roles with tasks organized in subdirectories +# are handled correctly by ansible-lint. diff --git a/test/roles/valid-due-to-meta/tasks/main.yaml b/test/roles/valid-due-to-meta/tasks/main.yaml new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/roles/valid-due-to-meta/tasks/main.yaml diff --git a/test/rules/EMatcherRule.py b/test/rules/EMatcherRule.py new file mode 100644 index 0000000..ed65883 --- /dev/null +++ b/test/rules/EMatcherRule.py @@ -0,0 +1,12 @@ +from ansiblelint.rules import AnsibleLintRule + + +class EMatcherRule(AnsibleLintRule): + id = 'TEST0001' + description = 'This is a test rule that looks for lines ' + \ + 'containing the letter e' + shortdesc = 'The letter "e" is present' + tags = ['fake', 'dummy', 'test1'] + + def match(self, filename, line): + return "e" in line diff --git a/test/rules/UnsetVariableMatcherRule.py b/test/rules/UnsetVariableMatcherRule.py new file mode 100644 index 0000000..b4c1756 --- /dev/null +++ b/test/rules/UnsetVariableMatcherRule.py @@ -0,0 +1,12 @@ +from ansiblelint.rules import AnsibleLintRule + + +class UnsetVariableMatcherRule(AnsibleLintRule): + id = 'TEST0002' + shortdesc = 'Line contains untemplated variable' + description = 'This is a test rule that looks for lines ' + \ + 'post templating that still contain {{' + tags = ['fake', 'dummy', 'test2'] + + def match(self, filename, line): + return "{{" in line diff --git a/test/rules/__init__.py b/test/rules/__init__.py new file mode 100644 index 0000000..4a6e23f --- /dev/null +++ b/test/rules/__init__.py @@ -0,0 +1,3 @@ +"""Test rules resources.""" + +__all__ = ['UnsetVariableMatcherRule', 'EMatcherRule'] diff --git a/test/simpletask.yml b/test/simpletask.yml new file mode 100644 index 0000000..0aba042 --- /dev/null +++ b/test/simpletask.yml @@ -0,0 +1,3 @@ +--- +- name: hello world + debug: msg="Hello!" diff --git a/test/skiptasks.yml b/test/skiptasks.yml new file mode 100644 index 0000000..c6a130b --- /dev/null +++ b/test/skiptasks.yml @@ -0,0 +1,70 @@ +--- +- hosts: all + + tasks: + + - name: test 401 + action: git + + - name: test 402 + action: hg + + - name: test 303 + command: git log + changed_when: False + + - name: test 302 + command: creates=B chmod 644 A + + - name: test invalid action (skip) + foo: bar + tags: + - skip_ansible_lint + + - name: test 401 (skip) + action: git + tags: + - skip_ansible_lint + + - name: test 402 (skip) + action: hg + tags: + - skip_ansible_lint + + - name: test 303 (skip) + command: git log + tags: + - skip_ansible_lint + + - name: test 302 (skip) + command: chmod 644 A + tags: + - skip_ansible_lint + + - name: test 401 (don't warn) + command: git log + args: + warn: False + changed_when: False + + - name: test 402 (don't warn) + command: chmod 644 A + args: + warn: False + creates: B + + - name: test 402 (warn) + command: chmod 644 A + args: + warn: yes + creates: B + + - name: test 401 (don't warn single line) + command: warn=False chdir=/tmp/blah git log + changed_when: False + + - name: test 402 (don't warn single line) + command: warn=no creates=B chmod 644 A + + - name: test 402 (warn single line) + command: warn=yes creates=B chmod 644 A diff --git a/test/task-has-name-failure.yml b/test/task-has-name-failure.yml new file mode 100644 index 0000000..ce947f3 --- /dev/null +++ b/test/task-has-name-failure.yml @@ -0,0 +1,7 @@ +--- + +- hosts: all + tasks: + - command: echo "no name" + - name: + command: echo "empty name" diff --git a/test/task-has-name-success.yml b/test/task-has-name-success.yml new file mode 100644 index 0000000..b708f5a --- /dev/null +++ b/test/task-has-name-success.yml @@ -0,0 +1,9 @@ +--- + +- hosts: all + tasks: + - name: This task has a name + command: echo "Hello World" + - debug: + msg: "Hello World" + - meta: flush_handlers diff --git a/test/taskimports.yml b/test/taskimports.yml new file mode 100644 index 0000000..f21b678 --- /dev/null +++ b/test/taskimports.yml @@ -0,0 +1,9 @@ +--- +- hosts: webservers + vars: + varset: varset + tasks: + - import_tasks: nestedincludes.yml tags=nested + - import_tasks: "{{ varnotset }}.yml" + - import_tasks: "{{ varset }}.yml" + - import_tasks: "directory with spaces/main.yml" diff --git a/test/taskincludes.yml b/test/taskincludes.yml new file mode 100644 index 0000000..cba7909 --- /dev/null +++ b/test/taskincludes.yml @@ -0,0 +1,9 @@ +--- +- hosts: webservers + vars: + varset: varset + tasks: + - include: nestedincludes.yml tags=nested + - include: "{{ varnotset }}.yml" + - include: "{{ varset }}.yml" + - include: "directory with spaces/main.yml" diff --git a/test/taskincludes_2_4_style.yml b/test/taskincludes_2_4_style.yml new file mode 100644 index 0000000..f1ae9f4 --- /dev/null +++ b/test/taskincludes_2_4_style.yml @@ -0,0 +1,9 @@ +--- +- hosts: webservers + vars: + varset: varset + tasks: + - include_tasks: nestedincludes.yml tags=nested + - include_tasks: "{{ varnotset }}.yml" + - include_tasks: "{{ varset }}.yml" + - include_tasks: "directory with spaces/main.yml" diff --git a/test/test-role/tasks/main.yml b/test/test-role/tasks/main.yml new file mode 100644 index 0000000..53b968b --- /dev/null +++ b/test/test-role/tasks/main.yml @@ -0,0 +1,2 @@ +- name: shell instead of command + shell: echo hello world diff --git a/test/test-role/tasks/world.yml b/test/test-role/tasks/world.yml new file mode 100644 index 0000000..69ae661 --- /dev/null +++ b/test/test-role/tasks/world.yml @@ -0,0 +1 @@ +- command: echo this is a task without a name diff --git a/test/test/always-run-success.yml b/test/test/always-run-success.yml new file mode 100644 index 0000000..468a17c --- /dev/null +++ b/test/test/always-run-success.yml @@ -0,0 +1 @@ +- hosts: localhost diff --git a/test/testproject/roles/test-role/tasks/main.yml b/test/testproject/roles/test-role/tasks/main.yml new file mode 100644 index 0000000..53b968b --- /dev/null +++ b/test/testproject/roles/test-role/tasks/main.yml @@ -0,0 +1,2 @@ +- name: shell instead of command + shell: echo hello world diff --git a/test/unicode.yml b/test/unicode.yml new file mode 100644 index 0000000..c9204e4 --- /dev/null +++ b/test/unicode.yml @@ -0,0 +1,9 @@ +--- +- hosts: localhost + connection: local + vars: + unicode_var: a_b_cö + + tasks: + - name: bonjour, ça va? + file: state=touch dest=/tmp/naïve.yml mode=0600 diff --git a/test/using-bare-variables-failure.yml b/test/using-bare-variables-failure.yml new file mode 100644 index 0000000..865381b --- /dev/null +++ b/test/using-bare-variables-failure.yml @@ -0,0 +1,108 @@ +--- +- hosts: localhost + become: no + vars: + my_list: + - foo + - bar + + my_list2: + - 1 + - 2 + + my_list_of_dicts: + - foo: 1 + bar: 2 + - foo: 3 + bar: 4 + + my_list_of_lists: + - "{{ my_list }}" + - "{{ my_list2 }}" + + my_filenames: + - foo.txt + - bar.txt + + my_dict: + foo: bar + + tasks: + - name: with_items loop using bare variable + debug: + msg: "{{ item }}" + with_items: my_list + + - name: with_dict loop using bare variable + debug: + msg: "{{ item }}" + with_dict: my_dict + + ### Testing with_dict with a default empty dictionary + - name: with_dict loop using variable and default + debug: + msg: "{{ item.key }} - {{ item.value }}" + with_dict: uwsgi_ini | default({}) + + - name: with_nested loop using bare variable + debug: + msg: "{{ item.0 }} {{ item.1 }}" + with_nested: + - my_list + - "{{ my_list2 }}" + + - name: with_file loop using bare variable + debug: + msg: "{{ item }}" + with_file: my_list + + - name: with_fileglob loop using bare variable + debug: + msg: "{{ item }}" + with_fileglob: my_list + + - name: with_filetree loop using bare variable + debug: + msg: "{{ item }}" + with_filetree: my_list + + - name: with_together loop using bare variable + debug: + msg: "{{ item.0 }} {{ item.1 }}" + with_together: + - my_list + - "{{ my_list2 }}" + + - name: with_subelements loop using bare variable + debug: + msg: "{{ item.0 }}" + with_subelements: + - my_list_of_dicts + - bar + + - name: with_random_choice loop using bare variable + debug: + msg: "{{ item }}" + with_random_choice: my_list + + - name: with_first_found loop using bare variable + debug: + msg: "{{ item }}" + with_first_found: my_filenames + + - name: with_indexed_items loop + debug: + msg: "{{ item.0 }} {{ item.1 }}" + with_indexed_items: my_list + + - name: with_flattened loop + debug: + msg: "{{ item }}" + with_flattened: + - my_list + - my_list2 + + - name: with_flattened loop with a variable + debug: + msg: "{{ item }}" + with_flattened: my_list_of_lists diff --git a/test/using-bare-variables-success.yml b/test/using-bare-variables-success.yml new file mode 100644 index 0000000..c8f0f3f --- /dev/null +++ b/test/using-bare-variables-success.yml @@ -0,0 +1,200 @@ +--- +- hosts: localhost + become: no + vars: + my_list: + - foo + - bar + + my_list2: + - 1 + - 2 + + my_list_of_dicts: + - foo: 1 + bar: 2 + - foo: 3 + bar: 4 + + my_list_of_lists: + - "{{ my_list }}" + - "{{ my_list2 }}" + + my_filenames: + - foo.txt + - bar.txt + + my_dict: + foo: bar + + tasks: + ### Testing with_items loops + - name: with_items loop using static list + debug: + msg: "{{ item }}" + with_items: + - foo + - bar + + - name: with_items using a static hash + debug: + msg: "{{ item.key }} - {{ item.value }}" + with_items: + - { key: foo, value: 1 } + - { key: bar, value: 2 } + + - name: with_items loop using variable + debug: + msg: "{{ item }}" + with_items: "{{ my_list }}" + + ### Testing with_nested loops + - name: with_nested loop using static lists + debug: + msg: "{{ item[0] }} - {{ item[1] }}" + with_nested: + - [ 'foo', 'bar' ] + - [ '1', '2', '3' ] + + - name: with_nested loop using variable list and static + debug: + msg: "{{ item[0] }} - {{ item[1] }}" + with_nested: + - "{{ my_list }}" + - [ '1', '2', '3' ] + + ### Testing with_dict + - name: with_dict loop using variable + debug: + msg: "{{ item.key }} - {{ item.value }}" + with_dict: "{{ my_dict }}" + + ### Testing with_dict with a default empty dictionary + - name: with_dict loop using variable and default + debug: + msg: "{{ item.key }} - {{ item.value }}" + with_dict: "{{ uwsgi_ini | default({}) }}" + + ### Testing with_file + - name: with_file loop using static files list + debug: + msg: "{{ item }}" + with_file: + - foo.txt + - bar.txt + + - name: with_file loop using list of filenames + debug: + msg: "{{ item }}" + with_file: "{{ my_filenames }}" + + ### Testing with_fileglob + - name: with_fileglob loop using list of *.txt + debug: + msg: "{{ item }}" + with_fileglob: + - '*.txt' + + ### Testing non-list form of with_fileglob + - name: with_fileglob loop using single value *.txt + debug: + msg: "{{ item }}" + with_fileglob: '*.txt' + + ### Testing non-list form of with_fileglob with trailing templated pattern + - name: with_fileglob loop using templated pattern + debug: + msg: "{{ item }}" + with_fileglob: 'foo{{glob}}' + + ### Testing with_filetree + - name: with_filetree loop using list of path + debug: + msg: "{{ item }}" + with_filetree: + - path/to/dir1/ + - path/to/dir2/ + + ### Testing non-list form of with_filetree + - name: with_filetree loop using single path + debug: + msg: "{{ item }}" + with_filetree: path/to/dir/ + + ### Testing non-list form of with_filetree with trailing templated pattern + - name: with_filetree loop using templated pattern + debug: + msg: "{{ item }}" + with_filetree: 'path/to/{{ directory }}' + + ### Testing with_together + - name: with_together loop using variable lists + debug: + msg: "{{ item.0 }} - {{ item.1 }}" + with_together: + - "{{ my_list }}" + - "{{ my_list2 }}" + + - name: with_subelements loop + debug: + msg: "{{ item }}" + with_subelements: + - "{{ my_list_of_dicts }}" + - bar + + - name: with_sequence loop + debug: + msg: "{{ item }}" + with_sequence: count=2 + + - name: with_random_choice loop + debug: + msg: "{{ item }}" + with_random_choice: "{{ my_list }}" + + - name: with_first_found loop with static files list + debug: + msg: "{{ item }}" + with_first_found: + - foo.txt + - bar.txt + + - name: with_first_found loop with list of filenames + debug: + msg: "{{ item }}" + with_first_found: "{{ my_filenames }}" + + - name: with_indexed_items loop + debug: + msg: "{{ item.0 }} {{ item.1 }}" + with_indexed_items: "{{ my_list }}" + + - name: with_ini loop + debug: + msg: "{{ item }}" + with_ini: value[1-2] section=section1 file=foo.ini re=true + + - name: with_flattened loop + debug: + msg: "{{ item }}" + with_flattened: + - "{{ my_list }}" + - "{{ my_list2 }}" + + - name: with_flattened loop with a variable + debug: + msg: "{{ item }}" + with_flattened: "{{ my_list_of_lists }}" + + - name: with_flattened loop with a multiline template + debug: + msg: "{{ item }}" + with_flattened: > + {{ my_list + | union(my_list2) + | list }} + + - name: with_inventory_hostnames loop + debug: + msg: "{{ item }}" + with_inventory_hostnames: all diff --git a/test/varset.yml b/test/varset.yml new file mode 100644 index 0000000..fa28db9 --- /dev/null +++ b/test/varset.yml @@ -0,0 +1,3 @@ +- debug: msg="var was set" + +- git: repo=hello.git diff --git a/test/varunset.yml b/test/varunset.yml new file mode 100644 index 0000000..9e8a891 --- /dev/null +++ b/test/varunset.yml @@ -0,0 +1 @@ +- debug: msg="var was not set" diff --git a/test/with-skip-tag-id.yml b/test/with-skip-tag-id.yml new file mode 100644 index 0000000..12d2fb7 --- /dev/null +++ b/test/with-skip-tag-id.yml @@ -0,0 +1,6 @@ +- hosts: all + tasks: + - name: trailing whitespace on this line + git: + repo: '{{ archive_services_repo_url }}' + dest: '/home/www' |