sessionm-thrift 0.8.0.1
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.
- data/CHANGELOG +1 -0
- data/README +43 -0
- data/benchmark/Benchmark.thrift +24 -0
- data/benchmark/benchmark.rb +271 -0
- data/benchmark/client.rb +74 -0
- data/benchmark/server.rb +82 -0
- data/benchmark/thin_server.rb +44 -0
- data/ext/binary_protocol_accelerated.c +441 -0
- data/ext/binary_protocol_accelerated.h +20 -0
- data/ext/compact_protocol.c +618 -0
- data/ext/compact_protocol.h +20 -0
- data/ext/constants.h +96 -0
- data/ext/extconf.rb +30 -0
- data/ext/macros.h +41 -0
- data/ext/memory_buffer.c +131 -0
- data/ext/memory_buffer.h +20 -0
- data/ext/protocol.c +185 -0
- data/ext/protocol.h +20 -0
- data/ext/strlcpy.c +41 -0
- data/ext/strlcpy.h +30 -0
- data/ext/struct.c +691 -0
- data/ext/struct.h +25 -0
- data/ext/thrift_native.c +196 -0
- data/lib/thrift.rb +64 -0
- data/lib/thrift/client.rb +62 -0
- data/lib/thrift/core_ext.rb +23 -0
- data/lib/thrift/core_ext/fixnum.rb +29 -0
- data/lib/thrift/exceptions.rb +84 -0
- data/lib/thrift/processor.rb +57 -0
- data/lib/thrift/protocol/base_protocol.rb +290 -0
- data/lib/thrift/protocol/binary_protocol.rb +229 -0
- data/lib/thrift/protocol/binary_protocol_accelerated.rb +39 -0
- data/lib/thrift/protocol/compact_protocol.rb +426 -0
- data/lib/thrift/serializer/deserializer.rb +33 -0
- data/lib/thrift/serializer/serializer.rb +34 -0
- data/lib/thrift/server/base_server.rb +31 -0
- data/lib/thrift/server/mongrel_http_server.rb +58 -0
- data/lib/thrift/server/nonblocking_server.rb +305 -0
- data/lib/thrift/server/simple_server.rb +43 -0
- data/lib/thrift/server/thread_pool_server.rb +75 -0
- data/lib/thrift/server/threaded_server.rb +47 -0
- data/lib/thrift/struct.rb +237 -0
- data/lib/thrift/struct_union.rb +192 -0
- data/lib/thrift/thrift_native.rb +24 -0
- data/lib/thrift/transport/base_server_transport.rb +37 -0
- data/lib/thrift/transport/base_transport.rb +107 -0
- data/lib/thrift/transport/buffered_transport.rb +108 -0
- data/lib/thrift/transport/framed_transport.rb +116 -0
- data/lib/thrift/transport/http_client_transport.rb +51 -0
- data/lib/thrift/transport/io_stream_transport.rb +39 -0
- data/lib/thrift/transport/memory_buffer_transport.rb +125 -0
- data/lib/thrift/transport/server_socket.rb +63 -0
- data/lib/thrift/transport/socket.rb +137 -0
- data/lib/thrift/transport/unix_server_socket.rb +60 -0
- data/lib/thrift/transport/unix_socket.rb +40 -0
- data/lib/thrift/types.rb +101 -0
- data/lib/thrift/union.rb +179 -0
- data/spec/ThriftSpec.thrift +132 -0
- data/spec/base_protocol_spec.rb +160 -0
- data/spec/base_transport_spec.rb +351 -0
- data/spec/binary_protocol_accelerated_spec.rb +46 -0
- data/spec/binary_protocol_spec.rb +61 -0
- data/spec/binary_protocol_spec_shared.rb +375 -0
- data/spec/client_spec.rb +100 -0
- data/spec/compact_protocol_spec.rb +144 -0
- data/spec/exception_spec.rb +142 -0
- data/spec/http_client_spec.rb +64 -0
- data/spec/mongrel_http_server_spec.rb +117 -0
- data/spec/nonblocking_server_spec.rb +265 -0
- data/spec/processor_spec.rb +83 -0
- data/spec/serializer_spec.rb +69 -0
- data/spec/server_socket_spec.rb +80 -0
- data/spec/server_spec.rb +159 -0
- data/spec/socket_spec.rb +61 -0
- data/spec/socket_spec_shared.rb +104 -0
- data/spec/spec_helper.rb +58 -0
- data/spec/struct_spec.rb +295 -0
- data/spec/types_spec.rb +116 -0
- data/spec/union_spec.rb +193 -0
- data/spec/unix_socket_spec.rb +108 -0
- metadata +247 -0
data/spec/server_spec.rb
ADDED
@@ -0,0 +1,159 @@
|
|
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
|
+
require File.expand_path("#{File.dirname(__FILE__)}/spec_helper")
|
20
|
+
|
21
|
+
class ThriftServerSpec < Spec::ExampleGroup
|
22
|
+
include Thrift
|
23
|
+
|
24
|
+
describe BaseServer do
|
25
|
+
it "should default to BaseTransportFactory and BinaryProtocolFactory when not specified" do
|
26
|
+
server = BaseServer.new(mock("Processor"), mock("BaseServerTransport"))
|
27
|
+
server.instance_variable_get(:'@transport_factory').should be_an_instance_of(BaseTransportFactory)
|
28
|
+
server.instance_variable_get(:'@protocol_factory').should be_an_instance_of(BinaryProtocolFactory)
|
29
|
+
end
|
30
|
+
|
31
|
+
# serve is a noop, so can't test that
|
32
|
+
end
|
33
|
+
|
34
|
+
shared_examples_for "servers" do
|
35
|
+
before(:each) do
|
36
|
+
@processor = mock("Processor")
|
37
|
+
@serverTrans = mock("ServerTransport")
|
38
|
+
@trans = mock("BaseTransport")
|
39
|
+
@prot = mock("BaseProtocol")
|
40
|
+
@client = mock("Client")
|
41
|
+
@server = server_type.new(@processor, @serverTrans, @trans, @prot)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe SimpleServer do
|
46
|
+
it_should_behave_like "servers"
|
47
|
+
|
48
|
+
def server_type
|
49
|
+
SimpleServer
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should serve in the main thread" do
|
53
|
+
@serverTrans.should_receive(:listen).ordered
|
54
|
+
@serverTrans.should_receive(:accept).exactly(3).times.and_return(@client)
|
55
|
+
@trans.should_receive(:get_transport).exactly(3).times.with(@client).and_return(@trans)
|
56
|
+
@prot.should_receive(:get_protocol).exactly(3).times.with(@trans).and_return(@prot)
|
57
|
+
x = 0
|
58
|
+
@processor.should_receive(:process).exactly(3).times.with(@prot, @prot).and_return do
|
59
|
+
case (x += 1)
|
60
|
+
when 1 then raise Thrift::TransportException
|
61
|
+
when 2 then raise Thrift::ProtocolException
|
62
|
+
when 3 then throw :stop
|
63
|
+
end
|
64
|
+
end
|
65
|
+
@trans.should_receive(:close).exactly(3).times
|
66
|
+
@serverTrans.should_receive(:close).ordered
|
67
|
+
lambda { @server.serve }.should throw_symbol(:stop)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe ThreadedServer do
|
72
|
+
it_should_behave_like "servers"
|
73
|
+
|
74
|
+
def server_type
|
75
|
+
ThreadedServer
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should serve using threads" do
|
79
|
+
@serverTrans.should_receive(:listen).ordered
|
80
|
+
@serverTrans.should_receive(:accept).exactly(3).times.and_return(@client)
|
81
|
+
@trans.should_receive(:get_transport).exactly(3).times.with(@client).and_return(@trans)
|
82
|
+
@prot.should_receive(:get_protocol).exactly(3).times.with(@trans).and_return(@prot)
|
83
|
+
Thread.should_receive(:new).with(@prot, @trans).exactly(3).times.and_yield(@prot, @trans)
|
84
|
+
x = 0
|
85
|
+
@processor.should_receive(:process).exactly(3).times.with(@prot, @prot).and_return do
|
86
|
+
case (x += 1)
|
87
|
+
when 1 then raise Thrift::TransportException
|
88
|
+
when 2 then raise Thrift::ProtocolException
|
89
|
+
when 3 then throw :stop
|
90
|
+
end
|
91
|
+
end
|
92
|
+
@trans.should_receive(:close).exactly(3).times
|
93
|
+
@serverTrans.should_receive(:close).ordered
|
94
|
+
lambda { @server.serve }.should throw_symbol(:stop)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
describe ThreadPoolServer do
|
99
|
+
it_should_behave_like "servers"
|
100
|
+
|
101
|
+
def server_type
|
102
|
+
# put this stuff here so it runs before the server is created
|
103
|
+
@threadQ = mock("SizedQueue")
|
104
|
+
SizedQueue.should_receive(:new).with(20).and_return(@threadQ)
|
105
|
+
@excQ = mock("Queue")
|
106
|
+
Queue.should_receive(:new).and_return(@excQ)
|
107
|
+
ThreadPoolServer
|
108
|
+
end
|
109
|
+
|
110
|
+
it "should set up the queues" do
|
111
|
+
@server.instance_variable_get(:'@thread_q').should be(@threadQ)
|
112
|
+
@server.instance_variable_get(:'@exception_q').should be(@excQ)
|
113
|
+
end
|
114
|
+
|
115
|
+
it "should serve inside a thread" do
|
116
|
+
Thread.should_receive(:new).and_return do |block|
|
117
|
+
@server.should_receive(:serve)
|
118
|
+
block.call
|
119
|
+
@server.rspec_verify
|
120
|
+
end
|
121
|
+
@excQ.should_receive(:pop).and_throw(:popped)
|
122
|
+
lambda { @server.rescuable_serve }.should throw_symbol(:popped)
|
123
|
+
end
|
124
|
+
|
125
|
+
it "should avoid running the server twice when retrying rescuable_serve" do
|
126
|
+
Thread.should_receive(:new).and_return do |block|
|
127
|
+
@server.should_receive(:serve)
|
128
|
+
block.call
|
129
|
+
@server.rspec_verify
|
130
|
+
end
|
131
|
+
@excQ.should_receive(:pop).twice.and_throw(:popped)
|
132
|
+
lambda { @server.rescuable_serve }.should throw_symbol(:popped)
|
133
|
+
lambda { @server.rescuable_serve }.should throw_symbol(:popped)
|
134
|
+
end
|
135
|
+
|
136
|
+
it "should serve using a thread pool" do
|
137
|
+
@serverTrans.should_receive(:listen).ordered
|
138
|
+
@threadQ.should_receive(:push).with(:token)
|
139
|
+
@threadQ.should_receive(:pop)
|
140
|
+
Thread.should_receive(:new).and_yield
|
141
|
+
@serverTrans.should_receive(:accept).exactly(3).times.and_return(@client)
|
142
|
+
@trans.should_receive(:get_transport).exactly(3).times.and_return(@trans)
|
143
|
+
@prot.should_receive(:get_protocol).exactly(3).times.and_return(@prot)
|
144
|
+
x = 0
|
145
|
+
error = RuntimeError.new("Stopped")
|
146
|
+
@processor.should_receive(:process).exactly(3).times.with(@prot, @prot).and_return do
|
147
|
+
case (x += 1)
|
148
|
+
when 1 then raise Thrift::TransportException
|
149
|
+
when 2 then raise Thrift::ProtocolException
|
150
|
+
when 3 then raise error
|
151
|
+
end
|
152
|
+
end
|
153
|
+
@trans.should_receive(:close).exactly(3).times
|
154
|
+
@excQ.should_receive(:push).with(error).and_throw(:stop)
|
155
|
+
@serverTrans.should_receive(:close)
|
156
|
+
lambda { @server.serve }.should throw_symbol(:stop)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
data/spec/socket_spec.rb
ADDED
@@ -0,0 +1,61 @@
|
|
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.expand_path("#{File.dirname(__FILE__)}/spec_helper")
|
21
|
+
require File.expand_path("#{File.dirname(__FILE__)}/socket_spec_shared")
|
22
|
+
|
23
|
+
class ThriftSocketSpec < Spec::ExampleGroup
|
24
|
+
include Thrift
|
25
|
+
|
26
|
+
describe Socket do
|
27
|
+
before(:each) do
|
28
|
+
@socket = Socket.new
|
29
|
+
@handle = mock("Handle", :closed? => false)
|
30
|
+
@handle.stub!(:close)
|
31
|
+
@handle.stub!(:connect_nonblock)
|
32
|
+
::Socket.stub!(:new).and_return(@handle)
|
33
|
+
end
|
34
|
+
|
35
|
+
it_should_behave_like "a socket"
|
36
|
+
|
37
|
+
it "should raise a TransportException when it cannot open a socket" do
|
38
|
+
::Socket.should_receive(:new).and_raise(StandardError)
|
39
|
+
lambda { @socket.open }.should raise_error(Thrift::TransportException) { |e| e.type.should == Thrift::TransportException::NOT_OPEN }
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should open a ::Socket with default args" do
|
43
|
+
::Socket.should_receive(:new).and_return(mock("Handle", :connect_nonblock => true))
|
44
|
+
::Socket.should_receive(:getaddrinfo).with("localhost", 9090).and_return([[]])
|
45
|
+
::Socket.should_receive(:sockaddr_in)
|
46
|
+
@socket.open
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should accept host/port options" do
|
50
|
+
::Socket.should_receive(:new).and_return(mock("Handle", :connect_nonblock => true))
|
51
|
+
::Socket.should_receive(:getaddrinfo).with("my.domain", 1234).and_return([[]])
|
52
|
+
::Socket.should_receive(:sockaddr_in)
|
53
|
+
Socket.new('my.domain', 1234).open
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should accept an optional timeout" do
|
57
|
+
::Socket.stub!(:new)
|
58
|
+
Socket.new('localhost', 8080, 5).timeout.should == 5
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,104 @@
|
|
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.expand_path("#{File.dirname(__FILE__)}/spec_helper")
|
21
|
+
|
22
|
+
shared_examples_for "a socket" do
|
23
|
+
it "should open a socket" do
|
24
|
+
@socket.open.should == @handle
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should be open whenever it has a handle" do
|
28
|
+
@socket.should_not be_open
|
29
|
+
@socket.open
|
30
|
+
@socket.should be_open
|
31
|
+
@socket.handle = nil
|
32
|
+
@socket.should_not be_open
|
33
|
+
@socket.handle = @handle
|
34
|
+
@socket.close
|
35
|
+
@socket.should_not be_open
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should write data to the handle" do
|
39
|
+
@socket.open
|
40
|
+
@handle.should_receive(:write).with("foobar")
|
41
|
+
@socket.write("foobar")
|
42
|
+
@handle.should_receive(:write).with("fail").and_raise(StandardError)
|
43
|
+
lambda { @socket.write("fail") }.should raise_error(Thrift::TransportException) { |e| e.type.should == Thrift::TransportException::NOT_OPEN }
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should raise an error when it cannot read from the handle" do
|
47
|
+
@socket.open
|
48
|
+
@handle.should_receive(:readpartial).with(17).and_raise(StandardError)
|
49
|
+
lambda { @socket.read(17) }.should raise_error(Thrift::TransportException) { |e| e.type.should == Thrift::TransportException::NOT_OPEN }
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should return the data read when reading from the handle works" do
|
53
|
+
@socket.open
|
54
|
+
@handle.should_receive(:readpartial).with(17).and_return("test data")
|
55
|
+
@socket.read(17).should == "test data"
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should declare itself as closed when it has an error" do
|
59
|
+
@socket.open
|
60
|
+
@handle.should_receive(:write).with("fail").and_raise(StandardError)
|
61
|
+
@socket.should be_open
|
62
|
+
lambda { @socket.write("fail") }.should raise_error
|
63
|
+
@socket.should_not be_open
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should raise an error when the stream is closed" do
|
67
|
+
@socket.open
|
68
|
+
@handle.stub!(:closed?).and_return(true)
|
69
|
+
@socket.should_not be_open
|
70
|
+
lambda { @socket.write("fail") }.should raise_error(IOError, "closed stream")
|
71
|
+
lambda { @socket.read(10) }.should raise_error(IOError, "closed stream")
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should support the timeout accessor for read" do
|
75
|
+
@socket.timeout = 3
|
76
|
+
@socket.open
|
77
|
+
IO.should_receive(:select).with([@handle], nil, nil, 3).and_return([[@handle], [], []])
|
78
|
+
@handle.should_receive(:readpartial).with(17).and_return("test data")
|
79
|
+
@socket.read(17).should == "test data"
|
80
|
+
end
|
81
|
+
|
82
|
+
it "should support the timeout accessor for write" do
|
83
|
+
@socket.timeout = 3
|
84
|
+
@socket.open
|
85
|
+
IO.should_receive(:select).with(nil, [@handle], nil, 3).twice.and_return([[], [@handle], []])
|
86
|
+
@handle.should_receive(:write_nonblock).with("test data").and_return(4)
|
87
|
+
@handle.should_receive(:write_nonblock).with(" data").and_return(5)
|
88
|
+
@socket.write("test data").should == 9
|
89
|
+
end
|
90
|
+
|
91
|
+
it "should raise an error when read times out" do
|
92
|
+
@socket.timeout = 0.5
|
93
|
+
@socket.open
|
94
|
+
IO.should_receive(:select).once {sleep(0.5); nil}
|
95
|
+
lambda { @socket.read(17) }.should raise_error(Thrift::TransportException) { |e| e.type.should == Thrift::TransportException::TIMED_OUT }
|
96
|
+
end
|
97
|
+
|
98
|
+
it "should raise an error when write times out" do
|
99
|
+
@socket.timeout = 0.5
|
100
|
+
@socket.open
|
101
|
+
IO.should_receive(:select).with(nil, [@handle], nil, 0.5).any_number_of_times.and_return(nil)
|
102
|
+
lambda { @socket.write("test data") }.should raise_error(Thrift::TransportException) { |e| e.type.should == Thrift::TransportException::TIMED_OUT }
|
103
|
+
end
|
104
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,58 @@
|
|
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 'rubygems'
|
21
|
+
require 'spec'
|
22
|
+
|
23
|
+
$:.unshift File.join(File.dirname(__FILE__), *%w[.. ext])
|
24
|
+
|
25
|
+
# pretend we already loaded fastthread, otherwise the nonblocking_server_spec
|
26
|
+
# will get screwed up
|
27
|
+
# $" << 'fastthread.bundle'
|
28
|
+
|
29
|
+
require File.dirname(__FILE__) + '/../lib/thrift'
|
30
|
+
|
31
|
+
class Object
|
32
|
+
# tee is a useful method, so let's let our tests have it
|
33
|
+
def tee(&block)
|
34
|
+
block.call(self)
|
35
|
+
self
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
Spec::Runner.configure do |configuration|
|
40
|
+
configuration.before(:each) do
|
41
|
+
Thrift.type_checking = true
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
$:.unshift File.join(File.dirname(__FILE__), *%w[.. test debug_proto gen-rb])
|
46
|
+
require "srv"
|
47
|
+
require "debug_proto_test_constants"
|
48
|
+
|
49
|
+
$:.unshift File.join(File.dirname(__FILE__), *%w[gen-rb])
|
50
|
+
require 'thrift_spec_types'
|
51
|
+
require 'nonblocking_service'
|
52
|
+
|
53
|
+
module Fixtures
|
54
|
+
COMPACT_PROTOCOL_TEST_STRUCT = COMPACT_TEST.dup
|
55
|
+
COMPACT_PROTOCOL_TEST_STRUCT.a_binary = [0,1,2,3,4,5,6,7,8].pack('c*')
|
56
|
+
COMPACT_PROTOCOL_TEST_STRUCT.set_byte_map = nil
|
57
|
+
COMPACT_PROTOCOL_TEST_STRUCT.map_byte_map = nil
|
58
|
+
end
|
data/spec/struct_spec.rb
ADDED
@@ -0,0 +1,295 @@
|
|
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.expand_path("#{File.dirname(__FILE__)}/spec_helper")
|
21
|
+
|
22
|
+
class ThriftStructSpec < Spec::ExampleGroup
|
23
|
+
include Thrift
|
24
|
+
include SpecNamespace
|
25
|
+
|
26
|
+
describe Struct do
|
27
|
+
it "should iterate over all fields properly" do
|
28
|
+
fields = {}
|
29
|
+
Foo.new.each_field { |fid,field_info| fields[fid] = field_info }
|
30
|
+
fields.should == Foo::FIELDS
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should initialize all fields to defaults" do
|
34
|
+
validate_default_arguments(Foo.new)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should initialize all fields to defaults and accept a block argument" do
|
38
|
+
Foo.new do |f|
|
39
|
+
validate_default_arguments(f)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def validate_default_arguments(object)
|
44
|
+
object.simple.should == 53
|
45
|
+
object.words.should == "words"
|
46
|
+
object.hello.should == Hello.new(:greeting => 'hello, world!')
|
47
|
+
object.ints.should == [1, 2, 2, 3]
|
48
|
+
object.complex.should be_nil
|
49
|
+
object.shorts.should == Set.new([5, 17, 239])
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should not share default values between instances" do
|
53
|
+
begin
|
54
|
+
struct = Foo.new
|
55
|
+
struct.ints << 17
|
56
|
+
Foo.new.ints.should == [1,2,2,3]
|
57
|
+
ensure
|
58
|
+
# ensure no leakage to other tests
|
59
|
+
Foo::FIELDS[4][:default] = [1,2,2,3]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should properly initialize boolean values" do
|
64
|
+
struct = BoolStruct.new(:yesno => false)
|
65
|
+
struct.yesno.should be_false
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should have proper == semantics" do
|
69
|
+
Foo.new.should_not == Hello.new
|
70
|
+
Foo.new.should == Foo.new
|
71
|
+
Foo.new(:simple => 52).should_not == Foo.new
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should print enum value names in inspect" do
|
75
|
+
StructWithSomeEnum.new(:some_enum => SomeEnum::ONE).inspect.should == "<SpecNamespace::StructWithSomeEnum some_enum:ONE (0)>"
|
76
|
+
|
77
|
+
StructWithEnumMap.new(:my_map => {SomeEnum::ONE => [SomeEnum::TWO]}).inspect.should == "<SpecNamespace::StructWithEnumMap my_map:{ONE (0): [TWO (1)]}>"
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should pretty print binary fields" do
|
81
|
+
Foo2.new(:my_binary => "\001\002\003").inspect.should == "<SpecNamespace::Foo2 my_binary:010203>"
|
82
|
+
end
|
83
|
+
|
84
|
+
it "should offer field? methods" do
|
85
|
+
Foo.new.opt_string?.should be_false
|
86
|
+
Foo.new(:simple => 52).simple?.should be_true
|
87
|
+
Foo.new(:my_bool => false).my_bool?.should be_true
|
88
|
+
Foo.new(:my_bool => true).my_bool?.should be_true
|
89
|
+
end
|
90
|
+
|
91
|
+
it "should be comparable" do
|
92
|
+
s1 = StructWithSomeEnum.new(:some_enum => SomeEnum::ONE)
|
93
|
+
s2 = StructWithSomeEnum.new(:some_enum => SomeEnum::TWO)
|
94
|
+
|
95
|
+
(s1 <=> s2).should == -1
|
96
|
+
(s2 <=> s1).should == 1
|
97
|
+
(s1 <=> s1).should == 0
|
98
|
+
(s1 <=> StructWithSomeEnum.new()).should == -1
|
99
|
+
end
|
100
|
+
|
101
|
+
it "should read itself off the wire" do
|
102
|
+
struct = Foo.new
|
103
|
+
prot = BaseProtocol.new(mock("transport"))
|
104
|
+
prot.should_receive(:read_struct_begin).twice
|
105
|
+
prot.should_receive(:read_struct_end).twice
|
106
|
+
prot.should_receive(:read_field_begin).and_return(
|
107
|
+
['complex', Types::MAP, 5], # Foo
|
108
|
+
['words', Types::STRING, 2], # Foo
|
109
|
+
['hello', Types::STRUCT, 3], # Foo
|
110
|
+
['greeting', Types::STRING, 1], # Hello
|
111
|
+
[nil, Types::STOP, 0], # Hello
|
112
|
+
['simple', Types::I32, 1], # Foo
|
113
|
+
['ints', Types::LIST, 4], # Foo
|
114
|
+
['shorts', Types::SET, 6], # Foo
|
115
|
+
[nil, Types::STOP, 0] # Hello
|
116
|
+
)
|
117
|
+
prot.should_receive(:read_field_end).exactly(7).times
|
118
|
+
prot.should_receive(:read_map_begin).and_return(
|
119
|
+
[Types::I32, Types::MAP, 2], # complex
|
120
|
+
[Types::STRING, Types::DOUBLE, 2], # complex/1/value
|
121
|
+
[Types::STRING, Types::DOUBLE, 1] # complex/2/value
|
122
|
+
)
|
123
|
+
prot.should_receive(:read_map_end).exactly(3).times
|
124
|
+
prot.should_receive(:read_list_begin).and_return([Types::I32, 4])
|
125
|
+
prot.should_receive(:read_list_end)
|
126
|
+
prot.should_receive(:read_set_begin).and_return([Types::I16, 2])
|
127
|
+
prot.should_receive(:read_set_end)
|
128
|
+
prot.should_receive(:read_i32).and_return(
|
129
|
+
1, 14, # complex keys
|
130
|
+
42, # simple
|
131
|
+
4, 23, 4, 29 # ints
|
132
|
+
)
|
133
|
+
prot.should_receive(:read_string).and_return("pi", "e", "feigenbaum", "apple banana", "what's up?")
|
134
|
+
prot.should_receive(:read_double).and_return(Math::PI, Math::E, 4.669201609)
|
135
|
+
prot.should_receive(:read_i16).and_return(2, 3)
|
136
|
+
prot.should_not_receive(:skip)
|
137
|
+
struct.read(prot)
|
138
|
+
|
139
|
+
struct.simple.should == 42
|
140
|
+
struct.complex.should == {1 => {"pi" => Math::PI, "e" => Math::E}, 14 => {"feigenbaum" => 4.669201609}}
|
141
|
+
struct.hello.should == Hello.new(:greeting => "what's up?")
|
142
|
+
struct.words.should == "apple banana"
|
143
|
+
struct.ints.should == [4, 23, 4, 29]
|
144
|
+
struct.shorts.should == Set.new([3, 2])
|
145
|
+
end
|
146
|
+
|
147
|
+
it "should serialize false boolean fields correctly" do
|
148
|
+
b = BoolStruct.new(:yesno => false)
|
149
|
+
prot = BinaryProtocol.new(MemoryBufferTransport.new)
|
150
|
+
prot.should_receive(:write_bool).with(false)
|
151
|
+
b.write(prot)
|
152
|
+
end
|
153
|
+
|
154
|
+
it "should skip unexpected fields in structs and use default values" do
|
155
|
+
struct = Foo.new
|
156
|
+
prot = BaseProtocol.new(mock("transport"))
|
157
|
+
prot.should_receive(:read_struct_begin)
|
158
|
+
prot.should_receive(:read_struct_end)
|
159
|
+
prot.should_receive(:read_field_begin).and_return(
|
160
|
+
['simple', Types::I32, 1],
|
161
|
+
['complex', Types::STRUCT, 5],
|
162
|
+
['thinz', Types::MAP, 7],
|
163
|
+
['foobar', Types::I32, 3],
|
164
|
+
['words', Types::STRING, 2],
|
165
|
+
[nil, Types::STOP, 0]
|
166
|
+
)
|
167
|
+
prot.should_receive(:read_field_end).exactly(5).times
|
168
|
+
prot.should_receive(:read_i32).and_return(42)
|
169
|
+
prot.should_receive(:read_string).and_return("foobar")
|
170
|
+
prot.should_receive(:skip).with(Types::STRUCT)
|
171
|
+
prot.should_receive(:skip).with(Types::MAP)
|
172
|
+
# prot.should_receive(:read_map_begin).and_return([Types::I32, Types::I32, 0])
|
173
|
+
# prot.should_receive(:read_map_end)
|
174
|
+
prot.should_receive(:skip).with(Types::I32)
|
175
|
+
struct.read(prot)
|
176
|
+
|
177
|
+
struct.simple.should == 42
|
178
|
+
struct.complex.should be_nil
|
179
|
+
struct.words.should == "foobar"
|
180
|
+
struct.hello.should == Hello.new(:greeting => 'hello, world!')
|
181
|
+
struct.ints.should == [1, 2, 2, 3]
|
182
|
+
struct.shorts.should == Set.new([5, 17, 239])
|
183
|
+
end
|
184
|
+
|
185
|
+
it "should write itself to the wire" do
|
186
|
+
prot = BaseProtocol.new(mock("transport")) #mock("Protocol")
|
187
|
+
prot.should_receive(:write_struct_begin).with("SpecNamespace::Foo")
|
188
|
+
prot.should_receive(:write_struct_begin).with("SpecNamespace::Hello")
|
189
|
+
prot.should_receive(:write_struct_end).twice
|
190
|
+
prot.should_receive(:write_field_begin).with('ints', Types::LIST, 4)
|
191
|
+
prot.should_receive(:write_i32).with(1)
|
192
|
+
prot.should_receive(:write_i32).with(2).twice
|
193
|
+
prot.should_receive(:write_i32).with(3)
|
194
|
+
prot.should_receive(:write_field_begin).with('complex', Types::MAP, 5)
|
195
|
+
prot.should_receive(:write_i32).with(5)
|
196
|
+
prot.should_receive(:write_string).with('foo')
|
197
|
+
prot.should_receive(:write_double).with(1.23)
|
198
|
+
prot.should_receive(:write_field_begin).with('shorts', Types::SET, 6)
|
199
|
+
prot.should_receive(:write_i16).with(5)
|
200
|
+
prot.should_receive(:write_i16).with(17)
|
201
|
+
prot.should_receive(:write_i16).with(239)
|
202
|
+
prot.should_receive(:write_field_stop).twice
|
203
|
+
prot.should_receive(:write_field_end).exactly(6).times
|
204
|
+
prot.should_receive(:write_field_begin).with('simple', Types::I32, 1)
|
205
|
+
prot.should_receive(:write_i32).with(53)
|
206
|
+
prot.should_receive(:write_field_begin).with('hello', Types::STRUCT, 3)
|
207
|
+
prot.should_receive(:write_field_begin).with('greeting', Types::STRING, 1)
|
208
|
+
prot.should_receive(:write_string).with('hello, world!')
|
209
|
+
prot.should_receive(:write_map_begin).with(Types::I32, Types::MAP, 1)
|
210
|
+
prot.should_receive(:write_map_begin).with(Types::STRING, Types::DOUBLE, 1)
|
211
|
+
prot.should_receive(:write_map_end).twice
|
212
|
+
prot.should_receive(:write_list_begin).with(Types::I32, 4)
|
213
|
+
prot.should_receive(:write_list_end)
|
214
|
+
prot.should_receive(:write_set_begin).with(Types::I16, 3)
|
215
|
+
prot.should_receive(:write_set_end)
|
216
|
+
|
217
|
+
struct = Foo.new
|
218
|
+
struct.words = nil
|
219
|
+
struct.complex = {5 => {"foo" => 1.23}}
|
220
|
+
struct.write(prot)
|
221
|
+
end
|
222
|
+
|
223
|
+
it "should raise an exception if presented with an unknown container" do
|
224
|
+
# yeah this is silly, but I'm going for code coverage here
|
225
|
+
struct = Foo.new
|
226
|
+
lambda { struct.send :write_container, nil, nil, {:type => "foo"} }.should raise_error(StandardError, "Not a container type: foo")
|
227
|
+
end
|
228
|
+
|
229
|
+
it "should support optional type-checking in Thrift::Struct.new" do
|
230
|
+
Thrift.type_checking = true
|
231
|
+
begin
|
232
|
+
lambda { Hello.new(:greeting => 3) }.should raise_error(TypeError, "Expected Types::STRING, received Fixnum for field greeting")
|
233
|
+
ensure
|
234
|
+
Thrift.type_checking = false
|
235
|
+
end
|
236
|
+
lambda { Hello.new(:greeting => 3) }.should_not raise_error(TypeError)
|
237
|
+
end
|
238
|
+
|
239
|
+
it "should support optional type-checking in field accessors" do
|
240
|
+
Thrift.type_checking = true
|
241
|
+
begin
|
242
|
+
hello = Hello.new
|
243
|
+
lambda { hello.greeting = 3 }.should raise_error(TypeError, "Expected Types::STRING, received Fixnum for field greeting")
|
244
|
+
ensure
|
245
|
+
Thrift.type_checking = false
|
246
|
+
end
|
247
|
+
lambda { hello.greeting = 3 }.should_not raise_error(TypeError)
|
248
|
+
end
|
249
|
+
|
250
|
+
it "should raise an exception when unknown types are given to Thrift::Struct.new" do
|
251
|
+
lambda { Hello.new(:fish => 'salmon') }.should raise_error(Exception, "Unknown key given to SpecNamespace::Hello.new: fish")
|
252
|
+
end
|
253
|
+
|
254
|
+
it "should support `raise Xception, 'message'` for Exception structs" do
|
255
|
+
begin
|
256
|
+
raise Xception, "something happened"
|
257
|
+
rescue Thrift::Exception => e
|
258
|
+
e.message.should == "something happened"
|
259
|
+
e.code.should == 1
|
260
|
+
# ensure it gets serialized properly, this is the really important part
|
261
|
+
prot = BaseProtocol.new(mock("trans"))
|
262
|
+
prot.should_receive(:write_struct_begin).with("SpecNamespace::Xception")
|
263
|
+
prot.should_receive(:write_struct_end)
|
264
|
+
prot.should_receive(:write_field_begin).with('message', Types::STRING, 1)#, "something happened")
|
265
|
+
prot.should_receive(:write_string).with("something happened")
|
266
|
+
prot.should_receive(:write_field_begin).with('code', Types::I32, 2)#, 1)
|
267
|
+
prot.should_receive(:write_i32).with(1)
|
268
|
+
prot.should_receive(:write_field_stop)
|
269
|
+
prot.should_receive(:write_field_end).twice
|
270
|
+
|
271
|
+
e.write(prot)
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
it "should support the regular initializer for exception structs" do
|
276
|
+
begin
|
277
|
+
raise Xception, :message => "something happened", :code => 5
|
278
|
+
rescue Thrift::Exception => e
|
279
|
+
e.message.should == "something happened"
|
280
|
+
e.code.should == 5
|
281
|
+
prot = BaseProtocol.new(mock("trans"))
|
282
|
+
prot.should_receive(:write_struct_begin).with("SpecNamespace::Xception")
|
283
|
+
prot.should_receive(:write_struct_end)
|
284
|
+
prot.should_receive(:write_field_begin).with('message', Types::STRING, 1)
|
285
|
+
prot.should_receive(:write_string).with("something happened")
|
286
|
+
prot.should_receive(:write_field_begin).with('code', Types::I32, 2)
|
287
|
+
prot.should_receive(:write_i32).with(5)
|
288
|
+
prot.should_receive(:write_field_stop)
|
289
|
+
prot.should_receive(:write_field_end).twice
|
290
|
+
|
291
|
+
e.write(prot)
|
292
|
+
end
|
293
|
+
end
|
294
|
+
end
|
295
|
+
end
|