fl-thrift 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (83) hide show
  1. data/CHANGELOG +4 -0
  2. data/Manifest +81 -0
  3. data/README +43 -0
  4. data/Rakefile +102 -0
  5. data/benchmark/Benchmark.thrift +24 -0
  6. data/benchmark/benchmark.rb +271 -0
  7. data/benchmark/client.rb +74 -0
  8. data/benchmark/server.rb +82 -0
  9. data/benchmark/thin_server.rb +44 -0
  10. data/ext/binary_protocol_accelerated.c +474 -0
  11. data/ext/binary_protocol_accelerated.h +20 -0
  12. data/ext/compact_protocol.c +665 -0
  13. data/ext/compact_protocol.h +20 -0
  14. data/ext/constants.h +95 -0
  15. data/ext/extconf.rb +26 -0
  16. data/ext/macros.h +41 -0
  17. data/ext/memory_buffer.c +76 -0
  18. data/ext/memory_buffer.h +20 -0
  19. data/ext/protocol.c +185 -0
  20. data/ext/protocol.h +20 -0
  21. data/ext/struct.c +606 -0
  22. data/ext/struct.h +67 -0
  23. data/ext/thrift_native.c +194 -0
  24. data/lib/thrift.rb +59 -0
  25. data/lib/thrift/client.rb +62 -0
  26. data/lib/thrift/core_ext.rb +23 -0
  27. data/lib/thrift/core_ext/fixnum.rb +29 -0
  28. data/lib/thrift/exceptions.rb +82 -0
  29. data/lib/thrift/processor.rb +57 -0
  30. data/lib/thrift/protocol/base_protocol.rb +290 -0
  31. data/lib/thrift/protocol/binary_protocol.rb +225 -0
  32. data/lib/thrift/protocol/binary_protocol_accelerated.rb +35 -0
  33. data/lib/thrift/protocol/compact_protocol.rb +422 -0
  34. data/lib/thrift/serializer/deserializer.rb +33 -0
  35. data/lib/thrift/serializer/serializer.rb +34 -0
  36. data/lib/thrift/server/base_server.rb +31 -0
  37. data/lib/thrift/server/mongrel_http_server.rb +58 -0
  38. data/lib/thrift/server/nonblocking_server.rb +296 -0
  39. data/lib/thrift/server/simple_server.rb +43 -0
  40. data/lib/thrift/server/thread_pool_server.rb +75 -0
  41. data/lib/thrift/server/threaded_server.rb +47 -0
  42. data/lib/thrift/struct.rb +298 -0
  43. data/lib/thrift/thrift_native.rb +24 -0
  44. data/lib/thrift/transport/base_server_transport.rb +37 -0
  45. data/lib/thrift/transport/base_transport.rb +70 -0
  46. data/lib/thrift/transport/buffered_transport.rb +77 -0
  47. data/lib/thrift/transport/framed_transport.rb +90 -0
  48. data/lib/thrift/transport/http_client_transport.rb +45 -0
  49. data/lib/thrift/transport/io_stream_transport.rb +39 -0
  50. data/lib/thrift/transport/memory_buffer_transport.rb +96 -0
  51. data/lib/thrift/transport/server_socket.rb +63 -0
  52. data/lib/thrift/transport/socket.rb +136 -0
  53. data/lib/thrift/transport/unix_server_socket.rb +60 -0
  54. data/lib/thrift/transport/unix_socket.rb +40 -0
  55. data/lib/thrift/types.rb +101 -0
  56. data/script/proto_benchmark.rb +121 -0
  57. data/script/read_struct.rb +43 -0
  58. data/script/write_struct.rb +30 -0
  59. data/setup.rb +1585 -0
  60. data/spec/ThriftSpec.thrift +84 -0
  61. data/spec/base_protocol_spec.rb +160 -0
  62. data/spec/base_transport_spec.rb +351 -0
  63. data/spec/binary_protocol_accelerated_spec.rb +41 -0
  64. data/spec/binary_protocol_spec.rb +63 -0
  65. data/spec/binary_protocol_spec_shared.rb +375 -0
  66. data/spec/client_spec.rb +100 -0
  67. data/spec/compact_protocol_spec.rb +117 -0
  68. data/spec/exception_spec.rb +142 -0
  69. data/spec/http_client_spec.rb +49 -0
  70. data/spec/mongrel_http_server_spec.rb +117 -0
  71. data/spec/nonblocking_server_spec.rb +265 -0
  72. data/spec/processor_spec.rb +83 -0
  73. data/spec/serializer_spec.rb +69 -0
  74. data/spec/server_socket_spec.rb +80 -0
  75. data/spec/server_spec.rb +160 -0
  76. data/spec/socket_spec.rb +61 -0
  77. data/spec/socket_spec_shared.rb +104 -0
  78. data/spec/spec_helper.rb +60 -0
  79. data/spec/struct_spec.rb +252 -0
  80. data/spec/types_spec.rb +116 -0
  81. data/spec/unix_socket_spec.rb +108 -0
  82. data/thrift.gemspec +32 -0
  83. metadata +202 -0
