codeclimate-poseidon 0.0.8
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
- 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
|