From 1609afbd12abb68d1434ee5ce56fe09e12a53fdb Mon Sep 17 00:00:00 2001 From: Toshio Kuratomi Date: Mon, 30 Jan 2017 13:51:27 -0800 Subject: [PATCH] Unittests for some of module_common.py (#20812) * Unittests for some of module_common.py * Port test_run_command to use pytest-mock The use of addCleanup(patch.stopall) from the unittest idiom was conflicting with the pytest-mock idiom of closing all patches automatically. Switching to pytest-mock ensures that the patches are closed and removing the stopall stops the conflict. --- test/runner/requirements/units.txt | 1 + .../module_common/test_module_common.py | 118 ++++++++++++++++++ .../module_utils/basic/test_run_command.py | 16 +-- 3 files changed, 127 insertions(+), 8 deletions(-) create mode 100644 test/units/executor/module_common/test_module_common.py diff --git a/test/runner/requirements/units.txt b/test/runner/requirements/units.txt index 885050a7b6c..3d7f91211f6 100644 --- a/test/runner/requirements/units.txt +++ b/test/runner/requirements/units.txt @@ -5,6 +5,7 @@ nose passlib pycrypto pytest +pytest-mock python-memcached pyyaml redis diff --git a/test/units/executor/module_common/test_module_common.py b/test/units/executor/module_common/test_module_common.py new file mode 100644 index 00000000000..edbf836e105 --- /dev/null +++ b/test/units/executor/module_common/test_module_common.py @@ -0,0 +1,118 @@ +# (c) 2017, Toshio Kuratomi +# +# 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 . + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import pytest + +import ansible.errors +from ansible.compat.six import PY2 + +from ansible.executor import module_common as amc + + +class TestStripComments(object): + def test_no_changes(self): + no_comments = u"""def some_code(): + return False""" + assert amc._strip_comments(no_comments) == no_comments + + def test_all_comments(self): + all_comments = u"""# This is a test + # Being as it is + # To be + """ + assert amc._strip_comments(all_comments) == u"" + + def test_all_whitespace(self): + # Note: Do not remove the spaces on the blank lines below. They're + # test data to show that the lines get removed despite having spaces + # on them + all_whitespace = u""" + + + +\t\t\r\n + """ # nopep8 + assert amc._strip_comments(all_whitespace) == u"" + + def test_somewhat_normal(self): + mixed = u"""#!/usr/bin/python + +# here we go +def test(arg): + # this is a thing + thing = '# test' + return thing +# End +""" + mixed_results = u"""def test(arg): + thing = '# test' + return thing""" + assert amc._strip_comments(mixed) == mixed_results + + +class TestSlurp(object): + def test_slurp_nonexistent(self, mocker): + mocker.patch('os.path.exists', side_effect=lambda x: False) + with pytest.raises(ansible.errors.AnsibleError): + amc._slurp('no_file') + + def test_slurp_file(self, mocker): + mocker.patch('os.path.exists', side_effect=lambda x: True) + m = mocker.mock_open(read_data='This is a test') + if PY2: + mocker.patch('__builtin__.open', m) + else: + mocker.patch('builtins.open', m) + assert amc._slurp('some_file') == 'This is a test' + + def test_slurp_file_with_newlines(self, mocker): + mocker.patch('os.path.exists', side_effect=lambda x: True) + m = mocker.mock_open(read_data='#!/usr/bin/python\ndef test(args):\nprint("hi")\n') + if PY2: + mocker.patch('__builtin__.open', m) + else: + mocker.patch('builtins.open', m) + assert amc._slurp('some_file') == '#!/usr/bin/python\ndef test(args):\nprint("hi")\n' + + +class TestGetShebang(object): + """Note: We may want to change the API of this function in the future. It isn't a great API""" + def test_no_interpreter_set(self): + assert amc._get_shebang(u'/usr/bin/python', {}) == (None, u'/usr/bin/python') + + def test_non_python_interpreter(self): + assert amc._get_shebang(u'/usr/bin/ruby', {}) == (None, u'/usr/bin/ruby') + + def test_interpreter_set_in_task_vars(self): + assert amc._get_shebang(u'/usr/bin/python', {u'ansible_python_interpreter': u'/usr/bin/pypy'}) == \ + (u'#!/usr/bin/pypy', u'/usr/bin/pypy') + + def test_non_python_interpreter_in_task_vars(self): + assert amc._get_shebang(u'/usr/bin/ruby', {u'ansible_ruby_interpreter': u'/usr/local/bin/ruby'}) == \ + (u'#!/usr/local/bin/ruby', u'/usr/local/bin/ruby') + + def test_with_args(self): + assert amc._get_shebang(u'/usr/bin/python', {u'ansible_python_interpreter': u'/usr/bin/python3'}, args=('-tt', '-OO')) == \ + (u'#!/usr/bin/python3 -tt -OO', u'/usr/bin/python3') + + def test_python_via_env(self): + assert amc._get_shebang(u'/usr/bin/python', {u'ansible_python_interpreter': u'/usr/bin/env python'}) == \ + (u'#!/usr/bin/env python', u'/usr/bin/env python') diff --git a/test/units/module_utils/basic/test_run_command.py b/test/units/module_utils/basic/test_run_command.py index da56e56a601..b27b8cb2cd5 100644 --- a/test/units/module_utils/basic/test_run_command.py +++ b/test/units/module_utils/basic/test_run_command.py @@ -25,6 +25,8 @@ import sys import time from io import BytesIO, StringIO +import pytest + from ansible.compat.six import PY3 from ansible.compat.tests import unittest from ansible.compat.tests.mock import call, MagicMock, Mock, patch, sentinel @@ -44,7 +46,8 @@ class OpenBytesIO(BytesIO): class TestAnsibleModuleRunCommand(unittest.TestCase): - def setUp(self): + @pytest.fixture(autouse=True) + def run_command_mocked_env(self, mocker): self.cmd_out = { # os.read() is returning 'bytes', not strings sentinel.stdout: BytesIO(), @@ -76,7 +79,7 @@ class TestAnsibleModuleRunCommand(unittest.TestCase): self.module = ansible.module_utils.basic.AnsibleModule(argument_spec=dict()) self.module.fail_json = MagicMock(side_effect=SystemExit) - self.os = patch('ansible.module_utils.basic.os').start() + self.os = mocker.patch('ansible.module_utils.basic.os') self.os.path.expandvars.side_effect = lambda x: x self.os.path.expanduser.side_effect = lambda x: x self.os.environ = {'PATH': '/bin'} @@ -86,7 +89,7 @@ class TestAnsibleModuleRunCommand(unittest.TestCase): self.os.read.side_effect = mock_os_read self.os.path.abspath.side_effect = mock_os_abspath - self.subprocess = patch('ansible.module_utils.basic.subprocess').start() + self.subprocess = mocker.patch('ansible.module_utils.basic.subprocess') self.cmd = Mock() self.cmd.returncode = 0 self.cmd.stdin = OpenBytesIO() @@ -94,13 +97,10 @@ class TestAnsibleModuleRunCommand(unittest.TestCase): self.cmd.stderr.fileno.return_value = sentinel.stderr self.subprocess.Popen.return_value = self.cmd - self.select = patch('ansible.module_utils.basic.select').start() + self.select = mocker.patch('ansible.module_utils.basic.select') self.select.select.side_effect = mock_select + yield - self.addCleanup(patch.stopall) - - - def tearDown(self): # unittest doesn't have a clean place to use a context manager, so we have to enter/exit manually self.stdin_swap.__exit__(None, None, None)