@@ -0,0 +1,84 @@
1
+ /*
2
+ * Licensed to the Apache Software Foundation (ASF) under one
3
+ * or more contributor license agreements. See the NOTICE file
4
+ * distributed with this work for additional information
5
+ * regarding copyright ownership. The ASF licenses this file
6
+ * to you under the Apache License, Version 2.0 (the
7
+ * "License"); you may not use this file except in compliance
8
+ * with 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,
13
+ * software distributed under the License is distributed on an
14
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+ * KIND, either express or implied. See the License for the
16
+ * specific language governing permissions and limitations
17
+ * under the License.
18
+ */
19
+
20
+ #
21
+ # Licensed to the Apache Software Foundation (ASF) under one
22
+ # or more contributor license agreements. See the NOTICE file
23
+ # distributed with this work for additional information
24
+ # regarding copyright ownership. The ASF licenses this file
25
+ # to you under the Apache License, Version 2.0 (the
26
+ # "License"); you may not use this file except in compliance
27
+ # with the License. You may obtain a copy of the License at
28
+ #
29
+ # http://www.apache.org/licenses/LICENSE-2.0
30
+ #
31
+ # Unless required by applicable law or agreed to in writing,
32
+ # software distributed under the License is distributed on an
33
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
34
+ # KIND, either express or implied. See the License for the
35
+ # specific language governing permissions and limitations
36
+ # under the License.
37
+ #
38
+
39
+ namespace rb SpecNamespace
40
+
41
+ struct Hello {
42
+ 1: string greeting = "hello world"
43
+ }
44
+
45
+ struct Foo {
46
+ 1: i32 simple = 53,
47
+ 2: string words = "words",
48
+ 3: Hello hello = {'greeting' : "hello, world!"},
49
+ 4: list<i32> ints = [1, 2, 2, 3],
50
+ 5: map<i32, map<string, double>> complex,
51
+ 6: set<i16> shorts = [5, 17, 239],
52
+ 7: optional string opt_string
53
+ }
54
+
55
+ struct BoolStruct {
56
+ 1: bool yesno = 1
57
+ }
58
+
59
+ struct SimpleList {
60
+ 1: list<bool> bools,
61
+ 2: list<byte> bytes,
62
+ 3: list<i16> i16s,
63
+ 4: list<i32> i32s,
64
+ 5: list<i64> i64s,
65
+ 6: list<double> doubles,
66
+ 7: list<string> strings,
67
+ 8: list<map<i16, i16>> maps,
68
+ 9: list<list<i16>> lists,
69
+ 10: list<set<i16>> sets,
70
+ 11: list<Hello> hellos
71
+ }
72
+
73
+ exception Xception {
74
+ 1: string message,
75
+ 2: i32 code = 1
76
+ }
77
+
78
+ service NonblockingService {
79
+ Hello greeting(1:bool english)
80
+ bool block()
81
+ oneway void unblock(1:i32 n)
82
+ oneway void shutdown()
83
+ void sleep(1:double seconds)
84
+ }
@@ -0,0 +1,160 @@
1
+ #
2
+ # Licensed to the Apache Software Foundation (ASF) under one
3
+ # or more contributor license agreements. See the NOTICE file
4
+ # distributed with this work for additional information
5
+ # regarding copyright ownership. The ASF licenses this file
6
+ # to you under the Apache License, Version 2.0 (the
7
+ # "License"); you may not use this file except in compliance
8
+ # with 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,
13
+ # software distributed under the License is distributed on an
14
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+ # KIND, either express or implied. See the License for the
16
+ # specific language governing permissions and limitations
17
+ # under the License.
18
+ #
19
+
20
+ require File.dirname(__FILE__) + '/spec_helper'
21
+
22
+ class ThriftBaseProtocolSpec < Spec::ExampleGroup
23
+ include Thrift
24
+
25
+ before(:each) do
26
+ @trans = mock("MockTransport")
27
+ @prot = BaseProtocol.new(@trans)
28
+ end
29
+
30
+ describe BaseProtocol do
31
+ # most of the methods are stubs, so we can ignore them
32
+
33
+ it "should make trans accessible" do
34
+ @prot.trans.should eql(@trans)
35
+ end
36
+
37
+ it "should write out a field nicely" do
38
+ @prot.should_receive(:write_field_begin).with('field', 'type', 'fid').ordered
39
+ @prot.should_receive(:write_type).with('type', 'value').ordered
40
+ @prot.should_receive(:write_field_end).ordered
41
+ @prot.write_field('field', 'type', 'fid', 'value')
42
+ end
43
+
44
+ it "should write out the different types" do
45
+ @prot.should_receive(:write_bool).with('bool').ordered
46
+ @prot.should_receive(:write_byte).with('byte').ordered
47
+ @prot.should_receive(:write_double).with('double').ordered
48
+ @prot.should_receive(:write_i16).with('i16').ordered
49
+ @prot.should_receive(:write_i32).with('i32').ordered
50
+ @prot.should_receive(:write_i64).with('i64').ordered
51
+ @prot.should_receive(:write_string).with('string').ordered
52
+ struct = mock('Struct')
53
+ struct.should_receive(:write).with(@prot).ordered
54
+ @prot.write_type(Types::BOOL, 'bool')
55
+ @prot.write_type(Types::BYTE, 'byte')
56
+ @prot.write_type(Types::DOUBLE, 'double')
57
+ @prot.write_type(Types::I16, 'i16')
58
+ @prot.write_type(Types::I32, 'i32')
59
+ @prot.write_type(Types::I64, 'i64')
60
+ @prot.write_type(Types::STRING, 'string')
61
+ @prot.write_type(Types::STRUCT, struct)
62
+ # all other types are not implemented
63
+ [Types::STOP, Types::VOID, Types::MAP, Types::SET, Types::LIST].each do |type|
64
+ lambda { @prot.write_type(type, type.to_s) }.should raise_error(NotImplementedError)
65
+ end
66
+ end
67
+
68
+ it "should read the different types" do
69
+ @prot.should_receive(:read_bool).ordered
70
+ @prot.should_receive(:read_byte).ordered
71
+ @prot.should_receive(:read_i16).ordered
72
+ @prot.should_receive(:read_i32).ordered
73
+ @prot.should_receive(:read_i64).ordered
74
+ @prot.should_receive(:read_double).ordered
75
+ @prot.should_receive(:read_string).ordered
76
+ @prot.read_type(Types::BOOL)
77
+ @prot.read_type(Types::BYTE)
78
+ @prot.read_type(Types::I16)
79
+ @prot.read_type(Types::I32)
80
+ @prot.read_type(Types::I64)
81
+ @prot.read_type(Types::DOUBLE)
82
+ @prot.read_type(Types::STRING)
83
+ # all other types are not implemented
84
+ [Types::STOP, Types::VOID, Types::MAP, Types::SET, Types::LIST].each do |type|
85
+ lambda { @prot.read_type(type) }.should raise_error(NotImplementedError)
86
+ end
87
+ end
88
+
89
+ it "should skip the basic types" do
90
+ @prot.should_receive(:read_bool).ordered
91
+ @prot.should_receive(:read_byte).ordered
92
+ @prot.should_receive(:read_i16).ordered
93
+ @prot.should_receive(:read_i32).ordered
94
+ @prot.should_receive(:read_i64).ordered
95
+ @prot.should_receive(:read_double).ordered
96
+ @prot.should_receive(:read_string).ordered
97
+ @prot.skip(Types::BOOL)
98
+ @prot.skip(Types::BYTE)
99
+ @prot.skip(Types::I16)
100
+ @prot.skip(Types::I32)
101
+ @prot.skip(Types::I64)
102
+ @prot.skip(Types::DOUBLE)
103
+ @prot.skip(Types::STRING)
104
+ @prot.skip(Types::STOP) # should do absolutely nothing
105
+ end
106
+
107
+ it "should skip structs" do
108
+ real_skip = @prot.method(:skip)
109
+ @prot.should_receive(:read_struct_begin).ordered
110
+ @prot.should_receive(:read_field_begin).exactly(4).times.and_return(
111
+ ['field 1', Types::STRING, 1],
112
+ ['field 2', Types::I32, 2],
113
+ ['field 3', Types::MAP, 3],
114
+ [nil, Types::STOP, 0]
115
+ )
116
+ @prot.should_receive(:read_field_end).exactly(3).times
117
+ @prot.should_receive(:read_string).exactly(3).times
118
+ @prot.should_receive(:read_i32).ordered
119
+ @prot.should_receive(:read_map_begin).ordered.and_return([Types::STRING, Types::STRING, 1])
120
+ # @prot.should_receive(:read_string).exactly(2).times
121
+ @prot.should_receive(:read_map_end).ordered
122
+ @prot.should_receive(:read_struct_end).ordered
123
+ real_skip.call(Types::STRUCT)
124
+ end
125
+
126
+ it "should skip maps" do
127
+ real_skip = @prot.method(:skip)
128
+ @prot.should_receive(:read_map_begin).ordered.and_return([Types::STRING, Types::STRUCT, 1])
129
+ @prot.should_receive(:read_string).ordered
130
+ @prot.should_receive(:read_struct_begin).ordered.and_return(["some_struct"])
131
+ @prot.should_receive(:read_field_begin).ordered.and_return([nil, Types::STOP, nil]);
132
+ @prot.should_receive(:read_struct_end).ordered
133
+ @prot.should_receive(:read_map_end).ordered
134
+ real_skip.call(Types::MAP)
135
+ end
136
+
137
+ it "should skip sets" do
138
+ real_skip = @prot.method(:skip)
139
+ @prot.should_receive(:read_set_begin).ordered.and_return([Types::I64, 9])
140
+ @prot.should_receive(:read_i64).ordered.exactly(9).times
141
+ @prot.should_receive(:read_set_end)
142
+ real_skip.call(Types::SET)
143
+ end
144
+
145
+ it "should skip lists" do
146
+ real_skip = @prot.method(:skip)
147
+ @prot.should_receive(:read_list_begin).ordered.and_return([Types::DOUBLE, 11])
148
+ @prot.should_receive(:read_double).ordered.exactly(11).times
149
+ @prot.should_receive(:read_list_end)
150
+ real_skip.call(Types::LIST)
151
+ end
152
+ end
153
+
154
+ describe BaseProtocolFactory do
155
+ it "should raise NotImplementedError" do
156
+ # returning nil since Protocol is just an abstract class
157
+ lambda {BaseProtocolFactory.new.get_protocol(mock("MockTransport"))}.should raise_error(NotImplementedError)
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,351 @@
1
+ #
2
+ # Licensed to the Apache Software Foundation (ASF) under one
3
+ # or more contributor license agreements. See the NOTICE file
4
+ # distributed with this work for additional information
5
+ # regarding copyright ownership. The ASF licenses this file
6
+ # to you under the Apache License, Version 2.0 (the
7
+ # "License"); you may not use this file except in compliance
8
+ # with 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,
13
+ # software distributed under the License is distributed on an
14
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+ # KIND, either express or implied. See the License for the
16
+ # specific language governing permissions and limitations
17
+ # under the License.
18
+ #
19
+
20
+ require File.dirname(__FILE__) + '/spec_helper'
21
+
22
+ class ThriftBaseTransportSpec < Spec::ExampleGroup
23
+ include Thrift
24
+
25
+ describe TransportException do
26
+ it "should make type accessible" do
27
+ exc = TransportException.new(TransportException::ALREADY_OPEN, "msg")
28
+ exc.type.should == TransportException::ALREADY_OPEN
29
+ exc.message.should == "msg"
30
+ end
31
+ end
32
+
33
+ describe BaseTransport do
34
+ it "should read the specified size" do
35
+ transport = BaseTransport.new
36
+ transport.should_receive(:read).with(40).ordered.and_return("10 letters")
37
+ transport.should_receive(:read).with(30).ordered.and_return("fifteen letters")
38
+ transport.should_receive(:read).with(15).ordered.and_return("more characters")
39
+ transport.read_all(40).should == "10 lettersfifteen lettersmore characters"
40
+ end
41
+
42
+ it "should stub out the rest of the methods" do
43
+ # can't test for stubbiness, so just make sure they're defined
44
+ [:open?, :open, :close, :read, :write, :flush].each do |sym|
45
+ BaseTransport.method_defined?(sym).should be_true
46
+ end
47
+ end
48
+
49
+ it "should alias << to write" do
50
+ BaseTransport.instance_method(:<<).should == BaseTransport.instance_method(:write)
51
+ end
52
+ end
53
+
54
+ describe BaseServerTransport do
55
+ it "should stub out its methods" do
56
+ [:listen, :accept, :close].each do |sym|
57
+ BaseServerTransport.method_defined?(sym).should be_true
58
+ end
59
+ end
60
+ end
61
+
62
+ describe BaseTransportFactory do
63
+ it "should return the transport it's given" do
64
+ transport = mock("Transport")
65
+ BaseTransportFactory.new.get_transport(transport).should eql(transport)
66
+ end
67
+ end
68
+
69
+ describe BufferedTransport do
70
+ it "should pass through everything but write/flush/read" do
71
+ trans = mock("Transport")
72
+ trans.should_receive(:open?).ordered.and_return("+ open?")
73
+ trans.should_receive(:open).ordered.and_return("+ open")
74
+ trans.should_receive(:flush).ordered # from the close
75
+ trans.should_receive(:close).ordered.and_return("+ close")
76
+ btrans = BufferedTransport.new(trans)
77
+ btrans.open?.should == "+ open?"
78
+ btrans.open.should == "+ open"
79
+ btrans.close.should == "+ close"
80
+ end
81
+
82
+ it "should buffer reads in chunks of #{BufferedTransport::DEFAULT_BUFFER}" do
83
+ trans = mock("Transport")
84
+ trans.should_receive(:read).with(BufferedTransport::DEFAULT_BUFFER).and_return("lorum ipsum dolor emet")
85
+ btrans = BufferedTransport.new(trans)
86
+ btrans.read(6).should == "lorum "
87
+ btrans.read(6).should == "ipsum "
88
+ btrans.read(6).should == "dolor "
89
+ btrans.read(6).should == "emet"
90
+ end
91
+
92
+ it "should buffer writes and send them on flush" do
93
+ trans = mock("Transport")
94
+ btrans = BufferedTransport.new(trans)
95
+ btrans.write("one/")
96
+ btrans.write("two/")
97
+ btrans.write("three/")
98
+ trans.should_receive(:write).with("one/two/three/").ordered
99
+ trans.should_receive(:flush).ordered
100
+ btrans.flush
101
+ end
102
+
103
+ it "should only send buffered data once" do
104
+ trans = mock("Transport")
105
+ btrans = BufferedTransport.new(trans)
106
+ btrans.write("one/")
107
+ btrans.write("two/")
108
+ btrans.write("three/")
109
+ trans.should_receive(:write).with("one/two/three/")
110
+ trans.stub!(:flush)
111
+ btrans.flush
112
+ # Nothing to flush with no data
113
+ btrans.flush
114
+ end
115
+
116
+ it "should flush on close" do
117
+ trans = mock("Transport")
118
+ trans.should_receive(:close)
119
+ btrans = BufferedTransport.new(trans)
120
+ btrans.should_receive(:flush)
121
+ btrans.close
122
+ end
123
+
124
+ it "should not write to socket if there's no data" do
125
+ trans = mock("Transport")
126
+ trans.should_receive(:flush)
127
+ btrans = BufferedTransport.new(trans)
128
+ btrans.flush
129
+ end
130
+ end
131
+
132
+ describe BufferedTransportFactory do
133
+ it "should wrap the given transport in a BufferedTransport" do
134
+ trans = mock("Transport")
135
+ btrans = mock("BufferedTransport")
136
+ BufferedTransport.should_receive(:new).with(trans).and_return(btrans)
137
+ BufferedTransportFactory.new.get_transport(trans).should == btrans
138
+ end
139
+ end
140
+
141
+ describe FramedTransport do
142
+ before(:each) do
143
+ @trans = mock("Transport")
144
+ end
145
+
146
+ it "should pass through open?/open/close" do
147
+ ftrans = FramedTransport.new(@trans)
148
+ @trans.should_receive(:open?).ordered.and_return("+ open?")
149
+ @trans.should_receive(:open).ordered.and_return("+ open")
150
+ @trans.should_receive(:close).ordered.and_return("+ close")
151
+ ftrans.open?.should == "+ open?"
152
+ ftrans.open.should == "+ open"
153
+ ftrans.close.should == "+ close"
154
+ end
155
+
156
+ it "should pass through read when read is turned off" do
157
+ ftrans = FramedTransport.new(@trans, false, true)
158
+ @trans.should_receive(:read).with(17).ordered.and_return("+ read")
159
+ ftrans.read(17).should == "+ read"
160
+ end
161
+
162
+ it "should pass through write/flush when write is turned off" do
163
+ ftrans = FramedTransport.new(@trans, true, false)
164
+ @trans.should_receive(:write).with("foo").ordered.and_return("+ write")
165
+ @trans.should_receive(:flush).ordered.and_return("+ flush")
166
+ ftrans.write("foo").should == "+ write"
167
+ ftrans.flush.should == "+ flush"
168
+ end
169
+
170
+ it "should return a full frame if asked for >= the frame's length" do
171
+ frame = "this is a frame"
172
+ @trans.should_receive(:read_all).with(4).and_return("\000\000\000\017")
173
+ @trans.should_receive(:read_all).with(frame.length).and_return(frame)
174
+ FramedTransport.new(@trans).read(frame.length + 10).should == frame
175
+ end
176
+
177
+ it "should return slices of the frame when asked for < the frame's length" do
178
+ frame = "this is a frame"
179
+ @trans.should_receive(:read_all).with(4).and_return("\000\000\000\017")
180
+ @trans.should_receive(:read_all).with(frame.length).and_return(frame)
181
+ ftrans = FramedTransport.new(@trans)
182
+ ftrans.read(4).should == "this"
183
+ ftrans.read(4).should == " is "
184
+ ftrans.read(16).should == "a frame"
185
+ end
186
+
187
+ it "should return nothing if asked for <= 0" do
188
+ FramedTransport.new(@trans).read(-2).should == ""
189
+ end
190
+
191
+ it "should pull a new frame when the first is exhausted" do
192
+ frame = "this is a frame"
193
+ frame2 = "yet another frame"
194
+ @trans.should_receive(:read_all).with(4).and_return("\000\000\000\017", "\000\000\000\021")
195
+ @trans.should_receive(:read_all).with(frame.length).and_return(frame)
196
+ @trans.should_receive(:read_all).with(frame2.length).and_return(frame2)
197
+ ftrans = FramedTransport.new(@trans)
198
+ ftrans.read(4).should == "this"
199
+ ftrans.read(8).should == " is a fr"
200
+ ftrans.read(6).should == "ame"
201
+ ftrans.read(4).should == "yet "
202
+ ftrans.read(16).should == "another frame"
203
+ end
204
+
205
+ it "should buffer writes" do
206
+ ftrans = FramedTransport.new(@trans)
207
+ @trans.should_not_receive(:write)
208
+ ftrans.write("foo")
209
+ ftrans.write("bar")
210
+ ftrans.write("this is a frame")
211
+ end
212
+
213
+ it "should write slices of the buffer" do
214
+ ftrans = FramedTransport.new(@trans)
215
+ ftrans.write("foobar", 3)
216
+ ftrans.write("barfoo", 1)
217
+ @trans.stub!(:flush)
218
+ @trans.should_receive(:write).with("\000\000\000\004foob")
219
+ ftrans.flush
220
+ end
221
+
222
+ it "should flush frames with a 4-byte header" do
223
+ ftrans = FramedTransport.new(@trans)
224
+ @trans.should_receive(:write).with("\000\000\000\035one/two/three/this is a frame").ordered
225
+ @trans.should_receive(:flush).ordered
226
+ ftrans.write("one/")
227
+ ftrans.write("two/")
228
+ ftrans.write("three/")
229
+ ftrans.write("this is a frame")
230
+ ftrans.flush
231
+ end
232
+
233
+ it "should not flush the same buffered data twice" do
234
+ ftrans = FramedTransport.new(@trans)
235
+ @trans.should_receive(:write).with("\000\000\000\007foo/bar")
236
+ @trans.stub!(:flush)
237
+ ftrans.write("foo")
238
+ ftrans.write("/bar")
239
+ ftrans.flush
240
+ @trans.should_receive(:write).with("\000\000\000\000")
241
+ ftrans.flush
242
+ end
243
+ end
244
+
245
+ describe FramedTransportFactory do
246
+ it "should wrap the given transport in a FramedTransport" do
247
+ trans = mock("Transport")
248
+ FramedTransport.should_receive(:new).with(trans)
249
+ FramedTransportFactory.new.get_transport(trans)
250
+ end
251
+ end
252
+
253
+ describe MemoryBufferTransport do
254
+ before(:each) do
255
+ @buffer = MemoryBufferTransport.new
256
+ end
257
+
258
+ it "should accept a buffer on input and use it directly" do
259
+ s = "this is a test"
260
+ @buffer = MemoryBufferTransport.new(s)
261
+ @buffer.read(4).should == "this"
262
+ s.slice!(-4..-1)
263
+ @buffer.read(@buffer.available).should == " is a "
264
+ end
265
+
266
+ it "should always remain open" do
267
+ @buffer.should be_open
268
+ @buffer.close
269
+ @buffer.should be_open
270
+ end
271
+
272
+ it "should respond to peek and available" do
273
+ @buffer.write "some data"
274
+ @buffer.peek.should be_true
275
+ @buffer.available.should == 9
276
+ @buffer.read(4)
277
+ @buffer.peek.should be_true
278
+ @buffer.available.should == 5
279
+ @buffer.read(5)
280
+ @buffer.peek.should be_false
281
+ @buffer.available.should == 0
282
+ end
283
+
284
+ it "should be able to reset the buffer" do
285
+ @buffer.write "test data"
286
+ @buffer.reset_buffer("foobar")
287
+ @buffer.available.should == 6
288
+ @buffer.read(@buffer.available).should == "foobar"
289
+ @buffer.reset_buffer
290
+ @buffer.available.should == 0
291
+ end
292
+
293
+ it "should copy the given string when resetting the buffer" do
294
+ s = "this is a test"
295
+ @buffer.reset_buffer(s)
296
+ @buffer.available.should == 14
297
+ @buffer.read(10)
298
+ @buffer.available.should == 4
299
+ s.should == "this is a test"
300
+ end
301
+
302
+ it "should return from read what was given in write" do
303
+ @buffer.write "test data"
304
+ @buffer.read(4).should == "test"
305
+ @buffer.read(@buffer.available).should == " data"
306
+ @buffer.write "foo"
307
+ @buffer.write " bar"
308
+ @buffer.read(@buffer.available).should == "foo bar"
309
+ end
310
+
311
+ it "should throw an EOFError when there isn't enough data in the buffer" do
312
+ @buffer.reset_buffer("")
313
+ lambda{@buffer.read(1)}.should raise_error(EOFError)
314
+
315
+ @buffer.reset_buffer("1234")
316
+ lambda{@buffer.read(5)}.should raise_error(EOFError)
317
+ end
318
+ end
319
+
320
+ describe IOStreamTransport do
321
+ before(:each) do
322
+ @input = mock("Input", :closed? => false)
323
+ @output = mock("Output", :closed? => false)
324
+ @trans = IOStreamTransport.new(@input, @output)
325
+ end
326
+
327
+ it "should be open as long as both input or output are open" do
328
+ @trans.should be_open
329
+ @input.stub!(:closed?).and_return(true)
330
+ @trans.should be_open
331
+ @input.stub!(:closed?).and_return(false)
332
+ @output.stub!(:closed?).and_return(true)
333
+ @trans.should be_open
334
+ @input.stub!(:closed?).and_return(true)
335
+ @trans.should_not be_open
336
+ end
337
+
338
+ it "should pass through read/write to input/output" do
339
+ @input.should_receive(:read).with(17).and_return("+ read")
340
+ @output.should_receive(:write).with("foobar").and_return("+ write")
341
+ @trans.read(17).should == "+ read"
342
+ @trans.write("foobar").should == "+ write"
343
+ end
344
+
345
+ it "should close both input and output when closed" do
346
+ @input.should_receive(:close)
347
+ @output.should_receive(:close)
348
+ @trans.close
349
+ end
350
+ end
351
+ end