rdkafka 0.8.0 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +1 -0
  3. data/.semaphore/semaphore.yml +23 -0
  4. data/CHANGELOG.md +24 -1
  5. data/Guardfile +19 -0
  6. data/README.md +8 -3
  7. data/bin/console +11 -0
  8. data/docker-compose.yml +5 -3
  9. data/ext/README.md +8 -1
  10. data/ext/Rakefile +5 -20
  11. data/lib/rdkafka/abstract_handle.rb +82 -0
  12. data/lib/rdkafka/admin/create_topic_handle.rb +27 -0
  13. data/lib/rdkafka/admin/create_topic_report.rb +22 -0
  14. data/lib/rdkafka/admin/delete_topic_handle.rb +27 -0
  15. data/lib/rdkafka/admin/delete_topic_report.rb +22 -0
  16. data/lib/rdkafka/admin.rb +155 -0
  17. data/lib/rdkafka/bindings.rb +57 -18
  18. data/lib/rdkafka/callbacks.rb +106 -0
  19. data/lib/rdkafka/config.rb +59 -3
  20. data/lib/rdkafka/consumer.rb +125 -5
  21. data/lib/rdkafka/error.rb +29 -3
  22. data/lib/rdkafka/metadata.rb +6 -5
  23. data/lib/rdkafka/producer/delivery_handle.rb +7 -53
  24. data/lib/rdkafka/producer/delivery_report.rb +1 -1
  25. data/lib/rdkafka/producer.rb +27 -12
  26. data/lib/rdkafka/version.rb +3 -3
  27. data/lib/rdkafka.rb +7 -0
  28. data/rdkafka.gemspec +9 -7
  29. data/spec/rdkafka/abstract_handle_spec.rb +113 -0
  30. data/spec/rdkafka/admin/create_topic_handle_spec.rb +52 -0
  31. data/spec/rdkafka/admin/create_topic_report_spec.rb +16 -0
  32. data/spec/rdkafka/admin/delete_topic_handle_spec.rb +52 -0
  33. data/spec/rdkafka/admin/delete_topic_report_spec.rb +16 -0
  34. data/spec/rdkafka/admin_spec.rb +203 -0
  35. data/spec/rdkafka/bindings_spec.rb +32 -8
  36. data/spec/rdkafka/callbacks_spec.rb +20 -0
  37. data/spec/rdkafka/config_spec.rb +78 -9
  38. data/spec/rdkafka/consumer_spec.rb +326 -42
  39. data/spec/rdkafka/error_spec.rb +4 -0
  40. data/spec/rdkafka/metadata_spec.rb +78 -0
  41. data/spec/rdkafka/producer/delivery_handle_spec.rb +1 -41
  42. data/spec/rdkafka/producer_spec.rb +102 -34
  43. data/spec/spec_helper.rb +78 -20
  44. metadata +84 -29
  45. data/.travis.yml +0 -48
@@ -1,3 +1,5 @@
1
+ require "securerandom"
2
+
1
3
  module Rdkafka
2
4
  # A producer for Kafka messages. To create a producer set up a {Config} and call {Config#producer producer} on that.
3
5
  class Producer
@@ -9,8 +11,13 @@ module Rdkafka
9
11
 
10
12
  # @private
11
13
  def initialize(native_kafka)
14
+ @id = SecureRandom.uuid
12
15
  @closing = false
13
16
  @native_kafka = native_kafka
17
+
18
+ # Makes sure, that the producer gets closed before it gets GCed by Ruby
19
+ ObjectSpace.define_finalizer(@id, proc { close })
20
+
14
21
  # Start thread to poll client for delivery callbacks
15
22
  @polling_thread = Thread.new do
16
23
  loop do
@@ -27,16 +34,18 @@ module Rdkafka
27
34
  # Set a callback that will be called every time a message is successfully produced.
28
35
  # The callback is called with a {DeliveryReport}
29
36
  #
30
- # @param callback [Proc] The callback
37
+ # @param callback [Proc, #call] The callback
31
38
  #
32
39
  # @return [nil]
33
40
  def delivery_callback=(callback)
