codeclimate-poseidon 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +21 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +14 -0
  5. data/.yardopts +8 -0
  6. data/CHANGES.md +31 -0
  7. data/Gemfile +13 -0
  8. data/LICENSE.txt +22 -0
  9. data/README.md +72 -0
  10. data/Rakefile +20 -0
  11. data/TODO.md +27 -0
  12. data/examples/consumer.rb +18 -0
  13. data/examples/producer.rb +9 -0
  14. data/lib/poseidon.rb +120 -0
  15. data/lib/poseidon/broker_pool.rb +86 -0
  16. data/lib/poseidon/cluster_metadata.rb +94 -0
  17. data/lib/poseidon/compressed_value.rb +23 -0
  18. data/lib/poseidon/compression.rb +30 -0
  19. data/lib/poseidon/compression/gzip_codec.rb +23 -0
  20. data/lib/poseidon/compression/snappy_codec.rb +29 -0
  21. data/lib/poseidon/connection.rb +169 -0
  22. data/lib/poseidon/fetched_message.rb +37 -0
  23. data/lib/poseidon/message.rb +151 -0
  24. data/lib/poseidon/message_conductor.rb +86 -0
  25. data/lib/poseidon/message_set.rb +80 -0
  26. data/lib/poseidon/message_to_send.rb +33 -0
  27. data/lib/poseidon/messages_for_broker.rb +56 -0
  28. data/lib/poseidon/messages_to_send.rb +47 -0
  29. data/lib/poseidon/messages_to_send_batch.rb +27 -0
  30. data/lib/poseidon/partition_consumer.rb +225 -0
  31. data/lib/poseidon/producer.rb +199 -0
  32. data/lib/poseidon/producer_compression_config.rb +37 -0
  33. data/lib/poseidon/protocol.rb +122 -0
  34. data/lib/poseidon/protocol/protocol_struct.rb +256 -0
  35. data/lib/poseidon/protocol/request_buffer.rb +77 -0
  36. data/lib/poseidon/protocol/response_buffer.rb +72 -0
  37. data/lib/poseidon/sync_producer.rb +161 -0
  38. data/lib/poseidon/topic_metadata.rb +89 -0
  39. data/lib/poseidon/version.rb +4 -0
  40. data/log/.gitkeep +0 -0
  41. data/poseidon.gemspec +27 -0
  42. data/spec/integration/multiple_brokers/consumer_spec.rb +45 -0
  43. data/spec/integration/multiple_brokers/metadata_failures_spec.rb +144 -0
  44. data/spec/integration/multiple_brokers/rebalance_spec.rb +69 -0
  45. data/spec/integration/multiple_brokers/round_robin_spec.rb +41 -0
  46. data/spec/integration/multiple_brokers/spec_helper.rb +60 -0
  47. data/spec/integration/simple/compression_spec.rb +23 -0
  48. data/spec/integration/simple/connection_spec.rb +35 -0
  49. data/spec/integration/simple/multiple_brokers_spec.rb +10 -0
  50. data/spec/integration/simple/simple_producer_and_consumer_spec.rb +121 -0
  51. data/spec/integration/simple/spec_helper.rb +16 -0
  52. data/spec/integration/simple/truncated_messages_spec.rb +46 -0
  53. data/spec/integration/simple/unavailable_broker_spec.rb +72 -0
  54. data/spec/spec_helper.rb +32 -0
  55. data/spec/test_cluster.rb +211 -0
  56. data/spec/unit/broker_pool_spec.rb +98 -0
  57. data/spec/unit/cluster_metadata_spec.rb +46 -0
  58. data/spec/unit/compression/gzip_codec_spec.rb +34 -0
  59. data/spec/unit/compression/snappy_codec_spec.rb +49 -0
  60. data/spec/unit/compression_spec.rb +17 -0
  61. data/spec/unit/connection_spec.rb +4 -0
  62. data/spec/unit/fetched_message_spec.rb +11 -0
  63. data/spec/unit/message_conductor_spec.rb +164 -0
  64. data/spec/unit/message_set_spec.rb +42 -0
  65. data/spec/unit/message_spec.rb +129 -0
  66. data/spec/unit/message_to_send_spec.rb +10 -0
  67. data/spec/unit/messages_for_broker_spec.rb +54 -0
  68. data/spec/unit/messages_to_send_batch_spec.rb +25 -0
  69. data/spec/unit/messages_to_send_spec.rb +63 -0
  70. data/spec/unit/partition_consumer_spec.rb +142 -0
  71. data/spec/unit/producer_compression_config_spec.rb +42 -0
  72. data/spec/unit/producer_spec.rb +51 -0
  73. data/spec/unit/protocol/request_buffer_spec.rb +16 -0
  74. data/spec/unit/protocol_spec.rb +54 -0
  75. data/spec/unit/sync_producer_spec.rb +156 -0
  76. data/spec/unit/topic_metadata_spec.rb +43 -0
  77. 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,4 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Connection do
4
+ 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