summaryrefslogtreecommitdiffstats
path: root/test/units/module_utils/common
diff options
context:
space:
mode:
Diffstat (limited to 'test/units/module_utils/common')
-rw-r--r--test/units/module_utils/common/__init__.py0
-rw-r--r--test/units/module_utils/common/arg_spec/__init__.py0
-rw-r--r--test/units/module_utils/common/arg_spec/test_aliases.py132
-rw-r--r--test/units/module_utils/common/arg_spec/test_module_validate.py58
-rw-r--r--test/units/module_utils/common/arg_spec/test_sub_spec.py106
-rw-r--r--test/units/module_utils/common/arg_spec/test_validate_invalid.py134
-rw-r--r--test/units/module_utils/common/arg_spec/test_validate_valid.py335
-rw-r--r--test/units/module_utils/common/parameters/test_check_arguments.py38
-rw-r--r--test/units/module_utils/common/parameters/test_handle_aliases.py74
-rw-r--r--test/units/module_utils/common/parameters/test_list_deprecations.py44
-rw-r--r--test/units/module_utils/common/parameters/test_list_no_log_values.py228
-rw-r--r--test/units/module_utils/common/process/test_get_bin_path.py39
-rw-r--r--test/units/module_utils/common/test_collections.py175
-rw-r--r--test/units/module_utils/common/test_dict_transformations.py153
-rw-r--r--test/units/module_utils/common/test_locale.py42
-rw-r--r--test/units/module_utils/common/test_network.py79
-rw-r--r--test/units/module_utils/common/test_sys_info.py168
-rw-r--r--test/units/module_utils/common/test_utils.py46
-rw-r--r--test/units/module_utils/common/text/converters/test_container_to_bytes.py95
-rw-r--r--test/units/module_utils/common/text/converters/test_container_to_text.py78
-rw-r--r--test/units/module_utils/common/text/converters/test_json_encode_fallback.py68
-rw-r--r--test/units/module_utils/common/text/converters/test_jsonify.py27
-rw-r--r--test/units/module_utils/common/text/converters/test_to_str.py50
-rw-r--r--test/units/module_utils/common/text/formatters/test_bytes_to_human.py116
-rw-r--r--test/units/module_utils/common/text/formatters/test_human_to_bytes.py185
-rw-r--r--test/units/module_utils/common/text/formatters/test_lenient_lowercase.py68
-rw-r--r--test/units/module_utils/common/validation/test_check_missing_parameters.py35
-rw-r--r--test/units/module_utils/common/validation/test_check_mutually_exclusive.py57
-rw-r--r--test/units/module_utils/common/validation/test_check_required_arguments.py88
-rw-r--r--test/units/module_utils/common/validation/test_check_required_by.py98
-rw-r--r--test/units/module_utils/common/validation/test_check_required_if.py79
-rw-r--r--test/units/module_utils/common/validation/test_check_required_one_of.py47
-rw-r--r--test/units/module_utils/common/validation/test_check_required_together.py57
-rw-r--r--test/units/module_utils/common/validation/test_check_type_bits.py43
-rw-r--r--test/units/module_utils/common/validation/test_check_type_bool.py49
-rw-r--r--test/units/module_utils/common/validation/test_check_type_bytes.py50
-rw-r--r--test/units/module_utils/common/validation/test_check_type_dict.py34
-rw-r--r--test/units/module_utils/common/validation/test_check_type_float.py38
-rw-r--r--test/units/module_utils/common/validation/test_check_type_int.py34
-rw-r--r--test/units/module_utils/common/validation/test_check_type_jsonarg.py36
-rw-r--r--test/units/module_utils/common/validation/test_check_type_list.py32
-rw-r--r--test/units/module_utils/common/validation/test_check_type_path.py28
-rw-r--r--test/units/module_utils/common/validation/test_check_type_raw.py23
-rw-r--r--test/units/module_utils/common/validation/test_check_type_str.py33
-rw-r--r--test/units/module_utils/common/validation/test_count_terms.py40
-rw-r--r--test/units/module_utils/common/warnings/test_deprecate.py101
-rw-r--r--test/units/module_utils/common/warnings/test_warn.py61
47 files changed, 3601 insertions, 0 deletions
diff --git a/test/units/module_utils/common/__init__.py b/test/units/module_utils/common/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/units/module_utils/common/__init__.py
diff --git a/test/units/module_utils/common/arg_spec/__init__.py b/test/units/module_utils/common/arg_spec/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/units/module_utils/common/arg_spec/__init__.py
diff --git a/test/units/module_utils/common/arg_spec/test_aliases.py b/test/units/module_utils/common/arg_spec/test_aliases.py
new file mode 100644
index 0000000..7d30fb0
--- /dev/null
+++ b/test/units/module_utils/common/arg_spec/test_aliases.py
@@ -0,0 +1,132 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2021 Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+import pytest
+
+from ansible.module_utils.errors import AnsibleValidationError, AnsibleValidationErrorMultiple
+from ansible.module_utils.common.arg_spec import ArgumentSpecValidator, ValidationResult
+from ansible.module_utils.common.warnings import get_deprecation_messages, get_warning_messages
+
+# id, argument spec, parameters, expected parameters, deprecation, warning
+ALIAS_TEST_CASES = [
+ (
+ "alias",
+ {'path': {'aliases': ['dir', 'directory']}},
+ {'dir': '/tmp'},
+ {
+ 'dir': '/tmp',
+ 'path': '/tmp',
+ },
+ "",
+ "",
+ ),
+ (
+ "alias-duplicate-warning",
+ {'path': {'aliases': ['dir', 'directory']}},
+ {
+ 'dir': '/tmp',
+ 'directory': '/tmp',
+ },
+ {
+ 'dir': '/tmp',
+ 'directory': '/tmp',
+ 'path': '/tmp',
+ },
+ "",
+ {'alias': 'directory', 'option': 'path'},
+ ),
+ (
+ "deprecated-alias",
+ {
+ 'path': {
+ 'aliases': ['not_yo_path'],
+ 'deprecated_aliases': [
+ {
+ 'name': 'not_yo_path',
+ 'version': '1.7',
+ }
+ ]
+ }
+ },
+ {'not_yo_path': '/tmp'},
+ {
+ 'path': '/tmp',
+ 'not_yo_path': '/tmp',
+ },
+ {
+ 'version': '1.7',
+ 'date': None,
+ 'collection_name': None,
+ 'msg': "Alias 'not_yo_path' is deprecated. See the module docs for more information",
+ },
+ "",
+ )
+]
+
+
+# id, argument spec, parameters, expected parameters, error
+ALIAS_TEST_CASES_INVALID = [
+ (
+ "alias-invalid",
+ {'path': {'aliases': 'bad'}},
+ {},
+ {'path': None},
+ "internal error: aliases must be a list or tuple",
+ ),
+ (
+ # This isn't related to aliases, but it exists in the alias handling code
+ "default-and-required",
+ {'name': {'default': 'ray', 'required': True}},
+ {},
+ {'name': 'ray'},
+ "internal error: required and default are mutually exclusive for name",
+ ),
+]
+
+
+@pytest.mark.parametrize(
+ ('arg_spec', 'parameters', 'expected', 'deprecation', 'warning'),
+ ((i[1:]) for i in ALIAS_TEST_CASES),
+ ids=[i[0] for i in ALIAS_TEST_CASES]
+)
+def test_aliases(arg_spec, parameters, expected, deprecation, warning):
+ v = ArgumentSpecValidator(arg_spec)
+ result = v.validate(parameters)
+
+ assert isinstance(result, ValidationResult)
+ assert result.validated_parameters == expected
+ assert result.error_messages == []
+ assert result._aliases == {
+ alias: param
+ for param, value in arg_spec.items()
+ for alias in value.get("aliases", [])
+ }
+
+ if deprecation:
+ assert deprecation == result._deprecations[0]
+ else:
+ assert result._deprecations == []
+
+ if warning:
+ assert warning == result._warnings[0]
+ else:
+ assert result._warnings == []
+
+
+@pytest.mark.parametrize(
+ ('arg_spec', 'parameters', 'expected', 'error'),
+ ((i[1:]) for i in ALIAS_TEST_CASES_INVALID),
+ ids=[i[0] for i in ALIAS_TEST_CASES_INVALID]
+)
+def test_aliases_invalid(arg_spec, parameters, expected, error):
+ v = ArgumentSpecValidator(arg_spec)
+ result = v.validate(parameters)
+
+ assert isinstance(result, ValidationResult)
+ assert error in result.error_messages
+ assert isinstance(result.errors.errors[0], AnsibleValidationError)
+ assert isinstance(result.errors, AnsibleValidationErrorMultiple)
diff --git a/test/units/module_utils/common/arg_spec/test_module_validate.py b/test/units/module_utils/common/arg_spec/test_module_validate.py
new file mode 100644
index 0000000..2c2211c
--- /dev/null
+++ b/test/units/module_utils/common/arg_spec/test_module_validate.py
@@ -0,0 +1,58 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2021 Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+from ansible.module_utils.common import warnings
+
+from ansible.module_utils.common.arg_spec import ModuleArgumentSpecValidator, ValidationResult
+
+
+def test_module_validate():
+ arg_spec = {'name': {}}
+ parameters = {'name': 'larry'}
+ expected = {'name': 'larry'}
+
+ v = ModuleArgumentSpecValidator(arg_spec)
+ result = v.validate(parameters)
+
+ assert isinstance(result, ValidationResult)
+ assert result.error_messages == []
+ assert result._deprecations == []
+ assert result._warnings == []
+ assert result.validated_parameters == expected
+
+
+def test_module_alias_deprecations_warnings(monkeypatch):
+ monkeypatch.setattr(warnings, '_global_deprecations', [])
+
+ arg_spec = {
+ 'path': {
+ 'aliases': ['source', 'src', 'flamethrower'],
+ 'deprecated_aliases': [{'name': 'flamethrower', 'date': '2020-03-04'}],
+ },
+ }
+ parameters = {'flamethrower': '/tmp', 'source': '/tmp'}
+ expected = {
+ 'path': '/tmp',
+ 'flamethrower': '/tmp',
+ 'source': '/tmp',
+ }
+
+ v = ModuleArgumentSpecValidator(arg_spec)
+ result = v.validate(parameters)
+
+ assert result.validated_parameters == expected
+ assert result._deprecations == [
+ {
+ 'collection_name': None,
+ 'date': '2020-03-04',
+ 'msg': "Alias 'flamethrower' is deprecated. See the module docs for more information",
+ 'version': None,
+ }
+ ]
+ assert "Alias 'flamethrower' is deprecated" in warnings._global_deprecations[0]['msg']
+ assert result._warnings == [{'alias': 'flamethrower', 'option': 'path'}]
+ assert "Both option path and its alias flamethrower are set" in warnings._global_warnings[0]
diff --git a/test/units/module_utils/common/arg_spec/test_sub_spec.py b/test/units/module_utils/common/arg_spec/test_sub_spec.py
new file mode 100644
index 0000000..a6e7575
--- /dev/null
+++ b/test/units/module_utils/common/arg_spec/test_sub_spec.py
@@ -0,0 +1,106 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2021 Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+from ansible.module_utils.common.arg_spec import ArgumentSpecValidator, ValidationResult
+
+
+def test_sub_spec():
+ arg_spec = {
+ 'state': {},
+ 'user': {
+ 'type': 'dict',
+ 'options': {
+ 'first': {'no_log': True},
+ 'last': {},
+ 'age': {'type': 'int'},
+ }
+ }
+ }
+
+ parameters = {
+ 'state': 'present',
+ 'user': {
+ 'first': 'Rey',
+ 'last': 'Skywalker',
+ 'age': '19',
+ }
+ }
+
+ expected = {
+ 'state': 'present',
+ 'user': {
+ 'first': 'Rey',
+ 'last': 'Skywalker',
+ 'age': 19,
+ }
+ }
+
+ v = ArgumentSpecValidator(arg_spec)
+ result = v.validate(parameters)
+
+ assert isinstance(result, ValidationResult)
+ assert result.validated_parameters == expected
+ assert result.error_messages == []
+
+
+def test_nested_sub_spec():
+ arg_spec = {
+ 'type': {},
+ 'car': {
+ 'type': 'dict',
+ 'options': {
+ 'make': {},
+ 'model': {},
+ 'customizations': {
+ 'type': 'dict',
+ 'options': {
+ 'engine': {},
+ 'transmission': {},
+ 'color': {},
+ 'max_rpm': {'type': 'int'},
+ }
+ }
+ }
+ }
+ }
+
+ parameters = {
+ 'type': 'endurance',
+ 'car': {
+ 'make': 'Ford',
+ 'model': 'GT-40',
+ 'customizations': {
+ 'engine': '7.0 L',
+ 'transmission': '5-speed',
+ 'color': 'Ford blue',
+ 'max_rpm': '6000',
+ }
+
+ }
+ }
+
+ expected = {
+ 'type': 'endurance',
+ 'car': {
+ 'make': 'Ford',
+ 'model': 'GT-40',
+ 'customizations': {
+ 'engine': '7.0 L',
+ 'transmission': '5-speed',
+ 'color': 'Ford blue',
+ 'max_rpm': 6000,
+ }
+
+ }
+ }
+
+ v = ArgumentSpecValidator(arg_spec)
+ result = v.validate(parameters)
+
+ assert isinstance(result, ValidationResult)
+ assert result.validated_parameters == expected
+ assert result.error_messages == []
diff --git a/test/units/module_utils/common/arg_spec/test_validate_invalid.py b/test/units/module_utils/common/arg_spec/test_validate_invalid.py
new file mode 100644
index 0000000..7302e8a
--- /dev/null
+++ b/test/units/module_utils/common/arg_spec/test_validate_invalid.py
@@ -0,0 +1,134 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2021 Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+import pytest
+
+from ansible.module_utils.common.arg_spec import ArgumentSpecValidator, ValidationResult
+from ansible.module_utils.errors import AnsibleValidationErrorMultiple
+from ansible.module_utils.six import PY2
+
+
+# Each item is id, argument_spec, parameters, expected, unsupported parameters, error test string
+INVALID_SPECS = [
+ (
+ 'invalid-list',
+ {'packages': {'type': 'list'}},
+ {'packages': {'key': 'value'}},
+ {'packages': {'key': 'value'}},
+ set(),
+ "unable to convert to list: <class 'dict'> cannot be converted to a list",
+ ),
+ (
+ 'invalid-dict',
+ {'users': {'type': 'dict'}},
+ {'users': ['one', 'two']},
+ {'users': ['one', 'two']},
+ set(),
+ "unable to convert to dict: <class 'list'> cannot be converted to a dict",
+ ),
+ (
+ 'invalid-bool',
+ {'bool': {'type': 'bool'}},
+ {'bool': {'k': 'v'}},
+ {'bool': {'k': 'v'}},
+ set(),
+ "unable to convert to bool: <class 'dict'> cannot be converted to a bool",
+ ),
+ (
+ 'invalid-float',
+ {'float': {'type': 'float'}},
+ {'float': 'hello'},
+ {'float': 'hello'},
+ set(),
+ "unable to convert to float: <class 'str'> cannot be converted to a float",
+ ),
+ (
+ 'invalid-bytes',
+ {'bytes': {'type': 'bytes'}},
+ {'bytes': 'one'},
+ {'bytes': 'one'},
+ set(),
+ "unable to convert to bytes: <class 'str'> cannot be converted to a Byte value",
+ ),
+ (
+ 'invalid-bits',
+ {'bits': {'type': 'bits'}},
+ {'bits': 'one'},
+ {'bits': 'one'},
+ set(),
+ "unable to convert to bits: <class 'str'> cannot be converted to a Bit value",
+ ),
+ (
+ 'invalid-jsonargs',
+ {'some_json': {'type': 'jsonarg'}},
+ {'some_json': set()},
+ {'some_json': set()},
+ set(),
+ "unable to convert to jsonarg: <class 'set'> cannot be converted to a json string",
+ ),
+ (
+ 'invalid-parameter',
+ {'name': {}},
+ {
+ 'badparam': '',
+ 'another': '',
+ },
+ {
+ 'name': None,
+ 'badparam': '',
+ 'another': '',
+ },
+ set(('another', 'badparam')),
+ "another, badparam. Supported parameters include: name.",
+ ),
+ (
+ 'invalid-elements',
+ {'numbers': {'type': 'list', 'elements': 'int'}},
+ {'numbers': [55, 33, 34, {'key': 'value'}]},
+ {'numbers': [55, 33, 34]},
+ set(),
+ "Elements value for option 'numbers' is of type <class 'dict'> and we were unable to convert to int: <class 'dict'> cannot be converted to an int"
+ ),
+ (
+ 'required',
+ {'req': {'required': True}},
+ {},
+ {'req': None},
+ set(),
+ "missing required arguments: req"
+ ),
+ (
+ 'blank_values',
+ {'ch_param': {'elements': 'str', 'type': 'list', 'choices': ['a', 'b']}},
+ {'ch_param': ['']},
+ {'ch_param': ['']},
+ set(),
+ "value of ch_param must be one or more of"
+ )
+]
+
+
+@pytest.mark.parametrize(
+ ('arg_spec', 'parameters', 'expected', 'unsupported', 'error'),
+ (i[1:] for i in INVALID_SPECS),
+ ids=[i[0] for i in INVALID_SPECS]
+)
+def test_invalid_spec(arg_spec, parameters, expected, unsupported, error):
+ v = ArgumentSpecValidator(arg_spec)
+ result = v.validate(parameters)
+
+ with pytest.raises(AnsibleValidationErrorMultiple) as exc_info:
+ raise result.errors
+
+ if PY2:
+ error = error.replace('class', 'type')
+
+ assert isinstance(result, ValidationResult)
+ assert error in exc_info.value.msg
+ assert error in result.error_messages[0]
+ assert result.unsupported_parameters == unsupported
+ assert result.validated_parameters == expected
diff --git a/test/units/module_utils/common/arg_spec/test_validate_valid.py b/test/units/module_utils/common/arg_spec/test_validate_valid.py
new file mode 100644
index 0000000..7e41127
--- /dev/null
+++ b/test/units/module_utils/common/arg_spec/test_validate_valid.py
@@ -0,0 +1,335 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2021 Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+import pytest
+
+from ansible.module_utils.common.arg_spec import ArgumentSpecValidator, ValidationResult
+
+# Each item is id, argument_spec, parameters, expected, valid parameter names
+VALID_SPECS = [
+ (
+ 'str-no-type-specified',
+ {'name': {}},
+ {'name': 'rey'},
+ {'name': 'rey'},
+ set(('name',)),
+ ),
+ (
+ 'str',
+ {'name': {'type': 'str'}},
+ {'name': 'rey'},
+ {'name': 'rey'},
+ set(('name',)),
+ ),
+ (
+ 'str-convert',
+ {'name': {'type': 'str'}},
+ {'name': 5},
+ {'name': '5'},
+ set(('name',)),
+ ),
+ (
+ 'list',
+ {'packages': {'type': 'list'}},
+ {'packages': ['vim', 'python']},
+ {'packages': ['vim', 'python']},
+ set(('packages',)),
+ ),
+ (
+ 'list-comma-string',
+ {'packages': {'type': 'list'}},
+ {'packages': 'vim,python'},
+ {'packages': ['vim', 'python']},
+ set(('packages',)),
+ ),
+ (
+ 'list-comma-string-space',
+ {'packages': {'type': 'list'}},
+ {'packages': 'vim, python'},
+ {'packages': ['vim', ' python']},
+ set(('packages',)),
+ ),
+ (
+ 'dict',
+ {'user': {'type': 'dict'}},
+ {
+ 'user':
+ {
+ 'first': 'rey',
+ 'last': 'skywalker',
+ }
+ },
+ {
+ 'user':
+ {
+ 'first': 'rey',
+ 'last': 'skywalker',
+ }
+ },
+ set(('user',)),
+ ),
+ (
+ 'dict-k=v',
+ {'user': {'type': 'dict'}},
+ {'user': 'first=rey,last=skywalker'},
+ {
+ 'user':
+ {
+ 'first': 'rey',
+ 'last': 'skywalker',
+ }
+ },
+ set(('user',)),
+ ),
+ (
+ 'dict-k=v-spaces',
+ {'user': {'type': 'dict'}},
+ {'user': 'first=rey, last=skywalker'},
+ {
+ 'user':
+ {
+ 'first': 'rey',
+ 'last': 'skywalker',
+ }
+ },
+ set(('user',)),
+ ),
+ (
+ 'bool',
+ {
+ 'enabled': {'type': 'bool'},
+ 'disabled': {'type': 'bool'},
+ },
+ {
+ 'enabled': True,
+ 'disabled': False,
+ },
+ {
+ 'enabled': True,
+ 'disabled': False,
+ },
+ set(('enabled', 'disabled')),
+ ),
+ (
+ 'bool-ints',
+ {
+ 'enabled': {'type': 'bool'},
+ 'disabled': {'type': 'bool'},
+ },
+ {
+ 'enabled': 1,
+ 'disabled': 0,
+ },
+ {
+ 'enabled': True,
+ 'disabled': False,
+ },
+ set(('enabled', 'disabled')),
+ ),
+ (
+ 'bool-true-false',
+ {
+ 'enabled': {'type': 'bool'},
+ 'disabled': {'type': 'bool'},
+ },
+ {
+ 'enabled': 'true',
+ 'disabled': 'false',
+ },
+ {
+ 'enabled': True,
+ 'disabled': False,
+ },
+ set(('enabled', 'disabled')),
+ ),
+ (
+ 'bool-yes-no',
+ {
+ 'enabled': {'type': 'bool'},
+ 'disabled': {'type': 'bool'},
+ },
+ {
+ 'enabled': 'yes',
+ 'disabled': 'no',
+ },
+ {
+ 'enabled': True,
+ 'disabled': False,
+ },
+ set(('enabled', 'disabled')),
+ ),
+ (
+ 'bool-y-n',
+ {
+ 'enabled': {'type': 'bool'},
+ 'disabled': {'type': 'bool'},
+ },
+ {
+ 'enabled': 'y',
+ 'disabled': 'n',
+ },
+ {
+ 'enabled': True,
+ 'disabled': False,
+ },
+ set(('enabled', 'disabled')),
+ ),
+ (
+ 'bool-on-off',
+ {
+ 'enabled': {'type': 'bool'},
+ 'disabled': {'type': 'bool'},
+ },
+ {
+ 'enabled': 'on',
+ 'disabled': 'off',
+ },
+ {
+ 'enabled': True,
+ 'disabled': False,
+ },
+ set(('enabled', 'disabled')),
+ ),
+ (
+ 'bool-1-0',
+ {
+ 'enabled': {'type': 'bool'},
+ 'disabled': {'type': 'bool'},
+ },
+ {
+ 'enabled': '1',
+ 'disabled': '0',
+ },
+ {
+ 'enabled': True,
+ 'disabled': False,
+ },
+ set(('enabled', 'disabled')),
+ ),
+ (
+ 'bool-float',
+ {
+ 'enabled': {'type': 'bool'},
+ 'disabled': {'type': 'bool'},
+ },
+ {
+ 'enabled': 1.0,
+ 'disabled': 0.0,
+ },
+ {
+ 'enabled': True,
+ 'disabled': False,
+ },
+ set(('enabled', 'disabled')),
+ ),
+ (
+ 'float',
+ {'digit': {'type': 'float'}},
+ {'digit': 3.14159},
+ {'digit': 3.14159},
+ set(('digit',)),
+ ),
+ (
+ 'float-str',
+ {'digit': {'type': 'float'}},
+ {'digit': '3.14159'},
+ {'digit': 3.14159},
+ set(('digit',)),
+ ),
+ (
+ 'path',
+ {'path': {'type': 'path'}},
+ {'path': '~/bin'},
+ {'path': '/home/ansible/bin'},
+ set(('path',)),
+ ),
+ (
+ 'raw',
+ {'raw': {'type': 'raw'}},
+ {'raw': 0x644},
+ {'raw': 0x644},
+ set(('raw',)),
+ ),
+ (
+ 'bytes',
+ {'bytes': {'type': 'bytes'}},
+ {'bytes': '2K'},
+ {'bytes': 2048},
+ set(('bytes',)),
+ ),
+ (
+ 'bits',
+ {'bits': {'type': 'bits'}},
+ {'bits': '1Mb'},
+ {'bits': 1048576},
+ set(('bits',)),
+ ),
+ (
+ 'jsonarg',
+ {'some_json': {'type': 'jsonarg'}},
+ {'some_json': '{"users": {"bob": {"role": "accountant"}}}'},
+ {'some_json': '{"users": {"bob": {"role": "accountant"}}}'},
+ set(('some_json',)),
+ ),
+ (
+ 'jsonarg-list',
+ {'some_json': {'type': 'jsonarg'}},
+ {'some_json': ['one', 'two']},
+ {'some_json': '["one", "two"]'},
+ set(('some_json',)),
+ ),
+ (
+ 'jsonarg-dict',
+ {'some_json': {'type': 'jsonarg'}},
+ {'some_json': {"users": {"bob": {"role": "accountant"}}}},
+ {'some_json': '{"users": {"bob": {"role": "accountant"}}}'},
+ set(('some_json',)),
+ ),
+ (
+ 'defaults',
+ {'param': {'default': 'DEFAULT'}},
+ {},
+ {'param': 'DEFAULT'},
+ set(('param',)),
+ ),
+ (
+ 'elements',
+ {'numbers': {'type': 'list', 'elements': 'int'}},
+ {'numbers': [55, 33, 34, '22']},
+ {'numbers': [55, 33, 34, 22]},
+ set(('numbers',)),
+ ),
+ (
+ 'aliases',
+ {'src': {'aliases': ['path', 'source']}},
+ {'src': '/tmp'},
+ {'src': '/tmp'},
+ set(('src (path, source)',)),
+ )
+]
+
+
+@pytest.mark.parametrize(
+ ('arg_spec', 'parameters', 'expected', 'valid_params'),
+ (i[1:] for i in VALID_SPECS),
+ ids=[i[0] for i in VALID_SPECS]
+)
+def test_valid_spec(arg_spec, parameters, expected, valid_params, mocker):
+ mocker.patch('ansible.module_utils.common.validation.os.path.expanduser', return_value='/home/ansible/bin')
+ mocker.patch('ansible.module_utils.common.validation.os.path.expandvars', return_value='/home/ansible/bin')
+
+ v = ArgumentSpecValidator(arg_spec)
+ result = v.validate(parameters)
+
+ assert isinstance(result, ValidationResult)
+ assert result.validated_parameters == expected
+ assert result.unsupported_parameters == set()
+ assert result.error_messages == []
+ assert v._valid_parameter_names == valid_params
+
+ # Again to check caching
+ assert v._valid_parameter_names == valid_params
diff --git a/test/units/module_utils/common/parameters/test_check_arguments.py b/test/units/module_utils/common/parameters/test_check_arguments.py
new file mode 100644
index 0000000..5311217
--- /dev/null
+++ b/test/units/module_utils/common/parameters/test_check_arguments.py
@@ -0,0 +1,38 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020 Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+import pytest
+
+from ansible.module_utils.common.parameters import _get_unsupported_parameters
+
+
+@pytest.fixture
+def argument_spec():
+ return {
+ 'state': {'aliases': ['status']},
+ 'enabled': {},
+ }
+
+
+@pytest.mark.parametrize(
+ ('module_parameters', 'legal_inputs', 'expected'),
+ (
+ ({'fish': 'food'}, ['state', 'enabled'], set(['fish'])),
+ ({'state': 'enabled', 'path': '/var/lib/path'}, None, set(['path'])),
+ ({'state': 'enabled', 'path': '/var/lib/path'}, ['state', 'path'], set()),
+ ({'state': 'enabled', 'path': '/var/lib/path'}, ['state'], set(['path'])),
+ ({}, None, set()),
+ ({'state': 'enabled'}, None, set()),
+ ({'status': 'enabled', 'enabled': True, 'path': '/var/lib/path'}, None, set(['path'])),
+ ({'status': 'enabled', 'enabled': True}, None, set()),
+ )
+)
+def test_check_arguments(argument_spec, module_parameters, legal_inputs, expected, mocker):
+ result = _get_unsupported_parameters(argument_spec, module_parameters, legal_inputs)
+
+ assert result == expected
diff --git a/test/units/module_utils/common/parameters/test_handle_aliases.py b/test/units/module_utils/common/parameters/test_handle_aliases.py
new file mode 100644
index 0000000..e20a888
--- /dev/null
+++ b/test/units/module_utils/common/parameters/test_handle_aliases.py
@@ -0,0 +1,74 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2019 Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+import pytest
+
+from ansible.module_utils.common.parameters import _handle_aliases
+from ansible.module_utils._text import to_native
+
+
+def test_handle_aliases_no_aliases():
+ argument_spec = {
+ 'name': {'type': 'str'},
+ }
+
+ params = {
+ 'name': 'foo',
+ 'path': 'bar'
+ }
+
+ expected = {}
+ result = _handle_aliases(argument_spec, params)
+
+ assert expected == result
+
+
+def test_handle_aliases_basic():
+ argument_spec = {
+ 'name': {'type': 'str', 'aliases': ['surname', 'nick']},
+ }
+
+ params = {
+ 'name': 'foo',
+ 'path': 'bar',
+ 'surname': 'foo',
+ 'nick': 'foo',
+ }
+
+ expected = {'surname': 'name', 'nick': 'name'}
+ result = _handle_aliases(argument_spec, params)
+
+ assert expected == result
+
+
+def test_handle_aliases_value_error():
+ argument_spec = {
+ 'name': {'type': 'str', 'aliases': ['surname', 'nick'], 'default': 'bob', 'required': True},
+ }
+
+ params = {
+ 'name': 'foo',
+ }
+
+ with pytest.raises(ValueError) as ve:
+ _handle_aliases(argument_spec, params)
+ assert 'internal error: aliases must be a list or tuple' == to_native(ve.error)
+
+
+def test_handle_aliases_type_error():
+ argument_spec = {
+ 'name': {'type': 'str', 'aliases': 'surname'},
+ }
+
+ params = {
+ 'name': 'foo',
+ }
+
+ with pytest.raises(TypeError) as te:
+ _handle_aliases(argument_spec, params)
+ assert 'internal error: required and default are mutually exclusive' in to_native(te.error)
diff --git a/test/units/module_utils/common/parameters/test_list_deprecations.py b/test/units/module_utils/common/parameters/test_list_deprecations.py
new file mode 100644
index 0000000..6f0bb71
--- /dev/null
+++ b/test/units/module_utils/common/parameters/test_list_deprecations.py
@@ -0,0 +1,44 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2019 Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+import pytest
+
+from ansible.module_utils.common.parameters import _list_deprecations
+
+
+@pytest.fixture
+def params():
+ return {
+ 'name': 'bob',
+ 'dest': '/etc/hosts',
+ 'state': 'present',
+ 'value': 5,
+ }
+
+
+def test_list_deprecations():
+ argument_spec = {
+ 'old': {'type': 'str', 'removed_in_version': '2.5'},
+ 'foo': {'type': 'dict', 'options': {'old': {'type': 'str', 'removed_in_version': 1.0}}},
+ 'bar': {'type': 'list', 'elements': 'dict', 'options': {'old': {'type': 'str', 'removed_in_version': '2.10'}}},
+ }
+
+ params = {
+ 'name': 'rod',
+ 'old': 'option',
+ 'foo': {'old': 'value'},
+ 'bar': [{'old': 'value'}, {}],
+ }
+ result = _list_deprecations(argument_spec, params)
+ assert len(result) == 3
+ result.sort(key=lambda entry: entry['msg'])
+ assert result[0]['msg'] == """Param 'bar["old"]' is deprecated. See the module docs for more information"""
+ assert result[0]['version'] == '2.10'
+ assert result[1]['msg'] == """Param 'foo["old"]' is deprecated. See the module docs for more information"""
+ assert result[1]['version'] == 1.0
+ assert result[2]['msg'] == "Param 'old' is deprecated. See the module docs for more information"
+ assert result[2]['version'] == '2.5'
diff --git a/test/units/module_utils/common/parameters/test_list_no_log_values.py b/test/units/module_utils/common/parameters/test_list_no_log_values.py
new file mode 100644
index 0000000..ac0e735
--- /dev/null
+++ b/test/units/module_utils/common/parameters/test_list_no_log_values.py
@@ -0,0 +1,228 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2019 Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+import pytest
+
+from ansible.module_utils.common.parameters import _list_no_log_values
+
+
+@pytest.fixture
+def argument_spec():
+ # Allow extra specs to be passed to the fixture, which will be added to the output
+ def _argument_spec(extra_opts=None):
+ spec = {
+ 'secret': {'type': 'str', 'no_log': True},
+ 'other_secret': {'type': 'str', 'no_log': True},
+ 'state': {'type': 'str'},
+ 'value': {'type': 'int'},
+ }
+
+ if extra_opts:
+ spec.update(extra_opts)
+
+ return spec
+
+ return _argument_spec
+
+
+@pytest.fixture
+def module_parameters():
+ # Allow extra parameters to be passed to the fixture, which will be added to the output
+ def _module_parameters(extra_params=None):
+ params = {
+ 'secret': 'under',
+ 'other_secret': 'makeshift',
+ 'state': 'present',
+ 'value': 5,
+ }
+
+ if extra_params:
+ params.update(extra_params)
+
+ return params
+
+ return _module_parameters
+
+
+def test_list_no_log_values_no_secrets(module_parameters):
+ argument_spec = {
+ 'other_secret': {'type': 'str', 'no_log': False},
+ 'state': {'type': 'str'},
+ 'value': {'type': 'int'},
+ }
+ expected = set()
+ assert expected == _list_no_log_values(argument_spec, module_parameters)
+
+
+def test_list_no_log_values(argument_spec, module_parameters):
+ expected = set(('under', 'makeshift'))
+ assert expected == _list_no_log_values(argument_spec(), module_parameters())
+
+
+@pytest.mark.parametrize('extra_params', [
+ {'subopt1': 1},
+ {'subopt1': 3.14159},
+ {'subopt1': ['one', 'two']},
+ {'subopt1': ('one', 'two')},
+])
+def test_list_no_log_values_invalid_suboptions(argument_spec, module_parameters, extra_params):
+ extra_opts = {
+ 'subopt1': {
+ 'type': 'dict',
+ 'options': {
+ 'sub_1_1': {},
+ }
+ }
+ }
+
+ with pytest.raises(TypeError, match=r"(Value '.*?' in the sub parameter field '.*?' must by a dict, not '.*?')"
+ r"|(dictionary requested, could not parse JSON or key=value)"):
+ _list_no_log_values(argument_spec(extra_opts), module_parameters(extra_params))
+
+
+def test_list_no_log_values_suboptions(argument_spec, module_parameters):
+ extra_opts = {
+ 'subopt1': {
+ 'type': 'dict',
+ 'options': {
+ 'sub_1_1': {'no_log': True},
+ 'sub_1_2': {'type': 'list'},
+ }
+ }
+ }
+
+ extra_params = {
+ 'subopt1': {
+ 'sub_1_1': 'bagel',
+ 'sub_1_2': ['pebble'],
+ }
+ }
+
+ expected = set(('under', 'makeshift', 'bagel'))
+ assert expected == _list_no_log_values(argument_spec(extra_opts), module_parameters(extra_params))
+
+
+def test_list_no_log_values_sub_suboptions(argument_spec, module_parameters):
+ extra_opts = {
+ 'sub_level_1': {
+ 'type': 'dict',
+ 'options': {
+ 'l1_1': {'no_log': True},
+ 'l1_2': {},
+ 'l1_3': {
+ 'type': 'dict',
+ 'options': {
+ 'l2_1': {'no_log': True},
+ 'l2_2': {},
+ }
+ }
+ }
+ }
+ }
+
+ extra_params = {
+ 'sub_level_1': {
+ 'l1_1': 'saucy',
+ 'l1_2': 'napped',
+ 'l1_3': {
+ 'l2_1': 'corporate',
+ 'l2_2': 'tinsmith',
+ }
+ }
+ }
+
+ expected = set(('under', 'makeshift', 'saucy', 'corporate'))
+ assert expected == _list_no_log_values(argument_spec(extra_opts), module_parameters(extra_params))
+
+
+def test_list_no_log_values_suboptions_list(argument_spec, module_parameters):
+ extra_opts = {
+ 'subopt1': {
+ 'type': 'list',
+ 'elements': 'dict',
+ 'options': {
+ 'sub_1_1': {'no_log': True},
+ 'sub_1_2': {},
+ }
+ }
+ }
+
+ extra_params = {
+ 'subopt1': [
+ {
+ 'sub_1_1': ['playroom', 'luxury'],
+ 'sub_1_2': 'deuce',
+ },
+ {
+ 'sub_1_2': ['squishier', 'finished'],
+ }
+ ]
+ }
+
+ expected = set(('under', 'makeshift', 'playroom', 'luxury'))
+ assert expected == _list_no_log_values(argument_spec(extra_opts), module_parameters(extra_params))
+
+
+def test_list_no_log_values_sub_suboptions_list(argument_spec, module_parameters):
+ extra_opts = {
+ 'subopt1': {
+ 'type': 'list',
+ 'elements': 'dict',
+ 'options': {
+ 'sub_1_1': {'no_log': True},
+ 'sub_1_2': {},
+ 'subopt2': {
+ 'type': 'list',
+ 'elements': 'dict',
+ 'options': {
+ 'sub_2_1': {'no_log': True, 'type': 'list'},
+ 'sub_2_2': {},
+ }
+ }
+ }
+ }
+ }
+
+ extra_params = {
+ 'subopt1': {
+ 'sub_1_1': ['playroom', 'luxury'],
+ 'sub_1_2': 'deuce',
+ 'subopt2': [
+ {
+ 'sub_2_1': ['basis', 'gave'],
+ 'sub_2_2': 'liquid',
+ },
+ {
+ 'sub_2_1': ['composure', 'thumping']
+ },
+ ]
+ }
+ }
+
+ expected = set(('under', 'makeshift', 'playroom', 'luxury', 'basis', 'gave', 'composure', 'thumping'))
+ assert expected == _list_no_log_values(argument_spec(extra_opts), module_parameters(extra_params))
+
+
+@pytest.mark.parametrize('extra_params, expected', (
+ ({'subopt_dict': 'dict_subopt1=rekindle-scandal,dict_subopt2=subgroupavenge'}, ('rekindle-scandal',)),
+ ({'subopt_dict': 'dict_subopt1=aversion-mutable dict_subopt2=subgroupavenge'}, ('aversion-mutable',)),
+ ({'subopt_dict': ['dict_subopt1=blip-marine,dict_subopt2=subgroupavenge', 'dict_subopt1=tipping,dict_subopt2=hardening']}, ('blip-marine', 'tipping')),
+))
+def test_string_suboptions_as_string(argument_spec, module_parameters, extra_params, expected):
+ extra_opts = {
+ 'subopt_dict': {
+ 'type': 'dict',
+ 'options': {
+ 'dict_subopt1': {'no_log': True},
+ 'dict_subopt2': {},
+ },
+ },
+ }
+
+ result = set(('under', 'makeshift'))
+ result.update(expected)
+ assert result == _list_no_log_values(argument_spec(extra_opts), module_parameters(extra_params))
diff --git a/test/units/module_utils/common/process/test_get_bin_path.py b/test/units/module_utils/common/process/test_get_bin_path.py
new file mode 100644
index 0000000..7c0bd0a
--- /dev/null
+++ b/test/units/module_utils/common/process/test_get_bin_path.py
@@ -0,0 +1,39 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020 Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+import pytest
+
+from ansible.module_utils.common.process import get_bin_path
+
+
+def test_get_bin_path(mocker):
+ path = '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
+ mocker.patch.dict('os.environ', {'PATH': path})
+ mocker.patch('os.pathsep', ':')
+
+ mocker.patch('os.path.isdir', return_value=False)
+ mocker.patch('ansible.module_utils.common.process.is_executable', return_value=True)
+
+ # pytest-mock 2.0.0 will throw when os.path.exists is messed with
+ # and then another method is patched afterwards. Likely
+ # something in the pytest-mock chain uses os.path.exists internally, and
+ # since pytest-mock prohibits context-specific patching, there's not a
+ # good solution. For now, just patch os.path.exists last.
+ mocker.patch('os.path.exists', side_effect=[False, True])
+
+ assert '/usr/local/bin/notacommand' == get_bin_path('notacommand')
+
+
+def test_get_path_path_raise_valueerror(mocker):
+ mocker.patch.dict('os.environ', {'PATH': ''})
+
+ mocker.patch('os.path.exists', return_value=False)
+ mocker.patch('os.path.isdir', return_value=False)
+ mocker.patch('ansible.module_utils.common.process.is_executable', return_value=True)
+
+ with pytest.raises(ValueError, match='Failed to find required executable "notacommand"'):
+ get_bin_path('notacommand')
diff --git a/test/units/module_utils/common/test_collections.py b/test/units/module_utils/common/test_collections.py
new file mode 100644
index 0000000..95b2a40
--- /dev/null
+++ b/test/units/module_utils/common/test_collections.py
@@ -0,0 +1,175 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2018–2019, Sviatoslav Sydorenko <webknjaz@redhat.com>
+# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
+"""Test low-level utility functions from ``module_utils.common.collections``."""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+import pytest
+
+from ansible.module_utils.six import Iterator
+from ansible.module_utils.common._collections_compat import Sequence
+from ansible.module_utils.common.collections import ImmutableDict, is_iterable, is_sequence
+
+
+class SeqStub:
+ """Stub emulating a sequence type.
+
+ >>> from collections.abc import Sequence
+ >>> assert issubclass(SeqStub, Sequence)
+ >>> assert isinstance(SeqStub(), Sequence)
+ """
+
+
+Sequence.register(SeqStub)
+
+
+class IteratorStub(Iterator):
+ def __next__(self):
+ raise StopIteration
+
+
+class IterableStub:
+ def __iter__(self):
+ return IteratorStub()
+
+
+class FakeAnsibleVaultEncryptedUnicode(Sequence):
+ __ENCRYPTED__ = True
+
+ def __init__(self, data):
+ self.data = data
+
+ def __getitem__(self, index):
+ return self.data[index]
+
+ def __len__(self):
+ return len(self.data)
+
+
+TEST_STRINGS = u'he', u'Україна', u'Česká republika'
+TEST_STRINGS = TEST_STRINGS + tuple(s.encode('utf-8') for s in TEST_STRINGS) + (FakeAnsibleVaultEncryptedUnicode(u'foo'),)
+
+TEST_ITEMS_NON_SEQUENCES = (
+ {}, object(), frozenset(),
+ 4, 0.,
+) + TEST_STRINGS
+
+TEST_ITEMS_SEQUENCES = (
+ [], (),
+ SeqStub(),
+)
+TEST_ITEMS_SEQUENCES = TEST_ITEMS_SEQUENCES + (
+ # Iterable effectively containing nested random data:
+ TEST_ITEMS_NON_SEQUENCES,
+)
+
+
+@pytest.mark.parametrize('sequence_input', TEST_ITEMS_SEQUENCES)
+def test_sequence_positive(sequence_input):
+ """Test that non-string item sequences are identified correctly."""
+ assert is_sequence(sequence_input)
+ assert is_sequence(sequence_input, include_strings=False)
+
+
+@pytest.mark.parametrize('non_sequence_input', TEST_ITEMS_NON_SEQUENCES)
+def test_sequence_negative(non_sequence_input):
+ """Test that non-sequences are identified correctly."""
+ assert not is_sequence(non_sequence_input)
+
+
+@pytest.mark.parametrize('string_input', TEST_STRINGS)
+def test_sequence_string_types_with_strings(string_input):
+ """Test that ``is_sequence`` can separate string and non-string."""
+ assert is_sequence(string_input, include_strings=True)
+
+
+@pytest.mark.parametrize('string_input', TEST_STRINGS)
+def test_sequence_string_types_without_strings(string_input):
+ """Test that ``is_sequence`` can separate string and non-string."""
+ assert not is_sequence(string_input, include_strings=False)
+
+
+@pytest.mark.parametrize(
+ 'seq',
+ ([], (), {}, set(), frozenset(), IterableStub()),
+)
+def test_iterable_positive(seq):
+ assert is_iterable(seq)
+
+
+@pytest.mark.parametrize(
+ 'seq', (IteratorStub(), object(), 5, 9.)
+)
+def test_iterable_negative(seq):
+ assert not is_iterable(seq)
+
+
+@pytest.mark.parametrize('string_input', TEST_STRINGS)
+def test_iterable_including_strings(string_input):
+ assert is_iterable(string_input, include_strings=True)
+
+
+@pytest.mark.parametrize('string_input', TEST_STRINGS)
+def test_iterable_excluding_strings(string_input):
+ assert not is_iterable(string_input, include_strings=False)
+
+
+class TestImmutableDict:
+ def test_scalar(self):
+ imdict = ImmutableDict({1: 2})
+ assert imdict[1] == 2
+
+ def test_string(self):
+ imdict = ImmutableDict({u'café': u'くらとみ'})
+ assert imdict[u'café'] == u'くらとみ'
+
+ def test_container(self):
+ imdict = ImmutableDict({(1, 2): ['1', '2']})
+ assert imdict[(1, 2)] == ['1', '2']
+
+ def test_from_tuples(self):
+ imdict = ImmutableDict((('a', 1), ('b', 2)))
+ assert frozenset(imdict.items()) == frozenset((('a', 1), ('b', 2)))
+
+ def test_from_kwargs(self):
+ imdict = ImmutableDict(a=1, b=2)
+ assert frozenset(imdict.items()) == frozenset((('a', 1), ('b', 2)))
+
+ def test_immutable(self):
+ imdict = ImmutableDict({1: 2})
+
+ expected_reason = r"^'ImmutableDict' object does not support item assignment$"
+
+ with pytest.raises(TypeError, match=expected_reason):
+ imdict[1] = 3
+
+ with pytest.raises(TypeError, match=expected_reason):
+ imdict[5] = 3
+
+ def test_hashable(self):
+ # ImmutableDict is hashable when all of its values are hashable
+ imdict = ImmutableDict({u'café': u'くらとみ'})
+ assert hash(imdict)
+
+ def test_nonhashable(self):
+ # ImmutableDict is unhashable when one of its values is unhashable
+ imdict = ImmutableDict({u'café': u'くらとみ', 1: [1, 2]})
+
+ expected_reason = r"^unhashable type: 'list'$"
+
+ with pytest.raises(TypeError, match=expected_reason):
+ hash(imdict)
+
+ def test_len(self):
+ imdict = ImmutableDict({1: 2, 'a': 'b'})
+ assert len(imdict) == 2
+
+ def test_repr(self):
+ initial_data = {1: 2, 'a': 'b'}
+ initial_data_repr = repr(initial_data)
+ imdict = ImmutableDict(initial_data)
+ actual_repr = repr(imdict)
+ expected_repr = "ImmutableDict({0})".format(initial_data_repr)
+ assert actual_repr == expected_repr
diff --git a/test/units/module_utils/common/test_dict_transformations.py b/test/units/module_utils/common/test_dict_transformations.py
new file mode 100644
index 0000000..ba55299
--- /dev/null
+++ b/test/units/module_utils/common/test_dict_transformations.py
@@ -0,0 +1,153 @@
+# -*- coding: utf-8 -*-
+# Copyright: (c) 2017, Will Thames <will.thames@xvt.com.au>
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+import pytest
+
+from ansible.module_utils.common.dict_transformations import (
+ _camel_to_snake,
+ _snake_to_camel,
+ camel_dict_to_snake_dict,
+ dict_merge,
+ recursive_diff,
+)
+
+
+EXPECTED_SNAKIFICATION = {
+ 'alllower': 'alllower',
+ 'TwoWords': 'two_words',
+ 'AllUpperAtEND': 'all_upper_at_end',
+ 'AllUpperButPLURALs': 'all_upper_but_plurals',
+ 'TargetGroupARNs': 'target_group_arns',
+ 'HTTPEndpoints': 'http_endpoints',
+ 'PLURALs': 'plurals'
+}
+
+EXPECTED_REVERSIBLE = {
+ 'TwoWords': 'two_words',
+ 'AllUpperAtEND': 'all_upper_at_e_n_d',
+ 'AllUpperButPLURALs': 'all_upper_but_p_l_u_r_a_ls',
+ 'TargetGroupARNs': 'target_group_a_r_ns',
+ 'HTTPEndpoints': 'h_t_t_p_endpoints',
+ 'PLURALs': 'p_l_u_r_a_ls'
+}
+
+
+class TestCaseCamelToSnake:
+
+ def test_camel_to_snake(self):
+ for (k, v) in EXPECTED_SNAKIFICATION.items():
+ assert _camel_to_snake(k) == v
+
+ def test_reversible_camel_to_snake(self):
+ for (k, v) in EXPECTED_REVERSIBLE.items():
+ assert _camel_to_snake(k, reversible=True) == v
+
+
+class TestCaseSnakeToCamel:
+
+ def test_snake_to_camel_reversed(self):
+ for (k, v) in EXPECTED_REVERSIBLE.items():
+ assert _snake_to_camel(v, capitalize_first=True) == k
+
+
+class TestCaseCamelToSnakeAndBack:
+ def test_camel_to_snake_and_back(self):
+ for (k, v) in EXPECTED_REVERSIBLE.items():
+ assert _snake_to_camel(_camel_to_snake(k, reversible=True), capitalize_first=True) == k
+
+
+class TestCaseCamelDictToSnakeDict:
+ def test_ignore_list(self):
+ camel_dict = dict(Hello=dict(One='one', Two='two'), World=dict(Three='three', Four='four'))
+ snake_dict = camel_dict_to_snake_dict(camel_dict, ignore_list='World')
+ assert snake_dict['hello'] == dict(one='one', two='two')
+ assert snake_dict['world'] == dict(Three='three', Four='four')
+
+
+class TestCaseDictMerge:
+ def test_dict_merge(self):
+ base = dict(obj2=dict(), b1=True, b2=False, b3=False,
+ one=1, two=2, three=3, obj1=dict(key1=1, key2=2),
+ l1=[1, 3], l2=[1, 2, 3], l4=[4],
+ nested=dict(n1=dict(n2=2)))
+
+ other = dict(b1=True, b2=False, b3=True, b4=True,
+ one=1, three=4, four=4, obj1=dict(key1=2),
+ l1=[2, 1], l2=[3, 2, 1], l3=[1],
+ nested=dict(n1=dict(n2=2, n3=3)))
+
+ result = dict_merge(base, other)
+
+ # string assertions
+ assert 'one' in result
+ assert 'two' in result
+ assert result['three'] == 4
+ assert result['four'] == 4
+
+ # dict assertions
+ assert 'obj1' in result
+ assert 'key1' in result['obj1']
+ assert 'key2' in result['obj1']
+
+ # list assertions
+ # this line differs from the network_utils/common test of the function of the
+ # same name as this method does not merge lists
+ assert result['l1'], [2, 1]
+ assert 'l2' in result
+ assert result['l3'], [1]
+ assert 'l4' in result
+
+ # nested assertions
+ assert 'obj1' in result
+ assert result['obj1']['key1'], 2
+ assert 'key2' in result['obj1']
+
+ # bool assertions
+ assert 'b1' in result
+ assert 'b2' in result
+ assert result['b3']
+ assert result['b4']
+
+
+class TestCaseAzureIncidental:
+
+ def test_dict_merge_invalid_dict(self):
+ ''' if b is not a dict, return b '''
+ res = dict_merge({}, None)
+ assert res is None
+
+ def test_merge_sub_dicts(self):
+ '''merge sub dicts '''
+ a = {'a': {'a1': 1}}
+ b = {'a': {'b1': 2}}
+ c = {'a': {'a1': 1, 'b1': 2}}
+ res = dict_merge(a, b)
+ assert res == c
+
+
+class TestCaseRecursiveDiff:
+ def test_recursive_diff(self):
+ a = {'foo': {'bar': [{'baz': {'qux': 'ham_sandwich'}}]}}
+ c = {'foo': {'bar': [{'baz': {'qux': 'ham_sandwich'}}]}}
+ b = {'foo': {'bar': [{'baz': {'qux': 'turkey_sandwich'}}]}}
+
+ assert recursive_diff(a, b) is not None
+ assert len(recursive_diff(a, b)) == 2
+ assert recursive_diff(a, c) is None
+
+ @pytest.mark.parametrize(
+ 'p1, p2', (
+ ([1, 2], [2, 3]),
+ ({1: 2}, [2, 3]),
+ ([1, 2], {2: 3}),
+ ({2: 3}, 'notadict'),
+ ('notadict', {2: 3}),
+ )
+ )
+ def test_recursive_diff_negative(self, p1, p2):
+ with pytest.raises(TypeError, match="Unable to diff"):
+ recursive_diff(p1, p2)
diff --git a/test/units/module_utils/common/test_locale.py b/test/units/module_utils/common/test_locale.py
new file mode 100644
index 0000000..9d95986
--- /dev/null
+++ b/test/units/module_utils/common/test_locale.py
@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+# (c) Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+from units.compat.mock import MagicMock
+
+from ansible.module_utils.common.locale import get_best_parsable_locale
+
+
+class TestLocale:
+ """Tests for get_best_paresable_locale"""
+
+ mock_module = MagicMock()
+ mock_module.get_bin_path = MagicMock(return_value='/usr/bin/locale')
+
+ def test_finding_best(self):
+ self.mock_module.run_command = MagicMock(return_value=(0, "C.utf8\nen_US.utf8\nC\nPOSIX\n", ''))
+ locale = get_best_parsable_locale(self.mock_module)
+ assert locale == 'C.utf8'
+
+ def test_finding_last(self):
+ self.mock_module.run_command = MagicMock(return_value=(0, "fr_FR.utf8\nen_UK.utf8\nC\nPOSIX\n", ''))
+ locale = get_best_parsable_locale(self.mock_module)
+ assert locale == 'C'
+
+ def test_finding_middle(self):
+ self.mock_module.run_command = MagicMock(return_value=(0, "fr_FR.utf8\nen_US.utf8\nC\nPOSIX\n", ''))
+ locale = get_best_parsable_locale(self.mock_module)
+ assert locale == 'en_US.utf8'
+
+ def test_finding_prefered(self):
+ self.mock_module.run_command = MagicMock(return_value=(0, "es_ES.utf8\nMINE\nC\nPOSIX\n", ''))
+ locale = get_best_parsable_locale(self.mock_module, preferences=['MINE', 'C.utf8'])
+ assert locale == 'MINE'
+
+ def test_finding_C_on_no_match(self):
+ self.mock_module.run_command = MagicMock(return_value=(0, "fr_FR.UTF8\nMINE\n", ''))
+ locale = get_best_parsable_locale(self.mock_module)
+ assert locale == 'C'
diff --git a/test/units/module_utils/common/test_network.py b/test/units/module_utils/common/test_network.py
new file mode 100644
index 0000000..27d9503
--- /dev/null
+++ b/test/units/module_utils/common/test_network.py
@@ -0,0 +1,79 @@
+# -*- coding: utf-8 -*-
+# (c) 2017 Red Hat, Inc.
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+# Make coding more python3-ish
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+import pytest
+
+from ansible.module_utils.common.network import (
+ to_bits,
+ to_masklen,
+ to_netmask,
+ to_subnet,
+ to_ipv6_network,
+ is_masklen,
+ is_netmask
+)
+
+
+def test_to_masklen():
+ assert 24 == to_masklen('255.255.255.0')
+
+
+def test_to_masklen_invalid():
+ with pytest.raises(ValueError):
+ to_masklen('255')
+
+
+def test_to_netmask():
+ assert '255.0.0.0' == to_netmask(8)
+ assert '255.0.0.0' == to_netmask('8')
+
+
+def test_to_netmask_invalid():
+ with pytest.raises(ValueError):
+ to_netmask(128)
+
+
+def test_to_subnet():
+ result = to_subnet('192.168.1.1', 24)
+ assert '192.168.1.0/24' == result
+
+ result = to_subnet('192.168.1.1', 24, dotted_notation=True)
+ assert '192.168.1.0 255.255.255.0' == result
+
+
+def test_to_subnet_invalid():
+ with pytest.raises(ValueError):
+ to_subnet('foo', 'bar')
+
+
+def test_is_masklen():
+ assert is_masklen(32)
+ assert not is_masklen(33)
+ assert not is_masklen('foo')
+
+
+def test_is_netmask():
+ assert is_netmask('255.255.255.255')
+ assert not is_netmask(24)
+ assert not is_netmask('foo')
+
+
+def test_to_ipv6_network():
+ assert '2001:db8::' == to_ipv6_network('2001:db8::')
+ assert '2001:0db8:85a3::' == to_ipv6_network('2001:0db8:85a3:0000:0000:8a2e:0370:7334')
+ assert '2001:0db8:85a3::' == to_ipv6_network('2001:0db8:85a3:0:0:8a2e:0370:7334')
+
+
+def test_to_bits():
+ assert to_bits('0') == '00000000'
+ assert to_bits('1') == '00000001'
+ assert to_bits('2') == '00000010'
+ assert to_bits('1337') == '10100111001'
+ assert to_bits('127.0.0.1') == '01111111000000000000000000000001'
+ assert to_bits('255.255.255.255') == '11111111111111111111111111111111'
+ assert to_bits('255.255.255.0') == '11111111111111111111111100000000'
diff --git a/test/units/module_utils/common/test_sys_info.py b/test/units/module_utils/common/test_sys_info.py
new file mode 100644
index 0000000..18aafe5
--- /dev/null
+++ b/test/units/module_utils/common/test_sys_info.py
@@ -0,0 +1,168 @@
+# -*- coding: utf-8 -*-
+# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
+# (c) 2016 Toshio Kuratomi <tkuratomi@ansible.com>
+# (c) 2017-2018 Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+import pytest
+
+from units.compat.mock import patch
+
+from ansible.module_utils.six.moves import builtins
+
+# Functions being tested
+from ansible.module_utils.common.sys_info import get_distribution
+from ansible.module_utils.common.sys_info import get_distribution_version
+from ansible.module_utils.common.sys_info import get_platform_subclass
+
+
+realimport = builtins.__import__
+
+
+@pytest.fixture
+def platform_linux(mocker):
+ mocker.patch('platform.system', return_value='Linux')
+
+
+#
+# get_distribution tests
+#
+
+@pytest.mark.parametrize(
+ ('system', 'dist'),
+ (
+ ('Darwin', 'Darwin'),
+ ('SunOS', 'Solaris'),
+ ('FreeBSD', 'Freebsd'),
+ ),
+)
+def test_get_distribution_not_linux(system, dist, mocker):
+ """For platforms other than Linux, return the distribution"""
+ mocker.patch('platform.system', return_value=system)
+ mocker.patch('ansible.module_utils.common.sys_info.distro.id', return_value=dist)
+ assert get_distribution() == dist
+
+
+@pytest.mark.usefixtures("platform_linux")
+class TestGetDistribution:
+ """Tests for get_distribution that have to find something"""
+ def test_distro_known(self):
+ with patch('ansible.module_utils.distro.id', return_value="alpine"):
+ assert get_distribution() == "Alpine"
+
+ with patch('ansible.module_utils.distro.id', return_value="arch"):
+ assert get_distribution() == "Arch"
+
+ with patch('ansible.module_utils.distro.id', return_value="centos"):
+ assert get_distribution() == "Centos"
+
+ with patch('ansible.module_utils.distro.id', return_value="clear-linux-os"):
+ assert get_distribution() == "Clear-linux-os"
+
+ with patch('ansible.module_utils.distro.id', return_value="coreos"):
+ assert get_distribution() == "Coreos"
+
+ with patch('ansible.module_utils.distro.id', return_value="debian"):
+ assert get_distribution() == "Debian"
+
+ with patch('ansible.module_utils.distro.id', return_value="flatcar"):
+ assert get_distribution() == "Flatcar"
+
+ with patch('ansible.module_utils.distro.id', return_value="linuxmint"):
+ assert get_distribution() == "Linuxmint"
+
+ with patch('ansible.module_utils.distro.id', return_value="opensuse"):
+ assert get_distribution() == "Opensuse"
+
+ with patch('ansible.module_utils.distro.id', return_value="oracle"):
+ assert get_distribution() == "Oracle"
+
+ with patch('ansible.module_utils.distro.id', return_value="raspian"):
+ assert get_distribution() == "Raspian"
+
+ with patch('ansible.module_utils.distro.id', return_value="rhel"):
+ assert get_distribution() == "Redhat"
+
+ with patch('ansible.module_utils.distro.id', return_value="ubuntu"):
+ assert get_distribution() == "Ubuntu"
+
+ with patch('ansible.module_utils.distro.id', return_value="virtuozzo"):
+ assert get_distribution() == "Virtuozzo"
+
+ with patch('ansible.module_utils.distro.id', return_value="foo"):
+ assert get_distribution() == "Foo"
+
+ def test_distro_unknown(self):
+ with patch('ansible.module_utils.distro.id', return_value=""):
+ assert get_distribution() == "OtherLinux"
+
+ def test_distro_amazon_linux_short(self):
+ with patch('ansible.module_utils.distro.id', return_value="amzn"):
+ assert get_distribution() == "Amazon"
+
+ def test_distro_amazon_linux_long(self):
+ with patch('ansible.module_utils.distro.id', return_value="amazon"):
+ assert get_distribution() == "Amazon"
+
+
+#
+# get_distribution_version tests
+#
+
+@pytest.mark.parametrize(
+ ('system', 'version'),
+ (
+ ('Darwin', '19.6.0'),
+ ('SunOS', '11.4'),
+ ('FreeBSD', '12.1'),
+ ),
+)
+def test_get_distribution_version_not_linux(mocker, system, version):
+ """If it's not Linux, then it has no distribution"""
+ mocker.patch('platform.system', return_value=system)
+ mocker.patch('ansible.module_utils.common.sys_info.distro.version', return_value=version)
+ assert get_distribution_version() == version
+
+
+@pytest.mark.usefixtures("platform_linux")
+def test_distro_found():
+ with patch('ansible.module_utils.distro.version', return_value="1"):
+ assert get_distribution_version() == "1"
+
+
+#
+# Tests for get_platform_subclass
+#
+
+class TestGetPlatformSubclass:
+ class LinuxTest:
+ pass
+
+ class Foo(LinuxTest):
+ platform = "Linux"
+ distribution = None
+
+ class Bar(LinuxTest):
+ platform = "Linux"
+ distribution = "Bar"
+
+ def test_not_linux(self):
+ # if neither match, the fallback should be the top-level class
+ with patch('platform.system', return_value="Foo"):
+ with patch('ansible.module_utils.common.sys_info.get_distribution', return_value=None):
+ assert get_platform_subclass(self.LinuxTest) is self.LinuxTest
+
+ @pytest.mark.usefixtures("platform_linux")
+ def test_get_distribution_none(self):
+ # match just the platform class, not a specific distribution
+ with patch('ansible.module_utils.common.sys_info.get_distribution', return_value=None):
+ assert get_platform_subclass(self.LinuxTest) is self.Foo
+
+ @pytest.mark.usefixtures("platform_linux")
+ def test_get_distribution_found(self):
+ # match both the distribution and platform class
+ with patch('ansible.module_utils.common.sys_info.get_distribution', return_value="Bar"):
+ assert get_platform_subclass(self.LinuxTest) is self.Bar
diff --git a/test/units/module_utils/common/test_utils.py b/test/units/module_utils/common/test_utils.py
new file mode 100644
index 0000000..ef95239
--- /dev/null
+++ b/test/units/module_utils/common/test_utils.py
@@ -0,0 +1,46 @@
+# -*- coding: utf-8 -*-
+# (c) 2018 Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+from ansible.module_utils.common.sys_info import get_all_subclasses
+
+
+#
+# Tests for get_all_subclasses
+#
+
+class TestGetAllSubclasses:
+ class Base:
+ pass
+
+ class BranchI(Base):
+ pass
+
+ class BranchII(Base):
+ pass
+
+ class BranchIA(BranchI):
+ pass
+
+ class BranchIB(BranchI):
+ pass
+
+ class BranchIIA(BranchII):
+ pass
+
+ class BranchIIB(BranchII):
+ pass
+
+ def test_bottom_level(self):
+ assert get_all_subclasses(self.BranchIIB) == set()
+
+ def test_one_inheritance(self):
+ assert set(get_all_subclasses(self.BranchII)) == set([self.BranchIIA, self.BranchIIB])
+
+ def test_toplevel(self):
+ assert set(get_all_subclasses(self.Base)) == set([self.BranchI, self.BranchII,
+ self.BranchIA, self.BranchIB,
+ self.BranchIIA, self.BranchIIB])
diff --git a/test/units/module_utils/common/text/converters/test_container_to_bytes.py b/test/units/module_utils/common/text/converters/test_container_to_bytes.py
new file mode 100644
index 0000000..091545e
--- /dev/null
+++ b/test/units/module_utils/common/text/converters/test_container_to_bytes.py
@@ -0,0 +1,95 @@
+# -*- coding: utf-8 -*-
+# Copyright 2019, Andrew Klychkov @Andersson007 <aaklychkov@mail.ru>
+# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+import pytest
+
+from ansible.module_utils.common.text.converters import container_to_bytes
+
+
+DEFAULT_ENCODING = 'utf-8'
+DEFAULT_ERR_HANDLER = 'surrogate_or_strict'
+
+
+@pytest.mark.parametrize(
+ 'test_input,expected',
+ [
+ ({1: 1}, {1: 1}),
+ ([1, 2], [1, 2]),
+ ((1, 2), (1, 2)),
+ (1, 1),
+ (1.1, 1.1),
+ (b'str', b'str'),
+ (u'str', b'str'),
+ ([u'str'], [b'str']),
+ ((u'str'), (b'str')),
+ ({u'str': u'str'}, {b'str': b'str'}),
+ ]
+)
+@pytest.mark.parametrize('encoding', ['utf-8', 'latin1', 'shift_jis', 'big5', 'koi8_r'])
+@pytest.mark.parametrize('errors', ['strict', 'surrogate_or_strict', 'surrogate_then_replace'])
+def test_container_to_bytes(test_input, expected, encoding, errors):
+ """Test for passing objects to container_to_bytes()."""
+ assert container_to_bytes(test_input, encoding=encoding, errors=errors) == expected
+
+
+@pytest.mark.parametrize(
+ 'test_input,expected',
+ [
+ ({1: 1}, {1: 1}),
+ ([1, 2], [1, 2]),
+ ((1, 2), (1, 2)),
+ (1, 1),
+ (1.1, 1.1),
+ (True, True),
+ (None, None),
+ (u'str', u'str'.encode(DEFAULT_ENCODING)),
+ (u'くらとみ', u'くらとみ'.encode(DEFAULT_ENCODING)),
+ (u'café', u'café'.encode(DEFAULT_ENCODING)),
+ (b'str', u'str'.encode(DEFAULT_ENCODING)),
+ (u'str', u'str'.encode(DEFAULT_ENCODING)),
+ ([u'str'], [u'str'.encode(DEFAULT_ENCODING)]),
+ ((u'str'), (u'str'.encode(DEFAULT_ENCODING))),
+ ({u'str': u'str'}, {u'str'.encode(DEFAULT_ENCODING): u'str'.encode(DEFAULT_ENCODING)}),
+ ]
+)
+def test_container_to_bytes_default_encoding_err(test_input, expected):
+ """
+ Test for passing objects to container_to_bytes(). Default encoding and errors
+ """
+ assert container_to_bytes(test_input, encoding=DEFAULT_ENCODING,
+ errors=DEFAULT_ERR_HANDLER) == expected
+
+
+@pytest.mark.parametrize(
+ 'test_input,encoding',
+ [
+ (u'くらとみ', 'latin1'),
+ (u'café', 'shift_jis'),
+ ]
+)
+@pytest.mark.parametrize('errors', ['surrogate_or_strict', 'strict'])
+def test_container_to_bytes_incomp_chars_and_encod(test_input, encoding, errors):
+ """
+ Test for passing incompatible characters and encodings container_to_bytes().
+ """
+ with pytest.raises(UnicodeEncodeError, match="codec can't encode"):
+ container_to_bytes(test_input, encoding=encoding, errors=errors)
+
+
+@pytest.mark.parametrize(
+ 'test_input,encoding,expected',
+ [
+ (u'くらとみ', 'latin1', b'????'),
+ (u'café', 'shift_jis', b'caf?'),
+ ]
+)
+def test_container_to_bytes_surrogate_then_replace(test_input, encoding, expected):
+ """
+ Test for container_to_bytes() with surrogate_then_replace err handler.
+ """
+ assert container_to_bytes(test_input, encoding=encoding,
+ errors='surrogate_then_replace') == expected
diff --git a/test/units/module_utils/common/text/converters/test_container_to_text.py b/test/units/module_utils/common/text/converters/test_container_to_text.py
new file mode 100644
index 0000000..39038f5
--- /dev/null
+++ b/test/units/module_utils/common/text/converters/test_container_to_text.py
@@ -0,0 +1,78 @@
+# -*- coding: utf-8 -*-
+# Copyright 2019, Andrew Klychkov @Andersson007 <aaklychkov@mail.ru>
+# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+import pytest
+
+from ansible.module_utils.common.text.converters import container_to_text
+
+
+DEFAULT_ENCODING = 'utf-8'
+DEFAULT_ERR_HANDLER = 'surrogate_or_strict'
+
+
+@pytest.mark.parametrize(
+ 'test_input,expected',
+ [
+ ({1: 1}, {1: 1}),
+ ([1, 2], [1, 2]),
+ ((1, 2), (1, 2)),
+ (1, 1),
+ (1.1, 1.1),
+ (b'str', u'str'),
+ (u'str', u'str'),
+ ([b'str'], [u'str']),
+ ((b'str'), (u'str')),
+ ({b'str': b'str'}, {u'str': u'str'}),
+ ]
+)
+@pytest.mark.parametrize('encoding', ['utf-8', 'latin1', 'shift-jis', 'big5', 'koi8_r', ])
+@pytest.mark.parametrize('errors', ['strict', 'surrogate_or_strict', 'surrogate_then_replace', ])
+def test_container_to_text_different_types(test_input, expected, encoding, errors):
+ """Test for passing objects to container_to_text()."""
+ assert container_to_text(test_input, encoding=encoding, errors=errors) == expected
+
+
+@pytest.mark.parametrize(
+ 'test_input,expected',
+ [
+ ({1: 1}, {1: 1}),
+ ([1, 2], [1, 2]),
+ ((1, 2), (1, 2)),
+ (1, 1),
+ (1.1, 1.1),
+ (True, True),
+ (None, None),
+ (u'str', u'str'),
+ (u'くらとみ'.encode(DEFAULT_ENCODING), u'くらとみ'),
+ (u'café'.encode(DEFAULT_ENCODING), u'café'),
+ (u'str'.encode(DEFAULT_ENCODING), u'str'),
+ ([u'str'.encode(DEFAULT_ENCODING)], [u'str']),
+ ((u'str'.encode(DEFAULT_ENCODING)), (u'str')),
+ ({b'str': b'str'}, {u'str': u'str'}),
+ ]
+)
+def test_container_to_text_default_encoding_and_err(test_input, expected):
+ """
+ Test for passing objects to container_to_text(). Default encoding and errors
+ """
+ assert container_to_text(test_input, encoding=DEFAULT_ENCODING,
+ errors=DEFAULT_ERR_HANDLER) == expected
+
+
+@pytest.mark.parametrize(
+ 'test_input,encoding,expected',
+ [
+ (u'й'.encode('utf-8'), 'latin1', u'й'),
+ (u'café'.encode('utf-8'), 'shift_jis', u'cafテゥ'),
+ ]
+)
+@pytest.mark.parametrize('errors', ['strict', 'surrogate_or_strict', 'surrogate_then_replace', ])
+def test_container_to_text_incomp_encod_chars(test_input, encoding, errors, expected):
+ """
+ Test for passing incompatible characters and encodings container_to_text().
+ """
+ assert container_to_text(test_input, encoding=encoding, errors=errors) == expected
diff --git a/test/units/module_utils/common/text/converters/test_json_encode_fallback.py b/test/units/module_utils/common/text/converters/test_json_encode_fallback.py
new file mode 100644
index 0000000..022f38f
--- /dev/null
+++ b/test/units/module_utils/common/text/converters/test_json_encode_fallback.py
@@ -0,0 +1,68 @@
+# -*- coding: utf-8 -*-
+# Copyright 2019, Andrew Klychkov @Andersson007 <aaklychkov@mail.ru>
+# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+import pytest
+
+from datetime import datetime, timedelta, tzinfo
+
+from ansible.module_utils.common.text.converters import _json_encode_fallback
+
+
+class timezone(tzinfo):
+ """Simple timezone implementation for use until we drop Python 2.7 support."""
+ def __init__(self, offset):
+ self._offset = offset
+
+ def utcoffset(self, dt):
+ return self._offset
+
+ def dst(self, dt):
+ return timedelta(0)
+
+ def tzname(self, dt):
+ return None
+
+
+@pytest.mark.parametrize(
+ 'test_input,expected',
+ [
+ (set([1]), [1]),
+ (datetime(2019, 5, 14, 13, 39, 38, 569047), '2019-05-14T13:39:38.569047'),
+ (datetime(2019, 5, 14, 13, 47, 16, 923866), '2019-05-14T13:47:16.923866'),
+ (datetime(2019, 6, 15, 14, 45, tzinfo=timezone(timedelta(0))), '2019-06-15T14:45:00+00:00'),
+ (datetime(2019, 6, 15, 14, 45, tzinfo=timezone(timedelta(hours=1, minutes=40))), '2019-06-15T14:45:00+01:40'),
+ ]
+)
+def test_json_encode_fallback(test_input, expected):
+ """
+ Test for passing expected objects to _json_encode_fallback().
+ """
+ assert _json_encode_fallback(test_input) == expected
+
+
+@pytest.mark.parametrize(
+ 'test_input',
+ [
+ 1,
+ 1.1,
+ u'string',
+ b'string',
+ [1, 2],
+ True,
+ None,
+ {1: 1},
+ (1, 2),
+ ]
+)
+def test_json_encode_fallback_default_behavior(test_input):
+ """
+ Test for _json_encode_fallback() default behavior.
+
+ It must fail with TypeError.
+ """
+ with pytest.raises(TypeError, match='Cannot json serialize'):
+ _json_encode_fallback(test_input)
diff --git a/test/units/module_utils/common/text/converters/test_jsonify.py b/test/units/module_utils/common/text/converters/test_jsonify.py
new file mode 100644
index 0000000..a341531
--- /dev/null
+++ b/test/units/module_utils/common/text/converters/test_jsonify.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+# Copyright 2019, Andrew Klychkov @Andersson007 <aaklychkov@mail.ru>
+# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+import pytest
+
+from ansible.module_utils.common.text.converters import jsonify
+
+
+@pytest.mark.parametrize(
+ 'test_input,expected',
+ [
+ (1, '1'),
+ (u'string', u'"string"'),
+ (u'くらとみ', u'"\\u304f\\u3089\\u3068\\u307f"'),
+ (u'café', u'"caf\\u00e9"'),
+ (b'string', u'"string"'),
+ (False, u'false'),
+ (u'string'.encode('utf-8'), u'"string"'),
+ ]
+)
+def test_jsonify(test_input, expected):
+ """Test for jsonify()."""
+ assert jsonify(test_input) == expected
diff --git a/test/units/module_utils/common/text/converters/test_to_str.py b/test/units/module_utils/common/text/converters/test_to_str.py
new file mode 100644
index 0000000..712ed85
--- /dev/null
+++ b/test/units/module_utils/common/text/converters/test_to_str.py
@@ -0,0 +1,50 @@
+# -*- coding: utf-8 -*-
+# (c) 2016 Toshio Kuratomi <tkuratomi@ansible.com>
+# Copyright (c) 2017 Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+import itertools
+
+import pytest
+
+from ansible.module_utils.six import PY3
+
+from ansible.module_utils.common.text.converters import to_text, to_bytes, to_native
+
+
+# Format: byte representation, text representation, encoding of byte representation
+VALID_STRINGS = (
+ (b'abcde', u'abcde', 'ascii'),
+ (b'caf\xc3\xa9', u'caf\xe9', 'utf-8'),
+ (b'caf\xe9', u'caf\xe9', 'latin-1'),
+ # u'くらとみ'
+ (b'\xe3\x81\x8f\xe3\x82\x89\xe3\x81\xa8\xe3\x81\xbf', u'\u304f\u3089\u3068\u307f', 'utf-8'),
+ (b'\x82\xad\x82\xe7\x82\xc6\x82\xdd', u'\u304f\u3089\u3068\u307f', 'shift-jis'),
+)
+
+
+@pytest.mark.parametrize('in_string, encoding, expected',
+ itertools.chain(((d[0], d[2], d[1]) for d in VALID_STRINGS),
+ ((d[1], d[2], d[1]) for d in VALID_STRINGS)))
+def test_to_text(in_string, encoding, expected):
+ """test happy path of decoding to text"""
+ assert to_text(in_string, encoding) == expected
+
+
+@pytest.mark.parametrize('in_string, encoding, expected',
+ itertools.chain(((d[0], d[2], d[0]) for d in VALID_STRINGS),
+ ((d[1], d[2], d[0]) for d in VALID_STRINGS)))
+def test_to_bytes(in_string, encoding, expected):
+ """test happy path of encoding to bytes"""
+ assert to_bytes(in_string, encoding) == expected
+
+
+@pytest.mark.parametrize('in_string, encoding, expected',
+ itertools.chain(((d[0], d[2], d[1] if PY3 else d[0]) for d in VALID_STRINGS),
+ ((d[1], d[2], d[1] if PY3 else d[0]) for d in VALID_STRINGS)))
+def test_to_native(in_string, encoding, expected):
+ """test happy path of encoding to native strings"""
+ assert to_native(in_string, encoding) == expected
diff --git a/test/units/module_utils/common/text/formatters/test_bytes_to_human.py b/test/units/module_utils/common/text/formatters/test_bytes_to_human.py
new file mode 100644
index 0000000..41475f5
--- /dev/null
+++ b/test/units/module_utils/common/text/formatters/test_bytes_to_human.py
@@ -0,0 +1,116 @@
+# -*- coding: utf-8 -*-
+# Copyright 2019, Andrew Klychkov @Andersson007 <aaklychkov@mail.ru>
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+import pytest
+
+from ansible.module_utils.common.text.formatters import bytes_to_human
+
+
+@pytest.mark.parametrize(
+ 'input_data,expected',
+ [
+ (0, u'0.00 Bytes'),
+ (0.5, u'0.50 Bytes'),
+ (0.54, u'0.54 Bytes'),
+ (1024, u'1.00 KB'),
+ (1025, u'1.00 KB'),
+ (1536, u'1.50 KB'),
+ (1790, u'1.75 KB'),
+ (1048576, u'1.00 MB'),
+ (1073741824, u'1.00 GB'),
+ (1099511627776, u'1.00 TB'),
+ (1125899906842624, u'1.00 PB'),
+ (1152921504606846976, u'1.00 EB'),
+ (1180591620717411303424, u'1.00 ZB'),
+ (1208925819614629174706176, u'1.00 YB'),
+ ]
+)
+def test_bytes_to_human(input_data, expected):
+ """Test of bytes_to_human function, only proper numbers are passed."""
+ assert bytes_to_human(input_data) == expected
+
+
+@pytest.mark.parametrize(
+ 'input_data,expected',
+ [
+ (0, u'0.00 bits'),
+ (0.5, u'0.50 bits'),
+ (0.54, u'0.54 bits'),
+ (1024, u'1.00 Kb'),
+ (1025, u'1.00 Kb'),
+ (1536, u'1.50 Kb'),
+ (1790, u'1.75 Kb'),
+ (1048576, u'1.00 Mb'),
+ (1073741824, u'1.00 Gb'),
+ (1099511627776, u'1.00 Tb'),
+ (1125899906842624, u'1.00 Pb'),
+ (1152921504606846976, u'1.00 Eb'),
+ (1180591620717411303424, u'1.00 Zb'),
+ (1208925819614629174706176, u'1.00 Yb'),
+ ]
+)
+def test_bytes_to_human_isbits(input_data, expected):
+ """Test of bytes_to_human function with isbits=True proper results."""
+ assert bytes_to_human(input_data, isbits=True) == expected
+
+
+@pytest.mark.parametrize(
+ 'input_data,unit,expected',
+ [
+ (0, u'B', u'0.00 Bytes'),
+ (0.5, u'B', u'0.50 Bytes'),
+ (0.54, u'B', u'0.54 Bytes'),
+ (1024, u'K', u'1.00 KB'),
+ (1536, u'K', u'1.50 KB'),
+ (1790, u'K', u'1.75 KB'),
+ (1048576, u'M', u'1.00 MB'),
+ (1099511627776, u'T', u'1.00 TB'),
+ (1152921504606846976, u'E', u'1.00 EB'),
+ (1180591620717411303424, u'Z', u'1.00 ZB'),
+ (1208925819614629174706176, u'Y', u'1.00 YB'),
+ (1025, u'KB', u'1025.00 Bytes'),
+ (1073741824, u'Gb', u'1073741824.00 Bytes'),
+ (1125899906842624, u'Pb', u'1125899906842624.00 Bytes'),
+ ]
+)
+def test_bytes_to_human_unit(input_data, unit, expected):
+ """Test unit argument of bytes_to_human function proper results."""
+ assert bytes_to_human(input_data, unit=unit) == expected
+
+
+@pytest.mark.parametrize(
+ 'input_data,unit,expected',
+ [
+ (0, u'B', u'0.00 bits'),
+ (0.5, u'B', u'0.50 bits'),
+ (0.54, u'B', u'0.54 bits'),
+ (1024, u'K', u'1.00 Kb'),
+ (1536, u'K', u'1.50 Kb'),
+ (1790, u'K', u'1.75 Kb'),
+ (1048576, u'M', u'1.00 Mb'),
+ (1099511627776, u'T', u'1.00 Tb'),
+ (1152921504606846976, u'E', u'1.00 Eb'),
+ (1180591620717411303424, u'Z', u'1.00 Zb'),
+ (1208925819614629174706176, u'Y', u'1.00 Yb'),
+ (1025, u'KB', u'1025.00 bits'),
+ (1073741824, u'Gb', u'1073741824.00 bits'),
+ (1125899906842624, u'Pb', u'1125899906842624.00 bits'),
+ ]
+)
+def test_bytes_to_human_unit_isbits(input_data, unit, expected):
+ """Test unit argument of bytes_to_human function with isbits=True proper results."""
+ assert bytes_to_human(input_data, isbits=True, unit=unit) == expected
+
+
+@pytest.mark.parametrize('input_data', [0j, u'1B', [1], {1: 1}, None, b'1B'])
+def test_bytes_to_human_illegal_size(input_data):
+ """Test of bytes_to_human function, illegal objects are passed as a size."""
+ e_regexp = (r'(no ordering relation is defined for complex numbers)|'
+ r'(unsupported operand type\(s\) for /)|(unorderable types)|'
+ r'(not supported between instances of)')
+ with pytest.raises(TypeError, match=e_regexp):
+ bytes_to_human(input_data)
diff --git a/test/units/module_utils/common/text/formatters/test_human_to_bytes.py b/test/units/module_utils/common/text/formatters/test_human_to_bytes.py
new file mode 100644
index 0000000..d02699a
--- /dev/null
+++ b/test/units/module_utils/common/text/formatters/test_human_to_bytes.py
@@ -0,0 +1,185 @@
+# -*- coding: utf-8 -*-
+# Copyright 2019, Andrew Klychkov @Andersson007 <aaklychkov@mail.ru>
+# Copyright 2019, Sviatoslav Sydorenko <webknjaz@redhat.com>
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+import pytest
+
+from ansible.module_utils.common.text.formatters import human_to_bytes
+
+
+NUM_IN_METRIC = {
+ 'K': 2 ** 10,
+ 'M': 2 ** 20,
+ 'G': 2 ** 30,
+ 'T': 2 ** 40,
+ 'P': 2 ** 50,
+ 'E': 2 ** 60,
+ 'Z': 2 ** 70,
+ 'Y': 2 ** 80,
+}
+
+
+@pytest.mark.parametrize(
+ 'input_data,expected',
+ [
+ (0, 0),
+ (u'0B', 0),
+ (1024, NUM_IN_METRIC['K']),
+ (u'1024B', NUM_IN_METRIC['K']),
+ (u'1K', NUM_IN_METRIC['K']),
+ (u'1KB', NUM_IN_METRIC['K']),
+ (u'1M', NUM_IN_METRIC['M']),
+ (u'1MB', NUM_IN_METRIC['M']),
+ (u'1G', NUM_IN_METRIC['G']),
+ (u'1GB', NUM_IN_METRIC['G']),
+ (u'1T', NUM_IN_METRIC['T']),
+ (u'1TB', NUM_IN_METRIC['T']),
+ (u'1P', NUM_IN_METRIC['P']),
+ (u'1PB', NUM_IN_METRIC['P']),
+ (u'1E', NUM_IN_METRIC['E']),
+ (u'1EB', NUM_IN_METRIC['E']),
+ (u'1Z', NUM_IN_METRIC['Z']),
+ (u'1ZB', NUM_IN_METRIC['Z']),
+ (u'1Y', NUM_IN_METRIC['Y']),
+ (u'1YB', NUM_IN_METRIC['Y']),
+ ]
+)
+def test_human_to_bytes_number(input_data, expected):
+ """Test of human_to_bytes function, only number arg is passed."""
+ assert human_to_bytes(input_data) == expected
+
+
+@pytest.mark.parametrize(
+ 'input_data,unit',
+ [
+ (u'1024', 'B'),
+ (1, u'K'),
+ (1, u'KB'),
+ (u'1', u'M'),
+ (u'1', u'MB'),
+ (1, u'G'),
+ (1, u'GB'),
+ (1, u'T'),
+ (1, u'TB'),
+ (u'1', u'P'),
+ (u'1', u'PB'),
+ (u'1', u'E'),
+ (u'1', u'EB'),
+ (u'1', u'Z'),
+ (u'1', u'ZB'),
+ (u'1', u'Y'),
+ (u'1', u'YB'),
+ ]
+)
+def test_human_to_bytes_number_unit(input_data, unit):
+ """Test of human_to_bytes function, number and default_unit args are passed."""
+ assert human_to_bytes(input_data, default_unit=unit) == NUM_IN_METRIC.get(unit[0], 1024)
+
+
+@pytest.mark.parametrize('test_input', [u'1024s', u'1024w', ])
+def test_human_to_bytes_wrong_unit(test_input):
+ """Test of human_to_bytes function, wrong units."""
+ with pytest.raises(ValueError, match="The suffix must be one of"):
+ human_to_bytes(test_input)
+
+
+@pytest.mark.parametrize('test_input', [u'b1bbb', u'm2mmm', u'', u' ', -1])
+def test_human_to_bytes_wrong_number(test_input):
+ """Test of human_to_bytes function, number param is invalid string / number."""
+ with pytest.raises(ValueError, match="can't interpret"):
+ human_to_bytes(test_input)
+
+
+@pytest.mark.parametrize(
+ 'input_data,expected',
+ [
+ (0, 0),
+ (u'0B', 0),
+ (u'1024b', 1024),
+ (u'1024B', 1024),
+ (u'1K', NUM_IN_METRIC['K']),
+ (u'1Kb', NUM_IN_METRIC['K']),
+ (u'1M', NUM_IN_METRIC['M']),
+ (u'1Mb', NUM_IN_METRIC['M']),
+ (u'1G', NUM_IN_METRIC['G']),
+ (u'1Gb', NUM_IN_METRIC['G']),
+ (u'1T', NUM_IN_METRIC['T']),
+ (u'1Tb', NUM_IN_METRIC['T']),
+ (u'1P', NUM_IN_METRIC['P']),
+ (u'1Pb', NUM_IN_METRIC['P']),
+ (u'1E', NUM_IN_METRIC['E']),
+ (u'1Eb', NUM_IN_METRIC['E']),
+ (u'1Z', NUM_IN_METRIC['Z']),
+ (u'1Zb', NUM_IN_METRIC['Z']),
+ (u'1Y', NUM_IN_METRIC['Y']),
+ (u'1Yb', NUM_IN_METRIC['Y']),
+ ]
+)
+def test_human_to_bytes_isbits(input_data, expected):
+ """Test of human_to_bytes function, isbits = True."""
+ assert human_to_bytes(input_data, isbits=True) == expected
+
+
+@pytest.mark.parametrize(
+ 'input_data,unit',
+ [
+ (1024, 'b'),
+ (1024, 'B'),
+ (1, u'K'),
+ (1, u'Kb'),
+ (u'1', u'M'),
+ (u'1', u'Mb'),
+ (1, u'G'),
+ (1, u'Gb'),
+ (1, u'T'),
+ (1, u'Tb'),
+ (u'1', u'P'),
+ (u'1', u'Pb'),
+ (u'1', u'E'),
+ (u'1', u'Eb'),
+ (u'1', u'Z'),
+ (u'1', u'Zb'),
+ (u'1', u'Y'),
+ (u'1', u'Yb'),
+ ]
+)
+def test_human_to_bytes_isbits_default_unit(input_data, unit):
+ """Test of human_to_bytes function, isbits = True and default_unit args are passed."""
+ assert human_to_bytes(input_data, default_unit=unit, isbits=True) == NUM_IN_METRIC.get(unit[0], 1024)
+
+
+@pytest.mark.parametrize(
+ 'test_input,isbits',
+ [
+ ('1024Kb', False),
+ ('10Mb', False),
+ ('1Gb', False),
+ ('10MB', True),
+ ('2KB', True),
+ ('4GB', True),
+ ]
+)
+def test_human_to_bytes_isbits_wrong_unit(test_input, isbits):
+ """Test of human_to_bytes function, unit identifier is in an invalid format for isbits value."""
+ with pytest.raises(ValueError, match="Value is not a valid string"):
+ human_to_bytes(test_input, isbits=isbits)
+
+
+@pytest.mark.parametrize(
+ 'test_input,unit,isbits',
+ [
+ (1024, 'Kb', False),
+ ('10', 'Mb', False),
+ ('10', 'MB', True),
+ (2, 'KB', True),
+ ('4', 'GB', True),
+ ]
+)
+def test_human_to_bytes_isbits_wrong_default_unit(test_input, unit, isbits):
+ """Test of human_to_bytes function, default_unit is in an invalid format for isbits value."""
+ with pytest.raises(ValueError, match="Value is not a valid string"):
+ human_to_bytes(test_input, default_unit=unit, isbits=isbits)
diff --git a/test/units/module_utils/common/text/formatters/test_lenient_lowercase.py b/test/units/module_utils/common/text/formatters/test_lenient_lowercase.py
new file mode 100644
index 0000000..1ecc013
--- /dev/null
+++ b/test/units/module_utils/common/text/formatters/test_lenient_lowercase.py
@@ -0,0 +1,68 @@
+# -*- coding: utf-8 -*-
+# Copyright 2019, Andrew Klychkov @Andersson007 <aaklychkov@mail.ru>
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+from datetime import datetime
+
+import pytest
+
+from ansible.module_utils.common.text.formatters import lenient_lowercase
+
+
+INPUT_LIST = [
+ u'HELLO',
+ u'Ёлка',
+ u'cafÉ',
+ u'くらとみ',
+ b'HELLO',
+ 1,
+ {1: 'Dict'},
+ True,
+ [1],
+ 3.14159,
+]
+
+EXPECTED_LIST = [
+ u'hello',
+ u'ёлка',
+ u'café',
+ u'くらとみ',
+ b'hello',
+ 1,
+ {1: 'Dict'},
+ True,
+ [1],
+ 3.14159,
+]
+
+result_list = lenient_lowercase(INPUT_LIST)
+
+
+@pytest.mark.parametrize(
+ 'input_value,expected_outcome',
+ [
+ (result_list[0], EXPECTED_LIST[0]),
+ (result_list[1], EXPECTED_LIST[1]),
+ (result_list[2], EXPECTED_LIST[2]),
+ (result_list[3], EXPECTED_LIST[3]),
+ (result_list[4], EXPECTED_LIST[4]),
+ (result_list[5], EXPECTED_LIST[5]),
+ (result_list[6], EXPECTED_LIST[6]),
+ (result_list[7], EXPECTED_LIST[7]),
+ (result_list[8], EXPECTED_LIST[8]),
+ (result_list[9], EXPECTED_LIST[9]),
+ ]
+)
+def test_lenient_lowercase(input_value, expected_outcome):
+ """Test that lenient_lowercase() proper results."""
+ assert input_value == expected_outcome
+
+
+@pytest.mark.parametrize('input_data', [1, False, 1.001, 1j, datetime.now(), ])
+def test_lenient_lowercase_illegal_data_type(input_data):
+ """Test passing objects of illegal types to lenient_lowercase()."""
+ with pytest.raises(TypeError, match='object is not iterable'):
+ lenient_lowercase(input_data)
diff --git a/test/units/module_utils/common/validation/test_check_missing_parameters.py b/test/units/module_utils/common/validation/test_check_missing_parameters.py
new file mode 100644
index 0000000..6cbcb8b
--- /dev/null
+++ b/test/units/module_utils/common/validation/test_check_missing_parameters.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+# Copyright: (c) 2021, Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+import pytest
+
+from ansible.module_utils._text import to_native
+from ansible.module_utils.common.validation import check_required_one_of
+from ansible.module_utils.common.validation import check_missing_parameters
+
+
+@pytest.fixture
+def arguments_terms():
+ return {"path": ""}
+
+
+def test_check_missing_parameters():
+ assert check_missing_parameters([], {}) == []
+
+
+def test_check_missing_parameters_list():
+ expected = "missing required arguments: path"
+
+ with pytest.raises(TypeError) as e:
+ check_missing_parameters({}, ["path"])
+
+ assert to_native(e.value) == expected
+
+
+def test_check_missing_parameters_positive():
+ assert check_missing_parameters({"path": "/foo"}, ["path"]) == []
diff --git a/test/units/module_utils/common/validation/test_check_mutually_exclusive.py b/test/units/module_utils/common/validation/test_check_mutually_exclusive.py
new file mode 100644
index 0000000..7bf9076
--- /dev/null
+++ b/test/units/module_utils/common/validation/test_check_mutually_exclusive.py
@@ -0,0 +1,57 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2019 Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+import pytest
+
+from ansible.module_utils._text import to_native
+from ansible.module_utils.common.validation import check_mutually_exclusive
+
+
+@pytest.fixture
+def mutually_exclusive_terms():
+ return [
+ ('string1', 'string2',),
+ ('box', 'fox', 'socks'),
+ ]
+
+
+def test_check_mutually_exclusive(mutually_exclusive_terms):
+ params = {
+ 'string1': 'cat',
+ 'fox': 'hat',
+ }
+ assert check_mutually_exclusive(mutually_exclusive_terms, params) == []
+
+
+def test_check_mutually_exclusive_found(mutually_exclusive_terms):
+ params = {
+ 'string1': 'cat',
+ 'string2': 'hat',
+ 'fox': 'red',
+ 'socks': 'blue',
+ }
+ expected = "parameters are mutually exclusive: string1|string2, box|fox|socks"
+
+ with pytest.raises(TypeError) as e:
+ check_mutually_exclusive(mutually_exclusive_terms, params)
+
+ assert to_native(e.value) == expected
+
+
+def test_check_mutually_exclusive_none():
+ terms = None
+ params = {
+ 'string1': 'cat',
+ 'fox': 'hat',
+ }
+ assert check_mutually_exclusive(terms, params) == []
+
+
+def test_check_mutually_exclusive_no_params(mutually_exclusive_terms):
+ with pytest.raises(TypeError) as te:
+ check_mutually_exclusive(mutually_exclusive_terms, None)
+ assert "'NoneType' object is not iterable" in to_native(te.value)
diff --git a/test/units/module_utils/common/validation/test_check_required_arguments.py b/test/units/module_utils/common/validation/test_check_required_arguments.py
new file mode 100644
index 0000000..1dd5458
--- /dev/null
+++ b/test/units/module_utils/common/validation/test_check_required_arguments.py
@@ -0,0 +1,88 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020 Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+import pytest
+
+from ansible.module_utils._text import to_native
+from ansible.module_utils.common.validation import check_required_arguments
+
+
+@pytest.fixture
+def arguments_terms():
+ return {
+ 'foo': {
+ 'required': True,
+ },
+ 'bar': {
+ 'required': False,
+ },
+ 'tomato': {
+ 'irrelevant': 72,
+ },
+ }
+
+
+@pytest.fixture
+def arguments_terms_multiple():
+ return {
+ 'foo': {
+ 'required': True,
+ },
+ 'bar': {
+ 'required': True,
+ },
+ 'tomato': {
+ 'irrelevant': 72,
+ },
+ }
+
+
+def test_check_required_arguments(arguments_terms):
+ params = {
+ 'foo': 'hello',
+ 'bar': 'haha',
+ }
+ assert check_required_arguments(arguments_terms, params) == []
+
+
+def test_check_required_arguments_missing(arguments_terms):
+ params = {
+ 'apples': 'woohoo',
+ }
+ expected = "missing required arguments: foo"
+
+ with pytest.raises(TypeError) as e:
+ check_required_arguments(arguments_terms, params)
+
+ assert to_native(e.value) == expected
+
+
+def test_check_required_arguments_missing_multiple(arguments_terms_multiple):
+ params = {
+ 'apples': 'woohoo',
+ }
+ expected = "missing required arguments: bar, foo"
+
+ with pytest.raises(TypeError) as e:
+ check_required_arguments(arguments_terms_multiple, params)
+
+ assert to_native(e.value) == expected
+
+
+def test_check_required_arguments_missing_none():
+ terms = None
+ params = {
+ 'foo': 'bar',
+ 'baz': 'buzz',
+ }
+ assert check_required_arguments(terms, params) == []
+
+
+def test_check_required_arguments_no_params(arguments_terms):
+ with pytest.raises(TypeError) as te:
+ check_required_arguments(arguments_terms, None)
+ assert "'NoneType' is not iterable" in to_native(te.value)
diff --git a/test/units/module_utils/common/validation/test_check_required_by.py b/test/units/module_utils/common/validation/test_check_required_by.py
new file mode 100644
index 0000000..62cccff
--- /dev/null
+++ b/test/units/module_utils/common/validation/test_check_required_by.py
@@ -0,0 +1,98 @@
+# -*- coding: utf-8 -*-
+# Copyright: (c) 2021, Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+import pytest
+
+from ansible.module_utils._text import to_native
+from ansible.module_utils.common.validation import check_required_by
+
+
+@pytest.fixture
+def path_arguments_terms():
+ return {
+ "path": ["mode", "owner"],
+ }
+
+
+def test_check_required_by():
+ arguments_terms = {}
+ params = {}
+ assert check_required_by(arguments_terms, params) == {}
+
+
+def test_check_required_by_missing():
+ arguments_terms = {
+ "force": "force_reason",
+ }
+ params = {"force": True}
+ expected = "missing parameter(s) required by 'force': force_reason"
+
+ with pytest.raises(TypeError) as e:
+ check_required_by(arguments_terms, params)
+
+ assert to_native(e.value) == expected
+
+
+def test_check_required_by_multiple(path_arguments_terms):
+ params = {
+ "path": "/foo/bar",
+ }
+ expected = "missing parameter(s) required by 'path': mode, owner"
+
+ with pytest.raises(TypeError) as e:
+ check_required_by(path_arguments_terms, params)
+
+ assert to_native(e.value) == expected
+
+
+def test_check_required_by_single(path_arguments_terms):
+ params = {"path": "/foo/bar", "mode": "0700"}
+ expected = "missing parameter(s) required by 'path': owner"
+
+ with pytest.raises(TypeError) as e:
+ check_required_by(path_arguments_terms, params)
+
+ assert to_native(e.value) == expected
+
+
+def test_check_required_by_missing_none(path_arguments_terms):
+ params = {
+ "path": "/foo/bar",
+ "mode": "0700",
+ "owner": "root",
+ }
+ assert check_required_by(path_arguments_terms, params)
+
+
+def test_check_required_by_options_context(path_arguments_terms):
+ params = {"path": "/foo/bar", "mode": "0700"}
+
+ options_context = ["foo_context"]
+
+ expected = "missing parameter(s) required by 'path': owner found in foo_context"
+
+ with pytest.raises(TypeError) as e:
+ check_required_by(path_arguments_terms, params, options_context)
+
+ assert to_native(e.value) == expected
+
+
+def test_check_required_by_missing_multiple_options_context(path_arguments_terms):
+ params = {
+ "path": "/foo/bar",
+ }
+ options_context = ["foo_context"]
+
+ expected = (
+ "missing parameter(s) required by 'path': mode, owner found in foo_context"
+ )
+
+ with pytest.raises(TypeError) as e:
+ check_required_by(path_arguments_terms, params, options_context)
+
+ assert to_native(e.value) == expected
diff --git a/test/units/module_utils/common/validation/test_check_required_if.py b/test/units/module_utils/common/validation/test_check_required_if.py
new file mode 100644
index 0000000..4189164
--- /dev/null
+++ b/test/units/module_utils/common/validation/test_check_required_if.py
@@ -0,0 +1,79 @@
+# -*- coding: utf-8 -*-
+# Copyright: (c) 2021, Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+import pytest
+
+from ansible.module_utils._text import to_native
+from ansible.module_utils.common.validation import check_required_if
+
+
+def test_check_required_if():
+ arguments_terms = {}
+ params = {}
+ assert check_required_if(arguments_terms, params) == []
+
+
+def test_check_required_if_missing():
+ arguments_terms = [["state", "present", ("path",)]]
+ params = {"state": "present"}
+ expected = "state is present but all of the following are missing: path"
+
+ with pytest.raises(TypeError) as e:
+ check_required_if(arguments_terms, params)
+
+ assert to_native(e.value) == expected
+
+
+def test_check_required_if_missing_required():
+ arguments_terms = [["state", "present", ("path", "owner"), True]]
+ params = {"state": "present"}
+ expected = "state is present but any of the following are missing: path, owner"
+
+ with pytest.raises(TypeError) as e:
+ check_required_if(arguments_terms, params)
+
+ assert to_native(e.value) == expected
+
+
+def test_check_required_if_missing_multiple():
+ arguments_terms = [["state", "present", ("path", "owner")]]
+ params = {
+ "state": "present",
+ }
+ expected = "state is present but all of the following are missing: path, owner"
+
+ with pytest.raises(TypeError) as e:
+ check_required_if(arguments_terms, params)
+
+ assert to_native(e.value) == expected
+
+
+def test_check_required_if_missing_multiple_with_context():
+ arguments_terms = [["state", "present", ("path", "owner")]]
+ params = {
+ "state": "present",
+ }
+ options_context = ["foo_context"]
+ expected = "state is present but all of the following are missing: path, owner found in foo_context"
+
+ with pytest.raises(TypeError) as e:
+ check_required_if(arguments_terms, params, options_context)
+
+ assert to_native(e.value) == expected
+
+
+def test_check_required_if_multiple():
+ arguments_terms = [["state", "present", ("path", "owner")]]
+ params = {
+ "state": "present",
+ "path": "/foo",
+ "owner": "root",
+ }
+ options_context = ["foo_context"]
+ assert check_required_if(arguments_terms, params) == []
+ assert check_required_if(arguments_terms, params, options_context) == []
diff --git a/test/units/module_utils/common/validation/test_check_required_one_of.py b/test/units/module_utils/common/validation/test_check_required_one_of.py
new file mode 100644
index 0000000..b081889
--- /dev/null
+++ b/test/units/module_utils/common/validation/test_check_required_one_of.py
@@ -0,0 +1,47 @@
+# -*- coding: utf-8 -*-
+# Copyright: (c) 2021, Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+import pytest
+
+from ansible.module_utils._text import to_native
+from ansible.module_utils.common.validation import check_required_one_of
+
+
+@pytest.fixture
+def arguments_terms():
+ return [["path", "owner"]]
+
+
+def test_check_required_one_of():
+ assert check_required_one_of([], {}) == []
+
+
+def test_check_required_one_of_missing(arguments_terms):
+ params = {"state": "present"}
+ expected = "one of the following is required: path, owner"
+
+ with pytest.raises(TypeError) as e:
+ check_required_one_of(arguments_terms, params)
+
+ assert to_native(e.value) == expected
+
+
+def test_check_required_one_of_provided(arguments_terms):
+ params = {"state": "present", "path": "/foo"}
+ assert check_required_one_of(arguments_terms, params) == []
+
+
+def test_check_required_one_of_context(arguments_terms):
+ params = {"state": "present"}
+ expected = "one of the following is required: path, owner found in foo_context"
+ option_context = ["foo_context"]
+
+ with pytest.raises(TypeError) as e:
+ check_required_one_of(arguments_terms, params, option_context)
+
+ assert to_native(e.value) == expected
diff --git a/test/units/module_utils/common/validation/test_check_required_together.py b/test/units/module_utils/common/validation/test_check_required_together.py
new file mode 100644
index 0000000..8a2daab
--- /dev/null
+++ b/test/units/module_utils/common/validation/test_check_required_together.py
@@ -0,0 +1,57 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020 Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+import pytest
+
+from ansible.module_utils._text import to_native
+from ansible.module_utils.common.validation import check_required_together
+
+
+@pytest.fixture
+def together_terms():
+ return [
+ ['bananas', 'potatoes'],
+ ['cats', 'wolves']
+ ]
+
+
+def test_check_required_together(together_terms):
+ params = {
+ 'bananas': 'hello',
+ 'potatoes': 'this is here too',
+ 'dogs': 'haha',
+ }
+ assert check_required_together(together_terms, params) == []
+
+
+def test_check_required_together_missing(together_terms):
+ params = {
+ 'bananas': 'woohoo',
+ 'wolves': 'uh oh',
+ }
+ expected = "parameters are required together: bananas, potatoes"
+
+ with pytest.raises(TypeError) as e:
+ check_required_together(together_terms, params)
+
+ assert to_native(e.value) == expected
+
+
+def test_check_required_together_missing_none():
+ terms = None
+ params = {
+ 'foo': 'bar',
+ 'baz': 'buzz',
+ }
+ assert check_required_together(terms, params) == []
+
+
+def test_check_required_together_no_params(together_terms):
+ with pytest.raises(TypeError) as te:
+ check_required_together(together_terms, None)
+
+ assert "'NoneType' object is not iterable" in to_native(te.value)
diff --git a/test/units/module_utils/common/validation/test_check_type_bits.py b/test/units/module_utils/common/validation/test_check_type_bits.py
new file mode 100644
index 0000000..7f6b11d
--- /dev/null
+++ b/test/units/module_utils/common/validation/test_check_type_bits.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2019 Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+import pytest
+
+from ansible.module_utils._text import to_native
+from ansible.module_utils.common.validation import check_type_bits
+
+
+def test_check_type_bits():
+ test_cases = (
+ ('1', 1),
+ (99, 99),
+ (1.5, 2),
+ ('1.5', 2),
+ ('2b', 2),
+ ('2k', 2048),
+ ('2K', 2048),
+ ('1m', 1048576),
+ ('1M', 1048576),
+ ('1g', 1073741824),
+ ('1G', 1073741824),
+ (1073741824, 1073741824),
+ )
+ for case in test_cases:
+ assert case[1] == check_type_bits(case[0])
+
+
+def test_check_type_bits_fail():
+ test_cases = (
+ 'foo',
+ '2KB',
+ '1MB',
+ '1GB',
+ )
+ for case in test_cases:
+ with pytest.raises(TypeError) as e:
+ check_type_bits(case)
+ assert 'cannot be converted to a Bit value' in to_native(e.value)
diff --git a/test/units/module_utils/common/validation/test_check_type_bool.py b/test/units/module_utils/common/validation/test_check_type_bool.py
new file mode 100644
index 0000000..bd867dc
--- /dev/null
+++ b/test/units/module_utils/common/validation/test_check_type_bool.py
@@ -0,0 +1,49 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2019 Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+import pytest
+
+from ansible.module_utils._text import to_native
+from ansible.module_utils.common.validation import check_type_bool
+
+
+def test_check_type_bool():
+ test_cases = (
+ (True, True),
+ (False, False),
+ ('1', True),
+ ('on', True),
+ (1, True),
+ ('0', False),
+ (0, False),
+ ('n', False),
+ ('f', False),
+ ('false', False),
+ ('true', True),
+ ('y', True),
+ ('t', True),
+ ('yes', True),
+ ('no', False),
+ ('off', False),
+ )
+ for case in test_cases:
+ assert case[1] == check_type_bool(case[0])
+
+
+def test_check_type_bool_fail():
+ default_test_msg = 'cannot be converted to a bool'
+ test_cases = (
+ ({'k1': 'v1'}, 'is not a valid bool'),
+ (3.14159, default_test_msg),
+ (-1, default_test_msg),
+ (-90810398401982340981023948192349081, default_test_msg),
+ (90810398401982340981023948192349081, default_test_msg),
+ )
+ for case in test_cases:
+ with pytest.raises(TypeError) as e:
+ check_type_bool(case)
+ assert 'cannot be converted to a bool' in to_native(e.value)
diff --git a/test/units/module_utils/common/validation/test_check_type_bytes.py b/test/units/module_utils/common/validation/test_check_type_bytes.py
new file mode 100644
index 0000000..6ff62dc
--- /dev/null
+++ b/test/units/module_utils/common/validation/test_check_type_bytes.py
@@ -0,0 +1,50 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2019 Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+import pytest
+
+from ansible.module_utils._text import to_native
+from ansible.module_utils.common.validation import check_type_bytes
+
+
+def test_check_type_bytes():
+ test_cases = (
+ ('1', 1),
+ (99, 99),
+ (1.5, 2),
+ ('1.5', 2),
+ ('2b', 2),
+ ('2B', 2),
+ ('2k', 2048),
+ ('2K', 2048),
+ ('2KB', 2048),
+ ('1m', 1048576),
+ ('1M', 1048576),
+ ('1MB', 1048576),
+ ('1g', 1073741824),
+ ('1G', 1073741824),
+ ('1GB', 1073741824),
+ (1073741824, 1073741824),
+ )
+ for case in test_cases:
+ assert case[1] == check_type_bytes(case[0])
+
+
+def test_check_type_bytes_fail():
+ test_cases = (
+ 'foo',
+ '2kb',
+ '2Kb',
+ '1mb',
+ '1Mb',
+ '1gb',
+ '1Gb',
+ )
+ for case in test_cases:
+ with pytest.raises(TypeError) as e:
+ check_type_bytes(case)
+ assert 'cannot be converted to a Byte value' in to_native(e.value)
diff --git a/test/units/module_utils/common/validation/test_check_type_dict.py b/test/units/module_utils/common/validation/test_check_type_dict.py
new file mode 100644
index 0000000..75638c5
--- /dev/null
+++ b/test/units/module_utils/common/validation/test_check_type_dict.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2019 Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+import pytest
+
+from ansible.module_utils.common.validation import check_type_dict
+
+
+def test_check_type_dict():
+ test_cases = (
+ ({'k1': 'v1'}, {'k1': 'v1'}),
+ ('k1=v1,k2=v2', {'k1': 'v1', 'k2': 'v2'}),
+ ('k1=v1, k2=v2', {'k1': 'v1', 'k2': 'v2'}),
+ ('k1=v1, k2=v2, k3=v3', {'k1': 'v1', 'k2': 'v2', 'k3': 'v3'}),
+ ('{"key": "value", "list": ["one", "two"]}', {'key': 'value', 'list': ['one', 'two']})
+ )
+ for case in test_cases:
+ assert case[1] == check_type_dict(case[0])
+
+
+def test_check_type_dict_fail():
+ test_cases = (
+ 1,
+ 3.14159,
+ [1, 2],
+ 'a',
+ )
+ for case in test_cases:
+ with pytest.raises(TypeError):
+ check_type_dict(case)
diff --git a/test/units/module_utils/common/validation/test_check_type_float.py b/test/units/module_utils/common/validation/test_check_type_float.py
new file mode 100644
index 0000000..57837fa
--- /dev/null
+++ b/test/units/module_utils/common/validation/test_check_type_float.py
@@ -0,0 +1,38 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2019 Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+import pytest
+
+from ansible.module_utils._text import to_native
+from ansible.module_utils.common.validation import check_type_float
+
+
+def test_check_type_float():
+ test_cases = (
+ ('1.5', 1.5),
+ ('''1.5''', 1.5),
+ (u'1.5', 1.5),
+ (1002, 1002.0),
+ (1.0, 1.0),
+ (3.141592653589793, 3.141592653589793),
+ ('3.141592653589793', 3.141592653589793),
+ (b'3.141592653589793', 3.141592653589793),
+ )
+ for case in test_cases:
+ assert case[1] == check_type_float(case[0])
+
+
+def test_check_type_float_fail():
+ test_cases = (
+ {'k1': 'v1'},
+ ['a', 'b'],
+ 'b',
+ )
+ for case in test_cases:
+ with pytest.raises(TypeError) as e:
+ check_type_float(case)
+ assert 'cannot be converted to a float' in to_native(e.value)
diff --git a/test/units/module_utils/common/validation/test_check_type_int.py b/test/units/module_utils/common/validation/test_check_type_int.py
new file mode 100644
index 0000000..22cedf6
--- /dev/null
+++ b/test/units/module_utils/common/validation/test_check_type_int.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2019 Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+import pytest
+
+from ansible.module_utils._text import to_native
+from ansible.module_utils.common.validation import check_type_int
+
+
+def test_check_type_int():
+ test_cases = (
+ ('1', 1),
+ (u'1', 1),
+ (1002, 1002),
+ )
+ for case in test_cases:
+ assert case[1] == check_type_int(case[0])
+
+
+def test_check_type_int_fail():
+ test_cases = (
+ {'k1': 'v1'},
+ (b'1', 1),
+ (3.14159, 3),
+ 'b',
+ )
+ for case in test_cases:
+ with pytest.raises(TypeError) as e:
+ check_type_int(case)
+ assert 'cannot be converted to an int' in to_native(e.value)
diff --git a/test/units/module_utils/common/validation/test_check_type_jsonarg.py b/test/units/module_utils/common/validation/test_check_type_jsonarg.py
new file mode 100644
index 0000000..e78e54b
--- /dev/null
+++ b/test/units/module_utils/common/validation/test_check_type_jsonarg.py
@@ -0,0 +1,36 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2019 Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+import pytest
+
+from ansible.module_utils._text import to_native
+from ansible.module_utils.common.validation import check_type_jsonarg
+
+
+def test_check_type_jsonarg():
+ test_cases = (
+ ('a', 'a'),
+ ('a ', 'a'),
+ (b'99', b'99'),
+ (b'99 ', b'99'),
+ ({'k1': 'v1'}, '{"k1": "v1"}'),
+ ([1, 'a'], '[1, "a"]'),
+ ((1, 2, 'three'), '[1, 2, "three"]'),
+ )
+ for case in test_cases:
+ assert case[1] == check_type_jsonarg(case[0])
+
+
+def test_check_type_jsonarg_fail():
+ test_cases = (
+ 1.5,
+ 910313498012384012341982374109384098,
+ )
+ for case in test_cases:
+ with pytest.raises(TypeError) as e:
+ check_type_jsonarg(case)
+ assert 'cannot be converted to a json string' in to_native(e.value)
diff --git a/test/units/module_utils/common/validation/test_check_type_list.py b/test/units/module_utils/common/validation/test_check_type_list.py
new file mode 100644
index 0000000..3f7a9ee
--- /dev/null
+++ b/test/units/module_utils/common/validation/test_check_type_list.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2019 Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+import pytest
+
+from ansible.module_utils.common.validation import check_type_list
+
+
+def test_check_type_list():
+ test_cases = (
+ ([1, 2], [1, 2]),
+ (1, ['1']),
+ (['a', 'b'], ['a', 'b']),
+ ('a', ['a']),
+ (3.14159, ['3.14159']),
+ ('a,b,1,2', ['a', 'b', '1', '2'])
+ )
+ for case in test_cases:
+ assert case[1] == check_type_list(case[0])
+
+
+def test_check_type_list_failure():
+ test_cases = (
+ {'k1': 'v1'},
+ )
+ for case in test_cases:
+ with pytest.raises(TypeError):
+ check_type_list(case)
diff --git a/test/units/module_utils/common/validation/test_check_type_path.py b/test/units/module_utils/common/validation/test_check_type_path.py
new file mode 100644
index 0000000..d6ff433
--- /dev/null
+++ b/test/units/module_utils/common/validation/test_check_type_path.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2019 Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+import re
+
+import os
+from ansible.module_utils.common.validation import check_type_path
+
+
+def mock_expand(value):
+ return re.sub(r'~|\$HOME', '/home/testuser', value)
+
+
+def test_check_type_path(monkeypatch):
+ monkeypatch.setattr(os.path, 'expandvars', mock_expand)
+ monkeypatch.setattr(os.path, 'expanduser', mock_expand)
+ test_cases = (
+ ('~/foo', '/home/testuser/foo'),
+ ('$HOME/foo', '/home/testuser/foo'),
+ ('/home/jane', '/home/jane'),
+ (u'/home/jané', u'/home/jané'),
+ )
+ for case in test_cases:
+ assert case[1] == check_type_path(case[0])
diff --git a/test/units/module_utils/common/validation/test_check_type_raw.py b/test/units/module_utils/common/validation/test_check_type_raw.py
new file mode 100644
index 0000000..988e554
--- /dev/null
+++ b/test/units/module_utils/common/validation/test_check_type_raw.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2019 Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+from ansible.module_utils.common.validation import check_type_raw
+
+
+def test_check_type_raw():
+ test_cases = (
+ (1, 1),
+ ('1', '1'),
+ ('a', 'a'),
+ ({'k1': 'v1'}, {'k1': 'v1'}),
+ ([1, 2], [1, 2]),
+ (b'42', b'42'),
+ (u'42', u'42'),
+ )
+ for case in test_cases:
+ assert case[1] == check_type_raw(case[0])
diff --git a/test/units/module_utils/common/validation/test_check_type_str.py b/test/units/module_utils/common/validation/test_check_type_str.py
new file mode 100644
index 0000000..f10dad2
--- /dev/null
+++ b/test/units/module_utils/common/validation/test_check_type_str.py
@@ -0,0 +1,33 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2019 Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+import pytest
+
+from ansible.module_utils._text import to_native
+from ansible.module_utils.common.validation import check_type_str
+
+
+TEST_CASES = (
+ ('string', 'string'),
+ (100, '100'),
+ (1.5, '1.5'),
+ ({'k1': 'v1'}, "{'k1': 'v1'}"),
+ ([1, 2, 'three'], "[1, 2, 'three']"),
+ ((1, 2,), '(1, 2)'),
+)
+
+
+@pytest.mark.parametrize('value, expected', TEST_CASES)
+def test_check_type_str(value, expected):
+ assert expected == check_type_str(value)
+
+
+@pytest.mark.parametrize('value, expected', TEST_CASES[1:])
+def test_check_type_str_no_conversion(value, expected):
+ with pytest.raises(TypeError) as e:
+ check_type_str(value, allow_conversion=False)
+ assert 'is not a string and conversion is not allowed' in to_native(e.value)
diff --git a/test/units/module_utils/common/validation/test_count_terms.py b/test/units/module_utils/common/validation/test_count_terms.py
new file mode 100644
index 0000000..f41dc40
--- /dev/null
+++ b/test/units/module_utils/common/validation/test_count_terms.py
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2019 Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+import pytest
+
+from ansible.module_utils.common.validation import count_terms
+
+
+@pytest.fixture
+def params():
+ return {
+ 'name': 'bob',
+ 'dest': '/etc/hosts',
+ 'state': 'present',
+ 'value': 5,
+ }
+
+
+def test_count_terms(params):
+ check = set(('name', 'dest'))
+ assert count_terms(check, params) == 2
+
+
+def test_count_terms_str_input(params):
+ check = 'name'
+ assert count_terms(check, params) == 1
+
+
+def test_count_terms_tuple_input(params):
+ check = ('name', 'dest')
+ assert count_terms(check, params) == 2
+
+
+def test_count_terms_list_input(params):
+ check = ['name', 'dest']
+ assert count_terms(check, params) == 2
diff --git a/test/units/module_utils/common/warnings/test_deprecate.py b/test/units/module_utils/common/warnings/test_deprecate.py
new file mode 100644
index 0000000..08c1b35
--- /dev/null
+++ b/test/units/module_utils/common/warnings/test_deprecate.py
@@ -0,0 +1,101 @@
+# -*- coding: utf-8 -*-
+# (c) 2019 Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+import pytest
+
+from ansible.module_utils.common import warnings
+
+from ansible.module_utils.common.warnings import deprecate, get_deprecation_messages
+from ansible.module_utils.six import PY3
+
+
+@pytest.fixture
+def deprecation_messages():
+ return [
+ {'msg': 'First deprecation', 'version': None, 'collection_name': None},
+ {'msg': 'Second deprecation', 'version': None, 'collection_name': 'ansible.builtin'},
+ {'msg': 'Third deprecation', 'version': '2.14', 'collection_name': None},
+ {'msg': 'Fourth deprecation', 'version': '2.9', 'collection_name': None},
+ {'msg': 'Fifth deprecation', 'version': '2.9', 'collection_name': 'ansible.builtin'},
+ {'msg': 'Sixth deprecation', 'date': '2199-12-31', 'collection_name': None},
+ {'msg': 'Seventh deprecation', 'date': '2199-12-31', 'collection_name': 'ansible.builtin'},
+ ]
+
+
+@pytest.fixture
+def reset(monkeypatch):
+ monkeypatch.setattr(warnings, '_global_deprecations', [])
+
+
+def test_deprecate_message_only(reset):
+ deprecate('Deprecation message')
+ assert warnings._global_deprecations == [
+ {'msg': 'Deprecation message', 'version': None, 'collection_name': None}]
+
+
+def test_deprecate_with_collection(reset):
+ deprecate(msg='Deprecation message', collection_name='ansible.builtin')
+ assert warnings._global_deprecations == [
+ {'msg': 'Deprecation message', 'version': None, 'collection_name': 'ansible.builtin'}]
+
+
+def test_deprecate_with_version(reset):
+ deprecate(msg='Deprecation message', version='2.14')
+ assert warnings._global_deprecations == [
+ {'msg': 'Deprecation message', 'version': '2.14', 'collection_name': None}]
+
+
+def test_deprecate_with_version_and_collection(reset):
+ deprecate(msg='Deprecation message', version='2.14', collection_name='ansible.builtin')
+ assert warnings._global_deprecations == [
+ {'msg': 'Deprecation message', 'version': '2.14', 'collection_name': 'ansible.builtin'}]
+
+
+def test_deprecate_with_date(reset):
+ deprecate(msg='Deprecation message', date='2199-12-31')
+ assert warnings._global_deprecations == [
+ {'msg': 'Deprecation message', 'date': '2199-12-31', 'collection_name': None}]
+
+
+def test_deprecate_with_date_and_collection(reset):
+ deprecate(msg='Deprecation message', date='2199-12-31', collection_name='ansible.builtin')
+ assert warnings._global_deprecations == [
+ {'msg': 'Deprecation message', 'date': '2199-12-31', 'collection_name': 'ansible.builtin'}]
+
+
+def test_multiple_deprecations(deprecation_messages, reset):
+ for d in deprecation_messages:
+ deprecate(**d)
+
+ assert deprecation_messages == warnings._global_deprecations
+
+
+def test_get_deprecation_messages(deprecation_messages, reset):
+ for d in deprecation_messages:
+ deprecate(**d)
+
+ accessor_deprecations = get_deprecation_messages()
+ assert isinstance(accessor_deprecations, tuple)
+ assert len(accessor_deprecations) == 7
+
+
+@pytest.mark.parametrize(
+ 'test_case',
+ (
+ 1,
+ True,
+ [1],
+ {'k1': 'v1'},
+ (1, 2),
+ 6.62607004,
+ b'bytestr' if PY3 else None,
+ None,
+ )
+)
+def test_deprecate_failure(test_case):
+ with pytest.raises(TypeError, match='deprecate requires a string not a %s' % type(test_case)):
+ deprecate(test_case)
diff --git a/test/units/module_utils/common/warnings/test_warn.py b/test/units/module_utils/common/warnings/test_warn.py
new file mode 100644
index 0000000..41e1a7b
--- /dev/null
+++ b/test/units/module_utils/common/warnings/test_warn.py
@@ -0,0 +1,61 @@
+# -*- coding: utf-8 -*-
+# (c) 2019 Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+import pytest
+
+from ansible.module_utils.common import warnings
+
+from ansible.module_utils.common.warnings import warn, get_warning_messages
+from ansible.module_utils.six import PY3
+
+
+@pytest.fixture
+def warning_messages():
+ return [
+ 'First warning',
+ 'Second warning',
+ 'Third warning',
+ ]
+
+
+def test_warn():
+ warn('Warning message')
+ assert warnings._global_warnings == ['Warning message']
+
+
+def test_multiple_warningss(warning_messages):
+ for w in warning_messages:
+ warn(w)
+
+ assert warning_messages == warnings._global_warnings
+
+
+def test_get_warning_messages(warning_messages):
+ for w in warning_messages:
+ warn(w)
+
+ accessor_warnings = get_warning_messages()
+ assert isinstance(accessor_warnings, tuple)
+ assert len(accessor_warnings) == 3
+
+
+@pytest.mark.parametrize(
+ 'test_case',
+ (
+ 1,
+ True,
+ [1],
+ {'k1': 'v1'},
+ (1, 2),
+ 6.62607004,
+ b'bytestr' if PY3 else None,
+ None,
+ )
+)
+def test_warn_failure(test_case):
+ with pytest.raises(TypeError, match='warn requires a string not a %s' % type(test_case)):
+ warn(test_case)