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.
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