# Copyright 2015 Cloudbase Solutions Srl # All Rights Reserved. # # 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 errno from unittest import mock from six.moves import builtins from os_win import constants from os_win import exceptions from os_win.tests.unit import test_base from os_win.utils.io import namedpipe from os_win.utils.winapi import constants as w_const class NamedPipeTestCase(test_base.BaseTestCase): _FAKE_LOG_PATH = 'fake_log_path' @mock.patch.object(namedpipe.NamedPipeHandler, '_setup_io_structures') def setUp(self, mock_setup_structures): super(NamedPipeTestCase, self).setUp() self._mock_input_queue = mock.Mock() self._mock_output_queue = mock.Mock() self._mock_client_connected = mock.Mock() self._ioutils = mock.Mock() threading_patcher = mock.patch.object(namedpipe, 'threading') threading_patcher.start() self.addCleanup(threading_patcher.stop) self._handler = namedpipe.NamedPipeHandler( mock.sentinel.pipe_name, self._mock_input_queue, self._mock_output_queue, self._mock_client_connected, self._FAKE_LOG_PATH) self._handler._ioutils = self._ioutils def _mock_setup_pipe_handler(self): self._handler._log_file_handle = mock.Mock() self._handler._pipe_handle = mock.sentinel.pipe_handle self._r_worker = mock.Mock() self._w_worker = mock.Mock() self._handler._workers = [self._r_worker, self._w_worker] self._handler._r_buffer = mock.Mock() self._handler._w_buffer = mock.Mock() self._handler._r_overlapped = mock.Mock() self._handler._w_overlapped = mock.Mock() self._handler._r_completion_routine = mock.Mock() self._handler._w_completion_routine = mock.Mock() @mock.patch.object(builtins, 'open') @mock.patch.object(namedpipe.NamedPipeHandler, '_open_pipe') def test_start_pipe_handler(self, mock_open_pipe, mock_open): self._handler.start() mock_open_pipe.assert_called_once_with() mock_open.assert_called_once_with(self._FAKE_LOG_PATH, 'ab', 1) self.assertEqual(mock_open.return_value, self._handler._log_file_handle) thread = namedpipe.threading.Thread thread.assert_has_calls( [mock.call(target=self._handler._read_from_pipe), mock.call().start(), mock.call(target=self._handler._write_to_pipe), mock.call().start()]) for worker in self._handler._workers: self.assertIs(True, worker.daemon) @mock.patch.object(namedpipe.NamedPipeHandler, 'stop') @mock.patch.object(namedpipe.NamedPipeHandler, '_open_pipe') def test_start_pipe_handler_exception(self, mock_open_pipe, mock_stop_handler): mock_open_pipe.side_effect = Exception self.assertRaises(exceptions.OSWinException, self._handler.start) mock_stop_handler.assert_called_once_with() @mock.patch.object(namedpipe.NamedPipeHandler, '_cleanup_handles') @mock.patch.object(namedpipe.NamedPipeHandler, '_cancel_io') def _test_stop_pipe_handler(self, mock_cancel_io, mock_cleanup_handles, workers_started=True): self._mock_setup_pipe_handler() if not workers_started: handler_workers = [] self._handler._workers = handler_workers else: handler_workers = self._handler._workers self._r_worker.is_alive.side_effect = (True, False) self._w_worker.is_alive.return_value = False self._handler.stop() self._handler._stopped.set.assert_called_once_with() if not workers_started: mock_cleanup_handles.assert_called_once_with() else: self.assertFalse(mock_cleanup_handles.called) if workers_started: mock_cancel_io.assert_called_once_with() self._r_worker.join.assert_called_once_with(0.5) self.assertFalse(self._w_worker.join.called) self.assertEqual([], self._handler._workers) def test_stop_pipe_handler_workers_started(self): self._test_stop_pipe_handler() def test_stop_pipe_handler_workers_not_started(self): self._test_stop_pipe_handler(workers_started=False) @mock.patch.object(namedpipe.NamedPipeHandler, '_close_pipe') def test_cleanup_handles(self, mock_close_pipe): self._mock_setup_pipe_handler() log_handle = self._handler._log_file_handle r_event = self._handler._r_overlapped.hEvent w_event = self._handler._w_overlapped.hEvent self._handler._cleanup_handles() mock_close_pipe.assert_called_once_with() log_handle.close.assert_called_once_with() self._ioutils.close_handle.assert_has_calls( [mock.call(r_event), mock.call(w_event)]) self.assertIsNone(self._handler._log_file_handle) self.assertIsNone(self._handler._r_overlapped.hEvent) self.assertIsNone(self._handler._w_overlapped.hEvent) def test_setup_io_structures(self): self._handler._setup_io_structures() self.assertEqual(self._ioutils.get_buffer.return_value, self._handler._r_buffer) self.assertEqual(self._ioutils.get_buffer.return_value, self._handler._w_buffer) self.assertEqual( self._ioutils.get_new_overlapped_structure.return_value, self._handler._r_overlapped) self.assertEqual( self._ioutils.get_new_overlapped_structure.return_value, self._handler._w_overlapped) self.assertEqual( self._ioutils.get_completion_routine.return_value, self._handler._r_completion_routine) self.assertEqual( self._ioutils.get_completion_routine.return_value, self._handler._w_completion_routine) self.assertIsNone(self._handler._log_file_handle) self._ioutils.get_buffer.assert_has_calls( [mock.call(constants.SERIAL_CONSOLE_BUFFER_SIZE)] * 2) self._ioutils.get_completion_routine.assert_has_calls( [mock.call(self._handler._read_callback), mock.call()]) def test_open_pipe(self): self._handler._open_pipe() self._ioutils.wait_named_pipe.assert_called_once_with( mock.sentinel.pipe_name) self._ioutils.open.assert_called_once_with( mock.sentinel.pipe_name, desired_access=(w_const.GENERIC_READ | w_const.GENERIC_WRITE), share_mode=(w_const.FILE_SHARE_READ | w_const.FILE_SHARE_WRITE), creation_disposition=w_const.OPEN_EXISTING, flags_and_attributes=w_const.FILE_FLAG_OVERLAPPED) self.assertEqual(self._ioutils.open.return_value, self._handler._pipe_handle) def test_close_pipe(self): self._mock_setup_pipe_handler() self._handler._close_pipe() self._ioutils.close_handle.assert_called_once_with( mock.sentinel.pipe_handle) self.assertIsNone(self._handler._pipe_handle) def test_cancel_io(self): self._mock_setup_pipe_handler() self._handler._cancel_io() overlapped_structures = [self._handler._r_overlapped, self._handler._w_overlapped] self._ioutils.cancel_io.assert_has_calls( [mock.call(self._handler._pipe_handle, overlapped_structure, ignore_invalid_handle=True) for overlapped_structure in overlapped_structures]) @mock.patch.object(namedpipe.NamedPipeHandler, '_start_io_worker') def test_read_from_pipe(self, mock_start_worker): self._mock_setup_pipe_handler() self._handler._read_from_pipe() mock_start_worker.assert_called_once_with( self._ioutils.read, self._handler._r_buffer, self._handler._r_overlapped, self._handler._r_completion_routine) @mock.patch.object(namedpipe.NamedPipeHandler, '_start_io_worker') def test_write_to_pipe(self, mock_start_worker): self._mock_setup_pipe_handler() self._handler._write_to_pipe() mock_start_worker.assert_called_once_with( self._ioutils.write, self._handler._w_buffer, self._handler._w_overlapped, self._handler._w_completion_routine, self._handler._get_data_to_write) @mock.patch.object(namedpipe.NamedPipeHandler, '_cleanup_handles') def _test_start_io_worker(self, mock_cleanup_handles, buff_update_func=None, exception=None): self._handler._stopped.isSet.side_effect = [False, True] self._handler._pipe_handle = mock.sentinel.pipe_handle self._handler.stop = mock.Mock() io_func = mock.Mock(side_effect=exception) fake_buffer = 'fake_buffer' self._handler._start_io_worker(io_func, fake_buffer, mock.sentinel.overlapped_structure, mock.sentinel.completion_routine, buff_update_func) if buff_update_func: num_bytes = buff_update_func() else: num_bytes = len(fake_buffer) io_func.assert_called_once_with(mock.sentinel.pipe_handle, fake_buffer, num_bytes, mock.sentinel.overlapped_structure, mock.sentinel.completion_routine) if exception: self._handler._stopped.set.assert_called_once_with() mock_cleanup_handles.assert_called_once_with() def test_start_io_worker(self): self._test_start_io_worker() def test_start_io_worker_with_buffer_update_method(self): self._test_start_io_worker(buff_update_func=mock.Mock()) def test_start_io_worker_exception(self): self._test_start_io_worker(exception=IOError) @mock.patch.object(namedpipe.NamedPipeHandler, '_write_to_log') def test_read_callback(self, mock_write_to_log): self._mock_setup_pipe_handler() fake_data = self._ioutils.get_buffer_data.return_value self._handler._read_callback(mock.sentinel.num_bytes) self._ioutils.get_buffer_data.assert_called_once_with( self._handler._r_buffer, mock.sentinel.num_bytes) self._mock_output_queue.put.assert_called_once_with(fake_data) mock_write_to_log.assert_called_once_with(fake_data) @mock.patch.object(namedpipe, 'time') def test_get_data_to_write(self, mock_time): self._mock_setup_pipe_handler() self._handler._stopped.isSet.side_effect = [False, False] self._mock_client_connected.isSet.side_effect = [False, True] fake_data = 'fake input data' self._mock_input_queue.get.return_value = fake_data num_bytes = self._handler._get_data_to_write() mock_time.sleep.assert_called_once_with(1) self._ioutils.write_buffer_data.assert_called_once_with( self._handler._w_buffer, fake_data) self.assertEqual(len(fake_data), num_bytes) @mock.patch.object(namedpipe.NamedPipeHandler, '_rotate_logs') def _test_write_to_log(self, mock_rotate_logs, size_exceeded=False): self._mock_setup_pipe_handler() self._handler._stopped.isSet.return_value = False fake_handle = self._handler._log_file_handle fake_handle.tell.return_value = (constants.MAX_CONSOLE_LOG_FILE_SIZE if size_exceeded else 0) fake_data = 'fake_data' self._handler._write_to_log(fake_data) if size_exceeded: mock_rotate_logs.assert_called_once_with() self._handler._log_file_handle.write.assert_called_once_with( fake_data) def test_write_to_log(self): self._test_write_to_log() def test_write_to_log_size_exceeded(self): self._test_write_to_log(size_exceeded=True) def test_flush_log_file(self): self._handler._log_file_handle = None self._handler.flush_log_file() self._handler._log_file_handle = mock.Mock() self._handler.flush_log_file() self._handler._log_file_handle.flush.side_effect = ValueError self._handler.flush_log_file() @mock.patch.object(namedpipe.NamedPipeHandler, '_retry_if_file_in_use') @mock.patch.object(builtins, 'open') @mock.patch.object(namedpipe, 'os') def test_rotate_logs(self, mock_os, mock_open, mock_exec_retry): fake_archived_log_path = self._FAKE_LOG_PATH + '.1' mock_os.path.exists.return_value = True self._mock_setup_pipe_handler() fake_handle = self._handler._log_file_handle self._handler._rotate_logs() fake_handle.flush.assert_called_once_with() fake_handle.close.assert_called_once_with() mock_os.path.exists.assert_called_once_with( fake_archived_log_path) mock_exec_retry.assert_has_calls([mock.call(mock_os.remove, fake_archived_log_path), mock.call(mock_os.rename, self._FAKE_LOG_PATH, fake_archived_log_path)]) mock_open.assert_called_once_with(self._FAKE_LOG_PATH, 'ab', 1) self.assertEqual(mock_open.return_value, self._handler._log_file_handle) @mock.patch.object(namedpipe, 'time') def test_retry_if_file_in_use_exceeded_retries(self, mock_time): class FakeWindowsException(Exception): errno = errno.EACCES raise_count = self._handler._MAX_LOG_ROTATE_RETRIES + 1 mock_func_side_eff = [FakeWindowsException] * raise_count mock_func = mock.Mock(side_effect=mock_func_side_eff) with mock.patch.object(namedpipe, 'WindowsError', FakeWindowsException, create=True): self.assertRaises(FakeWindowsException, self._handler._retry_if_file_in_use, mock_func, mock.sentinel.arg) mock_time.sleep.assert_has_calls( [mock.call(1)] * self._handler._MAX_LOG_ROTATE_RETRIES)