Skip to content

Commit b25c427

Browse files
committed
Add system test for PubSub max_messages setting
1 parent 7d86713 commit b25c427

1 file changed

Lines changed: 99 additions & 0 deletions

File tree

pubsub/tests/system.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from __future__ import absolute_import
1616

1717
import datetime
18+
import itertools
1819
import threading
1920
import time
2021

@@ -24,6 +25,7 @@
2425

2526
import google.auth
2627
from google.cloud import pubsub_v1
28+
from google.cloud.pubsub_v1 import types
2729

2830

2931
from test_utils.system import unique_resource_id
@@ -206,6 +208,80 @@ class CallbackError(Exception):
206208
with pytest.raises(CallbackError):
207209
future.result(timeout=30)
208210

211+
def test_streaming_pull_max_messages(
212+
self, publisher, topic_path, subscriber, subscription_path, cleanup
213+
):
214+
# Make sure the topic and subscription get deleted.
215+
cleanup.append((publisher.delete_topic, topic_path))
216+
cleanup.append((subscriber.delete_subscription, subscription_path))
217+
218+
# create a topic and subscribe to it
219+
publisher.create_topic(topic_path)
220+
subscriber.create_subscription(subscription_path, topic_path)
221+
222+
batch_sizes = (7, 4, 8, 2, 10, 1, 3, 8, 6, 1) # total: 50
223+
self._publish_messages(publisher, topic_path, batch_sizes=batch_sizes)
224+
225+
# now subscribe and do the main part, check for max pending messages
226+
total_messages = sum(batch_sizes)
227+
flow_control = types.FlowControl(max_messages=5)
228+
callback = StreamingPullCallback(processing_time=1)
229+
230+
future = subscriber.subscribe(
231+
subscription_path, callback, flow_control=flow_control
232+
)
233+
234+
# Expected time to process all messages in ideal case:
235+
# total_messages / FlowControl.max_messages * processing_time
236+
#
237+
# With total=50, max messages=5, and processing_time=1 this amounts to
238+
# 10 seconds (+ overhead), thus a full minute should be more than enough
239+
# for the processing to complete. If not, fail the test with a timeout.
240+
for _ in six.moves.range(60):
241+
time.sleep(1)
242+
if callback.completed_calls >= total_messages:
243+
break
244+
else:
245+
pytest.fail(
246+
"Timeout: receiving/processing streamed messages took too long."
247+
)
248+
249+
try:
250+
# All messages should have been processed exactly once, and no more
251+
# than max_messages simultaneously at any time.
252+
assert callback.completed_calls == total_messages
253+
assert sorted(callback.seen_message_ids) == list(
254+
range(1, total_messages + 1)
255+
)
256+
assert callback.max_pending_ack <= flow_control.max_messages
257+
finally:
258+
future.cancel()
259+
260+
def _publish_messages(self, publisher, topic_path, batch_sizes):
261+
"""Publish ``count`` messages in batches and wait until completion."""
262+
publish_futures = []
263+
msg_counter = itertools.count(start=1)
264+
265+
for batch_size in batch_sizes:
266+
msg_batch = self._make_messages(count=batch_size)
267+
for msg in msg_batch:
268+
future = publisher.publish(
269+
topic_path, msg, seq_num=str(next(msg_counter))
270+
)
271+
publish_futures.append(future)
272+
time.sleep(0.1)
273+
274+
# wait untill all messages have been successfully published
275+
for future in publish_futures:
276+
future.result(timeout=30)
277+
278+
def _make_messages(self, count):
279+
messages = [
280+
u"message {}/{}".format(i, count).encode("utf-8")
281+
for i in range(1, count + 1)
282+
]
283+
return messages
284+
209285

210286
class AckCallback(object):
211287
def __init__(self):
@@ -236,3 +312,26 @@ def __call__(self, message):
236312
# ``calls`` is incremented to do it.
237313
self.call_times.append(now)
238314
self.calls += 1
315+
316+
317+
class StreamingPullCallback(object):
318+
def __init__(self, processing_time):
319+
self._lock = threading.Lock()
320+
self._processing_time = processing_time
321+
self._pending_ack = 0
322+
self.max_pending_ack = 0
323+
self.completed_calls = 0
324+
self.seen_message_ids = []
325+
326+
def __call__(self, message):
327+
with self._lock:
328+
self._pending_ack += 1
329+
self.max_pending_ack = max(self.max_pending_ack, self._pending_ack)
330+
self.seen_message_ids.append(int(message.attributes["seq_num"]))
331+
332+
time.sleep(self._processing_time)
333+
334+
with self._lock:
335+
self._pending_ack -= 1
336+
message.ack()
337+
self.completed_calls += 1

0 commit comments

Comments
 (0)