@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,2695 @@
1
+ #include "test.h"
2
+
3
+ #include "../src/rdkafka_proto.h"
4
+
5
+ /**
6
+ * @name Mock tests for share consumer and ShareGroupHeartbeat
7
+ *
8
+ * Note: rd_kafka_assignment() and rd_kafka_fatal_error() are called via
9
+ * test_share_consumer_get_rk() to access the underlying rd_kafka_t
10
+ * handle from the share consumer.
11
+ */
12
+
13
+ static rd_bool_t is_share_heartbeat_request(rd_kafka_mock_request_t *request,
14
+ void *opaque) {
15
+ return rd_kafka_mock_request_api_key(request) ==
16
+ RD_KAFKAP_ShareGroupHeartbeat;
17
+ }
18
+
19
+ /**
20
+ * @brief Wait for at least \p num ShareGroupHeartbeat requests
21
+ * to be received by the mock cluster.
22
+ *
23
+ * @return Number of heartbeats received.
24
+ */
25
+ static int wait_share_heartbeats(rd_kafka_mock_cluster_t *mcluster,
26
+ int num,
27
+ int confidence_interval) {
28
+ return test_mock_wait_matching_requests(
29
+ mcluster, num, confidence_interval, is_share_heartbeat_request,
30
+ NULL);
31
+ }
32
+
33
+ /**
34
+ * @brief Create a share consumer connected to mock cluster.
35
+ */
36
+ static rd_kafka_share_t *create_share_consumer(const char *bootstraps,
37
+ const char *group_id) {
38
+ rd_kafka_conf_t *conf;
39
+ rd_kafka_share_t *rkshare;
40
+ char errstr[512];
41
+
42
+ test_conf_init(&conf, NULL, 0);
43
+ test_conf_set(conf, "bootstrap.servers", bootstraps);
44
+ test_conf_set(conf, "group.id", group_id);
45
+
46
+ rkshare = rd_kafka_share_consumer_new(conf, errstr, sizeof(errstr));
47
+ TEST_ASSERT(rkshare != NULL, "Failed to create share consumer: %s",
48
+ errstr);
49
+
50
+ return rkshare;
51
+ }
52
+
53
+ /**
54
+ * @brief Poll rd_kafka_share_poll() until it returns a fatal
55
+ * error or \p timeout_ms elapses.
56
+ *
57
+ * While waiting for the fatal error no records are expected, and any
58
+ * error returned must be fatal; both are asserted.
59
+ *
60
+ * @return The fatal rd_kafka_error_t* (caller owns it and must destroy it),
61
+ * or NULL if the timeout elapsed without any error.
62
+ */
63
+ static rd_kafka_error_t *wait_fatal_error(rd_kafka_share_t *share_c,
64
+ int timeout_ms) {
65
+ int64_t deadline = test_clock() + (int64_t)timeout_ms * 1000;
66
+ rd_kafka_messages_t *batch = NULL;
67
+ size_t rcvd;
68
+ rd_kafka_error_t *error;
69
+
70
+ while (test_clock() < deadline) {
71
+ error = rd_kafka_share_poll(share_c, 100, &batch);
72
+ rcvd = rd_kafka_messages_count(batch);
73
+
74
+ TEST_ASSERT(rcvd == 0,
75
+ "Expected no records while waiting for fatal "
76
+ "error, got %d",
77
+ (int)rcvd);
78
+
79
+ rd_kafka_messages_destroy(batch);
80
+ batch = NULL;
81
+
82
+ if (error) {
83
+ TEST_ASSERT(rd_kafka_error_is_fatal(error),
84
+ "Expected a fatal error, got non-fatal %s",
85
+ rd_kafka_error_name(error));
86
+ return error;
87
+ }
88
+ }
89
+ return NULL;
90
+ }
91
+
92
+ static int count_topic_partitions(rd_kafka_topic_partition_list_t *assignment,
93
+ const char *topic) {
94
+ int i, count = 0;
95
+ for (i = 0; i < assignment->cnt; i++) {
96
+ if (strcmp(assignment->elems[i].topic, topic) == 0)
97
+ count++;
98
+ }
99
+ return count;
100
+ }
101
+
102
+ /**
103
+ * @brief Wait until rd_kafka_assignment() returns exactly
104
+ * \p expected_cnt partitions, or \p timeout_ms elapses.
105
+ *
106
+ * The heartbeat thread updates assignments independently,
107
+ * so no polling is needed.
108
+ *
109
+ * @return The final assignment count.
110
+ */
111
+ static int wait_assignment_count(rd_kafka_share_t *share_c,
112
+ int expected_cnt,
113
+ int timeout_ms) {
114
+ int64_t deadline = test_clock() + (int64_t)timeout_ms * 1000;
115
+ int cnt = -1;
116
+
117
+ while (test_clock() < deadline) {
118
+ rd_kafka_topic_partition_list_t *assignment;
119
+
120
+ TEST_CALL_ERR__(rd_kafka_assignment(
121
+ test_share_consumer_get_rk(share_c), &assignment));
122
+ cnt = assignment->cnt;
123
+ rd_kafka_topic_partition_list_destroy(assignment);
124
+
125
+ if (cnt == expected_cnt)
126
+ return cnt;
127
+
128
+ rd_usleep(500 * 1000, 0);
129
+ }
130
+ return cnt;
131
+ }
132
+
133
+
134
+ /**
135
+ * @brief Test basic ShareGroupHeartbeat flow:
136
+ * join, receive assignment, heartbeats, leave.
137
+ */
138
+ static void do_test_share_group_heartbeat_basic(void) {
139
+ rd_kafka_mock_cluster_t *mcluster;
140
+ const char *bootstraps;
141
+ rd_kafka_topic_partition_list_t *subscription;
142
+ rd_kafka_share_t *share_c;
143
+ int found_heartbeats, cnt;
144
+ const char *topic = test_mk_topic_name(__FUNCTION__, 0);
145
+ const char *group = "test-share-group";
146
+
147
+ SUB_TEST_QUICK();
148
+
149
+ /* Setup */
150
+ mcluster = test_mock_cluster_new(1, &bootstraps);
151
+ rd_kafka_mock_topic_create(mcluster, topic, 3, 1);
152
+
153
+ share_c = create_share_consumer(bootstraps, group);
154
+
155
+ subscription = rd_kafka_topic_partition_list_new(1);
156
+ rd_kafka_topic_partition_list_add(subscription, topic,
157
+ RD_KAFKA_PARTITION_UA);
158
+
159
+ rd_kafka_mock_start_request_tracking(mcluster);
160
+ TEST_CALL_ERR__(rd_kafka_share_subscribe(share_c, subscription));
161
+ rd_kafka_topic_partition_list_destroy(subscription);
162
+
163
+ /* Wait for join heartbeat */
164
+ found_heartbeats = wait_share_heartbeats(mcluster, 1, 1000);
165
+ TEST_ASSERT(found_heartbeats >= 1,
166
+ "Expected at least 1 heartbeat, got %d", found_heartbeats);
167
+
168
+ /* Wait until assignment propagates */
169
+ cnt = wait_assignment_count(share_c, 3, 10000);
170
+ TEST_ASSERT(cnt == 3, "Expected 3 partitions assigned, got %d", cnt);
171
+
172
+ /* Verify multiple heartbeats */
173
+ found_heartbeats = wait_share_heartbeats(mcluster, 2, 1000);
174
+ TEST_ASSERT(found_heartbeats >= 2,
175
+ "Expected at least 2 heartbeats, got %d", found_heartbeats);
176
+
177
+ /* Close consumer (sends leave heartbeat) */
178
+ test_share_consumer_close(share_c);
179
+ test_share_destroy(share_c);
180
+
181
+ /* Verify leave heartbeat was sent */
182
+ found_heartbeats = wait_share_heartbeats(mcluster, 3, 1000);
183
+
184
+ /* Verify no more heartbeats after leave.
185
+ * Use a generous sleep (5s) and confidence interval (1000ms)
186
+ * to avoid false positives under CPU contention. */
187
+ rd_kafka_mock_stop_request_tracking(mcluster);
188
+ rd_kafka_mock_start_request_tracking(mcluster);
189
+ rd_sleep(5);
190
+ found_heartbeats = wait_share_heartbeats(mcluster, 0, 1000);
191
+ TEST_ASSERT(found_heartbeats == 0,
192
+ "Expected 0 heartbeats after leave, got %d",
193
+ found_heartbeats);
194
+
195
+ rd_kafka_mock_stop_request_tracking(mcluster);
196
+ test_mock_cluster_destroy(mcluster);
197
+
198
+ SUB_TEST_PASS();
199
+ }
200
+
201
+ /**
202
+ * @brief Test assignment redistribution when consumers join/leave.
203
+ */
204
+ static void do_test_share_group_assignment_rebalance(void) {
205
+ rd_kafka_mock_cluster_t *mcluster;
206
+ const char *bootstraps;
207
+ rd_kafka_topic_partition_list_t *subscription;
208
+ rd_kafka_topic_partition_list_t *share_c1_assignment,
209
+ *share_c2_assignment;
210
+ rd_kafka_share_t *share_c1, *share_c2;
211
+ int64_t deadline;
212
+ int cnt;
213
+ const char *topic = test_mk_topic_name(__FUNCTION__, 0);
214
+ const char *group = "test-share-group-rebalance";
215
+
216
+ SUB_TEST_QUICK();
217
+
218
+ /* Setup */
219
+ mcluster = test_mock_cluster_new(1, &bootstraps);
220
+ rd_kafka_mock_topic_create(mcluster, topic, 3, 1);
221
+
222
+ share_c1 = create_share_consumer(bootstraps, group);
223
+
224
+ subscription = rd_kafka_topic_partition_list_new(1);
225
+ rd_kafka_topic_partition_list_add(subscription, topic,
226
+ RD_KAFKA_PARTITION_UA);
227
+
228
+ rd_kafka_mock_start_request_tracking(mcluster);
229
+ TEST_CALL_ERR__(rd_kafka_share_subscribe(share_c1, subscription));
230
+
231
+ /* C1 joins - wait for all 3 partitions */
232
+ cnt = wait_assignment_count(share_c1, 3, 10000);
233
+ TEST_ASSERT(cnt == 3, "Expected C1 to have 3 partitions, got %d", cnt);
234
+
235
+ /* C2 joins - partitions should be redistributed */
236
+ share_c2 = create_share_consumer(bootstraps, group);
237
+ TEST_CALL_ERR__(rd_kafka_share_subscribe(share_c2, subscription));
238
+ rd_kafka_topic_partition_list_destroy(subscription);
239
+
240
+ /* Wait until both consumers have partitions and total == 3 */
241
+ deadline = test_clock() + 15000 * 1000;
242
+ while (test_clock() < deadline) {
243
+
244
+ TEST_CALL_ERR__(
245
+ rd_kafka_assignment(test_share_consumer_get_rk(share_c1),
246
+ &share_c1_assignment));
247
+ TEST_CALL_ERR__(
248
+ rd_kafka_assignment(test_share_consumer_get_rk(share_c2),
249
+ &share_c2_assignment));
250
+
251
+ if (share_c1_assignment->cnt + share_c2_assignment->cnt == 3 &&
252
+ share_c1_assignment->cnt > 0 &&
253
+ share_c2_assignment->cnt > 0) {
254
+ rd_kafka_topic_partition_list_destroy(
255
+ share_c1_assignment);
256
+ rd_kafka_topic_partition_list_destroy(
257
+ share_c2_assignment);
258
+ break;
259
+ }
260
+ rd_kafka_topic_partition_list_destroy(share_c1_assignment);
261
+ rd_kafka_topic_partition_list_destroy(share_c2_assignment);
262
+ rd_usleep(200 * 1000, 0);
263
+ }
264
+ /* Final check after loop */
265
+ TEST_CALL_ERR__(rd_kafka_assignment(
266
+ test_share_consumer_get_rk(share_c1), &share_c1_assignment));
267
+ TEST_CALL_ERR__(rd_kafka_assignment(
268
+ test_share_consumer_get_rk(share_c2), &share_c2_assignment));
269
+ TEST_ASSERT(share_c1_assignment->cnt + share_c2_assignment->cnt == 3,
270
+ "Expected total 3 partitions, got %d + %d = %d",
271
+ share_c1_assignment->cnt, share_c2_assignment->cnt,
272
+ share_c1_assignment->cnt + share_c2_assignment->cnt);
273
+ TEST_ASSERT(share_c1_assignment->cnt > 0 &&
274
+ share_c2_assignment->cnt > 0,
275
+ "Expected both consumers to have partitions, "
276
+ "got C1=%d, C2=%d",
277
+ share_c1_assignment->cnt, share_c2_assignment->cnt);
278
+ rd_kafka_topic_partition_list_destroy(share_c1_assignment);
279
+ rd_kafka_topic_partition_list_destroy(share_c2_assignment);
280
+
281
+ /* C2 leaves - C1 should get all partitions back */
282
+ test_share_consumer_close(share_c2);
283
+ test_share_destroy(share_c2);
284
+
285
+ cnt = wait_assignment_count(share_c1, 3, 10000);
286
+ TEST_ASSERT(cnt == 3,
287
+ "Expected C1 to have 3 partitions after C2 left, got %d",
288
+ cnt);
289
+
290
+ /* Cleanup */
291
+ test_share_consumer_close(share_c1);
292
+ test_share_destroy(share_c1);
293
+
294
+ rd_kafka_mock_stop_request_tracking(mcluster);
295
+ test_mock_cluster_destroy(mcluster);
296
+
297
+ SUB_TEST_PASS();
298
+ }
299
+
300
+ /**
301
+ * @brief Test multi-topic assignment with mixed subscriptions.
302
+ * C1: both topics, C2: orders only, C3: events only
303
+ */
304
+ static void do_test_share_group_multi_topic_assignment(void) {
305
+ rd_kafka_mock_cluster_t *mcluster;
306
+ const char *bootstraps;
307
+ rd_kafka_topic_partition_list_t *sub_both, *sub_orders, *sub_events;
308
+ rd_kafka_topic_partition_list_t *share_c1_assign, *share_c2_assign,
309
+ *share_c3_assign;
310
+ rd_kafka_share_t *share_c1, *share_c2, *share_c3;
311
+ const char *topic_orders = "test-orders";
312
+ const char *topic_events = "test-events";
313
+ const char *group = "test-share-group-multi";
314
+ int total_orders, total_events, cnt;
315
+ int64_t deadline;
316
+
317
+ SUB_TEST_QUICK();
318
+
319
+ /* Setup: orders (4 partitions), events (2 partitions) */
320
+ mcluster = test_mock_cluster_new(1, &bootstraps);
321
+ rd_kafka_mock_topic_create(mcluster, topic_orders, 4, 1);
322
+ rd_kafka_mock_topic_create(mcluster, topic_events, 2, 1);
323
+
324
+ sub_both = rd_kafka_topic_partition_list_new(2);
325
+ rd_kafka_topic_partition_list_add(sub_both, topic_orders,
326
+ RD_KAFKA_PARTITION_UA);
327
+ rd_kafka_topic_partition_list_add(sub_both, topic_events,
328
+ RD_KAFKA_PARTITION_UA);
329
+
330
+ sub_orders = rd_kafka_topic_partition_list_new(1);
331
+ rd_kafka_topic_partition_list_add(sub_orders, topic_orders,
332
+ RD_KAFKA_PARTITION_UA);
333
+
334
+ sub_events = rd_kafka_topic_partition_list_new(1);
335
+ rd_kafka_topic_partition_list_add(sub_events, topic_events,
336
+ RD_KAFKA_PARTITION_UA);
337
+
338
+ rd_kafka_mock_start_request_tracking(mcluster);
339
+
340
+ /* C1 joins (both topics) - should get all 6 partitions */
341
+ share_c1 = create_share_consumer(bootstraps, group);
342
+ TEST_CALL_ERR__(rd_kafka_share_subscribe(share_c1, sub_both));
343
+ cnt = wait_assignment_count(share_c1, 6, 10000);
344
+ TEST_ASSERT(cnt == 6, "C1 should have all 6 partitions, got %d", cnt);
345
+
346
+ /* C2 joins (orders only) - orders should split.
347
+ * Wait until C2 has at least 1 orders partition and
348
+ * total orders == 4, total events == 2. */
349
+ share_c2 = create_share_consumer(bootstraps, group);
350
+ TEST_CALL_ERR__(rd_kafka_share_subscribe(share_c2, sub_orders));
351
+
352
+ deadline = test_clock() + 15000 * 1000;
353
+ while (test_clock() < deadline) {
354
+ TEST_CALL_ERR__(rd_kafka_assignment(
355
+ test_share_consumer_get_rk(share_c1), &share_c1_assign));
356
+ TEST_CALL_ERR__(rd_kafka_assignment(
357
+ test_share_consumer_get_rk(share_c2), &share_c2_assign));
358
+
359
+ total_orders =
360
+ count_topic_partitions(share_c1_assign, topic_orders) +
361
+ count_topic_partitions(share_c2_assign, topic_orders);
362
+ total_events =
363
+ count_topic_partitions(share_c1_assign, topic_events) +
364
+ count_topic_partitions(share_c2_assign, topic_events);
365
+
366
+ if (total_orders == 4 && total_events == 2 &&
367
+ count_topic_partitions(share_c2_assign, topic_orders) > 0) {
368
+ rd_kafka_topic_partition_list_destroy(share_c1_assign);
369
+ rd_kafka_topic_partition_list_destroy(share_c2_assign);
370
+ break;
371
+ }
372
+ rd_kafka_topic_partition_list_destroy(share_c1_assign);
373
+ rd_kafka_topic_partition_list_destroy(share_c2_assign);
374
+ rd_usleep(200 * 1000, 0);
375
+ }
376
+ TEST_CALL_ERR__(rd_kafka_assignment(
377
+ test_share_consumer_get_rk(share_c1), &share_c1_assign));
378
+ TEST_CALL_ERR__(rd_kafka_assignment(
379
+ test_share_consumer_get_rk(share_c2), &share_c2_assign));
380
+ total_orders = count_topic_partitions(share_c1_assign, topic_orders) +
381
+ count_topic_partitions(share_c2_assign, topic_orders);
382
+ total_events = count_topic_partitions(share_c1_assign, topic_events) +
383
+ count_topic_partitions(share_c2_assign, topic_events);
384
+ TEST_ASSERT(total_orders == 4, "Total orders should be 4, got %d",
385
+ total_orders);
386
+ TEST_ASSERT(total_events == 2, "Total events should be 2, got %d",
387
+ total_events);
388
+ TEST_ASSERT(count_topic_partitions(share_c2_assign, topic_orders) > 0,
389
+ "C2 should have at least 1 orders partition");
390
+ rd_kafka_topic_partition_list_destroy(share_c1_assign);
391
+ rd_kafka_topic_partition_list_destroy(share_c2_assign);
392
+
393
+ /* C3 joins (events only) - events should split.
394
+ * Wait until C3 has at least 1 events partition. */
395
+ share_c3 = create_share_consumer(bootstraps, group);
396
+ TEST_CALL_ERR__(rd_kafka_share_subscribe(share_c3, sub_events));
397
+
398
+ deadline = test_clock() + 15000 * 1000;
399
+ while (test_clock() < deadline) {
400
+
401
+ TEST_CALL_ERR__(rd_kafka_assignment(
402
+ test_share_consumer_get_rk(share_c1), &share_c1_assign));
403
+ TEST_CALL_ERR__(rd_kafka_assignment(
404
+ test_share_consumer_get_rk(share_c2), &share_c2_assign));
405
+ TEST_CALL_ERR__(rd_kafka_assignment(
406
+ test_share_consumer_get_rk(share_c3), &share_c3_assign));
407
+
408
+ total_orders =
409
+ count_topic_partitions(share_c1_assign, topic_orders) +
410
+ count_topic_partitions(share_c2_assign, topic_orders) +
411
+ count_topic_partitions(share_c3_assign, topic_orders);
412
+ total_events =
413
+ count_topic_partitions(share_c1_assign, topic_events) +
414
+ count_topic_partitions(share_c2_assign, topic_events) +
415
+ count_topic_partitions(share_c3_assign, topic_events);
416
+
417
+ if (total_orders == 4 && total_events == 2 &&
418
+ count_topic_partitions(share_c3_assign, topic_events) > 0) {
419
+ rd_kafka_topic_partition_list_destroy(share_c1_assign);
420
+ rd_kafka_topic_partition_list_destroy(share_c2_assign);
421
+ rd_kafka_topic_partition_list_destroy(share_c3_assign);
422
+ break;
423
+ }
424
+ rd_kafka_topic_partition_list_destroy(share_c1_assign);
425
+ rd_kafka_topic_partition_list_destroy(share_c2_assign);
426
+ rd_kafka_topic_partition_list_destroy(share_c3_assign);
427
+ rd_usleep(200 * 1000, 0);
428
+ }
429
+ TEST_CALL_ERR__(rd_kafka_assignment(
430
+ test_share_consumer_get_rk(share_c1), &share_c1_assign));
431
+ TEST_CALL_ERR__(rd_kafka_assignment(
432
+ test_share_consumer_get_rk(share_c2), &share_c2_assign));
433
+ TEST_CALL_ERR__(rd_kafka_assignment(
434
+ test_share_consumer_get_rk(share_c3), &share_c3_assign));
435
+ total_orders = count_topic_partitions(share_c1_assign, topic_orders) +
436
+ count_topic_partitions(share_c2_assign, topic_orders) +
437
+ count_topic_partitions(share_c3_assign, topic_orders);
438
+ total_events = count_topic_partitions(share_c1_assign, topic_events) +
439
+ count_topic_partitions(share_c2_assign, topic_events) +
440
+ count_topic_partitions(share_c3_assign, topic_events);
441
+ TEST_ASSERT(total_orders == 4, "Total orders should be 4, got %d",
442
+ total_orders);
443
+ TEST_ASSERT(total_events == 2, "Total events should be 2, got %d",
444
+ total_events);
445
+ TEST_ASSERT(count_topic_partitions(share_c3_assign, topic_events) > 0,
446
+ "C3 should have at least 1 events partition");
447
+ rd_kafka_topic_partition_list_destroy(share_c1_assign);
448
+ rd_kafka_topic_partition_list_destroy(share_c2_assign);
449
+ rd_kafka_topic_partition_list_destroy(share_c3_assign);
450
+
451
+ /* C1 leaves - C2 should get all orders, C3 all events.
452
+ * Wait until C2 has 4 orders and C3 has 2 events. */
453
+ test_share_consumer_close(share_c1);
454
+ test_share_destroy(share_c1);
455
+
456
+ deadline = test_clock() + 15000 * 1000;
457
+ while (test_clock() < deadline) {
458
+ TEST_CALL_ERR__(rd_kafka_assignment(
459
+ test_share_consumer_get_rk(share_c2), &share_c2_assign));
460
+ TEST_CALL_ERR__(rd_kafka_assignment(
461
+ test_share_consumer_get_rk(share_c3), &share_c3_assign));
462
+
463
+ if (count_topic_partitions(share_c2_assign, topic_orders) ==
464
+ 4 &&
465
+ count_topic_partitions(share_c3_assign, topic_events) ==
466
+ 2) {
467
+ rd_kafka_topic_partition_list_destroy(share_c2_assign);
468
+ rd_kafka_topic_partition_list_destroy(share_c3_assign);
469
+ break;
470
+ }
471
+ rd_kafka_topic_partition_list_destroy(share_c2_assign);
472
+ rd_kafka_topic_partition_list_destroy(share_c3_assign);
473
+ rd_usleep(200 * 1000, 0);
474
+ }
475
+ TEST_CALL_ERR__(rd_kafka_assignment(
476
+ test_share_consumer_get_rk(share_c2), &share_c2_assign));
477
+ TEST_CALL_ERR__(rd_kafka_assignment(
478
+ test_share_consumer_get_rk(share_c3), &share_c3_assign));
479
+ TEST_ASSERT(count_topic_partitions(share_c2_assign, topic_orders) == 4,
480
+ "C2 should have all 4 orders partitions, got %d",
481
+ count_topic_partitions(share_c2_assign, topic_orders));
482
+ TEST_ASSERT(count_topic_partitions(share_c3_assign, topic_events) == 2,
483
+ "C3 should have all 2 events partitions, got %d",
484
+ count_topic_partitions(share_c3_assign, topic_events));
485
+ rd_kafka_topic_partition_list_destroy(share_c2_assign);
486
+ rd_kafka_topic_partition_list_destroy(share_c3_assign);
487
+
488
+ /* C2 leaves - C3 should still have events only */
489
+ test_share_consumer_close(share_c2);
490
+ test_share_destroy(share_c2);
491
+
492
+ /* Wait for C3 to stabilize with 2 events, 0 orders */
493
+ cnt = wait_assignment_count(share_c3, 2, 10000);
494
+ TEST_CALL_ERR__(rd_kafka_assignment(
495
+ test_share_consumer_get_rk(share_c3), &share_c3_assign));
496
+ TEST_ASSERT(count_topic_partitions(share_c3_assign, topic_events) == 2,
497
+ "C3 should still have 2 events partitions, got %d",
498
+ count_topic_partitions(share_c3_assign, topic_events));
499
+ TEST_ASSERT(count_topic_partitions(share_c3_assign, topic_orders) == 0,
500
+ "C3 should have 0 orders partitions (not subscribed), "
501
+ "got %d",
502
+ count_topic_partitions(share_c3_assign, topic_orders));
503
+ rd_kafka_topic_partition_list_destroy(share_c3_assign);
504
+
505
+ /* Cleanup */
506
+ test_share_consumer_close(share_c3);
507
+ test_share_destroy(share_c3);
508
+
509
+ rd_kafka_topic_partition_list_destroy(sub_both);
510
+ rd_kafka_topic_partition_list_destroy(sub_orders);
511
+ rd_kafka_topic_partition_list_destroy(sub_events);
512
+
513
+ rd_kafka_mock_stop_request_tracking(mcluster);
514
+ test_mock_cluster_destroy(mcluster);
515
+
516
+ SUB_TEST_PASS();
517
+ }
518
+
519
+ /**
520
+ * @brief Test fatal error injection for ShareGroupHeartbeat.
521
+ *
522
+ * When a fatal exception occurs during heartbeat, the consumer should
523
+ * transition to fatal state and no longer be usable.
524
+ */
525
+ static void do_test_share_group_error_injection(void) {
526
+ rd_kafka_mock_cluster_t *mcluster;
527
+ const char *bootstraps;
528
+ rd_kafka_topic_partition_list_t *subscription;
529
+ rd_kafka_share_t *share_c;
530
+ rd_kafka_error_t *error;
531
+ const char *topic = test_mk_topic_name(__FUNCTION__, 0);
532
+ const char *group = "test-share-group-errors";
533
+
534
+ SUB_TEST_QUICK();
535
+
536
+ /* Setup */
537
+ mcluster = test_mock_cluster_new(1, &bootstraps);
538
+ rd_kafka_mock_topic_create(mcluster, topic, 3, 1);
539
+
540
+ share_c = create_share_consumer(bootstraps, group);
541
+
542
+ subscription = rd_kafka_topic_partition_list_new(1);
543
+ rd_kafka_topic_partition_list_add(subscription, topic,
544
+ RD_KAFKA_PARTITION_UA);
545
+
546
+ rd_kafka_mock_start_request_tracking(mcluster);
547
+ TEST_CALL_ERR__(rd_kafka_share_subscribe(share_c, subscription));
548
+ rd_kafka_topic_partition_list_destroy(subscription);
549
+
550
+ /* Wait for initial join and assignment */
551
+ TEST_ASSERT(wait_assignment_count(share_c, 3, 10000) == 3,
552
+ "Expected 3 partitions initially");
553
+
554
+ /* Inject a fatal error (INVALID_REQUEST) during heartbeat.
555
+ * This matches testFailureOnFatalException which verifies
556
+ * transitionToFatal() is called on fatal heartbeat errors. */
557
+ rd_kafka_mock_broker_push_request_error_rtts(
558
+ mcluster, 1, RD_KAFKAP_ShareGroupHeartbeat, 1,
559
+ RD_KAFKA_RESP_ERR_INVALID_REQUEST, 0);
560
+
561
+ /* Wait for the fatal error to propagate via consume_batch. */
562
+ error = wait_fatal_error(share_c, 7000);
563
+ TEST_ASSERT(error != NULL,
564
+ "Expected a fatal error but none received within timeout");
565
+ TEST_ASSERT(rd_kafka_error_code(error) ==
566
+ RD_KAFKA_RESP_ERR_INVALID_REQUEST,
567
+ "Expected INVALID_REQUEST fatal error, got %s",
568
+ rd_kafka_error_name(error));
569
+ TEST_SAY("Consumer entered fatal state: %s\n",
570
+ rd_kafka_error_string(error));
571
+ rd_kafka_error_destroy(error);
572
+
573
+ /* Cleanup. Consumer is in fatal state, but close() still flushes
574
+ * pending acks and leaves the share session, so it succeeds. */
575
+ error = rd_kafka_share_consumer_close(share_c);
576
+ TEST_ASSERT(!error, "Expected close to succeed, got %s",
577
+ rd_kafka_error_name(error));
578
+ test_share_destroy(share_c);
579
+
580
+ rd_kafka_mock_stop_request_tracking(mcluster);
581
+ test_mock_cluster_destroy(mcluster);
582
+
583
+ SUB_TEST_PASS();
584
+ }
585
+
586
+ /**
587
+ * @brief Test network timeout for ShareGroupHeartbeat.
588
+ *
589
+ * When a heartbeat times out due to network latency, the consumer should
590
+ * handle the timeout and retry with backoff, eventually recovering.
591
+ */
592
+ static void do_test_share_group_rtt_injection(void) {
593
+ rd_kafka_mock_cluster_t *mcluster;
594
+ const char *bootstraps;
595
+ rd_kafka_topic_partition_list_t *subscription, *assignment;
596
+ rd_kafka_share_t *share_c;
597
+ rd_kafka_conf_t *conf;
598
+ char errstr[512];
599
+ int found_heartbeats;
600
+ const char *topic = test_mk_topic_name(__FUNCTION__, 0);
601
+ const char *group = "test-share-group-rtt";
602
+
603
+ SUB_TEST_QUICK();
604
+
605
+ /* Setup */
606
+ mcluster = test_mock_cluster_new(1, &bootstraps);
607
+ rd_kafka_mock_topic_create(mcluster, topic, 3, 1);
608
+
609
+ /* Create consumer with short socket timeout so RTT injection
610
+ * causes an actual timeout. Default is 60s which is too long. */
611
+ test_conf_init(&conf, NULL, 0);
612
+ test_conf_set(conf, "bootstrap.servers", bootstraps);
613
+ test_conf_set(conf, "group.id", group);
614
+ test_conf_set(conf, "socket.timeout.ms", "3000");
615
+
616
+ share_c = rd_kafka_share_consumer_new(conf, errstr, sizeof(errstr));
617
+ TEST_ASSERT(share_c != NULL, "Failed to create share consumer: %s",
618
+ errstr);
619
+
620
+ subscription = rd_kafka_topic_partition_list_new(1);
621
+ rd_kafka_topic_partition_list_add(subscription, topic,
622
+ RD_KAFKA_PARTITION_UA);
623
+
624
+ rd_kafka_mock_start_request_tracking(mcluster);
625
+ TEST_CALL_ERR__(rd_kafka_share_subscribe(share_c, subscription));
626
+ rd_kafka_topic_partition_list_destroy(subscription);
627
+
628
+ /* Wait for initial join and assignment */
629
+ wait_share_heartbeats(mcluster, 1, 500);
630
+ rd_usleep(500 * 1000, 0);
631
+
632
+ /* Verify initial assignment */
633
+ TEST_CALL_ERR__(rd_kafka_assignment(test_share_consumer_get_rk(share_c),
634
+ &assignment));
635
+ TEST_ASSERT(assignment->cnt == 3,
636
+ "Expected 3 partitions initially, got %d", assignment->cnt);
637
+ rd_kafka_topic_partition_list_destroy(assignment);
638
+
639
+ /* Inject RTT larger than socket.timeout.ms to cause a real timeout.
640
+ * The Java test verifies TimeoutException + backoff retry. */
641
+ rd_kafka_mock_broker_push_request_error_rtts(
642
+ mcluster, 1, RD_KAFKAP_ShareGroupHeartbeat, 1,
643
+ RD_KAFKA_RESP_ERR_NO_ERROR, 5000);
644
+
645
+ /* Wait through the timeout period - consumer should recover */
646
+ rd_usleep(500 * 1000, 0);
647
+
648
+ /* Verify heartbeats resumed after timeout recovery */
649
+ found_heartbeats = wait_share_heartbeats(mcluster, 2, 1000);
650
+ TEST_ASSERT(found_heartbeats >= 1,
651
+ "Expected heartbeats to resume after timeout, got %d",
652
+ found_heartbeats);
653
+
654
+ /* Wait for assignment to be restored */
655
+ rd_usleep(500 * 1000, 0);
656
+
657
+ /* Verify consumer recovered and still has assignment */
658
+ TEST_CALL_ERR__(rd_kafka_assignment(test_share_consumer_get_rk(share_c),
659
+ &assignment));
660
+ TEST_ASSERT(assignment->cnt == 3,
661
+ "Expected 3 partitions after timeout recovery, got %d",
662
+ assignment->cnt);
663
+ rd_kafka_topic_partition_list_destroy(assignment);
664
+
665
+ /* Cleanup */
666
+ test_share_consumer_close(share_c);
667
+ test_share_destroy(share_c);
668
+
669
+ rd_kafka_mock_stop_request_tracking(mcluster);
670
+ test_mock_cluster_destroy(mcluster);
671
+
672
+ SUB_TEST_PASS();
673
+ }
674
+
675
+ /**
676
+ * @brief Test session timeout for ShareGroupHeartbeat.
677
+ *
678
+ * Tests that the mock broker correctly times out members that stop
679
+ * heartbeating. Uses a short session timeout (3000ms) and verifies:
680
+ * - Member is removed after timeout
681
+ * - Remaining members get reassigned partitions
682
+ */
683
+ static void do_test_share_group_session_timeout(void) {
684
+ rd_kafka_mock_cluster_t *mcluster;
685
+ const char *bootstraps;
686
+ rd_kafka_topic_partition_list_t *subscription;
687
+ rd_kafka_topic_partition_list_t *share_c1_assign, *share_c2_assign;
688
+ rd_kafka_share_t *share_c1, *share_c2;
689
+ int share_c1_initial = 0, share_c2_initial = 0;
690
+ int64_t dl;
691
+ const char *topic = test_mk_topic_name(__FUNCTION__, 0);
692
+ const char *group = "test-share-group-timeout";
693
+
694
+ SUB_TEST_QUICK();
695
+
696
+ /* Setup */
697
+ mcluster = test_mock_cluster_new(1, &bootstraps);
698
+ rd_kafka_mock_topic_create(mcluster, topic, 4, 1);
699
+
700
+ /* Set heartbeat interval shorter than session timeout so consumers
701
+ * don't time out while waiting for assignment updates. */
702
+ rd_kafka_mock_sharegroup_set_heartbeat_interval(mcluster, 1000);
703
+ /* Set session timeout. Must be > heartbeat_interval to avoid
704
+ * spurious timeouts but short enough for the test to finish
705
+ * quickly. */
706
+ rd_kafka_mock_sharegroup_set_session_timeout(mcluster, 3000);
707
+
708
+ share_c1 = create_share_consumer(bootstraps, group);
709
+ share_c2 = create_share_consumer(bootstraps, group);
710
+
711
+ subscription = rd_kafka_topic_partition_list_new(1);
712
+ rd_kafka_topic_partition_list_add(subscription, topic,
713
+ RD_KAFKA_PARTITION_UA);
714
+
715
+ rd_kafka_mock_start_request_tracking(mcluster);
716
+
717
+ TEST_CALL_ERR__(rd_kafka_share_subscribe(share_c1, subscription));
718
+ TEST_CALL_ERR__(rd_kafka_share_subscribe(share_c2, subscription));
719
+ rd_kafka_topic_partition_list_destroy(subscription);
720
+
721
+ /* Wait for both to join and rebalance to complete. */
722
+ dl = test_clock() + 15000 * 1000;
723
+ while (test_clock() < dl) {
724
+ TEST_CALL_ERR__(rd_kafka_assignment(
725
+ test_share_consumer_get_rk(share_c1), &share_c1_assign));
726
+ TEST_CALL_ERR__(rd_kafka_assignment(
727
+ test_share_consumer_get_rk(share_c2), &share_c2_assign));
728
+ share_c1_initial = share_c1_assign->cnt;
729
+ share_c2_initial = share_c2_assign->cnt;
730
+ rd_kafka_topic_partition_list_destroy(share_c1_assign);
731
+ rd_kafka_topic_partition_list_destroy(share_c2_assign);
732
+ if (share_c1_initial + share_c2_initial == 4 &&
733
+ share_c1_initial > 0 && share_c2_initial > 0)
734
+ break;
735
+ rd_usleep(200 * 1000, 0);
736
+ }
737
+ TEST_ASSERT(share_c1_initial + share_c2_initial == 4,
738
+ "Total should be 4 partitions, got %d",
739
+ share_c1_initial + share_c2_initial);
740
+ TEST_ASSERT(share_c1_initial > 0 && share_c2_initial > 0,
741
+ "Both consumers should have partitions");
742
+
743
+ /* Destroy C2 without close to simulate crash */
744
+ test_share_destroy(share_c2);
745
+
746
+ /* Wait for C2's session to time out (3s) and C1 to get
747
+ * all partitions back. */
748
+ TEST_ASSERT(wait_assignment_count(share_c1, 4, 10000) == 4,
749
+ "C1 should have all 4 partitions after C2 timeout");
750
+
751
+ test_share_consumer_close(share_c1);
752
+ test_share_destroy(share_c1);
753
+
754
+ rd_kafka_mock_stop_request_tracking(mcluster);
755
+ test_mock_cluster_destroy(mcluster);
756
+
757
+ SUB_TEST_PASS();
758
+ }
759
+
760
+ /**
761
+ * @brief Test target assignment API for ShareGroupHeartbeat.
762
+ *
763
+ * Tests that the mock broker can apply manual target assignments:
764
+ * 1. Two consumers join and get automatic assignment (2 partitions each)
765
+ * 2. Retrieve member IDs using rd_kafka_mock_sharegroup_get_member_ids()
766
+ * 3. Set manual target assignment: C1 gets all 4 partitions, C2 gets none
767
+ * 4. Verify consumers receive the manual assignment
768
+ */
769
+ static void do_test_share_group_target_assignment(void) {
770
+ rd_kafka_mock_cluster_t *mcluster;
771
+ const char *bootstraps;
772
+ rd_kafka_topic_partition_list_t *subscription;
773
+ rd_kafka_topic_partition_list_t *share_c1_assign, *share_c2_assign;
774
+ rd_kafka_topic_partition_list_t *target_c1, *target_c2;
775
+ rd_kafka_topic_partition_list_t *assignments[2];
776
+ rd_kafka_share_t *share_c1, *share_c2;
777
+ char **member_ids;
778
+ size_t member_cnt;
779
+ rd_kafka_resp_err_t err;
780
+ int64_t dl;
781
+ const char *topic = test_mk_topic_name(__FUNCTION__, 0);
782
+ const char *group = "test-share-group-target";
783
+
784
+ SUB_TEST_QUICK();
785
+
786
+ /* Setup */
787
+ mcluster = test_mock_cluster_new(1, &bootstraps);
788
+ rd_kafka_mock_topic_create(mcluster, topic, 4, 1);
789
+
790
+ share_c1 = create_share_consumer(bootstraps, group);
791
+ share_c2 = create_share_consumer(bootstraps, group);
792
+
793
+ subscription = rd_kafka_topic_partition_list_new(1);
794
+ rd_kafka_topic_partition_list_add(subscription, topic,
795
+ RD_KAFKA_PARTITION_UA);
796
+
797
+ rd_kafka_mock_start_request_tracking(mcluster);
798
+
799
+ TEST_CALL_ERR__(rd_kafka_share_subscribe(share_c1, subscription));
800
+ TEST_CALL_ERR__(rd_kafka_share_subscribe(share_c2, subscription));
801
+ rd_kafka_topic_partition_list_destroy(subscription);
802
+
803
+ /* Wait for both to join and rebalance to complete */
804
+ dl = test_clock() + 15000 * 1000;
805
+ while (test_clock() < dl) {
806
+ TEST_CALL_ERR__(rd_kafka_assignment(
807
+ test_share_consumer_get_rk(share_c1), &share_c1_assign));
808
+ TEST_CALL_ERR__(rd_kafka_assignment(
809
+ test_share_consumer_get_rk(share_c2), &share_c2_assign));
810
+ if (share_c1_assign->cnt + share_c2_assign->cnt == 4 &&
811
+ share_c1_assign->cnt > 0 && share_c2_assign->cnt > 0) {
812
+ rd_kafka_topic_partition_list_destroy(share_c1_assign);
813
+ rd_kafka_topic_partition_list_destroy(share_c2_assign);
814
+ break;
815
+ }
816
+ rd_kafka_topic_partition_list_destroy(share_c1_assign);
817
+ rd_kafka_topic_partition_list_destroy(share_c2_assign);
818
+ rd_usleep(200 * 1000, 0);
819
+ }
820
+ /* Final check */
821
+ TEST_CALL_ERR__(rd_kafka_assignment(
822
+ test_share_consumer_get_rk(share_c1), &share_c1_assign));
823
+ TEST_CALL_ERR__(rd_kafka_assignment(
824
+ test_share_consumer_get_rk(share_c2), &share_c2_assign));
825
+ TEST_ASSERT(share_c1_assign->cnt + share_c2_assign->cnt == 4,
826
+ "Total should be 4 partitions, got %d",
827
+ share_c1_assign->cnt + share_c2_assign->cnt);
828
+ TEST_ASSERT(share_c1_assign->cnt > 0 && share_c2_assign->cnt > 0,
829
+ "Both consumers should have partitions initially");
830
+ rd_kafka_topic_partition_list_destroy(share_c1_assign);
831
+ rd_kafka_topic_partition_list_destroy(share_c2_assign);
832
+
833
+ /* Retrieve member IDs */
834
+ err = rd_kafka_mock_sharegroup_get_member_ids(mcluster, group,
835
+ &member_ids, &member_cnt);
836
+ TEST_ASSERT(err == RD_KAFKA_RESP_ERR_NO_ERROR,
837
+ "Expected no error, got %s", rd_kafka_err2str(err));
838
+ TEST_ASSERT(member_cnt == 2, "Expected 2 members, got %zu", member_cnt);
839
+
840
+ /* Set manual target assignment: all to first member */
841
+ target_c1 = rd_kafka_topic_partition_list_new(4);
842
+ rd_kafka_topic_partition_list_add(target_c1, topic, 0);
843
+ rd_kafka_topic_partition_list_add(target_c1, topic, 1);
844
+ rd_kafka_topic_partition_list_add(target_c1, topic, 2);
845
+ rd_kafka_topic_partition_list_add(target_c1, topic, 3);
846
+
847
+ target_c2 = rd_kafka_topic_partition_list_new(0);
848
+
849
+ assignments[0] = target_c1;
850
+ assignments[1] = target_c2;
851
+
852
+ rd_kafka_mock_sharegroup_target_assignment(
853
+ mcluster, group, (const char **)member_ids, assignments, 2);
854
+
855
+ rd_kafka_topic_partition_list_destroy(target_c1);
856
+ rd_kafka_topic_partition_list_destroy(target_c2);
857
+
858
+ /* Wait until one consumer has all 4 and the other has 0. */
859
+ dl = test_clock() + 15000 * 1000;
860
+ while (test_clock() < dl) {
861
+
862
+ TEST_CALL_ERR__(rd_kafka_assignment(
863
+ test_share_consumer_get_rk(share_c1), &share_c1_assign));
864
+ TEST_CALL_ERR__(rd_kafka_assignment(
865
+ test_share_consumer_get_rk(share_c2), &share_c2_assign));
866
+
867
+ if ((share_c1_assign->cnt == 4 && share_c2_assign->cnt == 0) ||
868
+ (share_c1_assign->cnt == 0 && share_c2_assign->cnt == 4)) {
869
+ rd_kafka_topic_partition_list_destroy(share_c1_assign);
870
+ rd_kafka_topic_partition_list_destroy(share_c2_assign);
871
+ break;
872
+ }
873
+ rd_kafka_topic_partition_list_destroy(share_c1_assign);
874
+ rd_kafka_topic_partition_list_destroy(share_c2_assign);
875
+ rd_usleep(200 * 1000, 0);
876
+ }
877
+
878
+ /* Verify manual assignment was applied */
879
+ TEST_CALL_ERR__(rd_kafka_assignment(
880
+ test_share_consumer_get_rk(share_c1), &share_c1_assign));
881
+ TEST_CALL_ERR__(rd_kafka_assignment(
882
+ test_share_consumer_get_rk(share_c2), &share_c2_assign));
883
+
884
+ TEST_ASSERT(share_c1_assign->cnt + share_c2_assign->cnt == 4,
885
+ "Total should still be 4 partitions, got %d",
886
+ share_c1_assign->cnt + share_c2_assign->cnt);
887
+ TEST_ASSERT(
888
+ (share_c1_assign->cnt == 4 && share_c2_assign->cnt == 0) ||
889
+ (share_c1_assign->cnt == 0 && share_c2_assign->cnt == 4),
890
+ "Expected one consumer to have all 4 partitions and the "
891
+ "other to have 0, got C1=%d, C2=%d",
892
+ share_c1_assign->cnt, share_c2_assign->cnt);
893
+
894
+ rd_kafka_topic_partition_list_destroy(share_c1_assign);
895
+ rd_kafka_topic_partition_list_destroy(share_c2_assign);
896
+
897
+ /* Free member IDs */
898
+ rd_free(member_ids[0]);
899
+ rd_free(member_ids[1]);
900
+ rd_free(member_ids);
901
+
902
+ /* Cleanup */
903
+ test_share_consumer_close(share_c1);
904
+ test_share_consumer_close(share_c2);
905
+ test_share_destroy(share_c1);
906
+ test_share_destroy(share_c2);
907
+
908
+ rd_kafka_mock_stop_request_tracking(mcluster);
909
+ test_mock_cluster_destroy(mcluster);
910
+
911
+ SUB_TEST_PASS();
912
+ }
913
+
914
+ /**
915
+ * @brief Test that a healthy, continuously-heartbeating member is never
916
+ * fenced by the mock broker's session timeout timer.
917
+ *
918
+ * This test sets a 2-second session timeout and then keeps the consumer
919
+ * active for 10 seconds. If the session timeout timer incorrectly fences
920
+ * active members, the assignment would drop. This validates that the timer
921
+ * only fences truly timed-out members (those that stopped heartbeating).
922
+ * Related to KIP-932 session timeout logic.
923
+ */
924
+ static void do_test_share_group_no_spurious_fencing(void) {
925
+ rd_kafka_mock_cluster_t *mcluster;
926
+ const char *bootstraps;
927
+ rd_kafka_topic_partition_list_t *subscription, *assignment;
928
+ rd_kafka_share_t *share_c;
929
+ const char *topic = test_mk_topic_name(__FUNCTION__, 0);
930
+ const char *group = "test-share-group-no-fence";
931
+ int i;
932
+
933
+ SUB_TEST_QUICK();
934
+
935
+ /* Setup with a short session timeout and a heartbeat interval that
936
+ * is well below it, so the active consumer is never spuriously
937
+ * timed out. */
938
+ mcluster = test_mock_cluster_new(1, &bootstraps);
939
+ rd_kafka_mock_topic_create(mcluster, topic, 3, 1);
940
+ rd_kafka_mock_sharegroup_set_heartbeat_interval(mcluster, 500);
941
+ rd_kafka_mock_sharegroup_set_session_timeout(mcluster, 2000);
942
+
943
+ share_c = create_share_consumer(bootstraps, group);
944
+
945
+ subscription = rd_kafka_topic_partition_list_new(1);
946
+ rd_kafka_topic_partition_list_add(subscription, topic,
947
+ RD_KAFKA_PARTITION_UA);
948
+ TEST_CALL_ERR__(rd_kafka_share_subscribe(share_c, subscription));
949
+ rd_kafka_topic_partition_list_destroy(subscription);
950
+
951
+ /* Wait for join and initial assignment. */
952
+ TEST_ASSERT(wait_assignment_count(share_c, 3, 10000) == 3,
953
+ "Expected 3 partitions initially");
954
+
955
+ /* Wait for 5s (2.5x the 2s session timeout) and verify
956
+ * the assignment is not dropped.
957
+ * If the broker's session timeout timer incorrectly fences active
958
+ * members, the assignment will drop. */
959
+ TEST_SAY("Waiting for 5 seconds with 2s session timeout...\n");
960
+ for (i = 0; i < 5; i++) {
961
+ rd_usleep(1000 * 1000, 0);
962
+
963
+ /* Verify assignment is still intact */
964
+ TEST_CALL_ERR__(rd_kafka_assignment(
965
+ test_share_consumer_get_rk(share_c), &assignment));
966
+ TEST_ASSERT(assignment->cnt == 3,
967
+ "Assignment dropped at %ds (spurious fencing!)",
968
+ i + 1);
969
+ rd_kafka_topic_partition_list_destroy(assignment);
970
+ }
971
+
972
+ TEST_SAY("No spurious fencing after 5 seconds\n");
973
+
974
+ /* Cleanup */
975
+ test_share_consumer_close(share_c);
976
+ test_share_destroy(share_c);
977
+ test_mock_cluster_destroy(mcluster);
978
+
979
+ SUB_TEST_PASS();
980
+ }
981
+
982
+ /* TODO: Add do_test_share_group_max_size() once
983
+ * 0155 fix PR is merged*/
984
+
985
+ /**
986
+ * @brief UNKNOWN_MEMBER_ID error handling.
987
+ *
988
+ * When a consumer receives UNKNOWN_MEMBER_ID error, it should rejoin
989
+ * with epoch=0 (fresh join).
990
+ *
991
+ * NOT YET COMPATIBLE: UNKNOWN_MEMBER_ID triggers an incorrect assert in
992
+ * development builds (rdkafka_cgrp.c:6631). Pending fix by Pranav.
993
+ * See sghb_test_discrepancies.txt #3.
994
+ */
995
+ static void do_test_unknown_member_id_error(void) {
996
+ rd_kafka_mock_cluster_t *mcluster;
997
+ const char *bootstraps;
998
+ rd_kafka_topic_partition_list_t *subscription;
999
+ rd_kafka_share_t *share_c;
1000
+ int found_heartbeats;
1001
+ const char *topic = test_mk_topic_name(__FUNCTION__, 0);
1002
+ const char *group = "test-share-group-unknown-member";
1003
+
1004
+ SUB_TEST_QUICK();
1005
+
1006
+ /* Setup */
1007
+ mcluster = test_mock_cluster_new(1, &bootstraps);
1008
+ rd_kafka_mock_topic_create(mcluster, topic, 3, 1);
1009
+
1010
+ share_c = create_share_consumer(bootstraps, group);
1011
+
1012
+ subscription = rd_kafka_topic_partition_list_new(1);
1013
+ rd_kafka_topic_partition_list_add(subscription, topic,
1014
+ RD_KAFKA_PARTITION_UA);
1015
+
1016
+ rd_kafka_mock_start_request_tracking(mcluster);
1017
+ TEST_CALL_ERR__(rd_kafka_share_subscribe(share_c, subscription));
1018
+ rd_kafka_topic_partition_list_destroy(subscription);
1019
+
1020
+ /* Wait for initial join and assignment */
1021
+ wait_share_heartbeats(mcluster, 1, 1000);
1022
+ TEST_ASSERT(wait_assignment_count(share_c, 3, 10000) == 3,
1023
+ "Expected 3 partitions initially");
1024
+
1025
+ /* Inject UNKNOWN_MEMBER_ID error */
1026
+ rd_kafka_mock_broker_push_request_error_rtts(
1027
+ mcluster, 1, RD_KAFKAP_ShareGroupHeartbeat, 1,
1028
+ RD_KAFKA_RESP_ERR_UNKNOWN_MEMBER_ID, 0);
1029
+
1030
+ /* Wait for consumer to handle error and rejoin */
1031
+ rd_usleep(500 * 1000, 0);
1032
+
1033
+ /* Verify heartbeats continue (rejoin happened) */
1034
+ found_heartbeats = wait_share_heartbeats(mcluster, 2, 1000);
1035
+ TEST_ASSERT(found_heartbeats >= 1,
1036
+ "Expected heartbeats to continue after UNKNOWN_MEMBER_ID, "
1037
+ "got %d",
1038
+ found_heartbeats);
1039
+
1040
+ /* Verify consumer eventually gets assignment back */
1041
+ TEST_ASSERT(wait_assignment_count(share_c, 3, 10000) == 3,
1042
+ "Expected 3 partitions after rejoin");
1043
+
1044
+ /* Cleanup */
1045
+ test_share_consumer_close(share_c);
1046
+ test_share_destroy(share_c);
1047
+
1048
+ rd_kafka_mock_stop_request_tracking(mcluster);
1049
+ test_mock_cluster_destroy(mcluster);
1050
+
1051
+ SUB_TEST_PASS();
1052
+ }
1053
+
1054
+ /**
1055
+ * @brief FENCED_MEMBER_EPOCH error handling.
1056
+ *
1057
+ * When a consumer receives FENCED_MEMBER_EPOCH error, it should be fenced
1058
+ * and then rejoin with epoch=0.
1059
+ */
1060
+ static void do_test_fenced_member_epoch_error(void) {
1061
+ rd_kafka_mock_cluster_t *mcluster;
1062
+ const char *bootstraps;
1063
+ rd_kafka_topic_partition_list_t *subscription;
1064
+ rd_kafka_share_t *share_c;
1065
+ int found_heartbeats;
1066
+ const char *topic = test_mk_topic_name(__FUNCTION__, 0);
1067
+ const char *group = "test-share-group-fenced";
1068
+
1069
+ SUB_TEST_QUICK();
1070
+
1071
+ /* Setup */
1072
+ mcluster = test_mock_cluster_new(1, &bootstraps);
1073
+ rd_kafka_mock_topic_create(mcluster, topic, 3, 1);
1074
+
1075
+ share_c = create_share_consumer(bootstraps, group);
1076
+
1077
+ subscription = rd_kafka_topic_partition_list_new(1);
1078
+ rd_kafka_topic_partition_list_add(subscription, topic,
1079
+ RD_KAFKA_PARTITION_UA);
1080
+
1081
+ rd_kafka_mock_start_request_tracking(mcluster);
1082
+ TEST_CALL_ERR__(rd_kafka_share_subscribe(share_c, subscription));
1083
+ rd_kafka_topic_partition_list_destroy(subscription);
1084
+
1085
+ /* Wait for initial join and assignment */
1086
+ wait_share_heartbeats(mcluster, 1, 1000);
1087
+ TEST_ASSERT(wait_assignment_count(share_c, 3, 10000) == 3,
1088
+ "Expected 3 partitions initially");
1089
+
1090
+ /* Inject FENCED_MEMBER_EPOCH error */
1091
+ rd_kafka_mock_broker_push_request_error_rtts(
1092
+ mcluster, 1, RD_KAFKAP_ShareGroupHeartbeat, 1,
1093
+ RD_KAFKA_RESP_ERR_FENCED_MEMBER_EPOCH, 0);
1094
+
1095
+ /* Wait for consumer to handle error and rejoin */
1096
+ rd_usleep(500 * 1000, 0);
1097
+
1098
+ /* Verify heartbeats continue (rejoin happened) */
1099
+ found_heartbeats = wait_share_heartbeats(mcluster, 2, 1000);
1100
+ TEST_ASSERT(
1101
+ found_heartbeats >= 1,
1102
+ "Expected heartbeats to continue after FENCED_MEMBER_EPOCH, "
1103
+ "got %d",
1104
+ found_heartbeats);
1105
+
1106
+ /* Verify consumer eventually gets assignment back */
1107
+ TEST_ASSERT(wait_assignment_count(share_c, 3, 10000) == 3,
1108
+ "Expected 3 partitions after rejoin");
1109
+
1110
+ /* Cleanup */
1111
+ test_share_consumer_close(share_c);
1112
+ test_share_destroy(share_c);
1113
+
1114
+ rd_kafka_mock_stop_request_tracking(mcluster);
1115
+ test_mock_cluster_destroy(mcluster);
1116
+
1117
+ SUB_TEST_PASS();
1118
+ }
1119
+
1120
+ /**
1121
+ * @brief COORDINATOR_NOT_AVAILABLE error handling.
1122
+ *
1123
+ * When a consumer receives COORDINATOR_NOT_AVAILABLE, it should retry
1124
+ * (retriable error).
1125
+ */
1126
+ static void do_test_coordinator_not_available_error(void) {
1127
+ rd_kafka_mock_cluster_t *mcluster;
1128
+ const char *bootstraps;
1129
+ rd_kafka_topic_partition_list_t *subscription;
1130
+ rd_kafka_share_t *share_c;
1131
+ int found_heartbeats;
1132
+ const char *topic = test_mk_topic_name(__FUNCTION__, 0);
1133
+ const char *group = "test-share-group-coord-unavail";
1134
+
1135
+ SUB_TEST_QUICK();
1136
+
1137
+ /* Setup */
1138
+ mcluster = test_mock_cluster_new(1, &bootstraps);
1139
+ rd_kafka_mock_topic_create(mcluster, topic, 3, 1);
1140
+
1141
+ share_c = create_share_consumer(bootstraps, group);
1142
+
1143
+ subscription = rd_kafka_topic_partition_list_new(1);
1144
+ rd_kafka_topic_partition_list_add(subscription, topic,
1145
+ RD_KAFKA_PARTITION_UA);
1146
+
1147
+ rd_kafka_mock_start_request_tracking(mcluster);
1148
+ TEST_CALL_ERR__(rd_kafka_share_subscribe(share_c, subscription));
1149
+ rd_kafka_topic_partition_list_destroy(subscription);
1150
+
1151
+ /* Wait for initial join and assignment */
1152
+ wait_share_heartbeats(mcluster, 1, 1000);
1153
+ TEST_ASSERT(wait_assignment_count(share_c, 3, 10000) == 3,
1154
+ "Expected 3 partitions initially");
1155
+
1156
+ /* Inject COORDINATOR_NOT_AVAILABLE error (transient) */
1157
+ rd_kafka_mock_broker_push_request_error_rtts(
1158
+ mcluster, 1, RD_KAFKAP_ShareGroupHeartbeat, 1,
1159
+ RD_KAFKA_RESP_ERR_COORDINATOR_NOT_AVAILABLE, 0);
1160
+
1161
+ /* Wait for consumer to handle transient error and retry */
1162
+ rd_usleep(500 * 1000, 0);
1163
+
1164
+ /* Verify heartbeats continue after transient error */
1165
+ found_heartbeats = wait_share_heartbeats(mcluster, 2, 1000);
1166
+ TEST_ASSERT(
1167
+ found_heartbeats >= 1,
1168
+ "Expected heartbeats to continue after COORDINATOR_NOT_AVAILABLE, "
1169
+ "got %d",
1170
+ found_heartbeats);
1171
+
1172
+ /* Verify consumer still has assignment */
1173
+ TEST_ASSERT(wait_assignment_count(share_c, 3, 10000) == 3,
1174
+ "Expected 3 partitions after retry");
1175
+
1176
+ /* Cleanup */
1177
+ test_share_consumer_close(share_c);
1178
+ test_share_destroy(share_c);
1179
+
1180
+ rd_kafka_mock_stop_request_tracking(mcluster);
1181
+ test_mock_cluster_destroy(mcluster);
1182
+
1183
+ SUB_TEST_PASS();
1184
+ }
1185
+
1186
+ /**
1187
+ * @brief NOT_COORDINATOR error handling.
1188
+ *
1189
+ * When a consumer receives NOT_COORDINATOR, it should find a new coordinator.
1190
+ */
1191
+ static void do_test_not_coordinator_error(void) {
1192
+ rd_kafka_mock_cluster_t *mcluster;
1193
+ const char *bootstraps;
1194
+ rd_kafka_topic_partition_list_t *subscription;
1195
+ rd_kafka_share_t *share_c;
1196
+ int found_heartbeats;
1197
+ const char *topic = test_mk_topic_name(__FUNCTION__, 0);
1198
+ const char *group = "test-share-group-not-coord";
1199
+
1200
+ SUB_TEST_QUICK();
1201
+
1202
+ /* Setup */
1203
+ mcluster = test_mock_cluster_new(1, &bootstraps);
1204
+ rd_kafka_mock_topic_create(mcluster, topic, 3, 1);
1205
+
1206
+ share_c = create_share_consumer(bootstraps, group);
1207
+
1208
+ subscription = rd_kafka_topic_partition_list_new(1);
1209
+ rd_kafka_topic_partition_list_add(subscription, topic,
1210
+ RD_KAFKA_PARTITION_UA);
1211
+
1212
+ rd_kafka_mock_start_request_tracking(mcluster);
1213
+ TEST_CALL_ERR__(rd_kafka_share_subscribe(share_c, subscription));
1214
+ rd_kafka_topic_partition_list_destroy(subscription);
1215
+
1216
+ /* Wait for initial join and assignment */
1217
+ wait_share_heartbeats(mcluster, 1, 1000);
1218
+ TEST_ASSERT(wait_assignment_count(share_c, 3, 10000) == 3,
1219
+ "Expected 3 partitions initially");
1220
+
1221
+ /* Inject NOT_COORDINATOR error */
1222
+ rd_kafka_mock_broker_push_request_error_rtts(
1223
+ mcluster, 1, RD_KAFKAP_ShareGroupHeartbeat, 1,
1224
+ RD_KAFKA_RESP_ERR_NOT_COORDINATOR, 0);
1225
+
1226
+ /* Wait for consumer to find new coordinator and continue.
1227
+ * NOT_COORDINATOR triggers coordinator rediscovery which may take
1228
+ * longer than COORDINATOR_NOT_AVAILABLE. */
1229
+ rd_usleep(500 * 1000, 0);
1230
+
1231
+ /* Verify heartbeats continue after finding coordinator */
1232
+ found_heartbeats = wait_share_heartbeats(mcluster, 2, 1000);
1233
+ TEST_ASSERT(found_heartbeats >= 1,
1234
+ "Expected heartbeats to continue after NOT_COORDINATOR, "
1235
+ "got %d",
1236
+ found_heartbeats);
1237
+
1238
+ /* Verify consumer still has assignment */
1239
+ TEST_ASSERT(wait_assignment_count(share_c, 3, 10000) == 3,
1240
+ "Expected 3 partitions after finding coordinator");
1241
+
1242
+ /* Cleanup */
1243
+ test_share_consumer_close(share_c);
1244
+ test_share_destroy(share_c);
1245
+
1246
+ rd_kafka_mock_stop_request_tracking(mcluster);
1247
+ test_mock_cluster_destroy(mcluster);
1248
+
1249
+ SUB_TEST_PASS();
1250
+ }
1251
+
1252
+ /**
1253
+ * @brief GROUP_AUTHORIZATION_FAILED error handling (fatal).
1254
+ *
1255
+ * When a consumer receives GROUP_AUTHORIZATION_FAILED, it should treat
1256
+ * it as a fatal error.
1257
+ */
1258
+ static void do_test_group_authorization_failed_error(void) {
1259
+ rd_kafka_mock_cluster_t *mcluster;
1260
+ const char *bootstraps;
1261
+ rd_kafka_topic_partition_list_t *subscription;
1262
+ rd_kafka_share_t *share_c;
1263
+ rd_kafka_error_t *error;
1264
+ const char *topic = test_mk_topic_name(__FUNCTION__, 0);
1265
+ const char *group = "test-share-group-auth-failed";
1266
+
1267
+ SUB_TEST_QUICK();
1268
+
1269
+ /* Setup */
1270
+ mcluster = test_mock_cluster_new(1, &bootstraps);
1271
+ rd_kafka_mock_topic_create(mcluster, topic, 3, 1);
1272
+
1273
+ share_c = create_share_consumer(bootstraps, group);
1274
+
1275
+ subscription = rd_kafka_topic_partition_list_new(1);
1276
+ rd_kafka_topic_partition_list_add(subscription, topic,
1277
+ RD_KAFKA_PARTITION_UA);
1278
+
1279
+ rd_kafka_mock_start_request_tracking(mcluster);
1280
+ TEST_CALL_ERR__(rd_kafka_share_subscribe(share_c, subscription));
1281
+ rd_kafka_topic_partition_list_destroy(subscription);
1282
+
1283
+ /* Wait for initial join */
1284
+ wait_share_heartbeats(mcluster, 1, 1000);
1285
+ rd_usleep(500 * 1000, 0);
1286
+
1287
+ /* Inject GROUP_AUTHORIZATION_FAILED error (fatal) */
1288
+ rd_kafka_mock_broker_push_request_error_rtts(
1289
+ mcluster, 1, RD_KAFKAP_ShareGroupHeartbeat, 1,
1290
+ RD_KAFKA_RESP_ERR_GROUP_AUTHORIZATION_FAILED, 0);
1291
+
1292
+ /* Wait for the fatal error to propagate via consume_batch. */
1293
+ error = wait_fatal_error(share_c, 5000);
1294
+ TEST_ASSERT(error != NULL,
1295
+ "Expected a fatal error but none received within timeout");
1296
+ TEST_ASSERT(rd_kafka_error_code(error) ==
1297
+ RD_KAFKA_RESP_ERR_GROUP_AUTHORIZATION_FAILED,
1298
+ "Expected GROUP_AUTHORIZATION_FAILED fatal error, got %s",
1299
+ rd_kafka_error_name(error));
1300
+ TEST_SAY("Consumer entered fatal state: %s\n",
1301
+ rd_kafka_error_string(error));
1302
+ rd_kafka_error_destroy(error);
1303
+
1304
+ /* Cleanup. Consumer is in fatal state, but close() still flushes
1305
+ * pending acks and leaves the share session, so it succeeds. */
1306
+ error = rd_kafka_share_consumer_close(share_c);
1307
+ TEST_ASSERT(!error, "Expected close to succeed, got %s",
1308
+ rd_kafka_error_name(error));
1309
+ test_share_destroy(share_c);
1310
+
1311
+ rd_kafka_mock_stop_request_tracking(mcluster);
1312
+ test_mock_cluster_destroy(mcluster);
1313
+
1314
+ SUB_TEST_PASS();
1315
+ }
1316
+
1317
+ /**
1318
+ * @brief GROUP_MAX_SIZE_REACHED error handling.
1319
+ *
1320
+ * When a new member tries to join and gets GROUP_MAX_SIZE_REACHED,
1321
+ * the error should be treated as fatal for that consumer.
1322
+ */
1323
+ static void do_test_group_max_size_reached_error(void) {
1324
+ rd_kafka_mock_cluster_t *mcluster;
1325
+ const char *bootstraps;
1326
+ rd_kafka_topic_partition_list_t *subscription;
1327
+ rd_kafka_share_t *share_c1, *share_c2;
1328
+ rd_kafka_error_t *error;
1329
+ const char *topic = test_mk_topic_name(__FUNCTION__, 0);
1330
+ const char *group = "test-share-group-max-size";
1331
+
1332
+ SUB_TEST_QUICK();
1333
+
1334
+ /* Setup */
1335
+ mcluster = test_mock_cluster_new(1, &bootstraps);
1336
+ rd_kafka_mock_topic_create(mcluster, topic, 4, 1);
1337
+
1338
+ subscription = rd_kafka_topic_partition_list_new(1);
1339
+ rd_kafka_topic_partition_list_add(subscription, topic,
1340
+ RD_KAFKA_PARTITION_UA);
1341
+
1342
+ /* First consumer joins successfully */
1343
+ share_c1 = create_share_consumer(bootstraps, group);
1344
+
1345
+ rd_kafka_mock_start_request_tracking(mcluster);
1346
+
1347
+ TEST_CALL_ERR__(rd_kafka_share_subscribe(share_c1, subscription));
1348
+
1349
+ /* Wait for share_c1 to fully join and stabilize */
1350
+ TEST_ASSERT(wait_assignment_count(share_c1, 4, 10000) == 4,
1351
+ "Expected share_c1 to have 4 partitions");
1352
+
1353
+ /* Push multiple GROUP_MAX_SIZE_REACHED errors so that even if
1354
+ * share_c1's regular heartbeat consumes some, share_c2's join heartbeat
1355
+ * will also get one. The Java test uses server-side maxSize=1
1356
+ * config; we simulate by injecting errors for all heartbeats. */
1357
+ rd_kafka_mock_broker_push_request_error_rtts(
1358
+ mcluster, 1, RD_KAFKAP_ShareGroupHeartbeat, 5,
1359
+ RD_KAFKA_RESP_ERR_GROUP_MAX_SIZE_REACHED, 0);
1360
+
1361
+ /* Create second consumer - should be rejected */
1362
+ share_c2 = create_share_consumer(bootstraps, group);
1363
+ TEST_CALL_ERR__(rd_kafka_share_subscribe(share_c2, subscription));
1364
+
1365
+ /* Wait for the fatal error to propagate via consume_batch. */
1366
+ error = wait_fatal_error(share_c2, 5000);
1367
+ TEST_ASSERT(error != NULL,
1368
+ "Expected a fatal error but none received within timeout");
1369
+ TEST_ASSERT(rd_kafka_error_code(error) ==
1370
+ RD_KAFKA_RESP_ERR_GROUP_MAX_SIZE_REACHED,
1371
+ "Expected GROUP_MAX_SIZE_REACHED fatal error, got %s",
1372
+ rd_kafka_error_name(error));
1373
+ TEST_SAY("share consumer 2 correctly rejected with fatal error: %s\n",
1374
+ rd_kafka_error_string(error));
1375
+ rd_kafka_error_destroy(error);
1376
+
1377
+ rd_kafka_topic_partition_list_destroy(subscription);
1378
+
1379
+ /* Cleanup. share_c2 is in fatal state, but close() no longer
1380
+ * early-returns the stored fatal error, so it succeeds. */
1381
+ test_share_consumer_close(share_c1);
1382
+ rd_kafka_error_t *c2_close_error =
1383
+ rd_kafka_share_consumer_close(share_c2);
1384
+ TEST_ASSERT(!c2_close_error,
1385
+ "Expected share_c2 close to succeed, "
1386
+ "got %s",
1387
+ rd_kafka_error_name(c2_close_error));
1388
+ test_share_destroy(share_c1);
1389
+ test_share_destroy(share_c2);
1390
+
1391
+ rd_kafka_mock_stop_request_tracking(mcluster);
1392
+ test_mock_cluster_destroy(mcluster);
1393
+
1394
+ SUB_TEST_PASS();
1395
+ }
1396
+
1397
+ /**
1398
+ * @brief Member rejoin with epoch zero.
1399
+ *
1400
+ * A member in stable state (epoch > 0) that sends heartbeat with epoch=0
1401
+ * should be treated as a rejoin and assigned a new member ID.
1402
+ */
1403
+ static void do_test_member_rejoin_with_epoch_zero(void) {
1404
+ rd_kafka_mock_cluster_t *mcluster;
1405
+ const char *bootstraps;
1406
+ rd_kafka_topic_partition_list_t *subscription, *assignment;
1407
+ rd_kafka_share_t *share_c;
1408
+ int found_heartbeats;
1409
+ const char *topic = test_mk_topic_name(__FUNCTION__, 0);
1410
+ const char *group = "test-share-group-rejoin";
1411
+
1412
+ SUB_TEST_QUICK();
1413
+
1414
+ /* Setup */
1415
+ mcluster = test_mock_cluster_new(1, &bootstraps);
1416
+ rd_kafka_mock_topic_create(mcluster, topic, 3, 1);
1417
+
1418
+ share_c = create_share_consumer(bootstraps, group);
1419
+
1420
+ subscription = rd_kafka_topic_partition_list_new(1);
1421
+ rd_kafka_topic_partition_list_add(subscription, topic,
1422
+ RD_KAFKA_PARTITION_UA);
1423
+
1424
+ rd_kafka_mock_start_request_tracking(mcluster);
1425
+ TEST_CALL_ERR__(rd_kafka_share_subscribe(share_c, subscription));
1426
+ rd_kafka_topic_partition_list_destroy(subscription);
1427
+
1428
+ /* Wait for initial join and assignment */
1429
+ wait_share_heartbeats(mcluster, 1, 1000);
1430
+ rd_usleep(500 * 1000, 0);
1431
+
1432
+ /* Verify initial assignment (member is now in stable state) */
1433
+ TEST_CALL_ERR__(rd_kafka_assignment(test_share_consumer_get_rk(share_c),
1434
+ &assignment));
1435
+ TEST_ASSERT(assignment->cnt == 3,
1436
+ "Expected 3 partitions initially, got %d", assignment->cnt);
1437
+ rd_kafka_topic_partition_list_destroy(assignment);
1438
+
1439
+ /* Force a rejoin by injecting UNKNOWN_MEMBER_ID error
1440
+ * This will cause client to rejoin with epoch=0 */
1441
+ rd_kafka_mock_broker_push_request_error_rtts(
1442
+ mcluster, 1, RD_KAFKAP_ShareGroupHeartbeat, 1,
1443
+ RD_KAFKA_RESP_ERR_UNKNOWN_MEMBER_ID, 0);
1444
+
1445
+ /* Wait for consumer to rejoin with epoch=0 */
1446
+ rd_usleep(2000 * 1000, 0);
1447
+
1448
+ /* Verify rejoin heartbeats */
1449
+ found_heartbeats = wait_share_heartbeats(mcluster, 2, 1000);
1450
+ TEST_ASSERT(found_heartbeats >= 1, "Expected rejoin heartbeats, got %d",
1451
+ found_heartbeats);
1452
+
1453
+ /* Verify consumer gets assignment back */
1454
+ TEST_ASSERT(wait_assignment_count(share_c, 3, 10000) == 3,
1455
+ "Expected 3 partitions after rejoin");
1456
+
1457
+ /* Cleanup */
1458
+ test_share_consumer_close(share_c);
1459
+ test_share_destroy(share_c);
1460
+
1461
+ rd_kafka_mock_stop_request_tracking(mcluster);
1462
+ test_mock_cluster_destroy(mcluster);
1463
+
1464
+ SUB_TEST_PASS();
1465
+ }
1466
+
1467
+ /**
1468
+ * @brief Leaving member bumps group epoch.
1469
+ *
1470
+ * When a member sends leave heartbeat (epoch=-1), the group epoch should
1471
+ * be bumped and remaining members should get updated assignment.
1472
+ */
1473
+ static void do_test_leaving_member_bumps_group_epoch(void) {
1474
+ rd_kafka_mock_cluster_t *mcluster;
1475
+ const char *bootstraps;
1476
+ rd_kafka_topic_partition_list_t *subscription;
1477
+ rd_kafka_topic_partition_list_t *share_c1_assign, *share_c2_assign;
1478
+ rd_kafka_share_t *share_c1, *share_c2;
1479
+ int64_t dl;
1480
+ const char *topic = test_mk_topic_name(__FUNCTION__, 0);
1481
+ const char *group = "test-share-group-leave-epoch";
1482
+
1483
+ SUB_TEST_QUICK();
1484
+
1485
+ /* Setup */
1486
+ mcluster = test_mock_cluster_new(1, &bootstraps);
1487
+ rd_kafka_mock_topic_create(mcluster, topic, 4, 1);
1488
+
1489
+ /* Set heartbeat interval shorter than session timeout so both
1490
+ * consumers can heartbeat frequently enough for the rebalance
1491
+ * to complete before we check assignments. */
1492
+ rd_kafka_mock_sharegroup_set_heartbeat_interval(mcluster, 1000);
1493
+
1494
+ share_c1 = create_share_consumer(bootstraps, group);
1495
+ share_c2 = create_share_consumer(bootstraps, group);
1496
+
1497
+ subscription = rd_kafka_topic_partition_list_new(1);
1498
+ rd_kafka_topic_partition_list_add(subscription, topic,
1499
+ RD_KAFKA_PARTITION_UA);
1500
+
1501
+ rd_kafka_mock_start_request_tracking(mcluster);
1502
+
1503
+ TEST_CALL_ERR__(rd_kafka_share_subscribe(share_c1, subscription));
1504
+ TEST_CALL_ERR__(rd_kafka_share_subscribe(share_c2, subscription));
1505
+ rd_kafka_topic_partition_list_destroy(subscription);
1506
+
1507
+ /* Wait for both to join and rebalance to complete */
1508
+ dl = test_clock() + 15000 * 1000;
1509
+ while (test_clock() < dl) {
1510
+ TEST_CALL_ERR__(rd_kafka_assignment(
1511
+ test_share_consumer_get_rk(share_c1), &share_c1_assign));
1512
+ TEST_CALL_ERR__(rd_kafka_assignment(
1513
+ test_share_consumer_get_rk(share_c2), &share_c2_assign));
1514
+ if (share_c1_assign->cnt + share_c2_assign->cnt == 4 &&
1515
+ share_c1_assign->cnt > 0 && share_c2_assign->cnt > 0) {
1516
+ rd_kafka_topic_partition_list_destroy(share_c1_assign);
1517
+ rd_kafka_topic_partition_list_destroy(share_c2_assign);
1518
+ break;
1519
+ }
1520
+ rd_kafka_topic_partition_list_destroy(share_c1_assign);
1521
+ rd_kafka_topic_partition_list_destroy(share_c2_assign);
1522
+ rd_usleep(200 * 1000, 0);
1523
+ }
1524
+
1525
+ /* C2 leaves (sends epoch=-1 leave heartbeat) */
1526
+ test_share_consumer_close(share_c2);
1527
+ test_share_destroy(share_c2);
1528
+
1529
+ /* Wait for C1 to get all partitions after C2 left */
1530
+ TEST_ASSERT(wait_assignment_count(share_c1, 4, 10000) == 4,
1531
+ "C1 should have all 4 partitions after C2 left");
1532
+
1533
+ /* Cleanup */
1534
+ test_share_consumer_close(share_c1);
1535
+ test_share_destroy(share_c1);
1536
+
1537
+ rd_kafka_mock_stop_request_tracking(mcluster);
1538
+ test_mock_cluster_destroy(mcluster);
1539
+
1540
+ SUB_TEST_PASS();
1541
+ }
1542
+
1543
+ /**
1544
+ * @brief Partition assignment with changing topics.
1545
+ *
1546
+ * Note: This test is limited in mock broker - we can test initial assignment
1547
+ * with multiple topics in subscription, but cannot dynamically add topics.
1548
+ */
1549
+ static void do_test_partition_assignment_with_multiple_topics(void) {
1550
+ rd_kafka_mock_cluster_t *mcluster;
1551
+ const char *bootstraps;
1552
+ rd_kafka_topic_partition_list_t *subscription, *assignment;
1553
+ rd_kafka_share_t *share_c;
1554
+ const char *topic1 = "test-multi-topic-1";
1555
+ const char *topic2 = "test-multi-topic-2";
1556
+ const char *group = "test-share-group-multi-topic-sub";
1557
+ int topic1_count = 0, topic2_count = 0, i;
1558
+
1559
+ SUB_TEST_QUICK();
1560
+
1561
+ /* Setup - create two topics */
1562
+ mcluster = test_mock_cluster_new(1, &bootstraps);
1563
+ rd_kafka_mock_topic_create(mcluster, topic1, 3, 1);
1564
+ rd_kafka_mock_topic_create(mcluster, topic2, 2, 1);
1565
+
1566
+ share_c = create_share_consumer(bootstraps, group);
1567
+
1568
+ /* Subscribe to both topics */
1569
+ subscription = rd_kafka_topic_partition_list_new(2);
1570
+ rd_kafka_topic_partition_list_add(subscription, topic1,
1571
+ RD_KAFKA_PARTITION_UA);
1572
+ rd_kafka_topic_partition_list_add(subscription, topic2,
1573
+ RD_KAFKA_PARTITION_UA);
1574
+
1575
+ rd_kafka_mock_start_request_tracking(mcluster);
1576
+ TEST_CALL_ERR__(rd_kafka_share_subscribe(share_c, subscription));
1577
+ rd_kafka_topic_partition_list_destroy(subscription);
1578
+
1579
+ /* Wait for join and assignment */
1580
+ TEST_ASSERT(wait_assignment_count(share_c, 5, 10000) == 5,
1581
+ "Expected 5 partitions (3+2)");
1582
+
1583
+ /* Verify assignment includes partitions from both topics */
1584
+ TEST_CALL_ERR__(rd_kafka_assignment(test_share_consumer_get_rk(share_c),
1585
+ &assignment));
1586
+
1587
+ /* Count partitions per topic */
1588
+ for (i = 0; i < assignment->cnt; i++) {
1589
+ if (strcmp(assignment->elems[i].topic, topic1) == 0)
1590
+ topic1_count++;
1591
+ else if (strcmp(assignment->elems[i].topic, topic2) == 0)
1592
+ topic2_count++;
1593
+ }
1594
+ TEST_ASSERT(topic1_count == 3,
1595
+ "Expected 3 partitions from topic1, got %d", topic1_count);
1596
+ TEST_ASSERT(topic2_count == 2,
1597
+ "Expected 2 partitions from topic2, got %d", topic2_count);
1598
+ rd_kafka_topic_partition_list_destroy(assignment);
1599
+
1600
+ /* Cleanup */
1601
+ test_share_consumer_close(share_c);
1602
+ test_share_destroy(share_c);
1603
+
1604
+ rd_kafka_mock_stop_request_tracking(mcluster);
1605
+ test_mock_cluster_destroy(mcluster);
1606
+
1607
+ SUB_TEST_PASS();
1608
+ }
1609
+
1610
+ /**
1611
+ * @brief Multiple members partition distribution.
1612
+ *
1613
+ * N members join group subscribed to topic with M partitions.
1614
+ * Verify partitions are distributed fairly (all members get some).
1615
+ * Note: Share groups may allow the same partition to be assigned to
1616
+ * multiple consumers, so we check for fair distribution rather than
1617
+ * exclusive assignment.
1618
+ */
1619
+ static void do_test_multiple_members_partition_distribution(void) {
1620
+ rd_kafka_mock_cluster_t *mcluster;
1621
+ const char *bootstraps;
1622
+ rd_kafka_topic_partition_list_t *subscription;
1623
+ rd_kafka_topic_partition_list_t *share_c1_assign, *share_c2_assign,
1624
+ *share_c3_assign;
1625
+ rd_kafka_share_t *share_c1, *share_c2, *share_c3;
1626
+ const char *topic = test_mk_topic_name(__FUNCTION__, 0);
1627
+ const char *group = "test-share-group-distribution";
1628
+ int total_partitions = 0;
1629
+ rd_bool_t converged;
1630
+ int64_t dl;
1631
+
1632
+ SUB_TEST_QUICK();
1633
+
1634
+ /* Setup - 6 partitions, 3 consumers */
1635
+ mcluster = test_mock_cluster_new(1, &bootstraps);
1636
+ rd_kafka_mock_topic_create(mcluster, topic, 6, 1);
1637
+
1638
+ share_c1 = create_share_consumer(bootstraps, group);
1639
+ share_c2 = create_share_consumer(bootstraps, group);
1640
+ share_c3 = create_share_consumer(bootstraps, group);
1641
+
1642
+ subscription = rd_kafka_topic_partition_list_new(1);
1643
+ rd_kafka_topic_partition_list_add(subscription, topic,
1644
+ RD_KAFKA_PARTITION_UA);
1645
+
1646
+ rd_kafka_mock_start_request_tracking(mcluster);
1647
+
1648
+ TEST_CALL_ERR__(rd_kafka_share_subscribe(share_c1, subscription));
1649
+ TEST_CALL_ERR__(rd_kafka_share_subscribe(share_c2, subscription));
1650
+ TEST_CALL_ERR__(rd_kafka_share_subscribe(share_c3, subscription));
1651
+ rd_kafka_topic_partition_list_destroy(subscription);
1652
+
1653
+ /* Wait until all 3 consumers have converged on a fair distribution
1654
+ * (each member holds at least 1 partition and the total is >= 6) and
1655
+ * assert on the snapshot that satisfied that check.
1656
+ *
1657
+ * The assignment is driven by the heartbeat thread and churns while
1658
+ * the three members are still joining, so a member may transiently
1659
+ * hold partitions and then have them reassigned. Re-reading the
1660
+ * assignment after convergence would race against that churn and
1661
+ * observe an intermediate state, so the lists that satisfied the
1662
+ * check are kept and asserted on directly. */
1663
+ TEST_SAY(
1664
+ "Waiting for 3 members to converge on a fair distribution "
1665
+ "of 6 partitions...\n");
1666
+ share_c1_assign = share_c2_assign = share_c3_assign = NULL;
1667
+ converged = rd_false;
1668
+ dl = test_clock() + 15000 * 1000;
1669
+ while (test_clock() < dl) {
1670
+ TEST_CALL_ERR__(rd_kafka_assignment(
1671
+ test_share_consumer_get_rk(share_c1), &share_c1_assign));
1672
+ TEST_CALL_ERR__(rd_kafka_assignment(
1673
+ test_share_consumer_get_rk(share_c2), &share_c2_assign));
1674
+ TEST_CALL_ERR__(rd_kafka_assignment(
1675
+ test_share_consumer_get_rk(share_c3), &share_c3_assign));
1676
+ total_partitions = share_c1_assign->cnt + share_c2_assign->cnt +
1677
+ share_c3_assign->cnt;
1678
+ TEST_SAY(
1679
+ "Current assignment: share consumer 1=%d, "
1680
+ "share consumer 2=%d, share consumer 3=%d (total=%d)\n",
1681
+ share_c1_assign->cnt, share_c2_assign->cnt,
1682
+ share_c3_assign->cnt, total_partitions);
1683
+ if (share_c1_assign->cnt >= 1 && share_c2_assign->cnt >= 1 &&
1684
+ share_c3_assign->cnt >= 1 && total_partitions >= 6) {
1685
+ converged = rd_true;
1686
+ TEST_SAY(
1687
+ "All 3 members converged, fair distribution "
1688
+ "reached\n");
1689
+ break;
1690
+ }
1691
+ rd_kafka_topic_partition_list_destroy(share_c1_assign);
1692
+ rd_kafka_topic_partition_list_destroy(share_c2_assign);
1693
+ rd_kafka_topic_partition_list_destroy(share_c3_assign);
1694
+ TEST_SAY("Not yet converged, retrying in 200ms...\n");
1695
+ rd_usleep(200 * 1000, 0);
1696
+ }
1697
+
1698
+ TEST_ASSERT(converged,
1699
+ "Consumers did not converge on a fair distribution "
1700
+ "(each member >= 1 partition, total >= 6) within timeout");
1701
+
1702
+ TEST_SAY(
1703
+ "Partition distribution: share consumer 1=%d, share consumer 2=%d, "
1704
+ "share consumer 3=%d (total=%d)\n",
1705
+ share_c1_assign->cnt, share_c2_assign->cnt, share_c3_assign->cnt,
1706
+ total_partitions);
1707
+
1708
+ rd_kafka_topic_partition_list_destroy(share_c1_assign);
1709
+ rd_kafka_topic_partition_list_destroy(share_c2_assign);
1710
+ rd_kafka_topic_partition_list_destroy(share_c3_assign);
1711
+
1712
+ /* Cleanup */
1713
+ test_share_consumer_close(share_c1);
1714
+ test_share_consumer_close(share_c2);
1715
+ test_share_consumer_close(share_c3);
1716
+ test_share_destroy(share_c1);
1717
+ test_share_destroy(share_c2);
1718
+ test_share_destroy(share_c3);
1719
+
1720
+ rd_kafka_mock_stop_request_tracking(mcluster);
1721
+ test_mock_cluster_destroy(mcluster);
1722
+
1723
+ SUB_TEST_PASS();
1724
+ }
1725
+
1726
+ /**
1727
+ * @brief Heartbeat successful response completes leave.
1728
+ *
1729
+ * When a member sends leave heartbeat (epoch=-1), verify successful
1730
+ * response completes the leave.
1731
+ */
1732
+ static void do_test_leave_heartbeat_completes_successfully(void) {
1733
+ rd_kafka_mock_cluster_t *mcluster;
1734
+ const char *bootstraps;
1735
+ rd_kafka_topic_partition_list_t *subscription;
1736
+ rd_kafka_share_t *share_c;
1737
+ rd_kafka_error_t *error;
1738
+ const char *topic = test_mk_topic_name(__FUNCTION__, 0);
1739
+ const char *group = "test-share-group-leave-success";
1740
+
1741
+ SUB_TEST_QUICK();
1742
+
1743
+ /* Setup */
1744
+ mcluster = test_mock_cluster_new(1, &bootstraps);
1745
+ rd_kafka_mock_topic_create(mcluster, topic, 3, 1);
1746
+
1747
+ share_c = create_share_consumer(bootstraps, group);
1748
+
1749
+ subscription = rd_kafka_topic_partition_list_new(1);
1750
+ rd_kafka_topic_partition_list_add(subscription, topic,
1751
+ RD_KAFKA_PARTITION_UA);
1752
+
1753
+ rd_kafka_mock_start_request_tracking(mcluster);
1754
+ TEST_CALL_ERR__(rd_kafka_share_subscribe(share_c, subscription));
1755
+ rd_kafka_topic_partition_list_destroy(subscription);
1756
+
1757
+ /* Wait for join and assignment */
1758
+ wait_share_heartbeats(mcluster, 1, 1000);
1759
+ TEST_ASSERT(wait_assignment_count(share_c, 3, 10000) == 3,
1760
+ "Expected 3 partitions initially");
1761
+
1762
+ /* Leave group - should send leave heartbeat and complete.
1763
+ * Note: After close(), we cannot call rd_kafka_assignment() anymore
1764
+ * as the broker handle is destroyed. */
1765
+ error = rd_kafka_share_consumer_close(share_c);
1766
+ TEST_ASSERT(!error, "Expected close to succeed, got %s",
1767
+ rd_kafka_error_name(error));
1768
+
1769
+ /* Cleanup */
1770
+ test_share_destroy(share_c);
1771
+
1772
+ rd_kafka_mock_stop_request_tracking(mcluster);
1773
+ test_mock_cluster_destroy(mcluster);
1774
+
1775
+ SUB_TEST_PASS();
1776
+ }
1777
+
1778
+ /**
1779
+ * @brief Heartbeat failed response during leave still completes.
1780
+ *
1781
+ * When a member sends leave heartbeat and gets an error response,
1782
+ * the leave should still complete (best effort).
1783
+ */
1784
+ static void do_test_leave_heartbeat_completes_on_error(void) {
1785
+ rd_kafka_mock_cluster_t *mcluster;
1786
+ const char *bootstraps;
1787
+ rd_kafka_topic_partition_list_t *subscription;
1788
+ rd_kafka_share_t *share_c;
1789
+ rd_kafka_error_t *error;
1790
+ const char *topic = test_mk_topic_name(__FUNCTION__, 0);
1791
+ const char *group = "test-share-group-leave-error";
1792
+
1793
+ SUB_TEST_QUICK();
1794
+
1795
+ /* Setup */
1796
+ mcluster = test_mock_cluster_new(1, &bootstraps);
1797
+ rd_kafka_mock_topic_create(mcluster, topic, 3, 1);
1798
+
1799
+ share_c = create_share_consumer(bootstraps, group);
1800
+
1801
+ subscription = rd_kafka_topic_partition_list_new(1);
1802
+ rd_kafka_topic_partition_list_add(subscription, topic,
1803
+ RD_KAFKA_PARTITION_UA);
1804
+
1805
+ rd_kafka_mock_start_request_tracking(mcluster);
1806
+ TEST_CALL_ERR__(rd_kafka_share_subscribe(share_c, subscription));
1807
+ rd_kafka_topic_partition_list_destroy(subscription);
1808
+
1809
+ /* Wait for join and assignment */
1810
+ wait_share_heartbeats(mcluster, 1, 1000);
1811
+ TEST_ASSERT(wait_assignment_count(share_c, 3, 10000) == 3,
1812
+ "Expected 3 partitions initially");
1813
+
1814
+ /* Inject error for the leave heartbeat */
1815
+ rd_kafka_mock_broker_push_request_error_rtts(
1816
+ mcluster, 1, RD_KAFKAP_ShareGroupHeartbeat, 1,
1817
+ RD_KAFKA_RESP_ERR_COORDINATOR_NOT_AVAILABLE, 0);
1818
+
1819
+ /* Leave group - should still complete despite error (best effort).
1820
+ * The key behavior: close() must not hang even when the leave
1821
+ * heartbeat gets an error response. */
1822
+ error = rd_kafka_share_consumer_close(share_c);
1823
+ /* Close completed (didn't hang) - this is the primary assertion.
1824
+ * The return code may vary depending on whether the error was
1825
+ * processed during leave. */
1826
+ TEST_SAY("Leave completed with: %s (didn't hang - correct)\n",
1827
+ rd_kafka_error_name(error));
1828
+ if (error)
1829
+ rd_kafka_error_destroy(error);
1830
+
1831
+ /* Cleanup */
1832
+ test_share_destroy(share_c);
1833
+
1834
+ rd_kafka_mock_stop_request_tracking(mcluster);
1835
+ test_mock_cluster_destroy(mcluster);
1836
+
1837
+ SUB_TEST_PASS();
1838
+ }
1839
+
1840
+ /**
1841
+ * @brief Subscription change updates assignment.
1842
+ *
1843
+ * Consumer subscribed to topic A, change subscription to topic B,
1844
+ * verify assignment updates.
1845
+ */
1846
+ static void do_test_subscription_change(void) {
1847
+ rd_kafka_mock_cluster_t *mcluster;
1848
+ const char *bootstraps;
1849
+ rd_kafka_topic_partition_list_t *subscription, *assignment;
1850
+ rd_kafka_share_t *share_c;
1851
+ int found_topicA = 0, found_topicB = 0, i;
1852
+ int64_t dl;
1853
+ const char *topicA = "test-sub-change-topic-A";
1854
+ const char *topicB = "test-sub-change-topic-B";
1855
+ const char *group = "test-share-group-sub-change";
1856
+
1857
+ SUB_TEST_QUICK();
1858
+
1859
+ /* Setup */
1860
+ mcluster = test_mock_cluster_new(1, &bootstraps);
1861
+ rd_kafka_mock_topic_create(mcluster, topicA, 2, 1);
1862
+ rd_kafka_mock_topic_create(mcluster, topicB, 3, 1);
1863
+
1864
+ share_c = create_share_consumer(bootstraps, group);
1865
+
1866
+ /* First subscription: topic A */
1867
+ subscription = rd_kafka_topic_partition_list_new(1);
1868
+ rd_kafka_topic_partition_list_add(subscription, topicA,
1869
+ RD_KAFKA_PARTITION_UA);
1870
+
1871
+ rd_kafka_mock_start_request_tracking(mcluster);
1872
+ TEST_CALL_ERR__(rd_kafka_share_subscribe(share_c, subscription));
1873
+ rd_kafka_topic_partition_list_destroy(subscription);
1874
+
1875
+ /* Wait for assignment to topic A */
1876
+ TEST_ASSERT(wait_assignment_count(share_c, 2, 10000) == 2,
1877
+ "Expected 2 partitions from topicA");
1878
+
1879
+ /* Verify assignment has topic A only */
1880
+ TEST_CALL_ERR__(rd_kafka_assignment(test_share_consumer_get_rk(share_c),
1881
+ &assignment));
1882
+ for (i = 0; i < assignment->cnt; i++) {
1883
+ TEST_ASSERT(strcmp(assignment->elems[i].topic, topicA) == 0,
1884
+ "Expected topicA, got %s",
1885
+ assignment->elems[i].topic);
1886
+ }
1887
+ rd_kafka_topic_partition_list_destroy(assignment);
1888
+
1889
+ /* Change subscription to topic B */
1890
+ subscription = rd_kafka_topic_partition_list_new(1);
1891
+ rd_kafka_topic_partition_list_add(subscription, topicB,
1892
+ RD_KAFKA_PARTITION_UA);
1893
+ TEST_CALL_ERR__(rd_kafka_share_subscribe(share_c, subscription));
1894
+ rd_kafka_topic_partition_list_destroy(subscription);
1895
+
1896
+ /* Wait for assignment to switch to topic B (3 partitions) */
1897
+ dl = test_clock() + 15000 * 1000;
1898
+ while (test_clock() < dl) {
1899
+ TEST_CALL_ERR__(rd_kafka_assignment(
1900
+ test_share_consumer_get_rk(share_c), &assignment));
1901
+ found_topicA = 0;
1902
+ found_topicB = 0;
1903
+ for (i = 0; i < assignment->cnt; i++) {
1904
+ if (strcmp(assignment->elems[i].topic, topicA) == 0)
1905
+ found_topicA++;
1906
+ else if (strcmp(assignment->elems[i].topic, topicB) ==
1907
+ 0)
1908
+ found_topicB++;
1909
+ }
1910
+ rd_kafka_topic_partition_list_destroy(assignment);
1911
+ if (found_topicA == 0 && found_topicB == 3)
1912
+ break;
1913
+ rd_usleep(200 * 1000, 0);
1914
+ }
1915
+ TEST_ASSERT(found_topicA == 0,
1916
+ "Expected 0 partitions from topicA after change, got %d",
1917
+ found_topicA);
1918
+ TEST_ASSERT(found_topicB == 3,
1919
+ "Expected 3 partitions from topicB after change, got %d",
1920
+ found_topicB);
1921
+
1922
+ /* Cleanup */
1923
+ test_share_consumer_close(share_c);
1924
+ test_share_destroy(share_c);
1925
+
1926
+ rd_kafka_mock_stop_request_tracking(mcluster);
1927
+ test_mock_cluster_destroy(mcluster);
1928
+
1929
+ SUB_TEST_PASS();
1930
+ }
1931
+
1932
+ /**
1933
+ * @brief GROUP_ID_NOT_FOUND while unsubscribed is benign.
1934
+ *
1935
+ * When a member that has already unsubscribed receives GROUP_ID_NOT_FOUND,
1936
+ * it should be treated as benign (the group may have been auto-deleted).
1937
+ * This should NOT cause a fatal error.
1938
+ */
1939
+ static void do_test_group_id_not_found_while_unsubscribed(void) {
1940
+ rd_kafka_mock_cluster_t *mcluster;
1941
+ const char *bootstraps;
1942
+ rd_kafka_topic_partition_list_t *subscription;
1943
+ rd_kafka_share_t *share_c;
1944
+ rd_kafka_resp_err_t fatal_err;
1945
+ rd_kafka_error_t *error;
1946
+ char errstr[256];
1947
+ const char *topic = test_mk_topic_name(__FUNCTION__, 0);
1948
+ const char *group = "test-share-group-id-not-found-unsub";
1949
+
1950
+ SUB_TEST_QUICK();
1951
+
1952
+ /* Setup */
1953
+ mcluster = test_mock_cluster_new(1, &bootstraps);
1954
+ rd_kafka_mock_topic_create(mcluster, topic, 3, 1);
1955
+
1956
+ share_c = create_share_consumer(bootstraps, group);
1957
+
1958
+ subscription = rd_kafka_topic_partition_list_new(1);
1959
+ rd_kafka_topic_partition_list_add(subscription, topic,
1960
+ RD_KAFKA_PARTITION_UA);
1961
+
1962
+ rd_kafka_mock_start_request_tracking(mcluster);
1963
+ TEST_CALL_ERR__(rd_kafka_share_subscribe(share_c, subscription));
1964
+ rd_kafka_topic_partition_list_destroy(subscription);
1965
+
1966
+ /* Wait for initial join and assignment */
1967
+ wait_share_heartbeats(mcluster, 1, 1000);
1968
+ TEST_ASSERT(wait_assignment_count(share_c, 3, 10000) == 3,
1969
+ "Expected 3 partitions initially");
1970
+
1971
+ /* Unsubscribe first to transition to unsubscribed state.
1972
+ * The Java test has member in UNSUBSCRIBED state when the
1973
+ * error arrives. */
1974
+ TEST_CALL_ERR__(rd_kafka_share_unsubscribe(share_c));
1975
+ rd_usleep(500 * 1000, 0);
1976
+
1977
+ /* Now inject GROUP_ID_NOT_FOUND.
1978
+ * Since the member is unsubscribed, this should be benign. */
1979
+ rd_kafka_mock_broker_push_request_error_rtts(
1980
+ mcluster, 1, RD_KAFKAP_ShareGroupHeartbeat, 3,
1981
+ RD_KAFKA_RESP_ERR_GROUP_ID_NOT_FOUND, 0);
1982
+
1983
+ /* Wait for the error to be processed */
1984
+ rd_usleep(500 * 1000, 0);
1985
+
1986
+ /* Verify consumer is NOT in fatal state - error should be benign */
1987
+ fatal_err = rd_kafka_fatal_error(test_share_consumer_get_rk(share_c),
1988
+ errstr, sizeof(errstr));
1989
+ TEST_ASSERT(fatal_err == RD_KAFKA_RESP_ERR_NO_ERROR,
1990
+ "Expected no fatal error when GROUP_ID_NOT_FOUND arrives "
1991
+ "while unsubscribed, but got: %s (%s)",
1992
+ rd_kafka_err2str(fatal_err), errstr);
1993
+
1994
+ /* Close consumer */
1995
+ error = rd_kafka_share_consumer_close(share_c);
1996
+ TEST_SAY("Close returned: %s\n", rd_kafka_error_name(error));
1997
+ if (error)
1998
+ rd_kafka_error_destroy(error);
1999
+
2000
+ /* Cleanup */
2001
+ test_share_destroy(share_c);
2002
+
2003
+ rd_kafka_mock_stop_request_tracking(mcluster);
2004
+ test_mock_cluster_destroy(mcluster);
2005
+
2006
+ SUB_TEST_PASS();
2007
+ }
2008
+
2009
+ /**
2010
+ * @brief GROUP_ID_NOT_FOUND while stable is fatal.
2011
+ *
2012
+ * When an active member (epoch > 0) receives GROUP_ID_NOT_FOUND,
2013
+ * it should be treated as a fatal error (group unexpectedly deleted).
2014
+ */
2015
+ static void do_test_group_id_not_found_while_stable_is_fatal(void) {
2016
+ rd_kafka_mock_cluster_t *mcluster;
2017
+ const char *bootstraps;
2018
+ rd_kafka_topic_partition_list_t *subscription, *assignment;
2019
+ rd_kafka_share_t *share_c;
2020
+ rd_kafka_error_t *error;
2021
+ const char *topic = test_mk_topic_name(__FUNCTION__, 0);
2022
+ const char *group = "test-share-group-id-not-found-stable";
2023
+
2024
+ SUB_TEST_QUICK();
2025
+
2026
+ /* Setup */
2027
+ mcluster = test_mock_cluster_new(1, &bootstraps);
2028
+ rd_kafka_mock_topic_create(mcluster, topic, 3, 1);
2029
+
2030
+ share_c = create_share_consumer(bootstraps, group);
2031
+
2032
+ subscription = rd_kafka_topic_partition_list_new(1);
2033
+ rd_kafka_topic_partition_list_add(subscription, topic,
2034
+ RD_KAFKA_PARTITION_UA);
2035
+
2036
+ rd_kafka_mock_start_request_tracking(mcluster);
2037
+ TEST_CALL_ERR__(rd_kafka_share_subscribe(share_c, subscription));
2038
+ rd_kafka_topic_partition_list_destroy(subscription);
2039
+
2040
+ /* Wait for initial join and assignment */
2041
+ wait_share_heartbeats(mcluster, 1, 1000);
2042
+ rd_usleep(500 * 1000, 0);
2043
+
2044
+ /* Verify initial assignment - member is in stable state */
2045
+ TEST_CALL_ERR__(rd_kafka_assignment(test_share_consumer_get_rk(share_c),
2046
+ &assignment));
2047
+ TEST_ASSERT(assignment->cnt == 3,
2048
+ "Expected 3 partitions initially, got %d", assignment->cnt);
2049
+ rd_kafka_topic_partition_list_destroy(assignment);
2050
+
2051
+ /* Inject GROUP_ID_NOT_FOUND for an active/stable member.
2052
+ * This should be treated as fatal (group unexpectedly deleted). */
2053
+ rd_kafka_mock_broker_push_request_error_rtts(
2054
+ mcluster, 1, RD_KAFKAP_ShareGroupHeartbeat, 1,
2055
+ RD_KAFKA_RESP_ERR_GROUP_ID_NOT_FOUND, 0);
2056
+
2057
+ /* Wait for the fatal error to propagate via consume_batch. */
2058
+ error = wait_fatal_error(share_c, 5000);
2059
+ TEST_ASSERT(error != NULL,
2060
+ "Expected a fatal error but none received within timeout");
2061
+ TEST_ASSERT(rd_kafka_error_code(error) ==
2062
+ RD_KAFKA_RESP_ERR_GROUP_ID_NOT_FOUND,
2063
+ "Expected GROUP_ID_NOT_FOUND fatal error, got %s",
2064
+ rd_kafka_error_name(error));
2065
+ TEST_SAY("Consumer entered fatal state: %s\n",
2066
+ rd_kafka_error_string(error));
2067
+ rd_kafka_error_destroy(error);
2068
+
2069
+ /* Cleanup. Consumer is in fatal state, but close() still flushes
2070
+ * pending acks and leaves the share session, so it succeeds. */
2071
+ error = rd_kafka_share_consumer_close(share_c);
2072
+ TEST_ASSERT(!error, "Expected close to succeed, got %s",
2073
+ rd_kafka_error_name(error));
2074
+ test_share_destroy(share_c);
2075
+
2076
+ rd_kafka_mock_stop_request_tracking(mcluster);
2077
+ test_mock_cluster_destroy(mcluster);
2078
+
2079
+ SUB_TEST_PASS();
2080
+ }
2081
+
2082
+ /**
2083
+ * @brief INVALID_REQUEST error handling.
2084
+ *
2085
+ * When a consumer receives INVALID_REQUEST error, it should be treated
2086
+ * as a fatal error.
2087
+ */
2088
+ static void do_test_invalid_request_error(void) {
2089
+ rd_kafka_mock_cluster_t *mcluster;
2090
+ const char *bootstraps;
2091
+ rd_kafka_topic_partition_list_t *subscription;
2092
+ rd_kafka_share_t *share_c;
2093
+ rd_kafka_error_t *error;
2094
+ const char *topic = test_mk_topic_name(__FUNCTION__, 0);
2095
+ const char *group = "test-share-group-invalid-request";
2096
+
2097
+ SUB_TEST_QUICK();
2098
+
2099
+ /* Setup */
2100
+ mcluster = test_mock_cluster_new(1, &bootstraps);
2101
+ rd_kafka_mock_topic_create(mcluster, topic, 3, 1);
2102
+
2103
+ share_c = create_share_consumer(bootstraps, group);
2104
+
2105
+ subscription = rd_kafka_topic_partition_list_new(1);
2106
+ rd_kafka_topic_partition_list_add(subscription, topic,
2107
+ RD_KAFKA_PARTITION_UA);
2108
+
2109
+ rd_kafka_mock_start_request_tracking(mcluster);
2110
+ TEST_CALL_ERR__(rd_kafka_share_subscribe(share_c, subscription));
2111
+ rd_kafka_topic_partition_list_destroy(subscription);
2112
+
2113
+ /* Wait for initial join */
2114
+ wait_share_heartbeats(mcluster, 1, 1000);
2115
+ rd_usleep(500 * 1000, 0);
2116
+
2117
+ /* Inject INVALID_REQUEST error (fatal) */
2118
+ rd_kafka_mock_broker_push_request_error_rtts(
2119
+ mcluster, 1, RD_KAFKAP_ShareGroupHeartbeat, 1,
2120
+ RD_KAFKA_RESP_ERR_INVALID_REQUEST, 0);
2121
+
2122
+ /* Wait for the fatal error to propagate via consume_batch. */
2123
+ error = wait_fatal_error(share_c, 5000);
2124
+ TEST_ASSERT(error != NULL,
2125
+ "Expected a fatal error but none received within timeout");
2126
+ TEST_ASSERT(rd_kafka_error_code(error) ==
2127
+ RD_KAFKA_RESP_ERR_INVALID_REQUEST,
2128
+ "Expected INVALID_REQUEST fatal error, got %s",
2129
+ rd_kafka_error_name(error));
2130
+ TEST_SAY("Consumer entered fatal state: %s\n",
2131
+ rd_kafka_error_string(error));
2132
+ rd_kafka_error_destroy(error);
2133
+
2134
+ /* Cleanup. Consumer is in fatal state, but close() still flushes
2135
+ * pending acks and leaves the share session, so it succeeds. */
2136
+ error = rd_kafka_share_consumer_close(share_c);
2137
+ TEST_ASSERT(!error, "Expected close to succeed, got %s",
2138
+ rd_kafka_error_name(error));
2139
+ test_share_destroy(share_c);
2140
+
2141
+ rd_kafka_mock_stop_request_tracking(mcluster);
2142
+ test_mock_cluster_destroy(mcluster);
2143
+
2144
+ SUB_TEST_PASS();
2145
+ }
2146
+
2147
+ /**
2148
+ * @brief UNSUPPORTED_VERSION error handling.
2149
+ *
2150
+ * When a consumer receives UNSUPPORTED_VERSION error, it should be
2151
+ * treated as a fatal error.
2152
+ */
2153
+ static void do_test_unsupported_version_error(void) {
2154
+ rd_kafka_mock_cluster_t *mcluster;
2155
+ const char *bootstraps;
2156
+ rd_kafka_topic_partition_list_t *subscription;
2157
+ rd_kafka_share_t *share_c;
2158
+ rd_kafka_error_t *error;
2159
+ const char *topic = test_mk_topic_name(__FUNCTION__, 0);
2160
+ const char *group = "test-share-group-unsupported-version";
2161
+
2162
+ SUB_TEST_QUICK();
2163
+
2164
+ /* Setup */
2165
+ mcluster = test_mock_cluster_new(1, &bootstraps);
2166
+ rd_kafka_mock_topic_create(mcluster, topic, 3, 1);
2167
+
2168
+ share_c = create_share_consumer(bootstraps, group);
2169
+
2170
+ subscription = rd_kafka_topic_partition_list_new(1);
2171
+ rd_kafka_topic_partition_list_add(subscription, topic,
2172
+ RD_KAFKA_PARTITION_UA);
2173
+
2174
+ rd_kafka_mock_start_request_tracking(mcluster);
2175
+ TEST_CALL_ERR__(rd_kafka_share_subscribe(share_c, subscription));
2176
+ rd_kafka_topic_partition_list_destroy(subscription);
2177
+
2178
+ /* Wait for initial join */
2179
+ wait_share_heartbeats(mcluster, 1, 1000);
2180
+ rd_usleep(500 * 1000, 0);
2181
+
2182
+ /* Inject UNSUPPORTED_VERSION error (fatal) */
2183
+ rd_kafka_mock_broker_push_request_error_rtts(
2184
+ mcluster, 1, RD_KAFKAP_ShareGroupHeartbeat, 1,
2185
+ RD_KAFKA_RESP_ERR_UNSUPPORTED_VERSION, 0);
2186
+
2187
+ /* Wait for the fatal error to propagate via consume_batch. */
2188
+ error = wait_fatal_error(share_c, 5000);
2189
+ TEST_ASSERT(error != NULL,
2190
+ "Expected a fatal error but none received within timeout");
2191
+ TEST_ASSERT(rd_kafka_error_code(error) ==
2192
+ RD_KAFKA_RESP_ERR_UNSUPPORTED_VERSION,
2193
+ "Expected UNSUPPORTED_VERSION fatal error, got %s",
2194
+ rd_kafka_error_name(error));
2195
+ TEST_SAY("Consumer entered fatal state: %s\n",
2196
+ rd_kafka_error_string(error));
2197
+ rd_kafka_error_destroy(error);
2198
+
2199
+ /* Cleanup. Consumer is in fatal state, but close() still flushes
2200
+ * pending acks and leaves the share session, so it succeeds. */
2201
+ error = rd_kafka_share_consumer_close(share_c);
2202
+ TEST_ASSERT(!error, "Expected close to succeed, got %s",
2203
+ rd_kafka_error_name(error));
2204
+ test_share_destroy(share_c);
2205
+
2206
+ rd_kafka_mock_stop_request_tracking(mcluster);
2207
+ test_mock_cluster_destroy(mcluster);
2208
+
2209
+ SUB_TEST_PASS();
2210
+ }
2211
+
2212
+ /**
2213
+ * @brief COORDINATOR_LOAD_IN_PROGRESS error handling.
2214
+ *
2215
+ * When a consumer receives COORDINATOR_LOAD_IN_PROGRESS, it should
2216
+ * retry with backoff (transient error).
2217
+ */
2218
+ static void do_test_coordinator_load_in_progress_error(void) {
2219
+ rd_kafka_mock_cluster_t *mcluster;
2220
+ const char *bootstraps;
2221
+ rd_kafka_topic_partition_list_t *subscription;
2222
+ rd_kafka_share_t *share_c;
2223
+ int found_heartbeats;
2224
+ const char *topic = test_mk_topic_name(__FUNCTION__, 0);
2225
+ const char *group = "test-share-group-coord-load";
2226
+
2227
+ SUB_TEST_QUICK();
2228
+
2229
+ /* Setup */
2230
+ mcluster = test_mock_cluster_new(1, &bootstraps);
2231
+ rd_kafka_mock_topic_create(mcluster, topic, 3, 1);
2232
+
2233
+ share_c = create_share_consumer(bootstraps, group);
2234
+
2235
+ subscription = rd_kafka_topic_partition_list_new(1);
2236
+ rd_kafka_topic_partition_list_add(subscription, topic,
2237
+ RD_KAFKA_PARTITION_UA);
2238
+
2239
+ rd_kafka_mock_start_request_tracking(mcluster);
2240
+ TEST_CALL_ERR__(rd_kafka_share_subscribe(share_c, subscription));
2241
+ rd_kafka_topic_partition_list_destroy(subscription);
2242
+
2243
+ /* Wait for initial join and assignment */
2244
+ wait_share_heartbeats(mcluster, 1, 1000);
2245
+ TEST_ASSERT(wait_assignment_count(share_c, 3, 10000) == 3,
2246
+ "Expected 3 partitions initially");
2247
+
2248
+ /* Inject COORDINATOR_LOAD_IN_PROGRESS error (transient) */
2249
+ rd_kafka_mock_broker_push_request_error_rtts(
2250
+ mcluster, 1, RD_KAFKAP_ShareGroupHeartbeat, 1,
2251
+ RD_KAFKA_RESP_ERR_COORDINATOR_LOAD_IN_PROGRESS, 0);
2252
+
2253
+ /* Wait for consumer to handle transient error and retry */
2254
+ rd_usleep(500 * 1000, 0);
2255
+
2256
+ /* Verify heartbeats continue after transient error */
2257
+ found_heartbeats = wait_share_heartbeats(mcluster, 2, 1000);
2258
+ TEST_ASSERT(found_heartbeats >= 1,
2259
+ "Expected heartbeats to continue after "
2260
+ "COORDINATOR_LOAD_IN_PROGRESS, got %d",
2261
+ found_heartbeats);
2262
+
2263
+ /* Verify consumer still has assignment */
2264
+ TEST_ASSERT(wait_assignment_count(share_c, 3, 10000) == 3,
2265
+ "Expected 3 partitions after retry");
2266
+
2267
+ /* Cleanup */
2268
+ test_share_consumer_close(share_c);
2269
+ test_share_destroy(share_c);
2270
+
2271
+ rd_kafka_mock_stop_request_tracking(mcluster);
2272
+ test_mock_cluster_destroy(mcluster);
2273
+
2274
+ SUB_TEST_PASS();
2275
+ }
2276
+
2277
+ /**
2278
+ * @brief Consumer graceful shutdown during stable state.
2279
+ *
2280
+ * Consumer in stable state leaves group gracefully, sending leave
2281
+ * heartbeat with epoch=-1.
2282
+ */
2283
+ static void do_test_graceful_shutdown_stable_state(void) {
2284
+ rd_kafka_mock_cluster_t *mcluster;
2285
+ const char *bootstraps;
2286
+ rd_kafka_topic_partition_list_t *subscription, *assignment;
2287
+ rd_kafka_share_t *share_c;
2288
+ rd_kafka_error_t *error;
2289
+ int found_heartbeats;
2290
+ const char *topic = test_mk_topic_name(__FUNCTION__, 0);
2291
+ const char *group = "test-share-group-graceful-shutdown";
2292
+
2293
+ SUB_TEST_QUICK();
2294
+
2295
+ /* Setup */
2296
+ mcluster = test_mock_cluster_new(1, &bootstraps);
2297
+ rd_kafka_mock_topic_create(mcluster, topic, 3, 1);
2298
+
2299
+ share_c = create_share_consumer(bootstraps, group);
2300
+
2301
+ subscription = rd_kafka_topic_partition_list_new(1);
2302
+ rd_kafka_topic_partition_list_add(subscription, topic,
2303
+ RD_KAFKA_PARTITION_UA);
2304
+
2305
+ rd_kafka_mock_start_request_tracking(mcluster);
2306
+ TEST_CALL_ERR__(rd_kafka_share_subscribe(share_c, subscription));
2307
+ rd_kafka_topic_partition_list_destroy(subscription);
2308
+
2309
+ /* Wait for initial join and assignment */
2310
+ wait_share_heartbeats(mcluster, 1, 1000);
2311
+ rd_usleep(500 * 1000, 0);
2312
+
2313
+ /* Verify initial assignment - member is in stable state */
2314
+ TEST_CALL_ERR__(rd_kafka_assignment(test_share_consumer_get_rk(share_c),
2315
+ &assignment));
2316
+ TEST_ASSERT(assignment->cnt == 3,
2317
+ "Expected 3 partitions initially, got %d", assignment->cnt);
2318
+ rd_kafka_topic_partition_list_destroy(assignment);
2319
+
2320
+ /* Record heartbeat count before close */
2321
+ found_heartbeats = wait_share_heartbeats(mcluster, 1, 1000);
2322
+ rd_kafka_mock_stop_request_tracking(mcluster);
2323
+ rd_kafka_mock_start_request_tracking(mcluster);
2324
+
2325
+ /* Close consumer gracefully - should send leave heartbeat */
2326
+ error = rd_kafka_share_consumer_close(share_c);
2327
+ TEST_ASSERT(!error, "Expected close to succeed, got %s",
2328
+ rd_kafka_error_name(error));
2329
+
2330
+ /* Verify leave heartbeat was sent */
2331
+ found_heartbeats = wait_share_heartbeats(mcluster, 1, 1000);
2332
+ TEST_SAY("Found %d heartbeats during shutdown\n", found_heartbeats);
2333
+
2334
+ /* Cleanup */
2335
+ test_share_destroy(share_c);
2336
+
2337
+ rd_kafka_mock_stop_request_tracking(mcluster);
2338
+ test_mock_cluster_destroy(mcluster);
2339
+
2340
+ SUB_TEST_PASS();
2341
+ }
2342
+
2343
+ /**
2344
+ * @brief Consumer resubscribes after unsubscribe.
2345
+ *
2346
+ * Tests the unsubscribe then resubscribe flow.
2347
+ */
2348
+ static void do_test_resubscribe_after_unsubscribe(void) {
2349
+ rd_kafka_mock_cluster_t *mcluster;
2350
+ const char *bootstraps;
2351
+ rd_kafka_topic_partition_list_t *subscription, *assignment;
2352
+ rd_kafka_share_t *share_c;
2353
+ const char *topic = test_mk_topic_name(__FUNCTION__, 0);
2354
+ const char *group = "test-share-group-resubscribe";
2355
+
2356
+ SUB_TEST_QUICK();
2357
+
2358
+ /* Setup */
2359
+ mcluster = test_mock_cluster_new(1, &bootstraps);
2360
+ rd_kafka_mock_topic_create(mcluster, topic, 3, 1);
2361
+
2362
+ share_c = create_share_consumer(bootstraps, group);
2363
+
2364
+ subscription = rd_kafka_topic_partition_list_new(1);
2365
+ rd_kafka_topic_partition_list_add(subscription, topic,
2366
+ RD_KAFKA_PARTITION_UA);
2367
+
2368
+ rd_kafka_mock_start_request_tracking(mcluster);
2369
+
2370
+ /* First subscribe */
2371
+ TEST_CALL_ERR__(rd_kafka_share_subscribe(share_c, subscription));
2372
+ wait_share_heartbeats(mcluster, 1, 1000);
2373
+ rd_usleep(500 * 1000, 0);
2374
+
2375
+ TEST_CALL_ERR__(rd_kafka_assignment(test_share_consumer_get_rk(share_c),
2376
+ &assignment));
2377
+ TEST_ASSERT(assignment->cnt == 3,
2378
+ "Expected 3 partitions on first subscribe, got %d",
2379
+ assignment->cnt);
2380
+ rd_kafka_topic_partition_list_destroy(assignment);
2381
+
2382
+ /* Unsubscribe */
2383
+ TEST_SAY("Unsubscribing...\n");
2384
+ TEST_CALL_ERR__(rd_kafka_share_unsubscribe(share_c));
2385
+ rd_usleep(500 * 1000, 0);
2386
+
2387
+ /* Verify no assignment after unsubscribe */
2388
+ TEST_CALL_ERR__(rd_kafka_assignment(test_share_consumer_get_rk(share_c),
2389
+ &assignment));
2390
+ TEST_ASSERT(assignment->cnt == 0,
2391
+ "Expected 0 partitions after unsubscribe, got %d",
2392
+ assignment->cnt);
2393
+ rd_kafka_topic_partition_list_destroy(assignment);
2394
+
2395
+ /* Resubscribe */
2396
+ TEST_SAY("Resubscribing...\n");
2397
+ TEST_CALL_ERR__(rd_kafka_share_subscribe(share_c, subscription));
2398
+ rd_kafka_topic_partition_list_destroy(subscription);
2399
+
2400
+ /* Verify assignment restored */
2401
+ TEST_ASSERT(wait_assignment_count(share_c, 3, 10000) == 3,
2402
+ "Expected 3 partitions after resubscribe");
2403
+
2404
+ /* Cleanup */
2405
+ test_share_consumer_close(share_c);
2406
+ test_share_destroy(share_c);
2407
+
2408
+ rd_kafka_mock_stop_request_tracking(mcluster);
2409
+ test_mock_cluster_destroy(mcluster);
2410
+
2411
+ SUB_TEST_PASS();
2412
+ }
2413
+
2414
+ /**
2415
+ * @brief Consumer leaves and remaining consumers get reassigned.
2416
+ *
2417
+ * Tests rebalance when a consumer leaves the group.
2418
+ */
2419
+ static void do_test_consumer_leave_rebalance(void) {
2420
+ rd_kafka_mock_cluster_t *mcluster;
2421
+ const char *bootstraps;
2422
+ rd_kafka_topic_partition_list_t *subscription;
2423
+ rd_kafka_topic_partition_list_t *share_c1_assign, *share_c2_assign;
2424
+ rd_kafka_share_t *share_c1, *share_c2, *share_c3;
2425
+ int final_total;
2426
+ int64_t dl;
2427
+ const char *topic = test_mk_topic_name(__FUNCTION__, 0);
2428
+ const char *group = "test-share-group-leave-rebalance";
2429
+
2430
+ SUB_TEST_QUICK();
2431
+
2432
+ /* Setup */
2433
+ mcluster = test_mock_cluster_new(1, &bootstraps);
2434
+ rd_kafka_mock_topic_create(mcluster, topic, 6, 1);
2435
+
2436
+ subscription = rd_kafka_topic_partition_list_new(1);
2437
+ rd_kafka_topic_partition_list_add(subscription, topic,
2438
+ RD_KAFKA_PARTITION_UA);
2439
+
2440
+ share_c1 = create_share_consumer(bootstraps, group);
2441
+ share_c2 = create_share_consumer(bootstraps, group);
2442
+ share_c3 = create_share_consumer(bootstraps, group);
2443
+
2444
+ rd_kafka_mock_start_request_tracking(mcluster);
2445
+
2446
+ /* All three join */
2447
+ TEST_CALL_ERR__(rd_kafka_share_subscribe(share_c1, subscription));
2448
+ TEST_CALL_ERR__(rd_kafka_share_subscribe(share_c2, subscription));
2449
+ TEST_CALL_ERR__(rd_kafka_share_subscribe(share_c3, subscription));
2450
+ rd_kafka_topic_partition_list_destroy(subscription);
2451
+
2452
+ /* Wait for initial balance */
2453
+ wait_share_heartbeats(mcluster, 4, 1000);
2454
+ wait_share_heartbeats(mcluster, 3, 1000);
2455
+
2456
+ /* Get initial assignments */
2457
+ TEST_CALL_ERR__(rd_kafka_assignment(
2458
+ test_share_consumer_get_rk(share_c1), &share_c1_assign));
2459
+ TEST_CALL_ERR__(rd_kafka_assignment(
2460
+ test_share_consumer_get_rk(share_c2), &share_c2_assign));
2461
+ TEST_SAY(
2462
+ "Initial: share consumer 1=%d, share consumer 2=%d (before share "
2463
+ "consumer 3 leaves)\n",
2464
+ share_c1_assign->cnt, share_c2_assign->cnt);
2465
+ rd_kafka_topic_partition_list_destroy(share_c1_assign);
2466
+ rd_kafka_topic_partition_list_destroy(share_c2_assign);
2467
+
2468
+ /* share consumer 3 leaves */
2469
+ TEST_SAY("Share consumer 3 leaving...\n");
2470
+ test_share_consumer_close(share_c3);
2471
+ test_share_destroy(share_c3);
2472
+
2473
+ /* Wait for rebalance to propagate to remaining consumers */
2474
+ dl = test_clock() + 15000 * 1000;
2475
+ while (test_clock() < dl) {
2476
+
2477
+ TEST_CALL_ERR__(rd_kafka_assignment(
2478
+ test_share_consumer_get_rk(share_c1), &share_c1_assign));
2479
+ TEST_CALL_ERR__(rd_kafka_assignment(
2480
+ test_share_consumer_get_rk(share_c2), &share_c2_assign));
2481
+ final_total = share_c1_assign->cnt + share_c2_assign->cnt;
2482
+ rd_kafka_topic_partition_list_destroy(share_c1_assign);
2483
+ rd_kafka_topic_partition_list_destroy(share_c2_assign);
2484
+ if (final_total >= 6)
2485
+ break;
2486
+ rd_usleep(200 * 1000, 0);
2487
+ }
2488
+ TEST_CALL_ERR__(rd_kafka_assignment(
2489
+ test_share_consumer_get_rk(share_c1), &share_c1_assign));
2490
+ TEST_CALL_ERR__(rd_kafka_assignment(
2491
+ test_share_consumer_get_rk(share_c2), &share_c2_assign));
2492
+ final_total = share_c1_assign->cnt + share_c2_assign->cnt;
2493
+ TEST_SAY(
2494
+ "After share consumer 3 leave: share consumer 1=%d, "
2495
+ "share consumer 2=%d\n",
2496
+ share_c1_assign->cnt, share_c2_assign->cnt);
2497
+ rd_kafka_topic_partition_list_destroy(share_c1_assign);
2498
+ rd_kafka_topic_partition_list_destroy(share_c2_assign);
2499
+ TEST_ASSERT(final_total >= 6,
2500
+ "Expected >= 6 partitions after rebalance, got %d",
2501
+ final_total);
2502
+
2503
+ /* Cleanup */
2504
+ test_share_consumer_close(share_c1);
2505
+ test_share_consumer_close(share_c2);
2506
+ test_share_destroy(share_c1);
2507
+ test_share_destroy(share_c2);
2508
+
2509
+ rd_kafka_mock_stop_request_tracking(mcluster);
2510
+ test_mock_cluster_destroy(mcluster);
2511
+
2512
+ SUB_TEST_PASS();
2513
+ }
2514
+
2515
+ /**
2516
+ * @brief Test calling close twice on the same consumer
2517
+ */
2518
+ static void do_test_double_close(void) {
2519
+ rd_kafka_mock_cluster_t *mcluster;
2520
+ const char *bootstraps;
2521
+ const char *topic = test_mk_topic_name(__FUNCTION__, 1);
2522
+ const char *group_id = topic;
2523
+ rd_kafka_share_t *share_c;
2524
+ rd_kafka_topic_partition_list_t *subscription;
2525
+ rd_kafka_error_t *error;
2526
+
2527
+ SUB_TEST_QUICK();
2528
+
2529
+ mcluster = test_mock_cluster_new(1, &bootstraps);
2530
+ rd_kafka_mock_topic_create(mcluster, topic, 3, 1);
2531
+ rd_kafka_mock_start_request_tracking(mcluster);
2532
+
2533
+ share_c = create_share_consumer(bootstraps, group_id);
2534
+
2535
+ subscription = rd_kafka_topic_partition_list_new(1);
2536
+ rd_kafka_topic_partition_list_add(subscription, topic,
2537
+ RD_KAFKA_PARTITION_UA);
2538
+
2539
+ TEST_CALL_ERR__(rd_kafka_share_subscribe(share_c, subscription));
2540
+ wait_share_heartbeats(mcluster, 3, 1000);
2541
+
2542
+ /* First close - should succeed */
2543
+ error = rd_kafka_share_consumer_close(share_c);
2544
+ TEST_ASSERT(!error, "Expected first close to succeed, got %s",
2545
+ rd_kafka_error_name(error));
2546
+
2547
+ /* Second close - should handle gracefully without crashing.
2548
+ * The Java equivalent tests verify the CompletableFuture
2549
+ * completes immediately on double-leave. */
2550
+ error = rd_kafka_share_consumer_close(share_c);
2551
+ TEST_ASSERT(error != NULL,
2552
+ "Expected second close to return error on already-closed "
2553
+ "consumer, got NULL");
2554
+ TEST_ASSERT(rd_kafka_error_code(error) == RD_KAFKA_RESP_ERR__STATE,
2555
+ "Expected _STATE, got %s",
2556
+ rd_kafka_err2name(rd_kafka_error_code(error)));
2557
+ rd_kafka_error_destroy(error);
2558
+
2559
+ rd_kafka_topic_partition_list_destroy(subscription);
2560
+ test_share_destroy(share_c);
2561
+
2562
+ rd_kafka_mock_stop_request_tracking(mcluster);
2563
+ test_mock_cluster_destroy(mcluster);
2564
+
2565
+ SUB_TEST_PASS();
2566
+ }
2567
+
2568
+ /**
2569
+ * @brief Test consumer subscribed to a topic with no messages
2570
+ */
2571
+ static void do_test_empty_topic_subscription(void) {
2572
+ rd_kafka_mock_cluster_t *mcluster;
2573
+ const char *bootstraps;
2574
+ const char *topic = test_mk_topic_name(__FUNCTION__, 1);
2575
+ const char *group_id = topic;
2576
+ rd_kafka_share_t *share_c;
2577
+ rd_kafka_topic_partition_list_t *subscription, *assignment;
2578
+
2579
+ SUB_TEST_QUICK();
2580
+
2581
+ mcluster = test_mock_cluster_new(1, &bootstraps);
2582
+ rd_kafka_mock_topic_create(mcluster, topic, 3, 1);
2583
+ rd_kafka_mock_start_request_tracking(mcluster);
2584
+
2585
+ share_c = create_share_consumer(bootstraps, group_id);
2586
+
2587
+ subscription = rd_kafka_topic_partition_list_new(1);
2588
+ rd_kafka_topic_partition_list_add(subscription, topic,
2589
+ RD_KAFKA_PARTITION_UA);
2590
+
2591
+ TEST_CALL_ERR__(rd_kafka_share_subscribe(share_c, subscription));
2592
+ wait_share_heartbeats(mcluster, 3, 1000);
2593
+
2594
+ /* Wait for assignment on empty topic */
2595
+ rd_usleep(500 * 1000, 0);
2596
+
2597
+ TEST_CALL_ERR__(rd_kafka_assignment(test_share_consumer_get_rk(share_c),
2598
+ &assignment));
2599
+ TEST_SAY("Empty topic: %d partitions\n", assignment->cnt);
2600
+ TEST_ASSERT(assignment->cnt == 3, "Expected 3 partitions, got %d",
2601
+ assignment->cnt);
2602
+
2603
+ rd_kafka_topic_partition_list_destroy(subscription);
2604
+ rd_kafka_topic_partition_list_destroy(assignment);
2605
+ test_share_consumer_close(share_c);
2606
+ test_share_destroy(share_c);
2607
+
2608
+ rd_kafka_mock_stop_request_tracking(mcluster);
2609
+ test_mock_cluster_destroy(mcluster);
2610
+
2611
+ SUB_TEST_PASS();
2612
+ }
2613
+
2614
+
2615
+ /**
2616
+ * @brief subscribe() with an empty topic list is equivalent to
2617
+ * unsubscribe() — must return NO_ERROR and leave the consumer
2618
+ * in the unsubscribed state.
2619
+ */
2620
+ static void do_test_empty_topic_list_subscription(void) {
2621
+ rd_kafka_mock_cluster_t *mcluster;
2622
+ const char *bootstraps;
2623
+ rd_kafka_share_t *share_c;
2624
+ rd_kafka_topic_partition_list_t *empty_list;
2625
+ rd_kafka_resp_err_t err;
2626
+ const char *group = "test-share-group-empty-topic-list";
2627
+
2628
+ SUB_TEST_QUICK();
2629
+
2630
+ mcluster = test_mock_cluster_new(1, &bootstraps);
2631
+
2632
+ share_c = create_share_consumer(bootstraps, group);
2633
+
2634
+ /* subscribe(empty_list) must succeed and act as unsubscribe. */
2635
+ empty_list = rd_kafka_topic_partition_list_new(0);
2636
+ err = rd_kafka_share_subscribe(share_c, empty_list);
2637
+ TEST_ASSERT(err == RD_KAFKA_RESP_ERR_NO_ERROR,
2638
+ "Expected NO_ERROR from subscribe(empty_list), got %s",
2639
+ rd_kafka_err2str(err));
2640
+ rd_kafka_topic_partition_list_destroy(empty_list);
2641
+
2642
+ test_share_destroy(share_c);
2643
+
2644
+ test_mock_cluster_destroy(mcluster);
2645
+
2646
+ SUB_TEST_PASS();
2647
+ }
2648
+
2649
+
2650
+ int main_0155_share_group_heartbeat_mock(int argc, char **argv) {
2651
+ TEST_SKIP_MOCK_CLUSTER(0);
2652
+
2653
+ test_timeout_set(1500);
2654
+
2655
+ do_test_share_group_heartbeat_basic();
2656
+ do_test_share_group_assignment_rebalance();
2657
+ do_test_share_group_multi_topic_assignment();
2658
+ do_test_share_group_error_injection();
2659
+ do_test_share_group_rtt_injection();
2660
+ do_test_share_group_session_timeout();
2661
+ do_test_share_group_target_assignment();
2662
+ do_test_share_group_no_spurious_fencing();
2663
+ do_test_unknown_member_id_error();
2664
+
2665
+ do_test_fenced_member_epoch_error();
2666
+ do_test_coordinator_not_available_error();
2667
+ do_test_not_coordinator_error();
2668
+ do_test_group_authorization_failed_error();
2669
+ do_test_group_max_size_reached_error();
2670
+ do_test_invalid_request_error();
2671
+ do_test_unsupported_version_error();
2672
+ do_test_coordinator_load_in_progress_error();
2673
+
2674
+ do_test_member_rejoin_with_epoch_zero();
2675
+ do_test_leaving_member_bumps_group_epoch();
2676
+
2677
+ do_test_partition_assignment_with_multiple_topics();
2678
+ do_test_multiple_members_partition_distribution();
2679
+
2680
+ do_test_leave_heartbeat_completes_successfully();
2681
+ do_test_leave_heartbeat_completes_on_error();
2682
+ do_test_graceful_shutdown_stable_state();
2683
+ do_test_consumer_leave_rebalance();
2684
+ do_test_double_close();
2685
+
2686
+ do_test_subscription_change();
2687
+ do_test_resubscribe_after_unsubscribe();
2688
+ do_test_empty_topic_subscription();
2689
+ do_test_empty_topic_list_subscription();
2690
+
2691
+ do_test_group_id_not_found_while_unsubscribed();
2692
+ do_test_group_id_not_found_while_stable_is_fatal();
2693
+
2694
+ return 0;
2695
+ }