codeclimate-poseidon 0.0.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +21 -0
- data/.rspec +2 -0
- data/.travis.yml +14 -0
- data/.yardopts +8 -0
- data/CHANGES.md +31 -0
- data/Gemfile +13 -0
- data/LICENSE.txt +22 -0
- data/README.md +72 -0
- data/Rakefile +20 -0
- data/TODO.md +27 -0
- data/examples/consumer.rb +18 -0
- data/examples/producer.rb +9 -0
- data/lib/poseidon.rb +120 -0
- data/lib/poseidon/broker_pool.rb +86 -0
- data/lib/poseidon/cluster_metadata.rb +94 -0
- data/lib/poseidon/compressed_value.rb +23 -0
- data/lib/poseidon/compression.rb +30 -0
- data/lib/poseidon/compression/gzip_codec.rb +23 -0
- data/lib/poseidon/compression/snappy_codec.rb +29 -0
- data/lib/poseidon/connection.rb +169 -0
- data/lib/poseidon/fetched_message.rb +37 -0
- data/lib/poseidon/message.rb +151 -0
- data/lib/poseidon/message_conductor.rb +86 -0
- data/lib/poseidon/message_set.rb +80 -0
- data/lib/poseidon/message_to_send.rb +33 -0
- data/lib/poseidon/messages_for_broker.rb +56 -0
- data/lib/poseidon/messages_to_send.rb +47 -0
- data/lib/poseidon/messages_to_send_batch.rb +27 -0
- data/lib/poseidon/partition_consumer.rb +225 -0
- data/lib/poseidon/producer.rb +199 -0
- data/lib/poseidon/producer_compression_config.rb +37 -0
- data/lib/poseidon/protocol.rb +122 -0
- data/lib/poseidon/protocol/protocol_struct.rb +256 -0
- data/lib/poseidon/protocol/request_buffer.rb +77 -0
- data/lib/poseidon/protocol/response_buffer.rb +72 -0
- data/lib/poseidon/sync_producer.rb +161 -0
- data/lib/poseidon/topic_metadata.rb +89 -0
- data/lib/poseidon/version.rb +4 -0
- data/log/.gitkeep +0 -0
- data/poseidon.gemspec +27 -0
- data/spec/integration/multiple_brokers/consumer_spec.rb +45 -0
- data/spec/integration/multiple_brokers/metadata_failures_spec.rb +144 -0
- data/spec/integration/multiple_brokers/rebalance_spec.rb +69 -0
- data/spec/integration/multiple_brokers/round_robin_spec.rb +41 -0
- data/spec/integration/multiple_brokers/spec_helper.rb +60 -0
- data/spec/integration/simple/compression_spec.rb +23 -0
- data/spec/integration/simple/connection_spec.rb +35 -0
- data/spec/integration/simple/multiple_brokers_spec.rb +10 -0
- data/spec/integration/simple/simple_producer_and_consumer_spec.rb +121 -0
- data/spec/integration/simple/spec_helper.rb +16 -0
- data/spec/integration/simple/truncated_messages_spec.rb +46 -0
- data/spec/integration/simple/unavailable_broker_spec.rb +72 -0
- data/spec/spec_helper.rb +32 -0
- data/spec/test_cluster.rb +211 -0
- data/spec/unit/broker_pool_spec.rb +98 -0
- data/spec/unit/cluster_metadata_spec.rb +46 -0
- data/spec/unit/compression/gzip_codec_spec.rb +34 -0
- data/spec/unit/compression/snappy_codec_spec.rb +49 -0
- data/spec/unit/compression_spec.rb +17 -0
- data/spec/unit/connection_spec.rb +4 -0
- data/spec/unit/fetched_message_spec.rb +11 -0
- data/spec/unit/message_conductor_spec.rb +164 -0
- data/spec/unit/message_set_spec.rb +42 -0
- data/spec/unit/message_spec.rb +129 -0
- data/spec/unit/message_to_send_spec.rb +10 -0
- data/spec/unit/messages_for_broker_spec.rb +54 -0
- data/spec/unit/messages_to_send_batch_spec.rb +25 -0
- data/spec/unit/messages_to_send_spec.rb +63 -0
- data/spec/unit/partition_consumer_spec.rb +142 -0
- data/spec/unit/producer_compression_config_spec.rb +42 -0
- data/spec/unit/producer_spec.rb +51 -0
- data/spec/unit/protocol/request_buffer_spec.rb +16 -0
- data/spec/unit/protocol_spec.rb +54 -0
- data/spec/unit/sync_producer_spec.rb +156 -0
- data/spec/unit/topic_metadata_spec.rb +43 -0
- metadata +225 -0
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
include Protocol
|
4
|
+
RSpec.describe ClusterMetadata do
|
5
|
+
describe "populated" do
|
6
|
+
before(:each) do
|
7
|
+
partitions = [
|
8
|
+
PartitionMetadata.new(0, 1, 1, [1,2], [1,2]),
|
9
|
+
PartitionMetadata.new(0, 2, 2, [2,1], [2,1])
|
10
|
+
]
|
11
|
+
topics = [TopicMetadata.new(TopicMetadataStruct.new(0, "test", partitions))]
|
12
|
+
|
13
|
+
brokers = [Broker.new(1, "host1", 1), Broker.new(2, "host2", 2)]
|
14
|
+
|
15
|
+
@mr = MetadataResponse.new(nil, brokers, topics)
|
16
|
+
|
17
|
+
@cm = ClusterMetadata.new
|
18
|
+
@cm.update(@mr)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "knows when it has metadata for a set of topics" do
|
22
|
+
have_metadata = @cm.have_metadata_for_topics?(Set.new(["test"]))
|
23
|
+
expect(have_metadata).to eq(true)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "knows when it doesn't have metadata for a topic" do
|
27
|
+
have_metadata = @cm.have_metadata_for_topics?(Set.new(["test", "no_data"]))
|
28
|
+
expect(have_metadata).to eq(false)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "provides topic metadata for a set of topics" do
|
32
|
+
topic_metadata = @cm.metadata_for_topics(Set.new(["test"]))
|
33
|
+
expect(topic_metadata).to eq({ "test" => @mr.topics.first })
|
34
|
+
end
|
35
|
+
|
36
|
+
it "provides broker information" do
|
37
|
+
broker = @cm.broker(1)
|
38
|
+
expect(broker).to eq(@mr.brokers.first)
|
39
|
+
end
|
40
|
+
|
41
|
+
it "provides the lead broker for a partition" do
|
42
|
+
expect(@cm.lead_broker_for_partition("test",1).id).to eq(1)
|
43
|
+
expect(@cm.lead_broker_for_partition("test",2).id).to eq(2)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
RSpec.describe Poseidon::Compression::GzipCodec do
|
5
|
+
|
6
|
+
let :data do
|
7
|
+
%({"a":"val1"}\n{"a":"val2"}\n{"a":"val3"})
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should have an ID" do
|
11
|
+
expect(described_class.codec_id).to eq(1)
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should compress" do
|
15
|
+
compressed = described_class.compress(data)
|
16
|
+
expect(compressed.size).to eq(41)
|
17
|
+
expect(compressed.encoding).to eq(Encoding::BINARY)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should decompress" do
|
21
|
+
original = described_class.decompress(described_class.compress(data))
|
22
|
+
expect(original).to eq(data)
|
23
|
+
expect(original.encoding).to eq(Encoding::UTF_8)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should decompress unicode messages" do
|
27
|
+
str = "\x1F\x8B\b\x00\x00\x00\x00\x00\x00\x00c`\x80\x03\xE3I\x91\xD3|\x19\x18\xFE\x03\x01\x90\xA7Z\xAD\x94\xA8d\xA5\x14Z\x92XP\xEC\xE9\xE3\xE1\xEB\x12Y\xEE\xE8\x98\x16\xA4\xA4\xA3\x94\x04\x14~6}\xE9\xB39k\x94j\xA1Z\x19A\xDAm\f\xD9\xEF\x10\xD0\x1E\x8C\xA6\x1D\x00\x96\x98\x1E\xB9~\x00\x00\x00".force_encoding(Encoding::BINARY)
|
28
|
+
buf = Protocol::ResponseBuffer.new(described_class.decompress(str))
|
29
|
+
msg = MessageSet.read_without_size(buf).flatten
|
30
|
+
expect(msg.size).to eq(2)
|
31
|
+
expect(msg[0].value).to eq(%({"a":"UtapsILHMDYwAAfR","b":"日本"}))
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
RSpec.describe Poseidon::Compression::SnappyCodec do
|
5
|
+
|
6
|
+
let :data do
|
7
|
+
%({"a":"val1"}\n{"a":"val2"}\n{"a":"val3"})
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should have an ID" do
|
11
|
+
expect(described_class.codec_id).to eq(2)
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should compress" do
|
15
|
+
compressed = described_class.compress(data)
|
16
|
+
expect(compressed.size).to eq(34)
|
17
|
+
expect(compressed.encoding).to eq(Encoding::BINARY)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should decompress" do
|
21
|
+
original = described_class.decompress(described_class.compress(data))
|
22
|
+
expect(original).to eq(data)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should decompress streams" do
|
26
|
+
str = "\x82SNAPPY\x00\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x1E#\x00\x00\x19\x01\\\x17\x8B\xA7x\xB9\x00\x00\xFF\xFF\xFF\xFF\x00\x00\x00\tPLAINDATA".force_encoding(Encoding::BINARY)
|
27
|
+
buf = Protocol::ResponseBuffer.new(described_class.decompress(str))
|
28
|
+
msg = MessageSet.read_without_size(buf).flatten
|
29
|
+
expect(msg.size).to eq(1)
|
30
|
+
expect(msg[0].value).to eq("PLAINDATA")
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should decompress bulk streams" do
|
34
|
+
str = "\x82SNAPPY\x00\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\xCA\xE8\x04\x00\x00\x19\x01\xA0L`\x9E\xD4(\x00\x00\xFF\xFF\xFF\xFF\x00\x00\x00>{\"a\":\"UtaaKYLHMCwiAA-l\",\"bF\x17\x00Dm\",\"c\":1389795881}\rW \x01\x00\x00\x00Ln\x14\x98\xA8zX\x00\x00nVX\x00\x00o^X\x00\x00\x02\x01X\b3\xF1\e~\xB0\x00\x00pVX\x00\x00q^X\x00\x00\x03\x01X\b.\xE5\x82~X\x00\x00tVX\x00\x00u^X\x00\x00\x04\x01X\b o\xCE~\b\x01\x00vVX\x00\x00w^X\x00\x00\x05\x01X\f\t\xD8)(z`\x01\x00xVX\x00\x00y^X\x00\x00\x06\x01X\f@\bf\xA6zX\x00\x00zVX\x00\x000BX\x00".force_encoding(Encoding::BINARY)
|
35
|
+
buf = Protocol::ResponseBuffer.new(described_class.decompress(str))
|
36
|
+
msg = MessageSet.read_without_size(buf).flatten
|
37
|
+
expect(msg.size).to eq(7)
|
38
|
+
expect(msg[0].value).to eq(%({"a":"UtaaKYLHMCwiAA-l","b":"UtaaKYLHMCwiAA-m","c":1389795881}))
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should decompress unicode messages" do
|
42
|
+
str = "\x82SNAPPY\x00\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00:?\x00\x00\x19\x01\xCC3\xBA?\x91\xFA\x00\x00\xFF\xFF\xFF\xFF\x00\x00\x00%{\"a\":\"UtaitILHMDAAAAfU\",\"b\":\"\xE6\x97\xA5\xE6\x9C\xAC\"}".force_encoding(Encoding::BINARY)
|
43
|
+
buf = Protocol::ResponseBuffer.new(described_class.decompress(str))
|
44
|
+
msg = MessageSet.read_without_size(buf).flatten
|
45
|
+
expect(msg.size).to eq(1)
|
46
|
+
expect(msg[0].value).to eq(%({"a":"UtaitILHMDAAAAfU","b":"日本"}))
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe Compression do
|
4
|
+
it 'returns GzipCompessor for codec_id of 1' do
|
5
|
+
codec = Compression.find_codec(1)
|
6
|
+
expect(codec).to eq(Compression::GzipCodec)
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'returns SnappyCompessor for codec_id of 2' do
|
10
|
+
codec = Compression.find_codec(2)
|
11
|
+
expect(codec).to eq(Compression::SnappyCodec)
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'raises UnrecognizedCompressionCodec for codec_id of 3' do
|
15
|
+
expect { Compression.find_codec(3) }.to raise_error(Compression::UnrecognizedCompressionCodec)
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe FetchedMessage do
|
4
|
+
it "provides access to topic,value,key,offset" do
|
5
|
+
mts = FetchedMessage.new("hello_topic", "Hello World", "key", 0)
|
6
|
+
expect(mts.topic).to eq("hello_topic")
|
7
|
+
expect(mts.value).to eq("Hello World")
|
8
|
+
expect(mts.key).to eq("key")
|
9
|
+
expect(mts.offset).to eq(0)
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
include Protocol
|
4
|
+
RSpec.describe MessageConductor do
|
5
|
+
context "two available partitions" do
|
6
|
+
before(:each) do
|
7
|
+
partitions = [
|
8
|
+
# These are intentionally not ordered by partition_id.
|
9
|
+
# [:error, :id, :leader, :replicas, :isr]
|
10
|
+
PartitionMetadata.new(0, 1, 2, [2,1], [2,1]),
|
11
|
+
PartitionMetadata.new(0, 0, 1, [1,2], [1,2])
|
12
|
+
]
|
13
|
+
topics = [TopicMetadata.new(TopicMetadataStruct.new(0, "test", partitions))]
|
14
|
+
brokers = [Broker.new(1, "host1", 1), Broker.new(2, "host2", 2)]
|
15
|
+
|
16
|
+
@mr = MetadataResponse.new(0, brokers, topics)
|
17
|
+
|
18
|
+
@cm = ClusterMetadata.new
|
19
|
+
@cm.update(@mr)
|
20
|
+
end
|
21
|
+
|
22
|
+
context "no custom partitioner" do
|
23
|
+
before(:each) do
|
24
|
+
@mc = MessageConductor.new(@cm, nil)
|
25
|
+
end
|
26
|
+
|
27
|
+
context "for unkeyed messages" do
|
28
|
+
it "round robins which partition the message should go to" do
|
29
|
+
destinations = 4.times.map do
|
30
|
+
@mc.destination("test").first
|
31
|
+
end
|
32
|
+
|
33
|
+
first = [destinations[0], destinations[2]]
|
34
|
+
second = [destinations[1], destinations[3]]
|
35
|
+
expect([first.uniq, second.uniq].sort).to eq([[0],[1]])
|
36
|
+
end
|
37
|
+
|
38
|
+
context "unknown topic" do
|
39
|
+
it "returns -1 for broker and partition" do
|
40
|
+
expect(@mc.destination("no_exist")).to eq([-1,-1])
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context "keyed message" do
|
46
|
+
it "sends the same keys to the same destinations" do
|
47
|
+
keys = 1000.times.map { rand(500).to_s }
|
48
|
+
key_destinations = {}
|
49
|
+
|
50
|
+
keys.sort_by { rand }.each do |k|
|
51
|
+
partition,broker = @mc.destination("test", k)
|
52
|
+
|
53
|
+
key_destinations[k] ||= []
|
54
|
+
key_destinations[k].push([partition,broker])
|
55
|
+
end
|
56
|
+
|
57
|
+
expect(key_destinations.values.all? { |destinations| destinations.uniq.size == 1 }).to eq(true)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
context "custom partitioner" do
|
63
|
+
before(:each) do
|
64
|
+
partitioner = Proc.new { |key, count| key.split("_").first.to_i % count }
|
65
|
+
@mc = MessageConductor.new(@cm, partitioner)
|
66
|
+
end
|
67
|
+
|
68
|
+
it "obeys custom partitioner" do
|
69
|
+
expect(@mc.destination("test", "2_hello").first).to eq(0)
|
70
|
+
expect(@mc.destination("test", "3_hello").first).to eq(1)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
context "partitioner always sends to partition 1" do
|
75
|
+
before(:each) do
|
76
|
+
partitioner = Proc.new { 1 }
|
77
|
+
@mc = MessageConductor.new(@cm, partitioner)
|
78
|
+
end
|
79
|
+
|
80
|
+
it "sends to partition 1 on broker 2" do
|
81
|
+
expect(@mc.destination("test", "2_hello")).to eq([1,2])
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
context "broken partitioner" do
|
86
|
+
before(:each) do
|
87
|
+
partitioner = Proc.new { |key, count| count + 1 }
|
88
|
+
@mc = MessageConductor.new(@cm, partitioner)
|
89
|
+
end
|
90
|
+
|
91
|
+
it "raises InvalidPartitionError" do
|
92
|
+
expect{@mc.destination("test", "2_hello").first}.to raise_error(Errors::InvalidPartitionError)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
context "two partitions, one is unavailable" do
|
98
|
+
before(:each) do
|
99
|
+
partitions = [
|
100
|
+
Protocol::PartitionMetadata.new(0, 0, 1, [1,2], [1,2]),
|
101
|
+
Protocol::PartitionMetadata.new(0, 1, -1, [2,1], [2,1])
|
102
|
+
]
|
103
|
+
topics = [TopicMetadata.new(TopicMetadataStruct.new(0, "test", partitions))]
|
104
|
+
brokers = [Broker.new(1, "host1", 1), Broker.new(2, "host2", 2)]
|
105
|
+
|
106
|
+
@mr = MetadataResponse.new(0, brokers, topics)
|
107
|
+
|
108
|
+
@cm = ClusterMetadata.new
|
109
|
+
@cm.update(@mr)
|
110
|
+
|
111
|
+
@mc = MessageConductor.new(@cm, nil)
|
112
|
+
end
|
113
|
+
|
114
|
+
context "keyless message" do
|
115
|
+
it "is never sent to an unavailable partition" do
|
116
|
+
10.times do |destination|
|
117
|
+
expect(@mc.destination("test").first).to eq(0)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
context "keyed message" do
|
123
|
+
it "is sent to unavailable partition" do
|
124
|
+
destinations = Set.new
|
125
|
+
100.times do |key|
|
126
|
+
destinations << @mc.destination("test",key.to_s).first
|
127
|
+
end
|
128
|
+
expect(destinations).to eq(Set.new([0,1]))
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
context "no available partitions" do
|
134
|
+
before(:each) do
|
135
|
+
partitions = [
|
136
|
+
Protocol::PartitionMetadata.new(0, 0, -1, [1,2], [1,2]),
|
137
|
+
Protocol::PartitionMetadata.new(0, 1, -1, [2,1], [2,1])
|
138
|
+
]
|
139
|
+
topics = [TopicMetadata.new(TopicMetadataStruct.new(0, "test", partitions))]
|
140
|
+
brokers = [Broker.new(1, "host1", 1), Broker.new(2, "host2", 2)]
|
141
|
+
|
142
|
+
@mr = MetadataResponse.new(0, brokers, topics)
|
143
|
+
|
144
|
+
@cm = ClusterMetadata.new
|
145
|
+
@cm.update(@mr)
|
146
|
+
|
147
|
+
@mc = MessageConductor.new(@cm, nil)
|
148
|
+
end
|
149
|
+
|
150
|
+
context "keyless message" do
|
151
|
+
it "return -1 for broker and partition" do
|
152
|
+
expect(@mc.destination("test")).to eq([-1,-1])
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
context "keyed message" do
|
157
|
+
it "returns a valid partition and -1 for broker" do
|
158
|
+
partition_id, broker_id = @mc.destination("test", "key")
|
159
|
+
expect(partition_id).to_not eq(-1)
|
160
|
+
expect(broker_id).to eq(-1)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe MessageSet do
|
4
|
+
describe "converting to a compressed message" do
|
5
|
+
before(:each) do
|
6
|
+
ms = MessageSet.new([Message.new(:value => "I will be compressed", :topic => "test")])
|
7
|
+
|
8
|
+
@compressed_message_set = ms.compress(Compression::GzipCodec)
|
9
|
+
end
|
10
|
+
|
11
|
+
it "contains a compressed message" do
|
12
|
+
expect(@compressed_message_set.messages.first.compressed?).to eq(true)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "can be decompressed and reconstituted" do
|
16
|
+
expect(@compressed_message_set.flatten.first.value).to eq("I will be compressed")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "adding messages" do
|
21
|
+
it "adds the message to the struct" do
|
22
|
+
m = Message.new(:value => "sup", :topic => "topic")
|
23
|
+
ms = MessageSet.new
|
24
|
+
ms << m
|
25
|
+
expect(ms.struct.messages.first).to eq(m)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "encoding" do
|
30
|
+
it "round trips" do
|
31
|
+
m = Message.new(:value => "sup", :key => "keyz", :topic => "hello")
|
32
|
+
ms = MessageSet.new
|
33
|
+
ms << m
|
34
|
+
|
35
|
+
request_buffer = Protocol::RequestBuffer.new
|
36
|
+
ms.write(request_buffer)
|
37
|
+
|
38
|
+
response_buffer = Protocol::ResponseBuffer.new(request_buffer.to_s)
|
39
|
+
expect(MessageSet.read(response_buffer)).to eq(ms)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
RSpec.describe Message do
|
5
|
+
describe "when constructing a new message" do
|
6
|
+
it 'raises an ArgumentError on unknown options' do
|
7
|
+
expect { Message.new(:cow => "dog") }.to raise_error(ArgumentError)
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'handles options correctly' do
|
11
|
+
m = Message.new(:value => "value",
|
12
|
+
:key => "key",
|
13
|
+
:attributes => 1,
|
14
|
+
:topic => "topic")
|
15
|
+
|
16
|
+
expect(m.value).to eq("value")
|
17
|
+
expect(m.key).to eq("key")
|
18
|
+
expect(m.compressed?).to eq(true)
|
19
|
+
expect(m.topic).to eq("topic")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "checksum" do
|
24
|
+
context "is incorrect" do
|
25
|
+
before(:each) do
|
26
|
+
m = Message.new(:value => "value",
|
27
|
+
:key => "key",
|
28
|
+
:topic => "topic")
|
29
|
+
|
30
|
+
req_buf = Protocol::RequestBuffer.new
|
31
|
+
m.write(req_buf)
|
32
|
+
|
33
|
+
@s = req_buf.to_s
|
34
|
+
@s[-1] = "q" # break checksum
|
35
|
+
end
|
36
|
+
|
37
|
+
it "knows it" do
|
38
|
+
expect { Message.read(Protocol::ResponseBuffer.new(@s)) }.to raise_error(Errors::ChecksumError)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
context 'is correct' do
|
43
|
+
before(:each) do
|
44
|
+
m = Message.new(:value => "value",
|
45
|
+
:key => "key",
|
46
|
+
:topic => "topic")
|
47
|
+
|
48
|
+
req_buf = Protocol::RequestBuffer.new
|
49
|
+
m.write(req_buf)
|
50
|
+
|
51
|
+
@s = req_buf.to_s
|
52
|
+
end
|
53
|
+
|
54
|
+
it "raises no error" do
|
55
|
+
expect { Message.read(Protocol::ResponseBuffer.new(@s)) }.to_not raise_error
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe "truncated message" do
|
61
|
+
before(:each) do
|
62
|
+
m = Message.new(:value => "value",
|
63
|
+
:key => "key",
|
64
|
+
:topic => "topic")
|
65
|
+
|
66
|
+
req_buf = Protocol::RequestBuffer.new
|
67
|
+
m.write(req_buf)
|
68
|
+
|
69
|
+
@s = req_buf.to_s
|
70
|
+
end
|
71
|
+
|
72
|
+
it "reading returns nil" do
|
73
|
+
expect(Message.read(Protocol::ResponseBuffer.new(@s[0..-4]))).to eq(nil)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
context "invalid utf8 string for value" do
|
78
|
+
it "builds the payload without error" do
|
79
|
+
s = "asdf\xffasdf"
|
80
|
+
m = Message.new(:value => s,
|
81
|
+
:key => "key",
|
82
|
+
:topic => "topic")
|
83
|
+
|
84
|
+
req_buf = Protocol::RequestBuffer.new
|
85
|
+
expect {
|
86
|
+
m.write(req_buf)
|
87
|
+
}.to_not raise_error
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
context "utf8 string with multibyte characters" do
|
92
|
+
it "roundtrips correctly" do
|
93
|
+
s = "the µ is two bytes"
|
94
|
+
m = Message.new(:value => s,
|
95
|
+
:key => "key",
|
96
|
+
:topic => "topic")
|
97
|
+
|
98
|
+
req_buf = Protocol::RequestBuffer.new
|
99
|
+
m.write(req_buf)
|
100
|
+
|
101
|
+
resp_buf = Protocol::ResponseBuffer.new(req_buf.to_s)
|
102
|
+
|
103
|
+
expect(Message.read(resp_buf).value).to eq(s.force_encoding(Encoding::BINARY))
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
context "frozen string for value" do
|
108
|
+
it "builds the payload without error" do
|
109
|
+
s = "asdffasdf".freeze
|
110
|
+
m = Message.new(:value => s,
|
111
|
+
:key => "key",
|
112
|
+
:topic => "topic")
|
113
|
+
|
114
|
+
req_buf = Protocol::RequestBuffer.new
|
115
|
+
expect {
|
116
|
+
m.write(req_buf)
|
117
|
+
}.to_not raise_error
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
it "decompresses a compressed value"
|
122
|
+
|
123
|
+
it "raises an error if you try to decompress an uncompressed value"
|
124
|
+
|
125
|
+
describe "#write" do
|
126
|
+
it 'writes a MessageWithOffsetStruct to the request buffer' do
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|