6
6
from confluent_kafka import TopicPartition
7
7
8
8
from quixstreams .checkpointing import Checkpoint , InvalidStoredOffset
9
+ from quixstreams .checkpointing .exceptions import CheckpointProducerTimeout
9
10
from quixstreams .kafka import Consumer
10
11
from quixstreams .rowproducer import RowProducer
11
12
from quixstreams .state import StateStoreManager
@@ -31,6 +32,13 @@ def factory(
31
32
return factory
32
33
33
34
35
+ @pytest .fixture ()
36
+ def rowproducer_mock (request ):
37
+ p = MagicMock (spec_set = RowProducer )
38
+ p .flush .return_value = getattr (request , "param" , 0 )
39
+ return p
40
+
41
+
34
42
class TestCheckpoint :
35
43
def test_empty_true (self , checkpoint_factory ):
36
44
checkpoint = checkpoint_factory ()
@@ -68,13 +76,18 @@ def test_commit_no_state_success(
68
76
assert tp .offset == processed_offset + 1
69
77
70
78
def test_commit_with_state_no_changelog_success (
71
- self , checkpoint_factory , consumer , state_manager_factory , topic_factory
79
+ self ,
80
+ checkpoint_factory ,
81
+ consumer ,
82
+ state_manager_factory ,
83
+ topic_factory ,
84
+ rowproducer_mock ,
72
85
):
73
86
topic_name , _ = topic_factory ()
74
- producer_mock = MagicMock ( spec_set = RowProducer )
75
- state_manager = state_manager_factory (producer = producer_mock )
87
+
88
+ state_manager = state_manager_factory (producer = rowproducer_mock )
76
89
checkpoint = checkpoint_factory (
77
- consumer_ = consumer , state_manager_ = state_manager , producer_ = producer_mock
90
+ consumer_ = consumer , state_manager_ = state_manager , producer_ = rowproducer_mock
78
91
)
79
92
processed_offset = 999
80
93
key , value , prefix = "key" , "value" , b"__key__"
@@ -95,7 +108,7 @@ def test_commit_with_state_no_changelog_success(
95
108
assert tp .offset == processed_offset + 1
96
109
97
110
# Check the producer is flushed
98
- assert producer_mock .flush .call_count == 1
111
+ assert rowproducer_mock .flush .call_count == 1
99
112
100
113
# Check the state is flushed
101
114
assert tx .completed
@@ -186,34 +199,32 @@ def test_commit_with_state_and_changelog_no_updates_success(
186
199
assert not store_partition .get_processed_offset ()
187
200
188
201
def test_commit_no_offsets_stored_noop (
189
- self , checkpoint_factory , state_manager_factory , topic_factory
202
+ self , checkpoint_factory , state_manager_factory , topic_factory , rowproducer_mock
190
203
):
191
204
topic_name , _ = topic_factory ()
192
- producer_mock = MagicMock (spec_set = RowProducer )
193
205
consumer_mock = MagicMock (spec_set = Consumer )
194
- state_manager = state_manager_factory (producer = producer_mock )
206
+ state_manager = state_manager_factory (producer = rowproducer_mock )
195
207
checkpoint = checkpoint_factory (
196
208
consumer_ = consumer_mock ,
197
209
state_manager_ = state_manager ,
198
- producer_ = producer_mock ,
210
+ producer_ = rowproducer_mock ,
199
211
)
200
212
# Commit the checkpoint without processing any messages
201
213
checkpoint .commit ()
202
214
203
215
# Check nothing is committed
204
216
assert not consumer_mock .commit .call_count
205
- assert not producer_mock .flush .call_count
217
+ assert not rowproducer_mock .flush .call_count
206
218
207
219
def test_commit_has_failed_transactions_fails (
208
- self , checkpoint_factory , state_manager_factory , topic_factory
220
+ self , checkpoint_factory , state_manager_factory , topic_factory , rowproducer_mock
209
221
):
210
- producer_mock = MagicMock (spec_set = RowProducer )
211
222
consumer_mock = MagicMock (spec_set = Consumer )
212
- state_manager = state_manager_factory (producer = producer_mock )
223
+ state_manager = state_manager_factory (producer = rowproducer_mock )
213
224
checkpoint = checkpoint_factory (
214
225
consumer_ = consumer_mock ,
215
226
state_manager_ = state_manager ,
216
- producer_ = producer_mock ,
227
+ producer_ = rowproducer_mock ,
217
228
)
218
229
processed_offset = 999
219
230
key , value , prefix = "key" , "value" , b"__key__"
@@ -240,20 +251,19 @@ def test_commit_has_failed_transactions_fails(
240
251
checkpoint .commit ()
241
252
242
253
# The producer should not flush
243
- assert not producer_mock .flush .call_count
254
+ assert not rowproducer_mock .flush .call_count
244
255
# Consumer should not commit
245
256
assert not consumer_mock .commit .call_count
246
257
247
258
def test_commit_producer_flush_fails (
248
- self , checkpoint_factory , state_manager_factory , topic_factory
259
+ self , checkpoint_factory , state_manager_factory , topic_factory , rowproducer_mock
249
260
):
250
- producer_mock = MagicMock (spec_set = RowProducer )
251
261
consumer_mock = MagicMock (spec_set = Consumer )
252
- state_manager = state_manager_factory (producer = producer_mock )
262
+ state_manager = state_manager_factory (producer = rowproducer_mock )
253
263
checkpoint = checkpoint_factory (
254
264
consumer_ = consumer_mock ,
255
265
state_manager_ = state_manager ,
256
- producer_ = producer_mock ,
266
+ producer_ = rowproducer_mock ,
257
267
)
258
268
processed_offset = 999
259
269
key , value , prefix = "key" , "value" , b"__key__"
@@ -266,7 +276,7 @@ def test_commit_producer_flush_fails(
266
276
tx .set (key = key , value = value , prefix = prefix )
267
277
checkpoint .store_offset ("topic" , 0 , processed_offset )
268
278
269
- producer_mock .flush .side_effect = ValueError ("Flush failure" )
279
+ rowproducer_mock .flush .side_effect = ValueError ("Flush failure" )
270
280
# Checkpoint commit should fail if producer failed to flush
271
281
with pytest .raises (ValueError ):
272
282
checkpoint .commit ()
@@ -278,15 +288,14 @@ def test_commit_producer_flush_fails(
278
288
assert not tx .completed
279
289
280
290
def test_commit_consumer_commit_fails (
281
- self , checkpoint_factory , state_manager_factory , topic_factory
291
+ self , checkpoint_factory , state_manager_factory , topic_factory , rowproducer_mock
282
292
):
283
- producer_mock = MagicMock (spec_set = RowProducer )
284
293
consumer_mock = MagicMock (spec_set = Consumer )
285
- state_manager = state_manager_factory (producer = producer_mock )
294
+ state_manager = state_manager_factory (producer = rowproducer_mock )
286
295
checkpoint = checkpoint_factory (
287
296
consumer_ = consumer_mock ,
288
297
state_manager_ = state_manager ,
289
- producer_ = producer_mock ,
298
+ producer_ = rowproducer_mock ,
290
299
)
291
300
processed_offset = 999
292
301
key , value , prefix = "key" , "value" , b"__key__"
@@ -305,7 +314,7 @@ def test_commit_consumer_commit_fails(
305
314
checkpoint .commit ()
306
315
307
316
# Producer should flush
308
- assert producer_mock .flush .call_count
317
+ assert rowproducer_mock .flush .call_count
309
318
# The transaction should remain prepared, but not completed
310
319
assert tx .prepared
311
320
assert not tx .completed
@@ -326,3 +335,22 @@ def test_get_store_transaction_success(self, checkpoint_factory, state_manager):
326
335
assert tx
327
336
tx2 = checkpoint .get_store_transaction ("topic" , 0 , "default" )
328
337
assert tx2 is tx
338
+
339
+ @pytest .mark .parametrize ("rowproducer_mock" , [1 ], indirect = True )
340
+ def test_incomplete_flush (
341
+ self , checkpoint_factory , consumer , state_manager_factory , rowproducer_mock
342
+ ):
343
+
344
+ state_manager = state_manager_factory (producer = rowproducer_mock )
345
+ checkpoint = checkpoint_factory (
346
+ consumer_ = consumer , state_manager_ = state_manager , producer_ = rowproducer_mock
347
+ )
348
+ checkpoint .store_offset ("topic" , 0 , 0 )
349
+
350
+ with pytest .raises (CheckpointProducerTimeout ) as err :
351
+ checkpoint .commit ()
352
+
353
+ assert (
354
+ str (err .value )
355
+ == "'1' messages failed to be produced before the producer flush timeout"
356
+ )
0 commit comments