diff options
Diffstat (limited to 'test/units/module_utils/common')
36 files changed, 2533 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 00000000..e69de29b --- /dev/null +++ b/test/units/module_utils/common/__init__.py 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 00000000..bc88437f --- /dev/null +++ b/test/units/module_utils/common/parameters/test_handle_aliases.py @@ -0,0 +1,102 @@ +# -*- 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 + +DEFAULT_LEGAL_INPUTS = [ + '_ansible_check_mode', + '_ansible_debug', + '_ansible_diff', + '_ansible_keep_remote_files', + '_ansible_module_name', + '_ansible_no_log', + '_ansible_remote_tmp', + '_ansible_selinux_special_fs', + '_ansible_shell_executable', + '_ansible_socket', + '_ansible_string_conversion_action', + '_ansible_syslog_facility', + '_ansible_tmpdir', + '_ansible_verbosity', + '_ansible_version', +] + + +def test_handle_aliases_no_aliases(): + argument_spec = { + 'name': {'type': 'str'}, + } + + params = { + 'name': 'foo', + 'path': 'bar' + } + + expected = ( + {}, + DEFAULT_LEGAL_INPUTS + ['name'], + ) + expected[1].sort() + + result = handle_aliases(argument_spec, params) + result[1].sort() + 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'}, + DEFAULT_LEGAL_INPUTS + ['name', 'surname', 'nick'], + ) + expected[1].sort() + + result = handle_aliases(argument_spec, params) + result[1].sort() + 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 00000000..0a17187c --- /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 00000000..1b740555 --- /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 00000000..a337e78d --- /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 00000000..95b2a402 --- /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 00000000..ecb520b2 --- /dev/null +++ b/test/units/module_utils/common/test_dict_transformations.py @@ -0,0 +1,135 @@ +# -*- coding: utf-8 -*- +# (c) 2017, Will Thames <will.thames@xvt.com.au> +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from units.compat import unittest +from ansible.module_utils.common.dict_transformations import _camel_to_snake, _snake_to_camel, camel_dict_to_snake_dict, dict_merge + +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 CamelToSnakeTestCase(unittest.TestCase): + + def test_camel_to_snake(self): + for (k, v) in EXPECTED_SNAKIFICATION.items(): + self.assertEqual(_camel_to_snake(k), v) + + def test_reversible_camel_to_snake(self): + for (k, v) in EXPECTED_REVERSIBLE.items(): + self.assertEqual(_camel_to_snake(k, reversible=True), v) + + +class SnakeToCamelTestCase(unittest.TestCase): + + def test_snake_to_camel_reversed(self): + for (k, v) in EXPECTED_REVERSIBLE.items(): + self.assertEqual(_snake_to_camel(v, capitalize_first=True), k) + + +class CamelToSnakeAndBackTestCase(unittest.TestCase): + def test_camel_to_snake_and_back(self): + for (k, v) in EXPECTED_REVERSIBLE.items(): + self.assertEqual(_snake_to_camel(_camel_to_snake(k, reversible=True), capitalize_first=True), k) + + +class CamelDictToSnakeDictTestCase(unittest.TestCase): + 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') + self.assertEqual(snake_dict['hello'], dict(one='one', two='two')) + self.assertEqual(snake_dict['world'], dict(Three='three', Four='four')) + + +class DictMergeTestCase(unittest.TestCase): + 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 + self.assertTrue('one' in result) + self.assertTrue('two' in result) + self.assertEqual(result['three'], 4) + self.assertEqual(result['four'], 4) + + # dict assertions + self.assertTrue('obj1' in result) + self.assertTrue('key1' in result['obj1']) + self.assertTrue('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 + self.assertEqual(result['l1'], [2, 1]) + self.assertTrue('l2' in result) + self.assertEqual(result['l3'], [1]) + self.assertTrue('l4' in result) + + # nested assertions + self.assertTrue('obj1' in result) + self.assertEqual(result['obj1']['key1'], 2) + self.assertTrue('key2' in result['obj1']) + + # bool assertions + self.assertTrue('b1' in result) + self.assertTrue('b2' in result) + self.assertTrue(result['b3']) + self.assertTrue(result['b4']) + + +class AzureIncidentalTestCase(unittest.TestCase): + + def test_dict_merge_invalid_dict(self): + ''' if b is not a dict, return b ''' + res = dict_merge({}, None) + self.assertEqual(res, 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) + self.assertEqual(res, 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 00000000..1267d0ce --- /dev/null +++ b/test/units/module_utils/common/test_network.py @@ -0,0 +1,68 @@ +# -*- 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_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') diff --git a/test/units/module_utils/common/test_removed.py b/test/units/module_utils/common/test_removed.py new file mode 100644 index 00000000..36c1c1e9 --- /dev/null +++ b/test/units/module_utils/common/test_removed.py @@ -0,0 +1,62 @@ +# -*- 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.removed import removed_module + + +@pytest.mark.parametrize('input_data', [u'2.8', 2.8, 2, '', ]) +def test_removed_module_sys_exit(input_data): + """Test for removed_module function, sys.exit().""" + + with pytest.raises(SystemExit) as wrapped_e: + removed_module(input_data) + + assert wrapped_e.type == SystemExit + assert wrapped_e.value.code == 1 + + +@pytest.mark.parametrize( + 'input_data, expected_msg, expected_warn', + [ + ( + u'2.8', + u'This module has been removed. ' + 'The module documentation for Ansible-2.7 may contain hints for porting', + u'', + ), + ( + 2.8, + u'This module has been removed. ' + 'The module documentation for Ansible-2.7 may contain hints for porting', + u'', + ), + ( + 2, + u'This module has been removed. ' + 'The module documentation for Ansible-1 may contain hints for porting', + u'', + ), + ( + u'café', + u'This module has been removed', + u'"warnings": ["removed modules should specify the version they were removed in"]', + ), + ( + 0.1, + u'This module has been removed. ' + 'The module documentation for Ansible-0.0 may contain hints for porting', + u'', + ), + ] +) +def test_removed_module_msgs(input_data, expected_msg, expected_warn, capsys): + """Test for removed_module function, content of output messages.""" + + captured = capsys.readouterr() + assert expected_msg, expected_warn in captured.out 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 00000000..cd68225d --- /dev/null +++ b/test/units/module_utils/common/test_sys_info.py @@ -0,0 +1,150 @@ +# -*- 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 +# + +def test_get_distribution_not_linux(): + """If it's not Linux, then it has no distribution""" + with patch('platform.system', return_value='Foo'): + assert get_distribution() is None + + +@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 +# + +def test_get_distribution_version_not_linux(): + """If it's not Linux, then it has no distribution""" + with patch('platform.system', return_value='Foo'): + assert get_distribution_version() is None + + +@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 00000000..ef952393 --- /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 00000000..091545e3 --- /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 00000000..39038f51 --- /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 00000000..8cf33529 --- /dev/null +++ b/test/units/module_utils/common/text/converters/test_json_encode_fallback.py @@ -0,0 +1,55 @@ +# -*- 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 + +from pytz import timezone as tz + +from ansible.module_utils.common.text.converters import _json_encode_fallback + + +@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=tz('UTC')), '2019-06-15T14:45:00+00:00'), + (datetime(2019, 6, 15, 14, 45, tzinfo=tz('Europe/Helsinki')), '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 00000000..a3415313 --- /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 00000000..b645db6d --- /dev/null +++ b/test/units/module_utils/common/text/converters/test_to_str.py @@ -0,0 +1,61 @@ +# -*- 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 +from ansible.utils.unsafe_proxy import AnsibleUnsafeBytes, AnsibleUnsafeText + + +# 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 + + +def test_to_text_unsafe(): + assert isinstance(to_text(AnsibleUnsafeBytes(b'foo')), AnsibleUnsafeText) + assert to_text(AnsibleUnsafeBytes(b'foo')) == AnsibleUnsafeText(u'foo') + + +def test_to_bytes_unsafe(): + assert isinstance(to_bytes(AnsibleUnsafeText(u'foo')), AnsibleUnsafeBytes) + assert to_bytes(AnsibleUnsafeText(u'foo')) == AnsibleUnsafeBytes(b'foo') 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 00000000..41475f56 --- /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 00000000..d02699a6 --- /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 00000000..1ecc013e --- /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_mutually_exclusive.py b/test/units/module_utils/common/validation/test_check_mutually_exclusive.py new file mode 100644 index 00000000..7bf90760 --- /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 00000000..1dd54584 --- /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_together.py b/test/units/module_utils/common/validation/test_check_required_together.py new file mode 100644 index 00000000..8a2daab1 --- /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 00000000..7f6b11d3 --- /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 00000000..bd867dc9 --- /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 00000000..6ff62dc2 --- /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 00000000..75638c58 --- /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 00000000..57837fae --- /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 00000000..22cedf61 --- /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 00000000..e78e54bb --- /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 00000000..3f7a9ee6 --- /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 00000000..d6ff433a --- /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 00000000..988e5543 --- /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 00000000..f10dad28 --- /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 00000000..f41dc40d --- /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 00000000..42046bfe --- /dev/null +++ b/test/units/module_utils/common/warnings/test_deprecate.py @@ -0,0 +1,96 @@ +# -*- 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 + +import ansible.module_utils.common.warnings as 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'}, + ] + + +def test_deprecate_message_only(): + deprecate('Deprecation message') + assert warnings._global_deprecations == [ + {'msg': 'Deprecation message', 'version': None, 'collection_name': None}] + + +def test_deprecate_with_collection(): + 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(): + 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(): + 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(): + 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(): + 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): + for d in deprecation_messages: + deprecate(**d) + + assert deprecation_messages == warnings._global_deprecations + + +def test_get_deprecation_messages(deprecation_messages): + 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 00000000..020b0625 --- /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 + +import ansible.module_utils.common.warnings as 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) |