os-win/os_win/tests/unit/utils/io/test_ioutils.py

406 lines
16 KiB
Python

# Copyright 2014 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 mock
from unittest import mock
import ddt
import six
from os_win import constants
from os_win import exceptions
from os_win.tests.unit import test_base
from os_win.utils.io import ioutils
from os_win.utils.winapi import constants as w_const
from os_win.utils.winapi import wintypes
@ddt.ddt
class IOUtilsTestCase(test_base.BaseTestCase):
_autospec_classes = [
ioutils.win32utils.Win32Utils,
]
def setUp(self):
super(IOUtilsTestCase, self).setUp()
self._setup_lib_mocks()
self._ioutils = ioutils.IOUtils()
self._mock_run = self._ioutils._win32_utils.run_and_check_output
self._run_args = dict(kernel32_lib_func=True,
failure_exc=exceptions.Win32IOException,
eventlet_nonblocking_mode=False)
self.addCleanup(mock.patch.stopall)
def _setup_lib_mocks(self):
self._ctypes = mock.Mock()
# This is used in order to easily make assertions on the variables
# passed by reference.
self._ctypes.byref = lambda x: (x, "byref")
self._ctypes.c_wchar_p = lambda x: (x, "c_wchar_p")
mock.patch.multiple(ioutils,
ctypes=self._ctypes, kernel32=mock.DEFAULT,
create=True).start()
def test_run_and_check_output(self):
ret_val = self._ioutils._run_and_check_output(
mock.sentinel.func, mock.sentinel.arg)
self._mock_run.assert_called_once_with(mock.sentinel.func,
mock.sentinel.arg,
**self._run_args)
self.assertEqual(self._mock_run.return_value, ret_val)
@ddt.data({},
{'inherit_handle': True},
{'sec_attr': mock.sentinel.sec_attr})
@ddt.unpack
@mock.patch.object(wintypes, 'HANDLE')
@mock.patch.object(wintypes, 'SECURITY_ATTRIBUTES')
def test_create_pipe(self, mock_sec_attr_cls, mock_handle_cls,
inherit_handle=False, sec_attr=None):
r, w = self._ioutils.create_pipe(
sec_attr, mock.sentinel.size, inherit_handle)
exp_sec_attr = None
if sec_attr:
exp_sec_attr = sec_attr
elif inherit_handle:
exp_sec_attr = mock_sec_attr_cls.return_value
self.assertEqual(mock_handle_cls.return_value.value, r)
self.assertEqual(mock_handle_cls.return_value.value, w)
self._mock_run.assert_called_once_with(
ioutils.kernel32.CreatePipe,
self._ctypes.byref(mock_handle_cls.return_value),
self._ctypes.byref(mock_handle_cls.return_value),
self._ctypes.byref(exp_sec_attr) if exp_sec_attr else None,
mock.sentinel.size,
**self._run_args)
if not sec_attr and exp_sec_attr:
self.assertEqual(inherit_handle, exp_sec_attr.bInheritHandle)
self.assertEqual(self._ctypes.sizeof.return_value,
exp_sec_attr.nLength)
self._ctypes.sizeof.assert_called_once_with(exp_sec_attr)
def test_wait_named_pipe(self):
fake_timeout_s = 10
self._ioutils.wait_named_pipe(mock.sentinel.pipe_name,
timeout=fake_timeout_s)
self._mock_run.assert_called_once_with(
ioutils.kernel32.WaitNamedPipeW,
self._ctypes.c_wchar_p(mock.sentinel.pipe_name),
fake_timeout_s * 1000,
**self._run_args)
def test_open(self):
handle = self._ioutils.open(mock.sentinel.path,
mock.sentinel.access,
mock.sentinel.share_mode,
mock.sentinel.create_disposition,
mock.sentinel.flags)
self._mock_run.assert_called_once_with(
ioutils.kernel32.CreateFileW,
self._ctypes.c_wchar_p(mock.sentinel.path),
mock.sentinel.access,
mock.sentinel.share_mode,
None,
mock.sentinel.create_disposition,
mock.sentinel.flags,
None,
error_ret_vals=[w_const.INVALID_HANDLE_VALUE],
**self._run_args)
self.assertEqual(self._mock_run.return_value, handle)
def test_cancel_io(self):
self._ioutils.cancel_io(mock.sentinel.handle,
mock.sentinel.overlapped_struct,
ignore_invalid_handle=True)
expected_ignored_err_codes = [w_const.ERROR_NOT_FOUND,
w_const.ERROR_INVALID_HANDLE]
self._mock_run.assert_called_once_with(
ioutils.kernel32.CancelIoEx,
mock.sentinel.handle,
self._ctypes.byref(mock.sentinel.overlapped_struct),
ignored_error_codes=expected_ignored_err_codes,
**self._run_args)
def test_close_handle(self):
self._ioutils.close_handle(mock.sentinel.handle)
self._mock_run.assert_called_once_with(ioutils.kernel32.CloseHandle,
mock.sentinel.handle,
**self._run_args)
def test_wait_io_completion(self):
self._ioutils._wait_io_completion(mock.sentinel.event)
self._mock_run.assert_called_once_with(
ioutils.kernel32.WaitForSingleObjectEx,
mock.sentinel.event,
ioutils.WAIT_INFINITE_TIMEOUT,
True,
error_ret_vals=[w_const.WAIT_FAILED],
**self._run_args)
def test_set_event(self):
self._ioutils.set_event(mock.sentinel.event)
self._mock_run.assert_called_once_with(ioutils.kernel32.SetEvent,
mock.sentinel.event,
**self._run_args)
def test_reset_event(self):
self._ioutils._reset_event(mock.sentinel.event)
self._mock_run.assert_called_once_with(ioutils.kernel32.ResetEvent,
mock.sentinel.event,
**self._run_args)
def test_create_event(self):
event = self._ioutils._create_event(mock.sentinel.event_attributes,
mock.sentinel.manual_reset,
mock.sentinel.initial_state,
mock.sentinel.name)
self._mock_run.assert_called_once_with(ioutils.kernel32.CreateEventW,
mock.sentinel.event_attributes,
mock.sentinel.manual_reset,
mock.sentinel.initial_state,
mock.sentinel.name,
error_ret_vals=[None],
**self._run_args)
self.assertEqual(self._mock_run.return_value, event)
@mock.patch.object(wintypes, 'LPOVERLAPPED', create=True)
@mock.patch.object(wintypes, 'LPOVERLAPPED_COMPLETION_ROUTINE',
lambda x: x, create=True)
@mock.patch.object(ioutils.IOUtils, 'set_event')
def test_get_completion_routine(self, mock_set_event,
mock_LPOVERLAPPED):
mock_callback = mock.Mock()
compl_routine = self._ioutils.get_completion_routine(mock_callback)
compl_routine(mock.sentinel.error_code,
mock.sentinel.num_bytes,
mock.sentinel.lpOverLapped)
self._ctypes.cast.assert_called_once_with(mock.sentinel.lpOverLapped,
wintypes.LPOVERLAPPED)
mock_overlapped_struct = self._ctypes.cast.return_value.contents
mock_set_event.assert_called_once_with(mock_overlapped_struct.hEvent)
mock_callback.assert_called_once_with(mock.sentinel.num_bytes)
@mock.patch.object(wintypes, 'OVERLAPPED', create=True)
@mock.patch.object(ioutils.IOUtils, '_create_event')
def test_get_new_overlapped_structure(self, mock_create_event,
mock_OVERLAPPED):
overlapped_struct = self._ioutils.get_new_overlapped_structure()
self.assertEqual(mock_OVERLAPPED.return_value, overlapped_struct)
self.assertEqual(mock_create_event.return_value,
overlapped_struct.hEvent)
@mock.patch.object(ioutils.IOUtils, '_reset_event')
@mock.patch.object(ioutils.IOUtils, '_wait_io_completion')
def test_read(self, mock_wait_io_completion, mock_reset_event):
mock_overlapped_struct = mock.Mock()
mock_event = mock_overlapped_struct.hEvent
self._ioutils.read(mock.sentinel.handle, mock.sentinel.buff,
mock.sentinel.num_bytes,
mock_overlapped_struct,
mock.sentinel.compl_routine)
mock_reset_event.assert_called_once_with(mock_event)
self._mock_run.assert_called_once_with(ioutils.kernel32.ReadFileEx,
mock.sentinel.handle,
mock.sentinel.buff,
mock.sentinel.num_bytes,
self._ctypes.byref(
mock_overlapped_struct),
mock.sentinel.compl_routine,
**self._run_args)
mock_wait_io_completion.assert_called_once_with(mock_event)
@mock.patch.object(wintypes, 'DWORD')
def test_read_file(self, mock_dword):
num_bytes_read = mock_dword.return_value
ret_val = self._ioutils.read_file(
mock.sentinel.handle,
mock.sentinel.buff,
mock.sentinel.num_bytes,
mock.sentinel.overlapped_struct)
self.assertEqual(num_bytes_read.value, ret_val)
self._mock_run.assert_called_once_with(
ioutils.kernel32.ReadFile,
mock.sentinel.handle,
mock.sentinel.buff,
mock.sentinel.num_bytes,
self._ctypes.byref(num_bytes_read),
self._ctypes.byref(mock.sentinel.overlapped_struct),
**self._run_args)
@mock.patch.object(ioutils.IOUtils, '_reset_event')
@mock.patch.object(ioutils.IOUtils, '_wait_io_completion')
def test_write(self, mock_wait_io_completion, mock_reset_event):
mock_overlapped_struct = mock.Mock()
mock_event = mock_overlapped_struct.hEvent
self._ioutils.write(mock.sentinel.handle, mock.sentinel.buff,
mock.sentinel.num_bytes,
mock_overlapped_struct,
mock.sentinel.compl_routine)
mock_reset_event.assert_called_once_with(mock_event)
self._mock_run.assert_called_once_with(ioutils.kernel32.WriteFileEx,
mock.sentinel.handle,
mock.sentinel.buff,
mock.sentinel.num_bytes,
self._ctypes.byref(
mock_overlapped_struct),
mock.sentinel.compl_routine,
**self._run_args)
mock_wait_io_completion.assert_called_once_with(mock_event)
@mock.patch.object(wintypes, 'DWORD')
def test_write_file(self, mock_dword):
num_bytes_written = mock_dword.return_value
ret_val = self._ioutils.write_file(
mock.sentinel.handle,
mock.sentinel.buff,
mock.sentinel.num_bytes,
mock.sentinel.overlapped_struct)
self.assertEqual(num_bytes_written.value, ret_val)
self._mock_run.assert_called_once_with(
ioutils.kernel32.WriteFile,
mock.sentinel.handle,
mock.sentinel.buff,
mock.sentinel.num_bytes,
self._ctypes.byref(num_bytes_written),
self._ctypes.byref(mock.sentinel.overlapped_struct),
**self._run_args)
def test_buffer_ops(self):
mock.patch.stopall()
fake_data = 'fake data'
buff = self._ioutils.get_buffer(len(fake_data), data=fake_data)
buff_data = self._ioutils.get_buffer_data(buff, len(fake_data))
self.assertEqual(six.b(fake_data), buff_data)
class IOQueueTestCase(test_base.BaseTestCase):
def setUp(self):
super(IOQueueTestCase, self).setUp()
self._mock_queue = mock.Mock()
queue_patcher = mock.patch.object(ioutils.Queue, 'Queue',
new=self._mock_queue)
queue_patcher.start()
self.addCleanup(queue_patcher.stop)
self._mock_client_connected = mock.Mock()
self._ioqueue = ioutils.IOQueue(self._mock_client_connected)
def test_get(self):
self._mock_client_connected.isSet.return_value = True
self._mock_queue.get.return_value = mock.sentinel.item
queue_item = self._ioqueue.get(timeout=mock.sentinel.timeout)
self._mock_queue.get.assert_called_once_with(
self._ioqueue, timeout=mock.sentinel.timeout)
self.assertEqual(mock.sentinel.item, queue_item)
def _test_get_timeout(self, continue_on_timeout=True):
self._mock_client_connected.isSet.side_effect = [True, True, False]
self._mock_queue.get.side_effect = ioutils.Queue.Empty
queue_item = self._ioqueue.get(timeout=mock.sentinel.timeout,
continue_on_timeout=continue_on_timeout)
expected_calls_number = 2 if continue_on_timeout else 1
self._mock_queue.get.assert_has_calls(
[mock.call(self._ioqueue, timeout=mock.sentinel.timeout)] *
expected_calls_number)
self.assertIsNone(queue_item)
def test_get_continue_on_timeout(self):
# Test that the queue blocks as long
# as the client connected event is set.
self._test_get_timeout()
def test_get_break_on_timeout(self):
self._test_get_timeout(continue_on_timeout=False)
def test_put(self):
self._mock_client_connected.isSet.side_effect = [True, True, False]
self._mock_queue.put.side_effect = ioutils.Queue.Full
self._ioqueue.put(mock.sentinel.item,
timeout=mock.sentinel.timeout)
self._mock_queue.put.assert_has_calls(
[mock.call(self._ioqueue, mock.sentinel.item,
timeout=mock.sentinel.timeout)] * 2)
@mock.patch.object(ioutils.IOQueue, 'get')
def _test_get_burst(self, mock_get,
exceeded_max_size=False):
fake_data = 'fake_data'
mock_get.side_effect = [fake_data, fake_data, None]
if exceeded_max_size:
max_size = 0
else:
max_size = constants.SERIAL_CONSOLE_BUFFER_SIZE
ret_val = self._ioqueue.get_burst(
timeout=mock.sentinel.timeout,
burst_timeout=mock.sentinel.burst_timeout,
max_size=max_size)
expected_calls = [mock.call(timeout=mock.sentinel.timeout)]
expected_ret_val = fake_data
if not exceeded_max_size:
expected_calls.append(
mock.call(timeout=mock.sentinel.burst_timeout,
continue_on_timeout=False))
expected_ret_val += fake_data
mock_get.assert_has_calls(expected_calls)
self.assertEqual(expected_ret_val, ret_val)
def test_get_burst(self):
self._test_get_burst()
def test_get_burst_exceeded_size(self):
self._test_get_burst(exceeded_max_size=True)