import errno import logging import socket import unittest from unittest.mock import Mock, call, patch, sentinel import pykka from gi.repository import GLib from mopidy_mpd import network from tests import any_int, any_unicode class ConnectionTest(unittest.TestCase): def setUp(self): # noqa: N802 self.mock = Mock(spec=network.Connection) def test_init_ensure_nonblocking_io(self): sock = Mock(spec=socket.SocketType) network.Connection.__init__( self.mock, Mock(), {}, sock, (sentinel.host, sentinel.port), sentinel.timeout, ) sock.setblocking.assert_called_once_with(False) def test_init_starts_actor(self): protocol = Mock(spec=network.LineProtocol) network.Connection.__init__( self.mock, protocol, {}, Mock(), (sentinel.host, sentinel.port), sentinel.timeout, ) protocol.start.assert_called_once_with(self.mock) def test_init_enables_recv_and_timeout(self): network.Connection.__init__( self.mock, Mock(), {}, Mock(), (sentinel.host, sentinel.port), sentinel.timeout, ) self.mock.enable_recv.assert_called_once_with() self.mock.enable_timeout.assert_called_once_with() def test_init_stores_values_in_attributes(self): addr = (sentinel.host, sentinel.port) protocol = Mock(spec=network.LineProtocol) protocol_kwargs = {} sock = Mock(spec=socket.SocketType) network.Connection.__init__( self.mock, protocol, protocol_kwargs, sock, addr, sentinel.timeout ) assert sock == self.mock._sock assert protocol == self.mock.protocol assert protocol_kwargs == self.mock.protocol_kwargs assert sentinel.timeout == self.mock.timeout assert sentinel.host == self.mock.host assert sentinel.port == self.mock.port def test_init_handles_ipv6_addr(self): addr = ( sentinel.host, sentinel.port, sentinel.flowinfo, sentinel.scopeid, ) protocol = Mock(spec=network.LineProtocol) protocol_kwargs = {} sock = Mock(spec=socket.SocketType) network.Connection.__init__( self.mock, protocol, protocol_kwargs, sock, addr, sentinel.timeout ) assert sentinel.host == self.mock.host assert sentinel.port == self.mock.port def test_stop_disables_recv_send_and_timeout(self): self.mock.stopping = False self.mock.actor_ref = Mock() self.mock._sock = Mock(spec=socket.SocketType) network.Connection.stop(self.mock, sentinel.reason) self.mock.disable_timeout.assert_called_once_with() self.mock.disable_recv.assert_called_once_with() self.mock.disable_send.assert_called_once_with() def test_stop_closes_socket(self): self.mock.stopping = False self.mock.actor_ref = Mock() self.mock._sock = Mock(spec=socket.SocketType) network.Connection.stop(self.mock, sentinel.reason) self.mock._sock.close.assert_called_once_with() def test_stop_closes_socket_error(self): self.mock.stopping = False self.mock.actor_ref = Mock() self.mock._sock = Mock(spec=socket.SocketType) self.mock._sock.close.side_effect = socket.error network.Connection.stop(self.mock, sentinel.reason) self.mock._sock.close.assert_called_once_with() def test_stop_stops_actor(self): self.mock.stopping = False self.mock.actor_ref = Mock() self.mock._sock = Mock(spec=socket.SocketType) network.Connection.stop(self.mock, sentinel.reason) self.mock.actor_ref.stop.assert_called_once_with(block=False) def test_stop_handles_actor_already_being_stopped(self): self.mock.stopping = False self.mock.actor_ref = Mock() self.mock.actor_ref.stop.side_effect = pykka.ActorDeadError() self.mock._sock = Mock(spec=socket.SocketType) network.Connection.stop(self.mock, sentinel.reason) self.mock.actor_ref.stop.assert_called_once_with(block=False) def test_stop_sets_stopping_to_true(self): self.mock.stopping = False self.mock.actor_ref = Mock() self.mock._sock = Mock(spec=socket.SocketType) network.Connection.stop(self.mock, sentinel.reason) assert self.mock.stopping is True def test_stop_does_not_proceed_when_already_stopping(self): self.mock.stopping = True self.mock.actor_ref = Mock() self.mock._sock = Mock(spec=socket.SocketType) network.Connection.stop(self.mock, sentinel.reason) assert 0 == self.mock.actor_ref.stop.call_count assert 0 == self.mock._sock.close.call_count @patch.object(network.logger, "log", new=Mock()) def test_stop_logs_reason(self): self.mock.stopping = False self.mock.actor_ref = Mock() self.mock._sock = Mock(spec=socket.SocketType) network.Connection.stop(self.mock, sentinel.reason) network.logger.log.assert_called_once_with( logging.DEBUG, sentinel.reason ) @patch.object(network.logger, "log", new=Mock()) def test_stop_logs_reason_with_level(self): self.mock.stopping = False self.mock.actor_ref = Mock() self.mock._sock = Mock(spec=socket.SocketType) network.Connection.stop( self.mock, sentinel.reason, level=sentinel.level ) network.logger.log.assert_called_once_with( sentinel.level, sentinel.reason ) @patch.object(network.logger, "log", new=Mock()) def test_stop_logs_that_it_is_calling_itself(self): self.mock.stopping = True self.mock.actor_ref = Mock() self.mock._sock = Mock(spec=socket.SocketType) network.Connection.stop(self.mock, sentinel.reason) network.logger.log(any_int, any_unicode) @patch.object(GLib, "io_add_watch", new=Mock()) def test_enable_recv_registers_with_glib(self): self.mock.recv_id = None self.mock._sock = Mock(spec=socket.SocketType) self.mock._sock.fileno.return_value = sentinel.fileno GLib.io_add_watch.return_value = sentinel.tag network.Connection.enable_recv(self.mock) GLib.io_add_watch.assert_called_once_with( sentinel.fileno, GLib.IO_IN | GLib.IO_ERR | GLib.IO_HUP, self.mock.recv_callback, ) assert sentinel.tag == self.mock.recv_id @patch.object(GLib, "io_add_watch", new=Mock()) def test_enable_recv_already_registered(self): self.mock._sock = Mock(spec=socket.SocketType) self.mock.recv_id = sentinel.tag network.Connection.enable_recv(self.mock) assert 0 == GLib.io_add_watch.call_count def test_enable_recv_does_not_change_tag(self): self.mock.recv_id = sentinel.tag self.mock._sock = Mock(spec=socket.SocketType) network.Connection.enable_recv(self.mock) assert sentinel.tag == self.mock.recv_id @patch.object(GLib, "source_remove", new=Mock()) def test_disable_recv_deregisters(self): self.mock.recv_id = sentinel.tag network.Connection.disable_recv(self.mock) GLib.source_remove.assert_called_once_with(sentinel.tag) assert self.mock.recv_id is None @patch.object(GLib, "source_remove", new=Mock()) def test_disable_recv_already_deregistered(self): self.mock.recv_id = None network.Connection.disable_recv(self.mock) assert 0 == GLib.source_remove.call_count assert self.mock.recv_id is None def test_enable_recv_on_closed_socket(self): self.mock.recv_id = None self.mock._sock = Mock(spec=socket.SocketType) self.mock._sock.fileno.side_effect = socket.error(errno.EBADF, "") network.Connection.enable_recv(self.mock) self.mock.stop.assert_called_once_with(any_unicode) assert self.mock.recv_id is None @patch.object(GLib, "io_add_watch", new=Mock()) def test_enable_send_registers_with_glib(self): self.mock.send_id = None self.mock._sock = Mock(spec=socket.SocketType) self.mock._sock.fileno.return_value = sentinel.fileno GLib.io_add_watch.return_value = sentinel.tag network.Connection.enable_send(self.mock) GLib.io_add_watch.assert_called_once_with( sentinel.fileno, GLib.IO_OUT | GLib.IO_ERR | GLib.IO_HUP, self.mock.send_callback, ) assert sentinel.tag == self.mock.send_id @patch.object(GLib, "io_add_watch", new=Mock()) def test_enable_send_already_registered(self): self.mock._sock = Mock(spec=socket.SocketType) self.mock.send_id = sentinel.tag network.Connection.enable_send(self.mock) assert 0 == GLib.io_add_watch.call_count def test_enable_send_does_not_change_tag(self): self.mock.send_id = sentinel.tag self.mock._sock = Mock(spec=socket.SocketType) network.Connection.enable_send(self.mock) assert sentinel.tag == self.mock.send_id @patch.object(GLib, "source_remove", new=Mock()) def test_disable_send_deregisters(self): self.mock.send_id = sentinel.tag network.Connection.disable_send(self.mock) GLib.source_remove.assert_called_once_with(sentinel.tag) assert self.mock.send_id is None @patch.object(GLib, "source_remove", new=Mock()) def test_disable_send_already_deregistered(self): self.mock.send_id = None network.Connection.disable_send(self.mock) assert 0 == GLib.source_remove.call_count assert self.mock.send_id is None def test_enable_send_on_closed_socket(self): self.mock.send_id = None self.mock._sock = Mock(spec=socket.SocketType) self.mock._sock.fileno.side_effect = socket.error(errno.EBADF, "") network.Connection.enable_send(self.mock) assert self.mock.send_id is None @patch.object(GLib, "timeout_add_seconds", new=Mock()) def test_enable_timeout_clears_existing_timeouts(self): self.mock.timeout = 10 network.Connection.enable_timeout(self.mock) self.mock.disable_timeout.assert_called_once_with() @patch.object(GLib, "timeout_add_seconds", new=Mock()) def test_enable_timeout_add_glib_timeout(self): self.mock.timeout = 10 GLib.timeout_add_seconds.return_value = sentinel.tag network.Connection.enable_timeout(self.mock) GLib.timeout_add_seconds.assert_called_once_with( 10, self.mock.timeout_callback ) assert sentinel.tag == self.mock.timeout_id @patch.object(GLib, "timeout_add_seconds", new=Mock()) def test_enable_timeout_does_not_add_timeout(self): self.mock.timeout = 0 network.Connection.enable_timeout(self.mock) assert 0 == GLib.timeout_add_seconds.call_count self.mock.timeout = -1 network.Connection.enable_timeout(self.mock) assert 0 == GLib.timeout_add_seconds.call_count self.mock.timeout = None network.Connection.enable_timeout(self.mock) assert 0 == GLib.timeout_add_seconds.call_count def test_enable_timeout_does_not_call_disable_for_invalid_timeout(self): self.mock.timeout = 0 network.Connection.enable_timeout(self.mock) assert 0 == self.mock.disable_timeout.call_count self.mock.timeout = -1 network.Connection.enable_timeout(self.mock) assert 0 == self.mock.disable_timeout.call_count self.mock.timeout = None network.Connection.enable_timeout(self.mock) assert 0 == self.mock.disable_timeout.call_count @patch.object(GLib, "source_remove", new=Mock()) def test_disable_timeout_deregisters(self): self.mock.timeout_id = sentinel.tag network.Connection.disable_timeout(self.mock) GLib.source_remove.assert_called_once_with(sentinel.tag) assert self.mock.timeout_id is None @patch.object(GLib, "source_remove", new=Mock()) def test_disable_timeout_already_deregistered(self): self.mock.timeout_id = None network.Connection.disable_timeout(self.mock) assert 0 == GLib.source_remove.call_count assert self.mock.timeout_id is None def test_queue_send_acquires_and_releases_lock(self): self.mock.send_lock = Mock() self.mock.send_buffer = b"" network.Connection.queue_send(self.mock, b"data") self.mock.send_lock.acquire.assert_called_once_with(True) self.mock.send_lock.release.assert_called_once_with() def test_queue_send_calls_send(self): self.mock.send_buffer = b"" self.mock.send_lock = Mock() self.mock.send.return_value = b"" network.Connection.queue_send(self.mock, b"data") self.mock.send.assert_called_once_with(b"data") assert 0 == self.mock.enable_send.call_count assert b"" == self.mock.send_buffer def test_queue_send_calls_enable_send_for_partial_send(self): self.mock.send_buffer = b"" self.mock.send_lock = Mock() self.mock.send.return_value = b"ta" network.Connection.queue_send(self.mock, b"data") self.mock.send.assert_called_once_with(b"data") self.mock.enable_send.assert_called_once_with() assert b"ta" == self.mock.send_buffer def test_queue_send_calls_send_with_existing_buffer(self): self.mock.send_buffer = b"foo" self.mock.send_lock = Mock() self.mock.send.return_value = b"" network.Connection.queue_send(self.mock, b"bar") self.mock.send.assert_called_once_with(b"foobar") assert 0 == self.mock.enable_send.call_count assert b"" == self.mock.send_buffer def test_recv_callback_respects_io_err(self): self.mock._sock = Mock(spec=socket.SocketType) self.mock.actor_ref = Mock() assert network.Connection.recv_callback( self.mock, sentinel.fd, (GLib.IO_IN | GLib.IO_ERR) ) self.mock.stop.assert_called_once_with(any_unicode) def test_recv_callback_respects_io_hup(self): self.mock._sock = Mock(spec=socket.SocketType) self.mock.actor_ref = Mock() assert network.Connection.recv_callback( self.mock, sentinel.fd, (GLib.IO_IN | GLib.IO_HUP) ) self.mock.stop.assert_called_once_with(any_unicode) def test_recv_callback_respects_io_hup_and_io_err(self): self.mock._sock = Mock(spec=socket.SocketType) self.mock.actor_ref = Mock() assert network.Connection.recv_callback( self.mock, sentinel.fd, ((GLib.IO_IN | GLib.IO_HUP) | GLib.IO_ERR) ) self.mock.stop.assert_called_once_with(any_unicode) def test_recv_callback_sends_data_to_actor(self): self.mock._sock = Mock(spec=socket.SocketType) self.mock._sock.recv.return_value = b"data" self.mock.actor_ref = Mock() assert network.Connection.recv_callback( self.mock, sentinel.fd, GLib.IO_IN ) self.mock.actor_ref.tell.assert_called_once_with({"received": b"data"}) def test_recv_callback_handles_dead_actors(self): self.mock._sock = Mock(spec=socket.SocketType) self.mock._sock.recv.return_value = b"data" self.mock.actor_ref = Mock() self.mock.actor_ref.tell.side_effect = pykka.ActorDeadError() assert network.Connection.recv_callback( self.mock, sentinel.fd, GLib.IO_IN ) self.mock.stop.assert_called_once_with(any_unicode) def test_recv_callback_gets_no_data(self): self.mock._sock = Mock(spec=socket.SocketType) self.mock._sock.recv.return_value = b"" self.mock.actor_ref = Mock() assert network.Connection.recv_callback( self.mock, sentinel.fd, GLib.IO_IN ) assert self.mock.mock_calls == [ call._sock.recv(any_int), call.disable_recv(), call.actor_ref.tell({"close": True}), ] def test_recv_callback_recoverable_error(self): self.mock._sock = Mock(spec=socket.SocketType) for error in (errno.EWOULDBLOCK, errno.EINTR): self.mock._sock.recv.side_effect = socket.error(error, "") assert network.Connection.recv_callback( self.mock, sentinel.fd, GLib.IO_IN ) assert 0 == self.mock.stop.call_count def test_recv_callback_unrecoverable_error(self): self.mock._sock = Mock(spec=socket.SocketType) self.mock._sock.recv.side_effect = socket.error assert network.Connection.recv_callback( self.mock, sentinel.fd, GLib.IO_IN ) self.mock.stop.assert_called_once_with(any_unicode) def test_send_callback_respects_io_err(self): self.mock._sock = Mock(spec=socket.SocketType) self.mock._sock.send.return_value = 1 self.mock.send_lock = Mock() self.mock.actor_ref = Mock() self.mock.send_buffer = b"" assert network.Connection.send_callback( self.mock, sentinel.fd, (GLib.IO_IN | GLib.IO_ERR) ) self.mock.stop.assert_called_once_with(any_unicode) def test_send_callback_respects_io_hup(self): self.mock._sock = Mock(spec=socket.SocketType) self.mock._sock.send.return_value = 1 self.mock.send_lock = Mock() self.mock.actor_ref = Mock() self.mock.send_buffer = b"" assert network.Connection.send_callback( self.mock, sentinel.fd, (GLib.IO_IN | GLib.IO_HUP) ) self.mock.stop.assert_called_once_with(any_unicode) def test_send_callback_respects_io_hup_and_io_err(self): self.mock._sock = Mock(spec=socket.SocketType) self.mock._sock.send.return_value = 1 self.mock.send_lock = Mock() self.mock.actor_ref = Mock() self.mock.send_buffer = b"" assert network.Connection.send_callback( self.mock, sentinel.fd, ((GLib.IO_IN | GLib.IO_HUP) | GLib.IO_ERR) ) self.mock.stop.assert_called_once_with(any_unicode) def test_send_callback_acquires_and_releases_lock(self): self.mock.send_lock = Mock() self.mock.send_lock.acquire.return_value = True self.mock.send_buffer = b"" self.mock._sock = Mock(spec=socket.SocketType) self.mock._sock.send.return_value = 0 assert network.Connection.send_callback( self.mock, sentinel.fd, GLib.IO_IN ) self.mock.send_lock.acquire.assert_called_once_with(False) self.mock.send_lock.release.assert_called_once_with() def test_send_callback_fails_to_acquire_lock(self): self.mock.send_lock = Mock() self.mock.send_lock.acquire.return_value = False self.mock.send_buffer = b"" self.mock._sock = Mock(spec=socket.SocketType) self.mock._sock.send.return_value = 0 assert network.Connection.send_callback( self.mock, sentinel.fd, GLib.IO_IN ) self.mock.send_lock.acquire.assert_called_once_with(False) assert 0 == self.mock._sock.send.call_count def test_send_callback_sends_all_data(self): self.mock.send_lock = Mock() self.mock.send_lock.acquire.return_value = True self.mock.send_buffer = b"data" self.mock.send.return_value = b"" assert network.Connection.send_callback( self.mock, sentinel.fd, GLib.IO_IN ) self.mock.disable_send.assert_called_once_with() self.mock.send.assert_called_once_with(b"data") assert b"" == self.mock.send_buffer def test_send_callback_sends_partial_data(self): self.mock.send_lock = Mock() self.mock.send_lock.acquire.return_value = True self.mock.send_buffer = b"data" self.mock.send.return_value = b"ta" assert network.Connection.send_callback( self.mock, sentinel.fd, GLib.IO_IN ) self.mock.send.assert_called_once_with(b"data") assert b"ta" == self.mock.send_buffer def test_send_recoverable_error(self): self.mock._sock = Mock(spec=socket.SocketType) for error in (errno.EWOULDBLOCK, errno.EINTR): self.mock._sock.send.side_effect = socket.error(error, "") network.Connection.send(self.mock, b"data") assert 0 == self.mock.stop.call_count def test_send_calls_socket_send(self): self.mock._sock = Mock(spec=socket.SocketType) self.mock._sock.send.return_value = 4 assert b"" == network.Connection.send(self.mock, b"data") self.mock._sock.send.assert_called_once_with(b"data") def test_send_calls_socket_send_partial_send(self): self.mock._sock = Mock(spec=socket.SocketType) self.mock._sock.send.return_value = 2 assert b"ta" == network.Connection.send(self.mock, b"data") self.mock._sock.send.assert_called_once_with(b"data") def test_send_unrecoverable_error(self): self.mock._sock = Mock(spec=socket.SocketType) self.mock._sock.send.side_effect = socket.error assert b"" == network.Connection.send(self.mock, b"data") self.mock.stop.assert_called_once_with(any_unicode) def test_timeout_callback(self): self.mock.timeout = 10 assert not network.Connection.timeout_callback(self.mock) self.mock.stop.assert_called_once_with(any_unicode) def test_str(self): self.mock.host = "foo" self.mock.port = 999 assert "[foo]:999" == network.Connection.__str__(self.mock) def test_str_without_port(self): self.mock.host = "foo" self.mock.port = None assert "[foo]" == network.Connection.__str__(self.mock)