neutron/neutron/tests/unit/agent/common/test_async_process.py
Brian Haley 7594bb0627 Remove the dependency on the "mock" package
Now that we are python3 only, we should move to using the built
in version of mock that supports all of our testing needs and
remove the dependency on the "mock" package.

This patch moves all references to "import mock" to
"from unittest import mock". It also cleans up some new line
inconsistency.

Fixed an inconsistency in the OVSBridge.deferred() definition
as it needs to also have an *args argument.

Fixed an issue where an l3-agent test was mocking
functools.partial, causing a python3.8 failure.

Unit tests only, removing from tests/base.py affects
functional tests which need additional work.

Change-Id: I40e8a8410840c3774c72ae1a8054574445d66ece
2020-04-28 18:05:37 -04:00

337 lines
14 KiB
Python

# Copyright 2013 Red Hat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import signal
import sys
from unittest import mock
import eventlet.event
from eventlet.green import subprocess
import eventlet.queue
import testtools
from neutron.agent.common import async_process
from neutron.agent.common import utils
from neutron.tests import base
from neutron.tests.unit.agent.linux import failing_process
class TestAsyncProcess(base.BaseTestCase):
def setUp(self):
super(TestAsyncProcess, self).setUp()
self.proc = async_process.AsyncProcess(['fake'])
def test_construtor_raises_exception_for_negative_respawn_interval(self):
with testtools.ExpectedException(ValueError):
async_process.AsyncProcess(['fake'], respawn_interval=-1)
def test__spawn(self):
expected_process = 'Foo'
proc = self.proc
with mock.patch.object(utils, 'create_process') as mock_create_process:
mock_create_process.return_value = [expected_process, None]
with mock.patch('eventlet.spawn') as mock_spawn:
proc._spawn()
self.assertTrue(self.proc._is_running)
self.assertIsInstance(proc._kill_event, eventlet.event.Event)
self.assertEqual(proc._process, expected_process)
mock_spawn.assert_has_calls([
mock.call(proc._watch_process,
proc._read_stdout,
proc._kill_event),
mock.call(proc._watch_process,
proc._read_stderr,
proc._kill_event),
])
self.assertEqual(len(proc._watchers), 2)
def test__pid_none(self):
pid = 1
self.proc._pid = None
with mock.patch.object(self.proc, '_process') as _process:
with mock.patch.object(utils,
'get_root_helper_child_pid') as func:
func.return_value = pid
self.assertEqual(self.proc.pid, pid)
func.assert_called_once_with(_process.pid, ['fake'],
run_as_root=False)
self.assertEqual(self.proc._pid, pid)
def test__pid_not_none(self):
self.proc._pid = 1
with mock.patch.object(self.proc, '_process'),\
mock.patch.object(utils, 'get_root_helper_child_pid') as func:
self.assertEqual(self.proc.pid, 1)
func.assert_not_called()
def test__handle_process_error_kills_with_respawn(self):
with mock.patch.object(self.proc, '_kill') as kill:
self.proc._handle_process_error()
kill.assert_has_calls([mock.call(signal.SIGKILL)])
def test__handle_process_error_kills_without_respawn(self):
self.proc.respawn_interval = 1
with mock.patch.object(self.proc, '_kill') as kill:
with mock.patch.object(self.proc, '_spawn') as spawn:
with mock.patch('eventlet.sleep') as sleep:
self.proc._handle_process_error()
kill.assert_has_calls([mock.call(signal.SIGKILL)])
sleep.assert_has_calls([mock.call(self.proc.respawn_interval)])
spawn.assert_called_once_with()
def test__handle_process_error_no_crash_if_started(self):
self.proc._is_running = True
with mock.patch.object(self.proc, '_kill'):
with mock.patch.object(self.proc, '_spawn') as mock_spawn:
self.proc._handle_process_error()
mock_spawn.assert_not_called()
def _watch_process_exception(self):
raise Exception('Error!')
def _test__watch_process(self, callback, kill_event):
self.proc._is_running = True
self.proc._kill_event = kill_event
# Ensure the test times out eventually if the watcher loops endlessly
with self.assert_max_execution_time():
with mock.patch.object(self.proc,
'_handle_process_error') as func:
self.proc._watch_process(callback, kill_event)
if not kill_event.ready():
func.assert_called_once_with()
def test__watch_process_exits_on_callback_failure(self):
self._test__watch_process(lambda: None, eventlet.event.Event())
def test__watch_process_exits_on_exception(self):
self._test__watch_process(self._watch_process_exception,
eventlet.event.Event())
with mock.patch.object(self.proc,
'_handle_process_error') as func:
self.proc._watch_process(self._watch_process_exception,
self.proc._kill_event)
func.assert_not_called()
def test__watch_process_exits_on_sent_kill_event(self):
kill_event = eventlet.event.Event()
kill_event.send()
self._test__watch_process(None, kill_event)
def _test_read_output_queues_and_returns_result(self, output):
queue = eventlet.queue.LightQueue()
mock_stream = mock.Mock()
with mock.patch.object(mock_stream, 'readline') as mock_readline:
mock_readline.return_value = output
result = self.proc._read(mock_stream, queue)
if output:
self.assertEqual(output, result)
self.assertEqual(output, queue.get_nowait())
else:
self.assertFalse(result)
self.assertTrue(queue.empty())
def test__read_queues_and_returns_output(self):
self._test_read_output_queues_and_returns_result('foo')
def test__read_returns_none_for_missing_output(self):
self._test_read_output_queues_and_returns_result('')
def test_start_raises_exception_if_process_already_started(self):
self.proc._is_running = True
with testtools.ExpectedException(async_process.AsyncProcessException):
self.proc.start()
def test_start_invokes__spawn(self):
with mock.patch.object(self.proc, '_spawn') as mock_start:
self.proc.start()
mock_start.assert_called_once_with()
def test__iter_queue_returns_empty_list_for_empty_queue(self):
result = list(self.proc._iter_queue(eventlet.queue.LightQueue(),
False))
self.assertEqual([], result)
def test__iter_queue_returns_queued_data(self):
queue = eventlet.queue.LightQueue()
queue.put('foo')
result = list(self.proc._iter_queue(queue, False))
self.assertEqual(result, ['foo'])
def _test_iter_output_calls_iter_queue_on_output_queue(self, output_type):
expected_value = 'foo'
with mock.patch.object(self.proc, '_iter_queue') as mock_iter_queue:
mock_iter_queue.return_value = expected_value
target_func = getattr(self.proc, 'iter_%s' % output_type, None)
value = target_func()
self.assertEqual(value, expected_value)
queue = getattr(self.proc, '_%s_lines' % output_type, None)
mock_iter_queue.assert_called_with(queue, False)
def test_iter_stdout(self):
self._test_iter_output_calls_iter_queue_on_output_queue('stdout')
def test_iter_stderr(self):
self._test_iter_output_calls_iter_queue_on_output_queue('stderr')
def test__kill_targets_process_for_pid(self):
pid = 1
with mock.patch.object(self.proc, '_kill_event'
) as mock_kill_event,\
mock.patch.object(utils, 'get_root_helper_child_pid',
return_value=pid),\
mock.patch.object(self.proc, '_kill_process_and_wait'
) as mock_kill_process_and_wait,\
mock.patch.object(self.proc, '_process'):
self.proc._kill(signal.SIGKILL)
self.assertIsNone(self.proc._kill_event)
self.assertFalse(self.proc._is_running)
self.assertIsNone(self.proc._pid)
mock_kill_event.send.assert_called_once_with()
if pid:
mock_kill_process_and_wait.assert_called_once_with(
pid, signal.SIGKILL, None)
def _test__kill_process_and_wait(self, pid, expected,
exception_message=None,
kill_signal=signal.SIGKILL):
self.proc.run_as_root = True
if exception_message:
exc = RuntimeError(exception_message)
else:
exc = None
with mock.patch.object(utils, 'kill_process',
side_effect=exc) as mock_kill_process:
actual = self.proc._kill_process(pid, kill_signal)
self.assertEqual(expected, actual)
mock_kill_process.assert_called_with(pid,
kill_signal,
self.proc.run_as_root)
def test__kill_process_and_wait_returns_true_for_valid_pid(self):
self._test__kill_process_and_wait('1', True)
def test__kill_process_and_wait_returns_false_for_execute_exception(self):
self._test__kill_process_and_wait('1', False, 'Invalid')
def test_kill_process_and_wait_with_different_signal(self):
self._test__kill_process_and_wait(
'1', True, kill_signal=signal.SIGTERM)
def test__kill_process_timeout_reached(self):
self.proc.run_as_root = True
kill_timeout = 5
pid = '1'
with mock.patch.object(utils, 'kill_process') as mock_kill_process, \
mock.patch.object(self.proc, '_process') as process_mock:
process_mock.wait.side_effect = subprocess.TimeoutExpired(
self.proc.cmd, kill_timeout)
self.assertTrue(
self.proc._kill_process_and_wait(
pid, signal.SIGTERM, kill_timeout))
process_mock.wait.assert_called_once_with(kill_timeout)
mock_kill_process.assert_has_calls([
mock.call(pid, signal.SIGTERM, self.proc.run_as_root),
mock.call(pid, signal.SIGKILL, self.proc.run_as_root)])
def test_stop_calls_kill_with_provided_signal_number(self):
self.proc._is_running = True
with mock.patch.object(self.proc, '_kill') as mock_kill:
self.proc.stop(kill_signal=signal.SIGTERM)
mock_kill.assert_called_once_with(signal.SIGTERM, None)
def test_stop_raises_exception_if_already_started(self):
with testtools.ExpectedException(async_process.AsyncProcessException):
self.proc.stop()
def test_cmd(self):
for expected, cmd in (('ls -l file', ['ls', '-l', 'file']),
('fake', ['fake'])):
proc = async_process.AsyncProcess(cmd)
self.assertEqual(expected, proc.cmd)
class TestAsyncProcessLogging(base.BaseTestCase):
def setUp(self):
super(TestAsyncProcessLogging, self).setUp()
self.log_mock = mock.patch.object(async_process, 'LOG').start()
def _test__read_stdout_logging(self, enable):
proc = async_process.AsyncProcess(['fakecmd'], log_output=enable)
with mock.patch.object(proc, '_read', return_value='fakedata'),\
mock.patch.object(proc, '_process'):
proc._read_stdout()
self.assertEqual(enable, self.log_mock.debug.called)
def _test__read_stderr_logging(self, enable):
proc = async_process.AsyncProcess(['fake'], log_output=enable)
with mock.patch.object(proc, '_read', return_value='fakedata'),\
mock.patch.object(proc, '_process'):
proc._read_stderr()
self.assertEqual(enable, self.log_mock.error.called)
def test__read_stdout_logging_enabled(self):
self._test__read_stdout_logging(enable=True)
def test__read_stdout_logging_disabled(self):
self._test__read_stdout_logging(enable=False)
def test__read_stderr_logging_enabled(self):
self._test__read_stderr_logging(enable=True)
def test__read_stderr_logging_disabled(self):
self._test__read_stderr_logging(enable=False)
class TestAsyncProcessDieOnError(base.BaseTestCase):
def test__read_stderr_returns_none_on_error(self):
proc = async_process.AsyncProcess(['fakecmd'], die_on_error=True)
with mock.patch.object(proc, '_read', return_value='fakedata'),\
mock.patch.object(proc, '_process'):
self.assertIsNone(proc._read_stderr())
class TestFailingAsyncProcess(base.BaseTestCase):
def setUp(self):
super(TestFailingAsyncProcess, self).setUp()
path = self.get_temp_file_path('async.tmp', self.get_new_temp_dir())
self.process = async_process.AsyncProcess([sys.executable,
failing_process.__file__,
path],
respawn_interval=0)
def test_failing_async_process_handle_error_once(self):
with mock.patch.object(self.process, '_handle_process_error')\
as handle_error_mock:
self.process.start()
self.process._process.wait()
# Wait for the monitor process to complete
for thread in self.process._watchers:
thread.wait()
self.assertEqual(1, handle_error_mock.call_count)