34
- raise TypeError.new("Callback has to be a proc or lambda") unless callback.is_a? Proc
41
+ raise TypeError.new("Callback has to be callable") unless callback.respond_to?(:call)
35
42
  @delivery_callback = callback
36
43
  end
37
44
 
38
45
  # Close this producer and wait for the internal poll queue to empty.
39
46
  def close
47
+ ObjectSpace.undefine_finalizer(@id)
48
+
40
49
  return unless @native_kafka
41
50
 
42
51
  # Indicate to polling thread that we're closing
@@ -55,7 +64,8 @@ module Rdkafka
55
64
  # @return partition count [Integer,nil]
56
65
  #
57
66
  def partition_count(topic)
58
- Rdkafka::Metadata.new(@native_kafka, topic).topics&.select { |x| x[:topic_name] == topic }&.dig(0, :partition_count)
67
+ closed_producer_check(__method__)
68
+ Rdkafka::Metadata.new(@native_kafka, topic).topics&.first[:partition_count]
59
69
  end
60
70
 
61
71
  # Produces a message to a Kafka topic. The message is added to rdkafka's queue, call {DeliveryHandle#wait wait} on the returned delivery handle to make sure it is delivered.
@@ -65,8 +75,9 @@ module Rdkafka
65
75
  #
66
76
  # @param topic [String] The topic to produce to
67
77
  # @param payload [String,nil] The message's payload
68
- # @param key [String] The message's key
78
+ # @param key [String, nil] The message's key
69
79
  # @param partition [Integer,nil] Optional partition to produce to
80
+ # @param partition_key [String, nil] Optional partition key based on which partition assignment can happen
70
81
  # @param timestamp [Time,Integer,nil] Optional timestamp of this message. Integer timestamp is in milliseconds since Jan 1 1970.
71
82
  # @param headers [Hash<String,String>] Optional message headers
72
83
  #
@@ -74,6 +85,8 @@ module Rdkafka
74
85
  #
75
86
  # @return [DeliveryHandle] Delivery handle that can be used to wait for the result of producing this message
76
87
  def produce(topic:, payload: nil, key: nil, partition: nil, partition_key: nil, timestamp: nil, headers: nil)
88
+ closed_producer_check(__method__)
89
+
77
90
  # Start by checking and converting the input
78
91
 
79
92
  # Get payload length
@@ -117,7 +130,7 @@ module Rdkafka
117
130
  delivery_handle[:response] = -1
118
131
  delivery_handle[:partition] = -1
119
132
  delivery_handle[:offset] = -1
120
- DeliveryHandle.register(delivery_handle.to_ptr.address, delivery_handle)
133
+ DeliveryHandle.register(delivery_handle)
121
134
 
