summaryrefslogtreecommitdiffstats
path: root/test
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-14 20:04:50 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-14 20:04:50 +0000
commit782f8df6e41f29dce2db4970a3ad84aaeb7d8c5f (patch)
tree3a88a542cd8074743d251881131510157cfc510b /test
parentInitial commit. (diff)
downloadansible-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')
-rw-r--r--test/TestAlwaysRunRule.py24
-rw-r--r--test/TestAnsibleLintRule.py7
-rw-r--r--test/TestAnsibleSyntax.py16
-rw-r--r--test/TestBaseFormatter.py46
-rw-r--r--test/TestBecomeUserWithoutBecome.py24
-rw-r--r--test/TestCliRolePaths.py134
-rw-r--r--test/TestCommandHasChangesCheck.py24
-rw-r--r--test/TestCommandLineInvocationSameAsConfig.py137
-rw-r--r--test/TestComparisonToEmptyString.py39
-rw-r--r--test/TestComparisonToLiteralBool.py69
-rw-r--r--test/TestDependenciesInMeta.py22
-rw-r--r--test/TestDeprecatedModule.py32
-rw-r--r--test/TestEnvVarsInCommand.py90
-rw-r--r--test/TestExamples.py8
-rw-r--r--test/TestFormatter.py46
-rw-r--r--test/TestImportIncludeRole.py103
-rw-r--r--test/TestImportPlaybook.py17
-rw-r--r--test/TestImportWithMalformed.py65
-rw-r--r--test/TestIncludeMissFileWithRole.py122
-rw-r--r--test/TestIncludeMissingFileRule.py88
-rw-r--r--test/TestLineNumber.py36
-rw-r--r--test/TestLineTooLong.py24
-rw-r--r--test/TestLintRule.py45
-rw-r--r--test/TestLocalContent.py42
-rw-r--r--test/TestMatchError.py178
-rw-r--r--test/TestMetaChangeFromDefault.py33
-rw-r--r--test/TestMetaMainHasInfo.py94
-rw-r--r--test/TestMetaVideoLinks.py35
-rw-r--r--test/TestMissingFilePermissionsRule.py110
-rw-r--r--test/TestNestedJinjaRule.py208
-rw-r--r--test/TestNoFormattingInWhenRule.py24
-rw-r--r--test/TestOctalPermissions.py112
-rw-r--r--test/TestPackageIsNotLatest.py24
-rw-r--r--test/TestRoleHandlers.py20
-rw-r--r--test/TestRoleNames.py82
-rw-r--r--test/TestRoleRelativePath.py52
-rw-r--r--test/TestRuleProperties.py11
-rw-r--r--test/TestRulesCollection.py112
-rw-r--r--test/TestRunner.py86
-rw-r--r--test/TestShellWithoutPipefail.py84
-rw-r--r--test/TestSkipImportPlaybook.py35
-rw-r--r--test/TestSkipInsideYaml.py122
-rw-r--r--test/TestSkipPlaybookItems.py99
-rw-r--r--test/TestSudoRule.py67
-rw-r--r--test/TestTaskHasName.py24
-rw-r--r--test/TestTaskIncludes.py34
-rw-r--r--test/TestTaskNoLocalAction.py24
-rw-r--r--test/TestUseCommandInsteadOfShell.py24
-rw-r--r--test/TestUseHandlerRatherThanWhenChanged.py88
-rw-r--r--test/TestUsingBareVariablesIsDeprecated.py24
-rw-r--r--test/TestUtils.py317
-rw-r--r--test/TestVariableHasSpaces.py54
-rw-r--r--test/TestWithSkipTagId.py39
-rw-r--r--test/__init__.py1
-rw-r--r--test/always-run-failure.yml6
-rw-r--r--test/always-run-success.yml6
-rw-r--r--test/bar.txt1
-rw-r--r--test/become-user-without-become-failure.yml26
-rw-r--r--test/become-user-without-become-success.yml30
-rw-r--r--test/become.yml14
-rw-r--r--test/block.yml26
-rw-r--r--test/blockincludes.yml13
-rw-r--r--test/blockincludes2.yml13
-rw-r--r--test/brackets-do-not-match-test.yml22
-rw-r--r--test/bracketsmatchtest.yml3
-rw-r--r--test/command-check-failure.yml11
-rw-r--r--test/command-check-success.yml61
-rw-r--r--test/command-instead-of-shell-failure.yml8
-rw-r--r--test/command-instead-of-shell-success.yml37
-rw-r--r--test/common-include-1.yml4
-rw-r--r--test/common-include-2.yml4
-rw-r--r--test/contains_secrets.yml14
-rw-r--r--test/custom_rules/__init__.py1
-rw-r--r--test/custom_rules/example_com/ExampleComRule.py28
-rw-r--r--test/custom_rules/example_com/__init__.py1
-rw-r--r--test/custom_rules/example_inc/CustomAlwaysRunRule.py28
-rw-r--r--test/custom_rules/example_inc/__init__.py1
-rw-r--r--test/dependency-in-meta/bitbucket.yml10
-rw-r--r--test/dependency-in-meta/galaxy.yml5
-rw-r--r--test/dependency-in-meta/github.yml10
-rw-r--r--test/dependency-in-meta/gitlab.yml7
-rw-r--r--test/dependency-in-meta/webserver.yml6
-rw-r--r--test/directory with spaces/main.yml1
-rw-r--r--test/ematchtest.yml5
-rw-r--r--test/emptytags.yml7
-rw-r--r--test/fixtures/ansible-config-invalid.yml3
-rw-r--r--test/fixtures/ansible-config.yml4
-rw-r--r--test/fixtures/config-with-relative-path.yml5
-rw-r--r--test/fixtures/exclude-paths-with-expands.yml6
-rw-r--r--test/fixtures/exclude-paths.yml5
-rw-r--r--test/fixtures/parseable.yml4
-rw-r--r--test/fixtures/quiet.yml4
-rw-r--r--test/fixtures/rulesdir-defaults.yml6
-rw-r--r--test/fixtures/rulesdir.yml5
-rw-r--r--test/fixtures/show-abspath.yml4
-rw-r--r--test/fixtures/show-relpath.yml4
-rw-r--r--test/fixtures/skip-tags.yml5
-rw-r--r--test/fixtures/tags.yml5
-rw-r--r--test/fixtures/unknown-type.yml2
-rw-r--r--test/fixtures/verbosity.yml4
-rw-r--r--test/foo.txt1
-rw-r--r--test/include-import-role.yml17
-rw-r--r--test/include-import-tasks-in-role.yml3
-rw-r--r--test/include-in-block-inner.yml5
-rw-r--r--test/include-in-block.yml5
-rw-r--r--test/included-handlers.yml6
-rw-r--r--test/included-with-lint.yml4
-rw-r--r--test/includedoesnotexist.yml3
-rw-r--r--test/jinja2-when-failure.yml10
-rw-r--r--test/jinja2-when-success.yml8
-rw-r--r--test/local-content/README.md6
-rw-r--r--test/local-content/collections/ansible_collections/testns/testcoll/galaxy.yml3
-rw-r--r--test/local-content/collections/ansible_collections/testns/testcoll/plugins/filter/test_filter.py16
-rw-r--r--test/local-content/collections/ansible_collections/testns/testcoll/plugins/modules/test_module_2.py14
-rw-r--r--test/local-content/test-collection.yml10
-rw-r--r--test/local-content/test-roles-failed-complete/roles/role1/library/test_module_1_failed_complete.py14
-rw-r--r--test/local-content/test-roles-failed-complete/roles/role1/tasks/main.yml3
-rw-r--r--test/local-content/test-roles-failed-complete/roles/role2/tasks/main.yml11
-rw-r--r--test/local-content/test-roles-failed-complete/roles/role2/test_plugins/b_failed_complete.py16
-rw-r--r--test/local-content/test-roles-failed-complete/roles/role3/library/test_module_3_failed_complete.py14
-rw-r--r--test/local-content/test-roles-failed-complete/roles/role3/tasks/main.yml3
-rw-r--r--test/local-content/test-roles-failed-complete/test.yml5
-rw-r--r--test/local-content/test-roles-failed/roles/role1/library/test_module_1_failed.py14
-rw-r--r--test/local-content/test-roles-failed/roles/role1/tasks/main.yml3
-rw-r--r--test/local-content/test-roles-failed/roles/role2/tasks/main.yml11
-rw-r--r--test/local-content/test-roles-failed/roles/role2/test_plugins/b_failed.py16
-rw-r--r--test/local-content/test-roles-failed/roles/role3/library/test_module_3_failed.py14
-rw-r--r--test/local-content/test-roles-failed/roles/role3/tasks/main.yml3
-rw-r--r--test/local-content/test-roles-failed/test.yml7
-rw-r--r--test/local-content/test-roles-success/roles/role1/library/test_module_1_success.py14
-rw-r--r--test/local-content/test-roles-success/roles/role1/tasks/main.yml3
-rw-r--r--test/local-content/test-roles-success/roles/role2/tasks/main.yml11
-rw-r--r--test/local-content/test-roles-success/roles/role2/test_plugins/b_success.py16
-rw-r--r--test/local-content/test-roles-success/roles/role3/library/test_module_3_success.py14
-rw-r--r--test/local-content/test-roles-success/roles/role3/tasks/main.yml3
-rw-r--r--test/local-content/test-roles-success/test.yml7
-rw-r--r--test/multiline-brackets-do-not-match-test.yml22
-rw-r--r--test/multiline-bracketsmatchtest.yml22
-rw-r--r--test/nestedincludes.yml2
-rw-r--r--test/nomatchestest.yml9
-rw-r--r--test/norole.yml5
-rw-r--r--test/norole2.yml5
-rw-r--r--test/package-check-failure.yml14
-rw-r--r--test/package-check-success.yml15
-rw-r--r--test/playbook-import/playbook_imported.yml9
-rw-r--r--test/playbook-import/playbook_parent.yml3
-rw-r--r--test/role-with-handler/a-role/handlers/main.yml5
-rw-r--r--test/role-with-handler/main.yml4
-rw-r--r--test/role-with-included-imported-tasks/tasks/imported_tasks.yml2
-rw-r--r--test/role-with-included-imported-tasks/tasks/included_tasks.yml2
-rw-r--r--test/role-with-included-imported-tasks/tasks/main.yml6
-rw-r--r--test/roles/ansible-role-foo/tasks/main.yaml0
-rw-r--r--test/roles/invalid-name/tasks/main.yaml4
-rw-r--r--test/roles/invalid_due_to_meta/meta/main.yml8
-rw-r--r--test/roles/invalid_due_to_meta/tasks/main.yaml0
-rw-r--r--test/roles/test-role/molecule/default/include-import-role.yml6
-rw-r--r--test/roles/test-role/tasks/main.yml2
-rw-r--r--test/roles/valid-due-to-meta/meta/main.yml8
-rw-r--r--test/roles/valid-due-to-meta/tasks/debian/main.yml2
-rw-r--r--test/roles/valid-due-to-meta/tasks/main.yaml0
-rw-r--r--test/rules/EMatcherRule.py12
-rw-r--r--test/rules/UnsetVariableMatcherRule.py12
-rw-r--r--test/rules/__init__.py3
-rw-r--r--test/simpletask.yml3
-rw-r--r--test/skiptasks.yml70
-rw-r--r--test/task-has-name-failure.yml7
-rw-r--r--test/task-has-name-success.yml9
-rw-r--r--test/taskimports.yml9
-rw-r--r--test/taskincludes.yml9
-rw-r--r--test/taskincludes_2_4_style.yml9
-rw-r--r--test/test-role/tasks/main.yml2
-rw-r--r--test/test-role/tasks/world.yml1
-rw-r--r--test/test/always-run-success.yml1
-rw-r--r--test/testproject/roles/test-role/tasks/main.yml2
-rw-r--r--test/unicode.yml9
-rw-r--r--test/using-bare-variables-failure.yml108
-rw-r--r--test/using-bare-variables-success.yml200
-rw-r--r--test/varset.yml3
-rw-r--r--test/varunset.yml1
-rw-r--r--test/with-skip-tag-id.yml6
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'