Move type checking methods out of basic.py and add unit tests (#53687)

* Move check_type_str() out of basic.py

* Move check_type_list() out of basic.py

* Move safe_eval() out of basic.py

* Move check_type_dict() out of basic.py

* Move json importing code to common location

* Move check_type_bool() out of basic.py

* Move _check_type_int() out of basic.py

* Move _check_type_float() out of basic.py

* Move _check_type_path() out of basic.py

* Move _check_type_raw() out of basic.py

* Move _check_type_bytes() out of basic.py

* Move _check_type_bits() out of basic.py

* Create text.formatters.py

Move human_to_bytes, bytes_to_human, and _lenient_lowercase out of basic.py into text.formatters.py
Change references in modules to point to function at new location

* Move _check_type_jsonarg() out of basic.py

* Rename json related functions and put them in common.text.converters

Move formatters.py to common.text.formatters.py and update references in modules.

* Rework check_type_str()

Add allow_conversion option to make the function more self-contained.
Move the messaging back to basic.py since those error messages are more relevant to using this function in the context of AnsibleModule and not when using the function in isolation.

* Add unit tests for type checking functions

* Change _lenient_lowercase to lenient_lowercase per feedback
This commit is contained in:
Sam Doran
2019-03-21 09:40:19 -04:00
committed by GitHub
parent bb61d7527f
commit ff88bd82b5
26 changed files with 957 additions and 326 deletions

View File

@@ -69,9 +69,10 @@ VALID_SPECS = (
INVALID_SPECS = (
# Type is int; unable to convert this string
({'arg': {'type': 'int'}}, {'arg': "bad"}, "invalid literal for int() with base 10: 'bad'"),
({'arg': {'type': 'int'}}, {'arg': "wolf"}, "is of type {0} and we were unable to convert to int: {0} cannot be converted to an int".format(type('bad'))),
# Type is list elements is int; unable to convert this string
({'arg': {'type': 'list', 'elements': 'int'}}, {'arg': [1, "bad"]}, "invalid literal for int() with base 10: 'bad'"),
({'arg': {'type': 'list', 'elements': 'int'}}, {'arg': [1, "bad"]}, "is of type {0} and we were unable to convert to int: {0} cannot be converted to "
"an int".format(type('int'))),
# Type is int; unable to convert float
({'arg': {'type': 'int'}}, {'arg': 42.1}, "'float'> cannot be converted to an int"),
# Type is list, elements is int; unable to convert float

View File

@@ -64,13 +64,12 @@ class TestImports(ModuleTestCase):
@patch.object(builtins, '__import__')
def test_module_utils_basic_import_json(self, mock_import):
def _mock_import(name, *args, **kwargs):
if name == 'json':
if name == 'ansible.module_utils.common._json_compat':
raise ImportError
return realimport(name, *args, **kwargs)
self.clear_modules(['json', 'ansible.module_utils.basic'])
builtins.__import__('ansible.module_utils.basic')
self.clear_modules(['json', 'ansible.module_utils.basic'])
mock_import.side_effect = _mock_import
with self.assertRaises(SystemExit):

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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])

View File

@@ -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])

View File

@@ -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)