ralphrodkey-kafka-rb 0.0.15
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.
- data/LICENSE +202 -0
- data/README.md +123 -0
- data/Rakefile +40 -0
- data/bin/leffen-kafka-consumer +6 -0
- data/bin/leffen-kafka-publish +6 -0
- data/lib/kafka.rb +40 -0
- data/lib/kafka/batch.rb +28 -0
- data/lib/kafka/cli.rb +170 -0
- data/lib/kafka/consumer.rb +126 -0
- data/lib/kafka/encoder.rb +59 -0
- data/lib/kafka/error_codes.rb +53 -0
- data/lib/kafka/io.rb +57 -0
- data/lib/kafka/message.rb +209 -0
- data/lib/kafka/multi_producer.rb +35 -0
- data/lib/kafka/producer.rb +42 -0
- data/lib/kafka/producer_request.rb +26 -0
- data/lib/kafka/request_type.rb +23 -0
- data/lib/leffen-kafka.rb +16 -0
- data/lib/ralphrodkey-kafka.rb +16 -0
- data/spec/batch_spec.rb +35 -0
- data/spec/cli_spec.rb +133 -0
- data/spec/consumer_spec.rb +146 -0
- data/spec/encoder_spec.rb +251 -0
- data/spec/io_spec.rb +88 -0
- data/spec/kafka_spec.rb +20 -0
- data/spec/message_spec.rb +227 -0
- data/spec/multi_producer_spec.rb +74 -0
- data/spec/producer_request_spec.rb +38 -0
- data/spec/producer_spec.rb +71 -0
- data/spec/spec_helper.rb +18 -0
- metadata +108 -0
@@ -0,0 +1,146 @@
|
|
1
|
+
# Licensed to the Apache Software Foundation (ASF) under one or more
|
2
|
+
# contributor license agreements. See the NOTICE file distributed with
|
3
|
+
# this work for additional information regarding copyright ownership.
|
4
|
+
# The ASF licenses this file to You under the Apache License, Version 2.0
|
5
|
+
# (the "License"); you may not use this file except in compliance with
|
6
|
+
# the License. You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
16
|
+
|
17
|
+
describe Consumer do
|
18
|
+
|
19
|
+
before(:each) do
|
20
|
+
@mocked_socket = mock(TCPSocket)
|
21
|
+
TCPSocket.stub!(:new).and_return(@mocked_socket) # don't use a real socket
|
22
|
+
@consumer = Consumer.new(:offset => 0)
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "Kafka Consumer" do
|
26
|
+
|
27
|
+
it "should have a Kafka::RequestType::FETCH" do
|
28
|
+
Kafka::RequestType::FETCH.should eql(1)
|
29
|
+
@consumer.should respond_to(:request_type)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should have a topic and a partition" do
|
33
|
+
@consumer.should respond_to(:topic)
|
34
|
+
@consumer.should respond_to(:partition)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should have a polling option, and a default value" do
|
38
|
+
Consumer::DEFAULT_POLLING_INTERVAL.should eql(2)
|
39
|
+
@consumer.should respond_to(:polling)
|
40
|
+
@consumer.polling.should eql(2)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should set a topic and partition on initialize" do
|
44
|
+
@consumer = Consumer.new({ :host => "localhost", :port => 9092, :topic => "testing" })
|
45
|
+
@consumer.topic.should eql("testing")
|
46
|
+
@consumer.partition.should eql(0)
|
47
|
+
@consumer = Consumer.new({ :topic => "testing", :partition => 3 })
|
48
|
+
@consumer.partition.should eql(3)
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should set default host and port if none is specified" do
|
52
|
+
@consumer = Consumer.new
|
53
|
+
@consumer.host.should eql("localhost")
|
54
|
+
@consumer.port.should eql(9092)
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should not have a default offset but be able to set it" do
|
58
|
+
@consumer = Consumer.new
|
59
|
+
@consumer.offset.should be_nil
|
60
|
+
@consumer = Consumer.new({ :offset => 1111 })
|
61
|
+
@consumer.offset.should eql(1111)
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should have a max size" do
|
65
|
+
Consumer::MAX_SIZE.should eql(1048576)
|
66
|
+
@consumer.max_size.should eql(1048576)
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should return the size of the request" do
|
70
|
+
@consumer.topic = "someothertopicname"
|
71
|
+
@consumer.encoded_request_size.should eql([38].pack("N"))
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should encode a request to consume" do
|
75
|
+
bytes = [Kafka::RequestType::FETCH].pack("n") + ["test".length].pack("n") + "test" + [0].pack("N") + [0].pack("q").reverse + [Kafka::Consumer::MAX_SIZE].pack("N")
|
76
|
+
@consumer.encode_request(Kafka::RequestType::FETCH, "test", 0, 0, Kafka::Consumer::MAX_SIZE).should eql(bytes)
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should read the response data" do
|
80
|
+
bytes = [0].pack("n") + [1120192889].pack("N") + "ale"
|
81
|
+
@mocked_socket.should_receive(:read).and_return([9].pack("N"))
|
82
|
+
@mocked_socket.should_receive(:read).with(9).and_return(bytes)
|
83
|
+
@consumer.read_data_response.should eql(bytes[2,7])
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should send a consumer request" do
|
87
|
+
@consumer.stub!(:encoded_request_size).and_return(666)
|
88
|
+
@consumer.stub!(:encode_request).and_return("someencodedrequest")
|
89
|
+
@consumer.should_receive(:write).with("someencodedrequest").exactly(:once).and_return(true)
|
90
|
+
@consumer.should_receive(:write).with(666).exactly(:once).and_return(true)
|
91
|
+
@consumer.send_consume_request.should eql(true)
|
92
|
+
end
|
93
|
+
|
94
|
+
it "should consume messages" do
|
95
|
+
@consumer.should_receive(:send_consume_request).and_return(true)
|
96
|
+
@consumer.should_receive(:read_data_response).and_return("")
|
97
|
+
@consumer.consume.should eql([])
|
98
|
+
end
|
99
|
+
|
100
|
+
it "should loop and execute a block with the consumed messages" do
|
101
|
+
@consumer.stub!(:consume).and_return([mock(Kafka::Message)])
|
102
|
+
messages = []
|
103
|
+
messages.should_receive(:<<).exactly(:once).and_return([])
|
104
|
+
@consumer.loop do |message|
|
105
|
+
messages << message
|
106
|
+
break # we don't wanna loop forever on the test
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
it "should loop (every N seconds, configurable on polling attribute), and execute a block with the consumed messages" do
|
111
|
+
@consumer = Consumer.new({ :polling => 1 })
|
112
|
+
@consumer.stub!(:consume).and_return([mock(Kafka::Message)])
|
113
|
+
messages = []
|
114
|
+
messages.should_receive(:<<).exactly(:twice).and_return([])
|
115
|
+
executed_times = 0
|
116
|
+
@consumer.loop do |message|
|
117
|
+
messages << message
|
118
|
+
executed_times += 1
|
119
|
+
break if executed_times >= 2 # we don't wanna loop forever on the test, only 2 seconds
|
120
|
+
end
|
121
|
+
|
122
|
+
executed_times.should eql(2)
|
123
|
+
end
|
124
|
+
|
125
|
+
it "should fetch initial offset if no offset is given" do
|
126
|
+
@consumer = Consumer.new
|
127
|
+
@consumer.should_receive(:fetch_latest_offset).exactly(:once).and_return(1000)
|
128
|
+
@consumer.should_receive(:send_consume_request).and_return(true)
|
129
|
+
@consumer.should_receive(:read_data_response).and_return("")
|
130
|
+
@consumer.consume
|
131
|
+
@consumer.offset.should eql(1000)
|
132
|
+
end
|
133
|
+
|
134
|
+
it "should encode an offset request" do
|
135
|
+
bytes = [Kafka::RequestType::OFFSETS].pack("n") + ["test".length].pack("n") + "test" + [0].pack("N") + [-1].pack("q").reverse + [Kafka::Consumer::MAX_OFFSETS].pack("N")
|
136
|
+
@consumer.encode_request(Kafka::RequestType::OFFSETS, "test", 0, -1, Kafka::Consumer::MAX_OFFSETS).should eql(bytes)
|
137
|
+
end
|
138
|
+
|
139
|
+
it "should parse an offsets response" do
|
140
|
+
bytes = [0].pack("n") + [1].pack('N') + [21346].pack('q').reverse
|
141
|
+
@mocked_socket.should_receive(:read).and_return([14].pack("N"))
|
142
|
+
@mocked_socket.should_receive(:read).and_return(bytes)
|
143
|
+
@consumer.read_offsets_response.should eql(21346)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,251 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# Licensed to the Apache Software Foundation (ASF) under one or more
|
4
|
+
# contributor license agreements. See the NOTICE file distributed with
|
5
|
+
# this work for additional information regarding copyright ownership.
|
6
|
+
# The ASF licenses this file to You under the Apache License, Version 2.0
|
7
|
+
# (the "License"); you may not use this file except in compliance with
|
8
|
+
# the License. You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
18
|
+
|
19
|
+
describe Encoder do
|
20
|
+
def check_message(bytes, message)
|
21
|
+
encoded = [message.magic].pack("C") + [message.calculate_checksum].pack("N") + message.payload
|
22
|
+
encoded = [encoded.length].pack("N") + encoded
|
23
|
+
bytes.should == encoded
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "Message Encoding" do
|
27
|
+
it "should encode a message" do
|
28
|
+
message = Kafka::Message.new("alejandro")
|
29
|
+
check_message(described_class.message(message), message)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should encode an empty message" do
|
33
|
+
message = Kafka::Message.new
|
34
|
+
check_message(described_class.message(message), message)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should encode strings containing non-ASCII characters" do
|
38
|
+
message = Kafka::Message.new("ümlaut")
|
39
|
+
encoded = described_class.message(message)
|
40
|
+
message = Kafka::Message.parse_from(encoded).messages.first
|
41
|
+
if RUBY_VERSION[0,3] == "1.8" # Use old iconv on Ruby 1.8 for encoding
|
42
|
+
#ic = Iconv.new('UTF-8//IGNORE', 'UTF-8')
|
43
|
+
#ic.iconv(message.payload).should eql("ümlaut")
|
44
|
+
message.payload.should eql("ümlaut")
|
45
|
+
else
|
46
|
+
message.payload.force_encoding(Encoding::UTF_8).should eql("ümlaut")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should encode strings containing non-ASCII characters" do
|
51
|
+
message = Kafka::Message.new("\214")
|
52
|
+
encoded = described_class.message(message)
|
53
|
+
message = Kafka::Message.parse_from(encoded).messages.first
|
54
|
+
if RUBY_VERSION[0,3] == "1.8"
|
55
|
+
message.payload.should eql("\214")
|
56
|
+
else
|
57
|
+
message.payload.force_encoding(Encoding::UTF_8).should eql("\214")
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe :compression do
|
63
|
+
before do
|
64
|
+
@message = Kafka::Message.new "foo"
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should default to no compression" do
|
68
|
+
msg = "foo"
|
69
|
+
checksum = Zlib.crc32 msg
|
70
|
+
magic = 0
|
71
|
+
msg_size = 5 + msg.size
|
72
|
+
raw = [msg_size, magic, checksum, msg].pack "NCNa#{msg.size}"
|
73
|
+
|
74
|
+
Encoder.message(@message).should == raw
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should support GZip compression" do
|
78
|
+
buffer = StringIO.new
|
79
|
+
gz = Zlib::GzipWriter.new buffer, nil, nil
|
80
|
+
gz.write "foo"
|
81
|
+
gz.close
|
82
|
+
buffer.rewind
|
83
|
+
msg = buffer.string
|
84
|
+
checksum = Zlib.crc32 msg
|
85
|
+
magic = 1
|
86
|
+
attrs = 1
|
87
|
+
msg_size = 6 + msg.size
|
88
|
+
raw = [msg_size, magic, attrs, checksum, msg].pack "NCCNa#{msg.size}"
|
89
|
+
Encoder.message(@message, 1).should == raw
|
90
|
+
end
|
91
|
+
|
92
|
+
if Object.const_defined? "Snappy"
|
93
|
+
it "should support Snappy compression" do
|
94
|
+
buffer = StringIO.new
|
95
|
+
Snappy::Writer.new buffer do |w|
|
96
|
+
w << "foo"
|
97
|
+
end
|
98
|
+
buffer.rewind
|
99
|
+
msg = buffer.string
|
100
|
+
checksum = Zlib.crc32 msg
|
101
|
+
magic = 1
|
102
|
+
attrs = 2
|
103
|
+
msg_size = 6 + msg.size
|
104
|
+
raw = [msg_size, magic, attrs, checksum, msg].pack "NCCNa#{msg.size}"
|
105
|
+
|
106
|
+
Encoder.message(@message, 2).should == raw
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
describe "produce" do
|
112
|
+
it "should binary encode an empty request" do
|
113
|
+
bytes = described_class.produce("test", 0, [])
|
114
|
+
bytes.length.should eql(20)
|
115
|
+
bytes.should eql("\000\000\000\020\000\000\000\004test\000\000\000\000\000\000\000\000")
|
116
|
+
end
|
117
|
+
|
118
|
+
it "should binary encode a request with a message, using a specific wire format" do
|
119
|
+
message = Kafka::Message.new("ale")
|
120
|
+
bytes = described_class.produce("test", 3, message)
|
121
|
+
data_size = bytes[0, 4].unpack("N").shift
|
122
|
+
request_id = bytes[4, 2].unpack("n").shift
|
123
|
+
topic_length = bytes[6, 2].unpack("n").shift
|
124
|
+
topic = bytes[8, 4]
|
125
|
+
partition = bytes[12, 4].unpack("N").shift
|
126
|
+
messages_length = bytes[16, 4].unpack("N").shift
|
127
|
+
messages = bytes[20, messages_length]
|
128
|
+
|
129
|
+
bytes.length.should eql(32)
|
130
|
+
data_size.should eql(28)
|
131
|
+
request_id.should eql(0)
|
132
|
+
topic_length.should eql(4)
|
133
|
+
topic.should eql("test")
|
134
|
+
partition.should eql(3)
|
135
|
+
messages_length.should eql(12)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
describe "message_set" do
|
140
|
+
it "should compress messages into a message set" do
|
141
|
+
message_one = Kafka::Message.new "foo"
|
142
|
+
message_two = Kafka::Message.new "bar"
|
143
|
+
bytes = described_class.message_set [message_one, message_two], Kafka::Message::GZIP_COMPRESSION
|
144
|
+
|
145
|
+
messages = Kafka::Message.parse_from bytes
|
146
|
+
messages.should be_a Kafka::Message::MessageSet
|
147
|
+
messages.messages.size.should == 2
|
148
|
+
|
149
|
+
messages.messages[0].should be_a Kafka::Message
|
150
|
+
messages.messages[0].payload.should == "foo"
|
151
|
+
messages.messages[1].should be_a Kafka::Message
|
152
|
+
messages.messages[1].payload.should == "bar"
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
describe "multiproduce" do
|
157
|
+
it "encodes an empty request" do
|
158
|
+
bytes = described_class.multiproduce([])
|
159
|
+
bytes.length.should == 8
|
160
|
+
bytes.should == "\x00\x00\x00\x04\x00\x03\x00\x00"
|
161
|
+
end
|
162
|
+
|
163
|
+
it "encodes a request with a single topic/partition" do
|
164
|
+
message = Kafka::Message.new("ale")
|
165
|
+
bytes = described_class.multiproduce(Kafka::ProducerRequest.new("test", message))
|
166
|
+
|
167
|
+
req_length = bytes[0, 4].unpack("N").shift
|
168
|
+
req_type = bytes[4, 2].unpack("n").shift
|
169
|
+
tp_count = bytes[6, 2].unpack("n").shift
|
170
|
+
|
171
|
+
req_type.should == Kafka::RequestType::MULTIPRODUCE
|
172
|
+
tp_count.should == 1
|
173
|
+
|
174
|
+
topic_length = bytes[8, 2].unpack("n").shift
|
175
|
+
topic = bytes[10, 4]
|
176
|
+
partition = bytes[14, 4].unpack("N").shift
|
177
|
+
messages_length = bytes[18, 4].unpack("N").shift
|
178
|
+
messages_data = bytes[22, messages_length]
|
179
|
+
|
180
|
+
topic_length.should == 4
|
181
|
+
topic.should == "test"
|
182
|
+
partition.should == 0
|
183
|
+
messages_length.should == 12
|
184
|
+
check_message(messages_data, message)
|
185
|
+
end
|
186
|
+
|
187
|
+
it "encodes a request with a single topic/partition but multiple messages" do
|
188
|
+
messages = [Kafka::Message.new("ale"), Kafka::Message.new("beer")]
|
189
|
+
bytes = described_class.multiproduce(Kafka::ProducerRequest.new("test", messages))
|
190
|
+
|
191
|
+
req_length = bytes[0, 4].unpack("N").shift
|
192
|
+
req_type = bytes[4, 2].unpack("n").shift
|
193
|
+
tp_count = bytes[6, 2].unpack("n").shift
|
194
|
+
|
195
|
+
req_type.should == Kafka::RequestType::MULTIPRODUCE
|
196
|
+
tp_count.should == 1
|
197
|
+
|
198
|
+
topic_length = bytes[8, 2].unpack("n").shift
|
199
|
+
topic = bytes[10, 4]
|
200
|
+
partition = bytes[14, 4].unpack("N").shift
|
201
|
+
messages_length = bytes[18, 4].unpack("N").shift
|
202
|
+
messages_data = bytes[22, messages_length]
|
203
|
+
|
204
|
+
topic_length.should == 4
|
205
|
+
topic.should == "test"
|
206
|
+
partition.should == 0
|
207
|
+
messages_length.should == 25
|
208
|
+
check_message(messages_data[0, 12], messages[0])
|
209
|
+
check_message(messages_data[12, 13], messages[1])
|
210
|
+
end
|
211
|
+
|
212
|
+
it "encodes a request with multiple topic/partitions" do
|
213
|
+
messages = [Kafka::Message.new("ale"), Kafka::Message.new("beer")]
|
214
|
+
bytes = described_class.multiproduce([
|
215
|
+
Kafka::ProducerRequest.new("test", messages[0]),
|
216
|
+
Kafka::ProducerRequest.new("topic", messages[1], :partition => 1),
|
217
|
+
])
|
218
|
+
|
219
|
+
req_length = bytes[0, 4].unpack("N").shift
|
220
|
+
req_type = bytes[4, 2].unpack("n").shift
|
221
|
+
tp_count = bytes[6, 2].unpack("n").shift
|
222
|
+
|
223
|
+
req_type.should == Kafka::RequestType::MULTIPRODUCE
|
224
|
+
tp_count.should == 2
|
225
|
+
|
226
|
+
topic_length = bytes[8, 2].unpack("n").shift
|
227
|
+
topic = bytes[10, 4]
|
228
|
+
partition = bytes[14, 4].unpack("N").shift
|
229
|
+
messages_length = bytes[18, 4].unpack("N").shift
|
230
|
+
messages_data = bytes[22, 12]
|
231
|
+
|
232
|
+
topic_length.should == 4
|
233
|
+
topic.should == "test"
|
234
|
+
partition.should == 0
|
235
|
+
messages_length.should == 12
|
236
|
+
check_message(messages_data[0, 12], messages[0])
|
237
|
+
|
238
|
+
topic_length = bytes[34, 2].unpack("n").shift
|
239
|
+
topic = bytes[36, 5]
|
240
|
+
partition = bytes[41, 4].unpack("N").shift
|
241
|
+
messages_length = bytes[45, 4].unpack("N").shift
|
242
|
+
messages_data = bytes[49, 13]
|
243
|
+
|
244
|
+
topic_length.should == 5
|
245
|
+
topic.should == "topic"
|
246
|
+
partition.should == 1
|
247
|
+
messages_length.should == 13
|
248
|
+
check_message(messages_data[0, 13], messages[1])
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
data/spec/io_spec.rb
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
# Licensed to the Apache Software Foundation (ASF) under one or more
|
2
|
+
# contributor license agreements. See the NOTICE file distributed with
|
3
|
+
# this work for additional information regarding copyright ownership.
|
4
|
+
# The ASF licenses this file to You under the Apache License, Version 2.0
|
5
|
+
# (the "License"); you may not use this file except in compliance with
|
6
|
+
# the License. You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
16
|
+
|
17
|
+
class IOTest
|
18
|
+
include Kafka::IO
|
19
|
+
end
|
20
|
+
|
21
|
+
describe IO do
|
22
|
+
|
23
|
+
before(:each) do
|
24
|
+
@mocked_socket = mock(TCPSocket)
|
25
|
+
TCPSocket.stub!(:new).and_return(@mocked_socket) # don't use a real socket
|
26
|
+
@io = IOTest.new
|
27
|
+
@io.connect("somehost", 9093)
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "default methods" do
|
31
|
+
it "has a socket, a host and a port" do
|
32
|
+
[:socket, :host, :port].each do |m|
|
33
|
+
@io.should respond_to(m.to_sym)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
it "raises an exception if no host and port is specified" do
|
38
|
+
lambda {
|
39
|
+
io = IOTest.new
|
40
|
+
io.connect
|
41
|
+
}.should raise_error(ArgumentError)
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should remember the port and host on connect" do
|
45
|
+
@io.connect("somehost", 9093)
|
46
|
+
@io.host.should eql("somehost")
|
47
|
+
@io.port.should eql(9093)
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should write to a socket" do
|
51
|
+
data = "some data"
|
52
|
+
@mocked_socket.should_receive(:write).with(data).and_return(9)
|
53
|
+
@io.write(data).should eql(9)
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should read from a socket" do
|
57
|
+
length = 200
|
58
|
+
@mocked_socket.should_receive(:read).with(length).and_return("foo")
|
59
|
+
@io.read(length)
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should disconnect on a timeout when reading from a socket (to aviod protocol desync state)" do
|
63
|
+
length = 200
|
64
|
+
@mocked_socket.should_receive(:read).with(length).and_raise(Errno::EAGAIN)
|
65
|
+
@io.should_receive(:disconnect)
|
66
|
+
lambda { @io.read(length) }.should raise_error(Kafka::SocketError)
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should disconnect" do
|
70
|
+
@io.should respond_to(:disconnect)
|
71
|
+
@mocked_socket.should_receive(:close).and_return(nil)
|
72
|
+
@io.disconnect
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should reconnect" do
|
76
|
+
TCPSocket.should_receive(:new)
|
77
|
+
@io.reconnect
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should disconnect on a broken pipe error" do
|
81
|
+
[Errno::ECONNABORTED, Errno::EPIPE, Errno::ECONNRESET].each do |error|
|
82
|
+
@mocked_socket.should_receive(:write).exactly(:once).and_raise(error)
|
83
|
+
@mocked_socket.should_receive(:close).exactly(:once).and_return(nil)
|
84
|
+
lambda { @io.write("some data to send") }.should raise_error(Kafka::SocketError)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|