diff --git a/changelogs/fragments/82353-ansible-sanity-examples.yml b/changelogs/fragments/82353-ansible-sanity-examples.yml new file mode 100644 index 00000000000..66f65bc629d --- /dev/null +++ b/changelogs/fragments/82353-ansible-sanity-examples.yml @@ -0,0 +1,4 @@ +--- +minor_changes: + - ansible-test - sanity test allows ``EXAMPLES`` to be multi-document YAML (https://github.com/ansible/ansible/issues/82353). + - ansible-test - document block name now included in error message for YAML parsing errors (https://github.com/ansible/ansible/issues/82353). diff --git a/test/integration/targets/ansible-test-sanity-yamllint/aliases b/test/integration/targets/ansible-test-sanity-yamllint/aliases new file mode 100644 index 00000000000..7741d444515 --- /dev/null +++ b/test/integration/targets/ansible-test-sanity-yamllint/aliases @@ -0,0 +1,4 @@ +shippable/posix/group3 # runs in the distro test containers +shippable/generic/group1 # runs in the default test container +context/controller +needs/target/collection diff --git a/test/integration/targets/ansible-test-sanity-yamllint/ansible_collections/ns/col/plugins/inventory/inventory1.py b/test/integration/targets/ansible-test-sanity-yamllint/ansible_collections/ns/col/plugins/inventory/inventory1.py new file mode 100644 index 00000000000..b358e539007 --- /dev/null +++ b/test/integration/targets/ansible-test-sanity-yamllint/ansible_collections/ns/col/plugins/inventory/inventory1.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- + +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import annotations + +DOCUMENTATION = r""" +--- + module: module2 + short_description: Hello test module + description: Hello test module. + options: + plugin: + required: true + description: name of the plugin (cache_host) + author: + - Ansible Core Team +""" + +EXAMPLES = r""" +--- + +first_doc: +some_key: + +--- + +second_doc: +some_key: + +""" + +RETURN = r""" +--- +--- +""" + +from ansible.plugins.inventory import BaseInventoryPlugin + + +class InventoryModule(BaseInventoryPlugin): + + NAME = 'inventory1' diff --git a/test/integration/targets/ansible-test-sanity-yamllint/ansible_collections/ns/col/plugins/modules/module1.py b/test/integration/targets/ansible-test-sanity-yamllint/ansible_collections/ns/col/plugins/modules/module1.py new file mode 100644 index 00000000000..14e7239adc7 --- /dev/null +++ b/test/integration/targets/ansible-test-sanity-yamllint/ansible_collections/ns/col/plugins/modules/module1.py @@ -0,0 +1,40 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import annotations + +DOCUMENTATION = r""" +module: module1 +short_description: Hello test module +description: Hello test module. +options: {} +author: + - Ansible Core Team +short_description: Duplicate short description +""" + +EXAMPLES = r""" +- minimal: +""" + +RETURN = r""" +invalid_yaml: + bad_indent: + usual_indent: +""" + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + module = AnsibleModule( + argument_spec={}, + ) + + module.exit_json() + + +if __name__ == "__main__": + main() diff --git a/test/integration/targets/ansible-test-sanity-yamllint/expected.txt b/test/integration/targets/ansible-test-sanity-yamllint/expected.txt new file mode 100644 index 00000000000..c9ba525baf6 --- /dev/null +++ b/test/integration/targets/ansible-test-sanity-yamllint/expected.txt @@ -0,0 +1,4 @@ +plugins/inventory/inventory1.py:34:1: multiple-yaml-documents: RETURN: expected a single document in the stream +plugins/modules/module1.py:15:1: key-duplicates: DOCUMENTATION: duplication of key "short_description" in mapping +plugins/modules/module1.py:25:3: error: RETURN: syntax error: expected , but found '' (syntax) +plugins/modules/module1.py:25:3: unparsable-with-libyaml: RETURN: while parsing a block mapping - did not find expected key diff --git a/test/integration/targets/ansible-test-sanity-yamllint/runme.sh b/test/integration/targets/ansible-test-sanity-yamllint/runme.sh new file mode 100755 index 00000000000..a333ccbf619 --- /dev/null +++ b/test/integration/targets/ansible-test-sanity-yamllint/runme.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +set -eu + +source ../collection/setup.sh + +set -x + +ansible-test sanity --test yamllint --color --lint --failure-ok "${@}" > actual.txt + +diff -u "${TEST_DIR}/expected.txt" actual.txt diff --git a/test/lib/ansible_test/_util/controller/sanity/yamllint/yamllinter.py b/test/lib/ansible_test/_util/controller/sanity/yamllint/yamllinter.py index ed1afcf3a5e..0c39fc7547e 100644 --- a/test/lib/ansible_test/_util/controller/sanity/yamllint/yamllinter.py +++ b/test/lib/ansible_test/_util/controller/sanity/yamllint/yamllinter.py @@ -126,19 +126,31 @@ class YamlChecker: yaml_data = yaml_data[1:] lineno += 1 - self.check_parsable(path, yaml_data, lineno) + multiple_docs_allowed = [ + "EXAMPLES", + ] + self.check_parsable(path, yaml_data, lineno, (key in multiple_docs_allowed), key) messages = list(linter.run(yaml_data, conf, path)) self.messages += [self.result_to_message(r, path, lineno - 1, key) for r in messages] - def check_parsable(self, path, contents, lineno=1): # type: (str, str, int) -> None + def check_parsable(self, path, contents, lineno=1, allow_multiple=False, prefix=""): # type: (str, str, int, bool) -> None """Check the given contents to verify they can be parsed as YAML.""" + prefix = f"{prefix}: " if prefix else "" try: - yaml.load(contents, Loader=TestLoader) + documents = len(list(yaml.load_all(contents, Loader=TestLoader))) + if documents > 1 and not allow_multiple: + self.messages += [{'code': 'multiple-yaml-documents', + 'message': f'{prefix}expected a single document in the stream', + 'path': path, + 'line': lineno, + 'column': 1, + 'level': 'error', + }] except MarkedYAMLError as ex: self.messages += [{'code': 'unparsable-with-libyaml', - 'message': '%s - %s' % (ex.args[0], ex.args[2]), + 'message': f'{prefix}{ex.args[0]} - {ex.args[2]}', 'path': path, 'line': ex.problem_mark.line + lineno, 'column': ex.problem_mark.column + 1,