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 -*-
|
||||
# (c) 2016, James Cammarata <jimi@sngx.net>
|
||||
#
|
||||
# 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/>.
|
||||
# (c) 2017, Ansible Project
|
||||
# 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)
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
import json
|
||||
import sys
|
||||
|
||||
from units.mock.procenv import swap_stdin_and_argv
|
||||
|
||||
from ansible.compat.tests import unittest
|
||||
from ansible.compat.tests.mock import MagicMock
|
||||
|
||||
|
||||
class TestModuleUtilsBasic(unittest.TestCase):
|
||||
def test_module_utils_basic__log_invocation(self):
|
||||
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
|
||||
|
||||
# test basic log invocation
|
||||
basic._ANSIBLE_ARGS = None
|
||||
am = basic.AnsibleModule(
|
||||
argument_spec=dict(
|
||||
foo=dict(default=True, type='bool'),
|
||||
bar=dict(default=[], type='list'),
|
||||
bam=dict(default="bam"),
|
||||
baz=dict(default=u"baz"),
|
||||
password=dict(default=True),
|
||||
no_log=dict(default="you shouldn't see me", no_log=True),
|
||||
),
|
||||
)
|
||||
|
||||
am.log = MagicMock()
|
||||
am._log_invocation()
|
||||
|
||||
# Message is generated from a dict so it will be in an unknown order.
|
||||
# have to check this manually rather than with assert_called_with()
|
||||
args = am.log.call_args[0]
|
||||
self.assertEqual(len(args), 1)
|
||||
message = args[0]
|
||||
|
||||
self.assertEqual(
|
||||
len(message),
|
||||
len('Invoked with bam=bam bar=[1, 2, 3] foo=False baz=baz no_log=NOT_LOGGING_PARAMETER password=NOT_LOGGING_PASSWORD')
|
||||
)
|
||||
self.assertTrue(message.startswith('Invoked with '))
|
||||
self.assertIn(' bam=bam', message)
|
||||
self.assertIn(' bar=[1, 2, 3]', message)
|
||||
self.assertIn(' foo=False', message)
|
||||
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',
|
||||
})
|
||||
)
|
||||
import pytest
|
||||
|
||||
|
||||
ARGS = dict(foo=False, bar=[1, 2, 3], bam="bam", baz=u'baz')
|
||||
ARGUMENT_SPEC = dict(
|
||||
foo=dict(default=True, type='bool'),
|
||||
bar=dict(default=[], type='list'),
|
||||
bam=dict(default="bam"),
|
||||
baz=dict(default=u"baz"),
|
||||
password=dict(default=True),
|
||||
no_log=dict(default="you shouldn't see me", no_log=True),
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('am, stdin', [(ARGUMENT_SPEC, ARGS)], indirect=['am', 'stdin'])
|
||||
def test_module_utils_basic__log_invocation(am, mocker):
|
||||
|
||||
am.log = mocker.MagicMock()
|
||||
am._log_invocation()
|
||||
|
||||
# Message is generated from a dict so it will be in an unknown order.
|
||||
# have to check this manually rather than with assert_called_with()
|
||||
args = am.log.call_args[0]
|
||||
assert len(args) == 1
|
||||
message = args[0]
|
||||
|
||||
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')
|
||||
|
||||
assert message.startswith('Invoked with ')
|
||||
assert ' bam=bam' in message
|
||||
assert ' bar=[1, 2, 3]' in message
|
||||
assert ' foo=False' in message
|
||||
assert ' baz=baz' in message
|
||||
assert ' no_log=NOT_LOGGING_PARAMETER' in message
|
||||
assert ' password=NOT_LOGGING_PASSWORD' in message
|
||||
|
||||
kwargs = am.log.call_args[1]
|
||||
assert 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 -*-
|
||||
# (c) 2015-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/>.
|
||||
# (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)
|
||||
|
||||
# Make coding more python3-ish
|
||||
from __future__ import (absolute_import, division)
|
||||
__metaclass__ = type
|
||||
|
||||
import sys
|
||||
import json
|
||||
|
||||
from ansible.compat.tests import unittest
|
||||
from units.mock.procenv import ModuleTestCase
|
||||
from units.mock.generator import add_method
|
||||
from itertools import chain
|
||||
import pytest
|
||||
|
||||
|
||||
# Strings that should be converted into a typed value
|
||||
VALID_STRINGS = (
|
||||
[("'a'", 'a')],
|
||||
[("'1'", '1')],
|
||||
[("1", 1)],
|
||||
[("True", True)],
|
||||
[("False", False)],
|
||||
[("{}", {})],
|
||||
("'a'", 'a'),
|
||||
("'1'", '1'),
|
||||
("1", 1),
|
||||
("True", True),
|
||||
("False", False),
|
||||
("{}", {}),
|
||||
)
|
||||
|
||||
# Passing things that aren't strings should just return the object
|
||||
NONSTRINGS = (
|
||||
[({'a': 1}, {'a': 1})],
|
||||
({'a': 1}, {'a': 1}),
|
||||
)
|
||||
|
||||
# These strings are not basic types. For security, these should not be
|
||||
# executed. We return the same string and get an exception for some
|
||||
INVALID_STRINGS = (
|
||||
[("a=1", "a=1", SyntaxError)],
|
||||
[("a.foo()", "a.foo()", None)],
|
||||
[("import foo", "import foo", None)],
|
||||
[("__import__('foo')", "__import__('foo')", ValueError)],
|
||||
("a=1", "a=1", SyntaxError),
|
||||
("a.foo()", "a.foo()", None),
|
||||
("import foo", "import foo", None),
|
||||
("__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
|
||||
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
|
||||
self.assertEqual(self.am.safe_eval(code, include_exceptions=True), (expected, None))
|
||||
|
||||
|
||||
def _check_invalid_strings(self, code, expected):
|
||||
self.assertEqual(self.am.safe_eval(code), expected)
|
||||
|
||||
|
||||
def _check_invalid_strings_with_exceptions(self, code, expected, exception):
|
||||
res = self.am.safe_eval("a=1", include_exceptions=True)
|
||||
self.assertEqual(res[0], "a=1")
|
||||
self.assertEqual(type(res[1]), SyntaxError)
|
||||
|
||||
|
||||
@add_method(_check_simple_types, *VALID_STRINGS)
|
||||
@add_method(_check_simple_types, *NONSTRINGS)
|
||||
@add_method(_check_simple_types_with_exceptions, *VALID_STRINGS)
|
||||
@add_method(_check_simple_types_with_exceptions, *NONSTRINGS)
|
||||
@add_method(_check_invalid_strings, *[[i[0][0:-1]] for i in INVALID_STRINGS])
|
||||
@add_method(_check_invalid_strings_with_exceptions, *INVALID_STRINGS)
|
||||
class TestSafeEval(ModuleTestCase):
|
||||
|
||||
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
|
||||
assert am.safe_eval(code, include_exceptions=True), (expected, None)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('code, expected, stdin',
|
||||
((c, e, {}) for c, e, dummy in INVALID_STRINGS),
|
||||
indirect=['stdin'])
|
||||
def test_invalid_strings(am, code, expected):
|
||||
assert am.safe_eval(code) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize('code, expected, exception, stdin',
|
||||
((c, e, ex, {}) for c, e, ex in INVALID_STRINGS),
|
||||
indirect=['stdin'])
|
||||
def test_invalid_strings_with_exceptions(am, code, expected, exception):
|
||||
res = am.safe_eval(code, include_exceptions=True)
|
||||
assert res[0] == expected
|
||||
if exception is None:
|
||||
assert res[1] == exception
|
||||
else:
|
||||
assert type(res[1]) == exception
|
||||
|
@ -1,144 +1,147 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# (c) 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/>.
|
||||
# Copyright (c) 2017 Ansible Project
|
||||
# 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 os
|
||||
import pytest
|
||||
from itertools import product
|
||||
|
||||
try:
|
||||
import builtins
|
||||
except ImportError:
|
||||
import __builtin__ as builtins
|
||||
|
||||
from ansible.compat.tests import unittest
|
||||
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
|
||||
import pytest
|
||||
|
||||
|
||||
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()
|
||||
from ansible.module_utils import basic
|
||||
self.old_ANSIBLE_ARGS = basic._ANSIBLE_ARGS
|
||||
basic._ANSIBLE_ARGS = None
|
||||
@pytest.fixture
|
||||
def mock_stats(mocker):
|
||||
mock_stat1 = mocker.MagicMock()
|
||||
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):
|
||||
super(TestSetModeIfDifferentBase, self).tearDown()
|
||||
from ansible.module_utils import basic
|
||||
basic._ANSIBLE_ARGS = self.old_ANSIBLE_ARGS
|
||||
@pytest.fixture
|
||||
def am_check_mode(am):
|
||||
am.check_mode = True
|
||||
yield am
|
||||
am.check_mode = False
|
||||
|
||||
|
||||
def _check_no_mode_given_returns_previous_changes(self, previous_changes=True):
|
||||
with patch('os.lstat', side_effect=[self.mock_stat1]):
|
||||
@pytest.fixture
|
||||
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):
|
||||
# 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
|
||||
# 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)
|
||||
assert am.set_mode_if_different('/path/to/file', None, previous_changes) == previous_changes
|
||||
assert not m_lchmod.called
|
||||
|
||||
|
||||
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
|
||||
# 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_stat2, self.mock_stat2, self.mock_stat2]) as m_lstat:
|
||||
self.assertEqual(self.am.set_mode_if_different('/path/to/file', mode, False), False)
|
||||
|
||||
am.check_mode = check_mode
|
||||
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 = (
|
||||
[[0o660]],
|
||||
[['0o660']],
|
||||
[['660']],
|
||||
)
|
||||
assert am.set_mode_if_different('/path/to/file', mode, False)
|
||||
if check_mode:
|
||||
assert not m_lchmod.called
|
||||
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)], )
|
||||
@add_method(_check_mode_changed_to_0660, *SYNONYMS_0660)
|
||||
@add_method(_check_mode_unchanged_when_already_0660, *SYNONYMS_0660)
|
||||
class TestSetModeIfDifferent(TestSetModeIfDifferentBase):
|
||||
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):
|
||||
@pytest.mark.parametrize('mode, check_mode, stdin',
|
||||
product(SYNONYMS_0660, (True, False), ({},)),
|
||||
indirect=['stdin'])
|
||||
def test_mode_unchanged_when_already_0660(am, mock_stats, mocker, mode, check_mode):
|
||||
# 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
|
||||
# not equivalent to 0660 works.
|
||||
with patch('os.lstat', side_effect=[self.mock_stat1, self.mock_stat2, self.mock_stat2]) as m_lstat:
|
||||
self.assertEqual(self.am.set_mode_if_different('/path/to/file', mode, False), True)
|
||||
|
||||
|
||||
@add_method(_check_no_mode_given_returns_previous_changes, [dict(previous_changes=True)], [dict(previous_changes=False)],)
|
||||
@add_method(_check_knows_to_change_to_0660_in_check_mode, *SYNONYMS_0660)
|
||||
@add_method(_check_mode_unchanged_when_already_0660, *SYNONYMS_0660)
|
||||
class TestSetModeIfDifferentWithCheckMode(TestSetModeIfDifferentBase):
|
||||
def setUp(self):
|
||||
super(TestSetModeIfDifferentWithCheckMode, self).setUp()
|
||||
self.am.check_mode = True
|
||||
|
||||
def tearDown(self):
|
||||
super(TestSetModeIfDifferentWithCheckMode, self).tearDown()
|
||||
self.am.check_mode = False
|
||||
am.check_mode = check_mode
|
||||
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)
|
||||
|
||||
assert not am.set_mode_if_different('/path/to/file', mode, False)
|
||||
assert not m_lchmod.called
|
||||
|
||||
|
||||
@pytest.mark.parametrize('check_mode, stdin',
|
||||
product((True, False), ({},)),
|
||||
indirect=['stdin'])
|
||||
def test_missing_lchmod_is_not_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=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 -*-
|
||||
# (c) 2015, Michael Scherer <mscherer@redhat.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/>.
|
||||
# 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 json
|
||||
import os.path
|
||||
|
||||
import ansible.module_utils.basic
|
||||
from ansible.compat.tests import unittest
|
||||
from ansible.compat.tests.mock import Mock, patch
|
||||
import pytest
|
||||
|
||||
from ansible.module_utils import known_hosts
|
||||
from units.mock.procenv import swap_stdin_and_argv
|
||||
|
||||
|
||||
class TestAnsibleModuleKnownHosts(unittest.TestCase):
|
||||
urls = {
|
||||
'ssh://one.example.org/example.git': {
|
||||
'is_ssh_url': True,
|
||||
'get_fqdn': 'one.example.org',
|
||||
'add_host_key_cmd': " -t rsa one.example.org",
|
||||
'port': None,
|
||||
},
|
||||
'ssh+git://two.example.org/example.git': {
|
||||
'is_ssh_url': True,
|
||||
'get_fqdn': 'two.example.org',
|
||||
'add_host_key_cmd': " -t rsa two.example.org",
|
||||
'port': None,
|
||||
},
|
||||
'rsync://three.example.org/user/example.git': {
|
||||
'is_ssh_url': False,
|
||||
'get_fqdn': 'three.example.org',
|
||||
'add_host_key_cmd': None, # not called for non-ssh urls
|
||||
'port': None,
|
||||
},
|
||||
'git@four.example.org:user/example.git': {
|
||||
'is_ssh_url': True,
|
||||
'get_fqdn': 'four.example.org',
|
||||
'add_host_key_cmd': " -t rsa four.example.org",
|
||||
'port': None,
|
||||
},
|
||||
'git+ssh://five.example.org/example.git': {
|
||||
'is_ssh_url': True,
|
||||
'get_fqdn': 'five.example.org',
|
||||
'add_host_key_cmd': " -t rsa five.example.org",
|
||||
'port': None,
|
||||
},
|
||||
'ssh://six.example.org:21/example.org': {
|
||||
# ssh on FTP Port?
|
||||
'is_ssh_url': True,
|
||||
'get_fqdn': 'six.example.org',
|
||||
'add_host_key_cmd': " -t rsa -p 21 six.example.org",
|
||||
'port': '21',
|
||||
},
|
||||
'ssh://[2001:DB8::abcd:abcd]/example.git': {
|
||||
'is_ssh_url': True,
|
||||
'get_fqdn': '[2001:DB8::abcd:abcd]',
|
||||
'add_host_key_cmd': " -t rsa [2001:DB8::abcd:abcd]",
|
||||
'port': None,
|
||||
},
|
||||
'ssh://[2001:DB8::abcd:abcd]:22/example.git': {
|
||||
'is_ssh_url': True,
|
||||
'get_fqdn': '[2001:DB8::abcd:abcd]',
|
||||
'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,
|
||||
'get_fqdn': '[2001:DB8::abcd:abcd]',
|
||||
'add_host_key_cmd': " -t rsa [2001:DB8::abcd:abcd]",
|
||||
'port': None,
|
||||
},
|
||||
'username@[2001:DB8::abcd:abcd]:path/example.git': {
|
||||
'is_ssh_url': True,
|
||||
'get_fqdn': '[2001:DB8::abcd:abcd]',
|
||||
'add_host_key_cmd': " -t rsa [2001:DB8::abcd:abcd]",
|
||||
'port': None,
|
||||
},
|
||||
'ssh://internal.git.server:7999/repos/repo.git': {
|
||||
'is_ssh_url': True,
|
||||
'get_fqdn': 'internal.git.server',
|
||||
'add_host_key_cmd': " -t rsa -p 7999 internal.git.server",
|
||||
'port': '7999',
|
||||
},
|
||||
}
|
||||
|
||||
def test_is_ssh_url(self):
|
||||
for u in self.urls:
|
||||
self.assertEqual(known_hosts.is_ssh_url(u), self.urls[u]['is_ssh_url'])
|
||||
|
||||
def test_get_fqdn_and_port(self):
|
||||
for u in self.urls:
|
||||
self.assertEqual(known_hosts.get_fqdn_and_port(u), (self.urls[u]['get_fqdn'], self.urls[u]['port']))
|
||||
|
||||
def test_add_host_key(self):
|
||||
|
||||
# Copied
|
||||
args = json.dumps(dict(ANSIBLE_MODULE_ARGS={}))
|
||||
# unittest doesn't have a clean place to use a context manager, so we have to enter/exit manually
|
||||
|
||||
with swap_stdin_and_argv(stdin_data=args):
|
||||
ansible.module_utils.basic._ANSIBLE_ARGS = None
|
||||
self.module = ansible.module_utils.basic.AnsibleModule(argument_spec=dict())
|
||||
|
||||
get_bin_path = Mock()
|
||||
get_bin_path.return_value = keyscan_cmd = "/custom/path/ssh-keyscan"
|
||||
self.module.get_bin_path = get_bin_path
|
||||
|
||||
run_command = Mock()
|
||||
run_command.return_value = (0, "Needs output, otherwise thinks ssh-keyscan timed out'", "")
|
||||
self.module.run_command = run_command
|
||||
|
||||
append_to_file = Mock()
|
||||
append_to_file.return_value = (None,)
|
||||
self.module.append_to_file = append_to_file
|
||||
|
||||
with patch('os.path.isdir', return_value=True):
|
||||
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'])
|
||||
|
||||
|
||||
URLS = {
|
||||
'ssh://one.example.org/example.git': {
|
||||
'is_ssh_url': True,
|
||||
'get_fqdn': 'one.example.org',
|
||||
'add_host_key_cmd': " -t rsa one.example.org",
|
||||
'port': None,
|
||||
},
|
||||
'ssh+git://two.example.org/example.git': {
|
||||
'is_ssh_url': True,
|
||||
'get_fqdn': 'two.example.org',
|
||||
'add_host_key_cmd': " -t rsa two.example.org",
|
||||
'port': None,
|
||||
},
|
||||
'rsync://three.example.org/user/example.git': {
|
||||
'is_ssh_url': False,
|
||||
'get_fqdn': 'three.example.org',
|
||||
'add_host_key_cmd': None, # not called for non-ssh urls
|
||||
'port': None,
|
||||
},
|
||||
'git@four.example.org:user/example.git': {
|
||||
'is_ssh_url': True,
|
||||
'get_fqdn': 'four.example.org',
|
||||
'add_host_key_cmd': " -t rsa four.example.org",
|
||||
'port': None,
|
||||
},
|
||||
'git+ssh://five.example.org/example.git': {
|
||||
'is_ssh_url': True,
|
||||
'get_fqdn': 'five.example.org',
|
||||
'add_host_key_cmd': " -t rsa five.example.org",
|
||||
'port': None,
|
||||
},
|
||||
'ssh://six.example.org:21/example.org': {
|
||||
# ssh on FTP Port?
|
||||
'is_ssh_url': True,
|
||||
'get_fqdn': 'six.example.org',
|
||||
'add_host_key_cmd': " -t rsa -p 21 six.example.org",
|
||||
'port': '21',
|
||||
},
|
||||
'ssh://[2001:DB8::abcd:abcd]/example.git': {
|
||||
'is_ssh_url': True,
|
||||
'get_fqdn': '[2001:DB8::abcd:abcd]',
|
||||
'add_host_key_cmd': " -t rsa [2001:DB8::abcd:abcd]",
|
||||
'port': None,
|
||||
},
|
||||
'ssh://[2001:DB8::abcd:abcd]:22/example.git': {
|
||||
'is_ssh_url': True,
|
||||
'get_fqdn': '[2001:DB8::abcd:abcd]',
|
||||
'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,
|
||||
'get_fqdn': '[2001:DB8::abcd:abcd]',
|
||||
'add_host_key_cmd': " -t rsa [2001:DB8::abcd:abcd]",
|
||||
'port': None,
|
||||
},
|
||||
'username@[2001:DB8::abcd:abcd]:path/example.git': {
|
||||
'is_ssh_url': True,
|
||||
'get_fqdn': '[2001:DB8::abcd:abcd]',
|
||||
'add_host_key_cmd': " -t rsa [2001:DB8::abcd:abcd]",
|
||||
'port': None,
|
||||
},
|
||||
'ssh://internal.git.server:7999/repos/repo.git': {
|
||||
'is_ssh_url': True,
|
||||
'get_fqdn': 'internal.git.server',
|
||||
'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(url, is_ssh_url):
|
||||
assert known_hosts.is_ssh_url(url) == is_ssh_url
|
||||
|
||||
|
||||
@pytest.mark.parametrize('url, fqdn, port', ((k, v['get_fqdn'], v['port']) for k, v in URLS.items()))
|
||||
def test_get_fqdn_and_port(url, fqdn, port):
|
||||
assert known_hosts.get_fqdn_and_port(url) == (fqdn, port)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('fqdn, port, add_host_key_cmd, stdin',
|
||||
((v['get_fqdn'], v['port'], v['add_host_key_cmd'], {})
|
||||
for v in URLS.values() if v['is_ssh_url']),
|
||||
indirect=['stdin'])
|
||||
def test_add_host_key(am, mocker, fqdn, port, add_host_key_cmd):
|
||||
get_bin_path = mocker.MagicMock()
|
||||
get_bin_path.return_value = keyscan_cmd = "/custom/path/ssh-keyscan"
|
||||
am.get_bin_path = get_bin_path
|
||||
|
||||
run_command = mocker.MagicMock()
|
||||
run_command.return_value = (0, "Needs output, otherwise thinks ssh-keyscan timed out'", "")
|
||||
am.run_command = run_command
|
||||
|
||||
append_to_file = mocker.MagicMock()
|
||||
append_to_file.return_value = (None,)
|
||||
am.append_to_file = append_to_file
|
||||
|
||||
mocker.patch('os.path.isdir', return_value=True)
|
||||
mocker.patch('os.path.exists', return_value=True)
|
||||
|
||||
known_hosts.add_host_key(am, fqdn, port=port)
|
||||
run_command.assert_called_with(keyscan_cmd + 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
|
||||
|
||||
from ansible.compat.tests import unittest
|
||||
from ansible.compat.tests.mock import patch
|
||||
from ansible.module_utils import basic
|
||||
import pytest
|
||||
|
||||
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')
|
||||
def test_failure_when_pip_absent(self, mock_get_bin_path):
|
||||
@pytest.mark.parametrize('patch_ansible_module', [{'name': 'six'}], indirect=['patch_ansible_module'])
|
||||
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):
|
||||
set_module_args({'name': 'six'})
|
||||
pip.main()
|
||||
out, err = capfd.readouterr()
|
||||
results = json.loads(out)
|
||||
assert results['failed']
|
||||
assert 'pip needs to be installed' in results['msg']
|
||||
|
Loading…
Reference in New Issue