122
135
  args = [
123
136
  :int, Rdkafka::Bindings::RD_KAFKA_VTYPE_TOPIC, :string, topic,
@@ -133,16 +146,14 @@ module Rdkafka
133
146
  headers.each do |key0, value0|
134
147
  key = key0.to_s
135
148
  value = value0.to_s
136
- args += [
137
- :int, Rdkafka::Bindings::RD_KAFKA_VTYPE_HEADER,
138
- :string, key,
139
- :pointer, value,
140
- :size_t, value.bytes.size
141
- ]
149
+ args << :int << Rdkafka::Bindings::RD_KAFKA_VTYPE_HEADER
150
+ args << :string << key
151
+ args << :pointer << value
152
+ args << :size_t << value.bytes.size
142
153
  end
143
154
  end
144
155
 
145
- args += [:int, Rdkafka::Bindings::RD_KAFKA_VTYPE_END]
156
+ args << :int << Rdkafka::Bindings::RD_KAFKA_VTYPE_END
146
157
 
147
158
  # Produce the message
148
159
  response = Rdkafka::Bindings.rd_kafka_producev(
@@ -163,5 +174,9 @@ module Rdkafka
163
174
  def call_delivery_callback(delivery_handle)
164
175
  @delivery_callback.call(delivery_handle) if @delivery_callback
165
176
  end
177
+
178
+ def closed_producer_check(method)
179
+ raise Rdkafka::ClosedProducerError.new(method) if @native_kafka.nil?
180
+ end
166
181
  end
167
182
  end
@@ -1,5 +1,5 @@
1
1
  module Rdkafka
2
- VERSION = "0.8.0"
3
- LIBRDKAFKA_VERSION = "1.4.0"
4
- LIBRDKAFKA_SOURCE_SHA256 = "ae27ea3f3d0d32d29004e7f709efbba2666c5383a107cc45b3a1949486b2eb84"
2
+ VERSION = "0.11.0"
3
+ LIBRDKAFKA_VERSION = "1.8.2"
4
+ LIBRDKAFKA_SOURCE_SHA256 = "6a747d293a7a4613bd2897e28e8791476fbe1ae7361f2530a876e0fd483482a6"
5
5
  end
data/lib/rdkafka.rb CHANGED
@@ -1,6 +1,13 @@
1
1
  require "rdkafka/version"
2
2
 
3
+ require "rdkafka/abstract_handle"
4
+ require "rdkafka/admin"
5
+ require "rdkafka/admin/create_topic_handle"
6
+ require "rdkafka/admin/create_topic_report"
7
+ require "rdkafka/admin/delete_topic_handle"
8
+ require "rdkafka/admin/delete_topic_report"
3
9
  require "rdkafka/bindings"
10
+ require "rdkafka/callbacks"
4
11
  require "rdkafka/config"
5
12
  require "rdkafka/consumer"
6
13
  require "rdkafka/consumer/headers"
data/rdkafka.gemspec CHANGED
@@ -14,15 +14,17 @@ Gem::Specification.new do |gem|
14
14
  gem.name = 'rdkafka'
15
15
  gem.require_paths = ['lib']
16
16
  gem.version = Rdkafka::VERSION
17
- gem.required_ruby_version = '>= 2.4'
17
+ gem.required_ruby_version = '>= 2.6'
18
18
  gem.extensions = %w(ext/Rakefile)
19
19
 
20
- gem.add_dependency 'ffi', '~> 1.9'
21
- gem.add_dependency 'mini_portile2', '~> 2.1'
22
- gem.add_dependency 'rake', '>= 12.3'
20
+ gem.add_dependency 'ffi', '~> 1.15'
21
+ gem.add_dependency 'mini_portile2', '~> 2.7'
22
+ gem.add_dependency 'rake', '> 12'
23
23
 
24
- gem.add_development_dependency 'pry', '~> 0.10'
24
+ gem.add_development_dependency 'pry'
25
25
  gem.add_development_dependency 'rspec', '~> 3.5'
26
- gem.add_development_dependency 'rake', '~> 12.0'
27
- gem.add_development_dependency 'simplecov', '~> 0.15'
26
+ gem.add_development_dependency 'rake'
27
+ gem.add_development_dependency 'simplecov'
28
+ gem.add_development_dependency 'guard'
29
+ gem.add_development_dependency 'guard-rspec'
28
30
  end
@@ -0,0 +1,113 @@
1
+ require "spec_helper"
2
+
3
+ describe Rdkafka::AbstractHandle do
4
+ let(:response) { 0 }
5
+ let(:result) { -1 }
6
+
7
+ context "A subclass that does not implement the required methods" do
8
+
9
+ class BadTestHandle < Rdkafka::AbstractHandle
10
+ layout :pending, :bool,
11
+ :response, :int
12
+ end
13
+
14
+ it "raises an exception if operation_name is called" do
15
+ expect {
16
+ BadTestHandle.new.operation_name
17
+ }.to raise_exception(RuntimeError, /Must be implemented by subclass!/)
18
+ end
19
+
20
+ it "raises an exception if create_result is called" do
21
+ expect {
22
+ BadTestHandle.new.create_result
23
+ }.to raise_exception(RuntimeError, /Must be implemented by subclass!/)
24
+ end
25
+ end
26
+
27
+ class TestHandle < Rdkafka::AbstractHandle
28
+ layout :pending, :bool,
29
+ :response, :int,
30
+ :result, :int
31
+
32
+ def operation_name
33
+ "test_operation"
34
+ end
35
+
36
+ def create_result
37
+ self[:result]
38
+ end
39
+ end
40
+
41
+ subject do
42
+ TestHandle.new.tap do |handle|
43
+ handle[:pending] = pending_handle
44
+ handle[:response] = response
45
+ handle[:result] = result
46
+ end
47
+ end
48
+
49
+ describe ".register and .remove" do
50
+ let(:pending_handle) { true }
51
+
52
+ it "should register and remove a delivery handle" do
53
+ Rdkafka::AbstractHandle.register(subject)
54
+ removed = Rdkafka::AbstractHandle.remove(subject.to_ptr.address)
55
+ expect(removed).to eq subject
56
+ expect(Rdkafka::AbstractHandle::REGISTRY).to be_empty
57
+ end
58
+ end
59
+
60
+ describe "#pending?" do
61
+ context "when true" do
62
+ let(:pending_handle) { true }
63
+
64
+ it "should be true" do
65
+ expect(subject.pending?).to be true
66
+ end
67
+ end
68
+
69
+ context "when not true" do
70
+ let(:pending_handle) { false }
71
+
72
+ it "should be false" do
73
+ expect(subject.pending?).to be false
74
+ end
75
+ end
76
+ end
77
+
78
+ describe "#wait" do
79
+ let(:pending_handle) { true }
80
+
81
+ it "should wait until the timeout and then raise an error" do
82
+ expect {
83
+ subject.wait(max_wait_timeout: 0.1)
84
+ }.to raise_error Rdkafka::AbstractHandle::WaitTimeoutError, /test_operation/
85
+ end
86
+
87
+ context "when not pending anymore and no error" do
88
+ let(:pending_handle) { false }
89
+ let(:result) { 1 }
90
+
91
+ it "should return a result" do
92
+ wait_result = subject.wait
93
+ expect(wait_result).to eq(result)
94
+ end
95
+
96
+ it "should wait without a timeout" do
97
+ wait_result = subject.wait(max_wait_timeout: nil)
98
+ expect(wait_result).to eq(result)
99
+ end
100
+ end
101
+
102
+ context "when not pending anymore and there was an error" do
103
+ let(:pending_handle) { false }
104
+ let(:response) { 20 }
105
+
106
+ it "should raise an rdkafka error" do
107
+ expect {
108
+ subject.wait
109
+ }.to raise_error Rdkafka::RdkafkaError
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,52 @@
1
+ require "spec_helper"
2
+
3
+ describe Rdkafka::Admin::CreateTopicHandle do
4
+ let(:response) { 0 }
5
+
6
+ subject do
7
+ Rdkafka::Admin::CreateTopicHandle.new.tap do |handle|
8
+ handle[:pending] = pending_handle
9
+ handle[:response] = response
10
+ handle[:error_string] = FFI::Pointer::NULL
11
+ handle[:result_name] = FFI::MemoryPointer.from_string("my-test-topic")
12
+ end
13
+ end
14
+
15
+ describe "#wait" do
16
+ let(:pending_handle) { true }
17
+
18
+ it "should wait until the timeout and then raise an error" do
19
+ expect {
20
+ subject.wait(max_wait_timeout: 0.1)
21
+ }.to raise_error Rdkafka::Admin::CreateTopicHandle::WaitTimeoutError, /create topic/
22
+ end
23
+
24
+ context "when not pending anymore and no error" do
25
+ let(:pending_handle) { false }
26
+
27
+ it "should return a create topic report" do
28
+ report = subject.wait
29
+
30
+ expect(report.error_string).to eq(nil)
31
+ expect(report.result_name).to eq("my-test-topic")
32
+ end
33
+
34
+ it "should wait without a timeout" do
35
+ report = subject.wait(max_wait_timeout: nil)
36
+
37
+ expect(report.error_string).to eq(nil)
38
+ expect(report.result_name).to eq("my-test-topic")
39
+ end
40
+ end
41
+ end
42
+
43
+ describe "#raise_error" do
44
+ let(:pending_handle) { false }
45
+
46
+ it "should raise the appropriate error" do
47
+ expect {
48
+ subject.raise_error
49
+ }.to raise_exception(Rdkafka::RdkafkaError, /Success \(no_error\)/)
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,16 @@
1
+ require "spec_helper"
2
+
3
+ describe Rdkafka::Admin::CreateTopicReport do
4
+ subject { Rdkafka::Admin::CreateTopicReport.new(
5
+ FFI::MemoryPointer.from_string("error string"),
6
+ FFI::MemoryPointer.from_string("result name")
7
+ )}
8
+
9
+ it "should get the error string" do
10
+ expect(subject.error_string).to eq("error string")
11
+ end
12
+
13
+ it "should get the result name" do
14
+ expect(subject.result_name).to eq("result name")
15
+ end
16
+ end
@@ -0,0 +1,52 @@
1
+ require "spec_helper"
2
+
3
+ describe Rdkafka::Admin::DeleteTopicHandle do
4
+ let(:response) { 0 }
5
+
6
+ subject do
7
+ Rdkafka::Admin::DeleteTopicHandle.new.tap do |handle|
8
+ handle[:pending] = pending_handle
9
+ handle[:response] = response
10
+ handle[:error_string] = FFI::Pointer::NULL
11
+ handle[:result_name] = FFI::MemoryPointer.from_string("my-test-topic")
12
+ end
13
+ end
14
+
15
+ describe "#wait" do
16
+ let(:pending_handle) { true }
17
+
18
+ it "should wait until the timeout and then raise an error" do
19
+ expect {
20
+ subject.wait(max_wait_timeout: 0.1)
21
+ }.to raise_error Rdkafka::Admin::DeleteTopicHandle::WaitTimeoutError, /delete topic/
22
+ end
23
+
24
+ context "when not pending anymore and no error" do
25
+ let(:pending_handle) { false }
26
+
27
+ it "should return a delete topic report" do
28
+ report = subject.wait
29
+
30
+ expect(report.error_string).to eq(nil)
31
+ expect(report.result_name).to eq("my-test-topic")
32
+ end
33
+
34
+ it "should wait without a timeout" do
35
+ report = subject.wait(max_wait_timeout: nil)
36
+
37
+ expect(report.error_string).to eq(nil)
38
+ expect(report.result_name).to eq("my-test-topic")
39
+ end
40
+ end
41
+ end
42
+
43
+ describe "#raise_error" do
44
+ let(:pending_handle) { false }
45
+
46
+ it "should raise the appropriate error" do
47
+ expect {
48
+ subject.raise_error
49
+ }.to raise_exception(Rdkafka::RdkafkaError, /Success \(no_error\)/)
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,16 @@
1
+ require "spec_helper"
2
+
3
+ describe Rdkafka::Admin::DeleteTopicReport do
4
+ subject { Rdkafka::Admin::DeleteTopicReport.new(
5
+ FFI::MemoryPointer.from_string("error string"),
6
+ FFI::MemoryPointer.from_string("result name")
7
+ )}
8
+
9
+ it "should get the error string" do
10
+ expect(subject.error_string).to eq("error string")
11
+ end
12
+
13
+ it "should get the result name" do
14
+ expect(subject.result_name).to eq("result name")
15
+ end
16
+ end
@@ -0,0 +1,203 @@
1
+ require "spec_helper"
2
+ require "ostruct"
3
+
4
+ describe Rdkafka::Admin do
5
+ let(:config) { rdkafka_config }
6
+ let(:admin) { config.admin }
7
+
8
+ after do
9
+ # Registry should always end up being empty
10
+ expect(Rdkafka::Admin::CreateTopicHandle::REGISTRY).to be_empty
11
+ admin.close
12
+ end
13
+
14
+ let(:topic_name) { "test-topic-#{Random.new.rand(0..1_000_000)}" }
15
+ let(:topic_partition_count) { 3 }
16
+ let(:topic_replication_factor) { 1 }
17
+ let(:topic_config) { {"cleanup.policy" => "compact", "min.cleanable.dirty.ratio" => 0.8} }
18
+ let(:invalid_topic_config) { {"cleeeeenup.policee" => "campact"} }
19
+
20
+ describe "#create_topic" do
21
+ describe "called with invalid input" do
22
+ describe "with an invalid topic name" do
23
+ # https://github.com/apache/kafka/blob/trunk/clients/src/main/java/org/apache/kafka/common/internals/Topic.java#L29
24
+ # public static final String LEGAL_CHARS = "[a-zA-Z0-9._-]";
25
+ let(:topic_name) { "[!@#]" }
26
+
27
+ it "raises an exception" do
28
+ create_topic_handle = admin.create_topic(topic_name, topic_partition_count, topic_replication_factor)
29
+ expect {
30
+ create_topic_handle.wait(max_wait_timeout: 15.0)
31
+ }.to raise_exception { |ex|
32
+ expect(ex).to be_a(Rdkafka::RdkafkaError)
33
+ expect(ex.message).to match(/Broker: Invalid topic \(topic_exception\)/)
34
+ expect(ex.broker_message).to match(/Topic name.*is illegal, it contains a character other than ASCII alphanumerics/)
35
+ }
36
+ end
37
+ end
38
+
39
+ describe "with the name of a topic that already exists" do
40
+ let(:topic_name) { "empty_test_topic" } # created in spec_helper.rb
41
+
42
+ it "raises an exception" do
43
+ create_topic_handle = admin.create_topic(topic_name, topic_partition_count, topic_replication_factor)
44
+ expect {
45
+ create_topic_handle.wait(max_wait_timeout: 15.0)
46
+ }.to raise_exception { |ex|
47
+ expect(ex).to be_a(Rdkafka::RdkafkaError)
48
+ expect(ex.message).to match(/Broker: Topic already exists \(topic_already_exists\)/)
49
+ expect(ex.broker_message).to match(/Topic 'empty_test_topic' already exists/)
50
+ }
51
+ end
52
+ end
53
+
54
+ describe "with an invalid partition count" do
55
+ let(:topic_partition_count) { -999 }
56
+
57
+ it "raises an exception" do
58
+ expect {
59
+ admin.create_topic(topic_name, topic_partition_count, topic_replication_factor)
60
+ }.to raise_error Rdkafka::Config::ConfigError, /num_partitions out of expected range/
61
+ end
62
+ end
63
+
64
+ describe "with an invalid replication factor" do
65
+ let(:topic_replication_factor) { -2 }
66
+
67
+ it "raises an exception" do
68
+ expect {
69
+ admin.create_topic(topic_name, topic_partition_count, topic_replication_factor)
70
+ }.to raise_error Rdkafka::Config::ConfigError, /replication_factor out of expected range/
71
+ end
72
+ end
73
+
74
+ describe "with an invalid topic configuration" do
75
+ it "doesn't create the topic" do
76
+ create_topic_handle = admin.create_topic(topic_name, topic_partition_count, topic_replication_factor, invalid_topic_config)
77
+ expect {
78
+ create_topic_handle.wait(max_wait_timeout: 15.0)
79
+ }.to raise_error Rdkafka::RdkafkaError, /Broker: Configuration is invalid \(invalid_config\)/
80
+ end
81
+ end
82
+ end
83
+
84
+ context "edge case" do
85
+ context "where we are unable to get the background queue" do
86
+ before do
87
+ allow(Rdkafka::Bindings).to receive(:rd_kafka_queue_get_background).and_return(FFI::Pointer::NULL)
88
+ end
89
+
90
+ it "raises an exception" do
91
+ expect {
92
+ admin.create_topic(topic_name, topic_partition_count, topic_replication_factor)
93
+ }.to raise_error Rdkafka::Config::ConfigError, /rd_kafka_queue_get_background was NULL/
94
+ end
95
+ end
96
+
97
+ context "where rd_kafka_CreateTopics raises an exception" do
98
+ before do
99
+ allow(Rdkafka::Bindings).to receive(:rd_kafka_CreateTopics).and_raise(RuntimeError.new("oops"))
100
+ end
101
+
102
+ it "raises an exception" do
103
+ expect {
104
+ admin.create_topic(topic_name, topic_partition_count, topic_replication_factor)
105
+ }.to raise_error RuntimeError, /oops/
106
+ end
107
+ end
108
+ end
109
+
110
+ it "creates a topic" do
111
+ create_topic_handle = admin.create_topic(topic_name, topic_partition_count, topic_replication_factor, topic_config)
112
+ create_topic_report = create_topic_handle.wait(max_wait_timeout: 15.0)
113
+ expect(create_topic_report.error_string).to be_nil
114
+ expect(create_topic_report.result_name).to eq(topic_name)
115
+ end
116
+ end
117
+
118
+ describe "#delete_topic" do
119
+ describe "called with invalid input" do
120
+ # https://github.com/apache/kafka/blob/trunk/clients/src/main/java/org/apache/kafka/common/internals/Topic.java#L29
121
+ # public static final String LEGAL_CHARS = "[a-zA-Z0-9._-]";
122
+ describe "with an invalid topic name" do
123
+ let(:topic_name) { "[!@#]" }
124
+
125
+ it "raises an exception" do
126
+ delete_topic_handle = admin.delete_topic(topic_name)
127
+ expect {
128
+ delete_topic_handle.wait(max_wait_timeout: 15.0)
129
+ }.to raise_exception { |ex|
130
+ expect(ex).to be_a(Rdkafka::RdkafkaError)
131
+ expect(ex.message).to match(/Broker: Unknown topic or partition \(unknown_topic_or_part\)/)
132
+ expect(ex.broker_message).to match(/Broker: Unknown topic or partition/)
133
+ }
134
+ end
135
+ end
136
+
137
+ describe "with the name of a topic that does not exist" do
138
+ it "raises an exception" do
139
+ delete_topic_handle = admin.delete_topic(topic_name)
140
+ expect {
141
+ delete_topic_handle.wait(max_wait_timeout: 15.0)
142
+ }.to raise_exception { |ex|
143
+ expect(ex).to be_a(Rdkafka::RdkafkaError)
144
+ expect(ex.message).to match(/Broker: Unknown topic or partition \(unknown_topic_or_part\)/)
145
+ expect(ex.broker_message).to match(/Broker: Unknown topic or partition/)
146
+ }
147
+ end
148
+ end
149
+ end
150
+
151
+ context "edge case" do
152
+ context "where we are unable to get the background queue" do
153
+ before do
154
+ allow(Rdkafka::Bindings).to receive(:rd_kafka_queue_get_background).and_return(FFI::Pointer::NULL)
155
+ end
156
+
157
+ it "raises an exception" do
158
+ expect {
159
+ admin.delete_topic(topic_name)
160
+ }.to raise_error Rdkafka::Config::ConfigError, /rd_kafka_queue_get_background was NULL/
161
+ end
162
+ end
163
+
164
+ context "where rd_kafka_DeleteTopics raises an exception" do
165
+ before do
166
+ allow(Rdkafka::Bindings).to receive(:rd_kafka_DeleteTopics).and_raise(RuntimeError.new("oops"))
167
+ end
168
+
169
+ it "raises an exception" do
170
+ expect {
171
+ admin.delete_topic(topic_name)
172
+ }.to raise_error RuntimeError, /oops/
173
+ end
174
+ end
175
+ end
176
+
177
+
178
+ it "deletes a topic that was newly created" do
179
+ create_topic_handle = admin.create_topic(topic_name, topic_partition_count, topic_replication_factor)
180
+ create_topic_report = create_topic_handle.wait(max_wait_timeout: 15.0)
181
+ expect(create_topic_report.error_string).to be_nil
182
+ expect(create_topic_report.result_name).to eq(topic_name)
183
+
184
+ # Retry topic deletion a few times. On CI Kafka seems to not
185
+ # always be ready for it immediately
186
+ delete_topic_report = nil
187
+ 10.times do |i|
188
+ begin
189
+ delete_topic_handle = admin.delete_topic(topic_name)
190
+ delete_topic_report = delete_topic_handle.wait(max_wait_timeout: 15.0)
191
+ break
192
+ rescue Rdkafka::RdkafkaError => ex
193
+ if i > 3
194
+ raise ex
195
+ end
196
+ end
197
+ end
198
+
199
+ expect(delete_topic_report.error_string).to be_nil
200
+ expect(delete_topic_report.result_name).to eq(topic_name)
201
+ end
202
+ end
203
+ end