async def test_reply(connect_to_broker): """Publish a message with reply_to and correlation_id, get it from queue, use Message.reply(), get the reply message from the server""" async with await connect_to_broker() as connection: async with connection.channel() as channel: await channel.select_confirm() await channel.delete_queue('testcase_queue') await channel.declare_queue('testcase_queue') await channel.delete_exchange('testcase_exchange') await channel.declare_exchange('testcase_exchange', 'direct') await channel.bind_queue('testcase_queue', 'testcase_exchange', 'testcase_routing_key') reply_queue_name = (await channel.declare_queue( '', exclusive=True, durable=False)).queue_name await channel.publish('testcase_exchange', 'testcase_routing_key', 'original body', correlation_id='test correlation id', reply_to=reply_queue_name) orig_message = await channel.get('testcase_queue') assert orig_message.properties.correlation_id == 'test correlation id' assert orig_message.decode() == 'original body' await orig_message.reply(b'reply body') await orig_message.ack() reply_message = await channel.get(reply_queue_name, no_ack=True) assert reply_message.properties.correlation_id == 'test correlation id' assert reply_message.body == b'reply body' check_clean_channel_close(channel) check_clean_connection_close(connection)
async def test_amqplain_auth_directly(connect_to_broker): """directly as in no Authentication to choose the mech first""" auth = AMQPlainAuth('guest', 'guest') async with await connect_to_broker(auth=auth) as connection: async with connection.channel() as channel: pass check_clean_channel_close(channel) check_clean_connection_close(connection)
async def test_no_heartbeats(event_loop, connect_to_broker): # TODO: improve this so that there is a Connection subclass which overrides _heartbeat_subscriber and raises # an exception if there is a heartbeat sent. Might need a longer sleep, as Rabbit's default is 60 secs async with await connect_to_broker(heartbeat_interval=0) as connection: assert connection._heartbeat_interval == 0 async with connection.channel(): await asyncio.sleep(10.0, loop=event_loop) check_clean_connection_close(connection)
async def test_get_empty(connect_to_broker): async with await connect_to_broker() as connection: async with connection.channel() as channel: await channel.delete_queue('testcase_queue') await channel.declare_queue('testcase_queue') with raises(EmptyQueue): await channel.get('testcase_queue') check_clean_channel_close(channel) check_clean_connection_close(connection)
async def test_exchange_declare(connect_to_broker): async with await connect_to_broker() as connection: async with connection.channel() as channel: await channel.delete_exchange('testcase_exchange') await channel.declare_exchange('testcase_exchange', 'fanout') await channel.delete_exchange('testcase_exchange') await channel.delete_exchange('testcase_exchange') check_clean_channel_close(channel) check_clean_connection_close(connection)
async def test_queue_declare(connect_to_broker): async with await connect_to_broker() as connection: async with connection.channel() as channel: await channel.delete_queue('testcase_queue') await channel.declare_queue('testcase_queue') assert await channel.purge_queue('testcase_queue') == 0 assert await channel.delete_queue('testcase_queue') == 0 assert await channel.delete_queue('testcase_queue') == 0 check_clean_channel_close(channel) check_clean_connection_close(connection)
async def test_queue_bind(connect_to_broker): async with await connect_to_broker() as connection: async with connection.channel() as channel: await channel.declare_queue('testcase_queue') await channel.declare_exchange('testcase_exchange', 'fanout') await channel.bind_queue('testcase_queue', 'testcase_exchange', 'test_routing_key') await channel.unbind_queue('testcase_queue', 'testcase_exchange', 'test_routing_key') await channel.unbind_queue('testcase_queue', 'testcase_exchange', 'test_routing_key') check_clean_channel_close(channel) check_clean_connection_close(connection)
async def test_publish_bytes(connect_to_broker): async with await connect_to_broker() as connection: async with connection.channel() as channel: await channel.declare_exchange('testcase_exchange', 'fanout') await channel.select_confirm( ) # so server has a chance to nack/close await channel.publish('testcase_exchange', 'test_routing_key', b'abc') # bytes await channel.publish('testcase_exchange', 'test_routing_key', 'böö') # str await channel.delete_exchange('testcase_exchange') check_clean_channel_close(channel) check_clean_connection_close(connection)
async def test_publish_json(connect_to_broker): async with await connect_to_broker() as connection: async with connection.channel() as channel: await channel.declare_exchange('testcase_exchange', 'fanout') await channel.select_confirm() await channel.publish('testcase_exchange', 'test_routing_key', json=['string', { 'dict': 123 }]) await channel.delete_exchange('testcase_exchange') check_clean_channel_close(channel) check_clean_connection_close(connection)
async def test_concurrency(connect_to_broker, event_loop): logger = logging.getLogger('testcase') iterations = 10 publish_batch_size = 300 async with await connect_to_broker() as connection: async with connection.channel() as channel_one, connection.channel( ) as channel_two: names = await setup_concurrent_queues(channel_one) consumed_ids = set() ack_consumer_task = event_loop.create_task( consume_messages(channel_one, names, True, event_loop, 100, consumed_ids)) noack_consumer_one_task = event_loop.create_task( consume_messages(channel_two, names, True, event_loop, 500, consumed_ids)) publish_task = event_loop.create_task( publish_messages(channel_one, iterations, names, publish_batch_size, event_loop)) get_message_count = (iterations * publish_batch_size) // 4 get_ids = set() while len(get_ids) < get_message_count: logger.info('Got %s/%s messages', len(get_ids), get_message_count) try: message = await channel_two.get(names.get_queue_name, no_ack=False) except EmptyQueue: logger.info('No message in queue, sleeping a bit') await asyncio.sleep(0.1, loop=event_loop) else: get_ids.add(message.json()) await message.ack() await publish_task consume_message_count = (3 * iterations * publish_batch_size) // 4 while len(consumed_ids) < consume_message_count: logger.info('%s/%s messages consumed', len(consumed_ids), consume_message_count) await asyncio.sleep(0.1, loop=event_loop) for consumer_task in ack_consumer_task, noack_consumer_one_task: assert not consumer_task.done() consumer_task.cancel() with raises(asyncio.CancelledError): await consumer_task assert not channel_one._consumers assert not channel_two._consumers assert get_ids | consumed_ids == set( range(iterations * publish_batch_size)) check_clean_channel_close(channel_one) check_clean_channel_close(channel_two) check_clean_connection_close(connection)
async def test_caught_channel_exception(connect_to_broker): """A Channel closed exception raised inside async with channel() should not escape the context manager when it's caught inside the context manager""" exchange_name = 'testcase_exchange' routing_key = 'testcase_routing_key' async with await connect_to_broker() as connection: async with connection.channel() as channel: # without this channel.publish won't raise an exception, instead it occurs in channel.__aexit__ await channel.select_confirm() await channel.delete_exchange(exchange_name) with raises(ServerClosedChannel) as excinfo: await channel.publish(exchange_name, routing_key, 'test body 1') check_exchange_not_found(excinfo) assert channel._closing_exc is excinfo.value check_clean_connection_close(connection)
async def test_queue_exists(connect_to_broker): async with await connect_to_broker() as connection: async with connection.channel() as channel: queue_name = 'testcase_queue' await channel.delete_queue(queue_name) # durable, to see that the queue.declare(passive=True) from queue_exists doesn't mind await channel.declare_queue(queue_name, durable=True) await channel.assert_queue_exists(queue_name) await channel.delete_queue(queue_name) with raises(ServerClosedChannel) as excinfo: await channel.assert_queue_exists(queue_name) assert excinfo.value.reply_code == 404 assert excinfo.value.reply_text == "NOT_FOUND - no queue 'testcase_queue' in vhost '/'" assert excinfo.value.class_id == CLASS_QUEUE assert excinfo.value.method_id == METHOD_QUEUE_DECLARE assert channel._closing_exc is excinfo.value check_clean_connection_close(connection)
async def test_server_cancel(event_loop, connect_to_broker): async with await connect_to_broker() as connection: async with connection.channel() as channel: await channel.delete_queue('testcase_queue') await channel.declare_queue('testcase_queue') async with channel.consume('testcase_queue') as consumer: assert isinstance(consumer, Consumer) async def go(): async for message in consumer: raise ValueError("Didn't expect to get a message: {!r}".format(message)) raise ValueError('consumer iteration done') task = event_loop.create_task(go()) await channel.delete_queue('testcase_queue') with raises(ServerCancelledConsumer) as excinfo: await task assert consumer._closing_exc is excinfo.value await channel.delete_queue('testcase_queue') # just testing operations on channel still work check_clean_channel_close(channel) check_clean_connection_close(connection)
async def test_empty_message(event_loop, connect_to_broker): async with await connect_to_broker() as connection: async with connection.channel() as channel: await channel.select_confirm() await channel.delete_queue('testcase_queue') await channel.declare_queue('testcase_queue') await channel.delete_exchange('testcase_exchange') await channel.declare_exchange('testcase_exchange', 'direct') await channel.bind_queue('testcase_queue', 'testcase_exchange', 'testcase_routing_key') await channel.publish('testcase_exchange', 'testcase_routing_key', b'') message = await channel.get('testcase_queue') assert isinstance(message, GetMessage) assert message.body == b'' await message.ack() await asyncio.sleep(0.05, loop=event_loop) check_clean_channel_close(channel) check_clean_connection_close(connection)
async def test_uncaught_channel_exception(event_loop, connect_to_broker): """A Channel closed exception raised inside async with channel() should escape the context manager when it's not caught inside it""" exchange_name = 'testcase_exchange' routing_key = 'testcase_routing_key' async with await connect_to_broker() as connection: delete_succeeded = False publish_succeeded = False with raises(ServerClosedChannel) as excinfo: async with connection.channel() as channel: # without this channel.publish won't raise an exception, instead it occurs in channel.__aexit__ await channel.select_confirm() await channel.delete_exchange(exchange_name) delete_succeeded = True await channel.publish(exchange_name, routing_key, 'test body 1' ) # this ought to raise an exception publish_succeeded = True assert delete_succeeded assert not publish_succeeded check_exchange_not_found(excinfo) check_clean_connection_close(connection)
async def test_consume_and_cancel(connect_to_broker): async with await connect_to_broker() as connection: async with connection.channel() as channel: await channel.select_confirm() await channel.delete_queue('testcase_queue') await channel.declare_queue('testcase_queue') await channel.delete_exchange('testcase_exchange') await channel.declare_exchange('testcase_exchange', 'direct') await channel.bind_queue('testcase_queue', 'testcase_exchange', 'testcase_routing_key') await channel.publish('testcase_exchange', 'testcase_routing_key', b'message one') async with channel.consume('testcase_queue') as consumer: assert isinstance(consumer, Consumer) await channel.publish('testcase_exchange', 'testcase_routing_key', b'message two') assert consumer._message_queue.qsize() == 2, consumer._message_queue.qsize() async for i, message in aenumerate(consumer, 1): assert isinstance(message, DeliverMessage) await message.ack() if i == 2: break check_clean_consumer_cancel(consumer) check_clean_channel_close(channel) check_clean_connection_close(connection)
async def test_priority(event_loop, connect_to_broker): queue_name = 'testcase_queue' exchange_name = 'testcase_exchange' routing_key = 'testcase_routing_key' async with await connect_to_broker() as connection: async with connection.channel(prefetch_count=2) as channel: await channel.select_confirm() await asyncio.gather(channel.delete_queue(queue_name), channel.delete_exchange(exchange_name), loop=event_loop) await asyncio.gather(channel.declare_exchange( exchange_name, 'direct'), channel.declare_queue(queue_name, max_priority=7), loop=event_loop) await channel.bind_queue(queue_name, exchange_name, routing_key) message_priorities = [0, 3, 1, 6, 7, 9, 4, 2, 8, 5] # list items are (message_priorities index, priority). max_priority 7 -> 7 sorts before 9 expected_order = [(4, 7), (5, 9), (8, 8), (3, 6), (9, 5), (6, 4), (1, 3), (7, 2), (2, 1), (0, 0)] for i, message_priority in enumerate(message_priorities): await channel.publish(exchange_name, routing_key, 'message {} priority {}'.format( i, message_priority), priority=message_priority) async with channel.consume(queue_name) as consumer: async for i, (message, (send_i, expected_priority)) in aenumerate( azip(consumer, expected_order), 1): assert message.body.decode( ) == 'message {} priority {}'.format( send_i, expected_priority) assert message.properties.priority == expected_priority await message.ack() if i == len(message_priorities): break check_clean_channel_close(channel) check_clean_connection_close(connection)
async def test_recover(event_loop, connect_to_broker): logger = logging.getLogger('test_recover') message_n = 10 async with await connect_to_broker() as connection: async with connection.channel(prefetch_count=50) as channel: setup = await setup_channel(event_loop, channel) # TODO: test this, it causes the server to send basic.ack with multiple=True which we don't support yet await asyncio.gather(*( event_loop.create_task( channel.publish(setup.exchange_name, setup.routing_key, 'message {}'.format(i)) ) for i in range(10) ), loop=event_loop) #for i in range(10): # await channel.publish(setup.exchange_name, setup.routing_key, 'message {}'.format(i)) async with channel.consume(setup.queue_name) as consumer: async for i, message in aenumerate(consumer): logger.info( 'Got message number %s: redelivered = %s body = %r', i, message.redelivered, message.body) if i < 10: assert message.decode() == 'message {}'.format(i) assert message.redelivered is False else: # 10 -> 1, 11 -> 3, 12 -> 5 etc assert message.decode() == 'message {}'.format((i - 10) * 2 +1 ) assert message.redelivered is True if i % 2 == 0 or i >= 10: await message.ack() # messages 0, 2, 4, 6, 8 and 10+ elif i == 9: assert consumer._message_queue.qsize() == 0 await channel.recover(requeue=True) await asyncio.sleep(0.2, loop=event_loop) assert consumer._message_queue.qsize() == 5 # 10 - 5 = 5 if i == 14: assert consumer._message_queue.qsize() == 0 break check_clean_channel_close(channel) check_clean_connection_close(connection)
async def test_frame_max(event_loop, connect_to_broker): frame_max = 10 * 1024 # 10kB async with await connect_to_broker( frame_max=frame_max, connection_factory=FrameMaxConnection) as connection: assert connection.frame_max == frame_max assert isinstance(connection, FrameMaxConnection) async with connection.channel() as channel: setup = await setup_channel(event_loop, channel) body = b''.join(bytes([i]) * (frame_max - 8) for i in range(7)) + bytes([8]) * (7 * 8) assert len(body) == frame_max * 7 await channel.publish(setup.exchange_name, setup.routing_key, body) # check published message was sent in body frames with size == frame-max except the last one assert len(connection.sent_body_payloads) == 8 assert b''.join(connection.sent_body_payloads) == body for i, payload in enumerate(connection.sent_body_payloads[:-1]): assert payload == bytes([i]) * ( 10 * 1024 - 8) # 7 bytes for frame header, 1 for frame end # 7 * 8 = num of frames with max_frame payload * leftover/frame assert connection.sent_body_payloads[-1] == bytes([8]) * (7 * 8) # get a message, check that it's split into body frames with size == frame-max except the last one message = await channel.get(setup.queue_name) assert message.body == body assert len(connection.received_body_frames) == 8 for i, body_frame in enumerate( connection.received_body_frames[:-1]): assert body_frame.payload_size == 10 * 1024 - 8 assert body_frame.payload == bytes([i]) * (10 * 1024 - 8) assert connection.received_body_frames[-1].payload_size == 7 * 8 assert connection.received_body_frames[-1].payload == bytes( [8]) * (7 * 8) check_clean_channel_close(channel) check_clean_connection_close(connection)
async def test_consumer_priority(event_loop, connect_to_broker): """ - Set qos prefetch count to 2 - Publishes 10 messages to a queue - Start two consumers, one with priority 5 and one with 0 - Coordinate message acks in consumers so that broker will deliver messages to the high priority one when it can and to the low one when the high one is blocked - Check both consumers get the messages coordination from previous step should result in """ low_to_high = asyncio.Queue(loop=event_loop) # low signals to high that high can continue high_to_low = asyncio.Queue(loop=event_loop) # and vice versa async def consume_high(consumer): """This one takes messages 0, 1 and 4""" message_iter = aiter(consumer) message0 = await anext(message_iter) assert message0.body == b'message 0' message1 = await anext(message_iter) assert message1.body == b'message 1' assert consumer._message_queue.qsize() == 0 # now messages go to the low consumer too until we ack await message0.ack() # our queue gets another one assert (await low_to_high.get()) == 'l1' message4 = await anext(message_iter) assert message4.body == b'message 4' assert consumer._message_queue.qsize() == 0 # now messages go to the low consumer too until we ack high_to_low.put_nowait('h1') # signal that low can take rest of messages assert (await low_to_high.get()) == 'l2' await message1.ack() await message4.ack() assert consumer._message_queue.qsize() == 0 with raises(asyncio.TimeoutError): await asyncio.wait_for(anext(message_iter), 1.0, loop=event_loop) assert consumer._message_queue.qsize() == 0 async def consume_low(consumer): """This one takes messages 2, 3 and 5-9""" message_iter = aiter(consumer) message2 = await anext(message_iter) assert message2.body == b'message 2' message3 = await anext(message_iter) assert message3.body == b'message 3' assert consumer._message_queue.qsize() == 0 low_to_high.put_nowait('l1') # signal that high can ack a message assert (await high_to_low.get()) == 'h1' await message2.ack() await message3.ack() async for i, message in aenumerate(message_iter, 5): assert message.decode() == 'message {}'.format(i) await message.ack() if i == 9: break assert consumer._message_queue.qsize() == 0 low_to_high.put_nowait('l2') # go on then, nothing left for high async with await connect_to_broker() as connection: async with connection.channel(prefetch_count=2) as channel: setup = await setup_channel(event_loop, channel) for i in range(10): await channel.publish(setup.exchange_name, setup.routing_key, 'message {}'.format(i)) async with channel.consume(setup.queue_name, priority=5) as consumer_high: async with channel.consume(setup.queue_name) as consumer_low: # implicit priority 0 high_task = event_loop.create_task(consume_high(consumer_high)) low_task = event_loop.create_task(consume_low(consumer_low)) await asyncio.gather(high_task, low_task, loop=event_loop) check_clean_channel_close(channel) check_clean_connection_close(connection)
async def test_dead_letter_exchange(event_loop, connect_to_broker): def check_message_1(message): assert message.body == b'message 1' assert message.routing_key == 'orig_rk1' x_death_headers = message.properties.headers['x-death'] assert len(x_death_headers) == 1 x_death_header, = x_death_headers assert x_death_header['count'] == 1 assert x_death_header['reason'] == 'expired' assert x_death_header['queue'] == orig_queue_name_1 assert isinstance( x_death_header['time'], datetime) # TODO: convert this from UTC in datetime_pb assert x_death_header['exchange'] == orig_exchange_name assert x_death_header['routing-keys'] == ['orig_rk1'] def check_message_2(message): assert message.body == b'message 2' # routing should have changed because of dead_letter_routing_key assert message.routing_key == 'dead_rk' x_death_headers = message.properties.headers['x-death'] assert len(x_death_headers) == 1 x_death_header, = x_death_headers assert x_death_header['count'] == 1 assert x_death_header['reason'] == 'expired' assert x_death_header['queue'] == orig_queue_name_2 assert isinstance(x_death_header['time'], datetime) assert x_death_header['exchange'] == orig_exchange_name assert x_death_header['routing-keys'] == ['orig_rk2'] orig_queue_name_1 = 'testcase_queue_1' orig_queue_name_2 = 'testcase_queue_2' dead_queue_name = 'testcase_dead_queue' orig_exchange_name = 'testcase_orig_exchange' dead_exchange_name = 'testcase_dead_exchange' async with await connect_to_broker() as connection: async with connection.channel() as channel: await channel.select_confirm() await asyncio.gather(channel.delete_queue(orig_queue_name_1), channel.delete_queue(orig_queue_name_2), channel.delete_queue(dead_queue_name), channel.delete_exchange(dead_exchange_name), channel.delete_exchange(orig_exchange_name), loop=event_loop) # this is where the dead lettering puts rejected/expired messages await asyncio.gather(channel.declare_exchange( dead_exchange_name, 'direct'), channel.declare_queue(dead_queue_name), loop=event_loop) # queue 1 with dead lettering, keeps original routing key # queue 2 with dead lettering, modifies routing key await asyncio.gather( channel.declare_queue(orig_queue_name_1, dead_letter_exchange=dead_exchange_name, ttl=0), channel.declare_queue(orig_queue_name_2, dead_letter_exchange=dead_exchange_name, dead_letter_routing_key='dead_rk', ttl=0), channel.bind_queue(dead_queue_name, dead_exchange_name, 'orig_rk1'), channel.bind_queue(dead_queue_name, dead_exchange_name, 'dead_rk'), channel.declare_exchange(orig_exchange_name, 'direct'), loop=event_loop) await asyncio.gather(channel.bind_queue(orig_queue_name_1, orig_exchange_name, 'orig_rk1'), channel.bind_queue(orig_queue_name_2, orig_exchange_name, 'orig_rk2'), loop=event_loop) await channel.publish(orig_exchange_name, 'orig_rk1', 'message 1') await asyncio.sleep(0.1, loop=event_loop) for queue_name in orig_queue_name_1, orig_queue_name_2: with raises(EmptyQueue): await channel.get(queue_name) message = await channel.get(dead_queue_name) check_message_1(message) await channel.publish(orig_exchange_name, 'orig_rk2', 'message 2') await asyncio.sleep(0.1, loop=event_loop) for queue_name in orig_queue_name_1, orig_queue_name_2: with raises(EmptyQueue): await channel.get(queue_name) message = await channel.get(dead_queue_name) check_message_2(message) check_clean_channel_close(channel) check_clean_connection_close(connection)