@ohos-ports/confluentinc-kafka-javascript 1.10.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +457 -0
- package/LICENSE.kafkajs +31 -0
- package/LICENSE.node-rdkafka +25 -0
- package/LICENSE.txt +20 -0
- package/README.md +133 -0
- package/binding.gyp +184 -0
- package/build/Release/confluent-kafka-javascript.node +0 -0
- package/build/deps/librdkafka++.so.1 +0 -0
- package/build/deps/librdkafka.Makefile +6 -0
- package/build/deps/librdkafka.so.1 +0 -0
- package/build/deps/librdkafka.target.mk +55 -0
- package/deps/librdkafka/.clang-format +136 -0
- package/deps/librdkafka/.clang-format-cpp +103 -0
- package/deps/librdkafka/.dir-locals.el +10 -0
- package/deps/librdkafka/.formatignore +35 -0
- package/deps/librdkafka/.gdbmacros +19 -0
- package/deps/librdkafka/CHANGELOG.md +2477 -0
- package/deps/librdkafka/CMakeLists.txt +291 -0
- package/deps/librdkafka/CODE_OF_CONDUCT.md +46 -0
- package/deps/librdkafka/CONFIGURATION.md +212 -0
- package/deps/librdkafka/CONTRIBUTING.md +431 -0
- package/deps/librdkafka/Doxyfile +2375 -0
- package/deps/librdkafka/INTRODUCTION.md +2752 -0
- package/deps/librdkafka/LICENSE +26 -0
- package/deps/librdkafka/LICENSE.cjson +22 -0
- package/deps/librdkafka/LICENSE.crc32c +28 -0
- package/deps/librdkafka/LICENSE.fnv1a +18 -0
- package/deps/librdkafka/LICENSE.hdrhistogram +27 -0
- package/deps/librdkafka/LICENSE.lz4 +26 -0
- package/deps/librdkafka/LICENSE.murmur2 +25 -0
- package/deps/librdkafka/LICENSE.nanopb +22 -0
- package/deps/librdkafka/LICENSE.opentelemetry +203 -0
- package/deps/librdkafka/LICENSE.pycrc +23 -0
- package/deps/librdkafka/LICENSE.queue +31 -0
- package/deps/librdkafka/LICENSE.regexp +5 -0
- package/deps/librdkafka/LICENSE.snappy +36 -0
- package/deps/librdkafka/LICENSE.tinycthread +26 -0
- package/deps/librdkafka/LICENSE.wingetopt +49 -0
- package/deps/librdkafka/LICENSES.txt +625 -0
- package/deps/librdkafka/Makefile +125 -0
- package/deps/librdkafka/README.md +201 -0
- package/deps/librdkafka/README.win32 +26 -0
- package/deps/librdkafka/STATISTICS.md +624 -0
- package/deps/librdkafka/configure +214 -0
- package/deps/librdkafka/configure.self +341 -0
- package/deps/librdkafka/dev-conf.sh +123 -0
- package/deps/librdkafka/lds-gen.py +73 -0
- package/deps/librdkafka/mainpage.doxy +40 -0
- package/deps/librdkafka/mklove/Makefile.base +329 -0
- package/deps/librdkafka/mklove/modules/configure.atomics +144 -0
- package/deps/librdkafka/mklove/modules/configure.base +2484 -0
- package/deps/librdkafka/mklove/modules/configure.builtin +70 -0
- package/deps/librdkafka/mklove/modules/configure.cc +186 -0
- package/deps/librdkafka/mklove/modules/configure.cxx +8 -0
- package/deps/librdkafka/mklove/modules/configure.fileversion +65 -0
- package/deps/librdkafka/mklove/modules/configure.gitversion +29 -0
- package/deps/librdkafka/mklove/modules/configure.good_cflags +18 -0
- package/deps/librdkafka/mklove/modules/configure.host +132 -0
- package/deps/librdkafka/mklove/modules/configure.lib +49 -0
- package/deps/librdkafka/mklove/modules/configure.libcurl +99 -0
- package/deps/librdkafka/mklove/modules/configure.libsasl2 +36 -0
- package/deps/librdkafka/mklove/modules/configure.libssl +147 -0
- package/deps/librdkafka/mklove/modules/configure.libzstd +58 -0
- package/deps/librdkafka/mklove/modules/configure.parseversion +95 -0
- package/deps/librdkafka/mklove/modules/configure.pic +16 -0
- package/deps/librdkafka/mklove/modules/configure.socket +20 -0
- package/deps/librdkafka/mklove/modules/configure.zlib +61 -0
- package/deps/librdkafka/mklove/modules/patches/README.md +8 -0
- package/deps/librdkafka/mklove/modules/patches/libcurl.0000-no-runtime-linking-check.patch +11 -0
- package/deps/librdkafka/mklove/modules/patches/libssl.0000-osx-rand-include-fix-OpenSSL-PR16409.patch +56 -0
- package/deps/librdkafka/service.yml +182 -0
- package/deps/librdkafka/src/CMakeLists.txt +379 -0
- package/deps/librdkafka/src/Makefile +105 -0
- package/deps/librdkafka/src/README.lz4.md +30 -0
- package/deps/librdkafka/src/cJSON.c +3191 -0
- package/deps/librdkafka/src/cJSON.h +306 -0
- package/deps/librdkafka/src/crc32c.c +430 -0
- package/deps/librdkafka/src/crc32c.h +38 -0
- package/deps/librdkafka/src/generate_proto.sh +66 -0
- package/deps/librdkafka/src/librdkafka_cgrp_synch.png +0 -0
- package/deps/librdkafka/src/lz4.c +2727 -0
- package/deps/librdkafka/src/lz4.h +842 -0
- package/deps/librdkafka/src/lz4frame.c +2078 -0
- package/deps/librdkafka/src/lz4frame.h +692 -0
- package/deps/librdkafka/src/lz4frame_static.h +47 -0
- package/deps/librdkafka/src/lz4hc.c +1631 -0
- package/deps/librdkafka/src/lz4hc.h +413 -0
- package/deps/librdkafka/src/nanopb/pb.h +917 -0
- package/deps/librdkafka/src/nanopb/pb_common.c +388 -0
- package/deps/librdkafka/src/nanopb/pb_common.h +49 -0
- package/deps/librdkafka/src/nanopb/pb_decode.c +1727 -0
- package/deps/librdkafka/src/nanopb/pb_decode.h +193 -0
- package/deps/librdkafka/src/nanopb/pb_encode.c +1000 -0
- package/deps/librdkafka/src/nanopb/pb_encode.h +185 -0
- package/deps/librdkafka/src/opentelemetry/common.pb.c +32 -0
- package/deps/librdkafka/src/opentelemetry/common.pb.h +170 -0
- package/deps/librdkafka/src/opentelemetry/metrics.options +2 -0
- package/deps/librdkafka/src/opentelemetry/metrics.pb.c +67 -0
- package/deps/librdkafka/src/opentelemetry/metrics.pb.h +966 -0
- package/deps/librdkafka/src/opentelemetry/resource.pb.c +12 -0
- package/deps/librdkafka/src/opentelemetry/resource.pb.h +58 -0
- package/deps/librdkafka/src/queue.h +850 -0
- package/deps/librdkafka/src/rd.h +592 -0
- package/deps/librdkafka/src/rdaddr.c +255 -0
- package/deps/librdkafka/src/rdaddr.h +202 -0
- package/deps/librdkafka/src/rdatomic.h +262 -0
- package/deps/librdkafka/src/rdavg.h +260 -0
- package/deps/librdkafka/src/rdavl.c +210 -0
- package/deps/librdkafka/src/rdavl.h +250 -0
- package/deps/librdkafka/src/rdbase64.c +200 -0
- package/deps/librdkafka/src/rdbase64.h +43 -0
- package/deps/librdkafka/src/rdbuf.c +1884 -0
- package/deps/librdkafka/src/rdbuf.h +375 -0
- package/deps/librdkafka/src/rdcrc32.c +114 -0
- package/deps/librdkafka/src/rdcrc32.h +170 -0
- package/deps/librdkafka/src/rddl.c +179 -0
- package/deps/librdkafka/src/rddl.h +43 -0
- package/deps/librdkafka/src/rdendian.h +175 -0
- package/deps/librdkafka/src/rdfloat.h +67 -0
- package/deps/librdkafka/src/rdfnv1a.c +113 -0
- package/deps/librdkafka/src/rdfnv1a.h +35 -0
- package/deps/librdkafka/src/rdgz.c +120 -0
- package/deps/librdkafka/src/rdgz.h +46 -0
- package/deps/librdkafka/src/rdhdrhistogram.c +721 -0
- package/deps/librdkafka/src/rdhdrhistogram.h +87 -0
- package/deps/librdkafka/src/rdhttp.c +831 -0
- package/deps/librdkafka/src/rdhttp.h +101 -0
- package/deps/librdkafka/src/rdinterval.h +177 -0
- package/deps/librdkafka/src/rdkafka.c +7287 -0
- package/deps/librdkafka/src/rdkafka.h +11795 -0
- package/deps/librdkafka/src/rdkafka_admin.c +10165 -0
- package/deps/librdkafka/src/rdkafka_admin.h +658 -0
- package/deps/librdkafka/src/rdkafka_assignment.c +1282 -0
- package/deps/librdkafka/src/rdkafka_assignment.h +73 -0
- package/deps/librdkafka/src/rdkafka_assignor.c +1800 -0
- package/deps/librdkafka/src/rdkafka_assignor.h +402 -0
- package/deps/librdkafka/src/rdkafka_aux.c +409 -0
- package/deps/librdkafka/src/rdkafka_aux.h +174 -0
- package/deps/librdkafka/src/rdkafka_background.c +222 -0
- package/deps/librdkafka/src/rdkafka_broker.c +6736 -0
- package/deps/librdkafka/src/rdkafka_broker.h +851 -0
- package/deps/librdkafka/src/rdkafka_buf.c +543 -0
- package/deps/librdkafka/src/rdkafka_buf.h +1525 -0
- package/deps/librdkafka/src/rdkafka_cert.c +576 -0
- package/deps/librdkafka/src/rdkafka_cert.h +62 -0
- package/deps/librdkafka/src/rdkafka_cgrp.c +8310 -0
- package/deps/librdkafka/src/rdkafka_cgrp.h +589 -0
- package/deps/librdkafka/src/rdkafka_conf.c +5177 -0
- package/deps/librdkafka/src/rdkafka_conf.h +746 -0
- package/deps/librdkafka/src/rdkafka_confval.h +97 -0
- package/deps/librdkafka/src/rdkafka_coord.c +623 -0
- package/deps/librdkafka/src/rdkafka_coord.h +132 -0
- package/deps/librdkafka/src/rdkafka_error.c +228 -0
- package/deps/librdkafka/src/rdkafka_error.h +80 -0
- package/deps/librdkafka/src/rdkafka_event.c +502 -0
- package/deps/librdkafka/src/rdkafka_event.h +126 -0
- package/deps/librdkafka/src/rdkafka_feature.c +898 -0
- package/deps/librdkafka/src/rdkafka_feature.h +104 -0
- package/deps/librdkafka/src/rdkafka_fetcher.c +3820 -0
- package/deps/librdkafka/src/rdkafka_fetcher.h +62 -0
- package/deps/librdkafka/src/rdkafka_header.c +220 -0
- package/deps/librdkafka/src/rdkafka_header.h +76 -0
- package/deps/librdkafka/src/rdkafka_idempotence.c +807 -0
- package/deps/librdkafka/src/rdkafka_idempotence.h +144 -0
- package/deps/librdkafka/src/rdkafka_int.h +1505 -0
- package/deps/librdkafka/src/rdkafka_interceptor.c +842 -0
- package/deps/librdkafka/src/rdkafka_interceptor.h +104 -0
- package/deps/librdkafka/src/rdkafka_lz4.c +450 -0
- package/deps/librdkafka/src/rdkafka_lz4.h +49 -0
- package/deps/librdkafka/src/rdkafka_metadata.c +2347 -0
- package/deps/librdkafka/src/rdkafka_metadata.h +350 -0
- package/deps/librdkafka/src/rdkafka_metadata_cache.c +1183 -0
- package/deps/librdkafka/src/rdkafka_mock.c +4514 -0
- package/deps/librdkafka/src/rdkafka_mock.h +867 -0
- package/deps/librdkafka/src/rdkafka_mock_cgrp.c +1877 -0
- package/deps/librdkafka/src/rdkafka_mock_handlers.c +5255 -0
- package/deps/librdkafka/src/rdkafka_mock_int.h +1005 -0
- package/deps/librdkafka/src/rdkafka_mock_sharegrp.c +1207 -0
- package/deps/librdkafka/src/rdkafka_msg.c +2619 -0
- package/deps/librdkafka/src/rdkafka_msg.h +629 -0
- package/deps/librdkafka/src/rdkafka_msgbatch.h +62 -0
- package/deps/librdkafka/src/rdkafka_msgset.h +105 -0
- package/deps/librdkafka/src/rdkafka_msgset_reader.c +2099 -0
- package/deps/librdkafka/src/rdkafka_msgset_writer.c +1474 -0
- package/deps/librdkafka/src/rdkafka_offset.c +1565 -0
- package/deps/librdkafka/src/rdkafka_offset.h +150 -0
- package/deps/librdkafka/src/rdkafka_op.c +1219 -0
- package/deps/librdkafka/src/rdkafka_op.h +1065 -0
- package/deps/librdkafka/src/rdkafka_partition.c +5109 -0
- package/deps/librdkafka/src/rdkafka_partition.h +1215 -0
- package/deps/librdkafka/src/rdkafka_pattern.c +228 -0
- package/deps/librdkafka/src/rdkafka_pattern.h +70 -0
- package/deps/librdkafka/src/rdkafka_plugin.c +213 -0
- package/deps/librdkafka/src/rdkafka_plugin.h +41 -0
- package/deps/librdkafka/src/rdkafka_proto.h +739 -0
- package/deps/librdkafka/src/rdkafka_protocol.h +131 -0
- package/deps/librdkafka/src/rdkafka_queue.c +1351 -0
- package/deps/librdkafka/src/rdkafka_queue.h +1235 -0
- package/deps/librdkafka/src/rdkafka_range_assignor.c +1748 -0
- package/deps/librdkafka/src/rdkafka_request.c +7298 -0
- package/deps/librdkafka/src/rdkafka_request.h +761 -0
- package/deps/librdkafka/src/rdkafka_roundrobin_assignor.c +123 -0
- package/deps/librdkafka/src/rdkafka_sasl.c +589 -0
- package/deps/librdkafka/src/rdkafka_sasl.h +63 -0
- package/deps/librdkafka/src/rdkafka_sasl_cyrus.c +722 -0
- package/deps/librdkafka/src/rdkafka_sasl_int.h +89 -0
- package/deps/librdkafka/src/rdkafka_sasl_oauthbearer.c +1833 -0
- package/deps/librdkafka/src/rdkafka_sasl_oauthbearer.h +52 -0
- package/deps/librdkafka/src/rdkafka_sasl_oauthbearer_oidc.c +1827 -0
- package/deps/librdkafka/src/rdkafka_sasl_oauthbearer_oidc.h +52 -0
- package/deps/librdkafka/src/rdkafka_sasl_plain.c +142 -0
- package/deps/librdkafka/src/rdkafka_sasl_scram.c +858 -0
- package/deps/librdkafka/src/rdkafka_sasl_win32.c +550 -0
- package/deps/librdkafka/src/rdkafka_share_acknowledgement.c +1445 -0
- package/deps/librdkafka/src/rdkafka_share_acknowledgement.h +373 -0
- package/deps/librdkafka/src/rdkafka_ssl.c +2235 -0
- package/deps/librdkafka/src/rdkafka_ssl.h +86 -0
- package/deps/librdkafka/src/rdkafka_sticky_assignor.c +4785 -0
- package/deps/librdkafka/src/rdkafka_subscription.c +411 -0
- package/deps/librdkafka/src/rdkafka_telemetry.c +761 -0
- package/deps/librdkafka/src/rdkafka_telemetry.h +52 -0
- package/deps/librdkafka/src/rdkafka_telemetry_decode.c +1307 -0
- package/deps/librdkafka/src/rdkafka_telemetry_decode.h +59 -0
- package/deps/librdkafka/src/rdkafka_telemetry_encode.c +1309 -0
- package/deps/librdkafka/src/rdkafka_telemetry_encode.h +459 -0
- package/deps/librdkafka/src/rdkafka_timer.c +407 -0
- package/deps/librdkafka/src/rdkafka_timer.h +117 -0
- package/deps/librdkafka/src/rdkafka_topic.c +2382 -0
- package/deps/librdkafka/src/rdkafka_topic.h +369 -0
- package/deps/librdkafka/src/rdkafka_transport.c +1309 -0
- package/deps/librdkafka/src/rdkafka_transport.h +99 -0
- package/deps/librdkafka/src/rdkafka_transport_int.h +100 -0
- package/deps/librdkafka/src/rdkafka_txnmgr.c +3256 -0
- package/deps/librdkafka/src/rdkafka_txnmgr.h +171 -0
- package/deps/librdkafka/src/rdkafka_zstd.c +226 -0
- package/deps/librdkafka/src/rdkafka_zstd.h +57 -0
- package/deps/librdkafka/src/rdlist.c +593 -0
- package/deps/librdkafka/src/rdlist.h +444 -0
- package/deps/librdkafka/src/rdlog.c +89 -0
- package/deps/librdkafka/src/rdlog.h +41 -0
- package/deps/librdkafka/src/rdmap.c +508 -0
- package/deps/librdkafka/src/rdmap.h +492 -0
- package/deps/librdkafka/src/rdmurmur2.c +167 -0
- package/deps/librdkafka/src/rdmurmur2.h +35 -0
- package/deps/librdkafka/src/rdports.c +61 -0
- package/deps/librdkafka/src/rdports.h +38 -0
- package/deps/librdkafka/src/rdposix.h +250 -0
- package/deps/librdkafka/src/rdrand.c +151 -0
- package/deps/librdkafka/src/rdrand.h +72 -0
- package/deps/librdkafka/src/rdregex.c +156 -0
- package/deps/librdkafka/src/rdregex.h +43 -0
- package/deps/librdkafka/src/rdsignal.h +57 -0
- package/deps/librdkafka/src/rdstring.c +645 -0
- package/deps/librdkafka/src/rdstring.h +98 -0
- package/deps/librdkafka/src/rdsysqueue.h +404 -0
- package/deps/librdkafka/src/rdtime.h +356 -0
- package/deps/librdkafka/src/rdtypes.h +86 -0
- package/deps/librdkafka/src/rdunittest.c +564 -0
- package/deps/librdkafka/src/rdunittest.h +232 -0
- package/deps/librdkafka/src/rdunittest_acknowledge.c +1177 -0
- package/deps/librdkafka/src/rdunittest_fetcher.c +1211 -0
- package/deps/librdkafka/src/rdunittest_msgset_errors.c +1521 -0
- package/deps/librdkafka/src/rdvarint.c +134 -0
- package/deps/librdkafka/src/rdvarint.h +165 -0
- package/deps/librdkafka/src/rdwin32.h +382 -0
- package/deps/librdkafka/src/rdxxhash.c +1030 -0
- package/deps/librdkafka/src/rdxxhash.h +328 -0
- package/deps/librdkafka/src/regexp.c +1352 -0
- package/deps/librdkafka/src/regexp.h +41 -0
- package/deps/librdkafka/src/snappy.c +1866 -0
- package/deps/librdkafka/src/snappy.h +62 -0
- package/deps/librdkafka/src/snappy_compat.h +138 -0
- package/deps/librdkafka/src/statistics_schema.json +444 -0
- package/deps/librdkafka/src/tinycthread.c +932 -0
- package/deps/librdkafka/src/tinycthread.h +503 -0
- package/deps/librdkafka/src/tinycthread_extra.c +210 -0
- package/deps/librdkafka/src/tinycthread_extra.h +218 -0
- package/deps/librdkafka/src/win32_config.h +58 -0
- package/deps/librdkafka/src-cpp/CMakeLists.txt +90 -0
- package/deps/librdkafka/src-cpp/ConfImpl.cpp +84 -0
- package/deps/librdkafka/src-cpp/ConsumerImpl.cpp +244 -0
- package/deps/librdkafka/src-cpp/HandleImpl.cpp +436 -0
- package/deps/librdkafka/src-cpp/HeadersImpl.cpp +48 -0
- package/deps/librdkafka/src-cpp/KafkaConsumerImpl.cpp +296 -0
- package/deps/librdkafka/src-cpp/Makefile +55 -0
- package/deps/librdkafka/src-cpp/MessageImpl.cpp +38 -0
- package/deps/librdkafka/src-cpp/MetadataImpl.cpp +170 -0
- package/deps/librdkafka/src-cpp/ProducerImpl.cpp +197 -0
- package/deps/librdkafka/src-cpp/QueueImpl.cpp +70 -0
- package/deps/librdkafka/src-cpp/README.md +16 -0
- package/deps/librdkafka/src-cpp/RdKafka.cpp +59 -0
- package/deps/librdkafka/src-cpp/TopicImpl.cpp +124 -0
- package/deps/librdkafka/src-cpp/TopicPartitionImpl.cpp +57 -0
- package/deps/librdkafka/src-cpp/rdkafkacpp.h +3797 -0
- package/deps/librdkafka/src-cpp/rdkafkacpp_int.h +1641 -0
- package/deps/librdkafka/tests/0000-unittests.c +72 -0
- package/deps/librdkafka/tests/0001-multiobj.c +102 -0
- package/deps/librdkafka/tests/0002-unkpart.c +244 -0
- package/deps/librdkafka/tests/0003-msgmaxsize.c +173 -0
- package/deps/librdkafka/tests/0004-conf.c +934 -0
- package/deps/librdkafka/tests/0005-order.c +133 -0
- package/deps/librdkafka/tests/0006-symbols.c +163 -0
- package/deps/librdkafka/tests/0007-autotopic.c +136 -0
- package/deps/librdkafka/tests/0008-reqacks.c +179 -0
- package/deps/librdkafka/tests/0009-mock_cluster.c +272 -0
- package/deps/librdkafka/tests/0011-produce_batch.c +753 -0
- package/deps/librdkafka/tests/0012-produce_consume.c +537 -0
- package/deps/librdkafka/tests/0013-null-msgs.c +473 -0
- package/deps/librdkafka/tests/0014-reconsume-191.c +512 -0
- package/deps/librdkafka/tests/0015-offset_seeks.c +172 -0
- package/deps/librdkafka/tests/0016-client_swname.c +181 -0
- package/deps/librdkafka/tests/0017-compression.c +140 -0
- package/deps/librdkafka/tests/0018-cgrp_term.c +444 -0
- package/deps/librdkafka/tests/0019-list_groups.c +289 -0
- package/deps/librdkafka/tests/0020-destroy_hang.c +162 -0
- package/deps/librdkafka/tests/0021-rkt_destroy.c +72 -0
- package/deps/librdkafka/tests/0022-consume_batch.c +331 -0
- package/deps/librdkafka/tests/0025-timers.c +147 -0
- package/deps/librdkafka/tests/0026-consume_pause.c +547 -0
- package/deps/librdkafka/tests/0028-long_topicnames.c +79 -0
- package/deps/librdkafka/tests/0029-assign_offset.c +202 -0
- package/deps/librdkafka/tests/0030-offset_commit.c +589 -0
- package/deps/librdkafka/tests/0031-get_offsets.c +235 -0
- package/deps/librdkafka/tests/0033-regex_subscribe.c +536 -0
- package/deps/librdkafka/tests/0034-offset_reset.c +398 -0
- package/deps/librdkafka/tests/0035-api_version.c +73 -0
- package/deps/librdkafka/tests/0036-partial_fetch.c +87 -0
- package/deps/librdkafka/tests/0037-destroy_hang_local.c +85 -0
- package/deps/librdkafka/tests/0038-performance.c +121 -0
- package/deps/librdkafka/tests/0039-event.c +284 -0
- package/deps/librdkafka/tests/0040-io_event.c +257 -0
- package/deps/librdkafka/tests/0041-fetch_max_bytes.c +97 -0
- package/deps/librdkafka/tests/0042-many_topics.c +252 -0
- package/deps/librdkafka/tests/0043-no_connection.c +77 -0
- package/deps/librdkafka/tests/0044-partition_cnt.c +94 -0
- package/deps/librdkafka/tests/0045-subscribe_update.c +1010 -0
- package/deps/librdkafka/tests/0046-rkt_cache.c +65 -0
- package/deps/librdkafka/tests/0047-partial_buf_tmout.c +98 -0
- package/deps/librdkafka/tests/0048-partitioner.c +283 -0
- package/deps/librdkafka/tests/0049-consume_conn_close.c +162 -0
- package/deps/librdkafka/tests/0050-subscribe_adds.c +145 -0
- package/deps/librdkafka/tests/0051-assign_adds.c +126 -0
- package/deps/librdkafka/tests/0052-msg_timestamps.c +238 -0
- package/deps/librdkafka/tests/0053-stats_cb.cpp +527 -0
- package/deps/librdkafka/tests/0054-offset_time.cpp +236 -0
- package/deps/librdkafka/tests/0055-producer_latency.c +539 -0
- package/deps/librdkafka/tests/0056-balanced_group_mt.c +315 -0
- package/deps/librdkafka/tests/0057-invalid_topic.cpp +112 -0
- package/deps/librdkafka/tests/0058-log.cpp +123 -0
- package/deps/librdkafka/tests/0059-bsearch.cpp +241 -0
- package/deps/librdkafka/tests/0060-op_prio.cpp +163 -0
- package/deps/librdkafka/tests/0061-consumer_lag.cpp +295 -0
- package/deps/librdkafka/tests/0062-stats_event.c +126 -0
- package/deps/librdkafka/tests/0063-clusterid.cpp +180 -0
- package/deps/librdkafka/tests/0064-interceptors.c +529 -0
- package/deps/librdkafka/tests/0065-yield.cpp +140 -0
- package/deps/librdkafka/tests/0066-plugins.cpp +143 -0
- package/deps/librdkafka/tests/0067-empty_topic.cpp +151 -0
- package/deps/librdkafka/tests/0068-produce_timeout.c +136 -0
- package/deps/librdkafka/tests/0069-consumer_add_parts.c +119 -0
- package/deps/librdkafka/tests/0070-null_empty.cpp +197 -0
- package/deps/librdkafka/tests/0072-headers_ut.c +448 -0
- package/deps/librdkafka/tests/0073-headers.c +381 -0
- package/deps/librdkafka/tests/0074-producev.c +87 -0
- package/deps/librdkafka/tests/0075-retry.c +290 -0
- package/deps/librdkafka/tests/0076-produce_retry.c +452 -0
- package/deps/librdkafka/tests/0077-compaction.c +366 -0
- package/deps/librdkafka/tests/0078-c_from_cpp.cpp +96 -0
- package/deps/librdkafka/tests/0079-fork.c +93 -0
- package/deps/librdkafka/tests/0080-admin_ut.c +3095 -0
- package/deps/librdkafka/tests/0081-admin.c +5633 -0
- package/deps/librdkafka/tests/0082-fetch_max_bytes.cpp +137 -0
- package/deps/librdkafka/tests/0083-cb_event.c +233 -0
- package/deps/librdkafka/tests/0084-destroy_flags.c +208 -0
- package/deps/librdkafka/tests/0085-headers.cpp +392 -0
- package/deps/librdkafka/tests/0086-purge.c +368 -0
- package/deps/librdkafka/tests/0088-produce_metadata_timeout.c +162 -0
- package/deps/librdkafka/tests/0089-max_poll_interval.c +511 -0
- package/deps/librdkafka/tests/0090-idempotence.c +171 -0
- package/deps/librdkafka/tests/0091-max_poll_interval_timeout.c +295 -0
- package/deps/librdkafka/tests/0092-mixed_msgver.c +103 -0
- package/deps/librdkafka/tests/0093-holb.c +200 -0
- package/deps/librdkafka/tests/0094-idempotence_msg_timeout.c +231 -0
- package/deps/librdkafka/tests/0095-all_brokers_down.cpp +122 -0
- package/deps/librdkafka/tests/0097-ssl_verify.cpp +674 -0
- package/deps/librdkafka/tests/0098-consumer-txn.cpp +1218 -0
- package/deps/librdkafka/tests/0099-commit_metadata.c +194 -0
- package/deps/librdkafka/tests/0100-thread_interceptors.cpp +195 -0
- package/deps/librdkafka/tests/0101-fetch-from-follower.cpp +446 -0
- package/deps/librdkafka/tests/0102-static_group_rebalance.c +836 -0
- package/deps/librdkafka/tests/0103-transactions.c +1383 -0
- package/deps/librdkafka/tests/0104-fetch_from_follower_mock.c +625 -0
- package/deps/librdkafka/tests/0105-transactions_mock.c +3930 -0
- package/deps/librdkafka/tests/0106-cgrp_sess_timeout.c +318 -0
- package/deps/librdkafka/tests/0107-topic_recreate.c +259 -0
- package/deps/librdkafka/tests/0109-auto_create_topics.cpp +278 -0
- package/deps/librdkafka/tests/0110-batch_size.cpp +182 -0
- package/deps/librdkafka/tests/0111-delay_create_topics.cpp +127 -0
- package/deps/librdkafka/tests/0112-assign_unknown_part.c +87 -0
- package/deps/librdkafka/tests/0113-cooperative_rebalance.cpp +3473 -0
- package/deps/librdkafka/tests/0114-sticky_partitioning.cpp +176 -0
- package/deps/librdkafka/tests/0115-producer_auth.cpp +182 -0
- package/deps/librdkafka/tests/0116-kafkaconsumer_close.cpp +216 -0
- package/deps/librdkafka/tests/0117-mock_errors.c +331 -0
- package/deps/librdkafka/tests/0118-commit_rebalance.c +154 -0
- package/deps/librdkafka/tests/0119-consumer_auth.cpp +167 -0
- package/deps/librdkafka/tests/0120-asymmetric_subscription.c +185 -0
- package/deps/librdkafka/tests/0121-clusterid.c +115 -0
- package/deps/librdkafka/tests/0122-buffer_cleaning_after_rebalance.c +227 -0
- package/deps/librdkafka/tests/0123-connections_max_idle.c +98 -0
- package/deps/librdkafka/tests/0124-openssl_invalid_engine.c +69 -0
- package/deps/librdkafka/tests/0125-immediate_flush.c +144 -0
- package/deps/librdkafka/tests/0126-oauthbearer_oidc.c +1164 -0
- package/deps/librdkafka/tests/0127-fetch_queue_backoff.cpp +165 -0
- package/deps/librdkafka/tests/0128-sasl_callback_queue.cpp +217 -0
- package/deps/librdkafka/tests/0129-fetch_aborted_msgs.c +79 -0
- package/deps/librdkafka/tests/0130-store_offsets.c +178 -0
- package/deps/librdkafka/tests/0131-connect_timeout.c +81 -0
- package/deps/librdkafka/tests/0132-strategy_ordering.c +179 -0
- package/deps/librdkafka/tests/0133-ssl_keys.c +150 -0
- package/deps/librdkafka/tests/0134-ssl_provider.c +92 -0
- package/deps/librdkafka/tests/0135-sasl_credentials.cpp +279 -0
- package/deps/librdkafka/tests/0136-resolve_cb.c +181 -0
- package/deps/librdkafka/tests/0137-barrier_batch_consume.c +752 -0
- package/deps/librdkafka/tests/0138-admin_mock.c +281 -0
- package/deps/librdkafka/tests/0139-offset_validation_mock.c +950 -0
- package/deps/librdkafka/tests/0140-commit_metadata.cpp +108 -0
- package/deps/librdkafka/tests/0142-reauthentication.c +931 -0
- package/deps/librdkafka/tests/0143-exponential_backoff_mock.c +552 -0
- package/deps/librdkafka/tests/0144-idempotence_mock.c +373 -0
- package/deps/librdkafka/tests/0145-pause_resume_mock.c +119 -0
- package/deps/librdkafka/tests/0146-metadata_mock.c +505 -0
- package/deps/librdkafka/tests/0147-consumer_group_consumer_mock.c +1210 -0
- package/deps/librdkafka/tests/0148-offset_fetch_commit_error_mock.c +569 -0
- package/deps/librdkafka/tests/0149-broker-same-host-port.c +140 -0
- package/deps/librdkafka/tests/0150-telemetry_mock.c +651 -0
- package/deps/librdkafka/tests/0151-purge-brokers.c +566 -0
- package/deps/librdkafka/tests/0152-rebootstrap.c +59 -0
- package/deps/librdkafka/tests/0153-memberid.c +128 -0
- package/deps/librdkafka/tests/0155-share_group_heartbeat_mock.c +2695 -0
- package/deps/librdkafka/tests/0156-share_consumer_fetch_mock.c +1551 -0
- package/deps/librdkafka/tests/0157-share_consumer_ack_mock.c +1788 -0
- package/deps/librdkafka/tests/0158-share_consumer_transactions_mock.c +467 -0
- package/deps/librdkafka/tests/0170-share_consumer_subscription.c +2192 -0
- package/deps/librdkafka/tests/0171-share_consumer_consume.c +2150 -0
- package/deps/librdkafka/tests/0172-share_consumer_acknowledge.c +1919 -0
- package/deps/librdkafka/tests/0173-share_consumer_commit_async.c +2735 -0
- package/deps/librdkafka/tests/0174-share_consumer_concurrency.c +1434 -0
- package/deps/librdkafka/tests/0175-share_consumer_groups.c +574 -0
- package/deps/librdkafka/tests/0176-share_consumer_commit_sync.c +2986 -0
- package/deps/librdkafka/tests/0177-share_consumer_transactions.c +1815 -0
- package/deps/librdkafka/tests/0178-share_consumer_close.c +2102 -0
- package/deps/librdkafka/tests/0179-share_consumer_destroy.c +2482 -0
- package/deps/librdkafka/tests/0180-share_consumer_config.c +1677 -0
- package/deps/librdkafka/tests/0181-share_consumer_topic_delete.c +354 -0
- package/deps/librdkafka/tests/0182-share_consumer_error_handling_mock.c +3358 -0
- package/deps/librdkafka/tests/0183-share_consumer_leader_change_mock.c +1234 -0
- package/deps/librdkafka/tests/0184-share_consumer_topic_recreate.c +1356 -0
- package/deps/librdkafka/tests/0185-share_consumer_max_poll_interval.c +557 -0
- package/deps/librdkafka/tests/0186-share_consumer_fatal_error.c +360 -0
- package/deps/librdkafka/tests/0190-share_consumer_telemetry.c +387 -0
- package/deps/librdkafka/tests/1000-unktopic.c +164 -0
- package/deps/librdkafka/tests/8000-idle.cpp +60 -0
- package/deps/librdkafka/tests/8001-fetch_from_follower_mock_manual.c +113 -0
- package/deps/librdkafka/tests/CMakeLists.txt +193 -0
- package/deps/librdkafka/tests/LibrdkafkaTestApp.py +291 -0
- package/deps/librdkafka/tests/Makefile +182 -0
- package/deps/librdkafka/tests/README.md +514 -0
- package/deps/librdkafka/tests/autotest.sh +33 -0
- package/deps/librdkafka/tests/backtrace.gdb +30 -0
- package/deps/librdkafka/tests/broker_version_tests.py +315 -0
- package/deps/librdkafka/tests/buildbox.sh +17 -0
- package/deps/librdkafka/tests/cleanup-checker-tests.sh +20 -0
- package/deps/librdkafka/tests/cluster_testing.py +203 -0
- package/deps/librdkafka/tests/delete-test-topics.sh +56 -0
- package/deps/librdkafka/tests/fixtures/oauthbearer/jwt_assertion_template.json +10 -0
- package/deps/librdkafka/tests/fixtures/ssl/Makefile +8 -0
- package/deps/librdkafka/tests/fixtures/ssl/README.md +13 -0
- package/deps/librdkafka/tests/fixtures/ssl/client.keystore.intermediate.p12 +0 -0
- package/deps/librdkafka/tests/fixtures/ssl/client.keystore.p12 +0 -0
- package/deps/librdkafka/tests/fixtures/ssl/client2.certificate.intermediate.pem +72 -0
- package/deps/librdkafka/tests/fixtures/ssl/client2.certificate.pem +50 -0
- package/deps/librdkafka/tests/fixtures/ssl/client2.intermediate.key +46 -0
- package/deps/librdkafka/tests/fixtures/ssl/client2.key +46 -0
- package/deps/librdkafka/tests/fixtures/ssl/create_keys.sh +168 -0
- package/deps/librdkafka/tests/fuzzers/Makefile +12 -0
- package/deps/librdkafka/tests/fuzzers/README.md +31 -0
- package/deps/librdkafka/tests/fuzzers/fuzz_regex.c +74 -0
- package/deps/librdkafka/tests/fuzzers/helpers.h +90 -0
- package/deps/librdkafka/tests/gen-ssl-certs.sh +165 -0
- package/deps/librdkafka/tests/interactive_broker_version.py +170 -0
- package/deps/librdkafka/tests/interceptor_test/CMakeLists.txt +16 -0
- package/deps/librdkafka/tests/interceptor_test/Makefile +22 -0
- package/deps/librdkafka/tests/interceptor_test/interceptor_test.c +314 -0
- package/deps/librdkafka/tests/interceptor_test/interceptor_test.h +54 -0
- package/deps/librdkafka/tests/java/IncrementalRebalanceCli.java +97 -0
- package/deps/librdkafka/tests/java/Makefile +13 -0
- package/deps/librdkafka/tests/java/Murmur2Cli.java +46 -0
- package/deps/librdkafka/tests/java/README.md +14 -0
- package/deps/librdkafka/tests/java/TransactionProducerCli.java +162 -0
- package/deps/librdkafka/tests/java/run-class.sh +11 -0
- package/deps/librdkafka/tests/librdkafka.suppressions +483 -0
- package/deps/librdkafka/tests/lz4_manual_test.sh +59 -0
- package/deps/librdkafka/tests/multi-broker-version-test.sh +50 -0
- package/deps/librdkafka/tests/parse-refcnt.sh +43 -0
- package/deps/librdkafka/tests/performance_plot.py +115 -0
- package/deps/librdkafka/tests/plugin_test/Makefile +19 -0
- package/deps/librdkafka/tests/plugin_test/plugin_test.c +58 -0
- package/deps/librdkafka/tests/requirements.txt +2 -0
- package/deps/librdkafka/tests/run-all-tests.sh +81 -0
- package/deps/librdkafka/tests/run-consumer-tests.sh +16 -0
- package/deps/librdkafka/tests/run-producer-tests.sh +16 -0
- package/deps/librdkafka/tests/run-test-batches.py +157 -0
- package/deps/librdkafka/tests/run-test.sh +141 -0
- package/deps/librdkafka/tests/rusage.c +249 -0
- package/deps/librdkafka/tests/sasl_test.py +289 -0
- package/deps/librdkafka/tests/scenarios/README.md +6 -0
- package/deps/librdkafka/tests/scenarios/ak23.json +6 -0
- package/deps/librdkafka/tests/scenarios/default.json +5 -0
- package/deps/librdkafka/tests/scenarios/noautocreate.json +5 -0
- package/deps/librdkafka/tests/share_telemetry_verify/share-otel-collector-config.yaml +28 -0
- package/deps/librdkafka/tests/sockem.c +801 -0
- package/deps/librdkafka/tests/sockem.h +85 -0
- package/deps/librdkafka/tests/sockem_ctrl.c +145 -0
- package/deps/librdkafka/tests/sockem_ctrl.h +61 -0
- package/deps/librdkafka/tests/test.c +8376 -0
- package/deps/librdkafka/tests/test.conf.example +27 -0
- package/deps/librdkafka/tests/test.h +1150 -0
- package/deps/librdkafka/tests/testcpp.cpp +131 -0
- package/deps/librdkafka/tests/testcpp.h +388 -0
- package/deps/librdkafka/tests/testshared.h +416 -0
- package/deps/librdkafka/tests/tools/README.md +4 -0
- package/deps/librdkafka/tests/tools/stats/README.md +21 -0
- package/deps/librdkafka/tests/tools/stats/filter.jq +42 -0
- package/deps/librdkafka/tests/tools/stats/graph.py +150 -0
- package/deps/librdkafka/tests/tools/stats/requirements.txt +3 -0
- package/deps/librdkafka/tests/tools/stats/to_csv.py +124 -0
- package/deps/librdkafka/tests/trivup/trivup-0.15.0.tar.gz +0 -0
- package/deps/librdkafka/tests/until-fail.sh +87 -0
- package/deps/librdkafka/tests/xxxx-assign_partition.c +122 -0
- package/deps/librdkafka/tests/xxxx-metadata.cpp +159 -0
- package/deps/librdkafka/vcpkg.json +23 -0
- package/deps/librdkafka/win32/README.md +5 -0
- package/deps/librdkafka/win32/build-package.bat +3 -0
- package/deps/librdkafka/win32/build.bat +19 -0
- package/deps/librdkafka/win32/common.vcxproj +84 -0
- package/deps/librdkafka/win32/interceptor_test/interceptor_test.vcxproj +87 -0
- package/deps/librdkafka/win32/librdkafka.autopkg.template +54 -0
- package/deps/librdkafka/win32/librdkafka.master.testing.targets +13 -0
- package/deps/librdkafka/win32/librdkafka.sln +226 -0
- package/deps/librdkafka/win32/librdkafka.vcxproj +282 -0
- package/deps/librdkafka/win32/librdkafkacpp/librdkafkacpp.vcxproj +104 -0
- package/deps/librdkafka/win32/msbuild.ps1 +58 -0
- package/deps/librdkafka/win32/openssl_engine_example/openssl_engine_example.vcxproj +132 -0
- package/deps/librdkafka/win32/package-zip.ps1 +46 -0
- package/deps/librdkafka/win32/packages/repositories.config +4 -0
- package/deps/librdkafka/win32/push-package.bat +4 -0
- package/deps/librdkafka/win32/rdkafka_complex_consumer_example_cpp/rdkafka_complex_consumer_example_cpp.vcxproj +67 -0
- package/deps/librdkafka/win32/rdkafka_example/rdkafka_example.vcxproj +97 -0
- package/deps/librdkafka/win32/rdkafka_performance/rdkafka_performance.vcxproj +97 -0
- package/deps/librdkafka/win32/setup-msys2.ps1 +47 -0
- package/deps/librdkafka/win32/setup-vcpkg.ps1 +34 -0
- package/deps/librdkafka/win32/tests/test.conf.example +25 -0
- package/deps/librdkafka/win32/tests/tests.vcxproj +278 -0
- package/deps/librdkafka/win32/win_ssl_cert_store/win_ssl_cert_store.vcxproj +132 -0
- package/deps/librdkafka/win32/wingetopt.c +564 -0
- package/deps/librdkafka/win32/wingetopt.h +101 -0
- package/deps/librdkafka/win32/wintime.h +33 -0
- package/deps/librdkafka.gyp +83 -0
- package/deps/windows-install.py +80 -0
- package/index.d.ts +3 -0
- package/lib/admin.js +732 -0
- package/lib/client.js +679 -0
- package/lib/error.js +501 -0
- package/lib/index.js +8 -0
- package/lib/kafka-consumer-stream.js +399 -0
- package/lib/kafka-consumer.js +798 -0
- package/lib/kafkajs/_admin.js +1052 -0
- package/lib/kafkajs/_common.js +887 -0
- package/lib/kafkajs/_consumer.js +2156 -0
- package/lib/kafkajs/_consumer_cache.js +310 -0
- package/lib/kafkajs/_error.js +298 -0
- package/lib/kafkajs/_kafka.js +125 -0
- package/lib/kafkajs/_linked-list.js +220 -0
- package/lib/kafkajs/_producer.js +863 -0
- package/lib/kafkajs/index.js +5 -0
- package/lib/producer/high-level-producer.js +400 -0
- package/lib/producer-stream.js +337 -0
- package/lib/producer.js +405 -0
- package/lib/rdkafka.js +44 -0
- package/lib/tools/ref-counter.js +53 -0
- package/lib/topic-partition.js +91 -0
- package/lib/topic.js +43 -0
- package/lib/util.js +55 -0
- package/librdkafka.js +12 -0
- package/package.json +85 -0
- package/shim/source_location +54 -0
- package/src/admin.cc +1597 -0
- package/src/admin.h +112 -0
- package/src/binding.cc +76 -0
- package/src/binding.h +25 -0
- package/src/callbacks.cc +676 -0
- package/src/callbacks.h +302 -0
- package/src/common.cc +1628 -0
- package/src/common.h +192 -0
- package/src/config.cc +219 -0
- package/src/config.h +61 -0
- package/src/connection.cc +703 -0
- package/src/connection.h +117 -0
- package/src/errors.cc +119 -0
- package/src/errors.h +60 -0
- package/src/kafka-consumer.cc +1539 -0
- package/src/kafka-consumer.h +147 -0
- package/src/producer.cc +917 -0
- package/src/producer.h +124 -0
- package/src/topic.cc +173 -0
- package/src/topic.h +54 -0
- package/src/workers.cc +1714 -0
- package/src/workers.h +690 -0
- package/types/config.d.ts +1269 -0
- package/types/errors.d.ts +373 -0
- package/types/kafkajs.d.ts +568 -0
- package/types/rdkafka.d.ts +571 -0
- package/util/configure.js +32 -0
- package/util/generate-docs.sh +18 -0
- package/util/get-env.js +6 -0
- package/util/test-compile.js +11 -0
- package/util/test-producer-delivery.js +100 -0
|
@@ -0,0 +1,2192 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* librdkafka - Apache Kafka C library
|
|
3
|
+
*
|
|
4
|
+
* Copyright (c) 2025, Confluent Inc.
|
|
5
|
+
* All rights reserved.
|
|
6
|
+
*
|
|
7
|
+
* Redistribution and use in source and binary forms, with or without
|
|
8
|
+
* modification, are permitted provided that the following conditions are met:
|
|
9
|
+
*
|
|
10
|
+
* 1. Redistributions of source code must retain the above copyright notice,
|
|
11
|
+
* this list of conditions and the following disclaimer.
|
|
12
|
+
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
* this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
* and/or other materials provided with the distribution.
|
|
15
|
+
*
|
|
16
|
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
17
|
+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
18
|
+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
19
|
+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
20
|
+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
21
|
+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
22
|
+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
23
|
+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
24
|
+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
25
|
+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
26
|
+
* POSSIBILITY OF SUCH DAMAGE.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
#include "test.h"
|
|
30
|
+
#include "testshared.h"
|
|
31
|
+
#include "rdkafka.h"
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @brief Share consumer subscription tests using operation-based framework.
|
|
35
|
+
*
|
|
36
|
+
* This test file uses a declarative, operation-based framework where tests
|
|
37
|
+
* are defined as sequences of operations. The framework handles:
|
|
38
|
+
* - Automatic topic name generation
|
|
39
|
+
* - Topic creation and deletion
|
|
40
|
+
* - Consumer creation and destruction
|
|
41
|
+
* - Message production and consumption
|
|
42
|
+
* - Subscription verification
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
#define MAX_TOPICS 20
|
|
47
|
+
#define MAX_CONSUMERS 4
|
|
48
|
+
#define MAX_OPS 50
|
|
49
|
+
|
|
50
|
+
/** Common producer reused across all tests. */
|
|
51
|
+
static rd_kafka_t *common_producer;
|
|
52
|
+
|
|
53
|
+
/** Common admin client reused across all tests. */
|
|
54
|
+
static rd_kafka_t *common_admin;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @brief Operation types for subscription tests
|
|
58
|
+
*/
|
|
59
|
+
typedef enum {
|
|
60
|
+
TEST_OP_END = 0, /**< End of operations marker */
|
|
61
|
+
TEST_OP_SUBSCRIBE, /**< Subscribe to N new topics */
|
|
62
|
+
TEST_OP_SUBSCRIBE_ADD, /**< Add N topics to existing subscription */
|
|
63
|
+
TEST_OP_UNSUBSCRIBE, /**< Unsubscribe from all topics */
|
|
64
|
+
TEST_OP_RESUBSCRIBE, /**< Replace subscription with N new topics */
|
|
65
|
+
TEST_OP_PRODUCE, /**< Produce to specified topic set */
|
|
66
|
+
TEST_OP_CONSUME, /**< Consume messages */
|
|
67
|
+
TEST_OP_VERIFY_SUB_CNT, /**< Verify subscription count */
|
|
68
|
+
TEST_OP_DELETE_TOPIC, /**< Delete topic by index */
|
|
69
|
+
TEST_OP_WAIT, /**< Wait for specified milliseconds */
|
|
70
|
+
TEST_OP_CREATE_CONSUMER, /**< Create additional consumer */
|
|
71
|
+
TEST_OP_POLL_NO_SUB, /**< Poll without subscription (edge case) */
|
|
72
|
+
TEST_OP_CREATE_TOPIC, /**< Create subscribed topics that weren't created
|
|
73
|
+
*/
|
|
74
|
+
TEST_OP_SUBSCRIBE_EXISTING, /**< Subscribe to already created topics */
|
|
75
|
+
TEST_OP_PRODUCE_TO_TOPIC, /**< Produce to specific topic index */
|
|
76
|
+
TEST_OP_SUBSCRIBE_EMPTY, /**< Subscribe with empty list (==
|
|
77
|
+
unsubscribe) */
|
|
78
|
+
} test_op_type_t;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* @brief Flags for operations
|
|
82
|
+
*/
|
|
83
|
+
typedef enum {
|
|
84
|
+
TEST_OP_F_NONE = 0,
|
|
85
|
+
TEST_OP_F_SKIP_TOPIC_CREATE = 1 << 0, /**< Don't create topics */
|
|
86
|
+
TEST_OP_F_PRODUCE_TO_OLD = 1 << 1, /**< Produce to old subscription */
|
|
87
|
+
TEST_OP_F_VERIFY_NO_OLD_MSGS = 1 << 2, /**< Verify no old messages */
|
|
88
|
+
} test_op_flags_t;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* @brief Single operation in a test scenario
|
|
92
|
+
*/
|
|
93
|
+
typedef struct {
|
|
94
|
+
test_op_type_t op; /**< Operation type */
|
|
95
|
+
int topic_cnt; /**< Number of topics (SUBSCRIBE/RESUBSCRIBE) */
|
|
96
|
+
int msgs_per_topic; /**< Messages per topic (PRODUCE) */
|
|
97
|
+
int expected_msgs; /**< Expected message count (CONSUME, -1=any) */
|
|
98
|
+
int expected_sub_cnt; /**< Expected subscription count (VERIFY_SUB_CNT)
|
|
99
|
+
*/
|
|
100
|
+
int topic_idx; /**< Topic index (DELETE_TOPIC) */
|
|
101
|
+
int wait_ms; /**< Wait time (WAIT) */
|
|
102
|
+
int consumer_idx; /**< Consumer index (multi-consumer) */
|
|
103
|
+
int repeat_cnt; /**< Repeat count (SUBSCRIBE/UNSUBSCRIBE) */
|
|
104
|
+
test_op_flags_t flags; /**< Operation flags */
|
|
105
|
+
} test_op_t;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* @brief Test scenario configuration
|
|
109
|
+
*/
|
|
110
|
+
typedef struct {
|
|
111
|
+
const char *name; /**< Test name for logging */
|
|
112
|
+
int consumer_cnt; /**< Number of consumers (default: 1) */
|
|
113
|
+
test_op_t ops[MAX_OPS]; /**< Operations, terminated by TEST_OP_END */
|
|
114
|
+
} test_scenario_t;
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* @brief Runtime state for test execution
|
|
118
|
+
*/
|
|
119
|
+
typedef struct {
|
|
120
|
+
/* Consumers */
|
|
121
|
+
rd_kafka_share_t *consumers[MAX_CONSUMERS];
|
|
122
|
+
int consumer_cnt;
|
|
123
|
+
|
|
124
|
+
/* Topics: all created topics */
|
|
125
|
+
char *all_topics[MAX_TOPICS];
|
|
126
|
+
int all_topic_cnt;
|
|
127
|
+
int msgs_produced[MAX_TOPICS]; /**< Messages produced per topic */
|
|
128
|
+
|
|
129
|
+
/* Current subscription tracking per consumer */
|
|
130
|
+
int sub_start_idx[MAX_CONSUMERS]; /**< Start index in all_topics */
|
|
131
|
+
int sub_count[MAX_CONSUMERS]; /**< Count of subscribed topics */
|
|
132
|
+
|
|
133
|
+
/* Previous subscription (for RESUBSCRIBE verification) */
|
|
134
|
+
int old_sub_start_idx;
|
|
135
|
+
int old_sub_count;
|
|
136
|
+
|
|
137
|
+
/* Group name */
|
|
138
|
+
char group_name[128];
|
|
139
|
+
} sub_test_state_t;
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
#define SUBSCRIBE(n) \
|
|
143
|
+
{ .op = TEST_OP_SUBSCRIBE, .topic_cnt = (n), .repeat_cnt = 1 }
|
|
144
|
+
#define SUBSCRIBE_REPEAT(n, r) \
|
|
145
|
+
{ .op = TEST_OP_SUBSCRIBE, .topic_cnt = (n), .repeat_cnt = (r) }
|
|
146
|
+
#define SUBSCRIBE_ADD(n) \
|
|
147
|
+
{ .op = TEST_OP_SUBSCRIBE_ADD, .topic_cnt = (n) }
|
|
148
|
+
#define SUBSCRIBE_NO_CREATE(n) \
|
|
149
|
+
{ \
|
|
150
|
+
.op = TEST_OP_SUBSCRIBE, .topic_cnt = (n), .repeat_cnt = 1, \
|
|
151
|
+
.flags = TEST_OP_F_SKIP_TOPIC_CREATE \
|
|
152
|
+
}
|
|
153
|
+
#define UNSUBSCRIBE() \
|
|
154
|
+
{ .op = TEST_OP_UNSUBSCRIBE, .repeat_cnt = 1 }
|
|
155
|
+
#define UNSUBSCRIBE_REPEAT(r) \
|
|
156
|
+
{ .op = TEST_OP_UNSUBSCRIBE, .repeat_cnt = (r) }
|
|
157
|
+
#define RESUBSCRIBE(n) \
|
|
158
|
+
{ .op = TEST_OP_RESUBSCRIBE, .topic_cnt = (n) }
|
|
159
|
+
#define PRODUCE(msgs) \
|
|
160
|
+
{ .op = TEST_OP_PRODUCE, .msgs_per_topic = (msgs) }
|
|
161
|
+
#define PRODUCE_TO_OLD(msgs) \
|
|
162
|
+
{ \
|
|
163
|
+
.op = TEST_OP_PRODUCE, .msgs_per_topic = (msgs), \
|
|
164
|
+
.flags = TEST_OP_F_PRODUCE_TO_OLD \
|
|
165
|
+
}
|
|
166
|
+
#define PRODUCE_TO_TOPIC(idx, msgs) \
|
|
167
|
+
{ \
|
|
168
|
+
.op = TEST_OP_PRODUCE_TO_TOPIC, .topic_idx = (idx), \
|
|
169
|
+
.msgs_per_topic = (msgs) \
|
|
170
|
+
}
|
|
171
|
+
#define CONSUME(expected) \
|
|
172
|
+
{ .op = TEST_OP_CONSUME, .expected_msgs = (expected) }
|
|
173
|
+
#define CONSUME_VERIFY_NO_OLD(expected) \
|
|
174
|
+
{ \
|
|
175
|
+
.op = TEST_OP_CONSUME, .expected_msgs = (expected), \
|
|
176
|
+
.flags = TEST_OP_F_VERIFY_NO_OLD_MSGS \
|
|
177
|
+
}
|
|
178
|
+
#define CONSUME_ANY() \
|
|
179
|
+
{ .op = TEST_OP_CONSUME, .expected_msgs = -1 }
|
|
180
|
+
#define VERIFY_SUB(cnt) \
|
|
181
|
+
{ .op = TEST_OP_VERIFY_SUB_CNT, .expected_sub_cnt = (cnt) }
|
|
182
|
+
#define DELETE_TOPIC(idx) \
|
|
183
|
+
{ .op = TEST_OP_DELETE_TOPIC, .topic_idx = (idx) }
|
|
184
|
+
#define WAIT_MS(ms) \
|
|
185
|
+
{ .op = TEST_OP_WAIT, .wait_ms = (ms) }
|
|
186
|
+
#define CREATE_CONSUMER(idx) \
|
|
187
|
+
{ .op = TEST_OP_CREATE_CONSUMER, .consumer_idx = (idx) }
|
|
188
|
+
#define CREATE_TOPIC(n) \
|
|
189
|
+
{ .op = TEST_OP_CREATE_TOPIC, .topic_cnt = (n) }
|
|
190
|
+
#define SUBSCRIBE_EXISTING() \
|
|
191
|
+
{ .op = TEST_OP_SUBSCRIBE_EXISTING, .repeat_cnt = 1 }
|
|
192
|
+
#define POLL_NO_SUB() \
|
|
193
|
+
{ .op = TEST_OP_POLL_NO_SUB }
|
|
194
|
+
#define SUBSCRIBE_EMPTY() \
|
|
195
|
+
{ .op = TEST_OP_SUBSCRIBE_EMPTY }
|
|
196
|
+
#define TEST_OPS_END() \
|
|
197
|
+
{ .op = TEST_OP_END }
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* @brief Create a new topic with auto-generated name
|
|
202
|
+
*/
|
|
203
|
+
static const char *state_create_topic(sub_test_state_t *state,
|
|
204
|
+
rd_bool_t wait_exists) {
|
|
205
|
+
char name[128];
|
|
206
|
+
|
|
207
|
+
TEST_ASSERT(state->all_topic_cnt < MAX_TOPICS,
|
|
208
|
+
"Too many topics created");
|
|
209
|
+
|
|
210
|
+
rd_snprintf(name, sizeof(name), "0170-t%d", state->all_topic_cnt);
|
|
211
|
+
state->all_topics[state->all_topic_cnt] =
|
|
212
|
+
rd_strdup(test_mk_topic_name(name, 1));
|
|
213
|
+
|
|
214
|
+
if (wait_exists) {
|
|
215
|
+
test_create_topic_wait_exists(
|
|
216
|
+
NULL, state->all_topics[state->all_topic_cnt], 1, -1,
|
|
217
|
+
30000);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
state->msgs_produced[state->all_topic_cnt] = 0;
|
|
221
|
+
return state->all_topics[state->all_topic_cnt++];
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* @brief Execute TEST_OP_SUBSCRIBE
|
|
226
|
+
*/
|
|
227
|
+
static void exec_subscribe(sub_test_state_t *state, const test_op_t *op) {
|
|
228
|
+
rd_kafka_topic_partition_list_t *tlist;
|
|
229
|
+
int cidx = op->consumer_idx;
|
|
230
|
+
int i, r;
|
|
231
|
+
|
|
232
|
+
TEST_SAY(" SUBSCRIBE: %d topic(s), repeat=%d, consumer=%d\n",
|
|
233
|
+
op->topic_cnt, op->repeat_cnt, cidx);
|
|
234
|
+
|
|
235
|
+
/* Save old subscription for RESUBSCRIBE verification */
|
|
236
|
+
state->old_sub_start_idx = state->sub_start_idx[cidx];
|
|
237
|
+
state->old_sub_count = state->sub_count[cidx];
|
|
238
|
+
|
|
239
|
+
/* Track new subscription */
|
|
240
|
+
state->sub_start_idx[cidx] = state->all_topic_cnt;
|
|
241
|
+
state->sub_count[cidx] = op->topic_cnt;
|
|
242
|
+
|
|
243
|
+
/* Create topics and build subscription list */
|
|
244
|
+
tlist = rd_kafka_topic_partition_list_new(op->topic_cnt);
|
|
245
|
+
for (i = 0; i < op->topic_cnt; i++) {
|
|
246
|
+
const char *topic = state_create_topic(
|
|
247
|
+
state, !(op->flags & TEST_OP_F_SKIP_TOPIC_CREATE));
|
|
248
|
+
rd_kafka_topic_partition_list_add(tlist, topic,
|
|
249
|
+
RD_KAFKA_PARTITION_UA);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/* Subscribe (possibly multiple times) */
|
|
253
|
+
for (r = 0; r < op->repeat_cnt; r++) {
|
|
254
|
+
TEST_CALL_ERR__(
|
|
255
|
+
rd_kafka_share_subscribe(state->consumers[cidx], tlist));
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
rd_kafka_topic_partition_list_destroy(tlist);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* @brief Execute TEST_OP_SUBSCRIBE_ADD (incremental - add to existing
|
|
263
|
+
* subscription)
|
|
264
|
+
*/
|
|
265
|
+
static void exec_subscribe_add(sub_test_state_t *state, const test_op_t *op) {
|
|
266
|
+
rd_kafka_topic_partition_list_t *tlist;
|
|
267
|
+
int cidx = op->consumer_idx;
|
|
268
|
+
int i;
|
|
269
|
+
int new_start = state->all_topic_cnt;
|
|
270
|
+
|
|
271
|
+
TEST_SAY(
|
|
272
|
+
" SUBSCRIBE_ADD: adding %d topic(s) to existing %d, consumer=%d\n",
|
|
273
|
+
op->topic_cnt, state->sub_count[cidx], cidx);
|
|
274
|
+
|
|
275
|
+
/* Build subscription list including existing + new topics */
|
|
276
|
+
tlist = rd_kafka_topic_partition_list_new(state->sub_count[cidx] +
|
|
277
|
+
op->topic_cnt);
|
|
278
|
+
|
|
279
|
+
/* Add existing subscribed topics */
|
|
280
|
+
for (i = 0; i < state->sub_count[cidx]; i++) {
|
|
281
|
+
int idx = state->sub_start_idx[cidx] + i;
|
|
282
|
+
rd_kafka_topic_partition_list_add(tlist, state->all_topics[idx],
|
|
283
|
+
RD_KAFKA_PARTITION_UA);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/* Create and add new topics */
|
|
287
|
+
for (i = 0; i < op->topic_cnt; i++) {
|
|
288
|
+
const char *topic = state_create_topic(state, rd_true);
|
|
289
|
+
rd_kafka_topic_partition_list_add(tlist, topic,
|
|
290
|
+
RD_KAFKA_PARTITION_UA);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
TEST_CALL_ERR__(
|
|
294
|
+
rd_kafka_share_subscribe(state->consumers[cidx], tlist));
|
|
295
|
+
|
|
296
|
+
/* Update subscription tracking - topics are now spread across ranges */
|
|
297
|
+
state->sub_start_idx[cidx] = new_start - state->sub_count[cidx];
|
|
298
|
+
state->sub_count[cidx] += op->topic_cnt;
|
|
299
|
+
|
|
300
|
+
rd_kafka_topic_partition_list_destroy(tlist);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* @brief Execute TEST_OP_CREATE_TOPIC (create topics that weren't created)
|
|
305
|
+
*/
|
|
306
|
+
static void exec_create_topic(sub_test_state_t *state, const test_op_t *op) {
|
|
307
|
+
int cidx = op->consumer_idx;
|
|
308
|
+
int i;
|
|
309
|
+
|
|
310
|
+
TEST_SAY(" CREATE_TOPIC: creating subscribed topics for consumer=%d\n",
|
|
311
|
+
cidx);
|
|
312
|
+
|
|
313
|
+
/* Create the topics that were subscribed to but not yet created */
|
|
314
|
+
for (i = 0; i < state->sub_count[cidx]; i++) {
|
|
315
|
+
int idx = state->sub_start_idx[cidx] + i;
|
|
316
|
+
if (state->all_topics[idx]) {
|
|
317
|
+
test_create_topic_wait_exists(
|
|
318
|
+
NULL, state->all_topics[idx], 1, -1, 30000);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* @brief Execute TEST_OP_SUBSCRIBE_EXISTING (subscribe to all created topics)
|
|
325
|
+
*/
|
|
326
|
+
static void exec_subscribe_existing(sub_test_state_t *state,
|
|
327
|
+
const test_op_t *op) {
|
|
328
|
+
rd_kafka_topic_partition_list_t *tlist;
|
|
329
|
+
int cidx = op->consumer_idx;
|
|
330
|
+
int i;
|
|
331
|
+
|
|
332
|
+
TEST_SAY(" SUBSCRIBE_EXISTING: %d topic(s), consumer=%d\n",
|
|
333
|
+
state->all_topic_cnt, cidx);
|
|
334
|
+
|
|
335
|
+
tlist = rd_kafka_topic_partition_list_new(state->all_topic_cnt);
|
|
336
|
+
|
|
337
|
+
for (i = 0; i < state->all_topic_cnt; i++) {
|
|
338
|
+
rd_kafka_topic_partition_list_add(tlist, state->all_topics[i],
|
|
339
|
+
RD_KAFKA_PARTITION_UA);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
TEST_CALL_ERR__(
|
|
343
|
+
rd_kafka_share_subscribe(state->consumers[cidx], tlist));
|
|
344
|
+
|
|
345
|
+
state->sub_start_idx[cidx] = 0;
|
|
346
|
+
state->sub_count[cidx] = state->all_topic_cnt;
|
|
347
|
+
|
|
348
|
+
rd_kafka_topic_partition_list_destroy(tlist);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* @brief Execute TEST_OP_PRODUCE_TO_TOPIC (produce to specific topic by index)
|
|
353
|
+
*/
|
|
354
|
+
static void exec_produce_to_topic(sub_test_state_t *state,
|
|
355
|
+
const test_op_t *op) {
|
|
356
|
+
int cidx = op->consumer_idx;
|
|
357
|
+
int idx = state->sub_start_idx[cidx] + op->topic_idx;
|
|
358
|
+
|
|
359
|
+
TEST_ASSERT(op->topic_idx < state->sub_count[cidx],
|
|
360
|
+
"Topic index %d out of range (sub_count=%d)", op->topic_idx,
|
|
361
|
+
state->sub_count[cidx]);
|
|
362
|
+
|
|
363
|
+
TEST_SAY(" PRODUCE_TO_TOPIC: %d msgs to topic[%d] (%s)\n",
|
|
364
|
+
op->msgs_per_topic, op->topic_idx, state->all_topics[idx]);
|
|
365
|
+
|
|
366
|
+
test_produce_msgs_simple(common_producer, state->all_topics[idx], 0,
|
|
367
|
+
op->msgs_per_topic);
|
|
368
|
+
state->msgs_produced[idx] += op->msgs_per_topic;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* @brief Execute TEST_OP_RESUBSCRIBE (replace subscription with new topics)
|
|
373
|
+
*/
|
|
374
|
+
static void exec_resubscribe(sub_test_state_t *state, const test_op_t *op) {
|
|
375
|
+
rd_kafka_topic_partition_list_t *tlist;
|
|
376
|
+
int cidx = op->consumer_idx;
|
|
377
|
+
int i;
|
|
378
|
+
|
|
379
|
+
TEST_SAY(" RESUBSCRIBE: %d new topic(s), consumer=%d\n", op->topic_cnt,
|
|
380
|
+
cidx);
|
|
381
|
+
|
|
382
|
+
/* Save old subscription */
|
|
383
|
+
state->old_sub_start_idx = state->sub_start_idx[cidx];
|
|
384
|
+
state->old_sub_count = state->sub_count[cidx];
|
|
385
|
+
|
|
386
|
+
/* Track new subscription */
|
|
387
|
+
state->sub_start_idx[cidx] = state->all_topic_cnt;
|
|
388
|
+
state->sub_count[cidx] = op->topic_cnt;
|
|
389
|
+
|
|
390
|
+
/* Create new topics */
|
|
391
|
+
tlist = rd_kafka_topic_partition_list_new(op->topic_cnt);
|
|
392
|
+
for (i = 0; i < op->topic_cnt; i++) {
|
|
393
|
+
const char *topic = state_create_topic(state, rd_true);
|
|
394
|
+
rd_kafka_topic_partition_list_add(tlist, topic,
|
|
395
|
+
RD_KAFKA_PARTITION_UA);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
TEST_CALL_ERR__(
|
|
399
|
+
rd_kafka_share_subscribe(state->consumers[cidx], tlist));
|
|
400
|
+
rd_kafka_topic_partition_list_destroy(tlist);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* @brief Execute TEST_OP_UNSUBSCRIBE
|
|
405
|
+
*/
|
|
406
|
+
static void exec_unsubscribe(sub_test_state_t *state, const test_op_t *op) {
|
|
407
|
+
int cidx = op->consumer_idx;
|
|
408
|
+
int r;
|
|
409
|
+
|
|
410
|
+
TEST_SAY(" UNSUBSCRIBE: repeat=%d, consumer=%d\n", op->repeat_cnt,
|
|
411
|
+
cidx);
|
|
412
|
+
|
|
413
|
+
for (r = 0; r < op->repeat_cnt; r++) {
|
|
414
|
+
TEST_CALL_ERR__(
|
|
415
|
+
rd_kafka_share_unsubscribe(state->consumers[cidx]));
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
state->sub_count[cidx] = 0;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* @brief Execute TEST_OP_SUBSCRIBE_EMPTY
|
|
423
|
+
*
|
|
424
|
+
* Subscribe with an empty topic list is equivalent to unsubscribe:
|
|
425
|
+
* must return NO_ERROR and clear the subscription flag.
|
|
426
|
+
*/
|
|
427
|
+
static void exec_subscribe_empty(sub_test_state_t *state, const test_op_t *op) {
|
|
428
|
+
rd_kafka_topic_partition_list_t *tlist;
|
|
429
|
+
int cidx = op->consumer_idx;
|
|
430
|
+
|
|
431
|
+
TEST_SAY(" SUBSCRIBE_EMPTY: consumer=%d\n", cidx);
|
|
432
|
+
|
|
433
|
+
tlist = rd_kafka_topic_partition_list_new(0);
|
|
434
|
+
TEST_CALL_ERR__(
|
|
435
|
+
rd_kafka_share_subscribe(state->consumers[cidx], tlist));
|
|
436
|
+
rd_kafka_topic_partition_list_destroy(tlist);
|
|
437
|
+
|
|
438
|
+
state->sub_count[cidx] = 0;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* @brief Execute TEST_OP_PRODUCE
|
|
443
|
+
*/
|
|
444
|
+
static void exec_produce(sub_test_state_t *state, const test_op_t *op) {
|
|
445
|
+
int cidx = op->consumer_idx;
|
|
446
|
+
int start_idx, count, i;
|
|
447
|
+
|
|
448
|
+
if (op->flags & TEST_OP_F_PRODUCE_TO_OLD) {
|
|
449
|
+
start_idx = state->old_sub_start_idx;
|
|
450
|
+
count = state->old_sub_count;
|
|
451
|
+
TEST_SAY(" PRODUCE: %d msgs/topic to OLD %d topic(s)\n",
|
|
452
|
+
op->msgs_per_topic, count);
|
|
453
|
+
} else {
|
|
454
|
+
start_idx = state->sub_start_idx[cidx];
|
|
455
|
+
count = state->sub_count[cidx];
|
|
456
|
+
TEST_SAY(" PRODUCE: %d msgs/topic to %d topic(s)\n",
|
|
457
|
+
op->msgs_per_topic, count);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
for (i = 0; i < count; i++) {
|
|
461
|
+
int idx = start_idx + i;
|
|
462
|
+
test_produce_msgs_simple(common_producer,
|
|
463
|
+
state->all_topics[idx], 0,
|
|
464
|
+
op->msgs_per_topic);
|
|
465
|
+
state->msgs_produced[idx] += op->msgs_per_topic;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* @brief Execute TEST_OP_CONSUME
|
|
471
|
+
*/
|
|
472
|
+
static void exec_consume(sub_test_state_t *state, const test_op_t *op) {
|
|
473
|
+
int cidx = op->consumer_idx;
|
|
474
|
+
int start_idx = state->sub_start_idx[cidx];
|
|
475
|
+
int count = state->sub_count[cidx];
|
|
476
|
+
const char *topics[MAX_TOPICS];
|
|
477
|
+
int i, consumed;
|
|
478
|
+
|
|
479
|
+
/* Build expected topics array */
|
|
480
|
+
for (i = 0; i < count; i++) {
|
|
481
|
+
topics[i] = state->all_topics[start_idx + i];
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
if (op->expected_msgs >= 0) {
|
|
485
|
+
TEST_SAY(" CONSUME: expecting %d msgs from %d topic(s)\n",
|
|
486
|
+
op->expected_msgs, count);
|
|
487
|
+
consumed = test_share_consume_msgs(
|
|
488
|
+
state->consumers[cidx], op->expected_msgs, 25, 3000,
|
|
489
|
+
count > 0 ? topics : NULL, count);
|
|
490
|
+
|
|
491
|
+
if (op->flags & TEST_OP_F_VERIFY_NO_OLD_MSGS) {
|
|
492
|
+
TEST_ASSERT(consumed >= 0,
|
|
493
|
+
"Received message from old subscription!");
|
|
494
|
+
}
|
|
495
|
+
TEST_ASSERT(consumed == op->expected_msgs,
|
|
496
|
+
"Expected %d messages, got %d", op->expected_msgs,
|
|
497
|
+
consumed);
|
|
498
|
+
} else {
|
|
499
|
+
/* Consume any available */
|
|
500
|
+
TEST_SAY(" CONSUME: any available from %d topic(s)\n", count);
|
|
501
|
+
test_share_consume_msgs(state->consumers[cidx], 100, 10, 2000,
|
|
502
|
+
count > 0 ? topics : NULL, count);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* @brief Execute TEST_OP_VERIFY_SUB_CNT
|
|
508
|
+
*/
|
|
509
|
+
static void exec_verify_sub_cnt(sub_test_state_t *state, const test_op_t *op) {
|
|
510
|
+
int cidx = op->consumer_idx;
|
|
511
|
+
rd_kafka_topic_partition_list_t *sub;
|
|
512
|
+
|
|
513
|
+
TEST_SAY(" VERIFY_SUB_CNT: expecting %d, consumer=%d\n",
|
|
514
|
+
op->expected_sub_cnt, cidx);
|
|
515
|
+
|
|
516
|
+
sub = test_get_subscription(state->consumers[cidx]);
|
|
517
|
+
TEST_ASSERT(sub->cnt == op->expected_sub_cnt,
|
|
518
|
+
"Expected %d subscriptions, got %d", op->expected_sub_cnt,
|
|
519
|
+
sub->cnt);
|
|
520
|
+
rd_kafka_topic_partition_list_destroy(sub);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* @brief Execute TEST_OP_DELETE_TOPIC
|
|
525
|
+
*/
|
|
526
|
+
static void exec_delete_topic(sub_test_state_t *state, const test_op_t *op) {
|
|
527
|
+
int cidx = op->consumer_idx;
|
|
528
|
+
int idx = state->sub_start_idx[cidx] + op->topic_idx;
|
|
529
|
+
|
|
530
|
+
TEST_ASSERT(op->topic_idx < state->sub_count[cidx],
|
|
531
|
+
"Topic index %d out of range (sub_count=%d)", op->topic_idx,
|
|
532
|
+
state->sub_count[cidx]);
|
|
533
|
+
|
|
534
|
+
TEST_SAY(" DELETE_TOPIC: index %d (%s)\n", op->topic_idx,
|
|
535
|
+
state->all_topics[idx]);
|
|
536
|
+
|
|
537
|
+
/* Actually delete the topic */
|
|
538
|
+
test_DeleteTopics_simple(common_admin, NULL, &state->all_topics[idx], 1,
|
|
539
|
+
NULL);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* @brief Execute TEST_OP_WAIT
|
|
544
|
+
*/
|
|
545
|
+
static void exec_wait(sub_test_state_t *state, const test_op_t *op) {
|
|
546
|
+
TEST_SAY(" WAIT: %d ms\n", op->wait_ms);
|
|
547
|
+
rd_sleep(op->wait_ms / 1000);
|
|
548
|
+
if (op->wait_ms % 1000)
|
|
549
|
+
rd_usleep((op->wait_ms % 1000) * 1000, NULL);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* @brief Execute TEST_OP_CREATE_CONSUMER
|
|
554
|
+
*/
|
|
555
|
+
static void exec_create_consumer(sub_test_state_t *state, const test_op_t *op) {
|
|
556
|
+
int cidx = op->consumer_idx;
|
|
557
|
+
|
|
558
|
+
TEST_SAY(" CREATE_CONSUMER: index %d\n", cidx);
|
|
559
|
+
|
|
560
|
+
TEST_ASSERT(cidx < MAX_CONSUMERS, "Consumer index out of range");
|
|
561
|
+
TEST_ASSERT(state->consumers[cidx] == NULL,
|
|
562
|
+
"Consumer %d already exists", cidx);
|
|
563
|
+
|
|
564
|
+
state->consumers[cidx] =
|
|
565
|
+
test_create_share_consumer(state->group_name, NULL);
|
|
566
|
+
if (cidx >= state->consumer_cnt)
|
|
567
|
+
state->consumer_cnt = cidx + 1;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* @brief Execute TEST_OP_POLL_NO_SUB
|
|
572
|
+
*
|
|
573
|
+
* consume_batch must surface RD_KAFKA_RESP_ERR__STATE with a "not
|
|
574
|
+
* subscribed" message when no subscription is active.
|
|
575
|
+
*/
|
|
576
|
+
static void exec_poll_no_sub(sub_test_state_t *state, const test_op_t *op) {
|
|
577
|
+
rd_kafka_messages_t *batch = NULL;
|
|
578
|
+
rd_kafka_error_t *err;
|
|
579
|
+
size_t rcvd = 0;
|
|
580
|
+
int cidx = op->consumer_idx;
|
|
581
|
+
|
|
582
|
+
TEST_SAY(" POLL_NO_SUB: consumer=%d\n", cidx);
|
|
583
|
+
|
|
584
|
+
err = rd_kafka_share_poll(state->consumers[cidx], 2000, &batch);
|
|
585
|
+
rcvd = rd_kafka_messages_count(batch);
|
|
586
|
+
TEST_ASSERT(err != NULL,
|
|
587
|
+
"POLL_NO_SUB consumer=%d: expected error, got NULL", cidx);
|
|
588
|
+
TEST_ASSERT(rd_kafka_error_code(err) == RD_KAFKA_RESP_ERR__STATE,
|
|
589
|
+
"POLL_NO_SUB consumer=%d: expected __STATE, got: %s", cidx,
|
|
590
|
+
rd_kafka_err2name(rd_kafka_error_code(err)));
|
|
591
|
+
TEST_ASSERT(strstr(rd_kafka_error_string(err), "not subscribed"),
|
|
592
|
+
"POLL_NO_SUB consumer=%d: expected 'not subscribed' "
|
|
593
|
+
"substring, got: %s",
|
|
594
|
+
cidx, rd_kafka_error_string(err));
|
|
595
|
+
TEST_ASSERT(rcvd == 0,
|
|
596
|
+
"POLL_NO_SUB consumer=%d: expected 0 messages, got %zu",
|
|
597
|
+
cidx, rcvd);
|
|
598
|
+
rd_kafka_error_destroy(err);
|
|
599
|
+
rd_kafka_messages_destroy(batch);
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* @brief Initialize test state
|
|
604
|
+
*/
|
|
605
|
+
static void state_init(sub_test_state_t *state,
|
|
606
|
+
const test_scenario_t *scenario) {
|
|
607
|
+
int i;
|
|
608
|
+
|
|
609
|
+
memset(state, 0, sizeof(*state));
|
|
610
|
+
|
|
611
|
+
/* Append a per-invocation unique suffix so each run gets a
|
|
612
|
+
* fresh share-group on the broker (avoids collisions across
|
|
613
|
+
* re-runs or parallel runs against the same cluster). */
|
|
614
|
+
rd_snprintf(state->group_name, sizeof(state->group_name),
|
|
615
|
+
"share-%s-rnd%" PRIx64, scenario->name, test_id_generate());
|
|
616
|
+
TEST_SAY("Scenario '%s' using group '%s'\n", scenario->name,
|
|
617
|
+
state->group_name);
|
|
618
|
+
|
|
619
|
+
state->consumer_cnt =
|
|
620
|
+
scenario->consumer_cnt > 0 ? scenario->consumer_cnt : 1;
|
|
621
|
+
|
|
622
|
+
/* Create initial consumers */
|
|
623
|
+
for (i = 0; i < state->consumer_cnt; i++) {
|
|
624
|
+
state->consumers[i] =
|
|
625
|
+
test_create_share_consumer(state->group_name, NULL);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
/* Set group offset to earliest */
|
|
629
|
+
test_share_set_auto_offset_reset(state->group_name, "earliest");
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* @brief Cleanup test state
|
|
634
|
+
*/
|
|
635
|
+
static void state_cleanup(sub_test_state_t *state) {
|
|
636
|
+
int i;
|
|
637
|
+
|
|
638
|
+
for (i = 0; i < state->all_topic_cnt; i++) {
|
|
639
|
+
if (state->all_topics[i]) {
|
|
640
|
+
rd_free(state->all_topics[i]);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
/* Destroy all consumers */
|
|
645
|
+
for (i = 0; i < MAX_CONSUMERS; i++) {
|
|
646
|
+
if (state->consumers[i]) {
|
|
647
|
+
test_share_consumer_close(state->consumers[i]);
|
|
648
|
+
test_share_destroy(state->consumers[i]);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* @brief Run a test scenario
|
|
655
|
+
*/
|
|
656
|
+
static void do_test_scenario(const test_scenario_t *scenario) {
|
|
657
|
+
sub_test_state_t state;
|
|
658
|
+
int op_idx;
|
|
659
|
+
|
|
660
|
+
SUB_TEST();
|
|
661
|
+
|
|
662
|
+
state_init(&state, scenario);
|
|
663
|
+
|
|
664
|
+
/* Execute operations */
|
|
665
|
+
for (op_idx = 0; scenario->ops[op_idx].op != TEST_OP_END; op_idx++) {
|
|
666
|
+
const test_op_t *op = &scenario->ops[op_idx];
|
|
667
|
+
|
|
668
|
+
switch (op->op) {
|
|
669
|
+
case TEST_OP_SUBSCRIBE:
|
|
670
|
+
exec_subscribe(&state, op);
|
|
671
|
+
break;
|
|
672
|
+
case TEST_OP_SUBSCRIBE_ADD:
|
|
673
|
+
exec_subscribe_add(&state, op);
|
|
674
|
+
break;
|
|
675
|
+
case TEST_OP_RESUBSCRIBE:
|
|
676
|
+
exec_resubscribe(&state, op);
|
|
677
|
+
break;
|
|
678
|
+
case TEST_OP_UNSUBSCRIBE:
|
|
679
|
+
exec_unsubscribe(&state, op);
|
|
680
|
+
break;
|
|
681
|
+
case TEST_OP_PRODUCE:
|
|
682
|
+
exec_produce(&state, op);
|
|
683
|
+
break;
|
|
684
|
+
case TEST_OP_PRODUCE_TO_TOPIC:
|
|
685
|
+
exec_produce_to_topic(&state, op);
|
|
686
|
+
break;
|
|
687
|
+
case TEST_OP_CONSUME:
|
|
688
|
+
exec_consume(&state, op);
|
|
689
|
+
break;
|
|
690
|
+
case TEST_OP_VERIFY_SUB_CNT:
|
|
691
|
+
exec_verify_sub_cnt(&state, op);
|
|
692
|
+
break;
|
|
693
|
+
case TEST_OP_DELETE_TOPIC:
|
|
694
|
+
exec_delete_topic(&state, op);
|
|
695
|
+
break;
|
|
696
|
+
case TEST_OP_WAIT:
|
|
697
|
+
exec_wait(&state, op);
|
|
698
|
+
break;
|
|
699
|
+
case TEST_OP_CREATE_CONSUMER:
|
|
700
|
+
exec_create_consumer(&state, op);
|
|
701
|
+
break;
|
|
702
|
+
case TEST_OP_CREATE_TOPIC:
|
|
703
|
+
exec_create_topic(&state, op);
|
|
704
|
+
break;
|
|
705
|
+
case TEST_OP_SUBSCRIBE_EXISTING:
|
|
706
|
+
exec_subscribe_existing(&state, op);
|
|
707
|
+
break;
|
|
708
|
+
case TEST_OP_POLL_NO_SUB:
|
|
709
|
+
exec_poll_no_sub(&state, op);
|
|
710
|
+
break;
|
|
711
|
+
case TEST_OP_SUBSCRIBE_EMPTY:
|
|
712
|
+
exec_subscribe_empty(&state, op);
|
|
713
|
+
break;
|
|
714
|
+
default:
|
|
715
|
+
TEST_FAIL("Unknown operation: %d", op->op);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
state_cleanup(&state);
|
|
720
|
+
|
|
721
|
+
SUB_TEST_PASS();
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
|
|
725
|
+
/**
|
|
726
|
+
* Basic subscription tests
|
|
727
|
+
*/
|
|
728
|
+
static const test_scenario_t test_single_subscribe = {
|
|
729
|
+
.name = "single-subscribe",
|
|
730
|
+
.ops = {SUBSCRIBE(2), PRODUCE(5), VERIFY_SUB(2), CONSUME(10),
|
|
731
|
+
TEST_OPS_END()}};
|
|
732
|
+
|
|
733
|
+
static const test_scenario_t test_single_unsubscribe = {
|
|
734
|
+
.name = "single-unsubscribe",
|
|
735
|
+
.ops = {SUBSCRIBE(2), PRODUCE(5), CONSUME(10), UNSUBSCRIBE(), VERIFY_SUB(0),
|
|
736
|
+
TEST_OPS_END()}};
|
|
737
|
+
|
|
738
|
+
static const test_scenario_t test_repeated_subscribe = {
|
|
739
|
+
.name = "repeated-subscribe-no-duplicates",
|
|
740
|
+
.ops = {SUBSCRIBE_REPEAT(2, 3), /* Subscribe 3 times to same topics */
|
|
741
|
+
PRODUCE(5), VERIFY_SUB(2), /* Should still be 2, not 6 */
|
|
742
|
+
CONSUME(10), TEST_OPS_END()}};
|
|
743
|
+
|
|
744
|
+
static const test_scenario_t test_repeated_unsubscribe = {
|
|
745
|
+
.name = "repeated-unsubscribe-no-error",
|
|
746
|
+
.ops = {SUBSCRIBE(2), PRODUCE(5), CONSUME(10),
|
|
747
|
+
UNSUBSCRIBE_REPEAT(3), /* Unsubscribe 3 times */
|
|
748
|
+
VERIFY_SUB(0), TEST_OPS_END()}};
|
|
749
|
+
|
|
750
|
+
/**
|
|
751
|
+
* Subscription replacement tests
|
|
752
|
+
*/
|
|
753
|
+
/* TODO KIP-932: test_topic_switch might be incorrect. Verify and remove. */
|
|
754
|
+
// static const test_scenario_t test_topic_switch = {
|
|
755
|
+
// .name = "topic-switch",
|
|
756
|
+
// .ops = {SUBSCRIBE(2), PRODUCE(10), CONSUME_ANY(),
|
|
757
|
+
// RESUBSCRIBE(2), /* Switch to 2 new topics */
|
|
758
|
+
// PRODUCE(10), /* Produce to new topics */
|
|
759
|
+
// PRODUCE_TO_OLD(5), /* Produce to old topics */
|
|
760
|
+
// CONSUME_VERIFY_NO_OLD(20), /* Should only get new topic msgs */
|
|
761
|
+
// TEST_OPS_END()}};
|
|
762
|
+
|
|
763
|
+
static const test_scenario_t test_incremental_subscription = {
|
|
764
|
+
.name = "incremental-subscription",
|
|
765
|
+
.ops = {/* Start with 1 topic */
|
|
766
|
+
SUBSCRIBE(1), PRODUCE(10), VERIFY_SUB(1), CONSUME(10),
|
|
767
|
+
/* Add 1 more topic (now 2 total) */
|
|
768
|
+
SUBSCRIBE_ADD(1), PRODUCE(10), VERIFY_SUB(2),
|
|
769
|
+
CONSUME(20), /* 10 from each of 2 topics */
|
|
770
|
+
/* Add 1 more topic (now 3 total) */
|
|
771
|
+
SUBSCRIBE_ADD(1), PRODUCE(10), VERIFY_SUB(3),
|
|
772
|
+
CONSUME(30), /* 10 from each of 3 topics */
|
|
773
|
+
TEST_OPS_END()}};
|
|
774
|
+
|
|
775
|
+
/**
|
|
776
|
+
* Edge case tests
|
|
777
|
+
*/
|
|
778
|
+
static const test_scenario_t test_subscribe_before_topic_exists = {
|
|
779
|
+
.name = "subscribe-before-topic-exists",
|
|
780
|
+
.ops = {SUBSCRIBE_NO_CREATE(1), /* Subscribe without creating topic */
|
|
781
|
+
CREATE_TOPIC(0), /* Now create the subscribed topic */
|
|
782
|
+
PRODUCE(5), /* Produce to the topic */
|
|
783
|
+
CONSUME(5), /* Should receive all messages */
|
|
784
|
+
TEST_OPS_END()}};
|
|
785
|
+
|
|
786
|
+
static const test_scenario_t test_poll_empty_topic = {
|
|
787
|
+
.name = "poll-empty-topic",
|
|
788
|
+
.ops = {SUBSCRIBE(1),
|
|
789
|
+
/* Don't produce - topic is empty */
|
|
790
|
+
CONSUME(0), /* Should return 0, not error */
|
|
791
|
+
TEST_OPS_END()}};
|
|
792
|
+
|
|
793
|
+
static const test_scenario_t test_poll_no_subscription = {
|
|
794
|
+
.name = "poll-no-subscription",
|
|
795
|
+
.ops = {POLL_NO_SUB(), /* Poll without subscribing */
|
|
796
|
+
TEST_OPS_END()}};
|
|
797
|
+
|
|
798
|
+
static const test_scenario_t test_poll_after_unsubscribe = {
|
|
799
|
+
.name = "poll-after-unsubscribe",
|
|
800
|
+
.ops = {SUBSCRIBE(1), PRODUCE(5), CONSUME_ANY(), /* Consume some */
|
|
801
|
+
UNSUBSCRIBE(), POLL_NO_SUB(), /* Poll after unsubscribe */
|
|
802
|
+
TEST_OPS_END()}};
|
|
803
|
+
|
|
804
|
+
/**
|
|
805
|
+
* Topic deletion tests
|
|
806
|
+
*/
|
|
807
|
+
static const test_scenario_t test_topic_deletion = {
|
|
808
|
+
.name = "topic-deletion-while-subscribed",
|
|
809
|
+
.ops = {SUBSCRIBE(2), PRODUCE(10), CONSUME_ANY(),
|
|
810
|
+
DELETE_TOPIC(1), /* Delete second topic */
|
|
811
|
+
WAIT_MS(3000),
|
|
812
|
+
PRODUCE_TO_TOPIC(0, 5), /* Produce to remaining topic */
|
|
813
|
+
CONSUME_ANY(), /* Continue consuming from remaining */
|
|
814
|
+
TEST_OPS_END()}};
|
|
815
|
+
|
|
816
|
+
/**
|
|
817
|
+
* Stress tests
|
|
818
|
+
*/
|
|
819
|
+
static const test_scenario_t test_rapid_updates = {
|
|
820
|
+
.name = "rapid-subscription-updates",
|
|
821
|
+
.ops = {SUBSCRIBE(2), RESUBSCRIBE(1), RESUBSCRIBE(3), UNSUBSCRIBE(),
|
|
822
|
+
SUBSCRIBE(2), RESUBSCRIBE(2), UNSUBSCRIBE(), SUBSCRIBE(3),
|
|
823
|
+
VERIFY_SUB(3), TEST_OPS_END()}};
|
|
824
|
+
|
|
825
|
+
/**
|
|
826
|
+
* @brief Multi-consumer overlap test (standalone)
|
|
827
|
+
*
|
|
828
|
+
* Two consumers in same group with overlapping subscriptions:
|
|
829
|
+
* - Consumer 0: [shared, c0_only]
|
|
830
|
+
* - Consumer 1: [shared, c1_only]
|
|
831
|
+
*
|
|
832
|
+
* This test verifies share group consumers can have overlapping
|
|
833
|
+
* subscriptions and both receive messages from shared topics.
|
|
834
|
+
*/
|
|
835
|
+
static void do_test_multi_consumer_overlap(void) {
|
|
836
|
+
const char *group = test_mk_topic_name("share-overlap", 1);
|
|
837
|
+
char *shared = rd_strdup(test_mk_topic_name("0170-shared", 1));
|
|
838
|
+
char *c0_only = rd_strdup(test_mk_topic_name("0170-c0only", 1));
|
|
839
|
+
char *c1_only = rd_strdup(test_mk_topic_name("0170-c1only", 1));
|
|
840
|
+
const char *c0_topics[] = {shared, c0_only};
|
|
841
|
+
const char *c1_topics[] = {shared, c1_only};
|
|
842
|
+
rd_kafka_share_t *rkshare0, *rkshare1;
|
|
843
|
+
int c0_cnt = 0, c1_cnt = 0;
|
|
844
|
+
int attempts;
|
|
845
|
+
|
|
846
|
+
SUB_TEST("multi-consumer-overlapping-subscriptions");
|
|
847
|
+
|
|
848
|
+
/* Create topics */
|
|
849
|
+
test_create_topic_wait_exists(NULL, shared, 1, -1, 30000);
|
|
850
|
+
test_create_topic_wait_exists(NULL, c0_only, 1, -1, 30000);
|
|
851
|
+
test_create_topic_wait_exists(NULL, c1_only, 1, -1, 30000);
|
|
852
|
+
|
|
853
|
+
/* Produce messages */
|
|
854
|
+
test_produce_msgs_simple(common_producer, shared, 0, 20);
|
|
855
|
+
test_produce_msgs_simple(common_producer, c0_only, 0, 10);
|
|
856
|
+
test_produce_msgs_simple(common_producer, c1_only, 0, 10);
|
|
857
|
+
|
|
858
|
+
/* Create consumers */
|
|
859
|
+
rkshare0 = test_create_share_consumer(group, NULL);
|
|
860
|
+
rkshare1 = test_create_share_consumer(group, NULL);
|
|
861
|
+
|
|
862
|
+
/* Set group offset */
|
|
863
|
+
test_share_set_auto_offset_reset(group, "earliest");
|
|
864
|
+
|
|
865
|
+
/* Subscribe with overlapping topics */
|
|
866
|
+
test_share_consumer_subscribe_multi(rkshare0, 2, shared, c0_only);
|
|
867
|
+
test_share_consumer_subscribe_multi(rkshare1, 2, shared, c1_only);
|
|
868
|
+
|
|
869
|
+
/* Consume - alternate between consumers */
|
|
870
|
+
attempts = 20;
|
|
871
|
+
while ((c0_cnt + c1_cnt) < 10 && attempts-- > 0) {
|
|
872
|
+
int batch_cnt = 0;
|
|
873
|
+
int ret;
|
|
874
|
+
|
|
875
|
+
ret = test_share_poll(rkshare0, 2000, c0_topics, 2, &batch_cnt);
|
|
876
|
+
TEST_ASSERT(ret >= 0, "C0 wrong topic");
|
|
877
|
+
c0_cnt += batch_cnt;
|
|
878
|
+
|
|
879
|
+
batch_cnt = 0;
|
|
880
|
+
ret = test_share_poll(rkshare1, 2000, c1_topics, 2, &batch_cnt);
|
|
881
|
+
TEST_ASSERT(ret >= 0, "C1 wrong topic");
|
|
882
|
+
c1_cnt += batch_cnt;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
TEST_SAY("C0: %d, C1: %d (total: %d)\n", c0_cnt, c1_cnt,
|
|
886
|
+
c0_cnt + c1_cnt);
|
|
887
|
+
TEST_ASSERT(c0_cnt > 0 || c1_cnt > 0, "no messages received");
|
|
888
|
+
|
|
889
|
+
/* Cleanup */
|
|
890
|
+
test_share_consumer_close(rkshare0);
|
|
891
|
+
test_share_consumer_close(rkshare1);
|
|
892
|
+
test_share_destroy(rkshare0);
|
|
893
|
+
test_share_destroy(rkshare1);
|
|
894
|
+
|
|
895
|
+
rd_free(shared);
|
|
896
|
+
rd_free(c0_only);
|
|
897
|
+
rd_free(c1_only);
|
|
898
|
+
|
|
899
|
+
SUB_TEST_PASS();
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
|
|
903
|
+
/**
|
|
904
|
+
* @brief Topic-level metadata-error policy: when a subscribed topic is
|
|
905
|
+
* deleted mid-stream, the resulting metadata error
|
|
906
|
+
* (UNKNOWN_TOPIC_OR_PART / UNKNOWN_TOPIC) is treated as a
|
|
907
|
+
* transient code by the share consumer — logged internally and
|
|
908
|
+
* not surfaced to the application via consume_batch.
|
|
909
|
+
*
|
|
910
|
+
* Subscribe to a valid topic, prime the share assignment so the rktp
|
|
911
|
+
* is on rkt_desp, delete the topic, allow metadata propagation, and
|
|
912
|
+
* verify that no topic-level err shows up in subsequent consume_batch
|
|
913
|
+
* calls.
|
|
914
|
+
*/
|
|
915
|
+
static void test_topic_deletion_does_not_surface_error(void) {
|
|
916
|
+
const char *topic;
|
|
917
|
+
const char *group = "share-topic-delete-no-surface";
|
|
918
|
+
rd_kafka_share_t *consumer;
|
|
919
|
+
rd_kafka_conf_t *conf;
|
|
920
|
+
rd_kafka_topic_partition_list_t *subs;
|
|
921
|
+
rd_kafka_messages_t *batch = NULL;
|
|
922
|
+
rd_kafka_error_t *error;
|
|
923
|
+
char errstr[512];
|
|
924
|
+
char *topic_dup;
|
|
925
|
+
char *topics_for_delete[1];
|
|
926
|
+
rd_kafka_resp_err_t del_err;
|
|
927
|
+
size_t rcvd;
|
|
928
|
+
size_t j;
|
|
929
|
+
int consumed = 0;
|
|
930
|
+
int attempts = 0;
|
|
931
|
+
int post_attempts;
|
|
932
|
+
rd_bool_t surfaced_topic_err = rd_false;
|
|
933
|
+
|
|
934
|
+
SUB_TEST();
|
|
935
|
+
|
|
936
|
+
if (!strcmp(test_getenv("TEST_BROKER_OS", ""), "windows"))
|
|
937
|
+
SUB_TEST_SKIP(
|
|
938
|
+
"topic-deletion-does-not-surface-error "
|
|
939
|
+
"(broker on Windows)\n");
|
|
940
|
+
|
|
941
|
+
topic = test_mk_topic_name("0170-delete-no-surface", 1);
|
|
942
|
+
topic_dup = rd_strdup(topic);
|
|
943
|
+
test_create_topic_wait_exists(common_admin, topic, 1, -1, 60 * 1000);
|
|
944
|
+
test_share_set_auto_offset_reset(group, "earliest");
|
|
945
|
+
test_produce_msgs_simple(common_producer, topic, 0, 5);
|
|
946
|
+
|
|
947
|
+
/* Custom consumer that disables the set_notexists defer window
|
|
948
|
+
* (default 30s) so the log-and-drop path runs on the first
|
|
949
|
+
* post-delete metadata refresh instead of being deferred.
|
|
950
|
+
* Also reduce the metadata refresh interval so a refresh actually
|
|
951
|
+
* happens during the post-delete settle window — the default is
|
|
952
|
+
* 5 minutes, which would never fire during the test. */
|
|
953
|
+
test_conf_init(&conf, NULL, 60);
|
|
954
|
+
rd_kafka_conf_set(conf, "group.id", group, errstr, sizeof(errstr));
|
|
955
|
+
rd_kafka_conf_set(conf, "share.acknowledgement.mode", "explicit",
|
|
956
|
+
errstr, sizeof(errstr));
|
|
957
|
+
rd_kafka_conf_set(conf, "topic.metadata.propagation.max.ms", "0",
|
|
958
|
+
errstr, sizeof(errstr));
|
|
959
|
+
rd_kafka_conf_set(conf, "topic.metadata.refresh.interval.ms", "500",
|
|
960
|
+
errstr, sizeof(errstr));
|
|
961
|
+
consumer = rd_kafka_share_consumer_new(conf, errstr, sizeof(errstr));
|
|
962
|
+
TEST_ASSERT(consumer, "Failed to create share consumer: %s", errstr);
|
|
963
|
+
subs = rd_kafka_topic_partition_list_new(1);
|
|
964
|
+
rd_kafka_topic_partition_list_add(subs, topic, RD_KAFKA_PARTITION_UA);
|
|
965
|
+
rd_kafka_share_subscribe(consumer, subs);
|
|
966
|
+
rd_kafka_topic_partition_list_destroy(subs);
|
|
967
|
+
|
|
968
|
+
/* Prime the share assignment: consume + ACCEPT at least one
|
|
969
|
+
* record so the rktp materialises on rkt_desp (precondition
|
|
970
|
+
* for the metadata-error propagation path to fire). */
|
|
971
|
+
while (consumed < 1 && attempts++ < 30) {
|
|
972
|
+
rd_kafka_messages_destroy(batch);
|
|
973
|
+
batch = NULL;
|
|
974
|
+
error = rd_kafka_share_poll(consumer, 1000, &batch);
|
|
975
|
+
rcvd = rd_kafka_messages_count(batch);
|
|
976
|
+
if (error) {
|
|
977
|
+
rd_kafka_error_destroy(error);
|
|
978
|
+
continue;
|
|
979
|
+
}
|
|
980
|
+
for (j = 0; j < rcvd; j++) {
|
|
981
|
+
rd_kafka_message_t *msg =
|
|
982
|
+
rd_kafka_messages_get(batch, j);
|
|
983
|
+
if (!msg->err) {
|
|
984
|
+
rd_kafka_share_acknowledge(consumer, msg);
|
|
985
|
+
consumed++;
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
rd_kafka_messages_destroy(batch);
|
|
990
|
+
batch = NULL;
|
|
991
|
+
TEST_ASSERT(consumed >= 1,
|
|
992
|
+
"Pre-condition: expected to consume + ack at least one "
|
|
993
|
+
"record before deleting the topic");
|
|
994
|
+
|
|
995
|
+
TEST_SAY("Deleting topic %s\n", topic);
|
|
996
|
+
topics_for_delete[0] = topic_dup;
|
|
997
|
+
del_err = test_DeleteTopics_simple(common_admin, NULL,
|
|
998
|
+
topics_for_delete, 1, NULL);
|
|
999
|
+
TEST_ASSERT(!del_err, "DeleteTopics failed: %s",
|
|
1000
|
+
rd_kafka_err2str(del_err));
|
|
1001
|
+
|
|
1002
|
+
/* Wait for the topic delete to propagate in the cluster. */
|
|
1003
|
+
rd_sleep(3);
|
|
1004
|
+
|
|
1005
|
+
/* Drain consume_batch and verify no topic-level error reaches
|
|
1006
|
+
* the application. _STATE / generic timeouts are expected
|
|
1007
|
+
* (no more records); only the metadata-derived topic codes
|
|
1008
|
+
* must not surface. */
|
|
1009
|
+
for (post_attempts = 0; post_attempts < 30; post_attempts++) {
|
|
1010
|
+
rd_kafka_messages_destroy(batch);
|
|
1011
|
+
batch = NULL;
|
|
1012
|
+
error = rd_kafka_share_poll(consumer, 500, &batch);
|
|
1013
|
+
rcvd = rd_kafka_messages_count(batch);
|
|
1014
|
+
if (error) {
|
|
1015
|
+
rd_kafka_resp_err_t code = rd_kafka_error_code(error);
|
|
1016
|
+
TEST_SAY("Post-delete consume_batch: %s\n",
|
|
1017
|
+
rd_kafka_err2name(code));
|
|
1018
|
+
if (code == RD_KAFKA_RESP_ERR_TOPIC_EXCEPTION ||
|
|
1019
|
+
code == RD_KAFKA_RESP_ERR_UNKNOWN_TOPIC_OR_PART ||
|
|
1020
|
+
code == RD_KAFKA_RESP_ERR__UNKNOWN_TOPIC ||
|
|
1021
|
+
code == RD_KAFKA_RESP_ERR__UNKNOWN_PARTITION ||
|
|
1022
|
+
code == RD_KAFKA_RESP_ERR_UNKNOWN_TOPIC_ID ||
|
|
1023
|
+
code ==
|
|
1024
|
+
RD_KAFKA_RESP_ERR_TOPIC_AUTHORIZATION_FAILED)
|
|
1025
|
+
surfaced_topic_err = rd_true;
|
|
1026
|
+
rd_kafka_error_destroy(error);
|
|
1027
|
+
continue;
|
|
1028
|
+
}
|
|
1029
|
+
for (j = 0; j < rcvd; j++) {
|
|
1030
|
+
rd_kafka_message_t *msg =
|
|
1031
|
+
rd_kafka_messages_get(batch, j);
|
|
1032
|
+
if (!msg->err)
|
|
1033
|
+
rd_kafka_share_acknowledge(consumer, msg);
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
rd_kafka_messages_destroy(batch);
|
|
1037
|
+
batch = NULL;
|
|
1038
|
+
|
|
1039
|
+
TEST_ASSERT(!surfaced_topic_err,
|
|
1040
|
+
"Topic-level metadata errors must not surface to "
|
|
1041
|
+
"the application after the subscribed topic is "
|
|
1042
|
+
"deleted (transient codes are log-only)");
|
|
1043
|
+
|
|
1044
|
+
rd_free(topic_dup);
|
|
1045
|
+
test_share_consumer_close(consumer);
|
|
1046
|
+
test_share_destroy(consumer);
|
|
1047
|
+
|
|
1048
|
+
SUB_TEST_PASS();
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
|
|
1052
|
+
/**
|
|
1053
|
+
* @brief Test subscribing to 15 topics - triggers multiple fetch responses
|
|
1054
|
+
*
|
|
1055
|
+
* Creates 15 topics, subscribes to all of them, produces messages to each,
|
|
1056
|
+
* and verifies all messages are consumed. This tests the scenario where
|
|
1057
|
+
* topics are spread across multiple brokers and require multiple
|
|
1058
|
+
* ShareFetch responses.
|
|
1059
|
+
*/
|
|
1060
|
+
static void do_test_subscribe_15_topics(void) {
|
|
1061
|
+
const char *group = test_mk_topic_name("share-15topics", 1);
|
|
1062
|
+
const int topic_cnt = 15;
|
|
1063
|
+
const int msgs_per_topic = 100;
|
|
1064
|
+
const int total_expected = topic_cnt * msgs_per_topic;
|
|
1065
|
+
char *topics[15];
|
|
1066
|
+
rd_kafka_share_t *rkshare;
|
|
1067
|
+
rd_kafka_topic_partition_list_t *subs;
|
|
1068
|
+
rd_kafka_messages_t *batch = NULL;
|
|
1069
|
+
int consumed = 0;
|
|
1070
|
+
int attempts;
|
|
1071
|
+
int t;
|
|
1072
|
+
|
|
1073
|
+
SUB_TEST("subscribe-15-topics");
|
|
1074
|
+
|
|
1075
|
+
/* Create 15 topics */
|
|
1076
|
+
for (t = 0; t < topic_cnt; t++) {
|
|
1077
|
+
topics[t] = rd_strdup(test_mk_topic_name("0170-15topics", 1));
|
|
1078
|
+
test_create_topic_wait_exists(NULL, topics[t], 1, -1, 30000);
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
/* Produce messages to each topic */
|
|
1082
|
+
for (t = 0; t < topic_cnt; t++) {
|
|
1083
|
+
test_produce_msgs_simple(common_producer, topics[t], 0,
|
|
1084
|
+
msgs_per_topic);
|
|
1085
|
+
}
|
|
1086
|
+
TEST_SAY("Produced %d messages to %d topics\n", total_expected,
|
|
1087
|
+
topic_cnt);
|
|
1088
|
+
|
|
1089
|
+
/* Create consumer */
|
|
1090
|
+
rkshare = test_create_share_consumer(group, NULL);
|
|
1091
|
+
|
|
1092
|
+
/* Set group offset */
|
|
1093
|
+
test_share_set_auto_offset_reset(group, "earliest");
|
|
1094
|
+
|
|
1095
|
+
/* Subscribe to all topics */
|
|
1096
|
+
subs = rd_kafka_topic_partition_list_new(topic_cnt);
|
|
1097
|
+
for (t = 0; t < topic_cnt; t++) {
|
|
1098
|
+
rd_kafka_topic_partition_list_add(subs, topics[t],
|
|
1099
|
+
RD_KAFKA_PARTITION_UA);
|
|
1100
|
+
}
|
|
1101
|
+
rd_kafka_share_subscribe(rkshare, subs);
|
|
1102
|
+
rd_kafka_topic_partition_list_destroy(subs);
|
|
1103
|
+
|
|
1104
|
+
TEST_SAY("Subscribed to %d topics\n", topic_cnt);
|
|
1105
|
+
|
|
1106
|
+
/* Consume all messages */
|
|
1107
|
+
attempts = 100;
|
|
1108
|
+
while (consumed < total_expected && attempts-- > 0) {
|
|
1109
|
+
size_t rcvd = 0;
|
|
1110
|
+
size_t m;
|
|
1111
|
+
rd_kafka_error_t *err;
|
|
1112
|
+
|
|
1113
|
+
rd_kafka_messages_destroy(batch);
|
|
1114
|
+
batch = NULL;
|
|
1115
|
+
err = rd_kafka_share_poll(rkshare, 2000, &batch);
|
|
1116
|
+
rcvd = rd_kafka_messages_count(batch);
|
|
1117
|
+
if (err) {
|
|
1118
|
+
rd_kafka_error_destroy(err);
|
|
1119
|
+
continue;
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
for (m = 0; m < rcvd; m++) {
|
|
1123
|
+
rd_kafka_message_t *msg =
|
|
1124
|
+
rd_kafka_messages_get(batch, m);
|
|
1125
|
+
if (!msg->err)
|
|
1126
|
+
consumed++;
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
if (rcvd > 0)
|
|
1130
|
+
TEST_SAY("Progress: %d/%d\n", consumed, total_expected);
|
|
1131
|
+
}
|
|
1132
|
+
rd_kafka_messages_destroy(batch);
|
|
1133
|
+
batch = NULL;
|
|
1134
|
+
|
|
1135
|
+
TEST_ASSERT(consumed == total_expected,
|
|
1136
|
+
"Expected %d messages, consumed %d", total_expected,
|
|
1137
|
+
consumed);
|
|
1138
|
+
|
|
1139
|
+
/* Cleanup */
|
|
1140
|
+
test_share_consumer_close(rkshare);
|
|
1141
|
+
test_share_destroy(rkshare);
|
|
1142
|
+
|
|
1143
|
+
for (t = 0; t < topic_cnt; t++) {
|
|
1144
|
+
rd_free(topics[t]);
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
SUB_TEST_PASS();
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
|
|
1151
|
+
/**
|
|
1152
|
+
* @brief Test share.auto.offset.reset = earliest
|
|
1153
|
+
*
|
|
1154
|
+
* Verifies that with share.auto.offset.reset = earliest, consumer receives
|
|
1155
|
+
* all messages including those produced before subscription.
|
|
1156
|
+
*/
|
|
1157
|
+
static void do_test_auto_offset_reset_earliest(void) {
|
|
1158
|
+
rd_kafka_share_t *consumer;
|
|
1159
|
+
rd_kafka_messages_t *batch = NULL;
|
|
1160
|
+
const char *topic;
|
|
1161
|
+
const char *group = "share-offset-earliest-test";
|
|
1162
|
+
rd_kafka_topic_partition_list_t *subs;
|
|
1163
|
+
int consumed = 0;
|
|
1164
|
+
int attempts;
|
|
1165
|
+
const int msg_cnt = 100;
|
|
1166
|
+
|
|
1167
|
+
SUB_TEST("share.auto.offset.reset=earliest");
|
|
1168
|
+
|
|
1169
|
+
/* Create topic */
|
|
1170
|
+
topic = test_mk_topic_name("0170-offset-earliest", 1);
|
|
1171
|
+
test_create_topic_wait_exists(NULL, topic, 1, -1, 60 * 1000);
|
|
1172
|
+
|
|
1173
|
+
/* Produce messages BEFORE consumer subscribes */
|
|
1174
|
+
TEST_SAY("Producing %d messages BEFORE subscription...\n", msg_cnt);
|
|
1175
|
+
test_produce_msgs_easy(topic, 0, 0, msg_cnt);
|
|
1176
|
+
|
|
1177
|
+
/* Create consumer */
|
|
1178
|
+
consumer = test_create_share_consumer(group, NULL);
|
|
1179
|
+
|
|
1180
|
+
/* Configure group with earliest offset */
|
|
1181
|
+
test_share_set_auto_offset_reset(group, "earliest");
|
|
1182
|
+
|
|
1183
|
+
/* Subscribe */
|
|
1184
|
+
subs = rd_kafka_topic_partition_list_new(1);
|
|
1185
|
+
rd_kafka_topic_partition_list_add(subs, topic, RD_KAFKA_PARTITION_UA);
|
|
1186
|
+
rd_kafka_share_subscribe(consumer, subs);
|
|
1187
|
+
rd_kafka_topic_partition_list_destroy(subs);
|
|
1188
|
+
|
|
1189
|
+
/* Consume - should get all 100 messages */
|
|
1190
|
+
TEST_SAY("Consuming with earliest offset...\n");
|
|
1191
|
+
attempts = 50;
|
|
1192
|
+
while (consumed < msg_cnt && attempts-- > 0) {
|
|
1193
|
+
size_t rcvd = 0;
|
|
1194
|
+
size_t m;
|
|
1195
|
+
rd_kafka_error_t *err;
|
|
1196
|
+
|
|
1197
|
+
rd_kafka_messages_destroy(batch);
|
|
1198
|
+
batch = NULL;
|
|
1199
|
+
err = rd_kafka_share_poll(consumer, 2000, &batch);
|
|
1200
|
+
rcvd = rd_kafka_messages_count(batch);
|
|
1201
|
+
if (err) {
|
|
1202
|
+
rd_kafka_error_destroy(err);
|
|
1203
|
+
continue;
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
for (m = 0; m < rcvd; m++) {
|
|
1207
|
+
rd_kafka_message_t *msg =
|
|
1208
|
+
rd_kafka_messages_get(batch, m);
|
|
1209
|
+
if (!msg->err)
|
|
1210
|
+
consumed++;
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
if (rcvd > 0)
|
|
1214
|
+
TEST_SAY("Progress: %d/%d\n", consumed, msg_cnt);
|
|
1215
|
+
}
|
|
1216
|
+
rd_kafka_messages_destroy(batch);
|
|
1217
|
+
batch = NULL;
|
|
1218
|
+
|
|
1219
|
+
TEST_ASSERT(consumed == msg_cnt, "Expected %d messages, got %d",
|
|
1220
|
+
msg_cnt, consumed);
|
|
1221
|
+
|
|
1222
|
+
TEST_SAY("SUCCESS: earliest offset - consumed all %d messages\n",
|
|
1223
|
+
consumed);
|
|
1224
|
+
|
|
1225
|
+
/* Cleanup */
|
|
1226
|
+
test_share_consumer_close(consumer);
|
|
1227
|
+
test_share_destroy(consumer);
|
|
1228
|
+
|
|
1229
|
+
SUB_TEST_PASS();
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
/**
|
|
1233
|
+
* @brief Test share.auto.offset.reset default value (latest)
|
|
1234
|
+
*
|
|
1235
|
+
* Verifies that with default (latest) offset, consumer only receives
|
|
1236
|
+
* messages produced after subscription, not pre-existing messages.
|
|
1237
|
+
*/
|
|
1238
|
+
static void do_test_auto_offset_reset_default_latest(void) {
|
|
1239
|
+
rd_kafka_share_t *consumer;
|
|
1240
|
+
rd_kafka_messages_t *batch = NULL;
|
|
1241
|
+
const char *topic;
|
|
1242
|
+
const char *group = "share-offset-default-test";
|
|
1243
|
+
rd_kafka_topic_partition_list_t *subs;
|
|
1244
|
+
int consumed_before = 0, consumed_after = 0;
|
|
1245
|
+
int attempts;
|
|
1246
|
+
const int initial_msgs = 100;
|
|
1247
|
+
const int later_msgs = 50;
|
|
1248
|
+
|
|
1249
|
+
SUB_TEST("share.auto.offset.reset=latest (default)");
|
|
1250
|
+
|
|
1251
|
+
/* Create topic */
|
|
1252
|
+
topic = test_mk_topic_name("0170-offset-default", 1);
|
|
1253
|
+
test_create_topic_wait_exists(NULL, topic, 1, -1, 60 * 1000);
|
|
1254
|
+
|
|
1255
|
+
/* Produce messages BEFORE consumer subscribes */
|
|
1256
|
+
TEST_SAY("Producing %d messages BEFORE subscription...\n",
|
|
1257
|
+
initial_msgs);
|
|
1258
|
+
test_produce_msgs_easy(topic, 0, 0, initial_msgs);
|
|
1259
|
+
|
|
1260
|
+
/* Create consumer - NO offset reset config (uses default "latest") */
|
|
1261
|
+
consumer = test_create_share_consumer(group, NULL);
|
|
1262
|
+
|
|
1263
|
+
/* Subscribe */
|
|
1264
|
+
subs = rd_kafka_topic_partition_list_new(1);
|
|
1265
|
+
rd_kafka_topic_partition_list_add(subs, topic, RD_KAFKA_PARTITION_UA);
|
|
1266
|
+
rd_kafka_share_subscribe(consumer, subs);
|
|
1267
|
+
rd_kafka_topic_partition_list_destroy(subs);
|
|
1268
|
+
|
|
1269
|
+
/* Allow consumer to establish position */
|
|
1270
|
+
rd_sleep(3);
|
|
1271
|
+
|
|
1272
|
+
/* Try to consume - should get 0 messages (default is latest) */
|
|
1273
|
+
TEST_SAY("Trying to consume with default (latest) offset...\n");
|
|
1274
|
+
attempts = 10;
|
|
1275
|
+
while (attempts-- > 0) {
|
|
1276
|
+
size_t rcvd = 0;
|
|
1277
|
+
size_t m;
|
|
1278
|
+
rd_kafka_error_t *err;
|
|
1279
|
+
|
|
1280
|
+
rd_kafka_messages_destroy(batch);
|
|
1281
|
+
batch = NULL;
|
|
1282
|
+
err = rd_kafka_share_poll(consumer, 1000, &batch);
|
|
1283
|
+
rcvd = rd_kafka_messages_count(batch);
|
|
1284
|
+
if (err) {
|
|
1285
|
+
rd_kafka_error_destroy(err);
|
|
1286
|
+
continue;
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
for (m = 0; m < rcvd; m++) {
|
|
1290
|
+
rd_kafka_message_t *msg =
|
|
1291
|
+
rd_kafka_messages_get(batch, m);
|
|
1292
|
+
if (!msg->err)
|
|
1293
|
+
consumed_before++;
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
rd_kafka_messages_destroy(batch);
|
|
1297
|
+
batch = NULL;
|
|
1298
|
+
|
|
1299
|
+
TEST_SAY(
|
|
1300
|
+
"Consumed %d messages from before subscription (expected: 0)\n",
|
|
1301
|
+
consumed_before);
|
|
1302
|
+
TEST_ASSERT(consumed_before == 0,
|
|
1303
|
+
"With default (latest) offset, should not receive "
|
|
1304
|
+
"pre-existing messages, got %d",
|
|
1305
|
+
consumed_before);
|
|
1306
|
+
|
|
1307
|
+
/* Now produce more messages */
|
|
1308
|
+
TEST_SAY("Producing %d more messages AFTER subscription...\n",
|
|
1309
|
+
later_msgs);
|
|
1310
|
+
test_produce_msgs_easy(topic, 0, 0, later_msgs);
|
|
1311
|
+
|
|
1312
|
+
/* Consume - should get the new 50 messages */
|
|
1313
|
+
TEST_SAY("Consuming messages produced after subscription...\n");
|
|
1314
|
+
attempts = 30;
|
|
1315
|
+
while (consumed_after < later_msgs && attempts-- > 0) {
|
|
1316
|
+
size_t rcvd = 0;
|
|
1317
|
+
size_t m;
|
|
1318
|
+
rd_kafka_error_t *err;
|
|
1319
|
+
|
|
1320
|
+
rd_kafka_messages_destroy(batch);
|
|
1321
|
+
batch = NULL;
|
|
1322
|
+
err = rd_kafka_share_poll(consumer, 1000, &batch);
|
|
1323
|
+
rcvd = rd_kafka_messages_count(batch);
|
|
1324
|
+
if (err) {
|
|
1325
|
+
rd_kafka_error_destroy(err);
|
|
1326
|
+
continue;
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
for (m = 0; m < rcvd; m++) {
|
|
1330
|
+
rd_kafka_message_t *msg =
|
|
1331
|
+
rd_kafka_messages_get(batch, m);
|
|
1332
|
+
if (!msg->err)
|
|
1333
|
+
consumed_after++;
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
rd_kafka_messages_destroy(batch);
|
|
1337
|
+
batch = NULL;
|
|
1338
|
+
|
|
1339
|
+
TEST_SAY("Consumed %d messages after subscription (expected: %d)\n",
|
|
1340
|
+
consumed_after, later_msgs);
|
|
1341
|
+
TEST_ASSERT(consumed_after == later_msgs,
|
|
1342
|
+
"Expected %d messages after subscription, got %d",
|
|
1343
|
+
later_msgs, consumed_after);
|
|
1344
|
+
|
|
1345
|
+
TEST_SAY(
|
|
1346
|
+
"SUCCESS: default (latest) offset verified - before=%d, "
|
|
1347
|
+
"after=%d\n",
|
|
1348
|
+
consumed_before, consumed_after);
|
|
1349
|
+
|
|
1350
|
+
/* Cleanup */
|
|
1351
|
+
test_share_consumer_close(consumer);
|
|
1352
|
+
test_share_destroy(consumer);
|
|
1353
|
+
|
|
1354
|
+
SUB_TEST_PASS();
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
/**
|
|
1358
|
+
* @brief rd_kafka_share_subscribe input validation against a real
|
|
1359
|
+
* broker.
|
|
1360
|
+
*
|
|
1361
|
+
* Covers the share-subscribe API surface contract:
|
|
1362
|
+
* 1. A topic name starting with '^' is treated as a literal — no
|
|
1363
|
+
* regex compilation, no rejection. The name is forwarded
|
|
1364
|
+
* verbatim to the broker via
|
|
1365
|
+
* ShareGroupHeartbeat.SubscribedTopicNames; the broker silently
|
|
1366
|
+
* drops names it cannot resolve.
|
|
1367
|
+
* 2. Subscription list containing an empty topic name returns
|
|
1368
|
+
* INVALID_ARG.
|
|
1369
|
+
* 3. Duplicate topic names return INVALID_ARG.
|
|
1370
|
+
*/
|
|
1371
|
+
static void do_test_subscribe_input_validation(void) {
|
|
1372
|
+
rd_kafka_share_t *consumer;
|
|
1373
|
+
rd_kafka_topic_partition_list_t *subs;
|
|
1374
|
+
rd_kafka_resp_err_t err;
|
|
1375
|
+
const char *group = "share-subscribe-validation";
|
|
1376
|
+
|
|
1377
|
+
SUB_TEST();
|
|
1378
|
+
|
|
1379
|
+
/* Case 1: '^foo.*' is forwarded as a literal, not interpreted
|
|
1380
|
+
* as a regex. */
|
|
1381
|
+
consumer = test_create_share_consumer(group, NULL);
|
|
1382
|
+
|
|
1383
|
+
subs = rd_kafka_topic_partition_list_new(1);
|
|
1384
|
+
rd_kafka_topic_partition_list_add(subs, "^foo.*",
|
|
1385
|
+
RD_KAFKA_PARTITION_UA);
|
|
1386
|
+
err = rd_kafka_share_subscribe(consumer, subs);
|
|
1387
|
+
TEST_ASSERT(err == RD_KAFKA_RESP_ERR_NO_ERROR,
|
|
1388
|
+
"expected NO_ERROR for '^foo.*' literal topic, got: %s",
|
|
1389
|
+
rd_kafka_err2str(err));
|
|
1390
|
+
rd_kafka_topic_partition_list_destroy(subs);
|
|
1391
|
+
|
|
1392
|
+
test_share_consumer_close(consumer);
|
|
1393
|
+
test_share_destroy(consumer);
|
|
1394
|
+
|
|
1395
|
+
/* Case 2: entry with empty topic name. */
|
|
1396
|
+
consumer = test_create_share_consumer(group, NULL);
|
|
1397
|
+
|
|
1398
|
+
subs = rd_kafka_topic_partition_list_new(2);
|
|
1399
|
+
rd_kafka_topic_partition_list_add(subs, "valid-topic",
|
|
1400
|
+
RD_KAFKA_PARTITION_UA);
|
|
1401
|
+
rd_kafka_topic_partition_list_add(subs, "", RD_KAFKA_PARTITION_UA);
|
|
1402
|
+
err = rd_kafka_share_subscribe(consumer, subs);
|
|
1403
|
+
TEST_ASSERT(err == RD_KAFKA_RESP_ERR__INVALID_ARG,
|
|
1404
|
+
"expected INVALID_ARG for empty topic name, got: %s",
|
|
1405
|
+
rd_kafka_err2str(err));
|
|
1406
|
+
rd_kafka_topic_partition_list_destroy(subs);
|
|
1407
|
+
|
|
1408
|
+
test_share_consumer_close(consumer);
|
|
1409
|
+
test_share_destroy(consumer);
|
|
1410
|
+
|
|
1411
|
+
/* Case 3: duplicate topic names. */
|
|
1412
|
+
consumer = test_create_share_consumer(group, NULL);
|
|
1413
|
+
|
|
1414
|
+
subs = rd_kafka_topic_partition_list_new(2);
|
|
1415
|
+
rd_kafka_topic_partition_list_add(subs, "duplicate-topic",
|
|
1416
|
+
RD_KAFKA_PARTITION_UA);
|
|
1417
|
+
rd_kafka_topic_partition_list_add(subs, "duplicate-topic",
|
|
1418
|
+
RD_KAFKA_PARTITION_UA);
|
|
1419
|
+
err = rd_kafka_share_subscribe(consumer, subs);
|
|
1420
|
+
TEST_ASSERT(err == RD_KAFKA_RESP_ERR__INVALID_ARG,
|
|
1421
|
+
"expected INVALID_ARG for duplicate topic, got: %s",
|
|
1422
|
+
rd_kafka_err2str(err));
|
|
1423
|
+
rd_kafka_topic_partition_list_destroy(subs);
|
|
1424
|
+
|
|
1425
|
+
test_share_consumer_close(consumer);
|
|
1426
|
+
test_share_destroy(consumer);
|
|
1427
|
+
|
|
1428
|
+
SUB_TEST_PASS();
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
/**
|
|
1432
|
+
* @brief End-to-end verification that '^'-prefixed entries are
|
|
1433
|
+
* forwarded as literals (no regex resolution).
|
|
1434
|
+
*
|
|
1435
|
+
* Creates a real topic, produces records to it, then:
|
|
1436
|
+
* Phase 1. Subscribes with a regex-shaped string '^<topic>$' that
|
|
1437
|
+
* would match the topic if treated as a regex. The broker
|
|
1438
|
+
* cannot resolve the literal name and silently drops it
|
|
1439
|
+
* from the assignment. The share consumer must receive
|
|
1440
|
+
* zero records.
|
|
1441
|
+
* Phase 2. Unsubscribes, resubscribes with the plain topic name.
|
|
1442
|
+
* The consumer must now receive all produced records.
|
|
1443
|
+
*/
|
|
1444
|
+
static void do_test_subscribe_caret_treated_as_literal_e2e(void) {
|
|
1445
|
+
rd_kafka_share_t *consumer;
|
|
1446
|
+
rd_kafka_topic_partition_list_t *subs;
|
|
1447
|
+
rd_kafka_messages_t *batch = NULL;
|
|
1448
|
+
const char *topic;
|
|
1449
|
+
const char *group = "share-caret-literal-e2e";
|
|
1450
|
+
const int msg_cnt = 50;
|
|
1451
|
+
char caret_pattern[512];
|
|
1452
|
+
size_t records_phase1 = 0;
|
|
1453
|
+
size_t records_phase2 = 0;
|
|
1454
|
+
int attempts;
|
|
1455
|
+
|
|
1456
|
+
SUB_TEST();
|
|
1457
|
+
|
|
1458
|
+
topic = test_mk_topic_name("0170-caret-literal", 1);
|
|
1459
|
+
test_create_topic_wait_exists(NULL, topic, 1, -1, 60 * 1000);
|
|
1460
|
+
test_share_set_auto_offset_reset(group, "earliest");
|
|
1461
|
+
test_produce_msgs_easy(topic, 0, 0, msg_cnt);
|
|
1462
|
+
|
|
1463
|
+
consumer = test_create_share_consumer(group, NULL);
|
|
1464
|
+
|
|
1465
|
+
/* Phase 1: '^<topic>$' as a literal — broker drops, no records. */
|
|
1466
|
+
rd_snprintf(caret_pattern, sizeof(caret_pattern), "^%s$", topic);
|
|
1467
|
+
subs = rd_kafka_topic_partition_list_new(1);
|
|
1468
|
+
rd_kafka_topic_partition_list_add(subs, caret_pattern,
|
|
1469
|
+
RD_KAFKA_PARTITION_UA);
|
|
1470
|
+
TEST_CALL_ERR__(rd_kafka_share_subscribe(consumer, subs));
|
|
1471
|
+
rd_kafka_topic_partition_list_destroy(subs);
|
|
1472
|
+
|
|
1473
|
+
/* rd_kafka_share_subscription must return the subscribed names
|
|
1474
|
+
* verbatim (the '^' prefix is not stripped). */
|
|
1475
|
+
subs = NULL;
|
|
1476
|
+
TEST_CALL_ERR__(rd_kafka_share_subscription(consumer, &subs));
|
|
1477
|
+
TEST_ASSERT(subs && subs->cnt == 1,
|
|
1478
|
+
"Phase 1: expected subscription cnt 1, got %d",
|
|
1479
|
+
subs ? subs->cnt : -1);
|
|
1480
|
+
TEST_ASSERT(!strcmp(subs->elems[0].topic, caret_pattern),
|
|
1481
|
+
"Phase 1: expected subscription[0]='%s', got '%s'",
|
|
1482
|
+
caret_pattern, subs->elems[0].topic);
|
|
1483
|
+
rd_kafka_topic_partition_list_destroy(subs);
|
|
1484
|
+
|
|
1485
|
+
TEST_SAY("Phase 1: subscribed with '%s' (literal); polling...\n",
|
|
1486
|
+
caret_pattern);
|
|
1487
|
+
attempts = 10;
|
|
1488
|
+
while (attempts-- > 0) {
|
|
1489
|
+
size_t rcvd = 0;
|
|
1490
|
+
size_t m;
|
|
1491
|
+
rd_kafka_error_t *err;
|
|
1492
|
+
|
|
1493
|
+
rd_kafka_messages_destroy(batch);
|
|
1494
|
+
batch = NULL;
|
|
1495
|
+
err = rd_kafka_share_poll(consumer, 2000, &batch);
|
|
1496
|
+
rcvd = rd_kafka_messages_count(batch);
|
|
1497
|
+
if (err)
|
|
1498
|
+
rd_kafka_error_destroy(err);
|
|
1499
|
+
|
|
1500
|
+
for (m = 0; m < rcvd; m++) {
|
|
1501
|
+
rd_kafka_message_t *msg =
|
|
1502
|
+
rd_kafka_messages_get(batch, m);
|
|
1503
|
+
if (!msg->err)
|
|
1504
|
+
records_phase1++;
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
rd_kafka_messages_destroy(batch);
|
|
1508
|
+
batch = NULL;
|
|
1509
|
+
TEST_ASSERT(records_phase1 == 0,
|
|
1510
|
+
"Phase 1: expected 0 records for '%s' literal "
|
|
1511
|
+
"subscription, got %zu",
|
|
1512
|
+
caret_pattern, records_phase1);
|
|
1513
|
+
TEST_SAY("Phase 1: received 0 records as expected\n");
|
|
1514
|
+
|
|
1515
|
+
/* Phase 2: resubscribe with the plain topic name — broker
|
|
1516
|
+
* resolves it, consumer receives records. */
|
|
1517
|
+
TEST_CALL_ERR__(rd_kafka_share_unsubscribe(consumer));
|
|
1518
|
+
|
|
1519
|
+
subs = rd_kafka_topic_partition_list_new(1);
|
|
1520
|
+
rd_kafka_topic_partition_list_add(subs, topic, RD_KAFKA_PARTITION_UA);
|
|
1521
|
+
TEST_CALL_ERR__(rd_kafka_share_subscribe(consumer, subs));
|
|
1522
|
+
rd_kafka_topic_partition_list_destroy(subs);
|
|
1523
|
+
|
|
1524
|
+
/* rd_kafka_share_subscription must now return the plain name. */
|
|
1525
|
+
subs = NULL;
|
|
1526
|
+
TEST_CALL_ERR__(rd_kafka_share_subscription(consumer, &subs));
|
|
1527
|
+
TEST_ASSERT(subs && subs->cnt == 1,
|
|
1528
|
+
"Phase 2: expected subscription cnt 1, got %d",
|
|
1529
|
+
subs ? subs->cnt : -1);
|
|
1530
|
+
TEST_ASSERT(!strcmp(subs->elems[0].topic, topic),
|
|
1531
|
+
"Phase 2: expected subscription[0]='%s', got '%s'", topic,
|
|
1532
|
+
subs->elems[0].topic);
|
|
1533
|
+
rd_kafka_topic_partition_list_destroy(subs);
|
|
1534
|
+
|
|
1535
|
+
TEST_SAY("Phase 2: resubscribed with plain name '%s'; consuming...\n",
|
|
1536
|
+
topic);
|
|
1537
|
+
attempts = 10;
|
|
1538
|
+
while (records_phase2 < (size_t)msg_cnt && attempts-- > 0) {
|
|
1539
|
+
size_t rcvd = 0;
|
|
1540
|
+
size_t m;
|
|
1541
|
+
rd_kafka_error_t *err;
|
|
1542
|
+
|
|
1543
|
+
rd_kafka_messages_destroy(batch);
|
|
1544
|
+
batch = NULL;
|
|
1545
|
+
err = rd_kafka_share_poll(consumer, 2000, &batch);
|
|
1546
|
+
rcvd = rd_kafka_messages_count(batch);
|
|
1547
|
+
if (err)
|
|
1548
|
+
rd_kafka_error_destroy(err);
|
|
1549
|
+
|
|
1550
|
+
for (m = 0; m < rcvd; m++) {
|
|
1551
|
+
rd_kafka_message_t *msg =
|
|
1552
|
+
rd_kafka_messages_get(batch, m);
|
|
1553
|
+
if (!msg->err)
|
|
1554
|
+
records_phase2++;
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
rd_kafka_messages_destroy(batch);
|
|
1558
|
+
batch = NULL;
|
|
1559
|
+
TEST_ASSERT(records_phase2 == (size_t)msg_cnt,
|
|
1560
|
+
"Phase 2: expected %d records, got %zu", msg_cnt,
|
|
1561
|
+
records_phase2);
|
|
1562
|
+
TEST_SAY("Phase 2: received %zu/%d records\n", records_phase2, msg_cnt);
|
|
1563
|
+
|
|
1564
|
+
test_share_consumer_close(consumer);
|
|
1565
|
+
test_share_destroy(consumer);
|
|
1566
|
+
|
|
1567
|
+
SUB_TEST_PASS();
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
/**
|
|
1571
|
+
* @brief Verify consume_batch surfaces RD_KAFKA_RESP_ERR__STATE with
|
|
1572
|
+
* "not subscribed" when there is no active subscription.
|
|
1573
|
+
*
|
|
1574
|
+
* Walks the subscription state machine:
|
|
1575
|
+
* Case 1: never subscribed -> __STATE
|
|
1576
|
+
* Case 2: after subscribe -> not __STATE (recovery)
|
|
1577
|
+
* Case 3: after unsubscribe -> __STATE
|
|
1578
|
+
* Case 4: after re-subscribe following unsub -> not __STATE (recovery)
|
|
1579
|
+
* Case 5: subscribe([]) (== unsubscribe) -> __STATE
|
|
1580
|
+
*/
|
|
1581
|
+
static void do_test_consume_batch_without_subscription(void) {
|
|
1582
|
+
rd_kafka_share_t *consumer;
|
|
1583
|
+
rd_kafka_topic_partition_list_t *subs;
|
|
1584
|
+
rd_kafka_messages_t *batch = NULL;
|
|
1585
|
+
rd_kafka_error_t *err;
|
|
1586
|
+
size_t rcvd = 0;
|
|
1587
|
+
const char *group = "share-no-subscription";
|
|
1588
|
+
const char *topic;
|
|
1589
|
+
|
|
1590
|
+
SUB_TEST();
|
|
1591
|
+
|
|
1592
|
+
topic = test_mk_topic_name("0170-no-subscription", 1);
|
|
1593
|
+
test_create_topic_wait_exists(NULL, topic, 1, -1, 60 * 1000);
|
|
1594
|
+
|
|
1595
|
+
consumer = test_create_share_consumer(group, NULL);
|
|
1596
|
+
|
|
1597
|
+
/* Case 1: never subscribed. */
|
|
1598
|
+
rd_kafka_messages_destroy(batch);
|
|
1599
|
+
batch = NULL;
|
|
1600
|
+
err = rd_kafka_share_poll(consumer, 500, &batch);
|
|
1601
|
+
rcvd = rd_kafka_messages_count(batch);
|
|
1602
|
+
TEST_ASSERT(err != NULL,
|
|
1603
|
+
"Case 1 (never subscribed): expected error, got NULL");
|
|
1604
|
+
TEST_ASSERT(rd_kafka_error_code(err) == RD_KAFKA_RESP_ERR__STATE,
|
|
1605
|
+
"Case 1: expected __STATE, got: %s",
|
|
1606
|
+
rd_kafka_err2name(rd_kafka_error_code(err)));
|
|
1607
|
+
TEST_ASSERT(strstr(rd_kafka_error_string(err), "not subscribed"),
|
|
1608
|
+
"Case 1: expected 'not subscribed' substring, got: %s",
|
|
1609
|
+
rd_kafka_error_string(err));
|
|
1610
|
+
TEST_ASSERT(rcvd == 0, "Case 1: expected 0 msgs, got %zu", rcvd);
|
|
1611
|
+
rd_kafka_error_destroy(err);
|
|
1612
|
+
|
|
1613
|
+
/* Case 2: subscribed -> must not surface __STATE. */
|
|
1614
|
+
subs = rd_kafka_topic_partition_list_new(1);
|
|
1615
|
+
rd_kafka_topic_partition_list_add(subs, topic, RD_KAFKA_PARTITION_UA);
|
|
1616
|
+
TEST_CALL_ERR__(rd_kafka_share_subscribe(consumer, subs));
|
|
1617
|
+
rd_kafka_topic_partition_list_destroy(subs);
|
|
1618
|
+
|
|
1619
|
+
rd_kafka_messages_destroy(batch);
|
|
1620
|
+
batch = NULL;
|
|
1621
|
+
err = rd_kafka_share_poll(consumer, 500, &batch);
|
|
1622
|
+
rcvd = rd_kafka_messages_count(batch);
|
|
1623
|
+
if (err) {
|
|
1624
|
+
TEST_ASSERT(rd_kafka_error_code(err) !=
|
|
1625
|
+
RD_KAFKA_RESP_ERR__STATE,
|
|
1626
|
+
"Case 2 (subscribed): unexpected __STATE: %s",
|
|
1627
|
+
rd_kafka_error_string(err));
|
|
1628
|
+
rd_kafka_error_destroy(err);
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
/* Case 3: unsubscribed -> __STATE again. */
|
|
1632
|
+
TEST_CALL_ERR__(rd_kafka_share_unsubscribe(consumer));
|
|
1633
|
+
|
|
1634
|
+
rd_kafka_messages_destroy(batch);
|
|
1635
|
+
batch = NULL;
|
|
1636
|
+
err = rd_kafka_share_poll(consumer, 500, &batch);
|
|
1637
|
+
rcvd = rd_kafka_messages_count(batch);
|
|
1638
|
+
TEST_ASSERT(err != NULL,
|
|
1639
|
+
"Case 3 (after unsubscribe): expected error, got NULL");
|
|
1640
|
+
TEST_ASSERT(rd_kafka_error_code(err) == RD_KAFKA_RESP_ERR__STATE,
|
|
1641
|
+
"Case 3: expected __STATE, got: %s",
|
|
1642
|
+
rd_kafka_err2name(rd_kafka_error_code(err)));
|
|
1643
|
+
TEST_ASSERT(strstr(rd_kafka_error_string(err), "not subscribed"),
|
|
1644
|
+
"Case 3: expected 'not subscribed' substring, got: %s",
|
|
1645
|
+
rd_kafka_error_string(err));
|
|
1646
|
+
TEST_ASSERT(rcvd == 0, "Case 3: expected 0 msgs, got %zu", rcvd);
|
|
1647
|
+
rd_kafka_error_destroy(err);
|
|
1648
|
+
|
|
1649
|
+
/* Case 4: re-subscribed after unsubscribe -> must not surface __STATE.
|
|
1650
|
+
*/
|
|
1651
|
+
subs = rd_kafka_topic_partition_list_new(1);
|
|
1652
|
+
rd_kafka_topic_partition_list_add(subs, topic, RD_KAFKA_PARTITION_UA);
|
|
1653
|
+
TEST_CALL_ERR__(rd_kafka_share_subscribe(consumer, subs));
|
|
1654
|
+
rd_kafka_topic_partition_list_destroy(subs);
|
|
1655
|
+
|
|
1656
|
+
rd_kafka_messages_destroy(batch);
|
|
1657
|
+
batch = NULL;
|
|
1658
|
+
err = rd_kafka_share_poll(consumer, 500, &batch);
|
|
1659
|
+
rcvd = rd_kafka_messages_count(batch);
|
|
1660
|
+
if (err) {
|
|
1661
|
+
TEST_ASSERT(rd_kafka_error_code(err) !=
|
|
1662
|
+
RD_KAFKA_RESP_ERR__STATE,
|
|
1663
|
+
"Case 4 (re-subscribed): unexpected __STATE: %s",
|
|
1664
|
+
rd_kafka_error_string(err));
|
|
1665
|
+
rd_kafka_error_destroy(err);
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
/* Case 5: subscribe([]) is equivalent to unsubscribe — must
|
|
1669
|
+
* return NO_ERROR and clear the F_SUBSCRIPTION flag, so the
|
|
1670
|
+
* next consume_batch returns __STATE. */
|
|
1671
|
+
rd_kafka_resp_err_t empty_err;
|
|
1672
|
+
|
|
1673
|
+
subs = rd_kafka_topic_partition_list_new(0);
|
|
1674
|
+
empty_err = rd_kafka_share_subscribe(consumer, subs);
|
|
1675
|
+
TEST_ASSERT(empty_err == RD_KAFKA_RESP_ERR_NO_ERROR,
|
|
1676
|
+
"Case 5 (subscribe([])): expected NO_ERROR "
|
|
1677
|
+
"(treated as unsubscribe), got: %s",
|
|
1678
|
+
rd_kafka_err2name(empty_err));
|
|
1679
|
+
rd_kafka_topic_partition_list_destroy(subs);
|
|
1680
|
+
|
|
1681
|
+
rd_kafka_messages_destroy(batch);
|
|
1682
|
+
batch = NULL;
|
|
1683
|
+
err = rd_kafka_share_poll(consumer, 500, &batch);
|
|
1684
|
+
rcvd = rd_kafka_messages_count(batch);
|
|
1685
|
+
TEST_ASSERT(err != NULL,
|
|
1686
|
+
"Case 5 (after subscribe([])): expected error, got NULL");
|
|
1687
|
+
TEST_ASSERT(rd_kafka_error_code(err) == RD_KAFKA_RESP_ERR__STATE,
|
|
1688
|
+
"Case 5: expected __STATE, got: %s",
|
|
1689
|
+
rd_kafka_err2name(rd_kafka_error_code(err)));
|
|
1690
|
+
TEST_ASSERT(strstr(rd_kafka_error_string(err), "not subscribed"),
|
|
1691
|
+
"Case 5: expected 'not subscribed' substring, got: %s",
|
|
1692
|
+
rd_kafka_error_string(err));
|
|
1693
|
+
TEST_ASSERT(rcvd == 0, "Case 5: expected 0 msgs, got %zu", rcvd);
|
|
1694
|
+
rd_kafka_error_destroy(err);
|
|
1695
|
+
rd_kafka_messages_destroy(batch);
|
|
1696
|
+
batch = NULL;
|
|
1697
|
+
|
|
1698
|
+
test_share_consumer_close(consumer);
|
|
1699
|
+
test_share_destroy(consumer);
|
|
1700
|
+
|
|
1701
|
+
SUB_TEST_PASS();
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
/* Extends test_ack_cb_state_t to also record the topic of the first
|
|
1705
|
+
* callback invocation, so do_test_subscription_change_acks_pending can
|
|
1706
|
+
* verify that the implicit ack driven by the subscription change was for
|
|
1707
|
+
* t1 (not later t2 acks fired by subsequent polls in the loop). */
|
|
1708
|
+
typedef struct subchg_ack_state_s {
|
|
1709
|
+
test_ack_cb_state_t base; /* must be first - cast-compat with base */
|
|
1710
|
+
char first_callback_topic[256];
|
|
1711
|
+
} subchg_ack_state_t;
|
|
1712
|
+
|
|
1713
|
+
static void subchg_ack_cb(rd_kafka_share_t *rkshare,
|
|
1714
|
+
rd_kafka_share_partition_offsets_list_t *partitions,
|
|
1715
|
+
rd_kafka_resp_err_t err,
|
|
1716
|
+
void *opaque) {
|
|
1717
|
+
subchg_ack_state_t *st = (subchg_ack_state_t *)opaque;
|
|
1718
|
+
const rd_kafka_share_partition_offsets_t *entry;
|
|
1719
|
+
const rd_kafka_topic_partition_t *part;
|
|
1720
|
+
|
|
1721
|
+
(void)rkshare;
|
|
1722
|
+
|
|
1723
|
+
/* Mirror test_share_ack_cb's base accounting */
|
|
1724
|
+
test_ack_cb_state_push_err(&st->base, err);
|
|
1725
|
+
|
|
1726
|
+
entry = rd_kafka_share_partition_offsets_list_get(partitions, 0);
|
|
1727
|
+
if (!entry)
|
|
1728
|
+
return;
|
|
1729
|
+
|
|
1730
|
+
st->base.total_offsets +=
|
|
1731
|
+
rd_kafka_share_partition_offsets_offsets_cnt(entry);
|
|
1732
|
+
|
|
1733
|
+
/* Record the topic of the first callback invocation only. */
|
|
1734
|
+
if (st->base.callback_cnt == 1) {
|
|
1735
|
+
part = rd_kafka_share_partition_offsets_partition(entry);
|
|
1736
|
+
if (part && part->topic)
|
|
1737
|
+
rd_snprintf(st->first_callback_topic,
|
|
1738
|
+
sizeof(st->first_callback_topic), "%s",
|
|
1739
|
+
part->topic);
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
|
|
1744
|
+
/**
|
|
1745
|
+
* @brief Test that a subscription change drives an implicit ack of the
|
|
1746
|
+
* previously-fetched batch, firing the share ack callback.
|
|
1747
|
+
*
|
|
1748
|
+
* Produce one record to t1 and one to t2. In implicit mode, subscribe to t1,
|
|
1749
|
+
* fetch the t1 record (without explicitly acknowledging), then re-subscribe
|
|
1750
|
+
* to t2. The subscription change should cause t1's outstanding record to be
|
|
1751
|
+
* implicitly acknowledged, firing the ack commit callback for t1's
|
|
1752
|
+
* partition.
|
|
1753
|
+
*
|
|
1754
|
+
* A custom callback records the topic of the first callback invocation so
|
|
1755
|
+
* we can verify the callback fired for t1 (not for t2's implicit ack,
|
|
1756
|
+
* which can also fire if the polling loop iterates past the t2 record's
|
|
1757
|
+
* arrival).
|
|
1758
|
+
*/
|
|
1759
|
+
static void do_test_subscription_change_acks_pending(void) {
|
|
1760
|
+
char *group;
|
|
1761
|
+
char *t1;
|
|
1762
|
+
char *t2;
|
|
1763
|
+
rd_kafka_share_t *rkshare;
|
|
1764
|
+
rd_kafka_topic_partition_list_t *subs;
|
|
1765
|
+
rd_kafka_messages_t *batch = NULL;
|
|
1766
|
+
rd_kafka_error_t *err;
|
|
1767
|
+
subchg_ack_state_t state = {0};
|
|
1768
|
+
size_t rcvd = 0;
|
|
1769
|
+
int attempts;
|
|
1770
|
+
|
|
1771
|
+
SUB_TEST();
|
|
1772
|
+
|
|
1773
|
+
/* rd_strdup each test_mk_topic_name() result because the helper
|
|
1774
|
+
* returns a pointer to a single TLS static buffer that gets
|
|
1775
|
+
* clobbered by the next call. */
|
|
1776
|
+
group = rd_strdup(test_mk_topic_name("share-sub-change-ack", 1));
|
|
1777
|
+
t1 = rd_strdup(test_mk_topic_name("0170-subchg-t1", 1));
|
|
1778
|
+
t2 = rd_strdup(test_mk_topic_name("0170-subchg-t2", 1));
|
|
1779
|
+
|
|
1780
|
+
TEST_SAY("\n");
|
|
1781
|
+
TEST_SAY(
|
|
1782
|
+
"============================================================\n");
|
|
1783
|
+
TEST_SAY("=== subscription-change-drives-ack-callback ===\n");
|
|
1784
|
+
TEST_SAY(
|
|
1785
|
+
"============================================================\n");
|
|
1786
|
+
|
|
1787
|
+
test_create_topic_wait_exists(NULL, t1, 1, -1, 30000);
|
|
1788
|
+
test_create_topic_wait_exists(NULL, t2, 1, -1, 30000);
|
|
1789
|
+
|
|
1790
|
+
test_produce_msgs_simple(common_producer, t1, 0, 1);
|
|
1791
|
+
test_produce_msgs_simple(common_producer, t2, 0, 1);
|
|
1792
|
+
|
|
1793
|
+
rkshare = test_create_share_consumer_with_cb(
|
|
1794
|
+
group, "implicit", &state.base, subchg_ack_cb);
|
|
1795
|
+
test_share_set_auto_offset_reset(group, "earliest");
|
|
1796
|
+
|
|
1797
|
+
/* Subscribe to t1 only */
|
|
1798
|
+
subs = rd_kafka_topic_partition_list_new(1);
|
|
1799
|
+
rd_kafka_topic_partition_list_add(subs, t1, RD_KAFKA_PARTITION_UA);
|
|
1800
|
+
rd_kafka_share_subscribe(rkshare, subs);
|
|
1801
|
+
rd_kafka_topic_partition_list_destroy(subs);
|
|
1802
|
+
|
|
1803
|
+
/* Poll until the t1 record arrives */
|
|
1804
|
+
attempts = 30;
|
|
1805
|
+
while (rcvd < 1 && attempts-- > 0) {
|
|
1806
|
+
size_t batch_rcvd = 0;
|
|
1807
|
+
rd_kafka_messages_destroy(batch);
|
|
1808
|
+
batch = NULL;
|
|
1809
|
+
err = rd_kafka_share_poll(rkshare, 2000, &batch);
|
|
1810
|
+
batch_rcvd = rd_kafka_messages_count(batch);
|
|
1811
|
+
if (err)
|
|
1812
|
+
rd_kafka_error_destroy(err);
|
|
1813
|
+
rcvd += batch_rcvd;
|
|
1814
|
+
}
|
|
1815
|
+
TEST_ASSERT(rcvd == 1, "Expected 1 record from t1, got %zu", rcvd);
|
|
1816
|
+
rd_kafka_messages_destroy(batch);
|
|
1817
|
+
batch = NULL;
|
|
1818
|
+
|
|
1819
|
+
TEST_ASSERT(state.base.callback_cnt == 0,
|
|
1820
|
+
"Did not expect callback before subscription change, "
|
|
1821
|
+
"got %d",
|
|
1822
|
+
state.base.callback_cnt);
|
|
1823
|
+
|
|
1824
|
+
/* Re-subscribe to t2 only - this should drive an implicit ack of
|
|
1825
|
+
* t1's outstanding batch. */
|
|
1826
|
+
subs = rd_kafka_topic_partition_list_new(1);
|
|
1827
|
+
rd_kafka_topic_partition_list_add(subs, t2, RD_KAFKA_PARTITION_UA);
|
|
1828
|
+
rd_kafka_share_subscribe(rkshare, subs);
|
|
1829
|
+
rd_kafka_topic_partition_list_destroy(subs);
|
|
1830
|
+
|
|
1831
|
+
/* Poll a few times to drive the callback and pick up the t2 record.
|
|
1832
|
+
* Subsequent polls in this loop may also piggyback t2's implicit
|
|
1833
|
+
* ack and produce a second callback, but the FIRST callback must
|
|
1834
|
+
* be the one driven by the t1 -> t2 subscription change. The
|
|
1835
|
+
* custom callback records the topic of the first invocation so
|
|
1836
|
+
* we can assert that below. */
|
|
1837
|
+
rcvd = 0;
|
|
1838
|
+
attempts = 30;
|
|
1839
|
+
while ((rcvd < 1 || state.base.callback_cnt < 1) && attempts-- > 0) {
|
|
1840
|
+
size_t batch_rcvd = 0;
|
|
1841
|
+
rd_kafka_messages_destroy(batch);
|
|
1842
|
+
batch = NULL;
|
|
1843
|
+
err = rd_kafka_share_poll(rkshare, 1000, &batch);
|
|
1844
|
+
batch_rcvd = rd_kafka_messages_count(batch);
|
|
1845
|
+
if (err)
|
|
1846
|
+
rd_kafka_error_destroy(err);
|
|
1847
|
+
rcvd += batch_rcvd;
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
TEST_ASSERT(rcvd == 1, "Expected 1 record from t2, got %zu", rcvd);
|
|
1851
|
+
TEST_ASSERT(state.base.callback_cnt >= 1,
|
|
1852
|
+
"Expected ack callback to fire after subscription change, "
|
|
1853
|
+
"got %d callbacks",
|
|
1854
|
+
state.base.callback_cnt);
|
|
1855
|
+
TEST_ASSERT(
|
|
1856
|
+
test_ack_cb_state_first_err(&state.base) ==
|
|
1857
|
+
RD_KAFKA_RESP_ERR_NO_ERROR,
|
|
1858
|
+
"Expected no error in callback, got %s",
|
|
1859
|
+
rd_kafka_err2name(test_ack_cb_state_first_err(&state.base)));
|
|
1860
|
+
TEST_ASSERT(strcmp(state.first_callback_topic, t1) == 0,
|
|
1861
|
+
"Expected first ack callback to be for t1 (%s), got %s", t1,
|
|
1862
|
+
state.first_callback_topic);
|
|
1863
|
+
|
|
1864
|
+
rd_kafka_messages_destroy(batch);
|
|
1865
|
+
batch = NULL;
|
|
1866
|
+
|
|
1867
|
+
test_share_consumer_close(rkshare);
|
|
1868
|
+
test_share_destroy(rkshare);
|
|
1869
|
+
test_ack_cb_state_destroy(&state.base);
|
|
1870
|
+
|
|
1871
|
+
rd_free(t1);
|
|
1872
|
+
rd_free(t2);
|
|
1873
|
+
rd_free(group);
|
|
1874
|
+
|
|
1875
|
+
TEST_SAY("=== subscription-change-drives-ack-callback: PASSED ===\n");
|
|
1876
|
+
|
|
1877
|
+
SUB_TEST_PASS();
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1880
|
+
|
|
1881
|
+
/**
|
|
1882
|
+
* @brief Test two share groups on the same topic with different
|
|
1883
|
+
* auto.offset.reset policies (earliest vs latest).
|
|
1884
|
+
*
|
|
1885
|
+
* Group A is configured for earliest, group B for latest. Records produced
|
|
1886
|
+
* before either group joins are visible only to group A; records produced
|
|
1887
|
+
* after both have joined are visible to both.
|
|
1888
|
+
*/
|
|
1889
|
+
static void do_test_two_groups_earliest_vs_latest(void) {
|
|
1890
|
+
char *grpA;
|
|
1891
|
+
char *grpB;
|
|
1892
|
+
char *topic;
|
|
1893
|
+
rd_kafka_share_t *consA, *consB;
|
|
1894
|
+
rd_kafka_topic_partition_list_t *subs;
|
|
1895
|
+
int countA = 0, countB = 0;
|
|
1896
|
+
int attempts;
|
|
1897
|
+
|
|
1898
|
+
SUB_TEST();
|
|
1899
|
+
|
|
1900
|
+
/* rd_strdup each test_mk_topic_name() result because the helper
|
|
1901
|
+
* returns a pointer to a single TLS static buffer that gets
|
|
1902
|
+
* clobbered by the next call. Without this, grpA, grpB and topic
|
|
1903
|
+
* would all alias to the same buffer. */
|
|
1904
|
+
grpA = rd_strdup(test_mk_topic_name("share-grpA-earliest", 1));
|
|
1905
|
+
grpB = rd_strdup(test_mk_topic_name("share-grpB-latest", 1));
|
|
1906
|
+
topic = rd_strdup(test_mk_topic_name("0170-evl", 1));
|
|
1907
|
+
|
|
1908
|
+
TEST_SAY("\n");
|
|
1909
|
+
TEST_SAY(
|
|
1910
|
+
"============================================================\n");
|
|
1911
|
+
TEST_SAY("=== two-groups-earliest-vs-latest ===\n");
|
|
1912
|
+
TEST_SAY(
|
|
1913
|
+
"============================================================\n");
|
|
1914
|
+
|
|
1915
|
+
test_create_topic_wait_exists(NULL, topic, 1, -1, 30000);
|
|
1916
|
+
|
|
1917
|
+
/* Produce 5 records BEFORE the consumers join. These are the
|
|
1918
|
+
* "original records present in the broker" that earliest fetches
|
|
1919
|
+
* and latest skips. */
|
|
1920
|
+
test_produce_msgs_simple(common_producer, topic, 0, 5);
|
|
1921
|
+
|
|
1922
|
+
/* Match the working librdkafka ordering used by
|
|
1923
|
+
* do_test_multi_consumer_overlap (0170) and 0171's
|
|
1924
|
+
* test_poll_callback_piggybacked_acks:
|
|
1925
|
+
* produce -> create consumer -> set reset -> subscribe -> poll.
|
|
1926
|
+
* Setting share.auto.offset.reset before any consumer exists at
|
|
1927
|
+
* the broker doesn't take effect; setting it after consumer
|
|
1928
|
+
* creation but before subscribe does. */
|
|
1929
|
+
consA = test_create_share_consumer(grpA, NULL);
|
|
1930
|
+
consB = test_create_share_consumer(grpB, NULL);
|
|
1931
|
+
|
|
1932
|
+
test_share_set_auto_offset_reset(grpA, "earliest");
|
|
1933
|
+
test_share_set_auto_offset_reset(grpB, "latest");
|
|
1934
|
+
|
|
1935
|
+
subs = rd_kafka_topic_partition_list_new(1);
|
|
1936
|
+
rd_kafka_topic_partition_list_add(subs, topic, RD_KAFKA_PARTITION_UA);
|
|
1937
|
+
rd_kafka_share_subscribe(consA, subs);
|
|
1938
|
+
rd_kafka_share_subscribe(consB, subs);
|
|
1939
|
+
rd_kafka_topic_partition_list_destroy(subs);
|
|
1940
|
+
|
|
1941
|
+
/* groupA (earliest) fetches the 5 pre-existing records;
|
|
1942
|
+
* groupB (latest) skips them (HW=5 anchors the start). */
|
|
1943
|
+
attempts = 30;
|
|
1944
|
+
while (attempts-- > 0 && countA < 5) {
|
|
1945
|
+
int batch_cnt = 0;
|
|
1946
|
+
test_share_poll(consA, 1000, NULL, 0, &batch_cnt);
|
|
1947
|
+
countA += batch_cnt;
|
|
1948
|
+
|
|
1949
|
+
batch_cnt = 0;
|
|
1950
|
+
test_share_poll(consB, 500, NULL, 0, &batch_cnt);
|
|
1951
|
+
countB += batch_cnt;
|
|
1952
|
+
}
|
|
1953
|
+
TEST_ASSERT(countA == 5, "groupA (earliest) expected 5 records, got %d",
|
|
1954
|
+
countA);
|
|
1955
|
+
TEST_ASSERT(countB == 0,
|
|
1956
|
+
"groupB (latest) expected 0 records for pre-subscribe "
|
|
1957
|
+
"produce, got %d",
|
|
1958
|
+
countB);
|
|
1959
|
+
|
|
1960
|
+
/* Produce 3 more records AFTER both groups have joined - both
|
|
1961
|
+
* groups should see these (earliest continues from HW=5, latest
|
|
1962
|
+
* advances from HW=5 too). */
|
|
1963
|
+
test_produce_msgs_simple(common_producer, topic, 0, 3);
|
|
1964
|
+
|
|
1965
|
+
attempts = 30;
|
|
1966
|
+
while (attempts-- > 0 && countB < 3) {
|
|
1967
|
+
int batch_cnt = 0;
|
|
1968
|
+
test_share_poll(consA, 500, NULL, 0, &batch_cnt);
|
|
1969
|
+
countA += batch_cnt;
|
|
1970
|
+
|
|
1971
|
+
batch_cnt = 0;
|
|
1972
|
+
test_share_poll(consB, 1000, NULL, 0, &batch_cnt);
|
|
1973
|
+
countB += batch_cnt;
|
|
1974
|
+
}
|
|
1975
|
+
TEST_ASSERT(countA == 8, "groupA expected 8 records total, got %d",
|
|
1976
|
+
countA);
|
|
1977
|
+
TEST_ASSERT(countB == 3,
|
|
1978
|
+
"groupB expected 3 records (post-subscribe only), got %d",
|
|
1979
|
+
countB);
|
|
1980
|
+
|
|
1981
|
+
test_share_consumer_close(consA);
|
|
1982
|
+
test_share_consumer_close(consB);
|
|
1983
|
+
test_share_destroy(consA);
|
|
1984
|
+
test_share_destroy(consB);
|
|
1985
|
+
|
|
1986
|
+
rd_free(grpA);
|
|
1987
|
+
rd_free(grpB);
|
|
1988
|
+
rd_free(topic);
|
|
1989
|
+
|
|
1990
|
+
TEST_SAY("=== two-groups-earliest-vs-latest: PASSED ===\n");
|
|
1991
|
+
|
|
1992
|
+
SUB_TEST_PASS();
|
|
1993
|
+
}
|
|
1994
|
+
|
|
1995
|
+
|
|
1996
|
+
/**
|
|
1997
|
+
* @brief Test that DeleteRecords advances the LSO and a new share group
|
|
1998
|
+
* with auto.offset.reset=earliest starts at the new LSO.
|
|
1999
|
+
*
|
|
2000
|
+
* Produce 10 records, DeleteRecords up to offset 5 (LSO -> 5), then create
|
|
2001
|
+
* a new share group with earliest and verify exactly 5 records
|
|
2002
|
+
* (offsets 5..9) are consumed.
|
|
2003
|
+
*/
|
|
2004
|
+
static void do_test_delete_records_advances_lso(void) {
|
|
2005
|
+
char *group;
|
|
2006
|
+
char *topic;
|
|
2007
|
+
rd_kafka_topic_partition_list_t *offsets;
|
|
2008
|
+
rd_kafka_share_t *rkshare;
|
|
2009
|
+
rd_kafka_topic_partition_list_t *subs;
|
|
2010
|
+
rd_kafka_messages_t *batch = NULL;
|
|
2011
|
+
rd_kafka_error_t *err_obj;
|
|
2012
|
+
rd_kafka_resp_err_t err;
|
|
2013
|
+
int64_t first_offset = -1;
|
|
2014
|
+
int consumed = 0;
|
|
2015
|
+
size_t batch_cnt = 0;
|
|
2016
|
+
int attempts;
|
|
2017
|
+
size_t k;
|
|
2018
|
+
|
|
2019
|
+
SUB_TEST();
|
|
2020
|
+
|
|
2021
|
+
if (!strcmp(test_getenv("TEST_BROKER_OS", ""), "windows"))
|
|
2022
|
+
SUB_TEST_SKIP(
|
|
2023
|
+
"delete-records scenario not supported (broker on "
|
|
2024
|
+
"Windows)\n");
|
|
2025
|
+
|
|
2026
|
+
group = rd_strdup(test_mk_topic_name("share-lso-delete", 1));
|
|
2027
|
+
topic = rd_strdup(test_mk_topic_name("0170-lso", 1));
|
|
2028
|
+
|
|
2029
|
+
TEST_SAY("\n");
|
|
2030
|
+
TEST_SAY(
|
|
2031
|
+
"============================================================\n");
|
|
2032
|
+
TEST_SAY("=== delete-records-advances-lso ===\n");
|
|
2033
|
+
TEST_SAY(
|
|
2034
|
+
"============================================================\n");
|
|
2035
|
+
|
|
2036
|
+
test_create_topic_wait_exists(NULL, topic, 1, -1, 30000);
|
|
2037
|
+
test_produce_msgs_simple(common_producer, topic, 0, 10);
|
|
2038
|
+
|
|
2039
|
+
/* DeleteRecords up to offset 5: deletes offsets 0..4, LSO moves to 5 */
|
|
2040
|
+
offsets = rd_kafka_topic_partition_list_new(1);
|
|
2041
|
+
rd_kafka_topic_partition_list_add(offsets, topic, 0)->offset = 5;
|
|
2042
|
+
err = test_DeleteRecords_simple(common_admin, NULL, offsets, NULL);
|
|
2043
|
+
rd_kafka_topic_partition_list_destroy(offsets);
|
|
2044
|
+
TEST_ASSERT(err == RD_KAFKA_RESP_ERR_NO_ERROR,
|
|
2045
|
+
"DeleteRecords failed: %s", rd_kafka_err2name(err));
|
|
2046
|
+
|
|
2047
|
+
/* New share group with earliest should start at the new LSO */
|
|
2048
|
+
test_share_set_auto_offset_reset(group, "earliest");
|
|
2049
|
+
rkshare = test_create_share_consumer(group, NULL);
|
|
2050
|
+
|
|
2051
|
+
subs = rd_kafka_topic_partition_list_new(1);
|
|
2052
|
+
rd_kafka_topic_partition_list_add(subs, topic, RD_KAFKA_PARTITION_UA);
|
|
2053
|
+
rd_kafka_share_subscribe(rkshare, subs);
|
|
2054
|
+
rd_kafka_topic_partition_list_destroy(subs);
|
|
2055
|
+
|
|
2056
|
+
attempts = 30;
|
|
2057
|
+
while (consumed < 5 && attempts-- > 0) {
|
|
2058
|
+
rd_kafka_messages_destroy(batch);
|
|
2059
|
+
batch = NULL;
|
|
2060
|
+
err_obj = rd_kafka_share_poll(rkshare, 2000, &batch);
|
|
2061
|
+
batch_cnt = rd_kafka_messages_count(batch);
|
|
2062
|
+
if (err_obj) {
|
|
2063
|
+
rd_kafka_error_destroy(err_obj);
|
|
2064
|
+
continue;
|
|
2065
|
+
}
|
|
2066
|
+
for (k = 0; k < batch_cnt; k++) {
|
|
2067
|
+
rd_kafka_message_t *msg =
|
|
2068
|
+
rd_kafka_messages_get(batch, k);
|
|
2069
|
+
if (!msg->err) {
|
|
2070
|
+
if (first_offset == -1)
|
|
2071
|
+
first_offset = msg->offset;
|
|
2072
|
+
consumed++;
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
2075
|
+
}
|
|
2076
|
+
rd_kafka_messages_destroy(batch);
|
|
2077
|
+
batch = NULL;
|
|
2078
|
+
TEST_ASSERT(consumed == 5,
|
|
2079
|
+
"Expected 5 records (offsets 5..9) after DeleteRecords, "
|
|
2080
|
+
"consumed %d",
|
|
2081
|
+
consumed);
|
|
2082
|
+
TEST_ASSERT(first_offset == 5,
|
|
2083
|
+
"Expected first delivered offset to be 5 (new LSO after "
|
|
2084
|
+
"DeleteRecords), got %" PRId64,
|
|
2085
|
+
first_offset);
|
|
2086
|
+
|
|
2087
|
+
/* Verify no additional records show up */
|
|
2088
|
+
rd_kafka_messages_destroy(batch);
|
|
2089
|
+
batch = NULL;
|
|
2090
|
+
err_obj = rd_kafka_share_poll(rkshare, 1000, &batch);
|
|
2091
|
+
batch_cnt = rd_kafka_messages_count(batch);
|
|
2092
|
+
if (err_obj)
|
|
2093
|
+
rd_kafka_error_destroy(err_obj);
|
|
2094
|
+
TEST_ASSERT(batch_cnt == 0,
|
|
2095
|
+
"Did not expect more records after consuming 5, got %zu",
|
|
2096
|
+
batch_cnt);
|
|
2097
|
+
rd_kafka_messages_destroy(batch);
|
|
2098
|
+
batch = NULL;
|
|
2099
|
+
|
|
2100
|
+
test_share_consumer_close(rkshare);
|
|
2101
|
+
test_share_destroy(rkshare);
|
|
2102
|
+
|
|
2103
|
+
rd_free(group);
|
|
2104
|
+
rd_free(topic);
|
|
2105
|
+
|
|
2106
|
+
TEST_SAY("=== delete-records-advances-lso: PASSED ===\n");
|
|
2107
|
+
|
|
2108
|
+
SUB_TEST_PASS();
|
|
2109
|
+
}
|
|
2110
|
+
|
|
2111
|
+
/* subscribe([]) is equivalent to unsubscribe — must clear the
|
|
2112
|
+
* subscription flag so the subsequent poll surfaces __STATE. */
|
|
2113
|
+
static const test_scenario_t test_poll_after_empty_subscribe = {
|
|
2114
|
+
.name = "poll-after-empty-subscribe",
|
|
2115
|
+
.ops = {SUBSCRIBE(1), PRODUCE(5), CONSUME_ANY(),
|
|
2116
|
+
SUBSCRIBE_EMPTY(), /* Empty list == unsubscribe */
|
|
2117
|
+
POLL_NO_SUB(), TEST_OPS_END()}};
|
|
2118
|
+
|
|
2119
|
+
int main_0170_share_consumer_subscription(int argc, char **argv) {
|
|
2120
|
+
/* Create common handles for all tests */
|
|
2121
|
+
common_producer = test_create_producer();
|
|
2122
|
+
common_admin = test_create_producer();
|
|
2123
|
+
|
|
2124
|
+
test_timeout_set(200);
|
|
2125
|
+
|
|
2126
|
+
/* Auto offset reset tests */
|
|
2127
|
+
do_test_auto_offset_reset_earliest();
|
|
2128
|
+
do_test_auto_offset_reset_default_latest();
|
|
2129
|
+
|
|
2130
|
+
/* Basic subscription tests */
|
|
2131
|
+
do_test_scenario(&test_single_subscribe);
|
|
2132
|
+
do_test_scenario(&test_single_unsubscribe);
|
|
2133
|
+
do_test_scenario(&test_repeated_subscribe);
|
|
2134
|
+
do_test_scenario(&test_repeated_unsubscribe);
|
|
2135
|
+
|
|
2136
|
+
/* Subscription replacement tests */
|
|
2137
|
+
/* TODO KIP-932: test_topic_switch might be incorrect. Verify and
|
|
2138
|
+
* remove. */
|
|
2139
|
+
// do_test_scenario(&test_topic_switch);
|
|
2140
|
+
do_test_scenario(&test_incremental_subscription);
|
|
2141
|
+
|
|
2142
|
+
/* Edge case tests */
|
|
2143
|
+
do_test_scenario(&test_subscribe_before_topic_exists);
|
|
2144
|
+
do_test_scenario(&test_poll_empty_topic);
|
|
2145
|
+
do_test_scenario(&test_poll_no_subscription);
|
|
2146
|
+
do_test_scenario(&test_poll_after_unsubscribe);
|
|
2147
|
+
|
|
2148
|
+
/* Topic deletion tests (Skipped for Windows)*/
|
|
2149
|
+
if (!strcmp(test_getenv("TEST_BROKER_OS", ""), "windows"))
|
|
2150
|
+
TEST_SAY(
|
|
2151
|
+
"Skipping topic deletion scenario"
|
|
2152
|
+
"(broker on Windows)\n");
|
|
2153
|
+
else
|
|
2154
|
+
do_test_scenario(&test_topic_deletion);
|
|
2155
|
+
|
|
2156
|
+
/* Verify that topic deletion does not surface a topic-level
|
|
2157
|
+
* error code to consume_batch (transient codes are log-only). */
|
|
2158
|
+
test_topic_deletion_does_not_surface_error();
|
|
2159
|
+
|
|
2160
|
+
/* Stress tests */
|
|
2161
|
+
do_test_scenario(&test_rapid_updates);
|
|
2162
|
+
|
|
2163
|
+
/* Multi-consumer tests (standalone - requires shared topics) */
|
|
2164
|
+
do_test_multi_consumer_overlap();
|
|
2165
|
+
|
|
2166
|
+
/* Scale tests (many topics) */
|
|
2167
|
+
do_test_subscribe_15_topics();
|
|
2168
|
+
|
|
2169
|
+
/* Subscription change drives ack callback */
|
|
2170
|
+
do_test_subscription_change_acks_pending();
|
|
2171
|
+
|
|
2172
|
+
/* Two groups with earliest vs latest offset reset */
|
|
2173
|
+
do_test_two_groups_earliest_vs_latest();
|
|
2174
|
+
|
|
2175
|
+
/* DeleteRecords advances LSO; new group with earliest sees fewer
|
|
2176
|
+
* records (skipped on Windows brokers). */
|
|
2177
|
+
do_test_delete_records_advances_lso();
|
|
2178
|
+
|
|
2179
|
+
/* Input validation tests */
|
|
2180
|
+
do_test_subscribe_input_validation();
|
|
2181
|
+
do_test_subscribe_caret_treated_as_literal_e2e();
|
|
2182
|
+
|
|
2183
|
+
/* Subscription state guard tests */
|
|
2184
|
+
do_test_consume_batch_without_subscription();
|
|
2185
|
+
do_test_scenario(&test_poll_after_empty_subscribe);
|
|
2186
|
+
|
|
2187
|
+
/* Cleanup common handles */
|
|
2188
|
+
rd_kafka_destroy(common_admin);
|
|
2189
|
+
rd_kafka_destroy(common_producer);
|
|
2190
|
+
|
|
2191
|
+
return 0;
|
|
2192
|
+
}
|