"""blocking adapter test""" from datetime import datetime import functools import logging import socket import threading import time import unittest import uuid from forward_server import ForwardServer from test_utils import retry_assertion import pika from pika.adapters import blocking_connection from pika.compat import as_bytes import pika.connection import pika.exceptions # Disable warning about access to protected member # pylint: disable=W0212 # Disable warning Attribute defined outside __init__ # pylint: disable=W0201 # Disable warning Missing docstring # pylint: disable=C0111 # Disable warning Too many public methods # pylint: disable=R0904 # Disable warning Invalid variable name # pylint: disable=C0103 LOGGER = logging.getLogger(__name__) PARAMS_URL_TEMPLATE = ( 'amqp://guest:guest@127.0.0.1:%(port)s/%%2f?socket_timeout=1') DEFAULT_URL = PARAMS_URL_TEMPLATE % {'port': 5672} DEFAULT_PARAMS = pika.URLParameters(DEFAULT_URL) DEFAULT_TIMEOUT = 15 def setUpModule(): logging.basicConfig(level=logging.DEBUG) class BlockingTestCaseBase(unittest.TestCase): TIMEOUT = DEFAULT_TIMEOUT def _connect(self, url=DEFAULT_URL, connection_class=pika.BlockingConnection, impl_class=None): parameters = pika.URLParameters(url) connection = connection_class(parameters, _impl_class=impl_class) self.addCleanup(lambda: connection.close() if connection.is_open else None) connection._impl.add_timeout( self.TIMEOUT, # pylint: disable=E1101 self._on_test_timeout) return connection def _on_test_timeout(self): """Called when test times out""" LOGGER.info('%s TIMED OUT (%s)', datetime.utcnow(), self) self.fail('Test timed out') @retry_assertion(TIMEOUT/2) def _assert_exact_message_count_with_retries(self, channel, queue, expected_count): frame = channel.queue_declare(queue, passive=True) self.assertEqual(frame.method.message_count, expected_count) class TestCreateAndCloseConnection(BlockingTestCaseBase): def test(self): """BlockingConnection: Create and close connection""" connection = self._connect() self.assertIsInstance(connection, pika.BlockingConnection) self.assertTrue(connection.is_open) self.assertFalse(connection.is_closed) self.assertFalse(connection.is_closing) connection.close() self.assertTrue(connection.is_closed) self.assertFalse(connection.is_open) self.assertFalse(connection.is_closing) class TestMultiCloseConnection(BlockingTestCaseBase): def test(self): """BlockingConnection: Close connection twice""" connection = self._connect() self.assertIsInstance(connection, pika.BlockingConnection) self.assertTrue(connection.is_open) self.assertFalse(connection.is_closed) self.assertFalse(connection.is_closing) connection.close() self.assertTrue(connection.is_closed) self.assertFalse(connection.is_open) self.assertFalse(connection.is_closing) # Second close call shouldn't crash connection.close() class TestConnectionContextManagerClosesConnection(BlockingTestCaseBase): def test(self): """BlockingConnection: connection context manager closes connection""" with self._connect() as connection: self.assertIsInstance(connection, pika.BlockingConnection) self.assertTrue(connection.is_open) self.assertTrue(connection.is_closed) class TestConnectionContextManagerClosesConnectionAndPassesOriginalException(BlockingTestCaseBase): def test(self): """BlockingConnection: connection context manager closes connection and passes original exception""" # pylint: disable=C0301 class MyException(Exception): pass with self.assertRaises(MyException): with self._connect() as connection: self.assertTrue(connection.is_open) raise MyException() self.assertTrue(connection.is_closed) class TestConnectionContextManagerClosesConnectionAndPassesSystemException(BlockingTestCaseBase): def test(self): """BlockingConnection: connection context manager closes connection and passes system exception""" # pylint: disable=C0301 with self.assertRaises(SystemExit): with self._connect() as connection: self.assertTrue(connection.is_open) raise SystemExit() self.assertTrue(connection.is_closed) class TestLostConnectionResultsInIsClosedConnectionAndChannel(BlockingTestCaseBase): def test(self): connection = self._connect() channel = connection.channel() # Simulate the server dropping the socket connection connection._impl.socket.shutdown(socket.SHUT_RDWR) with self.assertRaises(pika.exceptions.ConnectionClosed): # Changing QoS should result in ConnectionClosed channel.basic_qos() # Now check is_open/is_closed on channel and connection self.assertFalse(channel.is_open) self.assertTrue(channel.is_closed) self.assertFalse(connection.is_open) self.assertTrue(connection.is_closed) class TestInvalidExchangeTypeRaisesConnectionClosed(BlockingTestCaseBase): def test(self): """BlockingConnection: ConnectionClosed raised when creating exchange with invalid type""" # pylint: disable=C0301 # This test exploits behavior specific to RabbitMQ whereby the broker # closes the connection if an attempt is made to declare an exchange # with an invalid exchange type connection = self._connect() ch = connection.channel() exg_name = ("TestInvalidExchangeTypeRaisesConnectionClosed_" + uuid.uuid1().hex) with self.assertRaises(pika.exceptions.ConnectionClosed) as ex_cm: # Attempt to create an exchange with invalid exchange type ch.exchange_declare(exg_name, exchange_type='ZZwwInvalid') self.assertEqual(ex_cm.exception.args[0], 503) class TestCreateAndCloseConnectionWithChannelAndConsumer(BlockingTestCaseBase): def test(self): """BlockingConnection: Create and close connection with channel and consumer""" # pylint: disable=C0301 connection = self._connect() ch = connection.channel() q_name = ( 'TestCreateAndCloseConnectionWithChannelAndConsumer_q' + uuid.uuid1().hex) body1 = 'a' * 1024 # Declare a new queue ch.queue_declare(q_name, auto_delete=True) self.addCleanup(self._connect().channel().queue_delete, q_name) # Publish the message to the queue by way of default exchange ch.publish(exchange='', routing_key=q_name, body=body1) # Create a non-ackable consumer ch.basic_consume(lambda *x: None, q_name, no_ack=True, exclusive=False, arguments=None) connection.close() self.assertTrue(connection.is_closed) self.assertFalse(connection.is_open) self.assertFalse(connection.is_closing) self.assertFalse(connection._impl._channels) self.assertFalse(ch._consumer_infos) self.assertFalse(ch._impl._consumers) class TestSuddenBrokerDisconnectBeforeChannel(BlockingTestCaseBase): def test(self): """BlockingConnection resets properly on TCP/IP drop during channel() """ with ForwardServer( remote_addr=(DEFAULT_PARAMS.host, DEFAULT_PARAMS.port), local_linger_args=(1, 0)) as fwd: self.connection = self._connect( PARAMS_URL_TEMPLATE % {"port": fwd.server_address[1]}) # Once outside the context, the connection is broken # BlockingConnection should raise ConnectionClosed with self.assertRaises(pika.exceptions.ConnectionClosed): self.connection.channel() self.assertTrue(self.connection.is_closed) self.assertFalse(self.connection.is_open) self.assertIsNone(self.connection._impl.socket) class TestNoAccessToFileDescriptorAfterConnectionClosed(BlockingTestCaseBase): def test(self): """BlockingConnection no access file descriptor after ConnectionClosed """ with ForwardServer( remote_addr=(DEFAULT_PARAMS.host, DEFAULT_PARAMS.port), local_linger_args=(1, 0)) as fwd: self.connection = self._connect( PARAMS_URL_TEMPLATE % {"port": fwd.server_address[1]}) # Once outside the context, the connection is broken # BlockingConnection should raise ConnectionClosed with self.assertRaises(pika.exceptions.ConnectionClosed): self.connection.channel() self.assertTrue(self.connection.is_closed) self.assertFalse(self.connection.is_open) self.assertIsNone(self.connection._impl.socket) # Attempt to operate on the connection once again after ConnectionClosed self.assertIsNone(self.connection._impl.socket) with self.assertRaises(pika.exceptions.ConnectionClosed): self.connection.channel() class TestConnectWithDownedBroker(BlockingTestCaseBase): def test(self): """ BlockingConnection to downed broker results in AMQPConnectionError """ # Reserve a port for use in connect sock = socket.socket() self.addCleanup(sock.close) sock.bind(("127.0.0.1", 0)) port = sock.getsockname()[1] sock.close() with self.assertRaises(pika.exceptions.AMQPConnectionError): self.connection = self._connect( PARAMS_URL_TEMPLATE % {"port": port}) class TestDisconnectDuringConnectionStart(BlockingTestCaseBase): def test(self): """ BlockingConnection TCP/IP connection loss in CONNECTION_START """ fwd = ForwardServer( remote_addr=(DEFAULT_PARAMS.host, DEFAULT_PARAMS.port), local_linger_args=(1, 0)) fwd.start() self.addCleanup(lambda: fwd.stop() if fwd.running else None) class MySelectConnection(pika.SelectConnection): assert hasattr(pika.SelectConnection, '_on_connection_start') def _on_connection_start(self, *args, **kwargs): fwd.stop() return super(MySelectConnection, self)._on_connection_start( *args, **kwargs) with self.assertRaises(pika.exceptions.ProbableAuthenticationError): self._connect( PARAMS_URL_TEMPLATE % {"port": fwd.server_address[1]}, impl_class=MySelectConnection) class TestDisconnectDuringConnectionTune(BlockingTestCaseBase): def test(self): """ BlockingConnection TCP/IP connection loss in CONNECTION_TUNE """ fwd = ForwardServer( remote_addr=(DEFAULT_PARAMS.host, DEFAULT_PARAMS.port), local_linger_args=(1, 0)) fwd.start() self.addCleanup(lambda: fwd.stop() if fwd.running else None) class MySelectConnection(pika.SelectConnection): assert hasattr(pika.SelectConnection, '_on_connection_tune') def _on_connection_tune(self, *args, **kwargs): fwd.stop() return super(MySelectConnection, self)._on_connection_tune( *args, **kwargs) with self.assertRaises(pika.exceptions.ProbableAccessDeniedError): self._connect( PARAMS_URL_TEMPLATE % {"port": fwd.server_address[1]}, impl_class=MySelectConnection) class TestDisconnectDuringConnectionProtocol(BlockingTestCaseBase): def test(self): """ BlockingConnection TCP/IP connection loss in CONNECTION_PROTOCOL """ fwd = ForwardServer( remote_addr=(DEFAULT_PARAMS.host, DEFAULT_PARAMS.port), local_linger_args=(1, 0)) fwd.start() self.addCleanup(lambda: fwd.stop() if fwd.running else None) class MySelectConnection(pika.SelectConnection): assert hasattr(pika.SelectConnection, '_on_connected') def _on_connected(self, *args, **kwargs): fwd.stop() return super(MySelectConnection, self)._on_connected( *args, **kwargs) with self.assertRaises(pika.exceptions.IncompatibleProtocolError): self._connect(PARAMS_URL_TEMPLATE % {"port": fwd.server_address[1]}, impl_class=MySelectConnection) class TestProcessDataEvents(BlockingTestCaseBase): def test(self): """BlockingConnection.process_data_events""" connection = self._connect() # Try with time_limit=0 start_time = time.time() connection.process_data_events(time_limit=0) elapsed = time.time() - start_time self.assertLess(elapsed, 0.25) # Try with time_limit=0.005 start_time = time.time() connection.process_data_events(time_limit=0.005) elapsed = time.time() - start_time self.assertGreaterEqual(elapsed, 0.005) self.assertLess(elapsed, 0.25) class TestConnectionRegisterForBlockAndUnblock(BlockingTestCaseBase): def test(self): """BlockingConnection register for Connection.Blocked/Unblocked""" connection = self._connect() # NOTE: I haven't figured out yet how to coerce RabbitMQ to emit # Connection.Block and Connection.Unblock from the test, so we'll # just call the registration functions for now, to make sure that # registration doesn't crash connection.add_on_connection_blocked_callback(lambda frame: None) blocked_buffer = [] evt = blocking_connection._ConnectionBlockedEvt( lambda f: blocked_buffer.append("blocked"), pika.frame.Method(1, pika.spec.Connection.Blocked('reason'))) repr(evt) evt.dispatch() self.assertEqual(blocked_buffer, ["blocked"]) unblocked_buffer = [] connection.add_on_connection_unblocked_callback(lambda frame: None) evt = blocking_connection._ConnectionUnblockedEvt( lambda f: unblocked_buffer.append("unblocked"), pika.frame.Method(1, pika.spec.Connection.Unblocked())) repr(evt) evt.dispatch() self.assertEqual(unblocked_buffer, ["unblocked"]) class TestBlockedConnectionTimeout(BlockingTestCaseBase): def test(self): """BlockingConnection Connection.Blocked timeout """ url = DEFAULT_URL + '&blocked_connection_timeout=0.001' conn = self._connect(url=url) # NOTE: I haven't figured out yet how to coerce RabbitMQ to emit # Connection.Block and Connection.Unblock from the test, so we'll # simulate it for now # Simulate Connection.Blocked conn._impl._on_connection_blocked(pika.frame.Method( 0, pika.spec.Connection.Blocked('TestBlockedConnectionTimeout'))) # Wait for connection teardown with self.assertRaises(pika.exceptions.ConnectionClosed) as excCtx: while True: conn.process_data_events(time_limit=1) self.assertEqual( excCtx.exception.args, (pika.connection.InternalCloseReasons.BLOCKED_CONNECTION_TIMEOUT, 'Blocked connection timeout expired')) class TestAddCallbackThreadsafeFromSameThread(BlockingTestCaseBase): def test(self): """BlockingConnection.add_callback_threadsafe from same thread""" connection = self._connect() # Test timer completion start_time = time.time() rx_callback = [] connection.add_callback_threadsafe( lambda: rx_callback.append(time.time())) while not rx_callback: connection.process_data_events(time_limit=None) self.assertEqual(len(rx_callback), 1) elapsed = time.time() - start_time self.assertLess(elapsed, 0.25) class TestAddCallbackThreadsafeFromAnotherThread(BlockingTestCaseBase): def test(self): """BlockingConnection.add_callback_threadsafe from another thread""" connection = self._connect() # Test timer completion start_time = time.time() rx_callback = [] timer = threading.Timer( 0, functools.partial(connection.add_callback_threadsafe, lambda: rx_callback.append(time.time()))) self.addCleanup(timer.cancel) timer.start() while not rx_callback: connection.process_data_events(time_limit=None) self.assertEqual(len(rx_callback), 1) elapsed = time.time() - start_time self.assertLess(elapsed, 0.25) class TestAddTimeoutRemoveTimeout(BlockingTestCaseBase): def test(self): """BlockingConnection.add_timeout and remove_timeout""" connection = self._connect() # Test timer completion start_time = time.time() rx_callback = [] timer_id = connection.add_timeout( 0.005, lambda: rx_callback.append(time.time())) while not rx_callback: connection.process_data_events(time_limit=None) self.assertEqual(len(rx_callback), 1) elapsed = time.time() - start_time self.assertLess(elapsed, 0.25) # Test removing triggered timeout connection.remove_timeout(timer_id) # Test aborted timer rx_callback = [] timer_id = connection.add_timeout( 0.001, lambda: rx_callback.append(time.time())) connection.remove_timeout(timer_id) connection.process_data_events(time_limit=0.1) self.assertFalse(rx_callback) # Make sure _TimerEvt repr doesn't crash evt = blocking_connection._TimerEvt(lambda: None) repr(evt) class TestViabilityOfMultipleTimeoutsWithSameDeadlineAndCallback(BlockingTestCaseBase): def test(self): """BlockingConnection viability of multiple timeouts with same deadline and callback""" connection = self._connect() rx_callback = [] def callback(): rx_callback.append(1) timer1 = connection.add_timeout(0, callback) timer2 = connection.add_timeout(0, callback) self.assertIsNot(timer1, timer2) connection.remove_timeout(timer1) # Wait for second timer to fire start_wait_time = time.time() while not rx_callback and time.time() - start_wait_time < 0.25: connection.process_data_events(time_limit=0.001) self.assertListEqual(rx_callback, [1]) class TestRemoveTimeoutFromTimeoutCallback(BlockingTestCaseBase): def test(self): """BlockingConnection.remove_timeout from timeout callback""" connection = self._connect() # Test timer completion timer_id1 = connection.add_timeout(5, lambda: 0/0) rx_timer2 = [] def on_timer2(): connection.remove_timeout(timer_id1) connection.remove_timeout(timer_id2) rx_timer2.append(1) timer_id2 = connection.add_timeout(0, on_timer2) while not rx_timer2: connection.process_data_events(time_limit=None) self.assertIsNone(timer_id1.callback) self.assertFalse(connection._ready_events) class TestSleep(BlockingTestCaseBase): def test(self): """BlockingConnection.sleep""" connection = self._connect() # Try with duration=0 start_time = time.time() connection.sleep(duration=0) elapsed = time.time() - start_time self.assertLess(elapsed, 0.25) # Try with duration=0.005 start_time = time.time() connection.sleep(duration=0.005) elapsed = time.time() - start_time self.assertGreaterEqual(elapsed, 0.005) self.assertLess(elapsed, 0.25) class TestConnectionProperties(BlockingTestCaseBase): def test(self): """Test BlockingConnection properties""" connection = self._connect() self.assertTrue(connection.is_open) self.assertFalse(connection.is_closing) self.assertFalse(connection.is_closed) self.assertTrue(connection.basic_nack_supported) self.assertTrue(connection.consumer_cancel_notify_supported) self.assertTrue(connection.exchange_exchange_bindings_supported) self.assertTrue(connection.publisher_confirms_supported) connection.close() self.assertFalse(connection.is_open) self.assertFalse(connection.is_closing) self.assertTrue(connection.is_closed) class TestCreateAndCloseChannel(BlockingTestCaseBase): def test(self): """BlockingChannel: Create and close channel""" connection = self._connect() ch = connection.channel() self.assertIsInstance(ch, blocking_connection.BlockingChannel) self.assertTrue(ch.is_open) self.assertFalse(ch.is_closed) self.assertFalse(ch.is_closing) self.assertIs(ch.connection, connection) ch.close() self.assertTrue(ch.is_closed) self.assertFalse(ch.is_open) self.assertFalse(ch.is_closing) class TestExchangeDeclareAndDelete(BlockingTestCaseBase): def test(self): """BlockingChannel: Test exchange_declare and exchange_delete""" connection = self._connect() ch = connection.channel() name = "TestExchangeDeclareAndDelete_" + uuid.uuid1().hex # Declare a new exchange frame = ch.exchange_declare(name, exchange_type='direct') self.addCleanup(connection.channel().exchange_delete, name) self.assertIsInstance(frame.method, pika.spec.Exchange.DeclareOk) # Check if it exists by declaring it passively frame = ch.exchange_declare(name, passive=True) self.assertIsInstance(frame.method, pika.spec.Exchange.DeclareOk) # Delete the exchange frame = ch.exchange_delete(name) self.assertIsInstance(frame.method, pika.spec.Exchange.DeleteOk) # Verify that it's been deleted with self.assertRaises(pika.exceptions.ChannelClosed) as cm: ch.exchange_declare(name, passive=True) self.assertEqual(cm.exception.args[0], 404) class TestExchangeBindAndUnbind(BlockingTestCaseBase): def test(self): """BlockingChannel: Test exchange_bind and exchange_unbind""" connection = self._connect() ch = connection.channel() q_name = 'TestExchangeBindAndUnbind_q' + uuid.uuid1().hex src_exg_name = 'TestExchangeBindAndUnbind_src_exg_' + uuid.uuid1().hex dest_exg_name = 'TestExchangeBindAndUnbind_dest_exg_' + uuid.uuid1().hex routing_key = 'TestExchangeBindAndUnbind' # Place channel in publisher-acknowledgments mode so that we may test # whether the queue is reachable by publishing with mandatory=True res = ch.confirm_delivery() self.assertIsNone(res) # Declare both exchanges ch.exchange_declare(src_exg_name, exchange_type='direct') self.addCleanup(connection.channel().exchange_delete, src_exg_name) ch.exchange_declare(dest_exg_name, exchange_type='direct') self.addCleanup(connection.channel().exchange_delete, dest_exg_name) # Declare a new queue ch.queue_declare(q_name, auto_delete=True) self.addCleanup(self._connect().channel().queue_delete, q_name) # Bind the queue to the destination exchange ch.queue_bind(q_name, exchange=dest_exg_name, routing_key=routing_key) # Verify that the queue is unreachable without exchange-exchange binding with self.assertRaises(pika.exceptions.UnroutableError): ch.publish(src_exg_name, routing_key, body='', mandatory=True) # Bind the exchanges frame = ch.exchange_bind(destination=dest_exg_name, source=src_exg_name, routing_key=routing_key) self.assertIsInstance(frame.method, pika.spec.Exchange.BindOk) # Publish a message via the source exchange ch.publish(src_exg_name, routing_key, body='TestExchangeBindAndUnbind', mandatory=True) # Check that the queue now has one message self._assert_exact_message_count_with_retries(channel=ch, queue=q_name, expected_count=1) # Unbind the exchanges frame = ch.exchange_unbind(destination=dest_exg_name, source=src_exg_name, routing_key=routing_key) self.assertIsInstance(frame.method, pika.spec.Exchange.UnbindOk) # Verify that the queue is now unreachable via the source exchange with self.assertRaises(pika.exceptions.UnroutableError): ch.publish(src_exg_name, routing_key, body='', mandatory=True) class TestQueueDeclareAndDelete(BlockingTestCaseBase): def test(self): """BlockingChannel: Test queue_declare and queue_delete""" connection = self._connect() ch = connection.channel() q_name = 'TestQueueDeclareAndDelete_' + uuid.uuid1().hex # Declare a new queue frame = ch.queue_declare(q_name, auto_delete=True) self.addCleanup(self._connect().channel().queue_delete, q_name) self.assertIsInstance(frame.method, pika.spec.Queue.DeclareOk) # Check if it exists by declaring it passively frame = ch.queue_declare(q_name, passive=True) self.assertIsInstance(frame.method, pika.spec.Queue.DeclareOk) # Delete the queue frame = ch.queue_delete(q_name) self.assertIsInstance(frame.method, pika.spec.Queue.DeleteOk) # Verify that it's been deleted with self.assertRaises(pika.exceptions.ChannelClosed) as cm: ch.queue_declare(q_name, passive=True) self.assertEqual(cm.exception.args[0], 404) class TestPassiveQueueDeclareOfUnknownQueueRaisesChannelClosed( BlockingTestCaseBase): def test(self): """BlockingChannel: ChannelClosed raised when passive-declaring unknown queue""" # pylint: disable=C0301 connection = self._connect() ch = connection.channel() q_name = ("TestPassiveQueueDeclareOfUnknownQueueRaisesChannelClosed_q_" + uuid.uuid1().hex) with self.assertRaises(pika.exceptions.ChannelClosed) as ex_cm: ch.queue_declare(q_name, passive=True) self.assertEqual(ex_cm.exception.args[0], 404) class TestQueueBindAndUnbindAndPurge(BlockingTestCaseBase): def test(self): """BlockingChannel: Test queue_bind and queue_unbind""" connection = self._connect() ch = connection.channel() q_name = 'TestQueueBindAndUnbindAndPurge_q' + uuid.uuid1().hex exg_name = 'TestQueueBindAndUnbindAndPurge_exg_' + uuid.uuid1().hex routing_key = 'TestQueueBindAndUnbindAndPurge' # Place channel in publisher-acknowledgments mode so that we may test # whether the queue is reachable by publishing with mandatory=True res = ch.confirm_delivery() self.assertIsNone(res) # Declare a new exchange ch.exchange_declare(exg_name, exchange_type='direct') self.addCleanup(connection.channel().exchange_delete, exg_name) # Declare a new queue ch.queue_declare(q_name, auto_delete=True) self.addCleanup(self._connect().channel().queue_delete, q_name) # Bind the queue to the exchange using routing key frame = ch.queue_bind(q_name, exchange=exg_name, routing_key=routing_key) self.assertIsInstance(frame.method, pika.spec.Queue.BindOk) # Check that the queue is empty frame = ch.queue_declare(q_name, passive=True) self.assertEqual(frame.method.message_count, 0) # Deposit a message in the queue ch.publish(exg_name, routing_key, body='TestQueueBindAndUnbindAndPurge', mandatory=True) # Check that the queue now has one message frame = ch.queue_declare(q_name, passive=True) self.assertEqual(frame.method.message_count, 1) # Unbind the queue frame = ch.queue_unbind(queue=q_name, exchange=exg_name, routing_key=routing_key) self.assertIsInstance(frame.method, pika.spec.Queue.UnbindOk) # Verify that the queue is now unreachable via that binding with self.assertRaises(pika.exceptions.UnroutableError): ch.publish(exg_name, routing_key, body='TestQueueBindAndUnbindAndPurge-2', mandatory=True) # Purge the queue and verify that 1 message was purged frame = ch.queue_purge(q_name) self.assertIsInstance(frame.method, pika.spec.Queue.PurgeOk) self.assertEqual(frame.method.message_count, 1) # Verify that the queue is now empty frame = ch.queue_declare(q_name, passive=True) self.assertEqual(frame.method.message_count, 0) class TestBasicGet(BlockingTestCaseBase): def tearDown(self): LOGGER.info('%s TEARING DOWN (%s)', datetime.utcnow(), self) def test(self): """BlockingChannel.basic_get""" LOGGER.info('%s STARTED (%s)', datetime.utcnow(), self) connection = self._connect() LOGGER.info('%s CONNECTED (%s)', datetime.utcnow(), self) ch = connection.channel() LOGGER.info('%s CREATED CHANNEL (%s)', datetime.utcnow(), self) q_name = 'TestBasicGet_q' + uuid.uuid1().hex # Place channel in publisher-acknowledgments mode so that the message # may be delivered synchronously to the queue by publishing it with # mandatory=True ch.confirm_delivery() LOGGER.info('%s ENABLED PUB-ACKS (%s)', datetime.utcnow(), self) # Declare a new queue ch.queue_declare(q_name, auto_delete=True) self.addCleanup(self._connect().channel().queue_delete, q_name) LOGGER.info('%s DECLARED QUEUE (%s)', datetime.utcnow(), self) # Verify result of getting a message from an empty queue msg = ch.basic_get(q_name, no_ack=False) self.assertTupleEqual(msg, (None, None, None)) LOGGER.info('%s GOT FROM EMPTY QUEUE (%s)', datetime.utcnow(), self) body = 'TestBasicGet' # Deposit a message in the queue via default exchange ch.publish(exchange='', routing_key=q_name, body=body, mandatory=True) LOGGER.info('%s PUBLISHED (%s)', datetime.utcnow(), self) # Get the message (method, properties, body) = ch.basic_get(q_name, no_ack=False) LOGGER.info('%s GOT FROM NON-EMPTY QUEUE (%s)', datetime.utcnow(), self) self.assertIsInstance(method, pika.spec.Basic.GetOk) self.assertEqual(method.delivery_tag, 1) self.assertFalse(method.redelivered) self.assertEqual(method.exchange, '') self.assertEqual(method.routing_key, q_name) self.assertEqual(method.message_count, 0) self.assertIsInstance(properties, pika.BasicProperties) self.assertIsNone(properties.headers) self.assertEqual(body, as_bytes(body)) # Ack it ch.basic_ack(delivery_tag=method.delivery_tag) LOGGER.info('%s ACKED (%s)', datetime.utcnow(), self) # Verify that the queue is now empty self._assert_exact_message_count_with_retries(channel=ch, queue=q_name, expected_count=0) class TestBasicReject(BlockingTestCaseBase): def test(self): """BlockingChannel.basic_reject""" connection = self._connect() ch = connection.channel() q_name = 'TestBasicReject_q' + uuid.uuid1().hex # Place channel in publisher-acknowledgments mode so that the message # may be delivered synchronously to the queue by publishing it with # mandatory=True ch.confirm_delivery() # Declare a new queue ch.queue_declare(q_name, auto_delete=True) self.addCleanup(self._connect().channel().queue_delete, q_name) # Deposit two messages in the queue via default exchange ch.publish(exchange='', routing_key=q_name, body='TestBasicReject1', mandatory=True) ch.publish(exchange='', routing_key=q_name, body='TestBasicReject2', mandatory=True) # Get the messages (rx_method, _, rx_body) = ch.basic_get(q_name, no_ack=False) self.assertEqual(rx_body, as_bytes('TestBasicReject1')) (rx_method, _, rx_body) = ch.basic_get(q_name, no_ack=False) self.assertEqual(rx_body, as_bytes('TestBasicReject2')) # Nack the second message ch.basic_reject(rx_method.delivery_tag, requeue=True) # Verify that exactly one message is present in the queue, namely the # second one self._assert_exact_message_count_with_retries(channel=ch, queue=q_name, expected_count=1) (rx_method, _, rx_body) = ch.basic_get(q_name, no_ack=False) self.assertEqual(rx_body, as_bytes('TestBasicReject2')) class TestBasicRejectNoRequeue(BlockingTestCaseBase): def test(self): """BlockingChannel.basic_reject with requeue=False""" connection = self._connect() ch = connection.channel() q_name = 'TestBasicRejectNoRequeue_q' + uuid.uuid1().hex # Place channel in publisher-acknowledgments mode so that the message # may be delivered synchronously to the queue by publishing it with # mandatory=True ch.confirm_delivery() # Declare a new queue ch.queue_declare(q_name, auto_delete=True) self.addCleanup(self._connect().channel().queue_delete, q_name) # Deposit two messages in the queue via default exchange ch.publish(exchange='', routing_key=q_name, body='TestBasicRejectNoRequeue1', mandatory=True) ch.publish(exchange='', routing_key=q_name, body='TestBasicRejectNoRequeue2', mandatory=True) # Get the messages (rx_method, _, rx_body) = ch.basic_get(q_name, no_ack=False) self.assertEqual(rx_body, as_bytes('TestBasicRejectNoRequeue1')) (rx_method, _, rx_body) = ch.basic_get(q_name, no_ack=False) self.assertEqual(rx_body, as_bytes('TestBasicRejectNoRequeue2')) # Nack the second message ch.basic_reject(rx_method.delivery_tag, requeue=False) # Verify that no messages are present in the queue self._assert_exact_message_count_with_retries(channel=ch, queue=q_name, expected_count=0) class TestBasicNack(BlockingTestCaseBase): def test(self): """BlockingChannel.basic_nack single message""" connection = self._connect() ch = connection.channel() q_name = 'TestBasicNack_q' + uuid.uuid1().hex # Place channel in publisher-acknowledgments mode so that the message # may be delivered synchronously to the queue by publishing it with # mandatory=True ch.confirm_delivery() # Declare a new queue ch.queue_declare(q_name, auto_delete=True) self.addCleanup(self._connect().channel().queue_delete, q_name) # Deposit two messages in the queue via default exchange ch.publish(exchange='', routing_key=q_name, body='TestBasicNack1', mandatory=True) ch.publish(exchange='', routing_key=q_name, body='TestBasicNack2', mandatory=True) # Get the messages (rx_method, _, rx_body) = ch.basic_get(q_name, no_ack=False) self.assertEqual(rx_body, as_bytes('TestBasicNack1')) (rx_method, _, rx_body) = ch.basic_get(q_name, no_ack=False) self.assertEqual(rx_body, as_bytes('TestBasicNack2')) # Nack the second message ch.basic_nack(rx_method.delivery_tag, multiple=False, requeue=True) # Verify that exactly one message is present in the queue, namely the # second one self._assert_exact_message_count_with_retries(channel=ch, queue=q_name, expected_count=1) (rx_method, _, rx_body) = ch.basic_get(q_name, no_ack=False) self.assertEqual(rx_body, as_bytes('TestBasicNack2')) class TestBasicNackNoRequeue(BlockingTestCaseBase): def test(self): """BlockingChannel.basic_nack with requeue=False""" connection = self._connect() ch = connection.channel() q_name = 'TestBasicNackNoRequeue_q' + uuid.uuid1().hex # Place channel in publisher-acknowledgments mode so that the message # may be delivered synchronously to the queue by publishing it with # mandatory=True ch.confirm_delivery() # Declare a new queue ch.queue_declare(q_name, auto_delete=True) self.addCleanup(self._connect().channel().queue_delete, q_name) # Deposit two messages in the queue via default exchange ch.publish(exchange='', routing_key=q_name, body='TestBasicNackNoRequeue1', mandatory=True) ch.publish(exchange='', routing_key=q_name, body='TestBasicNackNoRequeue2', mandatory=True) # Get the messages (rx_method, _, rx_body) = ch.basic_get(q_name, no_ack=False) self.assertEqual(rx_body, as_bytes('TestBasicNackNoRequeue1')) (rx_method, _, rx_body) = ch.basic_get(q_name, no_ack=False) self.assertEqual(rx_body, as_bytes('TestBasicNackNoRequeue2')) # Nack the second message ch.basic_nack(rx_method.delivery_tag, requeue=False) # Verify that no messages are present in the queue self._assert_exact_message_count_with_retries(channel=ch, queue=q_name, expected_count=0) class TestBasicNackMultiple(BlockingTestCaseBase): def test(self): """BlockingChannel.basic_nack multiple messages""" connection = self._connect() ch = connection.channel() q_name = 'TestBasicNackMultiple_q' + uuid.uuid1().hex # Place channel in publisher-acknowledgments mode so that the message # may be delivered synchronously to the queue by publishing it with # mandatory=True ch.confirm_delivery() # Declare a new queue ch.queue_declare(q_name, auto_delete=True) self.addCleanup(self._connect().channel().queue_delete, q_name) # Deposit two messages in the queue via default exchange ch.publish(exchange='', routing_key=q_name, body='TestBasicNackMultiple1', mandatory=True) ch.publish(exchange='', routing_key=q_name, body='TestBasicNackMultiple2', mandatory=True) # Get the messages (rx_method, _, rx_body) = ch.basic_get(q_name, no_ack=False) self.assertEqual(rx_body, as_bytes('TestBasicNackMultiple1')) (rx_method, _, rx_body) = ch.basic_get(q_name, no_ack=False) self.assertEqual(rx_body, as_bytes('TestBasicNackMultiple2')) # Nack both messages via the "multiple" option ch.basic_nack(rx_method.delivery_tag, multiple=True, requeue=True) # Verify that both messages are present in the queue self._assert_exact_message_count_with_retries(channel=ch, queue=q_name, expected_count=2) (rx_method, _, rx_body) = ch.basic_get(q_name, no_ack=False) self.assertEqual(rx_body, as_bytes('TestBasicNackMultiple1')) (rx_method, _, rx_body) = ch.basic_get(q_name, no_ack=False) self.assertEqual(rx_body, as_bytes('TestBasicNackMultiple2')) class TestBasicRecoverWithRequeue(BlockingTestCaseBase): def test(self): """BlockingChannel.basic_recover with requeue=True. NOTE: the requeue=False option is not supported by RabbitMQ broker as of this writing (using RabbitMQ 3.5.1) """ connection = self._connect() ch = connection.channel() q_name = ( 'TestBasicRecoverWithRequeue_q' + uuid.uuid1().hex) # Place channel in publisher-acknowledgments mode so that the message # may be delivered synchronously to the queue by publishing it with # mandatory=True ch.confirm_delivery() # Declare a new queue ch.queue_declare(q_name, auto_delete=True) self.addCleanup(self._connect().channel().queue_delete, q_name) # Deposit two messages in the queue via default exchange ch.publish(exchange='', routing_key=q_name, body='TestBasicRecoverWithRequeue1', mandatory=True) ch.publish(exchange='', routing_key=q_name, body='TestBasicRecoverWithRequeue2', mandatory=True) rx_messages = [] num_messages = 0 for msg in ch.consume(q_name, no_ack=False): num_messages += 1 if num_messages == 2: ch.basic_recover(requeue=True) if num_messages > 2: rx_messages.append(msg) if num_messages == 4: break else: self.fail('consumer aborted prematurely') # Get the messages (_, _, rx_body) = rx_messages[0] self.assertEqual(rx_body, as_bytes('TestBasicRecoverWithRequeue1')) (_, _, rx_body) = rx_messages[1] self.assertEqual(rx_body, as_bytes('TestBasicRecoverWithRequeue2')) class TestTxCommit(BlockingTestCaseBase): def test(self): """BlockingChannel.tx_commit""" connection = self._connect() ch = connection.channel() q_name = 'TestTxCommit_q' + uuid.uuid1().hex # Declare a new queue ch.queue_declare(q_name, auto_delete=True) self.addCleanup(self._connect().channel().queue_delete, q_name) # Select standard transaction mode frame = ch.tx_select() self.assertIsInstance(frame.method, pika.spec.Tx.SelectOk) # Deposit a message in the queue via default exchange ch.publish(exchange='', routing_key=q_name, body='TestTxCommit1', mandatory=True) # Verify that queue is still empty frame = ch.queue_declare(q_name, passive=True) self.assertEqual(frame.method.message_count, 0) # Commit the transaction ch.tx_commit() # Verify that the queue has the expected message frame = ch.queue_declare(q_name, passive=True) self.assertEqual(frame.method.message_count, 1) (_, _, rx_body) = ch.basic_get(q_name, no_ack=False) self.assertEqual(rx_body, as_bytes('TestTxCommit1')) class TestTxRollback(BlockingTestCaseBase): def test(self): """BlockingChannel.tx_commit""" connection = self._connect() ch = connection.channel() q_name = 'TestTxRollback_q' + uuid.uuid1().hex # Declare a new queue ch.queue_declare(q_name, auto_delete=True) self.addCleanup(self._connect().channel().queue_delete, q_name) # Select standard transaction mode frame = ch.tx_select() self.assertIsInstance(frame.method, pika.spec.Tx.SelectOk) # Deposit a message in the queue via default exchange ch.publish(exchange='', routing_key=q_name, body='TestTxRollback1', mandatory=True) # Verify that queue is still empty frame = ch.queue_declare(q_name, passive=True) self.assertEqual(frame.method.message_count, 0) # Roll back the transaction ch.tx_rollback() # Verify that the queue continues to be empty frame = ch.queue_declare(q_name, passive=True) self.assertEqual(frame.method.message_count, 0) class TestBasicConsumeFromUnknownQueueRaisesChannelClosed(BlockingTestCaseBase): def test(self): """ChannelClosed raised when consuming from unknown queue""" connection = self._connect() ch = connection.channel() q_name = ("TestBasicConsumeFromUnknownQueueRaisesChannelClosed_q_" + uuid.uuid1().hex) with self.assertRaises(pika.exceptions.ChannelClosed) as ex_cm: ch.basic_consume(lambda *args: None, q_name) self.assertEqual(ex_cm.exception.args[0], 404) class TestPublishAndBasicPublishWithPubacksUnroutable(BlockingTestCaseBase): def test(self): # pylint: disable=R0914 """BlockingChannel.publish amd basic_publish unroutable message with pubacks""" # pylint: disable=C0301 connection = self._connect() ch = connection.channel() exg_name = ('TestPublishAndBasicPublishUnroutable_exg_' + uuid.uuid1().hex) routing_key = 'TestPublishAndBasicPublishUnroutable' # Place channel in publisher-acknowledgments mode so that publishing # with mandatory=True will be synchronous res = ch.confirm_delivery() self.assertIsNone(res) # Declare a new exchange ch.exchange_declare(exg_name, exchange_type='direct') self.addCleanup(connection.channel().exchange_delete, exg_name) # Verify unroutable message handling using basic_publish res = ch.basic_publish(exg_name, routing_key=routing_key, body='', mandatory=True) self.assertEqual(res, False) # Verify unroutable message handling using publish msg2_headers = dict( test_name='TestPublishAndBasicPublishWithPubacksUnroutable') msg2_properties = pika.spec.BasicProperties(headers=msg2_headers) with self.assertRaises(pika.exceptions.UnroutableError) as cm: ch.publish(exg_name, routing_key=routing_key, body='', properties=msg2_properties, mandatory=True) (msg,) = cm.exception.messages self.assertIsInstance(msg, blocking_connection.ReturnedMessage) self.assertIsInstance(msg.method, pika.spec.Basic.Return) self.assertEqual(msg.method.reply_code, 312) self.assertEqual(msg.method.exchange, exg_name) self.assertEqual(msg.method.routing_key, routing_key) self.assertIsInstance(msg.properties, pika.BasicProperties) self.assertEqual(msg.properties.headers, msg2_headers) self.assertEqual(msg.body, as_bytes('')) class TestConfirmDeliveryAfterUnroutableMessage(BlockingTestCaseBase): def test(self): # pylint: disable=R0914 """BlockingChannel.confirm_delivery following unroutable message""" connection = self._connect() ch = connection.channel() exg_name = ('TestConfirmDeliveryAfterUnroutableMessage_exg_' + uuid.uuid1().hex) routing_key = 'TestConfirmDeliveryAfterUnroutableMessage' # Declare a new exchange ch.exchange_declare(exg_name, exchange_type='direct') self.addCleanup(connection.channel().exchange_delete, exg_name) # Register on-return callback returned_messages = [] ch.add_on_return_callback(lambda *args: returned_messages.append(args)) # Emit unroutable message without pubacks res = ch.basic_publish(exg_name, routing_key=routing_key, body='', mandatory=True) self.assertEqual(res, True) # Select delivery confirmations ch.confirm_delivery() # Verify that unroutable message is in pending events self.assertEqual(len(ch._pending_events), 1) self.assertIsInstance(ch._pending_events[0], blocking_connection._ReturnedMessageEvt) # Verify that repr of _ReturnedMessageEvt instance does crash repr(ch._pending_events[0]) # Dispach events connection.process_data_events() self.assertEqual(len(ch._pending_events), 0) # Verify that unroutable message was dispatched ((channel, method, properties, body,),) = returned_messages self.assertIs(channel, ch) self.assertIsInstance(method, pika.spec.Basic.Return) self.assertEqual(method.reply_code, 312) self.assertEqual(method.exchange, exg_name) self.assertEqual(method.routing_key, routing_key) self.assertIsInstance(properties, pika.BasicProperties) self.assertEqual(body, as_bytes('')) class TestUnroutableMessagesReturnedInNonPubackMode(BlockingTestCaseBase): def test(self): # pylint: disable=R0914 """BlockingChannel: unroutable messages is returned in non-puback mode""" # pylint: disable=C0301 connection = self._connect() ch = connection.channel() exg_name = ( 'TestUnroutableMessageReturnedInNonPubackMode_exg_' + uuid.uuid1().hex) routing_key = 'TestUnroutableMessageReturnedInNonPubackMode' # Declare a new exchange ch.exchange_declare(exg_name, exchange_type='direct') self.addCleanup(connection.channel().exchange_delete, exg_name) # Register on-return callback returned_messages = [] ch.add_on_return_callback( lambda *args: returned_messages.append(args)) # Emit unroutable messages without pubacks ch.publish(exg_name, routing_key=routing_key, body='msg1', mandatory=True) ch.publish(exg_name, routing_key=routing_key, body='msg2', mandatory=True) # Process I/O until Basic.Return are dispatched while len(returned_messages) < 2: connection.process_data_events() self.assertEqual(len(returned_messages), 2) self.assertEqual(len(ch._pending_events), 0) # Verify returned messages (channel, method, properties, body,) = returned_messages[0] self.assertIs(channel, ch) self.assertIsInstance(method, pika.spec.Basic.Return) self.assertEqual(method.reply_code, 312) self.assertEqual(method.exchange, exg_name) self.assertEqual(method.routing_key, routing_key) self.assertIsInstance(properties, pika.BasicProperties) self.assertEqual(body, as_bytes('msg1')) (channel, method, properties, body,) = returned_messages[1] self.assertIs(channel, ch) self.assertIsInstance(method, pika.spec.Basic.Return) self.assertEqual(method.reply_code, 312) self.assertEqual(method.exchange, exg_name) self.assertEqual(method.routing_key, routing_key) self.assertIsInstance(properties, pika.BasicProperties) self.assertEqual(body, as_bytes('msg2')) class TestUnroutableMessageReturnedInPubackMode(BlockingTestCaseBase): def test(self): # pylint: disable=R0914 """BlockingChannel: unroutable messages is returned in puback mode""" connection = self._connect() ch = connection.channel() exg_name = ( 'TestUnroutableMessageReturnedInPubackMode_exg_' + uuid.uuid1().hex) routing_key = 'TestUnroutableMessageReturnedInPubackMode' # Declare a new exchange ch.exchange_declare(exg_name, exchange_type='direct') self.addCleanup(connection.channel().exchange_delete, exg_name) # Select delivery confirmations ch.confirm_delivery() # Register on-return callback returned_messages = [] ch.add_on_return_callback( lambda *args: returned_messages.append(args)) # Emit unroutable messages with pubacks res = ch.basic_publish(exg_name, routing_key=routing_key, body='msg1', mandatory=True) self.assertEqual(res, False) res = ch.basic_publish(exg_name, routing_key=routing_key, body='msg2', mandatory=True) self.assertEqual(res, False) # Verify that unroutable messages are already in pending events self.assertEqual(len(ch._pending_events), 2) self.assertIsInstance(ch._pending_events[0], blocking_connection._ReturnedMessageEvt) self.assertIsInstance(ch._pending_events[1], blocking_connection._ReturnedMessageEvt) # Verify that repr of _ReturnedMessageEvt instance does crash repr(ch._pending_events[0]) repr(ch._pending_events[1]) # Dispatch events connection.process_data_events() self.assertEqual(len(ch._pending_events), 0) # Verify returned messages (channel, method, properties, body,) = returned_messages[0] self.assertIs(channel, ch) self.assertIsInstance(method, pika.spec.Basic.Return) self.assertEqual(method.reply_code, 312) self.assertEqual(method.exchange, exg_name) self.assertEqual(method.routing_key, routing_key) self.assertIsInstance(properties, pika.BasicProperties) self.assertEqual(body, as_bytes('msg1')) (channel, method, properties, body,) = returned_messages[1] self.assertIs(channel, ch) self.assertIsInstance(method, pika.spec.Basic.Return) self.assertEqual(method.reply_code, 312) self.assertEqual(method.exchange, exg_name) self.assertEqual(method.routing_key, routing_key) self.assertIsInstance(properties, pika.BasicProperties) self.assertEqual(body, as_bytes('msg2')) class TestBasicPublishDeliveredWhenPendingUnroutable(BlockingTestCaseBase): def test(self): # pylint: disable=R0914 """BlockingChannel.basic_publish msg delivered despite pending unroutable message""" # pylint: disable=C0301 connection = self._connect() ch = connection.channel() q_name = ('TestBasicPublishDeliveredWhenPendingUnroutable_q' + uuid.uuid1().hex) exg_name = ('TestBasicPublishDeliveredWhenPendingUnroutable_exg_' + uuid.uuid1().hex) routing_key = 'TestBasicPublishDeliveredWhenPendingUnroutable' # Declare a new exchange ch.exchange_declare(exg_name, exchange_type='direct') self.addCleanup(connection.channel().exchange_delete, exg_name) # Declare a new queue ch.queue_declare(q_name, auto_delete=True) self.addCleanup(self._connect().channel().queue_delete, q_name) # Bind the queue to the exchange using routing key ch.queue_bind(q_name, exchange=exg_name, routing_key=routing_key) # Attempt to send an unroutable message in the queue via basic_publish res = ch.basic_publish(exg_name, routing_key='', body='unroutable-message', mandatory=True) self.assertEqual(res, True) # Flush connection to force Basic.Return connection.channel().close() # Deposit a routable message in the queue res = ch.basic_publish(exg_name, routing_key=routing_key, body='routable-message', mandatory=True) self.assertEqual(res, True) # Wait for the queue to get the routable message self._assert_exact_message_count_with_retries(channel=ch, queue=q_name, expected_count=1) msg = ch.basic_get(q_name) # Check the first message self.assertIsInstance(msg, tuple) rx_method, rx_properties, rx_body = msg self.assertIsInstance(rx_method, pika.spec.Basic.GetOk) self.assertEqual(rx_method.delivery_tag, 1) self.assertFalse(rx_method.redelivered) self.assertEqual(rx_method.exchange, exg_name) self.assertEqual(rx_method.routing_key, routing_key) self.assertIsInstance(rx_properties, pika.BasicProperties) self.assertEqual(rx_body, as_bytes('routable-message')) # There shouldn't be any more events now self.assertFalse(ch._pending_events) # Ack the message ch.basic_ack(delivery_tag=rx_method.delivery_tag, multiple=False) # Verify that the queue is now empty self._assert_exact_message_count_with_retries(channel=ch, queue=q_name, expected_count=0) class TestPublishAndConsumeWithPubacksAndQosOfOne(BlockingTestCaseBase): def test(self): # pylint: disable=R0914,R0915 """BlockingChannel.basic_publish, publish, basic_consume, QoS, \ Basic.Cancel from broker """ connection = self._connect() ch = connection.channel() q_name = 'TestPublishAndConsumeAndQos_q' + uuid.uuid1().hex exg_name = 'TestPublishAndConsumeAndQos_exg_' + uuid.uuid1().hex routing_key = 'TestPublishAndConsumeAndQos' # Place channel in publisher-acknowledgments mode so that publishing # with mandatory=True will be synchronous res = ch.confirm_delivery() self.assertIsNone(res) # Declare a new exchange ch.exchange_declare(exg_name, exchange_type='direct') self.addCleanup(connection.channel().exchange_delete, exg_name) # Declare a new queue ch.queue_declare(q_name, auto_delete=True) self.addCleanup(self._connect().channel().queue_delete, q_name) # Bind the queue to the exchange using routing key ch.queue_bind(q_name, exchange=exg_name, routing_key=routing_key) # Deposit a message in the queue via basic_publish msg1_headers = dict( test_name='TestPublishAndConsumeWithPubacksAndQosOfOne') msg1_properties = pika.spec.BasicProperties(headers=msg1_headers) res = ch.basic_publish(exg_name, routing_key=routing_key, body='via-basic_publish', properties=msg1_properties, mandatory=True) self.assertEqual(res, True) # Deposit another message in the queue via publish ch.publish(exg_name, routing_key, body='via-publish', mandatory=True) # Check that the queue now has two messages frame = ch.queue_declare(q_name, passive=True) self.assertEqual(frame.method.message_count, 2) # Configure QoS for one message ch.basic_qos(prefetch_size=0, prefetch_count=1, all_channels=False) # Create a consumer rx_messages = [] consumer_tag = ch.basic_consume( lambda *args: rx_messages.append(args), q_name, no_ack=False, exclusive=False, arguments=None) # Wait for first message to arrive while not rx_messages: connection.process_data_events(time_limit=None) self.assertEqual(len(rx_messages), 1) # Check the first message msg = rx_messages[0] self.assertIsInstance(msg, tuple) rx_ch, rx_method, rx_properties, rx_body = msg self.assertIs(rx_ch, ch) self.assertIsInstance(rx_method, pika.spec.Basic.Deliver) self.assertEqual(rx_method.consumer_tag, consumer_tag) self.assertEqual(rx_method.delivery_tag, 1) self.assertFalse(rx_method.redelivered) self.assertEqual(rx_method.exchange, exg_name) self.assertEqual(rx_method.routing_key, routing_key) self.assertIsInstance(rx_properties, pika.BasicProperties) self.assertEqual(rx_properties.headers, msg1_headers) self.assertEqual(rx_body, as_bytes('via-basic_publish')) # There shouldn't be any more events now self.assertFalse(ch._pending_events) # Ack the message so that the next one can arrive (we configured QoS # with prefetch_count=1) ch.basic_ack(delivery_tag=rx_method.delivery_tag, multiple=False) # Get the second message while len(rx_messages) < 2: connection.process_data_events(time_limit=None) self.assertEqual(len(rx_messages), 2) msg = rx_messages[1] self.assertIsInstance(msg, tuple) rx_ch, rx_method, rx_properties, rx_body = msg self.assertIs(rx_ch, ch) self.assertIsInstance(rx_method, pika.spec.Basic.Deliver) self.assertEqual(rx_method.consumer_tag, consumer_tag) self.assertEqual(rx_method.delivery_tag, 2) self.assertFalse(rx_method.redelivered) self.assertEqual(rx_method.exchange, exg_name) self.assertEqual(rx_method.routing_key, routing_key) self.assertIsInstance(rx_properties, pika.BasicProperties) self.assertEqual(rx_body, as_bytes('via-publish')) # There shouldn't be any more events now self.assertFalse(ch._pending_events) ch.basic_ack(delivery_tag=rx_method.delivery_tag, multiple=False) # Verify that the queue is now empty self._assert_exact_message_count_with_retries(channel=ch, queue=q_name, expected_count=0) # Attempt to consume again with a short timeout connection.process_data_events(time_limit=0.005) self.assertEqual(len(rx_messages), 2) # Delete the queue and wait for consumer cancellation rx_cancellations = [] ch.add_on_cancel_callback(rx_cancellations.append) ch.queue_delete(q_name) ch.start_consuming() self.assertEqual(len(rx_cancellations), 1) frame, = rx_cancellations self.assertEqual(frame.method.consumer_tag, consumer_tag) class TestBasicConsumeWithAckFromAnotherThread(BlockingTestCaseBase): def test(self): # pylint: disable=R0914,R0915 """BlockingChannel.basic_consume with ack from another thread and \ requesting basic_ack via add_callback_threadsafe """ # This test simulates processing of a message on another thread and # then requesting an ACK to be dispatched on the connection's thread # via BlockingConnection.add_callback_threadsafe connection = self._connect() ch = connection.channel() q_name = 'TestBasicConsumeWithAckFromAnotherThread_q' + uuid.uuid1().hex exg_name = ('TestBasicConsumeWithAckFromAnotherThread_exg' + uuid.uuid1().hex) routing_key = 'TestBasicConsumeWithAckFromAnotherThread' # Place channel in publisher-acknowledgments mode so that publishing # with mandatory=True will be synchronous (for convenience) res = ch.confirm_delivery() self.assertIsNone(res) # Declare a new exchange ch.exchange_declare(exg_name, exchange_type='direct') self.addCleanup(connection.channel().exchange_delete, exg_name) # Declare a new queue ch.queue_declare(q_name, auto_delete=True) self.addCleanup(self._connect().channel().queue_delete, q_name) # Bind the queue to the exchange using routing key ch.queue_bind(q_name, exchange=exg_name, routing_key=routing_key) # Publish 2 messages with mandatory=True for synchronous processing ch.publish(exg_name, routing_key, body='msg1', mandatory=True) ch.publish(exg_name, routing_key, body='last-msg', mandatory=True) # Configure QoS for one message so that the 2nd message will be # delivered only after the 1st one is ACKed ch.basic_qos(prefetch_size=0, prefetch_count=1, all_channels=False) # Create a consumer rx_messages = [] def ackAndEnqueueMessageViaAnotherThread(rx_ch, rx_method, rx_properties, # pylint: disable=W0613 rx_body): LOGGER.debug( '%s: Got message body=%r; delivery-tag=%r', datetime.now(), rx_body, rx_method.delivery_tag) # Request ACK dispatch via add_callback_threadsafe from other # thread; if last message, cancel consumer so that start_consuming # can return def processOnConnectionThread(): LOGGER.debug('%s: ACKing message body=%r; delivery-tag=%r', datetime.now(), rx_body, rx_method.delivery_tag) ch.basic_ack(delivery_tag=rx_method.delivery_tag, multiple=False) rx_messages.append(rx_body) # NOTE on python3, `b'last-msg' != 'last-msg'` if rx_body == b'last-msg': LOGGER.debug('%s: Canceling consumer consumer-tag=%r', datetime.now(), rx_method.consumer_tag) rx_ch.basic_cancel(rx_method.consumer_tag) # Spawn a thread to initiate ACKing timer = threading.Timer(0, lambda: connection.add_callback_threadsafe( processOnConnectionThread)) self.addCleanup(timer.cancel) timer.start() consumer_tag = ch.basic_consume( ackAndEnqueueMessageViaAnotherThread, q_name, no_ack=False, exclusive=False, arguments=None) # Wait for both messages LOGGER.debug('%s: calling start_consuming(); consumer tag=%r', datetime.now(), consumer_tag) ch.start_consuming() LOGGER.debug('%s: Returned from start_consuming(); consumer tag=%r', datetime.now(), consumer_tag) self.assertEqual(len(rx_messages), 2) self.assertEqual(rx_messages[0], b'msg1') self.assertEqual(rx_messages[1], b'last-msg') class TestConsumeGeneratorWithAckFromAnotherThread(BlockingTestCaseBase): def test(self): # pylint: disable=R0914,R0915 """BlockingChannel.consume and requesting basic_ack from another \ thread via add_callback_threadsafe """ connection = self._connect() ch = connection.channel() q_name = ('TestConsumeGeneratorWithAckFromAnotherThread_q' + uuid.uuid1().hex) exg_name = ('TestConsumeGeneratorWithAckFromAnotherThread_exg' + uuid.uuid1().hex) routing_key = 'TestConsumeGeneratorWithAckFromAnotherThread' # Place channel in publisher-acknowledgments mode so that publishing # with mandatory=True will be synchronous (for convenience) res = ch.confirm_delivery() self.assertIsNone(res) # Declare a new exchange ch.exchange_declare(exg_name, exchange_type='direct') self.addCleanup(connection.channel().exchange_delete, exg_name) # Declare a new queue ch.queue_declare(q_name, auto_delete=True) self.addCleanup(self._connect().channel().queue_delete, q_name) # Bind the queue to the exchange using routing key ch.queue_bind(q_name, exchange=exg_name, routing_key=routing_key) # Publish 2 messages with mandatory=True for synchronous processing ch.publish(exg_name, routing_key, body='msg1', mandatory=True) ch.publish(exg_name, routing_key, body='last-msg', mandatory=True) # Configure QoS for one message so that the 2nd message will be # delivered only after the 1st one is ACKed ch.basic_qos(prefetch_size=0, prefetch_count=1, all_channels=False) # Create a consumer rx_messages = [] def ackAndEnqueueMessageViaAnotherThread(rx_ch, rx_method, rx_properties, # pylint: disable=W0613 rx_body): LOGGER.debug( '%s: Got message body=%r; delivery-tag=%r', datetime.now(), rx_body, rx_method.delivery_tag) # Request ACK dispatch via add_callback_threadsafe from other # thread; if last message, cancel consumer so that consumer # generator completes def processOnConnectionThread(): LOGGER.debug('%s: ACKing message body=%r; delivery-tag=%r', datetime.now(), rx_body, rx_method.delivery_tag) ch.basic_ack(delivery_tag=rx_method.delivery_tag, multiple=False) rx_messages.append(rx_body) # NOTE on python3, `b'last-msg' != 'last-msg'` if rx_body == b'last-msg': LOGGER.debug('%s: Canceling consumer consumer-tag=%r', datetime.now(), rx_method.consumer_tag) # NOTE Need to use cancel() for the consumer generator # instead of basic_cancel() rx_ch.cancel() # Spawn a thread to initiate ACKing timer = threading.Timer(0, lambda: connection.add_callback_threadsafe( processOnConnectionThread)) self.addCleanup(timer.cancel) timer.start() for method, properties, body in ch.consume(q_name, no_ack=False): ackAndEnqueueMessageViaAnotherThread(rx_ch=ch, rx_method=method, rx_properties=properties, rx_body=body) self.assertEqual(len(rx_messages), 2) self.assertEqual(rx_messages[0], b'msg1') self.assertEqual(rx_messages[1], b'last-msg') class TestTwoBasicConsumersOnSameChannel(BlockingTestCaseBase): def test(self): # pylint: disable=R0914 """BlockingChannel: two basic_consume consumers on same channel """ connection = self._connect() ch = connection.channel() exg_name = 'TestPublishAndConsumeAndQos_exg_' + uuid.uuid1().hex q1_name = 'TestTwoBasicConsumersOnSameChannel_q1' + uuid.uuid1().hex q2_name = 'TestTwoBasicConsumersOnSameChannel_q2' + uuid.uuid1().hex q1_routing_key = 'TestTwoBasicConsumersOnSameChannel1' q2_routing_key = 'TestTwoBasicConsumersOnSameChannel2' # Place channel in publisher-acknowledgments mode so that publishing # with mandatory=True will be synchronous ch.confirm_delivery() # Declare a new exchange ch.exchange_declare(exg_name, exchange_type='direct') self.addCleanup(connection.channel().exchange_delete, exg_name) # Declare the two new queues and bind them to the exchange ch.queue_declare(q1_name, auto_delete=True) self.addCleanup(self._connect().channel().queue_delete, q1_name) ch.queue_bind(q1_name, exchange=exg_name, routing_key=q1_routing_key) ch.queue_declare(q2_name, auto_delete=True) self.addCleanup(self._connect().channel().queue_delete, q2_name) ch.queue_bind(q2_name, exchange=exg_name, routing_key=q2_routing_key) # Deposit messages in the queues q1_tx_message_bodies = ['q1_message+%s' % (i,) for i in pika.compat.xrange(100)] for message_body in q1_tx_message_bodies: ch.publish(exg_name, q1_routing_key, body=message_body, mandatory=True) q2_tx_message_bodies = ['q2_message+%s' % (i,) for i in pika.compat.xrange(150)] for message_body in q2_tx_message_bodies: ch.publish(exg_name, q2_routing_key, body=message_body, mandatory=True) # Create the consumers q1_rx_messages = [] q1_consumer_tag = ch.basic_consume( lambda *args: q1_rx_messages.append(args), q1_name, no_ack=False, exclusive=False, arguments=None) q2_rx_messages = [] q2_consumer_tag = ch.basic_consume( lambda *args: q2_rx_messages.append(args), q2_name, no_ack=False, exclusive=False, arguments=None) # Wait for all messages to be delivered while (len(q1_rx_messages) < len(q1_tx_message_bodies) or len(q2_rx_messages) < len(q2_tx_message_bodies)): connection.process_data_events(time_limit=None) self.assertEqual(len(q2_rx_messages), len(q2_tx_message_bodies)) # Verify the messages def validate_messages(rx_messages, routing_key, consumer_tag, tx_message_bodies): self.assertEqual(len(rx_messages), len(tx_message_bodies)) for msg, expected_body in zip(rx_messages, tx_message_bodies): self.assertIsInstance(msg, tuple) rx_ch, rx_method, rx_properties, rx_body = msg self.assertIs(rx_ch, ch) self.assertIsInstance(rx_method, pika.spec.Basic.Deliver) self.assertEqual(rx_method.consumer_tag, consumer_tag) self.assertFalse(rx_method.redelivered) self.assertEqual(rx_method.exchange, exg_name) self.assertEqual(rx_method.routing_key, routing_key) self.assertIsInstance(rx_properties, pika.BasicProperties) self.assertEqual(rx_body, as_bytes(expected_body)) # Validate q1 consumed messages validate_messages(rx_messages=q1_rx_messages, routing_key=q1_routing_key, consumer_tag=q1_consumer_tag, tx_message_bodies=q1_tx_message_bodies) # Validate q2 consumed messages validate_messages(rx_messages=q2_rx_messages, routing_key=q2_routing_key, consumer_tag=q2_consumer_tag, tx_message_bodies=q2_tx_message_bodies) # There shouldn't be any more events now self.assertFalse(ch._pending_events) class TestBasicCancelPurgesPendingConsumerCancellationEvt(BlockingTestCaseBase): def test(self): """BlockingChannel.basic_cancel purges pending _ConsumerCancellationEvt""" # pylint: disable=C0301 connection = self._connect() ch = connection.channel() q_name = ('TestBasicCancelPurgesPendingConsumerCancellationEvt_q' + uuid.uuid1().hex) ch.queue_declare(q_name) self.addCleanup(self._connect().channel().queue_delete, q_name) ch.publish('', routing_key=q_name, body='via-publish', mandatory=True) # Create a consumer rx_messages = [] consumer_tag = ch.basic_consume( lambda *args: rx_messages.append(args), q_name, no_ack=False, exclusive=False, arguments=None) # Wait for the published message to arrive, but don't consume it while not ch._pending_events: # Issue synchronous command that forces processing of incoming I/O connection.channel().close() self.assertEqual(len(ch._pending_events), 1) self.assertIsInstance(ch._pending_events[0], blocking_connection._ConsumerDeliveryEvt) # Delete the queue and wait for broker-initiated consumer cancellation ch.queue_delete(q_name) while len(ch._pending_events) < 2: # Issue synchronous command that forces processing of incoming I/O connection.channel().close() self.assertEqual(len(ch._pending_events), 2) self.assertIsInstance(ch._pending_events[1], blocking_connection._ConsumerCancellationEvt) # Issue consumer cancellation and verify that the pending # _ConsumerCancellationEvt instance was removed messages = ch.basic_cancel(consumer_tag) self.assertEqual(messages, []) self.assertEqual(len(ch._pending_events), 0) class TestBasicPublishWithoutPubacks(BlockingTestCaseBase): def test(self): # pylint: disable=R0914,R0915 """BlockingChannel.basic_publish without pubacks""" connection = self._connect() ch = connection.channel() q_name = 'TestBasicPublishWithoutPubacks_q' + uuid.uuid1().hex exg_name = 'TestBasicPublishWithoutPubacks_exg_' + uuid.uuid1().hex routing_key = 'TestBasicPublishWithoutPubacks' # Declare a new exchange ch.exchange_declare(exg_name, exchange_type='direct') self.addCleanup(connection.channel().exchange_delete, exg_name) # Declare a new queue ch.queue_declare(q_name, auto_delete=True) self.addCleanup(self._connect().channel().queue_delete, q_name) # Bind the queue to the exchange using routing key ch.queue_bind(q_name, exchange=exg_name, routing_key=routing_key) # Deposit a message in the queue via basic_publish and mandatory=True msg1_headers = dict( test_name='TestBasicPublishWithoutPubacks') msg1_properties = pika.spec.BasicProperties(headers=msg1_headers) res = ch.basic_publish(exg_name, routing_key=routing_key, body='via-basic_publish_mandatory=True', properties=msg1_properties, mandatory=True) self.assertEqual(res, True) # Deposit a message in the queue via basic_publish and mandatory=False res = ch.basic_publish(exg_name, routing_key=routing_key, body='via-basic_publish_mandatory=False', mandatory=False) self.assertEqual(res, True) # Wait for the messages to arrive in queue self._assert_exact_message_count_with_retries(channel=ch, queue=q_name, expected_count=2) # Create a consumer rx_messages = [] consumer_tag = ch.basic_consume( lambda *args: rx_messages.append(args), q_name, no_ack=False, exclusive=False, arguments=None) # Wait for first message to arrive while not rx_messages: connection.process_data_events(time_limit=None) self.assertGreaterEqual(len(rx_messages), 1) # Check the first message msg = rx_messages[0] self.assertIsInstance(msg, tuple) rx_ch, rx_method, rx_properties, rx_body = msg self.assertIs(rx_ch, ch) self.assertIsInstance(rx_method, pika.spec.Basic.Deliver) self.assertEqual(rx_method.consumer_tag, consumer_tag) self.assertEqual(rx_method.delivery_tag, 1) self.assertFalse(rx_method.redelivered) self.assertEqual(rx_method.exchange, exg_name) self.assertEqual(rx_method.routing_key, routing_key) self.assertIsInstance(rx_properties, pika.BasicProperties) self.assertEqual(rx_properties.headers, msg1_headers) self.assertEqual(rx_body, as_bytes('via-basic_publish_mandatory=True')) # There shouldn't be any more events now self.assertFalse(ch._pending_events) # Ack the message so that the next one can arrive (we configured QoS # with prefetch_count=1) ch.basic_ack(delivery_tag=rx_method.delivery_tag, multiple=False) # Get the second message while len(rx_messages) < 2: connection.process_data_events(time_limit=None) self.assertEqual(len(rx_messages), 2) msg = rx_messages[1] self.assertIsInstance(msg, tuple) rx_ch, rx_method, rx_properties, rx_body = msg self.assertIs(rx_ch, ch) self.assertIsInstance(rx_method, pika.spec.Basic.Deliver) self.assertEqual(rx_method.consumer_tag, consumer_tag) self.assertEqual(rx_method.delivery_tag, 2) self.assertFalse(rx_method.redelivered) self.assertEqual(rx_method.exchange, exg_name) self.assertEqual(rx_method.routing_key, routing_key) self.assertIsInstance(rx_properties, pika.BasicProperties) self.assertEqual(rx_body, as_bytes('via-basic_publish_mandatory=False')) # There shouldn't be any more events now self.assertFalse(ch._pending_events) ch.basic_ack(delivery_tag=rx_method.delivery_tag, multiple=False) # Verify that the queue is now empty self._assert_exact_message_count_with_retries(channel=ch, queue=q_name, expected_count=0) # Attempt to consume again with a short timeout connection.process_data_events(time_limit=0.005) self.assertEqual(len(rx_messages), 2) class TestPublishFromBasicConsumeCallback(BlockingTestCaseBase): def test(self): """BlockingChannel.basic_publish from basic_consume callback """ connection = self._connect() ch = connection.channel() src_q_name = ( 'TestPublishFromBasicConsumeCallback_src_q' + uuid.uuid1().hex) dest_q_name = ( 'TestPublishFromBasicConsumeCallback_dest_q' + uuid.uuid1().hex) # Place channel in publisher-acknowledgments mode so that publishing # with mandatory=True will be synchronous ch.confirm_delivery() # Declare source and destination queues ch.queue_declare(src_q_name, auto_delete=True) self.addCleanup(self._connect().channel().queue_delete, src_q_name) ch.queue_declare(dest_q_name, auto_delete=True) self.addCleanup(self._connect().channel().queue_delete, dest_q_name) # Deposit a message in the source queue ch.publish('', routing_key=src_q_name, body='via-publish', mandatory=True) # Create a consumer def on_consume(channel, method, props, body): channel.publish( '', routing_key=dest_q_name, body=body, properties=props, mandatory=True) channel.basic_ack(method.delivery_tag) ch.basic_consume(on_consume, src_q_name, no_ack=False, exclusive=False, arguments=None) # Consume from destination queue for _, _, rx_body in ch.consume(dest_q_name, no_ack=True): self.assertEqual(rx_body, as_bytes('via-publish')) break else: self.fail('failed to consume a messages from destination q') class TestStopConsumingFromBasicConsumeCallback(BlockingTestCaseBase): def test(self): """BlockingChannel.stop_consuming from basic_consume callback """ connection = self._connect() ch = connection.channel() q_name = ( 'TestStopConsumingFromBasicConsumeCallback_q' + uuid.uuid1().hex) # Place channel in publisher-acknowledgments mode so that publishing # with mandatory=True will be synchronous ch.confirm_delivery() # Declare the queue ch.queue_declare(q_name, auto_delete=False) self.addCleanup(connection.channel().queue_delete, q_name) # Deposit two messages in the queue ch.publish('', routing_key=q_name, body='via-publish1', mandatory=True) ch.publish('', routing_key=q_name, body='via-publish2', mandatory=True) # Create a consumer def on_consume(channel, method, props, body): # pylint: disable=W0613 channel.stop_consuming() channel.basic_ack(method.delivery_tag) ch.basic_consume(on_consume, q_name, no_ack=False, exclusive=False, arguments=None) ch.start_consuming() ch.close() ch = connection.channel() # Verify that only the second message is present in the queue _, _, rx_body = ch.basic_get(q_name) self.assertEqual(rx_body, as_bytes('via-publish2')) msg = ch.basic_get(q_name) self.assertTupleEqual(msg, (None, None, None)) class TestCloseChannelFromBasicConsumeCallback(BlockingTestCaseBase): def test(self): """BlockingChannel.close from basic_consume callback """ connection = self._connect() ch = connection.channel() q_name = ( 'TestCloseChannelFromBasicConsumeCallback_q' + uuid.uuid1().hex) # Place channel in publisher-acknowledgments mode so that publishing # with mandatory=True will be synchronous ch.confirm_delivery() # Declare the queue ch.queue_declare(q_name, auto_delete=False) self.addCleanup(connection.channel().queue_delete, q_name) # Deposit two messages in the queue ch.publish('', routing_key=q_name, body='via-publish1', mandatory=True) ch.publish('', routing_key=q_name, body='via-publish2', mandatory=True) # Create a consumer def on_consume(channel, method, props, body): # pylint: disable=W0613 channel.close() ch.basic_consume(on_consume, q_name, no_ack=False, exclusive=False, arguments=None) ch.start_consuming() self.assertTrue(ch.is_closed) # Verify that both messages are present in the queue ch = connection.channel() _, _, rx_body = ch.basic_get(q_name) self.assertEqual(rx_body, as_bytes('via-publish1')) _, _, rx_body = ch.basic_get(q_name) self.assertEqual(rx_body, as_bytes('via-publish2')) class TestCloseConnectionFromBasicConsumeCallback(BlockingTestCaseBase): def test(self): """BlockingConnection.close from basic_consume callback """ connection = self._connect() ch = connection.channel() q_name = ( 'TestCloseConnectionFromBasicConsumeCallback_q' + uuid.uuid1().hex) # Place channel in publisher-acknowledgments mode so that publishing # with mandatory=True will be synchronous ch.confirm_delivery() # Declare the queue ch.queue_declare(q_name, auto_delete=False) self.addCleanup(self._connect().channel().queue_delete, q_name) # Deposit two messages in the queue ch.publish('', routing_key=q_name, body='via-publish1', mandatory=True) ch.publish('', routing_key=q_name, body='via-publish2', mandatory=True) # Create a consumer def on_consume(channel, method, props, body): # pylint: disable=W0613 connection.close() ch.basic_consume(on_consume, q_name, no_ack=False, exclusive=False, arguments=None) ch.start_consuming() self.assertTrue(ch.is_closed) self.assertTrue(connection.is_closed) # Verify that both messages are present in the queue ch = self._connect().channel() _, _, rx_body = ch.basic_get(q_name) self.assertEqual(rx_body, as_bytes('via-publish1')) _, _, rx_body = ch.basic_get(q_name) self.assertEqual(rx_body, as_bytes('via-publish2')) class TestNonPubAckPublishAndConsumeHugeMessage(BlockingTestCaseBase): def test(self): """BlockingChannel.publish/consume huge message""" connection = self._connect() ch = connection.channel() q_name = 'TestPublishAndConsumeHugeMessage_q' + uuid.uuid1().hex body = 'a' * 1000000 # Declare a new queue ch.queue_declare(q_name, auto_delete=False) self.addCleanup(self._connect().channel().queue_delete, q_name) # Publish a message to the queue by way of default exchange ch.publish(exchange='', routing_key=q_name, body=body) LOGGER.info('Published message body size=%s', len(body)) # Consume the message for rx_method, rx_props, rx_body in ch.consume(q_name, no_ack=False, exclusive=False, arguments=None): self.assertIsInstance(rx_method, pika.spec.Basic.Deliver) self.assertEqual(rx_method.delivery_tag, 1) self.assertFalse(rx_method.redelivered) self.assertEqual(rx_method.exchange, '') self.assertEqual(rx_method.routing_key, q_name) self.assertIsInstance(rx_props, pika.BasicProperties) self.assertEqual(rx_body, as_bytes(body)) # Ack the message ch.basic_ack(delivery_tag=rx_method.delivery_tag, multiple=False) break # There shouldn't be any more events now self.assertFalse(ch._queue_consumer_generator.pending_events) # Verify that the queue is now empty ch.close() ch = connection.channel() self._assert_exact_message_count_with_retries(channel=ch, queue=q_name, expected_count=0) class TestNonPubackPublishAndConsumeManyMessages(BlockingTestCaseBase): def test(self): """BlockingChannel non-pub-ack publish/consume many messages""" connection = self._connect() ch = connection.channel() q_name = ('TestNonPubackPublishAndConsumeManyMessages_q' + uuid.uuid1().hex) body = 'b' * 1024 num_messages_to_publish = 500 # Declare a new queue ch.queue_declare(q_name, auto_delete=False) self.addCleanup(self._connect().channel().queue_delete, q_name) for _ in pika.compat.xrange(num_messages_to_publish): # Publish a message to the queue by way of default exchange ch.publish(exchange='', routing_key=q_name, body=body) # Consume the messages num_consumed = 0 for rx_method, rx_props, rx_body in ch.consume(q_name, no_ack=False, exclusive=False, arguments=None): num_consumed += 1 self.assertIsInstance(rx_method, pika.spec.Basic.Deliver) self.assertEqual(rx_method.delivery_tag, num_consumed) self.assertFalse(rx_method.redelivered) self.assertEqual(rx_method.exchange, '') self.assertEqual(rx_method.routing_key, q_name) self.assertIsInstance(rx_props, pika.BasicProperties) self.assertEqual(rx_body, as_bytes(body)) # Ack the message ch.basic_ack(delivery_tag=rx_method.delivery_tag, multiple=False) if num_consumed >= num_messages_to_publish: break # There shouldn't be any more events now self.assertFalse(ch._queue_consumer_generator.pending_events) ch.close() self.assertIsNone(ch._queue_consumer_generator) # Verify that the queue is now empty ch = connection.channel() self._assert_exact_message_count_with_retries(channel=ch, queue=q_name, expected_count=0) class TestBasicCancelWithNonAckableConsumer(BlockingTestCaseBase): def test(self): """BlockingChannel user cancels non-ackable consumer via basic_cancel""" connection = self._connect() ch = connection.channel() q_name = ( 'TestBasicCancelWithNonAckableConsumer_q' + uuid.uuid1().hex) body1 = 'a' * 1024 body2 = 'b' * 2048 # Declare a new queue ch.queue_declare(q_name, auto_delete=False) self.addCleanup(self._connect().channel().queue_delete, q_name) # Publish two messages to the queue by way of default exchange ch.publish(exchange='', routing_key=q_name, body=body1) ch.publish(exchange='', routing_key=q_name, body=body2) # Wait for queue to contain both messages self._assert_exact_message_count_with_retries(channel=ch, queue=q_name, expected_count=2) # Create a non-ackable consumer consumer_tag = ch.basic_consume(lambda *x: None, q_name, no_ack=True, exclusive=False, arguments=None) # Wait for all messages to be sent by broker to client self._assert_exact_message_count_with_retries(channel=ch, queue=q_name, expected_count=0) # Cancel the consumer messages = ch.basic_cancel(consumer_tag) # Both messages should have been on their way when we cancelled self.assertEqual(len(messages), 2) _, _, rx_body1 = messages[0] self.assertEqual(rx_body1, as_bytes(body1)) _, _, rx_body2 = messages[1] self.assertEqual(rx_body2, as_bytes(body2)) ch.close() ch = connection.channel() # Verify that the queue is now empty frame = ch.queue_declare(q_name, passive=True) self.assertEqual(frame.method.message_count, 0) class TestBasicCancelWithAckableConsumer(BlockingTestCaseBase): def test(self): """BlockingChannel user cancels ackable consumer via basic_cancel""" connection = self._connect() ch = connection.channel() q_name = ( 'TestBasicCancelWithAckableConsumer_q' + uuid.uuid1().hex) body1 = 'a' * 1024 body2 = 'b' * 2048 # Declare a new queue ch.queue_declare(q_name, auto_delete=False) self.addCleanup(self._connect().channel().queue_delete, q_name) # Publish two messages to the queue by way of default exchange ch.publish(exchange='', routing_key=q_name, body=body1) ch.publish(exchange='', routing_key=q_name, body=body2) # Wait for queue to contain both messages self._assert_exact_message_count_with_retries(channel=ch, queue=q_name, expected_count=2) # Create an ackable consumer consumer_tag = ch.basic_consume(lambda *x: None, q_name, no_ack=False, exclusive=False, arguments=None) # Wait for all messages to be sent by broker to client self._assert_exact_message_count_with_retries(channel=ch, queue=q_name, expected_count=0) # Cancel the consumer messages = ch.basic_cancel(consumer_tag) # Both messages should have been on their way when we cancelled self.assertEqual(len(messages), 0) ch.close() ch = connection.channel() # Verify that canceling the ackable consumer restored both messages self._assert_exact_message_count_with_retries(channel=ch, queue=q_name, expected_count=2) class TestUnackedMessageAutoRestoredToQueueOnChannelClose(BlockingTestCaseBase): def test(self): """BlockingChannel unacked message restored to q on channel close """ connection = self._connect() ch = connection.channel() q_name = ('TestUnackedMessageAutoRestoredToQueueOnChannelClose_q' + uuid.uuid1().hex) body1 = 'a' * 1024 body2 = 'b' * 2048 # Declare a new queue ch.queue_declare(q_name, auto_delete=False) self.addCleanup(self._connect().channel().queue_delete, q_name) # Publish two messages to the queue by way of default exchange ch.publish(exchange='', routing_key=q_name, body=body1) ch.publish(exchange='', routing_key=q_name, body=body2) # Consume the events, but don't ack rx_messages = [] ch.basic_consume(lambda *args: rx_messages.append(args), q_name, no_ack=False, exclusive=False, arguments=None) while len(rx_messages) != 2: connection.process_data_events(time_limit=None) self.assertEqual(rx_messages[0][1].delivery_tag, 1) self.assertEqual(rx_messages[1][1].delivery_tag, 2) # Verify no more ready messages in queue frame = ch.queue_declare(q_name, passive=True) self.assertEqual(frame.method.message_count, 0) # Closing channel should restore messages back to queue ch.close() # Verify that there are two messages in q now ch = connection.channel() self._assert_exact_message_count_with_retries(channel=ch, queue=q_name, expected_count=2) class TestNoAckMessageNotRestoredToQueueOnChannelClose(BlockingTestCaseBase): def test(self): """BlockingChannel unacked message restored to q on channel close """ connection = self._connect() ch = connection.channel() q_name = ('TestNoAckMessageNotRestoredToQueueOnChannelClose_q' + uuid.uuid1().hex) body1 = 'a' * 1024 body2 = 'b' * 2048 # Declare a new queue ch.queue_declare(q_name, auto_delete=False) self.addCleanup(self._connect().channel().queue_delete, q_name) # Publish two messages to the queue by way of default exchange ch.publish(exchange='', routing_key=q_name, body=body1) ch.publish(exchange='', routing_key=q_name, body=body2) # Consume, but don't ack num_messages = 0 for rx_method, _, _ in ch.consume(q_name, no_ack=True, exclusive=False): num_messages += 1 self.assertEqual(rx_method.delivery_tag, num_messages) if num_messages == 2: break else: self.fail('expected 2 messages, but consumed %i' % (num_messages,)) # Verify no more ready messages in queue frame = ch.queue_declare(q_name, passive=True) self.assertEqual(frame.method.message_count, 0) # Closing channel should not restore no-ack messages back to queue ch.close() # Verify that there are no messages in q now ch = connection.channel() frame = ch.queue_declare(q_name, passive=True) self.assertEqual(frame.method.message_count, 0) class TestConsumeInactivityTimeout(BlockingTestCaseBase): def test(self): """BlockingChannel consume returns 3-tuple on inactivity timeout """ connection = self._connect() ch = connection.channel() q_name = ('TestConsumeInactivityTimeout_q' + uuid.uuid1().hex) # Declare a new queue ch.queue_declare(q_name, auto_delete=True) # Consume, but don't ack for msg in ch.consume(q_name, inactivity_timeout=0.1): a, b, c = msg self.assertIsNone(a) self.assertIsNone(b) self.assertIsNone(c) break else: self.fail('expected (None, None, None), but got %s' % msg) ch.close() class TestChannelFlow(BlockingTestCaseBase): def test(self): """BlockingChannel Channel.Flow activate and deactivate """ connection = self._connect() ch = connection.channel() q_name = ('TestChannelFlow_q' + uuid.uuid1().hex) # Declare a new queue ch.queue_declare(q_name, auto_delete=False) self.addCleanup(self._connect().channel().queue_delete, q_name) # Verify zero active consumers on the queue frame = ch.queue_declare(q_name, passive=True) self.assertEqual(frame.method.consumer_count, 0) # Create consumer ch.basic_consume(lambda *args: None, q_name) # Verify one active consumer on the queue now frame = ch.queue_declare(q_name, passive=True) self.assertEqual(frame.method.consumer_count, 1) # Activate flow from default state (active by default) active = ch.flow(True) self.assertEqual(active, True) # Verify still one active consumer on the queue now frame = ch.queue_declare(q_name, passive=True) self.assertEqual(frame.method.consumer_count, 1) # active=False is not supported by RabbitMQ per # https://www.rabbitmq.com/specification.html: # "active=false is not supported by the server. Limiting prefetch with # basic.qos provides much better control" ## # Deactivate flow ## active = ch.flow(False) ## self.assertEqual(active, False) ## ## # Verify zero active consumers on the queue now ## frame = ch.queue_declare(q_name, passive=True) ## self.assertEqual(frame.method.consumer_count, 0) ## ## # Re-activate flow ## active = ch.flow(True) ## self.assertEqual(active, True) ## ## # Verify one active consumers on the queue once again ## frame = ch.queue_declare(q_name, passive=True) ## self.assertEqual(frame.method.consumer_count, 1) if __name__ == '__main__': unittest.main()