fl-thrift 0.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.
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,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 ThriftServerSpec < Spec::ExampleGroup
23
+ include Thrift
24
+
25
+ describe BaseServer do
26
+ it "should default to BaseTransportFactory and BinaryProtocolFactory when not specified" do
27
+ server = BaseServer.new(mock("Processor"), mock("BaseServerTransport"))
28
+ server.instance_variable_get(:'@transport_factory').should be_an_instance_of(BaseTransportFactory)
29
+ server.instance_variable_get(:'@protocol_factory').should be_an_instance_of(BinaryProtocolFactory)
30
+ end
31
+
32
+ # serve is a noop, so can't test that
33
+ end
34
+
35
+ shared_examples_for "servers" do
36
+ before(:each) do
37
+ @processor = mock("Processor")
38
+ @serverTrans = mock("ServerTransport")
39
+ @trans = mock("BaseTransport")
40
+ @prot = mock("BaseProtocol")
41
+ @client = mock("Client")
42
+ @server = server_type.new(@processor, @serverTrans, @trans, @prot)
43
+ end
44
+ end
45
+
46
+ describe SimpleServer do
47
+ it_should_behave_like "servers"
48
+
49
+ def server_type
50
+ SimpleServer
51
+ end
52
+
53
+ it "should serve in the main thread" do
54
+ @serverTrans.should_receive(:listen).ordered
55
+ @serverTrans.should_receive(:accept).exactly(3).times.and_return(@client)
56
+ @trans.should_receive(:get_transport).exactly(3).times.with(@client).and_return(@trans)
57
+ @prot.should_receive(:get_protocol).exactly(3).times.with(@trans).and_return(@prot)
58
+ x = 0
59
+ @processor.should_receive(:process).exactly(3).times.with(@prot, @prot).and_return do
60
+ case (x += 1)
61
+ when 1 then raise Thrift::TransportException
62
+ when 2 then raise Thrift::ProtocolException
63
+ when 3 then throw :stop
64
+ end
65
+ end
66
+ @trans.should_receive(:close).exactly(3).times
67
+ @serverTrans.should_receive(:close).ordered
68
+ lambda { @server.serve }.should throw_symbol(:stop)
69
+ end
70
+ end
71
+
72
+ describe ThreadedServer do
73
+ it_should_behave_like "servers"
74
+
75
+ def server_type
76
+ ThreadedServer
77
+ end
78
+
79
+ it "should serve using threads" do
80
+ @serverTrans.should_receive(:listen).ordered
81
+ @serverTrans.should_receive(:accept).exactly(3).times.and_return(@client)
82
+ @trans.should_receive(:get_transport).exactly(3).times.with(@client).and_return(@trans)
83
+ @prot.should_receive(:get_protocol).exactly(3).times.with(@trans).and_return(@prot)
84
+ Thread.should_receive(:new).with(@prot, @trans).exactly(3).times.and_yield(@prot, @trans)
85
+ x = 0
86
+ @processor.should_receive(:process).exactly(3).times.with(@prot, @prot).and_return do
87
+ case (x += 1)
88
+ when 1 then raise Thrift::TransportException
89
+ when 2 then raise Thrift::ProtocolException
90
+ when 3 then throw :stop
91
+ end
92
+ end
93
+ @trans.should_receive(:close).exactly(3).times
94
+ @serverTrans.should_receive(:close).ordered
95
+ lambda { @server.serve }.should throw_symbol(:stop)
96
+ end
97
+ end
98
+
99
+ describe ThreadPoolServer do
100
+ it_should_behave_like "servers"
101
+
102
+ def server_type
103
+ # put this stuff here so it runs before the server is created
104
+ @threadQ = mock("SizedQueue")
105
+ SizedQueue.should_receive(:new).with(20).and_return(@threadQ)
106
+ @excQ = mock("Queue")
107
+ Queue.should_receive(:new).and_return(@excQ)
108
+ ThreadPoolServer
109
+ end
110
+
111
+ it "should set up the queues" do
112
+ @server.instance_variable_get(:'@thread_q').should be(@threadQ)
113
+ @server.instance_variable_get(:'@exception_q').should be(@excQ)
114
+ end
115
+
116
+ it "should serve inside a thread" do
117
+ Thread.should_receive(:new).and_return do |block|
118
+ @server.should_receive(:serve)
119
+ block.call
120
+ @server.rspec_verify
121
+ end
122
+ @excQ.should_receive(:pop).and_throw(:popped)
123
+ lambda { @server.rescuable_serve }.should throw_symbol(:popped)
124
+ end
125
+
126
+ it "should avoid running the server twice when retrying rescuable_serve" do
127
+ Thread.should_receive(:new).and_return do |block|
128
+ @server.should_receive(:serve)
129
+ block.call
130
+ @server.rspec_verify
131
+ end
132
+ @excQ.should_receive(:pop).twice.and_throw(:popped)
133
+ lambda { @server.rescuable_serve }.should throw_symbol(:popped)
134
+ lambda { @server.rescuable_serve }.should throw_symbol(:popped)
135
+ end
136
+
137
+ it "should serve using a thread pool" do
138
+ @serverTrans.should_receive(:listen).ordered
139
+ @threadQ.should_receive(:push).with(:token)
140
+ @threadQ.should_receive(:pop)
141
+ Thread.should_receive(:new).and_yield
142
+ @serverTrans.should_receive(:accept).exactly(3).times.and_return(@client)
143
+ @trans.should_receive(:get_transport).exactly(3).times.and_return(@trans)
144
+ @prot.should_receive(:get_protocol).exactly(3).times.and_return(@prot)
145
+ x = 0
146
+ error = RuntimeError.new("Stopped")
147
+ @processor.should_receive(:process).exactly(3).times.with(@prot, @prot).and_return do
148
+ case (x += 1)
149
+ when 1 then raise Thrift::TransportException
150
+ when 2 then raise Thrift::ProtocolException
151
+ when 3 then raise error
152
+ end
153
+ end
154
+ @trans.should_receive(:close).exactly(3).times
155
+ @excQ.should_receive(:push).with(error).and_throw(:stop)
156
+ @serverTrans.should_receive(:close)
157
+ lambda { @server.serve }.should throw_symbol(:stop)
158
+ end
159
+ end
160
+ end
@@ -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.dirname(__FILE__) + '/spec_helper'
21
+ require 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.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).with([@handle], nil, nil, 0.5).at_least(1).times.and_return(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
@@ -0,0 +1,60 @@
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 at least 1.1.4 to fix a bug with describing Modules
22
+ gem 'rspec', '>= 1.1.4'
23
+ require 'spec'
24
+
25
+ $:.unshift File.join(File.dirname(__FILE__), *%w[.. ext])
26
+
27
+ # pretend we already loaded fastthread, otherwise the nonblocking_server_spec
28
+ # will get screwed up
29
+ # $" << 'fastthread.bundle'
30
+
31
+ require File.dirname(__FILE__) + '/../lib/thrift'
32
+
33
+ class Object
34
+ # tee is a useful method, so let's let our tests have it
35
+ def tee(&block)
36
+ block.call(self)
37
+ self
38
+ end
39
+ end
40
+
41
+ Spec::Runner.configure do |configuration|
42
+ configuration.before(:each) do
43
+ Thrift.type_checking = true
44
+ end
45
+ end
46
+
47
+ $:.unshift File.join(File.dirname(__FILE__), *%w[.. debug_proto_test gen-rb])
48
+ require "srv"
49
+ require "debug_proto_test_constants"
50
+
51
+ $:.unshift File.join(File.dirname(__FILE__), *%w[gen-rb])
52
+ require 'thrift_spec_types'
53
+ require 'nonblocking_service'
54
+
55
+ module Fixtures
56
+ COMPACT_PROTOCOL_TEST_STRUCT = COMPACT_TEST.dup
57
+ COMPACT_PROTOCOL_TEST_STRUCT.a_binary = [0,1,2,3,4,5,6,7,8].pack('c*')
58
+ COMPACT_PROTOCOL_TEST_STRUCT.set_byte_map = nil
59
+ COMPACT_PROTOCOL_TEST_STRUCT.map_byte_map = nil
60
+ end
@@ -0,0 +1,252 @@
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 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
+ struct = Foo.new
35
+ struct.simple.should == 53
36
+ struct.words.should == "words"
37
+ struct.hello.should == Hello.new(:greeting => 'hello, world!')
38
+ struct.ints.should == [1, 2, 2, 3]
39
+ struct.complex.should be_nil
40
+ struct.shorts.should == Set.new([5, 17, 239])
41
+ end
42
+
43
+ it "should not share default values between instances" do
44
+ begin
45
+ struct = Foo.new
46
+ struct.ints << 17
47
+ Foo.new.ints.should == [1,2,2,3]
48
+ ensure
49
+ # ensure no leakage to other tests
50
+ Foo::FIELDS[4][:default] = [1,2,2,3]
51
+ end
52
+ end
53
+
54
+ it "should properly initialize boolean values" do
55
+ struct = BoolStruct.new(:yesno => false)
56
+ struct.yesno.should be_false
57
+ end
58
+
59
+ it "should have proper == semantics" do
60
+ Foo.new.should_not == Hello.new
61
+ Foo.new.should == Foo.new
62
+ Foo.new(:simple => 52).should_not == Foo.new
63
+ end
64
+
65
+ it "should read itself off the wire" do
66
+ struct = Foo.new
67
+ prot = BaseProtocol.new(mock("transport"))
68
+ prot.should_receive(:read_struct_begin).twice
69
+ prot.should_receive(:read_struct_end).twice
70
+ prot.should_receive(:read_field_begin).and_return(
71
+ ['complex', Types::MAP, 5], # Foo
72
+ ['words', Types::STRING, 2], # Foo
73
+ ['hello', Types::STRUCT, 3], # Foo
74
+ ['greeting', Types::STRING, 1], # Hello
75
+ [nil, Types::STOP, 0], # Hello
76
+ ['simple', Types::I32, 1], # Foo
77
+ ['ints', Types::LIST, 4], # Foo
78
+ ['shorts', Types::SET, 6], # Foo
79
+ [nil, Types::STOP, 0] # Hello
80
+ )
81
+ prot.should_receive(:read_field_end).exactly(7).times
82
+ prot.should_receive(:read_map_begin).and_return(
83
+ [Types::I32, Types::MAP, 2], # complex
84
+ [Types::STRING, Types::DOUBLE, 2], # complex/1/value
85
+ [Types::STRING, Types::DOUBLE, 1] # complex/2/value
86
+ )
87
+ prot.should_receive(:read_map_end).exactly(3).times
88
+ prot.should_receive(:read_list_begin).and_return([Types::I32, 4])
89
+ prot.should_receive(:read_list_end)
90
+ prot.should_receive(:read_set_begin).and_return([Types::I16, 2])
91
+ prot.should_receive(:read_set_end)
92
+ prot.should_receive(:read_i32).and_return(
93
+ 1, 14, # complex keys
94
+ 42, # simple
95
+ 4, 23, 4, 29 # ints
96
+ )
97
+ prot.should_receive(:read_string).and_return("pi", "e", "feigenbaum", "apple banana", "what's up?")
98
+ prot.should_receive(:read_double).and_return(Math::PI, Math::E, 4.669201609)
99
+ prot.should_receive(:read_i16).and_return(2, 3)
100
+ prot.should_not_receive(:skip)
101
+ struct.read(prot)
102
+
103
+ struct.simple.should == 42
104
+ struct.complex.should == {1 => {"pi" => Math::PI, "e" => Math::E}, 14 => {"feigenbaum" => 4.669201609}}
105
+ struct.hello.should == Hello.new(:greeting => "what's up?")
106
+ struct.words.should == "apple banana"
107
+ struct.ints.should == [4, 23, 4, 29]
108
+ struct.shorts.should == Set.new([3, 2])
109
+ end
110
+
111
+ it "should skip unexpected fields in structs and use default values" do
112
+ struct = Foo.new
113
+ prot = BaseProtocol.new(mock("transport"))
114
+ prot.should_receive(:read_struct_begin)
115
+ prot.should_receive(:read_struct_end)
116
+ prot.should_receive(:read_field_begin).and_return(
117
+ ['simple', Types::I32, 1],
118
+ ['complex', Types::STRUCT, 5],
119
+ ['thinz', Types::MAP, 7],
120
+ ['foobar', Types::I32, 3],
121
+ ['words', Types::STRING, 2],
122
+ [nil, Types::STOP, 0]
123
+ )
124
+ prot.should_receive(:read_field_end).exactly(5).times
125
+ prot.should_receive(:read_i32).and_return(42)
126
+ prot.should_receive(:read_string).and_return("foobar")
127
+ prot.should_receive(:skip).with(Types::STRUCT)
128
+ prot.should_receive(:skip).with(Types::MAP)
129
+ # prot.should_receive(:read_map_begin).and_return([Types::I32, Types::I32, 0])
130
+ # prot.should_receive(:read_map_end)
131
+ prot.should_receive(:skip).with(Types::I32)
132
+ struct.read(prot)
133
+
134
+ struct.simple.should == 42
135
+ struct.complex.should be_nil
136
+ struct.words.should == "foobar"
137
+ struct.hello.should == Hello.new(:greeting => 'hello, world!')
138
+ struct.ints.should == [1, 2, 2, 3]
139
+ struct.shorts.should == Set.new([5, 17, 239])
140
+ end
141
+
142
+ it "should write itself to the wire" do
143
+ prot = BaseProtocol.new(mock("transport")) #mock("Protocol")
144
+ prot.should_receive(:write_struct_begin).with("SpecNamespace::Foo")
145
+ prot.should_receive(:write_struct_begin).with("SpecNamespace::Hello")
146
+ prot.should_receive(:write_struct_end).twice
147
+ prot.should_receive(:write_field_begin).with('ints', Types::LIST, 4)
148
+ prot.should_receive(:write_i32).with(1)
149
+ prot.should_receive(:write_i32).with(2).twice
150
+ prot.should_receive(:write_i32).with(3)
151
+ prot.should_receive(:write_field_begin).with('complex', Types::MAP, 5)
152
+ prot.should_receive(:write_i32).with(5)
153
+ prot.should_receive(:write_string).with('foo')
154
+ prot.should_receive(:write_double).with(1.23)
155
+ prot.should_receive(:write_field_begin).with('shorts', Types::SET, 6)
156
+ prot.should_receive(:write_i16).with(5)
157
+ prot.should_receive(:write_i16).with(17)
158
+ prot.should_receive(:write_i16).with(239)
159
+ prot.should_receive(:write_field_stop).twice
160
+ prot.should_receive(:write_field_end).exactly(6).times
161
+ prot.should_receive(:write_field_begin).with('simple', Types::I32, 1)
162
+ prot.should_receive(:write_i32).with(53)
163
+ prot.should_receive(:write_field_begin).with('hello', Types::STRUCT, 3)
164
+ prot.should_receive(:write_field_begin).with('greeting', Types::STRING, 1)
165
+ prot.should_receive(:write_string).with('hello, world!')
166
+ prot.should_receive(:write_map_begin).with(Types::I32, Types::MAP, 1)
167
+ prot.should_receive(:write_map_begin).with(Types::STRING, Types::DOUBLE, 1)
168
+ prot.should_receive(:write_map_end).twice
169
+ prot.should_receive(:write_list_begin).with(Types::I32, 4)
170
+ prot.should_receive(:write_list_end)
171
+ prot.should_receive(:write_set_begin).with(Types::I16, 3)
172
+ prot.should_receive(:write_set_end)
173
+
174
+ struct = Foo.new
175
+ struct.words = nil
176
+ struct.complex = {5 => {"foo" => 1.23}}
177
+ struct.write(prot)
178
+ end
179
+
180
+ it "should raise an exception if presented with an unknown container" do
181
+ # yeah this is silly, but I'm going for code coverage here
182
+ struct = Foo.new
183
+ lambda { struct.send :write_container, nil, nil, {:type => "foo"} }.should raise_error(StandardError, "Not a container type: foo")
184
+ end
185
+
186
+ it "should support optional type-checking in Thrift::Struct.new" do
187
+ Thrift.type_checking = true
188
+ begin
189
+ lambda { Hello.new(:greeting => 3) }.should raise_error(TypeError, "Expected Types::STRING, received Fixnum for field greeting")
190
+ ensure
191
+ Thrift.type_checking = false
192
+ end
193
+ lambda { Hello.new(:greeting => 3) }.should_not raise_error(TypeError)
194
+ end
195
+
196
+ it "should support optional type-checking in field accessors" do
197
+ Thrift.type_checking = true
198
+ begin
199
+ hello = Hello.new
200
+ lambda { hello.greeting = 3 }.should raise_error(TypeError, "Expected Types::STRING, received Fixnum for field greeting")
201
+ ensure
202
+ Thrift.type_checking = false
203
+ end
204
+ lambda { hello.greeting = 3 }.should_not raise_error(TypeError)
205
+ end
206
+
207
+ it "should raise an exception when unknown types are given to Thrift::Struct.new" do
208
+ lambda { Hello.new(:fish => 'salmon') }.should raise_error(Exception, "Unknown key given to SpecNamespace::Hello.new: fish")
209
+ end
210
+
211
+ it "should support `raise Xception, 'message'` for Exception structs" do
212
+ begin
213
+ raise Xception, "something happened"
214
+ rescue Thrift::Exception => e
215
+ e.message.should == "something happened"
216
+ e.code.should == 1
217
+ # ensure it gets serialized properly, this is the really important part
218
+ prot = BaseProtocol.new(mock("trans"))
219
+ prot.should_receive(:write_struct_begin).with("SpecNamespace::Xception")
220
+ prot.should_receive(:write_struct_end)
221
+ prot.should_receive(:write_field_begin).with('message', Types::STRING, 1)#, "something happened")
222
+ prot.should_receive(:write_string).with("something happened")
223
+ prot.should_receive(:write_field_begin).with('code', Types::I32, 2)#, 1)
224
+ prot.should_receive(:write_i32).with(1)
225
+ prot.should_receive(:write_field_stop)
226
+ prot.should_receive(:write_field_end).twice
227
+
228
+ e.write(prot)
229
+ end
230
+ end
231
+
232
+ it "should support the regular initializer for exception structs" do
233
+ begin
234
+ raise Xception, :message => "something happened", :code => 5
235
+ rescue Thrift::Exception => e
236
+ e.message.should == "something happened"
237
+ e.code.should == 5
238
+ prot = BaseProtocol.new(mock("trans"))
239
+ prot.should_receive(:write_struct_begin).with("SpecNamespace::Xception")
240
+ prot.should_receive(:write_struct_end)
241
+ prot.should_receive(:write_field_begin).with('message', Types::STRING, 1)
242
+ prot.should_receive(:write_string).with("something happened")
243
+ prot.should_receive(:write_field_begin).with('code', Types::I32, 2)
244
+ prot.should_receive(:write_i32).with(5)
245
+ prot.should_receive(:write_field_stop)
246
+ prot.should_receive(:write_field_end).twice
247
+
248
+ e.write(prot)
249
+ end
250
+ end
251
+ end
252
+ end