summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--changelogs/fragments/83530-validate-modules-casing.yml2
-rw-r--r--test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/col/plugins/modules/option_name_casing.py45
-rw-r--r--test/integration/targets/ansible-test-sanity-validate-modules/expected.txt2
-rw-r--r--test/lib/ansible_test/_util/controller/sanity/validate-modules/validate_modules/main.py43
4 files changed, 92 insertions, 0 deletions
diff --git a/changelogs/fragments/83530-validate-modules-casing.yml b/changelogs/fragments/83530-validate-modules-casing.yml
new file mode 100644
index 0000000000..d00a344d2f
--- /dev/null
+++ b/changelogs/fragments/83530-validate-modules-casing.yml
@@ -0,0 +1,2 @@
+minor_changes:
+ - "validate-modules sanity test - reject option/aliases names that are identical up to casing but belong to different options (https://github.com/ansible/ansible/pull/83530)."
diff --git a/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/col/plugins/modules/option_name_casing.py b/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/col/plugins/modules/option_name_casing.py
new file mode 100644
index 0000000000..7ffd75bb7c
--- /dev/null
+++ b/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/col/plugins/modules/option_name_casing.py
@@ -0,0 +1,45 @@
+#!/usr/bin/python
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import annotations
+
+
+DOCUMENTATION = '''
+module: option_name_casing
+short_description: Option names equal up to casing
+description: Option names equal up to casing.
+author:
+ - Ansible Core Team
+options:
+ foo:
+ description: Foo
+ type: str
+ aliases:
+ - bar
+ - FOO # this one is ok
+ Foo:
+ description: Foo alias
+ type: str
+ Bar:
+ description: Bar alias
+ type: str
+ bam:
+ description: Bar alias 2
+ aliases:
+ - baR
+ type: str
+'''
+
+EXAMPLES = '''#'''
+RETURN = ''''''
+
+from ansible.module_utils.basic import AnsibleModule
+
+if __name__ == '__main__':
+ module = AnsibleModule(argument_spec=dict(
+ foo=dict(type='str', aliases=['bar', 'FOO']),
+ Foo=dict(type='str'),
+ Bar=dict(type='str'),
+ bam=dict(type='str', aliases=['baR'])
+ ))
+ module.exit_json()
diff --git a/test/integration/targets/ansible-test-sanity-validate-modules/expected.txt b/test/integration/targets/ansible-test-sanity-validate-modules/expected.txt
index 3ae113c5ef..b01ec459d3 100644
--- a/test/integration/targets/ansible-test-sanity-validate-modules/expected.txt
+++ b/test/integration/targets/ansible-test-sanity-validate-modules/expected.txt
@@ -12,6 +12,8 @@ plugins/modules/invalid_yaml_syntax.py:0:0: missing-documentation: No DOCUMENTAT
plugins/modules/invalid_yaml_syntax.py:7:15: documentation-syntax-error: DOCUMENTATION is not valid YAML
plugins/modules/invalid_yaml_syntax.py:11:15: invalid-examples: EXAMPLES is not valid YAML
plugins/modules/invalid_yaml_syntax.py:15:15: return-syntax-error: RETURN is not valid YAML
+plugins/modules/option_name_casing.py:0:0: option-equal-up-to-casing: Multiple options/aliases are equal up to casing: option 'Bar', alias 'baR' of option 'bam', alias 'bar' of option 'foo'
+plugins/modules/option_name_casing.py:0:0: option-equal-up-to-casing: Multiple options/aliases are equal up to casing: option 'Foo', option 'foo'
plugins/modules/semantic_markup.py:0:0: invalid-documentation-markup: DOCUMENTATION.options.a11.suboptions.b1.description.0: While parsing "V(C\(" at index 1: Unnecessarily escaped "(" @ data['options']['a11']['suboptions']['b1']['description'][0]. Got 'V(C\\(foo\\)).'
plugins/modules/semantic_markup.py:0:0: invalid-documentation-markup: DOCUMENTATION.options.a11.suboptions.b1.description.2: While parsing "P(foo.bar#baz)" at index 1: Plugin name "foo.bar" is not a FQCN @ data['options']['a11']['suboptions']['b1']['description'][2]. Got 'P(foo.bar#baz).'
plugins/modules/semantic_markup.py:0:0: invalid-documentation-markup: DOCUMENTATION.options.a11.suboptions.b1.description.3: While parsing "P(foo.bar.baz)" at index 1: Parameter "foo.bar.baz" is not of the form FQCN#type @ data['options']['a11']['suboptions']['b1']['description'][3]. Got 'P(foo.bar.baz).'
diff --git a/test/lib/ansible_test/_util/controller/sanity/validate-modules/validate_modules/main.py b/test/lib/ansible_test/_util/controller/sanity/validate-modules/validate_modules/main.py
index ddfb8ca72d..990076e5bc 100644
--- a/test/lib/ansible_test/_util/controller/sanity/validate-modules/validate_modules/main.py
+++ b/test/lib/ansible_test/_util/controller/sanity/validate-modules/validate_modules/main.py
@@ -838,6 +838,46 @@ class ModuleValidator(Validator):
msg='%s: %s' % (combined_path, error_message)
)
+ def _validate_option_docs(self, options, context=None):
+ if not isinstance(options, dict):
+ return
+ if context is None:
+ context = []
+
+ normalized_option_alias_names = dict()
+
+ def add_option_alias_name(name, option_name):
+ normalized_name = str(name).lower()
+ normalized_option_alias_names.setdefault(normalized_name, {}).setdefault(option_name, set()).add(name)
+
+ for option, data in options.items():
+ if 'suboptions' in data:
+ self._validate_option_docs(data.get('suboptions'), context + [option])
+ add_option_alias_name(option, option)
+ if 'aliases' in data and isinstance(data['aliases'], list):
+ for alias in data['aliases']:
+ add_option_alias_name(alias, option)
+
+ for normalized_name, options in normalized_option_alias_names.items():
+ if len(options) < 2:
+ continue
+
+ what = []
+ for option_name, names in sorted(options.items()):
+ if option_name in names:
+ what.append("option '%s'" % option_name)
+ else:
+ what.append("alias '%s' of option '%s'" % (sorted(names)[0], option_name))
+ msg = "Multiple options/aliases"
+ if context:
+ msg += " found in %s" % " -> ".join(context)
+ msg += " are equal up to casing: %s" % ", ".join(what)
+ self.reporter.error(
+ path=self.object_path,
+ code='option-equal-up-to-casing',
+ msg=msg,
+ )
+
def _validate_docs(self):
doc = None
# We have three ways of marking deprecated/removed files. Have to check each one
@@ -1015,6 +1055,9 @@ class ModuleValidator(Validator):
'invalid-documentation',
)
+ if doc:
+ self._validate_option_docs(doc.get('options'))
+
self._validate_all_semantic_markup(doc, returns)
if not self.collection: