| 30 |
ashish |
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
|