karafka-rdkafka 0.12.0
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.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +2 -0
- data/.gitignore +8 -0
- data/.rspec +1 -0
- data/.semaphore/semaphore.yml +23 -0
- data/.yardopts +2 -0
- data/CHANGELOG.md +104 -0
- data/Gemfile +3 -0
- data/Guardfile +19 -0
- data/LICENSE +21 -0
- data/README.md +114 -0
- data/Rakefile +96 -0
- data/bin/console +11 -0
- data/docker-compose.yml +24 -0
- data/ext/README.md +18 -0
- data/ext/Rakefile +62 -0
- data/lib/rdkafka/abstract_handle.rb +82 -0
- data/lib/rdkafka/admin/create_topic_handle.rb +27 -0
- data/lib/rdkafka/admin/create_topic_report.rb +22 -0
- data/lib/rdkafka/admin/delete_topic_handle.rb +27 -0
- data/lib/rdkafka/admin/delete_topic_report.rb +22 -0
- data/lib/rdkafka/admin.rb +155 -0
- data/lib/rdkafka/bindings.rb +312 -0
- data/lib/rdkafka/callbacks.rb +106 -0
- data/lib/rdkafka/config.rb +299 -0
- data/lib/rdkafka/consumer/headers.rb +63 -0
- data/lib/rdkafka/consumer/message.rb +84 -0
- data/lib/rdkafka/consumer/partition.rb +49 -0
- data/lib/rdkafka/consumer/topic_partition_list.rb +164 -0
- data/lib/rdkafka/consumer.rb +565 -0
- data/lib/rdkafka/error.rb +86 -0
- data/lib/rdkafka/metadata.rb +92 -0
- data/lib/rdkafka/producer/client.rb +47 -0
- data/lib/rdkafka/producer/delivery_handle.rb +22 -0
- data/lib/rdkafka/producer/delivery_report.rb +26 -0
- data/lib/rdkafka/producer.rb +178 -0
- data/lib/rdkafka/version.rb +5 -0
- data/lib/rdkafka.rb +22 -0
- data/rdkafka.gemspec +36 -0
- data/spec/rdkafka/abstract_handle_spec.rb +113 -0
- data/spec/rdkafka/admin/create_topic_handle_spec.rb +52 -0
- data/spec/rdkafka/admin/create_topic_report_spec.rb +16 -0
- data/spec/rdkafka/admin/delete_topic_handle_spec.rb +52 -0
- data/spec/rdkafka/admin/delete_topic_report_spec.rb +16 -0
- data/spec/rdkafka/admin_spec.rb +203 -0
- data/spec/rdkafka/bindings_spec.rb +134 -0
- data/spec/rdkafka/callbacks_spec.rb +20 -0
- data/spec/rdkafka/config_spec.rb +182 -0
- data/spec/rdkafka/consumer/message_spec.rb +139 -0
- data/spec/rdkafka/consumer/partition_spec.rb +57 -0
- data/spec/rdkafka/consumer/topic_partition_list_spec.rb +223 -0
- data/spec/rdkafka/consumer_spec.rb +1008 -0
- data/spec/rdkafka/error_spec.rb +89 -0
- data/spec/rdkafka/metadata_spec.rb +78 -0
- data/spec/rdkafka/producer/client_spec.rb +145 -0
- data/spec/rdkafka/producer/delivery_handle_spec.rb +42 -0
- data/spec/rdkafka/producer/delivery_report_spec.rb +17 -0
- data/spec/rdkafka/producer_spec.rb +525 -0
- data/spec/spec_helper.rb +139 -0
- data.tar.gz.sig +0 -0
- metadata +277 -0
- metadata.gz.sig +0 -0
@@ -0,0 +1,92 @@
|
|
1
|
+
module Rdkafka
|
2
|
+
class Metadata
|
3
|
+
attr_reader :brokers, :topics
|
4
|
+
|
5
|
+
def initialize(native_client, topic_name = nil)
|
6
|
+
native_topic = if topic_name
|
7
|
+
Rdkafka::Bindings.rd_kafka_topic_new(native_client, topic_name, nil)
|
8
|
+
end
|
9
|
+
|
10
|
+
ptr = FFI::MemoryPointer.new(:pointer)
|
11
|
+
|
12
|
+
# If topic_flag is 1, we request info about *all* topics in the cluster. If topic_flag is 0,
|
13
|
+
# we only request info about locally known topics (or a single topic if one is passed in).
|
14
|
+
topic_flag = topic_name.nil? ? 1 : 0
|
15
|
+
|
16
|
+
# Retrieve the Metadata
|
17
|
+
result = Rdkafka::Bindings.rd_kafka_metadata(native_client, topic_flag, native_topic, ptr, 250)
|
18
|
+
|
19
|
+
# Error Handling
|
20
|
+
raise Rdkafka::RdkafkaError.new(result) unless result.zero?
|
21
|
+
|
22
|
+
metadata_from_native(ptr.read_pointer)
|
23
|
+
ensure
|
24
|
+
Rdkafka::Bindings.rd_kafka_topic_destroy(native_topic) if topic_name
|
25
|
+
Rdkafka::Bindings.rd_kafka_metadata_destroy(ptr.read_pointer)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def metadata_from_native(ptr)
|
31
|
+
metadata = Metadata.new(ptr)
|
32
|
+
@brokers = Array.new(metadata[:brokers_count]) do |i|
|
33
|
+
BrokerMetadata.new(metadata[:brokers_metadata] + (i * BrokerMetadata.size)).to_h
|
34
|
+
end
|
35
|
+
|
36
|
+
@topics = Array.new(metadata[:topics_count]) do |i|
|
37
|
+
topic = TopicMetadata.new(metadata[:topics_metadata] + (i * TopicMetadata.size))
|
38
|
+
raise Rdkafka::RdkafkaError.new(topic[:rd_kafka_resp_err]) unless topic[:rd_kafka_resp_err].zero?
|
39
|
+
|
40
|
+
partitions = Array.new(topic[:partition_count]) do |j|
|
41
|
+
partition = PartitionMetadata.new(topic[:partitions_metadata] + (j * PartitionMetadata.size))
|
42
|
+
raise Rdkafka::RdkafkaError.new(partition[:rd_kafka_resp_err]) unless partition[:rd_kafka_resp_err].zero?
|
43
|
+
partition.to_h
|
44
|
+
end
|
45
|
+
topic.to_h.merge!(partitions: partitions)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class CustomFFIStruct < FFI::Struct
|
50
|
+
def to_h
|
51
|
+
members.each_with_object({}) do |mem, hsh|
|
52
|
+
val = self.[](mem)
|
53
|
+
next if val.is_a?(FFI::Pointer) || mem == :rd_kafka_resp_err
|
54
|
+
|
55
|
+
hsh[mem] = self.[](mem)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class Metadata < CustomFFIStruct
|
61
|
+
layout :brokers_count, :int,
|
62
|
+
:brokers_metadata, :pointer,
|
63
|
+
:topics_count, :int,
|
64
|
+
:topics_metadata, :pointer,
|
65
|
+
:broker_id, :int32,
|
66
|
+
:broker_name, :string
|
67
|
+
end
|
68
|
+
|
69
|
+
class BrokerMetadata < CustomFFIStruct
|
70
|
+
layout :broker_id, :int32,
|
71
|
+
:broker_name, :string,
|
72
|
+
:broker_port, :int
|
73
|
+
end
|
74
|
+
|
75
|
+
class TopicMetadata < CustomFFIStruct
|
76
|
+
layout :topic_name, :string,
|
77
|
+
:partition_count, :int,
|
78
|
+
:partitions_metadata, :pointer,
|
79
|
+
:rd_kafka_resp_err, :int
|
80
|
+
end
|
81
|
+
|
82
|
+
class PartitionMetadata < CustomFFIStruct
|
83
|
+
layout :partition_id, :int32,
|
84
|
+
:rd_kafka_resp_err, :int,
|
85
|
+
:leader, :int32,
|
86
|
+
:replica_count, :int,
|
87
|
+
:replicas, :pointer,
|
88
|
+
:in_sync_replica_brokers, :int,
|
89
|
+
:isrs, :pointer
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Rdkafka
|
2
|
+
class Producer
|
3
|
+
class Client
|
4
|
+
def initialize(native)
|
5
|
+
@native = native
|
6
|
+
|
7
|
+
# Start thread to poll client for delivery callbacks
|
8
|
+
@polling_thread = Thread.new do
|
9
|
+
loop do
|
10
|
+
Rdkafka::Bindings.rd_kafka_poll(native, 250)
|
11
|
+
# Exit thread if closing and the poll queue is empty
|
12
|
+
if Thread.current[:closing] && Rdkafka::Bindings.rd_kafka_outq_len(native) == 0
|
13
|
+
break
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
@polling_thread.abort_on_exception = true
|
18
|
+
@polling_thread[:closing] = false
|
19
|
+
end
|
20
|
+
|
21
|
+
def native
|
22
|
+
@native
|
23
|
+
end
|
24
|
+
|
25
|
+
def finalizer
|
26
|
+
->(_) { close }
|
27
|
+
end
|
28
|
+
|
29
|
+
def closed?
|
30
|
+
@native.nil?
|
31
|
+
end
|
32
|
+
|
33
|
+
def close(object_id=nil)
|
34
|
+
return unless @native
|
35
|
+
|
36
|
+
# Indicate to polling thread that we're closing
|
37
|
+
@polling_thread[:closing] = true
|
38
|
+
# Wait for the polling thread to finish up
|
39
|
+
@polling_thread.join
|
40
|
+
|
41
|
+
Rdkafka::Bindings.rd_kafka_destroy(@native)
|
42
|
+
|
43
|
+
@native = nil
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Rdkafka
|
2
|
+
class Producer
|
3
|
+
# Handle to wait for a delivery report which is returned when
|
4
|
+
# producing a message.
|
5
|
+
class DeliveryHandle < Rdkafka::AbstractHandle
|
6
|
+
layout :pending, :bool,
|
7
|
+
:response, :int,
|
8
|
+
:partition, :int,
|
9
|
+
:offset, :int64
|
10
|
+
|
11
|
+
# @return [String] the name of the operation (e.g. "delivery")
|
12
|
+
def operation_name
|
13
|
+
"delivery"
|
14
|
+
end
|
15
|
+
|
16
|
+
# @return [DeliveryReport] a report on the delivery of the message
|
17
|
+
def create_result
|
18
|
+
DeliveryReport.new(self[:partition], self[:offset])
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Rdkafka
|
2
|
+
class Producer
|
3
|
+
# Delivery report for a successfully produced message.
|
4
|
+
class DeliveryReport
|
5
|
+
# The partition this message was produced to.
|
6
|
+
# @return [Integer]
|
7
|
+
attr_reader :partition
|
8
|
+
|
9
|
+
# The offset of the produced message.
|
10
|
+
# @return [Integer]
|
11
|
+
attr_reader :offset
|
12
|
+
|
13
|
+
# Error in case happen during produce.
|
14
|
+
# @return [String]
|
15
|
+
attr_reader :error
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def initialize(partition, offset, error = nil)
|
20
|
+
@partition = partition
|
21
|
+
@offset = offset
|
22
|
+
@error = error
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,178 @@
|
|
1
|
+
require "objspace"
|
2
|
+
|
3
|
+
module Rdkafka
|
4
|
+
# A producer for Kafka messages. To create a producer set up a {Config} and call {Config#producer producer} on that.
|
5
|
+
class Producer
|
6
|
+
# @private
|
7
|
+
# Returns the current delivery callback, by default this is nil.
|
8
|
+
#
|
9
|
+
# @return [Proc, nil]
|
10
|
+
attr_reader :delivery_callback
|
11
|
+
|
12
|
+
# @private
|
13
|
+
# Returns the number of arguments accepted by the callback, by default this is nil.
|
14
|
+
#
|
15
|
+
# @return [Integer, nil]
|
16
|
+
attr_reader :delivery_callback_arity
|
17
|
+
|
18
|
+
# @private
|
19
|
+
def initialize(client, partitioner_name)
|
20
|
+
@client = client
|
21
|
+
@partitioner_name = partitioner_name || "consistent_random"
|
22
|
+
|
23
|
+
# Makes sure, that the producer gets closed before it gets GCed by Ruby
|
24
|
+
ObjectSpace.define_finalizer(self, client.finalizer)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Set a callback that will be called every time a message is successfully produced.
|
28
|
+
# The callback is called with a {DeliveryReport} and {DeliveryHandle}
|
29
|
+
#
|
30
|
+
# @param callback [Proc, #call] The callback
|
31
|
+
#
|
32
|
+
# @return [nil]
|
33
|
+
def delivery_callback=(callback)
|
34
|
+
raise TypeError.new("Callback has to be callable") unless callback.respond_to?(:call)
|
35
|
+
@delivery_callback = callback
|
36
|
+
@delivery_callback_arity = arity(callback)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Close this producer and wait for the internal poll queue to empty.
|
40
|
+
def close
|
41
|
+
ObjectSpace.undefine_finalizer(self)
|
42
|
+
|
43
|
+
@client.close
|
44
|
+
end
|
45
|
+
|
46
|
+
# Partition count for a given topic.
|
47
|
+
# NOTE: If 'allow.auto.create.topics' is set to true in the broker, the topic will be auto-created after returning nil.
|
48
|
+
#
|
49
|
+
# @param topic [String] The topic name.
|
50
|
+
#
|
51
|
+
# @return partition count [Integer,nil]
|
52
|
+
#
|
53
|
+
def partition_count(topic)
|
54
|
+
closed_producer_check(__method__)
|
55
|
+
Rdkafka::Metadata.new(@client.native, topic).topics&.first[:partition_count]
|
56
|
+
end
|
57
|
+
|
58
|
+
# 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.
|
59
|
+
#
|
60
|
+
# When no partition is specified the underlying Kafka library picks a partition based on the key. If no key is specified, a random partition will be used.
|
61
|
+
# When a timestamp is provided this is used instead of the auto-generated timestamp.
|
62
|
+
#
|
63
|
+
# @param topic [String] The topic to produce to
|
64
|
+
# @param payload [String,nil] The message's payload
|
65
|
+
# @param key [String, nil] The message's key
|
66
|
+
# @param partition [Integer,nil] Optional partition to produce to
|
67
|
+
# @param partition_key [String, nil] Optional partition key based on which partition assignment can happen
|
68
|
+
# @param timestamp [Time,Integer,nil] Optional timestamp of this message. Integer timestamp is in milliseconds since Jan 1 1970.
|
69
|
+
# @param headers [Hash<String,String>] Optional message headers
|
70
|
+
#
|
71
|
+
# @raise [RdkafkaError] When adding the message to rdkafka's queue failed
|
72
|
+
#
|
73
|
+
# @return [DeliveryHandle] Delivery handle that can be used to wait for the result of producing this message
|
74
|
+
def produce(topic:, payload: nil, key: nil, partition: nil, partition_key: nil, timestamp: nil, headers: nil)
|
75
|
+
closed_producer_check(__method__)
|
76
|
+
|
77
|
+
# Start by checking and converting the input
|
78
|
+
|
79
|
+
# Get payload length
|
80
|
+
payload_size = if payload.nil?
|
81
|
+
0
|
82
|
+
else
|
83
|
+
payload.bytesize
|
84
|
+
end
|
85
|
+
|
86
|
+
# Get key length
|
87
|
+
key_size = if key.nil?
|
88
|
+
0
|
89
|
+
else
|
90
|
+
key.bytesize
|
91
|
+
end
|
92
|
+
|
93
|
+
if partition_key
|
94
|
+
partition_count = partition_count(topic)
|
95
|
+
# If the topic is not present, set to -1
|
96
|
+
partition = Rdkafka::Bindings.partitioner(partition_key, partition_count, @partitioner_name) if partition_count
|
97
|
+
end
|
98
|
+
|
99
|
+
# If partition is nil, use -1 to let librdafka set the partition randomly or
|
100
|
+
# based on the key when present.
|
101
|
+
partition ||= -1
|
102
|
+
|
103
|
+
# If timestamp is nil use 0 and let Kafka set one. If an integer or time
|
104
|
+
# use it.
|
105
|
+
raw_timestamp = if timestamp.nil?
|
106
|
+
0
|
107
|
+
elsif timestamp.is_a?(Integer)
|
108
|
+
timestamp
|
109
|
+
elsif timestamp.is_a?(Time)
|
110
|
+
(timestamp.to_i * 1000) + (timestamp.usec / 1000)
|
111
|
+
else
|
112
|
+
raise TypeError.new("Timestamp has to be nil, an Integer or a Time")
|
113
|
+
end
|
114
|
+
|
115
|
+
delivery_handle = DeliveryHandle.new
|
116
|
+
delivery_handle[:pending] = true
|
117
|
+
delivery_handle[:response] = -1
|
118
|
+
delivery_handle[:partition] = -1
|
119
|
+
delivery_handle[:offset] = -1
|
120
|
+
DeliveryHandle.register(delivery_handle)
|
121
|
+
|
122
|
+
args = [
|
123
|
+
:int, Rdkafka::Bindings::RD_KAFKA_VTYPE_TOPIC, :string, topic,
|
124
|
+
:int, Rdkafka::Bindings::RD_KAFKA_VTYPE_MSGFLAGS, :int, Rdkafka::Bindings::RD_KAFKA_MSG_F_COPY,
|
125
|
+
:int, Rdkafka::Bindings::RD_KAFKA_VTYPE_VALUE, :buffer_in, payload, :size_t, payload_size,
|
126
|
+
:int, Rdkafka::Bindings::RD_KAFKA_VTYPE_KEY, :buffer_in, key, :size_t, key_size,
|
127
|
+
:int, Rdkafka::Bindings::RD_KAFKA_VTYPE_PARTITION, :int32, partition,
|
128
|
+
:int, Rdkafka::Bindings::RD_KAFKA_VTYPE_TIMESTAMP, :int64, raw_timestamp,
|
129
|
+
:int, Rdkafka::Bindings::RD_KAFKA_VTYPE_OPAQUE, :pointer, delivery_handle,
|
130
|
+
]
|
131
|
+
|
132
|
+
if headers
|
133
|
+
headers.each do |key0, value0|
|
134
|
+
key = key0.to_s
|
135
|
+
value = value0.to_s
|
136
|
+
args << :int << Rdkafka::Bindings::RD_KAFKA_VTYPE_HEADER
|
137
|
+
args << :string << key
|
138
|
+
args << :pointer << value
|
139
|
+
args << :size_t << value.bytes.size
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
args << :int << Rdkafka::Bindings::RD_KAFKA_VTYPE_END
|
144
|
+
|
145
|
+
# Produce the message
|
146
|
+
response = Rdkafka::Bindings.rd_kafka_producev(
|
147
|
+
@client.native,
|
148
|
+
*args
|
149
|
+
)
|
150
|
+
|
151
|
+
# Raise error if the produce call was not successful
|
152
|
+
if response != 0
|
153
|
+
DeliveryHandle.remove(delivery_handle.to_ptr.address)
|
154
|
+
raise RdkafkaError.new(response)
|
155
|
+
end
|
156
|
+
|
157
|
+
delivery_handle
|
158
|
+
end
|
159
|
+
|
160
|
+
# @private
|
161
|
+
def call_delivery_callback(delivery_report, delivery_handle)
|
162
|
+
return unless @delivery_callback
|
163
|
+
|
164
|
+
args = [delivery_report, delivery_handle].take(@delivery_callback_arity)
|
165
|
+
@delivery_callback.call(*args)
|
166
|
+
end
|
167
|
+
|
168
|
+
def arity(callback)
|
169
|
+
return callback.arity if callback.respond_to?(:arity)
|
170
|
+
|
171
|
+
callback.method(:call).arity
|
172
|
+
end
|
173
|
+
|
174
|
+
def closed_producer_check(method)
|
175
|
+
raise Rdkafka::ClosedProducerError.new(method) if @client.closed?
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
data/lib/rdkafka.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require "rdkafka/version"
|
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"
|
9
|
+
require "rdkafka/bindings"
|
10
|
+
require "rdkafka/callbacks"
|
11
|
+
require "rdkafka/config"
|
12
|
+
require "rdkafka/consumer"
|
13
|
+
require "rdkafka/consumer/headers"
|
14
|
+
require "rdkafka/consumer/message"
|
15
|
+
require "rdkafka/consumer/partition"
|
16
|
+
require "rdkafka/consumer/topic_partition_list"
|
17
|
+
require "rdkafka/error"
|
18
|
+
require "rdkafka/metadata"
|
19
|
+
require "rdkafka/producer"
|
20
|
+
require "rdkafka/producer/client"
|
21
|
+
require "rdkafka/producer/delivery_handle"
|
22
|
+
require "rdkafka/producer/delivery_report"
|
data/rdkafka.gemspec
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require File.expand_path('lib/rdkafka/version', __dir__)
|
2
|
+
|
3
|
+
Gem::Specification.new do |gem|
|
4
|
+
gem.authors = ['Thijs Cadier']
|
5
|
+
gem.email = ["thijs@appsignal.com"]
|
6
|
+
gem.description = "Modern Kafka client library for Ruby based on librdkafka"
|
7
|
+
gem.summary = "The rdkafka gem is a modern Kafka client library for Ruby based on librdkafka. It wraps the production-ready C client using the ffi gem and targets Kafka 1.0+ and Ruby 2.4+."
|
8
|
+
gem.license = 'MIT'
|
9
|
+
gem.homepage = 'https://github.com/thijsc/rdkafka-ruby'
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = 'karafka-rdkafka'
|
15
|
+
gem.require_paths = ['lib']
|
16
|
+
gem.version = Rdkafka::VERSION
|
17
|
+
gem.required_ruby_version = '>= 2.6'
|
18
|
+
gem.extensions = %w(ext/Rakefile)
|
19
|
+
|
20
|
+
if $PROGRAM_NAME.end_with?('gem')
|
21
|
+
gem.signing_key = File.expand_path('~/.ssh/gem-private_key.pem')
|
22
|
+
end
|
23
|
+
|
24
|
+
gem.cert_chain = %w[certs/cert_chain.pem]
|
25
|
+
|
26
|
+
gem.add_dependency 'ffi', '~> 1.15'
|
27
|
+
gem.add_dependency 'mini_portile2', '~> 2.6'
|
28
|
+
gem.add_dependency 'rake', '> 12'
|
29
|
+
|
30
|
+
gem.add_development_dependency 'pry'
|
31
|
+
gem.add_development_dependency 'rspec', '~> 3.5'
|
32
|
+
gem.add_development_dependency 'rake'
|
33
|
+
gem.add_development_dependency 'simplecov'
|
34
|
+
gem.add_development_dependency 'guard'
|
35
|
+
gem.add_development_dependency 'guard-rspec'
|
36
|
+
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
|