mirror of https://github.com/ansible/ansible.git
Porting tests to pytest (#33387)
* Porting tests to pytest * Achievement Get: No longer need mock/generator.py * Now done via pytest's parametrization * Port safe_eval to pytest * Port text tests to pytest * Port test_set_mode_if_different to pytest * Change conftest AnsibleModule fixtures to be more flexible * Move the AnsibleModules fixtures to module_utils/conftest.py for sharing * Testing the argspec code requires: * injecting both the argspec and the arguments. * Patching the arguments into sys.stdin at a different level * More porting to obsolete mock/procenv.py * Port run_command to pytest * Port known_hosts tests to pytest * Port safe_eval to pytest * Port test_distribution_version.py to pytest * Port test_log to pytest * Port test__log_invocation to pytest * Remove unneeded import of procenv in test_postgresql * Port test_pip to pytest style * As part of this, create a pytest ansiblemodule fixture in modules/conftest.py. This is slightly different than the approach taken in module_utils because here we need to override the AnsibleModule that the modules will inherit from instead of one that we're instantiating ourselves. * Fixup usage of parametrization in test_deprecate_warn * Check that the pip module failed in our testpull/33698/head
parent
ed376abe42
commit
cd36164239
@ -1,72 +0,0 @@
|
|||||||
# Copyright 2016 Toshio Kuratomi <tkuratomi@ansible.com>
|
|
||||||
#
|
|
||||||
# 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/>.
|
|
||||||
|
|
||||||
# Make coding more python3-ish
|
|
||||||
from __future__ import (absolute_import, division, print_function)
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
from collections import Mapping
|
|
||||||
|
|
||||||
|
|
||||||
def make_method(func, args, kwargs):
|
|
||||||
|
|
||||||
def test_method(self):
|
|
||||||
func(self, *args, **kwargs)
|
|
||||||
|
|
||||||
# Format the argument string
|
|
||||||
arg_string = ', '.join(repr(a) for a in args)
|
|
||||||
kwarg_string = ', '.join('{0}={1}'.format(item[0], repr(item[1])) for item in kwargs.items())
|
|
||||||
arg_list = []
|
|
||||||
if arg_string:
|
|
||||||
arg_list.append(arg_string)
|
|
||||||
if kwarg_string:
|
|
||||||
arg_list.append(kwarg_string)
|
|
||||||
|
|
||||||
test_method.__name__ = 'test_{0}({1})'.format(func.__name__, ', '.join(arg_list))
|
|
||||||
return test_method
|
|
||||||
|
|
||||||
|
|
||||||
def add_method(func, *combined_args):
|
|
||||||
"""
|
|
||||||
Add a test case via a class decorator.
|
|
||||||
|
|
||||||
nose uses generators for this but doesn't work with unittest.TestCase
|
|
||||||
subclasses. So we have to write our own.
|
|
||||||
|
|
||||||
The first argument to this decorator is a test function. All subsequent
|
|
||||||
arguments are the arguments to create each generated test function with in
|
|
||||||
the following format:
|
|
||||||
|
|
||||||
Each set of arguments is a two-tuple. The first element is an iterable of
|
|
||||||
positional arguments. the second is a dict representing the kwargs.
|
|
||||||
"""
|
|
||||||
def wrapper(cls):
|
|
||||||
for combined_arg in combined_args:
|
|
||||||
if len(combined_arg) == 2:
|
|
||||||
args = combined_arg[0]
|
|
||||||
kwargs = combined_arg[1]
|
|
||||||
elif isinstance(combined_arg[0], Mapping):
|
|
||||||
args = []
|
|
||||||
kwargs = combined_arg[0]
|
|
||||||
else:
|
|
||||||
args = combined_arg[0]
|
|
||||||
kwargs = {}
|
|
||||||
test_method = make_method(func, args, kwargs)
|
|
||||||
setattr(cls, test_method.__name__, test_method)
|
|
||||||
return cls
|
|
||||||
|
|
||||||
return wrapper
|
|
@ -1,84 +1,55 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# (c) 2016, James Cammarata <jimi@sngx.net>
|
# (c) 2016, James Cammarata <jimi@sngx.net>
|
||||||
#
|
# (c) 2017, Ansible Project
|
||||||
# This file is part of Ansible
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
#
|
|
||||||
# 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/>.
|
|
||||||
|
|
||||||
# Make coding more python3-ish
|
from __future__ import absolute_import, division, print_function
|
||||||
from __future__ import (absolute_import, division)
|
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
import json
|
import pytest
|
||||||
import sys
|
|
||||||
|
|
||||||
from units.mock.procenv import swap_stdin_and_argv
|
ARGS = dict(foo=False, bar=[1, 2, 3], bam="bam", baz=u'baz')
|
||||||
|
ARGUMENT_SPEC = dict(
|
||||||
from ansible.compat.tests import unittest
|
foo=dict(default=True, type='bool'),
|
||||||
from ansible.compat.tests.mock import MagicMock
|
bar=dict(default=[], type='list'),
|
||||||
|
bam=dict(default="bam"),
|
||||||
|
baz=dict(default=u"baz"),
|
||||||
class TestModuleUtilsBasic(unittest.TestCase):
|
password=dict(default=True),
|
||||||
def test_module_utils_basic__log_invocation(self):
|
no_log=dict(default="you shouldn't see me", no_log=True),
|
||||||
with swap_stdin_and_argv(stdin_data=json.dumps(dict(
|
)
|
||||||
ANSIBLE_MODULE_ARGS=dict(foo=False, bar=[1, 2, 3], bam="bam", baz=u'baz')),
|
|
||||||
)):
|
|
||||||
from ansible.module_utils import basic
|
@pytest.mark.parametrize('am, stdin', [(ARGUMENT_SPEC, ARGS)], indirect=['am', 'stdin'])
|
||||||
|
def test_module_utils_basic__log_invocation(am, mocker):
|
||||||
# test basic log invocation
|
|
||||||
basic._ANSIBLE_ARGS = None
|
am.log = mocker.MagicMock()
|
||||||
am = basic.AnsibleModule(
|
am._log_invocation()
|
||||||
argument_spec=dict(
|
|
||||||
foo=dict(default=True, type='bool'),
|
# Message is generated from a dict so it will be in an unknown order.
|
||||||
bar=dict(default=[], type='list'),
|
# have to check this manually rather than with assert_called_with()
|
||||||
bam=dict(default="bam"),
|
args = am.log.call_args[0]
|
||||||
baz=dict(default=u"baz"),
|
assert len(args) == 1
|
||||||
password=dict(default=True),
|
message = args[0]
|
||||||
no_log=dict(default="you shouldn't see me", no_log=True),
|
|
||||||
),
|
assert len(message) == \
|
||||||
)
|
len('Invoked with bam=bam bar=[1, 2, 3] foo=False baz=baz no_log=NOT_LOGGING_PARAMETER password=NOT_LOGGING_PASSWORD')
|
||||||
|
|
||||||
am.log = MagicMock()
|
assert message.startswith('Invoked with ')
|
||||||
am._log_invocation()
|
assert ' bam=bam' in message
|
||||||
|
assert ' bar=[1, 2, 3]' in message
|
||||||
# Message is generated from a dict so it will be in an unknown order.
|
assert ' foo=False' in message
|
||||||
# have to check this manually rather than with assert_called_with()
|
assert ' baz=baz' in message
|
||||||
args = am.log.call_args[0]
|
assert ' no_log=NOT_LOGGING_PARAMETER' in message
|
||||||
self.assertEqual(len(args), 1)
|
assert ' password=NOT_LOGGING_PASSWORD' in message
|
||||||
message = args[0]
|
|
||||||
|
kwargs = am.log.call_args[1]
|
||||||
self.assertEqual(
|
assert kwargs == \
|
||||||
len(message),
|
dict(log_args={
|
||||||
len('Invoked with bam=bam bar=[1, 2, 3] foo=False baz=baz no_log=NOT_LOGGING_PARAMETER password=NOT_LOGGING_PASSWORD')
|
'foo': 'False',
|
||||||
)
|
'bar': '[1, 2, 3]',
|
||||||
self.assertTrue(message.startswith('Invoked with '))
|
'bam': 'bam',
|
||||||
self.assertIn(' bam=bam', message)
|
'baz': 'baz',
|
||||||
self.assertIn(' bar=[1, 2, 3]', message)
|
'password': 'NOT_LOGGING_PASSWORD',
|
||||||
self.assertIn(' foo=False', message)
|
'no_log': 'NOT_LOGGING_PARAMETER',
|
||||||
self.assertIn(' baz=baz', message)
|
})
|
||||||
self.assertIn(' no_log=NOT_LOGGING_PARAMETER', message)
|
|
||||||
self.assertIn(' password=NOT_LOGGING_PASSWORD', message)
|
|
||||||
|
|
||||||
kwargs = am.log.call_args[1]
|
|
||||||
self.assertEqual(
|
|
||||||
kwargs,
|
|
||||||
dict(log_args={
|
|
||||||
'foo': 'False',
|
|
||||||
'bar': '[1, 2, 3]',
|
|
||||||
'bam': 'bam',
|
|
||||||
'baz': 'baz',
|
|
||||||
'password': 'NOT_LOGGING_PASSWORD',
|
|
||||||
'no_log': 'NOT_LOGGING_PARAMETER',
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
@ -1,99 +1,70 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# (c) 2015-2016, Toshio Kuratomi <tkuratomi@ansible.com>
|
# (c) 2015-2017, Toshio Kuratomi <tkuratomi@ansible.com>
|
||||||
#
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
# 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/>.
|
|
||||||
|
|
||||||
# Make coding more python3-ish
|
# Make coding more python3-ish
|
||||||
from __future__ import (absolute_import, division)
|
from __future__ import (absolute_import, division)
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
import sys
|
from itertools import chain
|
||||||
import json
|
import pytest
|
||||||
|
|
||||||
from ansible.compat.tests import unittest
|
|
||||||
from units.mock.procenv import ModuleTestCase
|
|
||||||
from units.mock.generator import add_method
|
|
||||||
|
|
||||||
|
|
||||||
# Strings that should be converted into a typed value
|
# Strings that should be converted into a typed value
|
||||||
VALID_STRINGS = (
|
VALID_STRINGS = (
|
||||||
[("'a'", 'a')],
|
("'a'", 'a'),
|
||||||
[("'1'", '1')],
|
("'1'", '1'),
|
||||||
[("1", 1)],
|
("1", 1),
|
||||||
[("True", True)],
|
("True", True),
|
||||||
[("False", False)],
|
("False", False),
|
||||||
[("{}", {})],
|
("{}", {}),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Passing things that aren't strings should just return the object
|
# Passing things that aren't strings should just return the object
|
||||||
NONSTRINGS = (
|
NONSTRINGS = (
|
||||||
[({'a': 1}, {'a': 1})],
|
({'a': 1}, {'a': 1}),
|
||||||
)
|
)
|
||||||
|
|
||||||
# These strings are not basic types. For security, these should not be
|
# These strings are not basic types. For security, these should not be
|
||||||
# executed. We return the same string and get an exception for some
|
# executed. We return the same string and get an exception for some
|
||||||
INVALID_STRINGS = (
|
INVALID_STRINGS = (
|
||||||
[("a=1", "a=1", SyntaxError)],
|
("a=1", "a=1", SyntaxError),
|
||||||
[("a.foo()", "a.foo()", None)],
|
("a.foo()", "a.foo()", None),
|
||||||
[("import foo", "import foo", None)],
|
("import foo", "import foo", None),
|
||||||
[("__import__('foo')", "__import__('foo')", ValueError)],
|
("__import__('foo')", "__import__('foo')", ValueError),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _check_simple_types(self, code, expected):
|
@pytest.mark.parametrize('code, expected, stdin',
|
||||||
|
((c, e, {}) for c, e in chain(VALID_STRINGS, NONSTRINGS)),
|
||||||
|
indirect=['stdin'])
|
||||||
|
def test_simple_types(am, code, expected):
|
||||||
# test some basic usage for various types
|
# test some basic usage for various types
|
||||||
self.assertEqual(self.am.safe_eval(code), expected)
|
assert am.safe_eval(code) == expected
|
||||||
|
|
||||||
|
|
||||||
def _check_simple_types_with_exceptions(self, code, expected):
|
@pytest.mark.parametrize('code, expected, stdin',
|
||||||
|
((c, e, {}) for c, e in chain(VALID_STRINGS, NONSTRINGS)),
|
||||||
|
indirect=['stdin'])
|
||||||
|
def test_simple_types_with_exceptions(am, code, expected):
|
||||||
# Test simple types with exceptions requested
|
# Test simple types with exceptions requested
|
||||||
self.assertEqual(self.am.safe_eval(code, include_exceptions=True), (expected, None))
|
assert am.safe_eval(code, include_exceptions=True), (expected, None)
|
||||||
|
|
||||||
|
|
||||||
def _check_invalid_strings(self, code, expected):
|
@pytest.mark.parametrize('code, expected, stdin',
|
||||||
self.assertEqual(self.am.safe_eval(code), expected)
|
((c, e, {}) for c, e, dummy in INVALID_STRINGS),
|
||||||
|
indirect=['stdin'])
|
||||||
|
def test_invalid_strings(am, code, expected):
|
||||||
def _check_invalid_strings_with_exceptions(self, code, expected, exception):
|
assert am.safe_eval(code) == expected
|
||||||
res = self.am.safe_eval("a=1", include_exceptions=True)
|
|
||||||
self.assertEqual(res[0], "a=1")
|
|
||||||
self.assertEqual(type(res[1]), SyntaxError)
|
@pytest.mark.parametrize('code, expected, exception, stdin',
|
||||||
|
((c, e, ex, {}) for c, e, ex in INVALID_STRINGS),
|
||||||
|
indirect=['stdin'])
|
||||||
@add_method(_check_simple_types, *VALID_STRINGS)
|
def test_invalid_strings_with_exceptions(am, code, expected, exception):
|
||||||
@add_method(_check_simple_types, *NONSTRINGS)
|
res = am.safe_eval(code, include_exceptions=True)
|
||||||
@add_method(_check_simple_types_with_exceptions, *VALID_STRINGS)
|
assert res[0] == expected
|
||||||
@add_method(_check_simple_types_with_exceptions, *NONSTRINGS)
|
if exception is None:
|
||||||
@add_method(_check_invalid_strings, *[[i[0][0:-1]] for i in INVALID_STRINGS])
|
assert res[1] == exception
|
||||||
@add_method(_check_invalid_strings_with_exceptions, *INVALID_STRINGS)
|
else:
|
||||||
class TestSafeEval(ModuleTestCase):
|
assert type(res[1]) == exception
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestSafeEval, self).setUp()
|
|
||||||
|
|
||||||
from ansible.module_utils import basic
|
|
||||||
self.old_ansible_args = basic._ANSIBLE_ARGS
|
|
||||||
|
|
||||||
basic._ANSIBLE_ARGS = None
|
|
||||||
self.am = basic.AnsibleModule(
|
|
||||||
argument_spec=dict(),
|
|
||||||
)
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
super(TestSafeEval, self).tearDown()
|
|
||||||
|
|
||||||
from ansible.module_utils import basic
|
|
||||||
basic._ANSIBLE_ARGS = self.old_ansible_args
|
|
||||||
|
@ -1,144 +1,147 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# (c) 2016, Toshio Kuratomi <tkuratomi@ansible.com>
|
# (c) 2016, Toshio Kuratomi <tkuratomi@ansible.com>
|
||||||
#
|
# Copyright (c) 2017 Ansible Project
|
||||||
# This file is part of Ansible
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
#
|
|
||||||
# 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/>.
|
|
||||||
|
|
||||||
# Make coding more python3-ish
|
# Make coding more python3-ish
|
||||||
from __future__ import (absolute_import, division, print_function)
|
from __future__ import (absolute_import, division, print_function)
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import pytest
|
from itertools import product
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import builtins
|
import builtins
|
||||||
except ImportError:
|
except ImportError:
|
||||||
import __builtin__ as builtins
|
import __builtin__ as builtins
|
||||||
|
|
||||||
from ansible.compat.tests import unittest
|
import pytest
|
||||||
from ansible.compat.tests.mock import patch, MagicMock
|
|
||||||
from ansible.module_utils import known_hosts
|
|
||||||
|
|
||||||
from units.mock.procenv import ModuleTestCase
|
|
||||||
from units.mock.generator import add_method
|
|
||||||
|
|
||||||
|
|
||||||
class TestSetModeIfDifferentBase(ModuleTestCase):
|
SYNONYMS_0660 = (
|
||||||
|
0o660,
|
||||||
|
'0o660',
|
||||||
|
'660',
|
||||||
|
'u+rw-x,g+rw-x,o-rwx',
|
||||||
|
'u=rw,g=rw,o-rwx',
|
||||||
|
)
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
self.mock_stat1 = MagicMock()
|
|
||||||
self.mock_stat1.st_mode = 0o444
|
|
||||||
self.mock_stat2 = MagicMock()
|
|
||||||
self.mock_stat2.st_mode = 0o660
|
|
||||||
|
|
||||||
super(TestSetModeIfDifferentBase, self).setUp()
|
@pytest.fixture
|
||||||
from ansible.module_utils import basic
|
def mock_stats(mocker):
|
||||||
self.old_ANSIBLE_ARGS = basic._ANSIBLE_ARGS
|
mock_stat1 = mocker.MagicMock()
|
||||||
basic._ANSIBLE_ARGS = None
|
mock_stat1.st_mode = 0o444
|
||||||
|
mock_stat2 = mocker.MagicMock()
|
||||||
|
mock_stat2.st_mode = 0o660
|
||||||
|
yield {"before": mock_stat1, "after": mock_stat2}
|
||||||
|
|
||||||
self.am = basic.AnsibleModule(
|
|
||||||
argument_spec=dict(),
|
|
||||||
)
|
|
||||||
|
|
||||||
def tearDown(self):
|
@pytest.fixture
|
||||||
super(TestSetModeIfDifferentBase, self).tearDown()
|
def am_check_mode(am):
|
||||||
from ansible.module_utils import basic
|
am.check_mode = True
|
||||||
basic._ANSIBLE_ARGS = self.old_ANSIBLE_ARGS
|
yield am
|
||||||
|
am.check_mode = False
|
||||||
|
|
||||||
|
|
||||||
def _check_no_mode_given_returns_previous_changes(self, previous_changes=True):
|
@pytest.fixture
|
||||||
with patch('os.lstat', side_effect=[self.mock_stat1]):
|
def mock_lchmod(mocker):
|
||||||
|
m_lchmod = mocker.patch('ansible.module_utils.basic.os.lchmod', return_value=None, create=True)
|
||||||
|
yield m_lchmod
|
||||||
|
|
||||||
self.assertEqual(self.am.set_mode_if_different('/path/to/file', None, previous_changes), previous_changes)
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('previous_changes, check_mode, stdin',
|
||||||
|
product((True, False), (True, False), ({},)),
|
||||||
|
indirect=['stdin'])
|
||||||
|
def test_no_mode_given_returns_previous_changes(am, mock_stats, mock_lchmod, mocker, previous_changes, check_mode):
|
||||||
|
am.check_mode = check_mode
|
||||||
|
mocker.patch('os.lstat', side_effect=[mock_stats['before']])
|
||||||
|
m_lchmod = mocker.patch('os.lchmod', return_value=None, create=True)
|
||||||
|
|
||||||
def _check_mode_changed_to_0660(self, mode):
|
assert am.set_mode_if_different('/path/to/file', None, previous_changes) == previous_changes
|
||||||
# Note: This is for checking that all the different ways of specifying
|
assert not m_lchmod.called
|
||||||
# 0660 mode work. It cannot be used to check that setting a mode that is
|
|
||||||
# not equivalent to 0660 works.
|
|
||||||
with patch('os.lstat', side_effect=[self.mock_stat1, self.mock_stat2, self.mock_stat2]) as m_lstat:
|
|
||||||
with patch('os.lchmod', return_value=None, create=True) as m_lchmod:
|
|
||||||
self.assertEqual(self.am.set_mode_if_different('/path/to/file', mode, False), True)
|
|
||||||
m_lchmod.assert_called_with(b'/path/to/file', 0o660)
|
|
||||||
|
|
||||||
|
|
||||||
def _check_mode_unchanged_when_already_0660(self, mode):
|
@pytest.mark.parametrize('mode, check_mode, stdin',
|
||||||
|
product(SYNONYMS_0660, (True, False), ({},)),
|
||||||
|
indirect=['stdin'])
|
||||||
|
def test_mode_changed_to_0660(am, mock_stats, mocker, mode, check_mode):
|
||||||
# Note: This is for checking that all the different ways of specifying
|
# Note: This is for checking that all the different ways of specifying
|
||||||
# 0660 mode work. It cannot be used to check that setting a mode that is
|
# 0660 mode work. It cannot be used to check that setting a mode that is
|
||||||
# not equivalent to 0660 works.
|
# not equivalent to 0660 works.
|
||||||
with patch('os.lstat', side_effect=[self.mock_stat2, self.mock_stat2, self.mock_stat2]) as m_lstat:
|
am.check_mode = check_mode
|
||||||
self.assertEqual(self.am.set_mode_if_different('/path/to/file', mode, False), False)
|
mocker.patch('os.lstat', side_effect=[mock_stats['before'], mock_stats['after'], mock_stats['after']])
|
||||||
|
m_lchmod = mocker.patch('os.lchmod', return_value=None, create=True)
|
||||||
|
|
||||||
SYNONYMS_0660 = (
|
assert am.set_mode_if_different('/path/to/file', mode, False)
|
||||||
[[0o660]],
|
if check_mode:
|
||||||
[['0o660']],
|
assert not m_lchmod.called
|
||||||
[['660']],
|
else:
|
||||||
)
|
m_lchmod.assert_called_with(b'/path/to/file', 0o660)
|
||||||
|
|
||||||
|
|
||||||
@add_method(_check_no_mode_given_returns_previous_changes, [dict(previous_changes=True)], [dict(previous_changes=False)], )
|
@pytest.mark.parametrize('mode, check_mode, stdin',
|
||||||
@add_method(_check_mode_changed_to_0660, *SYNONYMS_0660)
|
product(SYNONYMS_0660, (True, False), ({},)),
|
||||||
@add_method(_check_mode_unchanged_when_already_0660, *SYNONYMS_0660)
|
indirect=['stdin'])
|
||||||
class TestSetModeIfDifferent(TestSetModeIfDifferentBase):
|
def test_mode_unchanged_when_already_0660(am, mock_stats, mocker, mode, check_mode):
|
||||||
def test_module_utils_basic_ansible_module_set_mode_if_different(self):
|
|
||||||
with patch('os.lstat') as m:
|
|
||||||
with patch('os.lchmod', return_value=None, create=True) as m_os:
|
|
||||||
m.side_effect = [self.mock_stat1, self.mock_stat2, self.mock_stat2]
|
|
||||||
self.am._symbolic_mode_to_octal = MagicMock(side_effect=Exception)
|
|
||||||
with pytest.raises(SystemExit):
|
|
||||||
self.am.set_mode_if_different('/path/to/file', 'o+w,g+w,a-r', False)
|
|
||||||
|
|
||||||
original_hasattr = hasattr
|
|
||||||
|
|
||||||
def _hasattr(obj, name):
|
|
||||||
if obj == os and name == 'lchmod':
|
|
||||||
return False
|
|
||||||
return original_hasattr(obj, name)
|
|
||||||
|
|
||||||
# FIXME: this isn't working yet
|
|
||||||
with patch('os.lstat', side_effect=[self.mock_stat1, self.mock_stat2]):
|
|
||||||
with patch.object(builtins, 'hasattr', side_effect=_hasattr):
|
|
||||||
with patch('os.path.islink', return_value=False):
|
|
||||||
with patch('os.chmod', return_value=None) as m_chmod:
|
|
||||||
self.assertEqual(self.am.set_mode_if_different('/path/to/file/no_lchmod', 0o660, False), True)
|
|
||||||
with patch('os.lstat', side_effect=[self.mock_stat1, self.mock_stat2]):
|
|
||||||
with patch.object(builtins, 'hasattr', side_effect=_hasattr):
|
|
||||||
with patch('os.path.islink', return_value=True):
|
|
||||||
with patch('os.chmod', return_value=None) as m_chmod:
|
|
||||||
with patch('os.stat', return_value=self.mock_stat2):
|
|
||||||
self.assertEqual(self.am.set_mode_if_different('/path/to/file', 0o660, False), True)
|
|
||||||
|
|
||||||
|
|
||||||
def _check_knows_to_change_to_0660_in_check_mode(self, mode):
|
|
||||||
# Note: This is for checking that all the different ways of specifying
|
# Note: This is for checking that all the different ways of specifying
|
||||||
# 0660 mode work. It cannot be used to check that setting a mode that is
|
# 0660 mode work. It cannot be used to check that setting a mode that is
|
||||||
# not equivalent to 0660 works.
|
# not equivalent to 0660 works.
|
||||||
with patch('os.lstat', side_effect=[self.mock_stat1, self.mock_stat2, self.mock_stat2]) as m_lstat:
|
am.check_mode = check_mode
|
||||||
self.assertEqual(self.am.set_mode_if_different('/path/to/file', mode, False), True)
|
mocker.patch('os.lstat', side_effect=[mock_stats['after'], mock_stats['after'], mock_stats['after']])
|
||||||
|
m_lchmod = mocker.patch('os.lchmod', return_value=None, create=True)
|
||||||
|
|
||||||
@add_method(_check_no_mode_given_returns_previous_changes, [dict(previous_changes=True)], [dict(previous_changes=False)],)
|
assert not am.set_mode_if_different('/path/to/file', mode, False)
|
||||||
@add_method(_check_knows_to_change_to_0660_in_check_mode, *SYNONYMS_0660)
|
assert not m_lchmod.called
|
||||||
@add_method(_check_mode_unchanged_when_already_0660, *SYNONYMS_0660)
|
|
||||||
class TestSetModeIfDifferentWithCheckMode(TestSetModeIfDifferentBase):
|
|
||||||
def setUp(self):
|
@pytest.mark.parametrize('check_mode, stdin',
|
||||||
super(TestSetModeIfDifferentWithCheckMode, self).setUp()
|
product((True, False), ({},)),
|
||||||
self.am.check_mode = True
|
indirect=['stdin'])
|
||||||
|
def test_missing_lchmod_is_not_link(am, mock_stats, mocker, check_mode):
|
||||||
def tearDown(self):
|
"""Some platforms have lchmod (*BSD) others do not (Linux)"""
|
||||||
super(TestSetModeIfDifferentWithCheckMode, self).tearDown()
|
|
||||||
self.am.check_mode = False
|
am.check_mode = check_mode
|
||||||
|
original_hasattr = hasattr
|
||||||
|
|
||||||
|
def _hasattr(obj, name):
|
||||||
|
if obj == os and name == 'lchmod':
|
||||||
|
return False
|
||||||
|
return original_hasattr(obj, name)
|
||||||
|
|
||||||
|
mocker.patch('os.lstat', side_effect=[mock_stats['before'], mock_stats['after']])
|
||||||
|
mocker.patch.object(builtins, 'hasattr', side_effect=_hasattr)
|
||||||
|
mocker.patch('os.path.islink', return_value=False)
|
||||||
|
m_chmod = mocker.patch('os.chmod', return_value=None)
|
||||||
|
|
||||||
|
assert am.set_mode_if_different('/path/to/file/no_lchmod', 0o660, False)
|
||||||
|
if check_mode:
|
||||||
|
assert not m_chmod.called
|
||||||
|
else:
|
||||||
|
m_chmod.assert_called_with(b'/path/to/file/no_lchmod', 0o660)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('check_mode, stdin',
|
||||||
|
product((True, False), ({},)),
|
||||||
|
indirect=['stdin'])
|
||||||
|
def test_missing_lchmod_is_link(am, mock_stats, mocker, check_mode):
|
||||||
|
"""Some platforms have lchmod (*BSD) others do not (Linux)"""
|
||||||
|
|
||||||
|
am.check_mode = check_mode
|
||||||
|
original_hasattr = hasattr
|
||||||
|
|
||||||
|
def _hasattr(obj, name):
|
||||||
|
if obj == os and name == 'lchmod':
|
||||||
|
return False
|
||||||
|
return original_hasattr(obj, name)
|
||||||
|
|
||||||
|
mocker.patch('os.lstat', side_effect=[mock_stats['before'], mock_stats['after']])
|
||||||
|
mocker.patch.object(builtins, 'hasattr', side_effect=_hasattr)
|
||||||
|
mocker.patch('os.path.islink', return_value=True)
|
||||||
|
m_chmod = mocker.patch('os.chmod', return_value=None)
|
||||||
|
mocker.patch('os.stat', return_value=mock_stats['after'])
|
||||||
|
|
||||||
|
assert am.set_mode_if_different('/path/to/file/no_lchmod', 0o660, False)
|
||||||
|
if check_mode:
|
||||||
|
assert not m_chmod.called
|
||||||
|
else:
|
||||||
|
m_chmod.assert_called_with(b'/path/to/file/no_lchmod', 0o660)
|
||||||
|
@ -0,0 +1,65 @@
|
|||||||
|
# Copyright (c) 2017 Ansible Project
|
||||||
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
from collections import MutableMapping
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
import ansible.module_utils.basic
|
||||||
|
from ansible.module_utils.six import PY3, string_types
|
||||||
|
from ansible.module_utils._text import to_bytes
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def stdin(mocker, request):
|
||||||
|
old_args = ansible.module_utils.basic._ANSIBLE_ARGS
|
||||||
|
ansible.module_utils.basic._ANSIBLE_ARGS = None
|
||||||
|
old_argv = sys.argv
|
||||||
|
sys.argv = ['ansible_unittest']
|
||||||
|
|
||||||
|
if isinstance(request.param, string_types):
|
||||||
|
args = request.param
|
||||||
|
elif isinstance(request.param, MutableMapping):
|
||||||
|
if 'ANSIBLE_MODULE_ARGS' not in request.param:
|
||||||
|
request.param = {'ANSIBLE_MODULE_ARGS': request.param}
|
||||||
|
args = json.dumps(request.param)
|
||||||
|
else:
|
||||||
|
raise Exception('Malformed data to the stdin pytest fixture')
|
||||||
|
|
||||||
|
fake_stdin = BytesIO(to_bytes(args, errors='surrogate_or_strict'))
|
||||||
|
if PY3:
|
||||||
|
mocker.patch('ansible.module_utils.basic.sys.stdin', mocker.MagicMock())
|
||||||
|
mocker.patch('ansible.module_utils.basic.sys.stdin.buffer', fake_stdin)
|
||||||
|
else:
|
||||||
|
mocker.patch('ansible.module_utils.basic.sys.stdin', fake_stdin)
|
||||||
|
|
||||||
|
yield fake_stdin
|
||||||
|
|
||||||
|
ansible.module_utils.basic._ANSIBLE_ARGS = old_args
|
||||||
|
sys.argv = old_argv
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def am(stdin, request):
|
||||||
|
old_args = ansible.module_utils.basic._ANSIBLE_ARGS
|
||||||
|
ansible.module_utils.basic._ANSIBLE_ARGS = None
|
||||||
|
old_argv = sys.argv
|
||||||
|
sys.argv = ['ansible_unittest']
|
||||||
|
|
||||||
|
argspec = {}
|
||||||
|
if hasattr(request, 'param'):
|
||||||
|
if isinstance(request.param, dict):
|
||||||
|
argspec = request.param
|
||||||
|
|
||||||
|
am = ansible.module_utils.basic.AnsibleModule(
|
||||||
|
argument_spec=argspec,
|
||||||
|
)
|
||||||
|
am._name = 'ansible_unittest'
|
||||||
|
|
||||||
|
yield am
|
||||||
|
|
||||||
|
ansible.module_utils.basic._ANSIBLE_ARGS = old_args
|
||||||
|
sys.argv = old_argv
|
@ -1,135 +1,119 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# (c) 2015, Michael Scherer <mscherer@redhat.com>
|
# (c) 2015, Michael Scherer <mscherer@redhat.com>
|
||||||
#
|
# Copyright (c) 2017 Ansible Project
|
||||||
# This file is part of Ansible
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
#
|
|
||||||
# Ansible is free software: you can redistribute it and/or modify
|
from __future__ import (absolute_import, division, print_function)
|
||||||
# it under the terms of the GNU General Public License as published by
|
__metaclass__ = type
|
||||||
# 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/>.
|
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import os.path
|
import os.path
|
||||||
|
|
||||||
import ansible.module_utils.basic
|
import pytest
|
||||||
from ansible.compat.tests import unittest
|
|
||||||
from ansible.compat.tests.mock import Mock, patch
|
|
||||||
from ansible.module_utils import known_hosts
|
from ansible.module_utils import known_hosts
|
||||||
from units.mock.procenv import swap_stdin_and_argv
|
|
||||||
|
|
||||||
|
URLS = {
|
||||||
class TestAnsibleModuleKnownHosts(unittest.TestCase):
|
'ssh://one.example.org/example.git': {
|
||||||
urls = {
|
'is_ssh_url': True,
|
||||||
'ssh://one.example.org/example.git': {
|
'get_fqdn': 'one.example.org',
|
||||||
'is_ssh_url': True,
|
'add_host_key_cmd': " -t rsa one.example.org",
|
||||||
'get_fqdn': 'one.example.org',
|
'port': None,
|
||||||
'add_host_key_cmd': " -t rsa one.example.org",
|
},
|
||||||
'port': None,
|
'ssh+git://two.example.org/example.git': {
|
||||||
},
|
'is_ssh_url': True,
|
||||||
'ssh+git://two.example.org/example.git': {
|
'get_fqdn': 'two.example.org',
|
||||||
'is_ssh_url': True,
|
'add_host_key_cmd': " -t rsa two.example.org",
|
||||||
'get_fqdn': 'two.example.org',
|
'port': None,
|
||||||
'add_host_key_cmd': " -t rsa two.example.org",
|
},
|
||||||
'port': None,
|
'rsync://three.example.org/user/example.git': {
|
||||||
},
|
'is_ssh_url': False,
|
||||||
'rsync://three.example.org/user/example.git': {
|
'get_fqdn': 'three.example.org',
|
||||||
'is_ssh_url': False,
|
'add_host_key_cmd': None, # not called for non-ssh urls
|
||||||
'get_fqdn': 'three.example.org',
|
'port': None,
|
||||||
'add_host_key_cmd': None, # not called for non-ssh urls
|
},
|
||||||
'port': None,
|
'git@four.example.org:user/example.git': {
|
||||||
},
|
'is_ssh_url': True,
|
||||||
'git@four.example.org:user/example.git': {
|
'get_fqdn': 'four.example.org',
|
||||||
'is_ssh_url': True,
|
'add_host_key_cmd': " -t rsa four.example.org",
|
||||||
'get_fqdn': 'four.example.org',
|
'port': None,
|
||||||
'add_host_key_cmd': " -t rsa four.example.org",
|
},
|
||||||
'port': None,
|
'git+ssh://five.example.org/example.git': {
|
||||||
},
|
'is_ssh_url': True,
|
||||||
'git+ssh://five.example.org/example.git': {
|
'get_fqdn': 'five.example.org',
|
||||||
'is_ssh_url': True,
|
'add_host_key_cmd': " -t rsa five.example.org",
|
||||||
'get_fqdn': 'five.example.org',
|
'port': None,
|
||||||
'add_host_key_cmd': " -t rsa five.example.org",
|
},
|
||||||
'port': None,
|
'ssh://six.example.org:21/example.org': {
|
||||||
},
|
# ssh on FTP Port?
|
||||||
'ssh://six.example.org:21/example.org': {
|
'is_ssh_url': True,
|
||||||
# ssh on FTP Port?
|
'get_fqdn': 'six.example.org',
|
||||||
'is_ssh_url': True,
|
'add_host_key_cmd': " -t rsa -p 21 six.example.org",
|
||||||
'get_fqdn': 'six.example.org',
|
'port': '21',
|
||||||
'add_host_key_cmd': " -t rsa -p 21 six.example.org",
|
},
|
||||||
'port': '21',
|
'ssh://[2001:DB8::abcd:abcd]/example.git': {
|
||||||
},
|
'is_ssh_url': True,
|
||||||
'ssh://[2001:DB8::abcd:abcd]/example.git': {
|
'get_fqdn': '[2001:DB8::abcd:abcd]',
|
||||||
'is_ssh_url': True,
|
'add_host_key_cmd': " -t rsa [2001:DB8::abcd:abcd]",
|
||||||
'get_fqdn': '[2001:DB8::abcd:abcd]',
|
'port': None,
|
||||||
'add_host_key_cmd': " -t rsa [2001:DB8::abcd:abcd]",
|
},
|
||||||
'port': None,
|
'ssh://[2001:DB8::abcd:abcd]:22/example.git': {
|
||||||
},
|
'is_ssh_url': True,
|
||||||
'ssh://[2001:DB8::abcd:abcd]:22/example.git': {
|
'get_fqdn': '[2001:DB8::abcd:abcd]',
|
||||||
'is_ssh_url': True,
|
'add_host_key_cmd': " -t rsa -p 22 [2001:DB8::abcd:abcd]",
|
||||||
'get_fqdn': '[2001:DB8::abcd:abcd]',
|
'port': '22',
|
||||||
'add_host_key_cmd': " -t rsa -p 22 [2001:DB8::abcd:abcd]",
|
},
|
||||||
'port': '22',
|
'username@[2001:DB8::abcd:abcd]/example.git': {
|
||||||
},
|
'is_ssh_url': True,
|
||||||
'username@[2001:DB8::abcd:abcd]/example.git': {
|
'get_fqdn': '[2001:DB8::abcd:abcd]',
|
||||||
'is_ssh_url': True,
|
'add_host_key_cmd': " -t rsa [2001:DB8::abcd:abcd]",
|
||||||
'get_fqdn': '[2001:DB8::abcd:abcd]',
|
'port': None,
|
||||||
'add_host_key_cmd': " -t rsa [2001:DB8::abcd:abcd]",
|
},
|
||||||
'port': None,
|
'username@[2001:DB8::abcd:abcd]:path/example.git': {
|
||||||
},
|
'is_ssh_url': True,
|
||||||
'username@[2001:DB8::abcd:abcd]:path/example.git': {
|
'get_fqdn': '[2001:DB8::abcd:abcd]',
|
||||||
'is_ssh_url': True,
|
'add_host_key_cmd': " -t rsa [2001:DB8::abcd:abcd]",
|
||||||
'get_fqdn': '[2001:DB8::abcd:abcd]',
|
'port': None,
|
||||||
'add_host_key_cmd': " -t rsa [2001:DB8::abcd:abcd]",
|
},
|
||||||
'port': None,
|
'ssh://internal.git.server:7999/repos/repo.git': {
|
||||||
},
|
'is_ssh_url': True,
|
||||||
'ssh://internal.git.server:7999/repos/repo.git': {
|
'get_fqdn': 'internal.git.server',
|
||||||
'is_ssh_url': True,
|
'add_host_key_cmd': " -t rsa -p 7999 internal.git.server",
|
||||||
'get_fqdn': 'internal.git.server',
|
'port': '7999',
|
||||||
'add_host_key_cmd': " -t rsa -p 7999 internal.git.server",
|
},
|
||||||
'port': '7999',
|
}
|
||||||
},
|
|
||||||
}
|
|
||||||
|
@pytest.mark.parametrize('url, is_ssh_url', ((k, v['is_ssh_url']) for k, v in URLS.items()))
|
||||||
def test_is_ssh_url(self):
|
def test_is_ssh_url(url, is_ssh_url):
|
||||||
for u in self.urls:
|
assert known_hosts.is_ssh_url(url) == is_ssh_url
|
||||||
self.assertEqual(known_hosts.is_ssh_url(u), self.urls[u]['is_ssh_url'])
|
|
||||||
|
|
||||||
def test_get_fqdn_and_port(self):
|
@pytest.mark.parametrize('url, fqdn, port', ((k, v['get_fqdn'], v['port']) for k, v in URLS.items()))
|
||||||
for u in self.urls:
|
def test_get_fqdn_and_port(url, fqdn, port):
|
||||||
self.assertEqual(known_hosts.get_fqdn_and_port(u), (self.urls[u]['get_fqdn'], self.urls[u]['port']))
|
assert known_hosts.get_fqdn_and_port(url) == (fqdn, port)
|
||||||
|
|
||||||
def test_add_host_key(self):
|
|
||||||
|
@pytest.mark.parametrize('fqdn, port, add_host_key_cmd, stdin',
|
||||||
# Copied
|
((v['get_fqdn'], v['port'], v['add_host_key_cmd'], {})
|
||||||
args = json.dumps(dict(ANSIBLE_MODULE_ARGS={}))
|
for v in URLS.values() if v['is_ssh_url']),
|
||||||
# unittest doesn't have a clean place to use a context manager, so we have to enter/exit manually
|
indirect=['stdin'])
|
||||||
|
def test_add_host_key(am, mocker, fqdn, port, add_host_key_cmd):
|
||||||
with swap_stdin_and_argv(stdin_data=args):
|
get_bin_path = mocker.MagicMock()
|
||||||
ansible.module_utils.basic._ANSIBLE_ARGS = None
|
get_bin_path.return_value = keyscan_cmd = "/custom/path/ssh-keyscan"
|
||||||
self.module = ansible.module_utils.basic.AnsibleModule(argument_spec=dict())
|
am.get_bin_path = get_bin_path
|
||||||
|
|
||||||
get_bin_path = Mock()
|
run_command = mocker.MagicMock()
|
||||||
get_bin_path.return_value = keyscan_cmd = "/custom/path/ssh-keyscan"
|
run_command.return_value = (0, "Needs output, otherwise thinks ssh-keyscan timed out'", "")
|
||||||
self.module.get_bin_path = get_bin_path
|
am.run_command = run_command
|
||||||
|
|
||||||
run_command = Mock()
|
append_to_file = mocker.MagicMock()
|
||||||
run_command.return_value = (0, "Needs output, otherwise thinks ssh-keyscan timed out'", "")
|
append_to_file.return_value = (None,)
|
||||||
self.module.run_command = run_command
|
am.append_to_file = append_to_file
|
||||||
|
|
||||||
append_to_file = Mock()
|
mocker.patch('os.path.isdir', return_value=True)
|
||||||
append_to_file.return_value = (None,)
|
mocker.patch('os.path.exists', return_value=True)
|
||||||
self.module.append_to_file = append_to_file
|
|
||||||
|
known_hosts.add_host_key(am, fqdn, port=port)
|
||||||
with patch('os.path.isdir', return_value=True):
|
run_command.assert_called_with(keyscan_cmd + add_host_key_cmd)
|
||||||
with patch('os.path.exists', return_value=True):
|
|
||||||
for u in self.urls:
|
|
||||||
if self.urls[u]['is_ssh_url']:
|
|
||||||
known_hosts.add_host_key(self.module, self.urls[u]['get_fqdn'], port=self.urls[u]['port'])
|
|
||||||
run_command.assert_called_with(keyscan_cmd + self.urls[u]['add_host_key_cmd'])
|
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
# Copyright (c) 2017 Ansible Project
|
||||||
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
|
||||||
|
import json
|
||||||
|
from collections import MutableMapping
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from ansible.module_utils.six import string_types
|
||||||
|
from ansible.module_utils._text import to_bytes
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def patch_ansible_module(request, mocker):
|
||||||
|
if isinstance(request.param, string_types):
|
||||||
|
args = request.param
|
||||||
|
elif isinstance(request.param, MutableMapping):
|
||||||
|
if 'ANSIBLE_MODULE_ARGS' not in request.param:
|
||||||
|
request.param = {'ANSIBLE_MODULE_ARGS': request.param}
|
||||||
|
args = json.dumps(request.param)
|
||||||
|
else:
|
||||||
|
raise Exception('Malformed data to the patch_ansible_module pytest fixture')
|
||||||
|
|
||||||
|
mocker.patch('ansible.module_utils.basic._ANSIBLE_ARGS', to_bytes(args))
|
@ -1,23 +1,24 @@
|
|||||||
|
# Copyright (c) 2017 Ansible Project
|
||||||
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from ansible.compat.tests import unittest
|
import pytest
|
||||||
from ansible.compat.tests.mock import patch
|
|
||||||
from ansible.module_utils import basic
|
|
||||||
|
|
||||||
from ansible.modules.packaging.language import pip
|
from ansible.modules.packaging.language import pip
|
||||||
|
|
||||||
from units.modules.utils import set_module_args, AnsibleFailJson, ModuleTestCase
|
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.usefixtures('patch_ansible_module')
|
||||||
|
|
||||||
class TestPip(ModuleTestCase):
|
|
||||||
def setUp(self):
|
|
||||||
super(TestPip, self).setUp()
|
|
||||||
|
|
||||||
@patch.object(basic.AnsibleModule, 'get_bin_path')
|
@pytest.mark.parametrize('patch_ansible_module', [{'name': 'six'}], indirect=['patch_ansible_module'])
|
||||||
def test_failure_when_pip_absent(self, mock_get_bin_path):
|
def test_failure_when_pip_absent(mocker, capfd):
|
||||||
|
get_bin_path = mocker.patch('ansible.module_utils.basic.AnsibleModule.get_bin_path')
|
||||||
|
get_bin_path.return_value = None
|
||||||
|
|
||||||
mock_get_bin_path.return_value = None
|
with pytest.raises(SystemExit):
|
||||||
|
pip.main()
|
||||||
|
|
||||||
with self.assertRaises(AnsibleFailJson):
|
out, err = capfd.readouterr()
|
||||||
set_module_args({'name': 'six'})
|
results = json.loads(out)
|
||||||
pip.main()
|
assert results['failed']
|
||||||
|
assert 'pip needs to be installed' in results['msg']
|
||||||
|
Loading…
Reference in New Issue