@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.
Files changed (628) hide show
  1. package/CHANGELOG.md +457 -0
  2. package/LICENSE.kafkajs +31 -0
  3. package/LICENSE.node-rdkafka +25 -0
  4. package/LICENSE.txt +20 -0
  5. package/README.md +133 -0
  6. package/binding.gyp +184 -0
  7. package/build/Release/confluent-kafka-javascript.node +0 -0
  8. package/build/deps/librdkafka++.so.1 +0 -0
  9. package/build/deps/librdkafka.Makefile +6 -0
  10. package/build/deps/librdkafka.so.1 +0 -0
  11. package/build/deps/librdkafka.target.mk +55 -0
  12. package/deps/librdkafka/.clang-format +136 -0
  13. package/deps/librdkafka/.clang-format-cpp +103 -0
  14. package/deps/librdkafka/.dir-locals.el +10 -0
  15. package/deps/librdkafka/.formatignore +35 -0
  16. package/deps/librdkafka/.gdbmacros +19 -0
  17. package/deps/librdkafka/CHANGELOG.md +2477 -0
  18. package/deps/librdkafka/CMakeLists.txt +291 -0
  19. package/deps/librdkafka/CODE_OF_CONDUCT.md +46 -0
  20. package/deps/librdkafka/CONFIGURATION.md +212 -0
  21. package/deps/librdkafka/CONTRIBUTING.md +431 -0
  22. package/deps/librdkafka/Doxyfile +2375 -0
  23. package/deps/librdkafka/INTRODUCTION.md +2752 -0
  24. package/deps/librdkafka/LICENSE +26 -0
  25. package/deps/librdkafka/LICENSE.cjson +22 -0
  26. package/deps/librdkafka/LICENSE.crc32c +28 -0
  27. package/deps/librdkafka/LICENSE.fnv1a +18 -0
  28. package/deps/librdkafka/LICENSE.hdrhistogram +27 -0
  29. package/deps/librdkafka/LICENSE.lz4 +26 -0
  30. package/deps/librdkafka/LICENSE.murmur2 +25 -0
  31. package/deps/librdkafka/LICENSE.nanopb +22 -0
  32. package/deps/librdkafka/LICENSE.opentelemetry +203 -0
  33. package/deps/librdkafka/LICENSE.pycrc +23 -0
  34. package/deps/librdkafka/LICENSE.queue +31 -0
  35. package/deps/librdkafka/LICENSE.regexp +5 -0
  36. package/deps/librdkafka/LICENSE.snappy +36 -0
  37. package/deps/librdkafka/LICENSE.tinycthread +26 -0
  38. package/deps/librdkafka/LICENSE.wingetopt +49 -0
  39. package/deps/librdkafka/LICENSES.txt +625 -0
  40. package/deps/librdkafka/Makefile +125 -0
  41. package/deps/librdkafka/README.md +201 -0
  42. package/deps/librdkafka/README.win32 +26 -0
  43. package/deps/librdkafka/STATISTICS.md +624 -0
  44. package/deps/librdkafka/configure +214 -0
  45. package/deps/librdkafka/configure.self +341 -0
  46. package/deps/librdkafka/dev-conf.sh +123 -0
  47. package/deps/librdkafka/lds-gen.py +73 -0
  48. package/deps/librdkafka/mainpage.doxy +40 -0
  49. package/deps/librdkafka/mklove/Makefile.base +329 -0
  50. package/deps/librdkafka/mklove/modules/configure.atomics +144 -0
  51. package/deps/librdkafka/mklove/modules/configure.base +2484 -0
  52. package/deps/librdkafka/mklove/modules/configure.builtin +70 -0
  53. package/deps/librdkafka/mklove/modules/configure.cc +186 -0
  54. package/deps/librdkafka/mklove/modules/configure.cxx +8 -0
  55. package/deps/librdkafka/mklove/modules/configure.fileversion +65 -0
  56. package/deps/librdkafka/mklove/modules/configure.gitversion +29 -0
  57. package/deps/librdkafka/mklove/modules/configure.good_cflags +18 -0
  58. package/deps/librdkafka/mklove/modules/configure.host +132 -0
  59. package/deps/librdkafka/mklove/modules/configure.lib +49 -0
  60. package/deps/librdkafka/mklove/modules/configure.libcurl +99 -0
  61. package/deps/librdkafka/mklove/modules/configure.libsasl2 +36 -0
  62. package/deps/librdkafka/mklove/modules/configure.libssl +147 -0
  63. package/deps/librdkafka/mklove/modules/configure.libzstd +58 -0
  64. package/deps/librdkafka/mklove/modules/configure.parseversion +95 -0
  65. package/deps/librdkafka/mklove/modules/configure.pic +16 -0
  66. package/deps/librdkafka/mklove/modules/configure.socket +20 -0
  67. package/deps/librdkafka/mklove/modules/configure.zlib +61 -0
  68. package/deps/librdkafka/mklove/modules/patches/README.md +8 -0
  69. package/deps/librdkafka/mklove/modules/patches/libcurl.0000-no-runtime-linking-check.patch +11 -0
  70. package/deps/librdkafka/mklove/modules/patches/libssl.0000-osx-rand-include-fix-OpenSSL-PR16409.patch +56 -0
  71. package/deps/librdkafka/service.yml +182 -0
  72. package/deps/librdkafka/src/CMakeLists.txt +379 -0
  73. package/deps/librdkafka/src/Makefile +105 -0
  74. package/deps/librdkafka/src/README.lz4.md +30 -0
  75. package/deps/librdkafka/src/cJSON.c +3191 -0
  76. package/deps/librdkafka/src/cJSON.h +306 -0
  77. package/deps/librdkafka/src/crc32c.c +430 -0
  78. package/deps/librdkafka/src/crc32c.h +38 -0
  79. package/deps/librdkafka/src/generate_proto.sh +66 -0
  80. package/deps/librdkafka/src/librdkafka_cgrp_synch.png +0 -0
  81. package/deps/librdkafka/src/lz4.c +2727 -0
  82. package/deps/librdkafka/src/lz4.h +842 -0
  83. package/deps/librdkafka/src/lz4frame.c +2078 -0
  84. package/deps/librdkafka/src/lz4frame.h +692 -0
  85. package/deps/librdkafka/src/lz4frame_static.h +47 -0
  86. package/deps/librdkafka/src/lz4hc.c +1631 -0
  87. package/deps/librdkafka/src/lz4hc.h +413 -0
  88. package/deps/librdkafka/src/nanopb/pb.h +917 -0
  89. package/deps/librdkafka/src/nanopb/pb_common.c +388 -0
  90. package/deps/librdkafka/src/nanopb/pb_common.h +49 -0
  91. package/deps/librdkafka/src/nanopb/pb_decode.c +1727 -0
  92. package/deps/librdkafka/src/nanopb/pb_decode.h +193 -0
  93. package/deps/librdkafka/src/nanopb/pb_encode.c +1000 -0
  94. package/deps/librdkafka/src/nanopb/pb_encode.h +185 -0
  95. package/deps/librdkafka/src/opentelemetry/common.pb.c +32 -0
  96. package/deps/librdkafka/src/opentelemetry/common.pb.h +170 -0
  97. package/deps/librdkafka/src/opentelemetry/metrics.options +2 -0
  98. package/deps/librdkafka/src/opentelemetry/metrics.pb.c +67 -0
  99. package/deps/librdkafka/src/opentelemetry/metrics.pb.h +966 -0
  100. package/deps/librdkafka/src/opentelemetry/resource.pb.c +12 -0
  101. package/deps/librdkafka/src/opentelemetry/resource.pb.h +58 -0
  102. package/deps/librdkafka/src/queue.h +850 -0
  103. package/deps/librdkafka/src/rd.h +592 -0
  104. package/deps/librdkafka/src/rdaddr.c +255 -0
  105. package/deps/librdkafka/src/rdaddr.h +202 -0
  106. package/deps/librdkafka/src/rdatomic.h +262 -0
  107. package/deps/librdkafka/src/rdavg.h +260 -0
  108. package/deps/librdkafka/src/rdavl.c +210 -0
  109. package/deps/librdkafka/src/rdavl.h +250 -0
  110. package/deps/librdkafka/src/rdbase64.c +200 -0
  111. package/deps/librdkafka/src/rdbase64.h +43 -0
  112. package/deps/librdkafka/src/rdbuf.c +1884 -0
  113. package/deps/librdkafka/src/rdbuf.h +375 -0
  114. package/deps/librdkafka/src/rdcrc32.c +114 -0
  115. package/deps/librdkafka/src/rdcrc32.h +170 -0
  116. package/deps/librdkafka/src/rddl.c +179 -0
  117. package/deps/librdkafka/src/rddl.h +43 -0
  118. package/deps/librdkafka/src/rdendian.h +175 -0
  119. package/deps/librdkafka/src/rdfloat.h +67 -0
  120. package/deps/librdkafka/src/rdfnv1a.c +113 -0
  121. package/deps/librdkafka/src/rdfnv1a.h +35 -0
  122. package/deps/librdkafka/src/rdgz.c +120 -0
  123. package/deps/librdkafka/src/rdgz.h +46 -0
  124. package/deps/librdkafka/src/rdhdrhistogram.c +721 -0
  125. package/deps/librdkafka/src/rdhdrhistogram.h +87 -0
  126. package/deps/librdkafka/src/rdhttp.c +831 -0
  127. package/deps/librdkafka/src/rdhttp.h +101 -0
  128. package/deps/librdkafka/src/rdinterval.h +177 -0
  129. package/deps/librdkafka/src/rdkafka.c +7287 -0
  130. package/deps/librdkafka/src/rdkafka.h +11795 -0
  131. package/deps/librdkafka/src/rdkafka_admin.c +10165 -0
  132. package/deps/librdkafka/src/rdkafka_admin.h +658 -0
  133. package/deps/librdkafka/src/rdkafka_assignment.c +1282 -0
  134. package/deps/librdkafka/src/rdkafka_assignment.h +73 -0
  135. package/deps/librdkafka/src/rdkafka_assignor.c +1800 -0
  136. package/deps/librdkafka/src/rdkafka_assignor.h +402 -0
  137. package/deps/librdkafka/src/rdkafka_aux.c +409 -0
  138. package/deps/librdkafka/src/rdkafka_aux.h +174 -0
  139. package/deps/librdkafka/src/rdkafka_background.c +222 -0
  140. package/deps/librdkafka/src/rdkafka_broker.c +6736 -0
  141. package/deps/librdkafka/src/rdkafka_broker.h +851 -0
  142. package/deps/librdkafka/src/rdkafka_buf.c +543 -0
  143. package/deps/librdkafka/src/rdkafka_buf.h +1525 -0
  144. package/deps/librdkafka/src/rdkafka_cert.c +576 -0
  145. package/deps/librdkafka/src/rdkafka_cert.h +62 -0
  146. package/deps/librdkafka/src/rdkafka_cgrp.c +8310 -0
  147. package/deps/librdkafka/src/rdkafka_cgrp.h +589 -0
  148. package/deps/librdkafka/src/rdkafka_conf.c +5177 -0
  149. package/deps/librdkafka/src/rdkafka_conf.h +746 -0
  150. package/deps/librdkafka/src/rdkafka_confval.h +97 -0
  151. package/deps/librdkafka/src/rdkafka_coord.c +623 -0
  152. package/deps/librdkafka/src/rdkafka_coord.h +132 -0
  153. package/deps/librdkafka/src/rdkafka_error.c +228 -0
  154. package/deps/librdkafka/src/rdkafka_error.h +80 -0
  155. package/deps/librdkafka/src/rdkafka_event.c +502 -0
  156. package/deps/librdkafka/src/rdkafka_event.h +126 -0
  157. package/deps/librdkafka/src/rdkafka_feature.c +898 -0
  158. package/deps/librdkafka/src/rdkafka_feature.h +104 -0
  159. package/deps/librdkafka/src/rdkafka_fetcher.c +3820 -0
  160. package/deps/librdkafka/src/rdkafka_fetcher.h +62 -0
  161. package/deps/librdkafka/src/rdkafka_header.c +220 -0
  162. package/deps/librdkafka/src/rdkafka_header.h +76 -0
  163. package/deps/librdkafka/src/rdkafka_idempotence.c +807 -0
  164. package/deps/librdkafka/src/rdkafka_idempotence.h +144 -0
  165. package/deps/librdkafka/src/rdkafka_int.h +1505 -0
  166. package/deps/librdkafka/src/rdkafka_interceptor.c +842 -0
  167. package/deps/librdkafka/src/rdkafka_interceptor.h +104 -0
  168. package/deps/librdkafka/src/rdkafka_lz4.c +450 -0
  169. package/deps/librdkafka/src/rdkafka_lz4.h +49 -0
  170. package/deps/librdkafka/src/rdkafka_metadata.c +2347 -0
  171. package/deps/librdkafka/src/rdkafka_metadata.h +350 -0
  172. package/deps/librdkafka/src/rdkafka_metadata_cache.c +1183 -0
  173. package/deps/librdkafka/src/rdkafka_mock.c +4514 -0
  174. package/deps/librdkafka/src/rdkafka_mock.h +867 -0
  175. package/deps/librdkafka/src/rdkafka_mock_cgrp.c +1877 -0
  176. package/deps/librdkafka/src/rdkafka_mock_handlers.c +5255 -0
  177. package/deps/librdkafka/src/rdkafka_mock_int.h +1005 -0
  178. package/deps/librdkafka/src/rdkafka_mock_sharegrp.c +1207 -0
  179. package/deps/librdkafka/src/rdkafka_msg.c +2619 -0
  180. package/deps/librdkafka/src/rdkafka_msg.h +629 -0
  181. package/deps/librdkafka/src/rdkafka_msgbatch.h +62 -0
  182. package/deps/librdkafka/src/rdkafka_msgset.h +105 -0
  183. package/deps/librdkafka/src/rdkafka_msgset_reader.c +2099 -0
  184. package/deps/librdkafka/src/rdkafka_msgset_writer.c +1474 -0
  185. package/deps/librdkafka/src/rdkafka_offset.c +1565 -0
  186. package/deps/librdkafka/src/rdkafka_offset.h +150 -0
  187. package/deps/librdkafka/src/rdkafka_op.c +1219 -0
  188. package/deps/librdkafka/src/rdkafka_op.h +1065 -0
  189. package/deps/librdkafka/src/rdkafka_partition.c +5109 -0
  190. package/deps/librdkafka/src/rdkafka_partition.h +1215 -0
  191. package/deps/librdkafka/src/rdkafka_pattern.c +228 -0
  192. package/deps/librdkafka/src/rdkafka_pattern.h +70 -0
  193. package/deps/librdkafka/src/rdkafka_plugin.c +213 -0
  194. package/deps/librdkafka/src/rdkafka_plugin.h +41 -0
  195. package/deps/librdkafka/src/rdkafka_proto.h +739 -0
  196. package/deps/librdkafka/src/rdkafka_protocol.h +131 -0
  197. package/deps/librdkafka/src/rdkafka_queue.c +1351 -0
  198. package/deps/librdkafka/src/rdkafka_queue.h +1235 -0
  199. package/deps/librdkafka/src/rdkafka_range_assignor.c +1748 -0
  200. package/deps/librdkafka/src/rdkafka_request.c +7298 -0
  201. package/deps/librdkafka/src/rdkafka_request.h +761 -0
  202. package/deps/librdkafka/src/rdkafka_roundrobin_assignor.c +123 -0
  203. package/deps/librdkafka/src/rdkafka_sasl.c +589 -0
  204. package/deps/librdkafka/src/rdkafka_sasl.h +63 -0
  205. package/deps/librdkafka/src/rdkafka_sasl_cyrus.c +722 -0
  206. package/deps/librdkafka/src/rdkafka_sasl_int.h +89 -0
  207. package/deps/librdkafka/src/rdkafka_sasl_oauthbearer.c +1833 -0
  208. package/deps/librdkafka/src/rdkafka_sasl_oauthbearer.h +52 -0
  209. package/deps/librdkafka/src/rdkafka_sasl_oauthbearer_oidc.c +1827 -0
  210. package/deps/librdkafka/src/rdkafka_sasl_oauthbearer_oidc.h +52 -0
  211. package/deps/librdkafka/src/rdkafka_sasl_plain.c +142 -0
  212. package/deps/librdkafka/src/rdkafka_sasl_scram.c +858 -0
  213. package/deps/librdkafka/src/rdkafka_sasl_win32.c +550 -0
  214. package/deps/librdkafka/src/rdkafka_share_acknowledgement.c +1445 -0
  215. package/deps/librdkafka/src/rdkafka_share_acknowledgement.h +373 -0
  216. package/deps/librdkafka/src/rdkafka_ssl.c +2235 -0
  217. package/deps/librdkafka/src/rdkafka_ssl.h +86 -0
  218. package/deps/librdkafka/src/rdkafka_sticky_assignor.c +4785 -0
  219. package/deps/librdkafka/src/rdkafka_subscription.c +411 -0
  220. package/deps/librdkafka/src/rdkafka_telemetry.c +761 -0
  221. package/deps/librdkafka/src/rdkafka_telemetry.h +52 -0
  222. package/deps/librdkafka/src/rdkafka_telemetry_decode.c +1307 -0
  223. package/deps/librdkafka/src/rdkafka_telemetry_decode.h +59 -0
  224. package/deps/librdkafka/src/rdkafka_telemetry_encode.c +1309 -0
  225. package/deps/librdkafka/src/rdkafka_telemetry_encode.h +459 -0
  226. package/deps/librdkafka/src/rdkafka_timer.c +407 -0
  227. package/deps/librdkafka/src/rdkafka_timer.h +117 -0
  228. package/deps/librdkafka/src/rdkafka_topic.c +2382 -0
  229. package/deps/librdkafka/src/rdkafka_topic.h +369 -0
  230. package/deps/librdkafka/src/rdkafka_transport.c +1309 -0
  231. package/deps/librdkafka/src/rdkafka_transport.h +99 -0
  232. package/deps/librdkafka/src/rdkafka_transport_int.h +100 -0
  233. package/deps/librdkafka/src/rdkafka_txnmgr.c +3256 -0
  234. package/deps/librdkafka/src/rdkafka_txnmgr.h +171 -0
  235. package/deps/librdkafka/src/rdkafka_zstd.c +226 -0
  236. package/deps/librdkafka/src/rdkafka_zstd.h +57 -0
  237. package/deps/librdkafka/src/rdlist.c +593 -0
  238. package/deps/librdkafka/src/rdlist.h +444 -0
  239. package/deps/librdkafka/src/rdlog.c +89 -0
  240. package/deps/librdkafka/src/rdlog.h +41 -0
  241. package/deps/librdkafka/src/rdmap.c +508 -0
  242. package/deps/librdkafka/src/rdmap.h +492 -0
  243. package/deps/librdkafka/src/rdmurmur2.c +167 -0
  244. package/deps/librdkafka/src/rdmurmur2.h +35 -0
  245. package/deps/librdkafka/src/rdports.c +61 -0
  246. package/deps/librdkafka/src/rdports.h +38 -0
  247. package/deps/librdkafka/src/rdposix.h +250 -0
  248. package/deps/librdkafka/src/rdrand.c +151 -0
  249. package/deps/librdkafka/src/rdrand.h +72 -0
  250. package/deps/librdkafka/src/rdregex.c +156 -0
  251. package/deps/librdkafka/src/rdregex.h +43 -0
  252. package/deps/librdkafka/src/rdsignal.h +57 -0
  253. package/deps/librdkafka/src/rdstring.c +645 -0
  254. package/deps/librdkafka/src/rdstring.h +98 -0
  255. package/deps/librdkafka/src/rdsysqueue.h +404 -0
  256. package/deps/librdkafka/src/rdtime.h +356 -0
  257. package/deps/librdkafka/src/rdtypes.h +86 -0
  258. package/deps/librdkafka/src/rdunittest.c +564 -0
  259. package/deps/librdkafka/src/rdunittest.h +232 -0
  260. package/deps/librdkafka/src/rdunittest_acknowledge.c +1177 -0
  261. package/deps/librdkafka/src/rdunittest_fetcher.c +1211 -0
  262. package/deps/librdkafka/src/rdunittest_msgset_errors.c +1521 -0
  263. package/deps/librdkafka/src/rdvarint.c +134 -0
  264. package/deps/librdkafka/src/rdvarint.h +165 -0
  265. package/deps/librdkafka/src/rdwin32.h +382 -0
  266. package/deps/librdkafka/src/rdxxhash.c +1030 -0
  267. package/deps/librdkafka/src/rdxxhash.h +328 -0
  268. package/deps/librdkafka/src/regexp.c +1352 -0
  269. package/deps/librdkafka/src/regexp.h +41 -0
  270. package/deps/librdkafka/src/snappy.c +1866 -0
  271. package/deps/librdkafka/src/snappy.h +62 -0
  272. package/deps/librdkafka/src/snappy_compat.h +138 -0
  273. package/deps/librdkafka/src/statistics_schema.json +444 -0
  274. package/deps/librdkafka/src/tinycthread.c +932 -0
  275. package/deps/librdkafka/src/tinycthread.h +503 -0
  276. package/deps/librdkafka/src/tinycthread_extra.c +210 -0
  277. package/deps/librdkafka/src/tinycthread_extra.h +218 -0
  278. package/deps/librdkafka/src/win32_config.h +58 -0
  279. package/deps/librdkafka/src-cpp/CMakeLists.txt +90 -0
  280. package/deps/librdkafka/src-cpp/ConfImpl.cpp +84 -0
  281. package/deps/librdkafka/src-cpp/ConsumerImpl.cpp +244 -0
  282. package/deps/librdkafka/src-cpp/HandleImpl.cpp +436 -0
  283. package/deps/librdkafka/src-cpp/HeadersImpl.cpp +48 -0
  284. package/deps/librdkafka/src-cpp/KafkaConsumerImpl.cpp +296 -0
  285. package/deps/librdkafka/src-cpp/Makefile +55 -0
  286. package/deps/librdkafka/src-cpp/MessageImpl.cpp +38 -0
  287. package/deps/librdkafka/src-cpp/MetadataImpl.cpp +170 -0
  288. package/deps/librdkafka/src-cpp/ProducerImpl.cpp +197 -0
  289. package/deps/librdkafka/src-cpp/QueueImpl.cpp +70 -0
  290. package/deps/librdkafka/src-cpp/README.md +16 -0
  291. package/deps/librdkafka/src-cpp/RdKafka.cpp +59 -0
  292. package/deps/librdkafka/src-cpp/TopicImpl.cpp +124 -0
  293. package/deps/librdkafka/src-cpp/TopicPartitionImpl.cpp +57 -0
  294. package/deps/librdkafka/src-cpp/rdkafkacpp.h +3797 -0
  295. package/deps/librdkafka/src-cpp/rdkafkacpp_int.h +1641 -0
  296. package/deps/librdkafka/tests/0000-unittests.c +72 -0
  297. package/deps/librdkafka/tests/0001-multiobj.c +102 -0
  298. package/deps/librdkafka/tests/0002-unkpart.c +244 -0
  299. package/deps/librdkafka/tests/0003-msgmaxsize.c +173 -0
  300. package/deps/librdkafka/tests/0004-conf.c +934 -0
  301. package/deps/librdkafka/tests/0005-order.c +133 -0
  302. package/deps/librdkafka/tests/0006-symbols.c +163 -0
  303. package/deps/librdkafka/tests/0007-autotopic.c +136 -0
  304. package/deps/librdkafka/tests/0008-reqacks.c +179 -0
  305. package/deps/librdkafka/tests/0009-mock_cluster.c +272 -0
  306. package/deps/librdkafka/tests/0011-produce_batch.c +753 -0
  307. package/deps/librdkafka/tests/0012-produce_consume.c +537 -0
  308. package/deps/librdkafka/tests/0013-null-msgs.c +473 -0
  309. package/deps/librdkafka/tests/0014-reconsume-191.c +512 -0
  310. package/deps/librdkafka/tests/0015-offset_seeks.c +172 -0
  311. package/deps/librdkafka/tests/0016-client_swname.c +181 -0
  312. package/deps/librdkafka/tests/0017-compression.c +140 -0
  313. package/deps/librdkafka/tests/0018-cgrp_term.c +444 -0
  314. package/deps/librdkafka/tests/0019-list_groups.c +289 -0
  315. package/deps/librdkafka/tests/0020-destroy_hang.c +162 -0
  316. package/deps/librdkafka/tests/0021-rkt_destroy.c +72 -0
  317. package/deps/librdkafka/tests/0022-consume_batch.c +331 -0
  318. package/deps/librdkafka/tests/0025-timers.c +147 -0
  319. package/deps/librdkafka/tests/0026-consume_pause.c +547 -0
  320. package/deps/librdkafka/tests/0028-long_topicnames.c +79 -0
  321. package/deps/librdkafka/tests/0029-assign_offset.c +202 -0
  322. package/deps/librdkafka/tests/0030-offset_commit.c +589 -0
  323. package/deps/librdkafka/tests/0031-get_offsets.c +235 -0
  324. package/deps/librdkafka/tests/0033-regex_subscribe.c +536 -0
  325. package/deps/librdkafka/tests/0034-offset_reset.c +398 -0
  326. package/deps/librdkafka/tests/0035-api_version.c +73 -0
  327. package/deps/librdkafka/tests/0036-partial_fetch.c +87 -0
  328. package/deps/librdkafka/tests/0037-destroy_hang_local.c +85 -0
  329. package/deps/librdkafka/tests/0038-performance.c +121 -0
  330. package/deps/librdkafka/tests/0039-event.c +284 -0
  331. package/deps/librdkafka/tests/0040-io_event.c +257 -0
  332. package/deps/librdkafka/tests/0041-fetch_max_bytes.c +97 -0
  333. package/deps/librdkafka/tests/0042-many_topics.c +252 -0
  334. package/deps/librdkafka/tests/0043-no_connection.c +77 -0
  335. package/deps/librdkafka/tests/0044-partition_cnt.c +94 -0
  336. package/deps/librdkafka/tests/0045-subscribe_update.c +1010 -0
  337. package/deps/librdkafka/tests/0046-rkt_cache.c +65 -0
  338. package/deps/librdkafka/tests/0047-partial_buf_tmout.c +98 -0
  339. package/deps/librdkafka/tests/0048-partitioner.c +283 -0
  340. package/deps/librdkafka/tests/0049-consume_conn_close.c +162 -0
  341. package/deps/librdkafka/tests/0050-subscribe_adds.c +145 -0
  342. package/deps/librdkafka/tests/0051-assign_adds.c +126 -0
  343. package/deps/librdkafka/tests/0052-msg_timestamps.c +238 -0
  344. package/deps/librdkafka/tests/0053-stats_cb.cpp +527 -0
  345. package/deps/librdkafka/tests/0054-offset_time.cpp +236 -0
  346. package/deps/librdkafka/tests/0055-producer_latency.c +539 -0
  347. package/deps/librdkafka/tests/0056-balanced_group_mt.c +315 -0
  348. package/deps/librdkafka/tests/0057-invalid_topic.cpp +112 -0
  349. package/deps/librdkafka/tests/0058-log.cpp +123 -0
  350. package/deps/librdkafka/tests/0059-bsearch.cpp +241 -0
  351. package/deps/librdkafka/tests/0060-op_prio.cpp +163 -0
  352. package/deps/librdkafka/tests/0061-consumer_lag.cpp +295 -0
  353. package/deps/librdkafka/tests/0062-stats_event.c +126 -0
  354. package/deps/librdkafka/tests/0063-clusterid.cpp +180 -0
  355. package/deps/librdkafka/tests/0064-interceptors.c +529 -0
  356. package/deps/librdkafka/tests/0065-yield.cpp +140 -0
  357. package/deps/librdkafka/tests/0066-plugins.cpp +143 -0
  358. package/deps/librdkafka/tests/0067-empty_topic.cpp +151 -0
  359. package/deps/librdkafka/tests/0068-produce_timeout.c +136 -0
  360. package/deps/librdkafka/tests/0069-consumer_add_parts.c +119 -0
  361. package/deps/librdkafka/tests/0070-null_empty.cpp +197 -0
  362. package/deps/librdkafka/tests/0072-headers_ut.c +448 -0
  363. package/deps/librdkafka/tests/0073-headers.c +381 -0
  364. package/deps/librdkafka/tests/0074-producev.c +87 -0
  365. package/deps/librdkafka/tests/0075-retry.c +290 -0
  366. package/deps/librdkafka/tests/0076-produce_retry.c +452 -0
  367. package/deps/librdkafka/tests/0077-compaction.c +366 -0
  368. package/deps/librdkafka/tests/0078-c_from_cpp.cpp +96 -0
  369. package/deps/librdkafka/tests/0079-fork.c +93 -0
  370. package/deps/librdkafka/tests/0080-admin_ut.c +3095 -0
  371. package/deps/librdkafka/tests/0081-admin.c +5633 -0
  372. package/deps/librdkafka/tests/0082-fetch_max_bytes.cpp +137 -0
  373. package/deps/librdkafka/tests/0083-cb_event.c +233 -0
  374. package/deps/librdkafka/tests/0084-destroy_flags.c +208 -0
  375. package/deps/librdkafka/tests/0085-headers.cpp +392 -0
  376. package/deps/librdkafka/tests/0086-purge.c +368 -0
  377. package/deps/librdkafka/tests/0088-produce_metadata_timeout.c +162 -0
  378. package/deps/librdkafka/tests/0089-max_poll_interval.c +511 -0
  379. package/deps/librdkafka/tests/0090-idempotence.c +171 -0
  380. package/deps/librdkafka/tests/0091-max_poll_interval_timeout.c +295 -0
  381. package/deps/librdkafka/tests/0092-mixed_msgver.c +103 -0
  382. package/deps/librdkafka/tests/0093-holb.c +200 -0
  383. package/deps/librdkafka/tests/0094-idempotence_msg_timeout.c +231 -0
  384. package/deps/librdkafka/tests/0095-all_brokers_down.cpp +122 -0
  385. package/deps/librdkafka/tests/0097-ssl_verify.cpp +674 -0
  386. package/deps/librdkafka/tests/0098-consumer-txn.cpp +1218 -0
  387. package/deps/librdkafka/tests/0099-commit_metadata.c +194 -0
  388. package/deps/librdkafka/tests/0100-thread_interceptors.cpp +195 -0
  389. package/deps/librdkafka/tests/0101-fetch-from-follower.cpp +446 -0
  390. package/deps/librdkafka/tests/0102-static_group_rebalance.c +836 -0
  391. package/deps/librdkafka/tests/0103-transactions.c +1383 -0
  392. package/deps/librdkafka/tests/0104-fetch_from_follower_mock.c +625 -0
  393. package/deps/librdkafka/tests/0105-transactions_mock.c +3930 -0
  394. package/deps/librdkafka/tests/0106-cgrp_sess_timeout.c +318 -0
  395. package/deps/librdkafka/tests/0107-topic_recreate.c +259 -0
  396. package/deps/librdkafka/tests/0109-auto_create_topics.cpp +278 -0
  397. package/deps/librdkafka/tests/0110-batch_size.cpp +182 -0
  398. package/deps/librdkafka/tests/0111-delay_create_topics.cpp +127 -0
  399. package/deps/librdkafka/tests/0112-assign_unknown_part.c +87 -0
  400. package/deps/librdkafka/tests/0113-cooperative_rebalance.cpp +3473 -0
  401. package/deps/librdkafka/tests/0114-sticky_partitioning.cpp +176 -0
  402. package/deps/librdkafka/tests/0115-producer_auth.cpp +182 -0
  403. package/deps/librdkafka/tests/0116-kafkaconsumer_close.cpp +216 -0
  404. package/deps/librdkafka/tests/0117-mock_errors.c +331 -0
  405. package/deps/librdkafka/tests/0118-commit_rebalance.c +154 -0
  406. package/deps/librdkafka/tests/0119-consumer_auth.cpp +167 -0
  407. package/deps/librdkafka/tests/0120-asymmetric_subscription.c +185 -0
  408. package/deps/librdkafka/tests/0121-clusterid.c +115 -0
  409. package/deps/librdkafka/tests/0122-buffer_cleaning_after_rebalance.c +227 -0
  410. package/deps/librdkafka/tests/0123-connections_max_idle.c +98 -0
  411. package/deps/librdkafka/tests/0124-openssl_invalid_engine.c +69 -0
  412. package/deps/librdkafka/tests/0125-immediate_flush.c +144 -0
  413. package/deps/librdkafka/tests/0126-oauthbearer_oidc.c +1164 -0
  414. package/deps/librdkafka/tests/0127-fetch_queue_backoff.cpp +165 -0
  415. package/deps/librdkafka/tests/0128-sasl_callback_queue.cpp +217 -0
  416. package/deps/librdkafka/tests/0129-fetch_aborted_msgs.c +79 -0
  417. package/deps/librdkafka/tests/0130-store_offsets.c +178 -0
  418. package/deps/librdkafka/tests/0131-connect_timeout.c +81 -0
  419. package/deps/librdkafka/tests/0132-strategy_ordering.c +179 -0
  420. package/deps/librdkafka/tests/0133-ssl_keys.c +150 -0
  421. package/deps/librdkafka/tests/0134-ssl_provider.c +92 -0
  422. package/deps/librdkafka/tests/0135-sasl_credentials.cpp +279 -0
  423. package/deps/librdkafka/tests/0136-resolve_cb.c +181 -0
  424. package/deps/librdkafka/tests/0137-barrier_batch_consume.c +752 -0
  425. package/deps/librdkafka/tests/0138-admin_mock.c +281 -0
  426. package/deps/librdkafka/tests/0139-offset_validation_mock.c +950 -0
  427. package/deps/librdkafka/tests/0140-commit_metadata.cpp +108 -0
  428. package/deps/librdkafka/tests/0142-reauthentication.c +931 -0
  429. package/deps/librdkafka/tests/0143-exponential_backoff_mock.c +552 -0
  430. package/deps/librdkafka/tests/0144-idempotence_mock.c +373 -0
  431. package/deps/librdkafka/tests/0145-pause_resume_mock.c +119 -0
  432. package/deps/librdkafka/tests/0146-metadata_mock.c +505 -0
  433. package/deps/librdkafka/tests/0147-consumer_group_consumer_mock.c +1210 -0
  434. package/deps/librdkafka/tests/0148-offset_fetch_commit_error_mock.c +569 -0
  435. package/deps/librdkafka/tests/0149-broker-same-host-port.c +140 -0
  436. package/deps/librdkafka/tests/0150-telemetry_mock.c +651 -0
  437. package/deps/librdkafka/tests/0151-purge-brokers.c +566 -0
  438. package/deps/librdkafka/tests/0152-rebootstrap.c +59 -0
  439. package/deps/librdkafka/tests/0153-memberid.c +128 -0
  440. package/deps/librdkafka/tests/0155-share_group_heartbeat_mock.c +2695 -0
  441. package/deps/librdkafka/tests/0156-share_consumer_fetch_mock.c +1551 -0
  442. package/deps/librdkafka/tests/0157-share_consumer_ack_mock.c +1788 -0
  443. package/deps/librdkafka/tests/0158-share_consumer_transactions_mock.c +467 -0
  444. package/deps/librdkafka/tests/0170-share_consumer_subscription.c +2192 -0
  445. package/deps/librdkafka/tests/0171-share_consumer_consume.c +2150 -0
  446. package/deps/librdkafka/tests/0172-share_consumer_acknowledge.c +1919 -0
  447. package/deps/librdkafka/tests/0173-share_consumer_commit_async.c +2735 -0
  448. package/deps/librdkafka/tests/0174-share_consumer_concurrency.c +1434 -0
  449. package/deps/librdkafka/tests/0175-share_consumer_groups.c +574 -0
  450. package/deps/librdkafka/tests/0176-share_consumer_commit_sync.c +2986 -0
  451. package/deps/librdkafka/tests/0177-share_consumer_transactions.c +1815 -0
  452. package/deps/librdkafka/tests/0178-share_consumer_close.c +2102 -0
  453. package/deps/librdkafka/tests/0179-share_consumer_destroy.c +2482 -0
  454. package/deps/librdkafka/tests/0180-share_consumer_config.c +1677 -0
  455. package/deps/librdkafka/tests/0181-share_consumer_topic_delete.c +354 -0
  456. package/deps/librdkafka/tests/0182-share_consumer_error_handling_mock.c +3358 -0
  457. package/deps/librdkafka/tests/0183-share_consumer_leader_change_mock.c +1234 -0
  458. package/deps/librdkafka/tests/0184-share_consumer_topic_recreate.c +1356 -0
  459. package/deps/librdkafka/tests/0185-share_consumer_max_poll_interval.c +557 -0
  460. package/deps/librdkafka/tests/0186-share_consumer_fatal_error.c +360 -0
  461. package/deps/librdkafka/tests/0190-share_consumer_telemetry.c +387 -0
  462. package/deps/librdkafka/tests/1000-unktopic.c +164 -0
  463. package/deps/librdkafka/tests/8000-idle.cpp +60 -0
  464. package/deps/librdkafka/tests/8001-fetch_from_follower_mock_manual.c +113 -0
  465. package/deps/librdkafka/tests/CMakeLists.txt +193 -0
  466. package/deps/librdkafka/tests/LibrdkafkaTestApp.py +291 -0
  467. package/deps/librdkafka/tests/Makefile +182 -0
  468. package/deps/librdkafka/tests/README.md +514 -0
  469. package/deps/librdkafka/tests/autotest.sh +33 -0
  470. package/deps/librdkafka/tests/backtrace.gdb +30 -0
  471. package/deps/librdkafka/tests/broker_version_tests.py +315 -0
  472. package/deps/librdkafka/tests/buildbox.sh +17 -0
  473. package/deps/librdkafka/tests/cleanup-checker-tests.sh +20 -0
  474. package/deps/librdkafka/tests/cluster_testing.py +203 -0
  475. package/deps/librdkafka/tests/delete-test-topics.sh +56 -0
  476. package/deps/librdkafka/tests/fixtures/oauthbearer/jwt_assertion_template.json +10 -0
  477. package/deps/librdkafka/tests/fixtures/ssl/Makefile +8 -0
  478. package/deps/librdkafka/tests/fixtures/ssl/README.md +13 -0
  479. package/deps/librdkafka/tests/fixtures/ssl/client.keystore.intermediate.p12 +0 -0
  480. package/deps/librdkafka/tests/fixtures/ssl/client.keystore.p12 +0 -0
  481. package/deps/librdkafka/tests/fixtures/ssl/client2.certificate.intermediate.pem +72 -0
  482. package/deps/librdkafka/tests/fixtures/ssl/client2.certificate.pem +50 -0
  483. package/deps/librdkafka/tests/fixtures/ssl/client2.intermediate.key +46 -0
  484. package/deps/librdkafka/tests/fixtures/ssl/client2.key +46 -0
  485. package/deps/librdkafka/tests/fixtures/ssl/create_keys.sh +168 -0
  486. package/deps/librdkafka/tests/fuzzers/Makefile +12 -0
  487. package/deps/librdkafka/tests/fuzzers/README.md +31 -0
  488. package/deps/librdkafka/tests/fuzzers/fuzz_regex.c +74 -0
  489. package/deps/librdkafka/tests/fuzzers/helpers.h +90 -0
  490. package/deps/librdkafka/tests/gen-ssl-certs.sh +165 -0
  491. package/deps/librdkafka/tests/interactive_broker_version.py +170 -0
  492. package/deps/librdkafka/tests/interceptor_test/CMakeLists.txt +16 -0
  493. package/deps/librdkafka/tests/interceptor_test/Makefile +22 -0
  494. package/deps/librdkafka/tests/interceptor_test/interceptor_test.c +314 -0
  495. package/deps/librdkafka/tests/interceptor_test/interceptor_test.h +54 -0
  496. package/deps/librdkafka/tests/java/IncrementalRebalanceCli.java +97 -0
  497. package/deps/librdkafka/tests/java/Makefile +13 -0
  498. package/deps/librdkafka/tests/java/Murmur2Cli.java +46 -0
  499. package/deps/librdkafka/tests/java/README.md +14 -0
  500. package/deps/librdkafka/tests/java/TransactionProducerCli.java +162 -0
  501. package/deps/librdkafka/tests/java/run-class.sh +11 -0
  502. package/deps/librdkafka/tests/librdkafka.suppressions +483 -0
  503. package/deps/librdkafka/tests/lz4_manual_test.sh +59 -0
  504. package/deps/librdkafka/tests/multi-broker-version-test.sh +50 -0
  505. package/deps/librdkafka/tests/parse-refcnt.sh +43 -0
  506. package/deps/librdkafka/tests/performance_plot.py +115 -0
  507. package/deps/librdkafka/tests/plugin_test/Makefile +19 -0
  508. package/deps/librdkafka/tests/plugin_test/plugin_test.c +58 -0
  509. package/deps/librdkafka/tests/requirements.txt +2 -0
  510. package/deps/librdkafka/tests/run-all-tests.sh +81 -0
  511. package/deps/librdkafka/tests/run-consumer-tests.sh +16 -0
  512. package/deps/librdkafka/tests/run-producer-tests.sh +16 -0
  513. package/deps/librdkafka/tests/run-test-batches.py +157 -0
  514. package/deps/librdkafka/tests/run-test.sh +141 -0
  515. package/deps/librdkafka/tests/rusage.c +249 -0
  516. package/deps/librdkafka/tests/sasl_test.py +289 -0
  517. package/deps/librdkafka/tests/scenarios/README.md +6 -0
  518. package/deps/librdkafka/tests/scenarios/ak23.json +6 -0
  519. package/deps/librdkafka/tests/scenarios/default.json +5 -0
  520. package/deps/librdkafka/tests/scenarios/noautocreate.json +5 -0
  521. package/deps/librdkafka/tests/share_telemetry_verify/share-otel-collector-config.yaml +28 -0
  522. package/deps/librdkafka/tests/sockem.c +801 -0
  523. package/deps/librdkafka/tests/sockem.h +85 -0
  524. package/deps/librdkafka/tests/sockem_ctrl.c +145 -0
  525. package/deps/librdkafka/tests/sockem_ctrl.h +61 -0
  526. package/deps/librdkafka/tests/test.c +8376 -0
  527. package/deps/librdkafka/tests/test.conf.example +27 -0
  528. package/deps/librdkafka/tests/test.h +1150 -0
  529. package/deps/librdkafka/tests/testcpp.cpp +131 -0
  530. package/deps/librdkafka/tests/testcpp.h +388 -0
  531. package/deps/librdkafka/tests/testshared.h +416 -0
  532. package/deps/librdkafka/tests/tools/README.md +4 -0
  533. package/deps/librdkafka/tests/tools/stats/README.md +21 -0
  534. package/deps/librdkafka/tests/tools/stats/filter.jq +42 -0
  535. package/deps/librdkafka/tests/tools/stats/graph.py +150 -0
  536. package/deps/librdkafka/tests/tools/stats/requirements.txt +3 -0
  537. package/deps/librdkafka/tests/tools/stats/to_csv.py +124 -0
  538. package/deps/librdkafka/tests/trivup/trivup-0.15.0.tar.gz +0 -0
  539. package/deps/librdkafka/tests/until-fail.sh +87 -0
  540. package/deps/librdkafka/tests/xxxx-assign_partition.c +122 -0
  541. package/deps/librdkafka/tests/xxxx-metadata.cpp +159 -0
  542. package/deps/librdkafka/vcpkg.json +23 -0
  543. package/deps/librdkafka/win32/README.md +5 -0
  544. package/deps/librdkafka/win32/build-package.bat +3 -0
  545. package/deps/librdkafka/win32/build.bat +19 -0
  546. package/deps/librdkafka/win32/common.vcxproj +84 -0
  547. package/deps/librdkafka/win32/interceptor_test/interceptor_test.vcxproj +87 -0
  548. package/deps/librdkafka/win32/librdkafka.autopkg.template +54 -0
  549. package/deps/librdkafka/win32/librdkafka.master.testing.targets +13 -0
  550. package/deps/librdkafka/win32/librdkafka.sln +226 -0
  551. package/deps/librdkafka/win32/librdkafka.vcxproj +282 -0
  552. package/deps/librdkafka/win32/librdkafkacpp/librdkafkacpp.vcxproj +104 -0
  553. package/deps/librdkafka/win32/msbuild.ps1 +58 -0
  554. package/deps/librdkafka/win32/openssl_engine_example/openssl_engine_example.vcxproj +132 -0
  555. package/deps/librdkafka/win32/package-zip.ps1 +46 -0
  556. package/deps/librdkafka/win32/packages/repositories.config +4 -0
  557. package/deps/librdkafka/win32/push-package.bat +4 -0
  558. package/deps/librdkafka/win32/rdkafka_complex_consumer_example_cpp/rdkafka_complex_consumer_example_cpp.vcxproj +67 -0
  559. package/deps/librdkafka/win32/rdkafka_example/rdkafka_example.vcxproj +97 -0
  560. package/deps/librdkafka/win32/rdkafka_performance/rdkafka_performance.vcxproj +97 -0
  561. package/deps/librdkafka/win32/setup-msys2.ps1 +47 -0
  562. package/deps/librdkafka/win32/setup-vcpkg.ps1 +34 -0
  563. package/deps/librdkafka/win32/tests/test.conf.example +25 -0
  564. package/deps/librdkafka/win32/tests/tests.vcxproj +278 -0
  565. package/deps/librdkafka/win32/win_ssl_cert_store/win_ssl_cert_store.vcxproj +132 -0
  566. package/deps/librdkafka/win32/wingetopt.c +564 -0
  567. package/deps/librdkafka/win32/wingetopt.h +101 -0
  568. package/deps/librdkafka/win32/wintime.h +33 -0
  569. package/deps/librdkafka.gyp +83 -0
  570. package/deps/windows-install.py +80 -0
  571. package/index.d.ts +3 -0
  572. package/lib/admin.js +732 -0
  573. package/lib/client.js +679 -0
  574. package/lib/error.js +501 -0
  575. package/lib/index.js +8 -0
  576. package/lib/kafka-consumer-stream.js +399 -0
  577. package/lib/kafka-consumer.js +798 -0
  578. package/lib/kafkajs/_admin.js +1052 -0
  579. package/lib/kafkajs/_common.js +887 -0
  580. package/lib/kafkajs/_consumer.js +2156 -0
  581. package/lib/kafkajs/_consumer_cache.js +310 -0
  582. package/lib/kafkajs/_error.js +298 -0
  583. package/lib/kafkajs/_kafka.js +125 -0
  584. package/lib/kafkajs/_linked-list.js +220 -0
  585. package/lib/kafkajs/_producer.js +863 -0
  586. package/lib/kafkajs/index.js +5 -0
  587. package/lib/producer/high-level-producer.js +400 -0
  588. package/lib/producer-stream.js +337 -0
  589. package/lib/producer.js +405 -0
  590. package/lib/rdkafka.js +44 -0
  591. package/lib/tools/ref-counter.js +53 -0
  592. package/lib/topic-partition.js +91 -0
  593. package/lib/topic.js +43 -0
  594. package/lib/util.js +55 -0
  595. package/librdkafka.js +12 -0
  596. package/package.json +85 -0
  597. package/shim/source_location +54 -0
  598. package/src/admin.cc +1597 -0
  599. package/src/admin.h +112 -0
  600. package/src/binding.cc +76 -0
  601. package/src/binding.h +25 -0
  602. package/src/callbacks.cc +676 -0
  603. package/src/callbacks.h +302 -0
  604. package/src/common.cc +1628 -0
  605. package/src/common.h +192 -0
  606. package/src/config.cc +219 -0
  607. package/src/config.h +61 -0
  608. package/src/connection.cc +703 -0
  609. package/src/connection.h +117 -0
  610. package/src/errors.cc +119 -0
  611. package/src/errors.h +60 -0
  612. package/src/kafka-consumer.cc +1539 -0
  613. package/src/kafka-consumer.h +147 -0
  614. package/src/producer.cc +917 -0
  615. package/src/producer.h +124 -0
  616. package/src/topic.cc +173 -0
  617. package/src/topic.h +54 -0
  618. package/src/workers.cc +1714 -0
  619. package/src/workers.h +690 -0
  620. package/types/config.d.ts +1269 -0
  621. package/types/errors.d.ts +373 -0
  622. package/types/kafkajs.d.ts +568 -0
  623. package/types/rdkafka.d.ts +571 -0
  624. package/util/configure.js +32 -0
  625. package/util/generate-docs.sh +18 -0
  626. package/util/get-env.js +6 -0
  627. package/util/test-compile.js +11 -0
  628. 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
+ }