diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:45:59 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:45:59 +0000 |
commit | 19fcec84d8d7d21e796c7624e521b60d28ee21ed (patch) | |
tree | 42d26aa27d1e3f7c0b8bd3fd14e7d7082f5008dc /src/jaegertracing/thrift/lib/rb/spec | |
parent | Initial commit. (diff) | |
download | ceph-upstream.tar.xz ceph-upstream.zip |
Adding upstream version 16.2.11+ds.upstream/16.2.11+dsupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/jaegertracing/thrift/lib/rb/spec')
34 files changed, 5002 insertions, 0 deletions
diff --git a/src/jaegertracing/thrift/lib/rb/spec/BaseService.thrift b/src/jaegertracing/thrift/lib/rb/spec/BaseService.thrift new file mode 100644 index 000000000..5c7d32a6c --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/spec/BaseService.thrift @@ -0,0 +1,27 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +namespace rb Base + +struct Hello { + 1: string greeting = "hello world" +} + +service BaseService { + Hello greeting(1:bool english) +} diff --git a/src/jaegertracing/thrift/lib/rb/spec/ExtendedService.thrift b/src/jaegertracing/thrift/lib/rb/spec/ExtendedService.thrift new file mode 100644 index 000000000..1a6b705aa --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/spec/ExtendedService.thrift @@ -0,0 +1,25 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +namespace rb Extended + +include "BaseService.thrift" + +service ExtendedService extends BaseService.BaseService { + void ping() +} diff --git a/src/jaegertracing/thrift/lib/rb/spec/Referenced.thrift b/src/jaegertracing/thrift/lib/rb/spec/Referenced.thrift new file mode 100644 index 000000000..98f183fe0 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/spec/Referenced.thrift @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +namespace rb OtherNamespace + +enum SomeEnum { + ONE + TWO +} diff --git a/src/jaegertracing/thrift/lib/rb/spec/ThriftNamespacedSpec.thrift b/src/jaegertracing/thrift/lib/rb/spec/ThriftNamespacedSpec.thrift new file mode 100644 index 000000000..02f28895a --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/spec/ThriftNamespacedSpec.thrift @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +namespace rb NamespacedSpecNamespace + +include "Referenced.thrift" + +struct Hello { + 1: string greeting = "hello world" +} + +service NamespacedNonblockingService { + Hello greeting(1:bool english) + bool block() + oneway void unblock(1:i32 n) + oneway void shutdown() + void sleep(1:double seconds) +} diff --git a/src/jaegertracing/thrift/lib/rb/spec/ThriftSpec.thrift b/src/jaegertracing/thrift/lib/rb/spec/ThriftSpec.thrift new file mode 100644 index 000000000..b42481b32 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/spec/ThriftSpec.thrift @@ -0,0 +1,183 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +namespace rb SpecNamespace + +struct Hello { + 1: string greeting = "hello world" +} + +enum SomeEnum { + ONE + TWO +} + +struct StructWithSomeEnum { + 1: SomeEnum some_enum; +} + +union TestUnion { + /** + * A doc string + */ + 1: string string_field; + 2: i32 i32_field; + 3: i32 other_i32_field; + 4: SomeEnum enum_field; + 5: binary binary_field; +} + +struct Foo { + 1: i32 simple = 53, + 2: string words = "words", + 3: Hello hello = {'greeting' : "hello, world!"}, + 4: list<i32> ints = [1, 2, 2, 3], + 5: map<i32, map<string, double>> complex, + 6: set<i16> shorts = [5, 17, 239], + 7: optional string opt_string + 8: bool my_bool +} + +struct Foo2 { + 1: binary my_binary +} + +struct BoolStruct { + 1: bool yesno = 1 +} + +struct SimpleList { + 1: list<bool> bools, + 2: list<byte> bytes, + 3: list<i16> i16s, + 4: list<i32> i32s, + 5: list<i64> i64s, + 6: list<double> doubles, + 7: list<string> strings, + 8: list<map<i16, i16>> maps, + 9: list<list<i16>> lists, + 10: list<set<i16>> sets, + 11: list<Hello> hellos +} + +exception Xception { + 1: string message, + 2: i32 code = 1 +} + +service NonblockingService { + Hello greeting(1:bool english) + bool block() + oneway void unblock(1:i32 n) + oneway void shutdown() + void sleep(1:double seconds) +} + +union My_union { + 1: bool im_true, + 2: byte a_bite, + 3: i16 integer16, + 4: i32 integer32, + 5: i64 integer64, + 6: double double_precision, + 7: string some_characters, + 8: i32 other_i32 + 9: SomeEnum some_enum; + 10: map<SomeEnum, list<SomeEnum>> my_map; +} + +struct Struct_with_union { + 1: My_union fun_union + 2: i32 integer32 + 3: string some_characters +} + +struct StructWithEnumMap { + 1: map<SomeEnum, list<SomeEnum>> my_map; +} + +# Nested lists +struct NestedListInList { + 1: list<list<byte>> value +} + +struct NestedListInSet { + 1: set<list<byte>> value +} + +struct NestedListInMapKey { + 1: map<list<byte>, byte> value +} + +struct NestedListInMapValue { + 1: map<byte, list<byte>> value +} + +# Nested sets +struct NestedSetInList { + 1: list<set<byte>> value +} + +struct NestedSetInSet { + 1: set<set<byte>> value +} + +struct NestedSetInMapKey { + 1: map<set<byte>, byte> value +} + +struct NestedSetInMapValue { + 1: map<byte, set<byte>> value +} + +# Nested maps +struct NestedMapInList { + 1: list<map<byte, byte>> value +} + +struct NestedMapInSet { + 1: set<map<byte, byte>> value +} + +struct NestedMapInMapKey { + 2: map<map<byte, byte>, byte> value +} + +struct NestedMapInMapValue { + 2: map<byte, map<byte, byte>> value +} diff --git a/src/jaegertracing/thrift/lib/rb/spec/base_protocol_spec.rb b/src/jaegertracing/thrift/lib/rb/spec/base_protocol_spec.rb new file mode 100644 index 000000000..cfa7573d8 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/spec/base_protocol_spec.rb @@ -0,0 +1,225 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +require 'spec_helper' + +describe 'BaseProtocol' do + + before(:each) do + @trans = double("MockTransport") + @prot = Thrift::BaseProtocol.new(@trans) + end + + describe Thrift::BaseProtocol do + # most of the methods are stubs, so we can ignore them + + it "should provide a reasonable to_s" do + expect(@trans).to receive(:to_s).once.and_return("trans") + expect(@prot.to_s).to eq("trans") + end + + it "should make trans accessible" do + expect(@prot.trans).to eql(@trans) + end + + it 'should write out a field nicely (deprecated write_field signature)' do + expect(@prot).to receive(:write_field_begin).with('field', 'type', 'fid').ordered + expect(@prot).to receive(:write_type).with({:name => 'field', :type => 'type'}, 'value').ordered + expect(@prot).to receive(:write_field_end).ordered + @prot.write_field('field', 'type', 'fid', 'value') + end + + it 'should write out a field nicely' do + expect(@prot).to receive(:write_field_begin).with('field', 'type', 'fid').ordered + expect(@prot).to receive(:write_type).with({:name => 'field', :type => 'type', :binary => false}, 'value').ordered + expect(@prot).to receive(:write_field_end).ordered + @prot.write_field({:name => 'field', :type => 'type', :binary => false}, 'fid', 'value') + end + + it 'should write out the different types (deprecated write_type signature)' do + expect(@prot).to receive(:write_bool).with('bool').ordered + expect(@prot).to receive(:write_byte).with('byte').ordered + expect(@prot).to receive(:write_double).with('double').ordered + expect(@prot).to receive(:write_i16).with('i16').ordered + expect(@prot).to receive(:write_i32).with('i32').ordered + expect(@prot).to receive(:write_i64).with('i64').ordered + expect(@prot).to receive(:write_string).with('string').ordered + struct = double('Struct') + expect(struct).to receive(:write).with(@prot).ordered + @prot.write_type(Thrift::Types::BOOL, 'bool') + @prot.write_type(Thrift::Types::BYTE, 'byte') + @prot.write_type(Thrift::Types::DOUBLE, 'double') + @prot.write_type(Thrift::Types::I16, 'i16') + @prot.write_type(Thrift::Types::I32, 'i32') + @prot.write_type(Thrift::Types::I64, 'i64') + @prot.write_type(Thrift::Types::STRING, 'string') + @prot.write_type(Thrift::Types::STRUCT, struct) + # all other types are not implemented + [Thrift::Types::STOP, Thrift::Types::VOID, Thrift::Types::MAP, Thrift::Types::SET, Thrift::Types::LIST].each do |type| + expect { @prot.write_type(type, type.to_s) }.to raise_error(NotImplementedError) + end + end + + it 'should write out the different types' do + expect(@prot).to receive(:write_bool).with('bool').ordered + expect(@prot).to receive(:write_byte).with('byte').ordered + expect(@prot).to receive(:write_double).with('double').ordered + expect(@prot).to receive(:write_i16).with('i16').ordered + expect(@prot).to receive(:write_i32).with('i32').ordered + expect(@prot).to receive(:write_i64).with('i64').ordered + expect(@prot).to receive(:write_string).with('string').ordered + expect(@prot).to receive(:write_binary).with('binary').ordered + struct = double('Struct') + expect(struct).to receive(:write).with(@prot).ordered + @prot.write_type({:type => Thrift::Types::BOOL}, 'bool') + @prot.write_type({:type => Thrift::Types::BYTE}, 'byte') + @prot.write_type({:type => Thrift::Types::DOUBLE}, 'double') + @prot.write_type({:type => Thrift::Types::I16}, 'i16') + @prot.write_type({:type => Thrift::Types::I32}, 'i32') + @prot.write_type({:type => Thrift::Types::I64}, 'i64') + @prot.write_type({:type => Thrift::Types::STRING}, 'string') + @prot.write_type({:type => Thrift::Types::STRING, :binary => true}, 'binary') + @prot.write_type({:type => Thrift::Types::STRUCT}, struct) + # all other types are not implemented + [Thrift::Types::STOP, Thrift::Types::VOID, Thrift::Types::MAP, Thrift::Types::SET, Thrift::Types::LIST].each do |type| + expect { @prot.write_type({:type => type}, type.to_s) }.to raise_error(NotImplementedError) + end + end + + it 'should read the different types (deprecated read_type signature)' do + expect(@prot).to receive(:read_bool).ordered + expect(@prot).to receive(:read_byte).ordered + expect(@prot).to receive(:read_i16).ordered + expect(@prot).to receive(:read_i32).ordered + expect(@prot).to receive(:read_i64).ordered + expect(@prot).to receive(:read_double).ordered + expect(@prot).to receive(:read_string).ordered + @prot.read_type(Thrift::Types::BOOL) + @prot.read_type(Thrift::Types::BYTE) + @prot.read_type(Thrift::Types::I16) + @prot.read_type(Thrift::Types::I32) + @prot.read_type(Thrift::Types::I64) + @prot.read_type(Thrift::Types::DOUBLE) + @prot.read_type(Thrift::Types::STRING) + # all other types are not implemented + [Thrift::Types::STOP, Thrift::Types::VOID, Thrift::Types::MAP, + Thrift::Types::SET, Thrift::Types::LIST, Thrift::Types::STRUCT].each do |type| + expect { @prot.read_type(type) }.to raise_error(NotImplementedError) + end + end + + it 'should read the different types' do + expect(@prot).to receive(:read_bool).ordered + expect(@prot).to receive(:read_byte).ordered + expect(@prot).to receive(:read_i16).ordered + expect(@prot).to receive(:read_i32).ordered + expect(@prot).to receive(:read_i64).ordered + expect(@prot).to receive(:read_double).ordered + expect(@prot).to receive(:read_string).ordered + expect(@prot).to receive(:read_binary).ordered + @prot.read_type({:type => Thrift::Types::BOOL}) + @prot.read_type({:type => Thrift::Types::BYTE}) + @prot.read_type({:type => Thrift::Types::I16}) + @prot.read_type({:type => Thrift::Types::I32}) + @prot.read_type({:type => Thrift::Types::I64}) + @prot.read_type({:type => Thrift::Types::DOUBLE}) + @prot.read_type({:type => Thrift::Types::STRING}) + @prot.read_type({:type => Thrift::Types::STRING, :binary => true}) + # all other types are not implemented + [Thrift::Types::STOP, Thrift::Types::VOID, Thrift::Types::MAP, + Thrift::Types::SET, Thrift::Types::LIST, Thrift::Types::STRUCT].each do |type| + expect { @prot.read_type({:type => type}) }.to raise_error(NotImplementedError) + end + end + + it "should skip the basic types" do + expect(@prot).to receive(:read_bool).ordered + expect(@prot).to receive(:read_byte).ordered + expect(@prot).to receive(:read_i16).ordered + expect(@prot).to receive(:read_i32).ordered + expect(@prot).to receive(:read_i64).ordered + expect(@prot).to receive(:read_double).ordered + expect(@prot).to receive(:read_string).ordered + @prot.skip(Thrift::Types::BOOL) + @prot.skip(Thrift::Types::BYTE) + @prot.skip(Thrift::Types::I16) + @prot.skip(Thrift::Types::I32) + @prot.skip(Thrift::Types::I64) + @prot.skip(Thrift::Types::DOUBLE) + @prot.skip(Thrift::Types::STRING) + end + + it "should skip structs" do + real_skip = @prot.method(:skip) + expect(@prot).to receive(:read_struct_begin).ordered + expect(@prot).to receive(:read_field_begin).exactly(4).times.and_return( + ['field 1', Thrift::Types::STRING, 1], + ['field 2', Thrift::Types::I32, 2], + ['field 3', Thrift::Types::MAP, 3], + [nil, Thrift::Types::STOP, 0] + ) + expect(@prot).to receive(:read_field_end).exactly(3).times + expect(@prot).to receive(:read_string).exactly(3).times + expect(@prot).to receive(:read_i32).ordered + expect(@prot).to receive(:read_map_begin).ordered.and_return([Thrift::Types::STRING, Thrift::Types::STRING, 1]) + # @prot.should_receive(:read_string).exactly(2).times + expect(@prot).to receive(:read_map_end).ordered + expect(@prot).to receive(:read_struct_end).ordered + real_skip.call(Thrift::Types::STRUCT) + end + + it "should skip maps" do + real_skip = @prot.method(:skip) + expect(@prot).to receive(:read_map_begin).ordered.and_return([Thrift::Types::STRING, Thrift::Types::STRUCT, 1]) + expect(@prot).to receive(:read_string).ordered + expect(@prot).to receive(:read_struct_begin).ordered.and_return(["some_struct"]) + expect(@prot).to receive(:read_field_begin).ordered.and_return([nil, Thrift::Types::STOP, nil]); + expect(@prot).to receive(:read_struct_end).ordered + expect(@prot).to receive(:read_map_end).ordered + real_skip.call(Thrift::Types::MAP) + end + + it "should skip sets" do + real_skip = @prot.method(:skip) + expect(@prot).to receive(:read_set_begin).ordered.and_return([Thrift::Types::I64, 9]) + expect(@prot).to receive(:read_i64).ordered.exactly(9).times + expect(@prot).to receive(:read_set_end) + real_skip.call(Thrift::Types::SET) + end + + it "should skip lists" do + real_skip = @prot.method(:skip) + expect(@prot).to receive(:read_list_begin).ordered.and_return([Thrift::Types::DOUBLE, 11]) + expect(@prot).to receive(:read_double).ordered.exactly(11).times + expect(@prot).to receive(:read_list_end) + real_skip.call(Thrift::Types::LIST) + end + end + + describe Thrift::BaseProtocolFactory do + it "should raise NotImplementedError" do + # returning nil since Protocol is just an abstract class + expect {Thrift::BaseProtocolFactory.new.get_protocol(double("MockTransport"))}.to raise_error(NotImplementedError) + end + + it "should provide a reasonable to_s" do + expect(Thrift::BaseProtocolFactory.new.to_s).to eq("base") + end + end +end diff --git a/src/jaegertracing/thrift/lib/rb/spec/base_transport_spec.rb b/src/jaegertracing/thrift/lib/rb/spec/base_transport_spec.rb new file mode 100644 index 000000000..d2f60aaea --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/spec/base_transport_spec.rb @@ -0,0 +1,388 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +require 'spec_helper' + +describe 'BaseTransport' do + + describe Thrift::TransportException do + it "should make type accessible" do + exc = Thrift::TransportException.new(Thrift::TransportException::ALREADY_OPEN, "msg") + expect(exc.type).to eq(Thrift::TransportException::ALREADY_OPEN) + expect(exc.message).to eq("msg") + end + end + + describe Thrift::BaseTransport do + it "should read the specified size" do + transport = Thrift::BaseTransport.new + expect(transport).to receive(:read).with(40).ordered.and_return("10 letters") + expect(transport).to receive(:read).with(30).ordered.and_return("fifteen letters") + expect(transport).to receive(:read).with(15).ordered.and_return("more characters") + expect(transport.read_all(40)).to eq("10 lettersfifteen lettersmore characters") + end + + it "should stub out the rest of the methods" do + # can't test for stubbiness, so just make sure they're defined + [:open?, :open, :close, :read, :write, :flush].each do |sym| + expect(Thrift::BaseTransport.method_defined?(sym)).to be_truthy + end + end + + it "should alias << to write" do + expect(Thrift::BaseTransport.instance_method(:<<)).to eq(Thrift::BaseTransport.instance_method(:write)) + end + + it "should provide a reasonable to_s" do + expect(Thrift::BaseTransport.new.to_s).to eq("base") + end + end + + describe Thrift::BaseServerTransport do + it "should stub out its methods" do + [:listen, :accept, :close].each do |sym| + expect(Thrift::BaseServerTransport.method_defined?(sym)).to be_truthy + end + end + end + + describe Thrift::BaseTransportFactory do + it "should return the transport it's given" do + transport = double("Transport") + expect(Thrift::BaseTransportFactory.new.get_transport(transport)).to eql(transport) + end + + it "should provide a reasonable to_s" do + expect(Thrift::BaseTransportFactory.new.to_s).to eq("base") + end + end + + describe Thrift::BufferedTransport do + it "should provide a to_s that describes the encapsulation" do + trans = double("Transport") + expect(trans).to receive(:to_s).and_return("mock") + expect(Thrift::BufferedTransport.new(trans).to_s).to eq("buffered(mock)") + end + + it "should pass through everything but write/flush/read" do + trans = double("Transport") + expect(trans).to receive(:open?).ordered.and_return("+ open?") + expect(trans).to receive(:open).ordered.and_return("+ open") + expect(trans).to receive(:flush).ordered # from the close + expect(trans).to receive(:close).ordered.and_return("+ close") + btrans = Thrift::BufferedTransport.new(trans) + expect(btrans.open?).to eq("+ open?") + expect(btrans.open).to eq("+ open") + expect(btrans.close).to eq("+ close") + end + + it "should buffer reads in chunks of #{Thrift::BufferedTransport::DEFAULT_BUFFER}" do + trans = double("Transport") + expect(trans).to receive(:read).with(Thrift::BufferedTransport::DEFAULT_BUFFER).and_return("lorum ipsum dolor emet") + btrans = Thrift::BufferedTransport.new(trans) + expect(btrans.read(6)).to eq("lorum ") + expect(btrans.read(6)).to eq("ipsum ") + expect(btrans.read(6)).to eq("dolor ") + expect(btrans.read(6)).to eq("emet") + end + + it "should buffer writes and send them on flush" do + trans = double("Transport") + btrans = Thrift::BufferedTransport.new(trans) + btrans.write("one/") + btrans.write("two/") + btrans.write("three/") + expect(trans).to receive(:write).with("one/two/three/").ordered + expect(trans).to receive(:flush).ordered + btrans.flush + end + + it "should only send buffered data once" do + trans = double("Transport") + btrans = Thrift::BufferedTransport.new(trans) + btrans.write("one/") + btrans.write("two/") + btrans.write("three/") + expect(trans).to receive(:write).with("one/two/three/") + allow(trans).to receive(:flush) + btrans.flush + # Nothing to flush with no data + btrans.flush + end + + it "should flush on close" do + trans = double("Transport") + expect(trans).to receive(:close) + btrans = Thrift::BufferedTransport.new(trans) + expect(btrans).to receive(:flush) + btrans.close + end + + it "should not write to socket if there's no data" do + trans = double("Transport") + expect(trans).to receive(:flush) + btrans = Thrift::BufferedTransport.new(trans) + btrans.flush + end + end + + describe Thrift::BufferedTransportFactory do + it "should wrap the given transport in a BufferedTransport" do + trans = double("Transport") + btrans = double("BufferedTransport") + expect(Thrift::BufferedTransport).to receive(:new).with(trans).and_return(btrans) + expect(Thrift::BufferedTransportFactory.new.get_transport(trans)).to eq(btrans) + end + + it "should provide a reasonable to_s" do + expect(Thrift::BufferedTransportFactory.new.to_s).to eq("buffered") + end + end + + describe Thrift::FramedTransport do + before(:each) do + @trans = double("Transport") + end + + it "should provide a to_s that describes the encapsulation" do + trans = double("Transport") + expect(trans).to receive(:to_s).and_return("mock") + expect(Thrift::FramedTransport.new(trans).to_s).to eq("framed(mock)") + end + + it "should pass through open?/open/close" do + ftrans = Thrift::FramedTransport.new(@trans) + expect(@trans).to receive(:open?).ordered.and_return("+ open?") + expect(@trans).to receive(:open).ordered.and_return("+ open") + expect(@trans).to receive(:close).ordered.and_return("+ close") + expect(ftrans.open?).to eq("+ open?") + expect(ftrans.open).to eq("+ open") + expect(ftrans.close).to eq("+ close") + end + + it "should pass through read when read is turned off" do + ftrans = Thrift::FramedTransport.new(@trans, false, true) + expect(@trans).to receive(:read).with(17).ordered.and_return("+ read") + expect(ftrans.read(17)).to eq("+ read") + end + + it "should pass through write/flush when write is turned off" do + ftrans = Thrift::FramedTransport.new(@trans, true, false) + expect(@trans).to receive(:write).with("foo").ordered.and_return("+ write") + expect(@trans).to receive(:flush).ordered.and_return("+ flush") + expect(ftrans.write("foo")).to eq("+ write") + expect(ftrans.flush).to eq("+ flush") + end + + it "should return a full frame if asked for >= the frame's length" do + frame = "this is a frame" + expect(@trans).to receive(:read_all).with(4).and_return("\000\000\000\017") + expect(@trans).to receive(:read_all).with(frame.length).and_return(frame) + expect(Thrift::FramedTransport.new(@trans).read(frame.length + 10)).to eq(frame) + end + + it "should return slices of the frame when asked for < the frame's length" do + frame = "this is a frame" + expect(@trans).to receive(:read_all).with(4).and_return("\000\000\000\017") + expect(@trans).to receive(:read_all).with(frame.length).and_return(frame) + ftrans = Thrift::FramedTransport.new(@trans) + expect(ftrans.read(4)).to eq("this") + expect(ftrans.read(4)).to eq(" is ") + expect(ftrans.read(16)).to eq("a frame") + end + + it "should return nothing if asked for <= 0" do + expect(Thrift::FramedTransport.new(@trans).read(-2)).to eq("") + end + + it "should pull a new frame when the first is exhausted" do + frame = "this is a frame" + frame2 = "yet another frame" + expect(@trans).to receive(:read_all).with(4).and_return("\000\000\000\017", "\000\000\000\021") + expect(@trans).to receive(:read_all).with(frame.length).and_return(frame) + expect(@trans).to receive(:read_all).with(frame2.length).and_return(frame2) + ftrans = Thrift::FramedTransport.new(@trans) + expect(ftrans.read(4)).to eq("this") + expect(ftrans.read(8)).to eq(" is a fr") + expect(ftrans.read(6)).to eq("ame") + expect(ftrans.read(4)).to eq("yet ") + expect(ftrans.read(16)).to eq("another frame") + end + + it "should buffer writes" do + ftrans = Thrift::FramedTransport.new(@trans) + expect(@trans).not_to receive(:write) + ftrans.write("foo") + ftrans.write("bar") + ftrans.write("this is a frame") + end + + it "should write slices of the buffer" do + ftrans = Thrift::FramedTransport.new(@trans) + ftrans.write("foobar", 3) + ftrans.write("barfoo", 1) + allow(@trans).to receive(:flush) + expect(@trans).to receive(:write).with("\000\000\000\004foob") + ftrans.flush + end + + it "should flush frames with a 4-byte header" do + ftrans = Thrift::FramedTransport.new(@trans) + expect(@trans).to receive(:write).with("\000\000\000\035one/two/three/this is a frame").ordered + expect(@trans).to receive(:flush).ordered + ftrans.write("one/") + ftrans.write("two/") + ftrans.write("three/") + ftrans.write("this is a frame") + ftrans.flush + end + + it "should not flush the same buffered data twice" do + ftrans = Thrift::FramedTransport.new(@trans) + expect(@trans).to receive(:write).with("\000\000\000\007foo/bar") + allow(@trans).to receive(:flush) + ftrans.write("foo") + ftrans.write("/bar") + ftrans.flush + expect(@trans).to receive(:write).with("\000\000\000\000") + ftrans.flush + end + end + + describe Thrift::FramedTransportFactory do + it "should wrap the given transport in a FramedTransport" do + trans = double("Transport") + expect(Thrift::FramedTransport).to receive(:new).with(trans) + Thrift::FramedTransportFactory.new.get_transport(trans) + end + + it "should provide a reasonable to_s" do + expect(Thrift::FramedTransportFactory.new.to_s).to eq("framed") + end + end + + describe Thrift::MemoryBufferTransport do + before(:each) do + @buffer = Thrift::MemoryBufferTransport.new + end + + it "should provide a reasonable to_s" do + expect(@buffer.to_s).to eq("memory") + end + + it "should accept a buffer on input and use it directly" do + s = "this is a test" + @buffer = Thrift::MemoryBufferTransport.new(s) + expect(@buffer.read(4)).to eq("this") + s.slice!(-4..-1) + expect(@buffer.read(@buffer.available)).to eq(" is a ") + end + + it "should always remain open" do + expect(@buffer).to be_open + @buffer.close + expect(@buffer).to be_open + end + + it "should respond to peek and available" do + @buffer.write "some data" + expect(@buffer.peek).to be_truthy + expect(@buffer.available).to eq(9) + @buffer.read(4) + expect(@buffer.peek).to be_truthy + expect(@buffer.available).to eq(5) + @buffer.read(5) + expect(@buffer.peek).to be_falsey + expect(@buffer.available).to eq(0) + end + + it "should be able to reset the buffer" do + @buffer.write "test data" + @buffer.reset_buffer("foobar") + expect(@buffer.available).to eq(6) + expect(@buffer.read(@buffer.available)).to eq("foobar") + @buffer.reset_buffer + expect(@buffer.available).to eq(0) + end + + it "should copy the given string when resetting the buffer" do + s = "this is a test" + @buffer.reset_buffer(s) + expect(@buffer.available).to eq(14) + @buffer.read(10) + expect(@buffer.available).to eq(4) + expect(s).to eq("this is a test") + end + + it "should return from read what was given in write" do + @buffer.write "test data" + expect(@buffer.read(4)).to eq("test") + expect(@buffer.read(@buffer.available)).to eq(" data") + @buffer.write "foo" + @buffer.write " bar" + expect(@buffer.read(@buffer.available)).to eq("foo bar") + end + + it "should throw an EOFError when there isn't enough data in the buffer" do + @buffer.reset_buffer("") + expect{@buffer.read(1)}.to raise_error(EOFError) + + @buffer.reset_buffer("1234") + expect{@buffer.read(5)}.to raise_error(EOFError) + end + end + + describe Thrift::IOStreamTransport do + before(:each) do + @input = double("Input", :closed? => false) + @output = double("Output", :closed? => false) + @trans = Thrift::IOStreamTransport.new(@input, @output) + end + + it "should provide a reasonable to_s" do + expect(@input).to receive(:to_s).and_return("mock_input") + expect(@output).to receive(:to_s).and_return("mock_output") + expect(@trans.to_s).to eq("iostream(input=mock_input,output=mock_output)") + end + + it "should be open as long as both input or output are open" do + expect(@trans).to be_open + allow(@input).to receive(:closed?).and_return(true) + expect(@trans).to be_open + allow(@input).to receive(:closed?).and_return(false) + allow(@output).to receive(:closed?).and_return(true) + expect(@trans).to be_open + allow(@input).to receive(:closed?).and_return(true) + expect(@trans).not_to be_open + end + + it "should pass through read/write to input/output" do + expect(@input).to receive(:read).with(17).and_return("+ read") + expect(@output).to receive(:write).with("foobar").and_return("+ write") + expect(@trans.read(17)).to eq("+ read") + expect(@trans.write("foobar")).to eq("+ write") + end + + it "should close both input and output when closed" do + expect(@input).to receive(:close) + expect(@output).to receive(:close) + @trans.close + end + end +end diff --git a/src/jaegertracing/thrift/lib/rb/spec/binary_protocol_accelerated_spec.rb b/src/jaegertracing/thrift/lib/rb/spec/binary_protocol_accelerated_spec.rb new file mode 100644 index 000000000..b2cd04bec --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/spec/binary_protocol_accelerated_spec.rb @@ -0,0 +1,46 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +require 'spec_helper' +require File.expand_path("#{File.dirname(__FILE__)}/binary_protocol_spec_shared") + +if defined? Thrift::BinaryProtocolAccelerated + + describe 'BinaryProtocolAccelerated' do + # since BinaryProtocolAccelerated should be directly equivalent to + # BinaryProtocol, we don't need any custom specs! + it_should_behave_like 'a binary protocol' + + def protocol_class + Thrift::BinaryProtocolAccelerated + end + + describe Thrift::BinaryProtocolAcceleratedFactory do + it "should create a BinaryProtocolAccelerated" do + expect(Thrift::BinaryProtocolAcceleratedFactory.new.get_protocol(double("MockTransport"))).to be_instance_of(Thrift::BinaryProtocolAccelerated) + end + + it "should provide a reasonable to_s" do + expect(Thrift::BinaryProtocolAcceleratedFactory.new.to_s).to eq("binary-accel") + end + end + end +else + puts "skipping BinaryProtocolAccelerated spec because it is not defined." +end diff --git a/src/jaegertracing/thrift/lib/rb/spec/binary_protocol_spec.rb b/src/jaegertracing/thrift/lib/rb/spec/binary_protocol_spec.rb new file mode 100644 index 000000000..065f5ce29 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/spec/binary_protocol_spec.rb @@ -0,0 +1,74 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +require 'spec_helper' +require File.expand_path("#{File.dirname(__FILE__)}/binary_protocol_spec_shared") + +describe 'BinaryProtocol' do + + it_should_behave_like 'a binary protocol' + + def protocol_class + Thrift::BinaryProtocol + end + + describe Thrift::BinaryProtocol do + + before(:each) do + @trans = Thrift::MemoryBufferTransport.new + @prot = protocol_class.new(@trans) + end + + it "should read a message header" do + @trans.write([protocol_class.const_get(:VERSION_1) | Thrift::MessageTypes::REPLY].pack('N')) + @trans.write([42].pack('N')) + expect(@prot).to receive(:read_string).and_return('testMessage') + expect(@prot.read_message_begin).to eq(['testMessage', Thrift::MessageTypes::REPLY, 42]) + end + + it "should raise an exception if the message header has the wrong version" do + expect(@prot).to receive(:read_i32).and_return(-1) + expect { @prot.read_message_begin }.to raise_error(Thrift::ProtocolException, 'Missing version identifier') do |e| + e.type == Thrift::ProtocolException::BAD_VERSION + end + end + + it "should raise an exception if the message header does not exist and strict_read is enabled" do + expect(@prot).to receive(:read_i32).and_return(42) + expect(@prot).to receive(:strict_read).and_return(true) + expect { @prot.read_message_begin }.to raise_error(Thrift::ProtocolException, 'No version identifier, old protocol client?') do |e| + e.type == Thrift::ProtocolException::BAD_VERSION + end + end + + it "should provide a reasonable to_s" do + expect(@prot.to_s).to eq("binary(memory)") + end + end + + describe Thrift::BinaryProtocolFactory do + it "should create a BinaryProtocol" do + expect(Thrift::BinaryProtocolFactory.new.get_protocol(double("MockTransport"))).to be_instance_of(Thrift::BinaryProtocol) + end + + it "should provide a reasonable to_s" do + expect(Thrift::BinaryProtocolFactory.new.to_s).to eq("binary") + end + end +end diff --git a/src/jaegertracing/thrift/lib/rb/spec/binary_protocol_spec_shared.rb b/src/jaegertracing/thrift/lib/rb/spec/binary_protocol_spec_shared.rb new file mode 100644 index 000000000..58d65f040 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/spec/binary_protocol_spec_shared.rb @@ -0,0 +1,458 @@ +# encoding: ascii-8bit +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +require 'spec_helper' + +shared_examples_for 'a binary protocol' do + before(:each) do + @trans = Thrift::MemoryBufferTransport.new + @prot = protocol_class.new(@trans) + end + + it "should define the proper VERSION_1, VERSION_MASK AND TYPE_MASK" do + expect(protocol_class.const_get(:VERSION_MASK)).to eq(0xffff0000) + expect(protocol_class.const_get(:VERSION_1)).to eq(0x80010000) + expect(protocol_class.const_get(:TYPE_MASK)).to eq(0x000000ff) + end + + it "should make strict_read readable" do + expect(@prot.strict_read).to eql(true) + end + + it "should make strict_write readable" do + expect(@prot.strict_write).to eql(true) + end + + it "should write the message header" do + @prot.write_message_begin('testMessage', Thrift::MessageTypes::CALL, 17) + expect(@trans.read(@trans.available)).to eq([protocol_class.const_get(:VERSION_1) | Thrift::MessageTypes::CALL, "testMessage".size, "testMessage", 17].pack("NNa11N")) + end + + it "should write the message header without version when writes are not strict" do + @prot = protocol_class.new(@trans, true, false) # no strict write + @prot.write_message_begin('testMessage', Thrift::MessageTypes::CALL, 17) + expect(@trans.read(@trans.available)).to eq("\000\000\000\vtestMessage\001\000\000\000\021") + end + + it "should write the message header with a version when writes are strict" do + @prot = protocol_class.new(@trans) # strict write + @prot.write_message_begin('testMessage', Thrift::MessageTypes::CALL, 17) + expect(@trans.read(@trans.available)).to eq("\200\001\000\001\000\000\000\vtestMessage\000\000\000\021") + end + + + # message footer is a noop + + it "should write the field header" do + @prot.write_field_begin('foo', Thrift::Types::DOUBLE, 3) + expect(@trans.read(@trans.available)).to eq([Thrift::Types::DOUBLE, 3].pack("cn")) + end + + # field footer is a noop + + it "should write the STOP field" do + @prot.write_field_stop + expect(@trans.read(1)).to eq("\000") + end + + it "should write the map header" do + @prot.write_map_begin(Thrift::Types::STRING, Thrift::Types::LIST, 17) + expect(@trans.read(@trans.available)).to eq([Thrift::Types::STRING, Thrift::Types::LIST, 17].pack("ccN")); + end + + # map footer is a noop + + it "should write the list header" do + @prot.write_list_begin(Thrift::Types::I16, 42) + expect(@trans.read(@trans.available)).to eq([Thrift::Types::I16, 42].pack("cN")) + end + + # list footer is a noop + + it "should write the set header" do + @prot.write_set_begin(Thrift::Types::I16, 42) + expect(@trans.read(@trans.available)).to eq([Thrift::Types::I16, 42].pack("cN")) + end + + it "should write a bool" do + @prot.write_bool(true) + @prot.write_bool(false) + expect(@trans.read(@trans.available)).to eq("\001\000") + end + + it "should treat a nil bool as false" do + @prot.write_bool(nil) + expect(@trans.read(1)).to eq("\000") + end + + it "should write a byte" do + # byte is small enough, let's check -128..127 + (-128..127).each do |i| + @prot.write_byte(i) + expect(@trans.read(1)).to eq([i].pack('c')) + end + end + + it "should clip numbers out of signed range" do + (128..255).each do |i| + @prot.write_byte(i) + expect(@trans.read(1)).to eq([i].pack('c')) + end + end + + it "errors out with a Bignum" do + expect { @prot.write_byte(2**65) }.to raise_error(RangeError) + end + + it "should error gracefully when trying to write a nil byte" do + expect { @prot.write_byte(nil) }.to raise_error + end + + it "should write an i16" do + # try a random scattering of values + # include the signed i16 minimum/maximum + [-2**15, -1024, 17, 0, -10000, 1723, 2**15-1].each do |i| + @prot.write_i16(i) + end + # and try something out of signed range, it should clip + @prot.write_i16(2**15 + 5) + + expect(@trans.read(@trans.available)).to eq("\200\000\374\000\000\021\000\000\330\360\006\273\177\377\200\005") + + # a Bignum should error + # lambda { @prot.write_i16(2**65) }.should raise_error(RangeError) + end + + it "should error gracefully when trying to write a nil i16" do + expect { @prot.write_i16(nil) }.to raise_error + end + + it "should write an i32" do + # try a random scattering of values + # include the signed i32 minimum/maximum + [-2**31, -123123, -2532, -3, 0, 2351235, 12331, 2**31-1].each do |i| + @prot.write_i32(i) + end + # try something out of signed range, it should clip + expect(@trans.read(@trans.available)).to eq("\200\000\000\000" + "\377\376\037\r" + "\377\377\366\034" + "\377\377\377\375" + "\000\000\000\000" + "\000#\340\203" + "\000\0000+" + "\177\377\377\377") + [2 ** 31 + 5, 2 ** 65 + 5].each do |i| + expect { @prot.write_i32(i) }.to raise_error(RangeError) + end + end + + it "should error gracefully when trying to write a nil i32" do + expect { @prot.write_i32(nil) }.to raise_error + end + + it "should write an i64" do + # try a random scattering of values + # try the signed i64 minimum/maximum + [-2**63, -12356123612323, -23512351, -234, 0, 1231, 2351236, 12361236213, 2**63-1].each do |i| + @prot.write_i64(i) + end + # try something out of signed range, it should clip + expect(@trans.read(@trans.available)).to eq(["\200\000\000\000\000\000\000\000", + "\377\377\364\303\035\244+]", + "\377\377\377\377\376\231:\341", + "\377\377\377\377\377\377\377\026", + "\000\000\000\000\000\000\000\000", + "\000\000\000\000\000\000\004\317", + "\000\000\000\000\000#\340\204", + "\000\000\000\002\340\311~\365", + "\177\377\377\377\377\377\377\377"].join("")) + expect { @prot.write_i64(2 ** 65 + 5) }.to raise_error(RangeError) + end + + it "should error gracefully when trying to write a nil i64" do + expect { @prot.write_i64(nil) }.to raise_error + end + + it "should write a double" do + # try a random scattering of values, including min/max + values = [Float::MIN,-1231.15325, -123123.23, -23.23515123, 0, 12351.1325, 523.23, Float::MAX] + values.each do |f| + @prot.write_double(f) + expect(@trans.read(@trans.available)).to eq([f].pack("G")) + end + end + + it "should error gracefully when trying to write a nil double" do + expect { @prot.write_double(nil) }.to raise_error + end + + if RUBY_VERSION >= '1.9' + it 'should write a string' do + str = 'abc' + @prot.write_string(str) + a = @trans.read(@trans.available) + expect(a.encoding).to eq(Encoding::BINARY) + expect(a.unpack('C*')).to eq([0x00, 0x00, 0x00, 0x03, 0x61, 0x62, 0x63]) + end + + it 'should write a string with unicode characters' do + str = "abc \u20AC \u20AD".encode('UTF-8') + @prot.write_string(str) + a = @trans.read(@trans.available) + expect(a.encoding).to eq(Encoding::BINARY) + expect(a.unpack('C*')).to eq([0x00, 0x00, 0x00, 0x0B, 0x61, 0x62, 0x63, 0x20, + 0xE2, 0x82, 0xAC, 0x20, 0xE2, 0x82, 0xAD]) + end + + it 'should write should write a string with unicode characters and transcoding' do + str = "abc \u20AC".encode('ISO-8859-15') + @prot.write_string(str) + a = @trans.read(@trans.available) + expect(a.encoding).to eq(Encoding::BINARY) + expect(a.unpack('C*')).to eq([0x00, 0x00, 0x00, 0x07, 0x61, 0x62, 0x63, 0x20, 0xE2, 0x82, 0xAC]) + end + + it 'should write a binary string' do + buffer = [0, 1, 2, 3].pack('C*') + @prot.write_binary(buffer) + a = @trans.read(@trans.available) + expect(a.encoding).to eq(Encoding::BINARY) + expect(a.unpack('C*')).to eq([0x00, 0x00, 0x00, 0x04, 0x00, 0x01, 0x02, 0x03]) + end + else + it 'should write a string' do + str = 'abc' + @prot.write_string(str) + a = @trans.read(@trans.available) + expect(a.unpack('C*')).to eq([0x00, 0x00, 0x00, 0x03, 0x61, 0x62, 0x63]) + end + + it 'should write a binary string' do + buffer = [0, 1, 2, 3].pack('C*') + @prot.write_binary(buffer) + a = @trans.read(@trans.available) + expect(a.unpack('C*')).to eq([0x00, 0x00, 0x00, 0x04, 0x00, 0x01, 0x02, 0x03]) + end + end + + it "should error gracefully when trying to write a nil string" do + expect { @prot.write_string(nil) }.to raise_error + end + + it "should write the message header without version when writes are not strict" do + @prot = protocol_class.new(@trans, true, false) # no strict write + @prot.write_message_begin('testMessage', Thrift::MessageTypes::CALL, 17) + expect(@trans.read(@trans.available)).to eq("\000\000\000\vtestMessage\001\000\000\000\021") + end + + it "should write the message header with a version when writes are strict" do + @prot = protocol_class.new(@trans) # strict write + @prot.write_message_begin('testMessage', Thrift::MessageTypes::CALL, 17) + expect(@trans.read(@trans.available)).to eq("\200\001\000\001\000\000\000\vtestMessage\000\000\000\021") + end + + # message footer is a noop + + it "should read a field header" do + @trans.write([Thrift::Types::STRING, 3].pack("cn")) + expect(@prot.read_field_begin).to eq([nil, Thrift::Types::STRING, 3]) + end + + # field footer is a noop + + it "should read a stop field" do + @trans.write([Thrift::Types::STOP].pack("c")); + expect(@prot.read_field_begin).to eq([nil, Thrift::Types::STOP, 0]) + end + + it "should read a map header" do + @trans.write([Thrift::Types::DOUBLE, Thrift::Types::I64, 42].pack("ccN")) + expect(@prot.read_map_begin).to eq([Thrift::Types::DOUBLE, Thrift::Types::I64, 42]) + end + + # map footer is a noop + + it "should read a list header" do + @trans.write([Thrift::Types::STRING, 17].pack("cN")) + expect(@prot.read_list_begin).to eq([Thrift::Types::STRING, 17]) + end + + # list footer is a noop + + it "should read a set header" do + @trans.write([Thrift::Types::STRING, 17].pack("cN")) + expect(@prot.read_set_begin).to eq([Thrift::Types::STRING, 17]) + end + + # set footer is a noop + + it "should read a bool" do + @trans.write("\001\000"); + expect(@prot.read_bool).to eq(true) + expect(@prot.read_bool).to eq(false) + end + + it "should read a byte" do + [-128, -57, -3, 0, 17, 24, 127].each do |i| + @trans.write([i].pack("c")) + expect(@prot.read_byte).to eq(i) + end + end + + it "should read an i16" do + # try a scattering of values, including min/max + [-2**15, -5237, -353, 0, 1527, 2234, 2**15-1].each do |i| + @trans.write([i].pack("n")); + expect(@prot.read_i16).to eq(i) + end + end + + it "should read an i32" do + # try a scattering of values, including min/max + [-2**31, -235125, -6236, 0, 2351, 123123, 2**31-1].each do |i| + @trans.write([i].pack("N")) + expect(@prot.read_i32).to eq(i) + end + end + + it "should read an i64" do + # try a scattering of values, including min/max + [-2**63, -123512312, -6346, 0, 32, 2346322323, 2**63-1].each do |i| + @trans.write([i >> 32, i & 0xFFFFFFFF].pack("NN")) + expect(@prot.read_i64).to eq(i) + end + end + + it "should read a double" do + # try a random scattering of values, including min/max + [Float::MIN, -231231.12351, -323.233513, 0, 123.2351235, 2351235.12351235, Float::MAX].each do |f| + @trans.write([f].pack("G")); + expect(@prot.read_double).to eq(f) + end + end + + if RUBY_VERSION >= '1.9' + it 'should read a string' do + # i32 of value 3, followed by three characters/UTF-8 bytes 'a', 'b', 'c' + buffer = [0x00, 0x00, 0x00, 0x03, 0x61, 0x62, 0x63].pack('C*') + @trans.write(buffer) + a = @prot.read_string + expect(a).to eq('abc'.encode('UTF-8')) + expect(a.encoding).to eq(Encoding::UTF_8) + end + + it 'should read a string containing unicode characters from UTF-8 encoded buffer' do + # i32 of value 3, followed by one character U+20AC made up of three bytes + buffer = [0x00, 0x00, 0x00, 0x03, 0xE2, 0x82, 0xAC].pack('C*') + @trans.write(buffer) + a = @prot.read_string + expect(a).to eq("\u20AC".encode('UTF-8')) + expect(a.encoding).to eq(Encoding::UTF_8) + end + + it 'should read a binary string' do + buffer = [0x00, 0x00, 0x00, 0x04, 0x00, 0x01, 0x02, 0x03].pack('C*') + @trans.write(buffer) + a = @prot.read_binary + expect(a).to eq([0x00, 0x01, 0x02, 0x03].pack('C*')) + expect(a.encoding).to eq(Encoding::BINARY) + end + else + it 'should read a string' do + # i32 of value 3, followed by three characters/UTF-8 bytes 'a', 'b', 'c' + buffer = [0x00, 0x00, 0x00, 0x03, 0x61, 0x62, 0x63].pack('C*') + @trans.write(buffer) + expect(@prot.read_string).to eq('abc') + end + + it 'should read a binary string' do + buffer = [0x00, 0x00, 0x00, 0x04, 0x00, 0x01, 0x02, 0x03].pack('C*') + @trans.write(buffer) + a = @prot.read_binary + expect(a).to eq([0x00, 0x01, 0x02, 0x03].pack('C*')) + end + end + + it "should perform a complete rpc with no args or return" do + srv_test( + proc {|client| client.send_voidMethod()}, + proc {|client| expect(client.recv_voidMethod).to eq(nil)} + ) + end + + it "should perform a complete rpc with a primitive return type" do + srv_test( + proc {|client| client.send_primitiveMethod()}, + proc {|client| expect(client.recv_primitiveMethod).to eq(1)} + ) + end + + it "should perform a complete rpc with a struct return type" do + srv_test( + proc {|client| client.send_structMethod()}, + proc {|client| + result = client.recv_structMethod + result.set_byte_map = nil + result.map_byte_map = nil + expect(result).to eq(Fixtures::COMPACT_PROTOCOL_TEST_STRUCT) + } + ) + end + + def get_socket_connection + server = Thrift::ServerSocket.new("localhost", 9090) + server.listen + + clientside = Thrift::Socket.new("localhost", 9090) + clientside.open + serverside = server.accept + [clientside, serverside, server] + end + + def srv_test(firstblock, secondblock) + clientside, serverside, server = get_socket_connection + + clientproto = protocol_class.new(clientside) + serverproto = protocol_class.new(serverside) + + processor = Thrift::Test::Srv::Processor.new(SrvHandler.new) + + client = Thrift::Test::Srv::Client.new(clientproto, clientproto) + + # first block + firstblock.call(client) + + processor.process(serverproto, serverproto) + + # second block + secondblock.call(client) + ensure + clientside.close + serverside.close + server.close + end + + class SrvHandler + def voidMethod() + end + + def primitiveMethod + 1 + end + + def structMethod + Fixtures::COMPACT_PROTOCOL_TEST_STRUCT + end + end +end diff --git a/src/jaegertracing/thrift/lib/rb/spec/bytes_spec.rb b/src/jaegertracing/thrift/lib/rb/spec/bytes_spec.rb new file mode 100644 index 000000000..2e8653cfc --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/spec/bytes_spec.rb @@ -0,0 +1,160 @@ +# encoding: UTF-8 +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +require 'spec_helper' + +describe Thrift::Bytes do + if RUBY_VERSION >= '1.9' + describe '.empty_byte_buffer' do + it 'should create an empty buffer' do + b = Thrift::Bytes.empty_byte_buffer + expect(b.length).to eq(0) + expect(b.encoding).to eq(Encoding::BINARY) + end + + it 'should create an empty buffer of given size' do + b = Thrift::Bytes.empty_byte_buffer 2 + expect(b.length).to eq(2) + expect(b.getbyte(0)).to eq(0) + expect(b.getbyte(1)).to eq(0) + expect(b.encoding).to eq(Encoding::BINARY) + end + end + + describe '.force_binary_encoding' do + it 'should change encoding' do + e = 'STRING'.encode('UTF-8') + expect(e.encoding).not_to eq(Encoding::BINARY) + a = Thrift::Bytes.force_binary_encoding e + expect(a.encoding).to eq(Encoding::BINARY) + end + end + + describe '.get_string_byte' do + it 'should get the byte at index' do + s = "\x41\x42" + expect(Thrift::Bytes.get_string_byte(s, 0)).to eq(0x41) + expect(Thrift::Bytes.get_string_byte(s, 1)).to eq(0x42) + end + end + + describe '.set_string_byte' do + it 'should set byte value at index' do + s = "\x41\x42" + Thrift::Bytes.set_string_byte(s, 0, 0x43) + expect(s.getbyte(0)).to eq(0x43) + expect(s).to eq('CB') + end + end + + describe '.convert_to_utf8_byte_buffer' do + it 'should convert UTF-8 String to byte buffer' do + e = "\u20AC".encode('UTF-8') # a string with euro sign character U+20AC + expect(e.length).to eq(1) + + a = Thrift::Bytes.convert_to_utf8_byte_buffer e + expect(a.encoding).to eq(Encoding::BINARY) + expect(a.length).to eq(3) + expect(a.unpack('C*')).to eq([0xE2, 0x82, 0xAC]) + end + + it 'should convert ISO-8859-15 String to UTF-8 byte buffer' do + # Assumptions + e = "\u20AC".encode('ISO-8859-15') # a string with euro sign character U+20AC, then converted to ISO-8859-15 + expect(e.length).to eq(1) + expect(e.unpack('C*')).to eq([0xA4]) # euro sign is a different code point in ISO-8859-15 + + a = Thrift::Bytes.convert_to_utf8_byte_buffer e + expect(a.encoding).to eq(Encoding::BINARY) + expect(a.length).to eq(3) + expect(a.unpack('C*')).to eq([0xE2, 0x82, 0xAC]) + end + end + + describe '.convert_to_string' do + it 'should convert UTF-8 byte buffer to a UTF-8 String' do + e = [0xE2, 0x82, 0xAC].pack("C*") + expect(e.encoding).to eq(Encoding::BINARY) + a = Thrift::Bytes.convert_to_string e + expect(a.encoding).to eq(Encoding::UTF_8) + expect(a).to eq("\u20AC") + end + end + + else # RUBY_VERSION + describe '.empty_byte_buffer' do + it 'should create an empty buffer' do + b = Thrift::Bytes.empty_byte_buffer + expect(b.length).to eq(0) + end + + it 'should create an empty buffer of given size' do + b = Thrift::Bytes.empty_byte_buffer 2 + expect(b.length).to eq(2) + expect(b[0]).to eq(0) + expect(b[1]).to eq(0) + end + end + + describe '.force_binary_encoding' do + it 'should be a no-op' do + e = 'STRING' + a = Thrift::Bytes.force_binary_encoding e + expect(a).to eq(e) + expect(a).to be(e) + end + end + + describe '.get_string_byte' do + it 'should get the byte at index' do + s = "\x41\x42" + expect(Thrift::Bytes.get_string_byte(s, 0)).to eq(0x41) + expect(Thrift::Bytes.get_string_byte(s, 1)).to eq(0x42) + end + end + + describe '.set_string_byte' do + it 'should set byte value at index' do + s = "\x41\x42" + Thrift::Bytes.set_string_byte(s, 0, 0x43) + expect(s[0]).to eq(0x43) + expect(s).to eq('CB') + end + end + + describe '.convert_to_utf8_byte_buffer' do + it 'should be a no-op' do + e = 'STRING' + a = Thrift::Bytes.convert_to_utf8_byte_buffer e + expect(a).to eq(e) + expect(a).to be(e) + end + end + + describe '.convert_to_string' do + it 'should be a no-op' do + e = 'STRING' + a = Thrift::Bytes.convert_to_string e + expect(a).to eq(e) + expect(a).to be(e) + end + end + end +end diff --git a/src/jaegertracing/thrift/lib/rb/spec/client_spec.rb b/src/jaegertracing/thrift/lib/rb/spec/client_spec.rb new file mode 100644 index 000000000..d5d4ceedb --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/spec/client_spec.rb @@ -0,0 +1,98 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +require 'spec_helper' + +describe 'Client' do + + class ClientSpec + include Thrift::Client + end + + before(:each) do + @prot = double("MockProtocol") + @client = ClientSpec.new(@prot) + end + + describe Thrift::Client do + it "should re-use iprot for oprot if not otherwise specified" do + expect(@client.instance_variable_get(:'@iprot')).to eql(@prot) + expect(@client.instance_variable_get(:'@oprot')).to eql(@prot) + end + + it "should send a test message" do + expect(@prot).to receive(:write_message_begin).with('testMessage', Thrift::MessageTypes::CALL, 0) + mock_args = double('#<TestMessage_args:mock>') + expect(mock_args).to receive(:foo=).with('foo') + expect(mock_args).to receive(:bar=).with(42) + expect(mock_args).to receive(:write).with(@prot) + expect(@prot).to receive(:write_message_end) + expect(@prot).to receive(:trans) do + double('trans').tap do |trans| + expect(trans).to receive(:flush) + end + end + klass = double("TestMessage_args", :new => mock_args) + @client.send_message('testMessage', klass, :foo => 'foo', :bar => 42) + end + + it "should increment the sequence id when sending messages" do + pending "it seems sequence ids are completely ignored right now" + @prot.expect(:write_message_begin).with('testMessage', Thrift::MessageTypes::CALL, 0).ordered + @prot.expect(:write_message_begin).with('testMessage2', Thrift::MessageTypes::CALL, 1).ordered + @prot.expect(:write_message_begin).with('testMessage3', Thrift::MessageTypes::CALL, 2).ordered + @prot.stub!(:write_message_end) + @prot.stub!(:trans).and_return double("trans").as_null_object + @client.send_message('testMessage', double("args class").as_null_object) + @client.send_message('testMessage2', double("args class").as_null_object) + @client.send_message('testMessage3', double("args class").as_null_object) + end + + it "should receive a test message" do + expect(@prot).to receive(:read_message_begin).and_return [nil, Thrift::MessageTypes::CALL, 0] + expect(@prot).to receive(:read_message_end) + mock_klass = double("#<MockClass:mock>") + expect(mock_klass).to receive(:read).with(@prot) + @client.receive_message(double("MockClass", :new => mock_klass)) + end + + it "should handle received exceptions" do + expect(@prot).to receive(:read_message_begin).and_return [nil, Thrift::MessageTypes::EXCEPTION, 0] + expect(@prot).to receive(:read_message_end) + expect(Thrift::ApplicationException).to receive(:new) do + StandardError.new.tap do |mock_exc| + expect(mock_exc).to receive(:read).with(@prot) + end + end + expect { @client.receive_message(nil) }.to raise_error(StandardError) + end + + it "should close the transport if an error occurs while sending a message" do + allow(@prot).to receive(:write_message_begin) + expect(@prot).not_to receive(:write_message_end) + mock_args = double("#<TestMessage_args:mock>") + expect(mock_args).to receive(:write).with(@prot).and_raise(StandardError) + trans = double("MockTransport") + allow(@prot).to receive(:trans).and_return(trans) + expect(trans).to receive(:close) + klass = double("TestMessage_args", :new => mock_args) + expect { @client.send_message("testMessage", klass) }.to raise_error(StandardError) + end + end +end diff --git a/src/jaegertracing/thrift/lib/rb/spec/compact_protocol_spec.rb b/src/jaegertracing/thrift/lib/rb/spec/compact_protocol_spec.rb new file mode 100644 index 000000000..513dd69cf --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/spec/compact_protocol_spec.rb @@ -0,0 +1,158 @@ +# encoding: UTF-8 +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +require 'spec_helper' + +describe Thrift::CompactProtocol do + TESTS = { + :byte => (-127..127).to_a, + :i16 => (0..14).map {|shift| [1 << shift, -(1 << shift)]}.flatten.sort, + :i32 => (0..30).map {|shift| [1 << shift, -(1 << shift)]}.flatten.sort, + :i64 => (0..62).map {|shift| [1 << shift, -(1 << shift)]}.flatten.sort, + :string => ["", "1", "short", "fourteen123456", "fifteen12345678", "unicode characters: \u20AC \u20AD", "1" * 127, "1" * 3000], + :binary => ["", "\001", "\001" * 5, "\001" * 14, "\001" * 15, "\001" * 127, "\001" * 3000], + :double => [0.0, 1.0, -1.0, 1.1, -1.1, 10000000.1, 1.0/0.0, -1.0/0.0], + :bool => [true, false] + } + + it "should encode and decode naked primitives correctly" do + TESTS.each_pair do |primitive_type, test_values| + test_values.each do |value| + # puts "testing #{value}" if primitive_type == :i64 + trans = Thrift::MemoryBufferTransport.new + proto = Thrift::CompactProtocol.new(trans) + + proto.send(writer(primitive_type), value) + # puts "buf: #{trans.inspect_buffer}" if primitive_type == :i64 + read_back = proto.send(reader(primitive_type)) + expect(read_back).to eq(value) + end + end + end + + it "should encode and decode primitives in fields correctly" do + TESTS.each_pair do |primitive_type, test_values| + final_primitive_type = primitive_type == :binary ? :string : primitive_type + thrift_type = Thrift::Types.const_get(final_primitive_type.to_s.upcase) + # puts primitive_type + test_values.each do |value| + trans = Thrift::MemoryBufferTransport.new + proto = Thrift::CompactProtocol.new(trans) + + proto.write_field_begin(nil, thrift_type, 15) + proto.send(writer(primitive_type), value) + proto.write_field_end + + proto = Thrift::CompactProtocol.new(trans) + name, type, id = proto.read_field_begin + expect(type).to eq(thrift_type) + expect(id).to eq(15) + read_back = proto.send(reader(primitive_type)) + expect(read_back).to eq(value) + proto.read_field_end + end + end + end + + it "should encode and decode a monster struct correctly" do + trans = Thrift::MemoryBufferTransport.new + proto = Thrift::CompactProtocol.new(trans) + + struct = Thrift::Test::CompactProtoTestStruct.new + # sets and maps don't hash well... not sure what to do here. + struct.write(proto) + + struct2 = Thrift::Test::CompactProtoTestStruct.new + struct2.read(proto) + expect(struct2).to eq(struct) + end + + it "should make method calls correctly" do + client_out_trans = Thrift::MemoryBufferTransport.new + client_out_proto = Thrift::CompactProtocol.new(client_out_trans) + + client_in_trans = Thrift::MemoryBufferTransport.new + client_in_proto = Thrift::CompactProtocol.new(client_in_trans) + + processor = Thrift::Test::Srv::Processor.new(JankyHandler.new) + + client = Thrift::Test::Srv::Client.new(client_in_proto, client_out_proto) + client.send_Janky(1) + # puts client_out_trans.inspect_buffer + processor.process(client_out_proto, client_in_proto) + expect(client.recv_Janky).to eq(2) + end + + it "should deal with fields following fields that have non-delta ids" do + brcp = Thrift::Test::BreaksRubyCompactProtocol.new( + :field1 => "blah", + :field2 => Thrift::Test::BigFieldIdStruct.new( + :field1 => "string1", + :field2 => "string2"), + :field3 => 3) + ser = Thrift::Serializer.new(Thrift::CompactProtocolFactory.new) + bytes = ser.serialize(brcp) + + deser = Thrift::Deserializer.new(Thrift::CompactProtocolFactory.new) + brcp2 = Thrift::Test::BreaksRubyCompactProtocol.new + deser.deserialize(brcp2, bytes) + expect(brcp2).to eq(brcp) + end + + it "should deserialize an empty map to an empty hash" do + struct = Thrift::Test::SingleMapTestStruct.new(:i32_map => {}) + ser = Thrift::Serializer.new(Thrift::CompactProtocolFactory.new) + bytes = ser.serialize(struct) + + deser = Thrift::Deserializer.new(Thrift::CompactProtocolFactory.new) + struct2 = Thrift::Test::SingleMapTestStruct.new + deser.deserialize(struct2, bytes) + expect(struct).to eq(struct2) + end + + it "should provide a reasonable to_s" do + trans = Thrift::MemoryBufferTransport.new + expect(Thrift::CompactProtocol.new(trans).to_s).to eq("compact(memory)") + end + + class JankyHandler + def Janky(i32arg) + i32arg * 2 + end + end + + def writer(sym) + "write_#{sym.to_s}" + end + + def reader(sym) + "read_#{sym.to_s}" + end +end + +describe Thrift::CompactProtocolFactory do + it "should create a CompactProtocol" do + expect(Thrift::CompactProtocolFactory.new.get_protocol(double("MockTransport"))).to be_instance_of(Thrift::CompactProtocol) + end + + it "should provide a reasonable to_s" do + expect(Thrift::CompactProtocolFactory.new.to_s).to eq("compact") + end +end diff --git a/src/jaegertracing/thrift/lib/rb/spec/exception_spec.rb b/src/jaegertracing/thrift/lib/rb/spec/exception_spec.rb new file mode 100644 index 000000000..379ae6980 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/spec/exception_spec.rb @@ -0,0 +1,141 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +require 'spec_helper' + +describe 'Exception' do + + describe Thrift::Exception do + it "should have an accessible message" do + e = Thrift::Exception.new("test message") + expect(e.message).to eq("test message") + end + end + + describe Thrift::ApplicationException do + it "should inherit from Thrift::Exception" do + expect(Thrift::ApplicationException.superclass).to eq(Thrift::Exception) + end + + it "should have an accessible type and message" do + e = Thrift::ApplicationException.new + expect(e.type).to eq(Thrift::ApplicationException::UNKNOWN) + expect(e.message).to be_nil + e = Thrift::ApplicationException.new(Thrift::ApplicationException::UNKNOWN_METHOD, "test message") + expect(e.type).to eq(Thrift::ApplicationException::UNKNOWN_METHOD) + expect(e.message).to eq("test message") + end + + it "should read a struct off of a protocol" do + prot = double("MockProtocol") + expect(prot).to receive(:read_struct_begin).ordered + expect(prot).to receive(:read_field_begin).exactly(3).times.and_return( + ["message", Thrift::Types::STRING, 1], + ["type", Thrift::Types::I32, 2], + [nil, Thrift::Types::STOP, 0] + ) + expect(prot).to receive(:read_string).ordered.and_return "test message" + expect(prot).to receive(:read_i32).ordered.and_return Thrift::ApplicationException::BAD_SEQUENCE_ID + expect(prot).to receive(:read_field_end).exactly(2).times + expect(prot).to receive(:read_struct_end).ordered + + e = Thrift::ApplicationException.new + e.read(prot) + expect(e.message).to eq("test message") + expect(e.type).to eq(Thrift::ApplicationException::BAD_SEQUENCE_ID) + end + + it "should skip bad fields when reading a struct" do + prot = double("MockProtocol") + expect(prot).to receive(:read_struct_begin).ordered + expect(prot).to receive(:read_field_begin).exactly(5).times.and_return( + ["type", Thrift::Types::I32, 2], + ["type", Thrift::Types::STRING, 2], + ["message", Thrift::Types::MAP, 1], + ["message", Thrift::Types::STRING, 3], + [nil, Thrift::Types::STOP, 0] + ) + expect(prot).to receive(:read_i32).and_return Thrift::ApplicationException::INVALID_MESSAGE_TYPE + expect(prot).to receive(:skip).with(Thrift::Types::STRING).twice + expect(prot).to receive(:skip).with(Thrift::Types::MAP) + expect(prot).to receive(:read_field_end).exactly(4).times + expect(prot).to receive(:read_struct_end).ordered + + e = Thrift::ApplicationException.new + e.read(prot) + expect(e.message).to be_nil + expect(e.type).to eq(Thrift::ApplicationException::INVALID_MESSAGE_TYPE) + end + + it "should write a Thrift::ApplicationException struct to the oprot" do + prot = double("MockProtocol") + expect(prot).to receive(:write_struct_begin).with("Thrift::ApplicationException").ordered + expect(prot).to receive(:write_field_begin).with("message", Thrift::Types::STRING, 1).ordered + expect(prot).to receive(:write_string).with("test message").ordered + expect(prot).to receive(:write_field_begin).with("type", Thrift::Types::I32, 2).ordered + expect(prot).to receive(:write_i32).with(Thrift::ApplicationException::UNKNOWN_METHOD).ordered + expect(prot).to receive(:write_field_end).twice + expect(prot).to receive(:write_field_stop).ordered + expect(prot).to receive(:write_struct_end).ordered + + e = Thrift::ApplicationException.new(Thrift::ApplicationException::UNKNOWN_METHOD, "test message") + e.write(prot) + end + + it "should skip nil fields when writing to the oprot" do + prot = double("MockProtocol") + expect(prot).to receive(:write_struct_begin).with("Thrift::ApplicationException").ordered + expect(prot).to receive(:write_field_begin).with("message", Thrift::Types::STRING, 1).ordered + expect(prot).to receive(:write_string).with("test message").ordered + expect(prot).to receive(:write_field_end).ordered + expect(prot).to receive(:write_field_stop).ordered + expect(prot).to receive(:write_struct_end).ordered + + e = Thrift::ApplicationException.new(nil, "test message") + e.write(prot) + + prot = double("MockProtocol") + expect(prot).to receive(:write_struct_begin).with("Thrift::ApplicationException").ordered + expect(prot).to receive(:write_field_begin).with("type", Thrift::Types::I32, 2).ordered + expect(prot).to receive(:write_i32).with(Thrift::ApplicationException::BAD_SEQUENCE_ID).ordered + expect(prot).to receive(:write_field_end).ordered + expect(prot).to receive(:write_field_stop).ordered + expect(prot).to receive(:write_struct_end).ordered + + e = Thrift::ApplicationException.new(Thrift::ApplicationException::BAD_SEQUENCE_ID) + e.write(prot) + + prot = double("MockProtocol") + expect(prot).to receive(:write_struct_begin).with("Thrift::ApplicationException").ordered + expect(prot).to receive(:write_field_stop).ordered + expect(prot).to receive(:write_struct_end).ordered + + e = Thrift::ApplicationException.new(nil) + e.write(prot) + end + end + + describe Thrift::ProtocolException do + it "should have an accessible type" do + prot = Thrift::ProtocolException.new(Thrift::ProtocolException::SIZE_LIMIT, "message") + expect(prot.type).to eq(Thrift::ProtocolException::SIZE_LIMIT) + expect(prot.message).to eq("message") + end + end +end diff --git a/src/jaegertracing/thrift/lib/rb/spec/flat_spec.rb b/src/jaegertracing/thrift/lib/rb/spec/flat_spec.rb new file mode 100644 index 000000000..893056c10 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/spec/flat_spec.rb @@ -0,0 +1,62 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +require 'spec_helper' + +describe 'generation' do + before do + require 'namespaced_nonblocking_service' + end + + it "did not generate the wrong files" do + prefix = File.expand_path("../gen-rb/flat", __FILE__) + ["namespaced_spec_namespace/namespaced_nonblocking_service.rb", + "namespaced_spec_namespace/thrift_namespaced_spec_constants.rb", + "namespaced_spec_namespace/thrift_namespaced_spec_types.rb", + "other_namespace/referenced_constants.rb", + "other_namespace/referenced_types.rb" + ].each do |name| + expect(File.exist?(File.join(prefix, name))).not_to be_truthy + end + end + + it "generated the right files" do + prefix = File.expand_path("../gen-rb/flat", __FILE__) + ["namespaced_nonblocking_service.rb", + "thrift_namespaced_spec_constants.rb", + "thrift_namespaced_spec_types.rb", + "referenced_constants.rb", + "referenced_types.rb" + ].each do |name| + expect(File.exist?(File.join(prefix, name))).to be_truthy + end + end + + it "has a service class in the right place" do + expect(defined?(NamespacedSpecNamespace::NamespacedNonblockingService)).to be_truthy + end + + it "has a struct in the right place" do + expect(defined?(NamespacedSpecNamespace::Hello)).to be_truthy + end + + it "required an included file" do + expect(defined?(OtherNamespace::SomeEnum)).to be_truthy + end +end diff --git a/src/jaegertracing/thrift/lib/rb/spec/http_client_spec.rb b/src/jaegertracing/thrift/lib/rb/spec/http_client_spec.rb new file mode 100644 index 000000000..df472ab33 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/spec/http_client_spec.rb @@ -0,0 +1,139 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +require 'spec_helper' + +describe 'Thrift::HTTPClientTransport' do + + describe Thrift::HTTPClientTransport do + before(:each) do + @client = Thrift::HTTPClientTransport.new("http://my.domain.com/path/to/service?param=value") + end + + it "should provide a reasonable to_s" do + @client.to_s == "http://my.domain.com/path/to/service?param=value" + end + + it "should always be open" do + expect(@client).to be_open + @client.close + expect(@client).to be_open + end + + it "should post via HTTP and return the results" do + @client.write "a test" + @client.write " frame" + expect(Net::HTTP).to receive(:new).with("my.domain.com", 80) do + double("Net::HTTP").tap do |http| + expect(http).to receive(:use_ssl=).with(false) + expect(http).to receive(:post).with("/path/to/service?param=value", "a test frame", {"Content-Type"=>"application/x-thrift"}) do + double("Net::HTTPOK").tap do |response| + expect(response).to receive(:body).and_return "data" + end + end + end + end + @client.flush + expect(@client.read(10)).to eq("data") + end + + it "should send custom headers if defined" do + @client.write "test" + custom_headers = {"Cookie" => "Foo"} + headers = {"Content-Type"=>"application/x-thrift"}.merge(custom_headers) + + @client.add_headers(custom_headers) + expect(Net::HTTP).to receive(:new).with("my.domain.com", 80) do + double("Net::HTTP").tap do |http| + expect(http).to receive(:use_ssl=).with(false) + expect(http).to receive(:post).with("/path/to/service?param=value", "test", headers) do + double("Net::HTTPOK").tap do |response| + expect(response).to receive(:body).and_return "data" + end + end + end + end + @client.flush + end + + it 'should reset the outbuf on HTTP failures' do + @client.write "test" + + expect(Net::HTTP).to receive(:new).with("my.domain.com", 80) do + double("Net::HTTP").tap do |http| + expect(http).to receive(:use_ssl=).with(false) + expect(http).to receive(:post).with("/path/to/service?param=value", "test", {"Content-Type"=>"application/x-thrift"}) { raise Net::ReadTimeout } + end + end + + @client.flush rescue + expect(@client.instance_variable_get(:@outbuf)).to eq(Thrift::Bytes.empty_byte_buffer) + end + + end + + describe 'ssl enabled' do + before(:each) do + @service_path = "/path/to/service?param=value" + @server_uri = "https://my.domain.com" + end + + it "should use SSL for https" do + client = Thrift::HTTPClientTransport.new("#{@server_uri}#{@service_path}") + + client.write "test" + + expect(Net::HTTP).to receive(:new).with("my.domain.com", 443) do + double("Net::HTTP").tap do |http| + expect(http).to receive(:use_ssl=).with(true) + expect(http).to receive(:verify_mode=).with(OpenSSL::SSL::VERIFY_PEER) + expect(http).to receive(:post).with(@service_path, "test", + "Content-Type" => "application/x-thrift") do + double("Net::HTTPOK").tap do |response| + expect(response).to receive(:body).and_return "data" + end + end + end + end + client.flush + expect(client.read(4)).to eq("data") + end + + it "should set SSL verify mode when specified" do + client = Thrift::HTTPClientTransport.new("#{@server_uri}#{@service_path}", + :ssl_verify_mode => OpenSSL::SSL::VERIFY_NONE) + + client.write "test" + expect(Net::HTTP).to receive(:new).with("my.domain.com", 443) do + double("Net::HTTP").tap do |http| + expect(http).to receive(:use_ssl=).with(true) + expect(http).to receive(:verify_mode=).with(OpenSSL::SSL::VERIFY_NONE) + expect(http).to receive(:post).with(@service_path, "test", + "Content-Type" => "application/x-thrift") do + double("Net::HTTPOK").tap do |response| + expect(response).to receive(:body).and_return "data" + end + end + end + end + client.flush + expect(client.read(4)).to eq("data") + end + end +end diff --git a/src/jaegertracing/thrift/lib/rb/spec/json_protocol_spec.rb b/src/jaegertracing/thrift/lib/rb/spec/json_protocol_spec.rb new file mode 100644 index 000000000..fe1af7bb2 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/spec/json_protocol_spec.rb @@ -0,0 +1,552 @@ +# encoding: UTF-8 +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +require 'spec_helper' + +describe 'JsonProtocol' do + + describe Thrift::JsonProtocol do + before(:each) do + @trans = Thrift::MemoryBufferTransport.new + @prot = Thrift::JsonProtocol.new(@trans) + end + + it "should write json escaped char" do + @prot.write_json_escape_char("\n") + expect(@trans.read(@trans.available)).to eq('\u000a') + + @prot.write_json_escape_char(" ") + expect(@trans.read(@trans.available)).to eq('\u0020') + end + + it "should write json char" do + @prot.write_json_char("\n") + expect(@trans.read(@trans.available)).to eq('\\n') + + @prot.write_json_char(" ") + expect(@trans.read(@trans.available)).to eq(' ') + + @prot.write_json_char("\\") + expect(@trans.read(@trans.available)).to eq("\\\\") + + @prot.write_json_char("@") + expect(@trans.read(@trans.available)).to eq('@') + end + + it "should write json string" do + @prot.write_json_string("this is a \\ json\nstring") + expect(@trans.read(@trans.available)).to eq("\"this is a \\\\ json\\nstring\"") + end + + it "should write json base64" do + @prot.write_json_base64("this is a base64 string") + expect(@trans.read(@trans.available)).to eq("\"dGhpcyBpcyBhIGJhc2U2NCBzdHJpbmc=\"") + end + + it "should write json integer" do + @prot.write_json_integer(45) + expect(@trans.read(@trans.available)).to eq("45") + + @prot.write_json_integer(33000) + expect(@trans.read(@trans.available)).to eq("33000") + + @prot.write_json_integer(3000000000) + expect(@trans.read(@trans.available)).to eq("3000000000") + + @prot.write_json_integer(6000000000) + expect(@trans.read(@trans.available)).to eq("6000000000") + end + + it "should write json double" do + @prot.write_json_double(12.3) + expect(@trans.read(@trans.available)).to eq("12.3") + + @prot.write_json_double(-3.21) + expect(@trans.read(@trans.available)).to eq("-3.21") + + @prot.write_json_double(((+1.0/0.0)/(+1.0/0.0))) + expect(@trans.read(@trans.available)).to eq("\"NaN\"") + + @prot.write_json_double((+1.0/0.0)) + expect(@trans.read(@trans.available)).to eq("\"Infinity\"") + + @prot.write_json_double((-1.0/0.0)) + expect(@trans.read(@trans.available)).to eq("\"-Infinity\"") + end + + it "should write json object start" do + @prot.write_json_object_start + expect(@trans.read(@trans.available)).to eq("{") + end + + it "should write json object end" do + @prot.write_json_object_end + expect(@trans.read(@trans.available)).to eq("}") + end + + it "should write json array start" do + @prot.write_json_array_start + expect(@trans.read(@trans.available)).to eq("[") + end + + it "should write json array end" do + @prot.write_json_array_end + expect(@trans.read(@trans.available)).to eq("]") + end + + it "should write message begin" do + @prot.write_message_begin("name", 12, 32) + expect(@trans.read(@trans.available)).to eq("[1,\"name\",12,32") + end + + it "should write message end" do + @prot.write_message_end + expect(@trans.read(@trans.available)).to eq("]") + end + + it "should write struct begin" do + @prot.write_struct_begin("name") + expect(@trans.read(@trans.available)).to eq("{") + end + + it "should write struct end" do + @prot.write_struct_end + expect(@trans.read(@trans.available)).to eq("}") + end + + it "should write field begin" do + @prot.write_field_begin("name", Thrift::Types::STRUCT, 32) + expect(@trans.read(@trans.available)).to eq("32{\"rec\"") + end + + it "should write field end" do + @prot.write_field_end + expect(@trans.read(@trans.available)).to eq("}") + end + + it "should write field stop" do + @prot.write_field_stop + expect(@trans.read(@trans.available)).to eq("") + end + + it "should write map begin" do + @prot.write_map_begin(Thrift::Types::STRUCT, Thrift::Types::LIST, 32) + expect(@trans.read(@trans.available)).to eq("[\"rec\",\"lst\",32,{") + end + + it "should write map end" do + @prot.write_map_end + expect(@trans.read(@trans.available)).to eq("}]") + end + + it "should write list begin" do + @prot.write_list_begin(Thrift::Types::STRUCT, 32) + expect(@trans.read(@trans.available)).to eq("[\"rec\",32") + end + + it "should write list end" do + @prot.write_list_end + expect(@trans.read(@trans.available)).to eq("]") + end + + it "should write set begin" do + @prot.write_set_begin(Thrift::Types::STRUCT, 32) + expect(@trans.read(@trans.available)).to eq("[\"rec\",32") + end + + it "should write set end" do + @prot.write_set_end + expect(@trans.read(@trans.available)).to eq("]") + end + + it "should write bool" do + @prot.write_bool(true) + expect(@trans.read(@trans.available)).to eq("1") + + @prot.write_bool(false) + expect(@trans.read(@trans.available)).to eq("0") + end + + it "should write byte" do + @prot.write_byte(100) + expect(@trans.read(@trans.available)).to eq("100") + end + + it "should write i16" do + @prot.write_i16(1000) + expect(@trans.read(@trans.available)).to eq("1000") + end + + it "should write i32" do + @prot.write_i32(3000000000) + expect(@trans.read(@trans.available)).to eq("3000000000") + end + + it "should write i64" do + @prot.write_i64(6000000000) + expect(@trans.read(@trans.available)).to eq("6000000000") + end + + it "should write double" do + @prot.write_double(1.23) + expect(@trans.read(@trans.available)).to eq("1.23") + + @prot.write_double(-32.1) + expect(@trans.read(@trans.available)).to eq("-32.1") + + @prot.write_double(((+1.0/0.0)/(+1.0/0.0))) + expect(@trans.read(@trans.available)).to eq("\"NaN\"") + + @prot.write_double((+1.0/0.0)) + expect(@trans.read(@trans.available)).to eq("\"Infinity\"") + + @prot.write_double((-1.0/0.0)) + expect(@trans.read(@trans.available)).to eq("\"-Infinity\"") + end + + if RUBY_VERSION >= '1.9' + it 'should write string' do + @prot.write_string('this is a test string') + a = @trans.read(@trans.available) + expect(a).to eq('"this is a test string"'.force_encoding(Encoding::BINARY)) + expect(a.encoding).to eq(Encoding::BINARY) + end + + it 'should write string with unicode characters' do + @prot.write_string("this is a test string with unicode characters: \u20AC \u20AD") + a = @trans.read(@trans.available) + expect(a).to eq("\"this is a test string with unicode characters: \u20AC \u20AD\"".force_encoding(Encoding::BINARY)) + expect(a.encoding).to eq(Encoding::BINARY) + end + else + it 'should write string' do + @prot.write_string('this is a test string') + expect(@trans.read(@trans.available)).to eq('"this is a test string"') + end + end + + it "should write binary" do + @prot.write_binary("this is a base64 string") + expect(@trans.read(@trans.available)).to eq("\"dGhpcyBpcyBhIGJhc2U2NCBzdHJpbmc=\"") + end + + it "should write long binary" do + @prot.write_binary((0...256).to_a.pack('C*')) + expect(@trans.read(@trans.available)).to eq("\"AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w==\"") + end + + it "should get type name for type id" do + expect {@prot.get_type_name_for_type_id(Thrift::Types::STOP)}.to raise_error(NotImplementedError) + expect {@prot.get_type_name_for_type_id(Thrift::Types::VOID)}.to raise_error(NotImplementedError) + expect(@prot.get_type_name_for_type_id(Thrift::Types::BOOL)).to eq("tf") + expect(@prot.get_type_name_for_type_id(Thrift::Types::BYTE)).to eq("i8") + expect(@prot.get_type_name_for_type_id(Thrift::Types::DOUBLE)).to eq("dbl") + expect(@prot.get_type_name_for_type_id(Thrift::Types::I16)).to eq("i16") + expect(@prot.get_type_name_for_type_id(Thrift::Types::I32)).to eq("i32") + expect(@prot.get_type_name_for_type_id(Thrift::Types::I64)).to eq("i64") + expect(@prot.get_type_name_for_type_id(Thrift::Types::STRING)).to eq("str") + expect(@prot.get_type_name_for_type_id(Thrift::Types::STRUCT)).to eq("rec") + expect(@prot.get_type_name_for_type_id(Thrift::Types::MAP)).to eq("map") + expect(@prot.get_type_name_for_type_id(Thrift::Types::SET)).to eq("set") + expect(@prot.get_type_name_for_type_id(Thrift::Types::LIST)).to eq("lst") + end + + it "should get type id for type name" do + expect {@prot.get_type_id_for_type_name("pp")}.to raise_error(NotImplementedError) + expect(@prot.get_type_id_for_type_name("tf")).to eq(Thrift::Types::BOOL) + expect(@prot.get_type_id_for_type_name("i8")).to eq(Thrift::Types::BYTE) + expect(@prot.get_type_id_for_type_name("dbl")).to eq(Thrift::Types::DOUBLE) + expect(@prot.get_type_id_for_type_name("i16")).to eq(Thrift::Types::I16) + expect(@prot.get_type_id_for_type_name("i32")).to eq(Thrift::Types::I32) + expect(@prot.get_type_id_for_type_name("i64")).to eq(Thrift::Types::I64) + expect(@prot.get_type_id_for_type_name("str")).to eq(Thrift::Types::STRING) + expect(@prot.get_type_id_for_type_name("rec")).to eq(Thrift::Types::STRUCT) + expect(@prot.get_type_id_for_type_name("map")).to eq(Thrift::Types::MAP) + expect(@prot.get_type_id_for_type_name("set")).to eq(Thrift::Types::SET) + expect(@prot.get_type_id_for_type_name("lst")).to eq(Thrift::Types::LIST) + end + + it "should read json syntax char" do + @trans.write('F') + expect {@prot.read_json_syntax_char('G')}.to raise_error(Thrift::ProtocolException) + @trans.write('H') + @prot.read_json_syntax_char('H') + end + + it "should read json escape char" do + @trans.write('0054') + expect(@prot.read_json_escape_char).to eq('T') + + @trans.write("\"\\\"\"") + expect(@prot.read_json_string(false)).to eq("\"") + + @trans.write("\"\\\\\"") + expect(@prot.read_json_string(false)).to eq("\\") + + @trans.write("\"\\/\"") + expect(@prot.read_json_string(false)).to eq("\/") + + @trans.write("\"\\b\"") + expect(@prot.read_json_string(false)).to eq("\b") + + @trans.write("\"\\f\"") + expect(@prot.read_json_string(false)).to eq("\f") + + @trans.write("\"\\n\"") + expect(@prot.read_json_string(false)).to eq("\n") + + @trans.write("\"\\r\"") + expect(@prot.read_json_string(false)).to eq("\r") + + @trans.write("\"\\t\"") + expect(@prot.read_json_string(false)).to eq("\t") + end + + it "should read json string" do + @trans.write("\"\\P") + expect {@prot.read_json_string(false)}.to raise_error(Thrift::ProtocolException) + + @trans.write("\"this is a test string\"") + expect(@prot.read_json_string).to eq("this is a test string") + end + + it "should read json base64" do + @trans.write("\"dGhpcyBpcyBhIHRlc3Qgc3RyaW5n\"") + expect(@prot.read_json_base64).to eq("this is a test string") + end + + it "should is json numeric" do + expect(@prot.is_json_numeric("A")).to eq(false) + expect(@prot.is_json_numeric("+")).to eq(true) + expect(@prot.is_json_numeric("-")).to eq(true) + expect(@prot.is_json_numeric(".")).to eq(true) + expect(@prot.is_json_numeric("0")).to eq(true) + expect(@prot.is_json_numeric("1")).to eq(true) + expect(@prot.is_json_numeric("2")).to eq(true) + expect(@prot.is_json_numeric("3")).to eq(true) + expect(@prot.is_json_numeric("4")).to eq(true) + expect(@prot.is_json_numeric("5")).to eq(true) + expect(@prot.is_json_numeric("6")).to eq(true) + expect(@prot.is_json_numeric("7")).to eq(true) + expect(@prot.is_json_numeric("8")).to eq(true) + expect(@prot.is_json_numeric("9")).to eq(true) + expect(@prot.is_json_numeric("E")).to eq(true) + expect(@prot.is_json_numeric("e")).to eq(true) + end + + it "should read json numeric chars" do + @trans.write("1.453E45T") + expect(@prot.read_json_numeric_chars).to eq("1.453E45") + end + + it "should read json integer" do + @trans.write("1.45\"\"") + expect {@prot.read_json_integer}.to raise_error(Thrift::ProtocolException) + @prot.read_string + + @trans.write("1453T") + expect(@prot.read_json_integer).to eq(1453) + end + + it "should read json double" do + @trans.write("1.45e3e01\"\"") + expect {@prot.read_json_double}.to raise_error(Thrift::ProtocolException) + @prot.read_string + + @trans.write("\"1.453e01\"") + expect {@prot.read_json_double}.to raise_error(Thrift::ProtocolException) + + @trans.write("1.453e01\"\"") + expect(@prot.read_json_double).to eq(14.53) + @prot.read_string + + @trans.write("\"NaN\"") + expect(@prot.read_json_double.nan?).to eq(true) + + @trans.write("\"Infinity\"") + expect(@prot.read_json_double).to eq(+1.0/0.0) + + @trans.write("\"-Infinity\"") + expect(@prot.read_json_double).to eq(-1.0/0.0) + end + + it "should read json object start" do + @trans.write("{") + expect(@prot.read_json_object_start).to eq(nil) + end + + it "should read json object end" do + @trans.write("}") + expect(@prot.read_json_object_end).to eq(nil) + end + + it "should read json array start" do + @trans.write("[") + expect(@prot.read_json_array_start).to eq(nil) + end + + it "should read json array end" do + @trans.write("]") + expect(@prot.read_json_array_end).to eq(nil) + end + + it "should read_message_begin" do + @trans.write("[2,") + expect {@prot.read_message_begin}.to raise_error(Thrift::ProtocolException) + + @trans.write("[1,\"name\",12,32\"\"") + expect(@prot.read_message_begin).to eq(["name", 12, 32]) + end + + it "should read message end" do + @trans.write("]") + expect(@prot.read_message_end).to eq(nil) + end + + it "should read struct begin" do + @trans.write("{") + expect(@prot.read_struct_begin).to eq(nil) + end + + it "should read struct end" do + @trans.write("}") + expect(@prot.read_struct_end).to eq(nil) + end + + it "should read field begin" do + @trans.write("1{\"rec\"") + expect(@prot.read_field_begin).to eq([nil, 12, 1]) + end + + it "should read field end" do + @trans.write("}") + expect(@prot.read_field_end).to eq(nil) + end + + it "should read map begin" do + @trans.write("[\"rec\",\"lst\",2,{") + expect(@prot.read_map_begin).to eq([12, 15, 2]) + end + + it "should read map end" do + @trans.write("}]") + expect(@prot.read_map_end).to eq(nil) + end + + it "should read list begin" do + @trans.write("[\"rec\",2\"\"") + expect(@prot.read_list_begin).to eq([12, 2]) + end + + it "should read list end" do + @trans.write("]") + expect(@prot.read_list_end).to eq(nil) + end + + it "should read set begin" do + @trans.write("[\"rec\",2\"\"") + expect(@prot.read_set_begin).to eq([12, 2]) + end + + it "should read set end" do + @trans.write("]") + expect(@prot.read_set_end).to eq(nil) + end + + it "should read bool" do + @trans.write("0\"\"") + expect(@prot.read_bool).to eq(false) + @prot.read_string + + @trans.write("1\"\"") + expect(@prot.read_bool).to eq(true) + end + + it "should read byte" do + @trans.write("60\"\"") + expect(@prot.read_byte).to eq(60) + end + + it "should read i16" do + @trans.write("1000\"\"") + expect(@prot.read_i16).to eq(1000) + end + + it "should read i32" do + @trans.write("3000000000\"\"") + expect(@prot.read_i32).to eq(3000000000) + end + + it "should read i64" do + @trans.write("6000000000\"\"") + expect(@prot.read_i64).to eq(6000000000) + end + + it "should read double" do + @trans.write("12.23\"\"") + expect(@prot.read_double).to eq(12.23) + end + + if RUBY_VERSION >= '1.9' + it 'should read string' do + @trans.write('"this is a test string"'.force_encoding(Encoding::BINARY)) + a = @prot.read_string + expect(a).to eq('this is a test string') + expect(a.encoding).to eq(Encoding::UTF_8) + end + + it 'should read string with unicode characters' do + @trans.write('"this is a test string with unicode characters: \u20AC \u20AD"'.force_encoding(Encoding::BINARY)) + a = @prot.read_string + expect(a).to eq("this is a test string with unicode characters: \u20AC \u20AD") + expect(a.encoding).to eq(Encoding::UTF_8) + end + else + it 'should read string' do + @trans.write('"this is a test string"') + expect(@prot.read_string).to eq('this is a test string') + end + end + + it "should read binary" do + @trans.write("\"dGhpcyBpcyBhIHRlc3Qgc3RyaW5n\"") + expect(@prot.read_binary).to eq("this is a test string") + end + + it "should read long binary" do + @trans.write("\"AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w==\"") + expect(@prot.read_binary.bytes.to_a).to eq((0...256).to_a) + end + + it "should provide a reasonable to_s" do + expect(@prot.to_s).to eq("json(memory)") + end + end + + describe Thrift::JsonProtocolFactory do + it "should create a JsonProtocol" do + expect(Thrift::JsonProtocolFactory.new.get_protocol(double("MockTransport"))).to be_instance_of(Thrift::JsonProtocol) + end + + it "should provide a reasonable to_s" do + expect(Thrift::JsonProtocolFactory.new.to_s).to eq("json") + end + end +end diff --git a/src/jaegertracing/thrift/lib/rb/spec/namespaced_spec.rb b/src/jaegertracing/thrift/lib/rb/spec/namespaced_spec.rb new file mode 100644 index 000000000..4d6d369e5 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/spec/namespaced_spec.rb @@ -0,0 +1,67 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +require 'spec_helper' + +describe 'namespaced generation' do + before do + require 'namespaced_spec_namespace/namespaced_nonblocking_service' + end + + it "generated the right files" do + prefix = File.expand_path("../gen-rb", __FILE__) + ["namespaced_spec_namespace/namespaced_nonblocking_service.rb", + "namespaced_spec_namespace/thrift_namespaced_spec_constants.rb", + "namespaced_spec_namespace/thrift_namespaced_spec_types.rb", + "other_namespace/referenced_constants.rb", + "other_namespace/referenced_types.rb" + ].each do |name| + expect(File.exist?(File.join(prefix, name))).to be_truthy + end + end + + it "did not generate the wrong files" do + prefix = File.expand_path("../gen-rb", __FILE__) + ["namespaced_nonblocking_service.rb", + "thrift_namespaced_spec_constants.rb", + "thrift_namespaced_spec_types.rb", + "referenced_constants.rb", + "referenced_types.rb" + ].each do |name| + expect(File.exist?(File.join(prefix, name))).not_to be_truthy + end + end + + it "has a service class in the right place" do + expect(defined?(NamespacedSpecNamespace::NamespacedNonblockingService)).to be_truthy + end + + it "has a struct in the right place" do + expect(defined?(NamespacedSpecNamespace::Hello)).to be_truthy + end + + it "required an included file" do + expect(defined?(OtherNamespace::SomeEnum)).to be_truthy + end + + it "extended a service" do + require "extended/extended_service" + end + +end diff --git a/src/jaegertracing/thrift/lib/rb/spec/nonblocking_server_spec.rb b/src/jaegertracing/thrift/lib/rb/spec/nonblocking_server_spec.rb new file mode 100644 index 000000000..613d88390 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/spec/nonblocking_server_spec.rb @@ -0,0 +1,263 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +require 'spec_helper' + +describe 'NonblockingServer' do + + class Handler + def initialize + @queue = Queue.new + end + + attr_accessor :server + + def greeting(english) + if english + SpecNamespace::Hello.new + else + SpecNamespace::Hello.new(:greeting => "Aloha!") + end + end + + def block + @queue.pop + end + + def unblock(n) + n.times { @queue.push true } + end + + def sleep(time) + Kernel.sleep time + end + + def shutdown + @server.shutdown(0, false) + end + end + + class SpecTransport < Thrift::BaseTransport + def initialize(transport, queue) + @transport = transport + @queue = queue + @flushed = false + end + + def open? + @transport.open? + end + + def open + @transport.open + end + + def close + @transport.close + end + + def read(sz) + @transport.read(sz) + end + + def write(buf,sz=nil) + @transport.write(buf, sz) + end + + def flush + @queue.push :flushed unless @flushed or @queue.nil? + @flushed = true + @transport.flush + end + end + + class SpecServerSocket < Thrift::ServerSocket + def initialize(host, port, queue) + super(host, port) + @queue = queue + end + + def listen + super + @queue.push :listen + end + end + + describe Thrift::NonblockingServer do + before(:each) do + @port = 43251 + handler = Handler.new + processor = SpecNamespace::NonblockingService::Processor.new(handler) + queue = Queue.new + @transport = SpecServerSocket.new('localhost', @port, queue) + transport_factory = Thrift::FramedTransportFactory.new + logger = Logger.new(STDERR) + logger.level = Logger::WARN + @server = Thrift::NonblockingServer.new(processor, @transport, transport_factory, nil, 5, logger) + handler.server = @server + @server_thread = Thread.new(Thread.current) do |master_thread| + begin + @server.serve + rescue => e + p e + puts e.backtrace * "\n" + master_thread.raise e + end + end + queue.pop + + @clients = [] + @catch_exceptions = false + end + + after(:each) do + @clients.each { |client, trans| trans.close } + # @server.shutdown(1) + @server_thread.kill + @transport.close + end + + def setup_client(queue = nil) + transport = SpecTransport.new(Thrift::FramedTransport.new(Thrift::Socket.new('localhost', @port)), queue) + protocol = Thrift::BinaryProtocol.new(transport) + client = SpecNamespace::NonblockingService::Client.new(protocol) + transport.open + @clients << [client, transport] + client + end + + def setup_client_thread(result) + queue = Queue.new + Thread.new do + begin + client = setup_client + while (cmd = queue.pop) + msg, *args = cmd + case msg + when :block + result << client.block + when :unblock + client.unblock(args.first) + when :hello + result << client.greeting(true) # ignore result + when :sleep + client.sleep(args[0] || 0.5) + result << :slept + when :shutdown + client.shutdown + when :exit + result << :done + break + end + end + @clients.each { |c,t| t.close and break if c == client } #close the transport + rescue => e + raise e unless @catch_exceptions + end + end + queue + end + + it "should handle basic message passing" do + client = setup_client + expect(client.greeting(true)).to eq(SpecNamespace::Hello.new) + expect(client.greeting(false)).to eq(SpecNamespace::Hello.new(:greeting => 'Aloha!')) + @server.shutdown + end + + it "should handle concurrent clients" do + queue = Queue.new + trans_queue = Queue.new + 4.times do + Thread.new(Thread.current) do |main_thread| + begin + queue.push setup_client(trans_queue).block + rescue => e + main_thread.raise e + end + end + end + 4.times { trans_queue.pop } + setup_client.unblock(4) + 4.times { expect(queue.pop).to be_truthy } + @server.shutdown + end + + it "should handle messages from more than 5 long-lived connections" do + queues = [] + result = Queue.new + 7.times do |i| + queues << setup_client_thread(result) + Thread.pass if i == 4 # give the server time to accept connections + end + client = setup_client + # block 4 connections + 4.times { |i| queues[i] << :block } + queues[4] << :hello + queues[5] << :hello + queues[6] << :hello + 3.times { expect(result.pop).to eq(SpecNamespace::Hello.new) } + expect(client.greeting(true)).to eq(SpecNamespace::Hello.new) + queues[5] << [:unblock, 4] + 4.times { expect(result.pop).to be_truthy } + queues[2] << :hello + expect(result.pop).to eq(SpecNamespace::Hello.new) + expect(client.greeting(false)).to eq(SpecNamespace::Hello.new(:greeting => 'Aloha!')) + 7.times { queues.shift << :exit } + expect(client.greeting(true)).to eq(SpecNamespace::Hello.new) + @server.shutdown + end + + it "should shut down when asked" do + # connect first to ensure it's running + client = setup_client + client.greeting(false) # force a message pass + @server.shutdown + expect(@server_thread.join(2)).to be_an_instance_of(Thread) + end + + it "should continue processing active messages when shutting down" do + result = Queue.new + client = setup_client_thread(result) + client << :sleep + sleep 0.1 # give the server time to start processing the client's message + @server.shutdown + expect(@server_thread.join(2)).to be_an_instance_of(Thread) + expect(result.pop).to eq(:slept) + end + + it "should kill active messages when they don't expire while shutting down" do + result = Queue.new + client = setup_client_thread(result) + client << [:sleep, 10] + sleep 0.1 # start processing the client's message + @server.shutdown(1) + @catch_exceptions = true + expect(@server_thread.join(3)).not_to be_nil + expect(result).to be_empty + end + + it "should allow shutting down in response to a message" do + client = setup_client + expect(client.greeting(true)).to eq(SpecNamespace::Hello.new) + client.shutdown + expect(@server_thread.join(2)).not_to be_nil + end + end +end diff --git a/src/jaegertracing/thrift/lib/rb/spec/processor_spec.rb b/src/jaegertracing/thrift/lib/rb/spec/processor_spec.rb new file mode 100644 index 000000000..d30553f55 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/spec/processor_spec.rb @@ -0,0 +1,80 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +require 'spec_helper' + +describe 'Processor' do + + class ProcessorSpec + include Thrift::Processor + end + + describe Thrift::Processor do + before(:each) do + @processor = ProcessorSpec.new(double("MockHandler")) + @prot = double("MockProtocol") + end + + def mock_trans(obj) + expect(obj).to receive(:trans).ordered do + double("trans").tap do |trans| + expect(trans).to receive(:flush).ordered + end + end + end + + it "should call process_<message> when it receives that message" do + expect(@prot).to receive(:read_message_begin).ordered.and_return ['testMessage', Thrift::MessageTypes::CALL, 17] + expect(@processor).to receive(:process_testMessage).with(17, @prot, @prot).ordered + expect(@processor.process(@prot, @prot)).to eq(true) + end + + it "should raise an ApplicationException when the received message cannot be processed" do + expect(@prot).to receive(:read_message_begin).ordered.and_return ['testMessage', Thrift::MessageTypes::CALL, 4] + expect(@prot).to receive(:skip).with(Thrift::Types::STRUCT).ordered + expect(@prot).to receive(:read_message_end).ordered + expect(@prot).to receive(:write_message_begin).with('testMessage', Thrift::MessageTypes::EXCEPTION, 4).ordered + e = double(Thrift::ApplicationException) + expect(e).to receive(:write).with(@prot).ordered + expect(Thrift::ApplicationException).to receive(:new).with(Thrift::ApplicationException::UNKNOWN_METHOD, "Unknown function testMessage").and_return(e) + expect(@prot).to receive(:write_message_end).ordered + mock_trans(@prot) + @processor.process(@prot, @prot) + end + + it "should pass args off to the args class" do + args_class = double("MockArgsClass") + args = double("#<MockArgsClass:mock>").tap do |args| + expect(args).to receive(:read).with(@prot).ordered + end + expect(args_class).to receive(:new).and_return args + expect(@prot).to receive(:read_message_end).ordered + expect(@processor.read_args(@prot, args_class)).to eql(args) + end + + it "should write out a reply when asked" do + expect(@prot).to receive(:write_message_begin).with('testMessage', Thrift::MessageTypes::REPLY, 23).ordered + result = double("MockResult") + expect(result).to receive(:write).with(@prot).ordered + expect(@prot).to receive(:write_message_end).ordered + mock_trans(@prot) + @processor.write_result(result, @prot, 'testMessage', 23) + end + end +end diff --git a/src/jaegertracing/thrift/lib/rb/spec/serializer_spec.rb b/src/jaegertracing/thrift/lib/rb/spec/serializer_spec.rb new file mode 100644 index 000000000..2a7dc6db9 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/spec/serializer_spec.rb @@ -0,0 +1,67 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +require 'spec_helper' + +describe 'Serializer' do + + describe Thrift::Serializer do + it "should serialize structs to binary by default" do + serializer = Thrift::Serializer.new(Thrift::BinaryProtocolAcceleratedFactory.new) + data = serializer.serialize(SpecNamespace::Hello.new(:greeting => "'Ello guv'nor!")) + expect(data).to eq("\x0B\x00\x01\x00\x00\x00\x0E'Ello guv'nor!\x00") + end + + it "should serialize structs to the given protocol" do + protocol = Thrift::BaseProtocol.new(double("transport")) + expect(protocol).to receive(:write_struct_begin).with("SpecNamespace::Hello") + expect(protocol).to receive(:write_field_begin).with("greeting", Thrift::Types::STRING, 1) + expect(protocol).to receive(:write_string).with("Good day") + expect(protocol).to receive(:write_field_end) + expect(protocol).to receive(:write_field_stop) + expect(protocol).to receive(:write_struct_end) + protocol_factory = double("ProtocolFactory") + allow(protocol_factory).to receive(:get_protocol).and_return(protocol) + serializer = Thrift::Serializer.new(protocol_factory) + serializer.serialize(SpecNamespace::Hello.new(:greeting => "Good day")) + end + end + + describe Thrift::Deserializer do + it "should deserialize structs from binary by default" do + deserializer = Thrift::Deserializer.new + data = "\x0B\x00\x01\x00\x00\x00\x0E'Ello guv'nor!\x00" + expect(deserializer.deserialize(SpecNamespace::Hello.new, data)).to eq(SpecNamespace::Hello.new(:greeting => "'Ello guv'nor!")) + end + + it "should deserialize structs from the given protocol" do + protocol = Thrift::BaseProtocol.new(double("transport")) + expect(protocol).to receive(:read_struct_begin).and_return("SpecNamespace::Hello") + expect(protocol).to receive(:read_field_begin).and_return(["greeting", Thrift::Types::STRING, 1], + [nil, Thrift::Types::STOP, 0]) + expect(protocol).to receive(:read_string).and_return("Good day") + expect(protocol).to receive(:read_field_end) + expect(protocol).to receive(:read_struct_end) + protocol_factory = double("ProtocolFactory") + allow(protocol_factory).to receive(:get_protocol).and_return(protocol) + deserializer = Thrift::Deserializer.new(protocol_factory) + expect(deserializer.deserialize(SpecNamespace::Hello.new, "")).to eq(SpecNamespace::Hello.new(:greeting => "Good day")) + end + end +end diff --git a/src/jaegertracing/thrift/lib/rb/spec/server_socket_spec.rb b/src/jaegertracing/thrift/lib/rb/spec/server_socket_spec.rb new file mode 100644 index 000000000..ec9e55005 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/spec/server_socket_spec.rb @@ -0,0 +1,84 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +require 'spec_helper' +require File.expand_path("#{File.dirname(__FILE__)}/socket_spec_shared") + +describe 'Thrift::ServerSocket' do + + describe Thrift::ServerSocket do + before(:each) do + @socket = Thrift::ServerSocket.new(1234) + end + + it "should create a handle when calling listen" do + expect(TCPServer).to receive(:new).with(nil, 1234) + @socket.listen + end + + it "should accept an optional host argument" do + @socket = Thrift::ServerSocket.new('localhost', 1234) + expect(TCPServer).to receive(:new).with('localhost', 1234) + @socket.to_s == "server(localhost:1234)" + @socket.listen + end + + it "should create a Thrift::Socket to wrap accepted sockets" do + handle = double("TCPServer") + expect(TCPServer).to receive(:new).with(nil, 1234).and_return(handle) + @socket.listen + sock = double("sock") + expect(handle).to receive(:accept).and_return(sock) + trans = double("Socket") + expect(Thrift::Socket).to receive(:new).and_return(trans) + expect(trans).to receive(:handle=).with(sock) + expect(@socket.accept).to eq(trans) + end + + it "should close the handle when closed" do + handle = double("TCPServer", :closed? => false) + expect(TCPServer).to receive(:new).with(nil, 1234).and_return(handle) + @socket.listen + expect(handle).to receive(:close) + @socket.close + end + + it "should return nil when accepting if there is no handle" do + expect(@socket.accept).to be_nil + end + + it "should return true for closed? when appropriate" do + handle = double("TCPServer", :closed? => false) + allow(TCPServer).to receive(:new).and_return(handle) + @socket.listen + expect(@socket).not_to be_closed + allow(handle).to receive(:close) + @socket.close + expect(@socket).to be_closed + @socket.listen + expect(@socket).not_to be_closed + allow(handle).to receive(:closed?).and_return(true) + expect(@socket).to be_closed + end + + it "should provide a reasonable to_s" do + expect(@socket.to_s).to eq("socket(:1234)") + end + end +end diff --git a/src/jaegertracing/thrift/lib/rb/spec/server_spec.rb b/src/jaegertracing/thrift/lib/rb/spec/server_spec.rb new file mode 100644 index 000000000..57f523776 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/spec/server_spec.rb @@ -0,0 +1,187 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +require 'spec_helper' + +describe 'Server' do + + describe Thrift::BaseServer do + before(:each) do + @processor = double("Processor") + @serverTrans = double("ServerTransport") + @trans = double("BaseTransport") + @prot = double("BaseProtocol") + @server = described_class.new(@processor, @serverTrans, @trans, @prot) + end + + it "should default to BaseTransportFactory and BinaryProtocolFactory when not specified" do + @server = Thrift::BaseServer.new(double("Processor"), double("BaseServerTransport")) + expect(@server.instance_variable_get(:'@transport_factory')).to be_an_instance_of(Thrift::BaseTransportFactory) + expect(@server.instance_variable_get(:'@protocol_factory')).to be_an_instance_of(Thrift::BinaryProtocolFactory) + end + + it "should not serve" do + expect { @server.serve()}.to raise_error(NotImplementedError) + end + + it "should provide a reasonable to_s" do + expect(@serverTrans).to receive(:to_s).once.and_return("serverTrans") + expect(@trans).to receive(:to_s).once.and_return("trans") + expect(@prot).to receive(:to_s).once.and_return("prot") + expect(@server.to_s).to eq("server(prot(trans(serverTrans)))") + end + end + + describe Thrift::SimpleServer do + before(:each) do + @processor = double("Processor") + @serverTrans = double("ServerTransport") + @trans = double("BaseTransport") + @prot = double("BaseProtocol") + @client = double("Client") + @server = described_class.new(@processor, @serverTrans, @trans, @prot) + end + + it "should provide a reasonable to_s" do + expect(@serverTrans).to receive(:to_s).once.and_return("serverTrans") + expect(@trans).to receive(:to_s).once.and_return("trans") + expect(@prot).to receive(:to_s).once.and_return("prot") + expect(@server.to_s).to eq("simple(server(prot(trans(serverTrans))))") + end + + it "should serve in the main thread" do + expect(@serverTrans).to receive(:listen).ordered + expect(@serverTrans).to receive(:accept).exactly(3).times.and_return(@client) + expect(@trans).to receive(:get_transport).exactly(3).times.with(@client).and_return(@trans) + expect(@prot).to receive(:get_protocol).exactly(3).times.with(@trans).and_return(@prot) + x = 0 + expect(@processor).to receive(:process).exactly(3).times.with(@prot, @prot) do + case (x += 1) + when 1 then raise Thrift::TransportException + when 2 then raise Thrift::ProtocolException + when 3 then throw :stop + end + end + expect(@trans).to receive(:close).exactly(3).times + expect(@serverTrans).to receive(:close).ordered + expect { @server.serve }.to throw_symbol(:stop) + end + end + + describe Thrift::ThreadedServer do + before(:each) do + @processor = double("Processor") + @serverTrans = double("ServerTransport") + @trans = double("BaseTransport") + @prot = double("BaseProtocol") + @client = double("Client") + @server = described_class.new(@processor, @serverTrans, @trans, @prot) + end + + it "should provide a reasonable to_s" do + expect(@serverTrans).to receive(:to_s).once.and_return("serverTrans") + expect(@trans).to receive(:to_s).once.and_return("trans") + expect(@prot).to receive(:to_s).once.and_return("prot") + expect(@server.to_s).to eq("threaded(server(prot(trans(serverTrans))))") + end + + it "should serve using threads" do + expect(@serverTrans).to receive(:listen).ordered + expect(@serverTrans).to receive(:accept).exactly(3).times.and_return(@client) + expect(@trans).to receive(:get_transport).exactly(3).times.with(@client).and_return(@trans) + expect(@prot).to receive(:get_protocol).exactly(3).times.with(@trans).and_return(@prot) + expect(Thread).to receive(:new).with(@prot, @trans).exactly(3).times.and_yield(@prot, @trans) + x = 0 + expect(@processor).to receive(:process).exactly(3).times.with(@prot, @prot) do + case (x += 1) + when 1 then raise Thrift::TransportException + when 2 then raise Thrift::ProtocolException + when 3 then throw :stop + end + end + expect(@trans).to receive(:close).exactly(3).times + expect(@serverTrans).to receive(:close).ordered + expect { @server.serve }.to throw_symbol(:stop) + end + end + + describe Thrift::ThreadPoolServer do + before(:each) do + @processor = double("Processor") + @server_trans = double("ServerTransport") + @trans = double("BaseTransport") + @prot = double("BaseProtocol") + @client = double("Client") + @server = described_class.new(@processor, @server_trans, @trans, @prot) + sleep(0.15) + end + + it "should provide a reasonable to_s" do + expect(@server_trans).to receive(:to_s).once.and_return("server_trans") + expect(@trans).to receive(:to_s).once.and_return("trans") + expect(@prot).to receive(:to_s).once.and_return("prot") + expect(@server.to_s).to eq("threadpool(server(prot(trans(server_trans))))") + end + + it "should serve inside a thread" do + exception_q = @server.instance_variable_get(:@exception_q) + expect_any_instance_of(described_class).to receive(:serve) do + exception_q.push(StandardError.new('ERROR')) + end + expect { @server.rescuable_serve }.to(raise_error('ERROR')) + sleep(0.15) + end + + it "should avoid running the server twice when retrying rescuable_serve" do + exception_q = @server.instance_variable_get(:@exception_q) + expect_any_instance_of(described_class).to receive(:serve) do + exception_q.push(StandardError.new('ERROR1')) + exception_q.push(StandardError.new('ERROR2')) + end + expect { @server.rescuable_serve }.to(raise_error('ERROR1')) + expect { @server.rescuable_serve }.to(raise_error('ERROR2')) + end + + it "should serve using a thread pool" do + thread_q = double("SizedQueue") + exception_q = double("Queue") + @server.instance_variable_set(:@thread_q, thread_q) + @server.instance_variable_set(:@exception_q, exception_q) + expect(@server_trans).to receive(:listen).ordered + expect(thread_q).to receive(:push).with(:token) + expect(thread_q).to receive(:pop) + expect(Thread).to receive(:new).and_yield + expect(@server_trans).to receive(:accept).exactly(3).times.and_return(@client) + expect(@trans).to receive(:get_transport).exactly(3).times.and_return(@trans) + expect(@prot).to receive(:get_protocol).exactly(3).times.and_return(@prot) + x = 0 + error = RuntimeError.new("Stopped") + expect(@processor).to receive(:process).exactly(3).times.with(@prot, @prot) do + case (x += 1) + when 1 then raise Thrift::TransportException + when 2 then raise Thrift::ProtocolException + when 3 then raise error + end + end + expect(@trans).to receive(:close).exactly(3).times + expect(exception_q).to receive(:push).with(error).and_throw(:stop) + expect(@server_trans).to receive(:close) + expect { @server.serve }.to(throw_symbol(:stop)) + end + end +end diff --git a/src/jaegertracing/thrift/lib/rb/spec/socket_spec.rb b/src/jaegertracing/thrift/lib/rb/spec/socket_spec.rb new file mode 100644 index 000000000..202c745ea --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/spec/socket_spec.rb @@ -0,0 +1,68 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +require 'spec_helper' +require File.expand_path("#{File.dirname(__FILE__)}/socket_spec_shared") + +describe 'Socket' do + + describe Thrift::Socket do + before(:each) do + @socket = Thrift::Socket.new + @handle = double("Handle", :closed? => false) + allow(@handle).to receive(:close) + allow(@handle).to receive(:connect_nonblock) + allow(@handle).to receive(:setsockopt) + allow(::Socket).to receive(:new).and_return(@handle) + end + + it_should_behave_like "a socket" + + it "should raise a TransportException when it cannot open a socket" do + expect(::Socket).to receive(:getaddrinfo).with("localhost", 9090, nil, ::Socket::SOCK_STREAM).and_return([[]]) + expect { @socket.open }.to raise_error(Thrift::TransportException) { |e| expect(e.type).to eq(Thrift::TransportException::NOT_OPEN) } + end + + it "should open a ::Socket with default args" do + expect(::Socket).to receive(:new).and_return(double("Handle", :connect_nonblock => true, :setsockopt => nil)) + expect(::Socket).to receive(:getaddrinfo).with("localhost", 9090, nil, ::Socket::SOCK_STREAM).and_return([[]]) + expect(::Socket).to receive(:sockaddr_in) + @socket.to_s == "socket(localhost:9090)" + @socket.open + end + + it "should accept host/port options" do + expect(::Socket).to receive(:new).and_return(double("Handle", :connect_nonblock => true, :setsockopt => nil)) + expect(::Socket).to receive(:getaddrinfo).with("my.domain", 1234, nil, ::Socket::SOCK_STREAM).and_return([[]]) + expect(::Socket).to receive(:sockaddr_in) + @socket = Thrift::Socket.new('my.domain', 1234).open + @socket.to_s == "socket(my.domain:1234)" + end + + it "should accept an optional timeout" do + allow(::Socket).to receive(:new) + expect(Thrift::Socket.new('localhost', 8080, 5).timeout).to eq(5) + end + + it "should provide a reasonable to_s" do + allow(::Socket).to receive(:new) + expect(Thrift::Socket.new('myhost', 8090).to_s).to eq("socket(myhost:8090)") + end + end +end diff --git a/src/jaegertracing/thrift/lib/rb/spec/socket_spec_shared.rb b/src/jaegertracing/thrift/lib/rb/spec/socket_spec_shared.rb new file mode 100644 index 000000000..32bdb71f0 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/spec/socket_spec_shared.rb @@ -0,0 +1,104 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +require 'spec_helper' + +shared_examples_for "a socket" do + it "should open a socket" do + expect(@socket.open).to eq(@handle) + end + + it "should be open whenever it has a handle" do + expect(@socket).not_to be_open + @socket.open + expect(@socket).to be_open + @socket.handle = nil + expect(@socket).not_to be_open + @socket.handle = @handle + @socket.close + expect(@socket).not_to be_open + end + + it "should write data to the handle" do + @socket.open + expect(@handle).to receive(:write).with("foobar") + @socket.write("foobar") + expect(@handle).to receive(:write).with("fail").and_raise(StandardError) + expect { @socket.write("fail") }.to raise_error(Thrift::TransportException) { |e| expect(e.type).to eq(Thrift::TransportException::NOT_OPEN) } + end + + it "should raise an error when it cannot read from the handle" do + @socket.open + expect(@handle).to receive(:readpartial).with(17).and_raise(StandardError) + expect { @socket.read(17) }.to raise_error(Thrift::TransportException) { |e| expect(e.type).to eq(Thrift::TransportException::NOT_OPEN) } + end + + it "should return the data read when reading from the handle works" do + @socket.open + expect(@handle).to receive(:readpartial).with(17).and_return("test data") + expect(@socket.read(17)).to eq("test data") + end + + it "should declare itself as closed when it has an error" do + @socket.open + expect(@handle).to receive(:write).with("fail").and_raise(StandardError) + expect(@socket).to be_open + expect { @socket.write("fail") }.to raise_error(Thrift::TransportException) { |e| expect(e.type).to eq(Thrift::TransportException::NOT_OPEN) } + expect(@socket).not_to be_open + end + + it "should raise an error when the stream is closed" do + @socket.open + allow(@handle).to receive(:closed?).and_return(true) + expect(@socket).not_to be_open + expect { @socket.write("fail") }.to raise_error(IOError, "closed stream") + expect { @socket.read(10) }.to raise_error(IOError, "closed stream") + end + + it "should support the timeout accessor for read" do + @socket.timeout = 3 + @socket.open + expect(IO).to receive(:select).with([@handle], nil, nil, 3).and_return([[@handle], [], []]) + expect(@handle).to receive(:readpartial).with(17).and_return("test data") + expect(@socket.read(17)).to eq("test data") + end + + it "should support the timeout accessor for write" do + @socket.timeout = 3 + @socket.open + expect(IO).to receive(:select).with(nil, [@handle], nil, 3).twice.and_return([[], [@handle], []]) + expect(@handle).to receive(:write_nonblock).with("test data").and_return(4) + expect(@handle).to receive(:write_nonblock).with(" data").and_return(5) + expect(@socket.write("test data")).to eq(9) + end + + it "should raise an error when read times out" do + @socket.timeout = 0.5 + @socket.open + expect(IO).to receive(:select).once {sleep(0.5); nil} + expect { @socket.read(17) }.to raise_error(Thrift::TransportException) { |e| expect(e.type).to eq(Thrift::TransportException::TIMED_OUT) } + end + + it "should raise an error when write times out" do + @socket.timeout = 0.5 + @socket.open + allow(IO).to receive(:select).with(nil, [@handle], nil, 0.5).and_return(nil) + expect { @socket.write("test data") }.to raise_error(Thrift::TransportException) { |e| expect(e.type).to eq(Thrift::TransportException::TIMED_OUT) } + end +end diff --git a/src/jaegertracing/thrift/lib/rb/spec/spec_helper.rb b/src/jaegertracing/thrift/lib/rb/spec/spec_helper.rb new file mode 100644 index 000000000..5bf98d077 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/spec/spec_helper.rb @@ -0,0 +1,64 @@ +# encoding: UTF-8 +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +require 'rubygems' +require 'rspec' + +$:.unshift File.join(File.dirname(__FILE__), *%w[.. ext]) + +# pretend we already loaded fastthread, otherwise the nonblocking_server_spec +# will get screwed up +# $" << 'fastthread.bundle' + +require 'thrift' + +unless Object.method_defined? :tap + # if Object#tap isn't defined, then add it; this should only happen in Ruby < 1.8.7 + class Object + def tap(&block) + block.call(self) + self + end + end +end + +RSpec.configure do |configuration| + configuration.before(:each) do + Thrift.type_checking = true + end +end + +$:.unshift File.join(File.dirname(__FILE__), *%w[.. test debug_proto gen-rb]) +require 'srv' +require 'debug_proto_test_constants' + +$:.unshift File.join(File.dirname(__FILE__), *%w[gen-rb]) +require 'thrift_spec_types' +require 'nonblocking_service' + +module Fixtures + COMPACT_PROTOCOL_TEST_STRUCT = Thrift::Test::COMPACT_TEST.dup + COMPACT_PROTOCOL_TEST_STRUCT.a_binary = [0,1,2,3,4,5,6,7,8].pack('c*') + COMPACT_PROTOCOL_TEST_STRUCT.set_byte_map = nil + COMPACT_PROTOCOL_TEST_STRUCT.map_byte_map = nil +end + +$:.unshift File.join(File.dirname(__FILE__), *%w[gen-rb/flat]) + diff --git a/src/jaegertracing/thrift/lib/rb/spec/ssl_server_socket_spec.rb b/src/jaegertracing/thrift/lib/rb/spec/ssl_server_socket_spec.rb new file mode 100644 index 000000000..82e651843 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/spec/ssl_server_socket_spec.rb @@ -0,0 +1,34 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +require 'spec_helper' +require File.expand_path("#{File.dirname(__FILE__)}/socket_spec_shared") + +describe 'SSLServerSocket' do + + describe Thrift::SSLServerSocket do + before(:each) do + @socket = Thrift::SSLServerSocket.new(1234) + end + + it "should provide a reasonable to_s" do + expect(@socket.to_s).to eq("ssl(socket(:1234))") + end + end +end diff --git a/src/jaegertracing/thrift/lib/rb/spec/ssl_socket_spec.rb b/src/jaegertracing/thrift/lib/rb/spec/ssl_socket_spec.rb new file mode 100644 index 000000000..808d8d512 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/spec/ssl_socket_spec.rb @@ -0,0 +1,78 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +require 'spec_helper' +require File.expand_path("#{File.dirname(__FILE__)}/socket_spec_shared") + +describe 'SSLSocket' do + + describe Thrift::SSLSocket do + before(:each) do + @context = OpenSSL::SSL::SSLContext.new + @socket = Thrift::SSLSocket.new + @simple_socket_handle = double("Handle", :closed? => false) + allow(@simple_socket_handle).to receive(:close) + allow(@simple_socket_handle).to receive(:connect_nonblock) + allow(@simple_socket_handle).to receive(:setsockopt) + + @handle = double(double("SSLHandle", :connect_nonblock => true, :post_connection_check => true), :closed? => false) + allow(@handle).to receive(:connect_nonblock) + allow(@handle).to receive(:close) + allow(@handle).to receive(:post_connection_check) + + allow(::Socket).to receive(:new).and_return(@simple_socket_handle) + allow(OpenSSL::SSL::SSLSocket).to receive(:new).and_return(@handle) + end + + it_should_behave_like "a socket" + + it "should raise a TransportException when it cannot open a ssl socket" do + expect(::Socket).to receive(:getaddrinfo).with("localhost", 9090, nil, ::Socket::SOCK_STREAM).and_return([[]]) + expect { @socket.open }.to raise_error(Thrift::TransportException) { |e| expect(e.type).to eq(Thrift::TransportException::NOT_OPEN) } + end + + it "should open a ::Socket with default args" do + expect(OpenSSL::SSL::SSLSocket).to receive(:new).with(@simple_socket_handle, nil).and_return(@handle) + expect(@handle).to receive(:post_connection_check).with('localhost') + @socket.open + end + + it "should accept host/port options" do + handle = double("Handle", :connect_nonblock => true, :setsockopt => nil) + allow(::Socket).to receive(:new).and_return(handle) + expect(::Socket).to receive(:getaddrinfo).with("my.domain", 1234, nil, ::Socket::SOCK_STREAM).and_return([[]]) + expect(::Socket).to receive(:sockaddr_in) + expect(OpenSSL::SSL::SSLSocket).to receive(:new).with(handle, nil).and_return(@handle) + expect(@handle).to receive(:post_connection_check).with('my.domain') + Thrift::SSLSocket.new('my.domain', 1234, 6000, nil).open + end + + it "should accept an optional timeout" do + expect(Thrift::SSLSocket.new('localhost', 8080, 5).timeout).to eq(5) + end + + it "should accept an optional context" do + expect(Thrift::SSLSocket.new('localhost', 8080, 5, @context).ssl_context).to eq(@context) + end + + it "should provide a reasonable to_s" do + expect(Thrift::SSLSocket.new('myhost', 8090).to_s).to eq("ssl(socket(myhost:8090))") + end + end +end diff --git a/src/jaegertracing/thrift/lib/rb/spec/struct_nested_containers_spec.rb b/src/jaegertracing/thrift/lib/rb/spec/struct_nested_containers_spec.rb new file mode 100644 index 000000000..d063569b5 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/spec/struct_nested_containers_spec.rb @@ -0,0 +1,191 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +require 'spec_helper' + +describe 'StructNestedContainers' do + + def with_type_checking + saved_type_checking, Thrift.type_checking = Thrift.type_checking, true + begin + yield + ensure + Thrift.type_checking = saved_type_checking + end + end + + describe Thrift::Struct do + # Nested container tests, see THRIFT-369. + it "should support nested lists inside lists" do + with_type_checking do + a, b = SpecNamespace::NestedListInList.new, SpecNamespace::NestedListInList.new + [a, b].each do |thrift_struct| + thrift_struct.value = [ [1, 2, 3], [2, 3, 4] ] + thrift_struct.validate + end + expect(a).to eq(b) + b.value.push [3, 4, 5] + expect(a).not_to eq(b) + end + end + + it "should support nested lists inside sets" do + with_type_checking do + a, b = SpecNamespace::NestedListInSet.new, SpecNamespace::NestedListInSet.new + [a, b].each do |thrift_struct| + thrift_struct.value = [ [1, 2, 3], [2, 3, 4] ].to_set + thrift_struct.validate + end + expect(a).to eq(b) + b.value.add [3, 4, 5] + expect(a).not_to eq(b) + end + end + + it "should support nested lists in map keys" do + with_type_checking do + a, b = SpecNamespace::NestedListInMapKey.new, SpecNamespace::NestedListInMapKey.new + [a, b].each do |thrift_struct| + thrift_struct.value = { [1, 2, 3] => 1, [2, 3, 4] => 2 } + thrift_struct.validate + end + expect(a).to eq(b) + b.value[[3, 4, 5]] = 3 + expect(a).not_to eq(b) + end + end + + it "should support nested lists in map values" do + with_type_checking do + a, b = SpecNamespace::NestedListInMapValue.new, SpecNamespace::NestedListInMapValue.new + [a, b].each do |thrift_struct| + thrift_struct.value = { 1 => [1, 2, 3], 2 => [2, 3, 4] } + thrift_struct.validate + end + expect(a).to eq(b) + b.value[3] = [3, 4, 5] + expect(a).not_to eq(b) + end + end + + it "should support nested sets inside lists" do + with_type_checking do + a, b = SpecNamespace::NestedSetInList.new, SpecNamespace::NestedSetInList.new + [a, b].each do |thrift_struct| + thrift_struct.value = [ [1, 2, 3].to_set, [2, 3, 4].to_set ] + thrift_struct.validate + end + expect(a).to eq(b) + b.value.push([3, 4, 5].to_set) + expect(a).not_to eq(b) + end + end + + it "should support nested sets inside sets" do + with_type_checking do + a, b = SpecNamespace::NestedSetInSet.new, SpecNamespace::NestedSetInSet.new + [a, b].each do |thrift_struct| + thrift_struct.value = [ [1, 2, 3].to_set, [2, 3, 4].to_set ].to_set + thrift_struct.validate + end + expect(a).to eq(b) + b.value.add([3, 4, 5].to_set) + expect(a).not_to eq(b) + end + end + + it "should support nested sets in map keys" do + with_type_checking do + a, b = SpecNamespace::NestedSetInMapKey.new, SpecNamespace::NestedSetInMapKey.new + [a, b].each do |thrift_struct| + thrift_struct.value = { [1, 2, 3].to_set => 1, [2, 3, 4].to_set => 2 } + thrift_struct.validate + end + expect(a).to eq(b) + b.value[[3, 4, 5].to_set] = 3 + expect(a).not_to eq(b) + end + end + + it "should support nested sets in map values" do + with_type_checking do + a, b = SpecNamespace::NestedSetInMapValue.new, SpecNamespace::NestedSetInMapValue.new + [a, b].each do |thrift_struct| + thrift_struct.value = { 1 => [1, 2, 3].to_set, 2 => [2, 3, 4].to_set } + thrift_struct.validate + end + expect(a).to eq(b) + b.value[3] = [3, 4, 5].to_set + expect(a).not_to eq(b) + end + end + + it "should support nested maps inside lists" do + with_type_checking do + a, b = SpecNamespace::NestedMapInList.new, SpecNamespace::NestedMapInList.new + [a, b].each do |thrift_struct| + thrift_struct.value = [ {1 => 2, 3 => 4}, {2 => 3, 4 => 5} ] + thrift_struct.validate + end + expect(a).to eq(b) + b.value.push({ 3 => 4, 5 => 6 }) + expect(a).not_to eq(b) + end + end + + it "should support nested maps inside sets" do + with_type_checking do + a, b = SpecNamespace::NestedMapInSet.new, SpecNamespace::NestedMapInSet.new + [a, b].each do |thrift_struct| + thrift_struct.value = [ {1 => 2, 3 => 4}, {2 => 3, 4 => 5} ].to_set + thrift_struct.validate + end + expect(a).to eq(b) + b.value.add({ 3 => 4, 5 => 6 }) + expect(a).not_to eq(b) + end + end + + it "should support nested maps in map keys" do + with_type_checking do + a, b = SpecNamespace::NestedMapInMapKey.new, SpecNamespace::NestedMapInMapKey.new + [a, b].each do |thrift_struct| + thrift_struct.value = { { 1 => 2, 3 => 4} => 1, {2 => 3, 4 => 5} => 2 } + thrift_struct.validate + end + expect(a).to eq(b) + b.value[{3 => 4, 5 => 6}] = 3 + expect(a).not_to eq(b) + end + end + + it "should support nested maps in map values" do + with_type_checking do + a, b = SpecNamespace::NestedMapInMapValue.new, SpecNamespace::NestedMapInMapValue.new + [a, b].each do |thrift_struct| + thrift_struct.value = { 1 => { 1 => 2, 3 => 4}, 2 => {2 => 3, 4 => 5} } + thrift_struct.validate + end + expect(a).to eq(b) + b.value[3] = { 3 => 4, 5 => 6 } + expect(a).not_to eq(b) + end + end + end +end diff --git a/src/jaegertracing/thrift/lib/rb/spec/struct_spec.rb b/src/jaegertracing/thrift/lib/rb/spec/struct_spec.rb new file mode 100644 index 000000000..bbd502b62 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/spec/struct_spec.rb @@ -0,0 +1,293 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +require 'spec_helper' + +describe 'Struct' do + + describe Thrift::Struct do + it "should iterate over all fields properly" do + fields = {} + SpecNamespace::Foo.new.each_field { |fid,field_info| fields[fid] = field_info } + expect(fields).to eq(SpecNamespace::Foo::FIELDS) + end + + it "should initialize all fields to defaults" do + validate_default_arguments(SpecNamespace::Foo.new) + end + + it "should initialize all fields to defaults and accept a block argument" do + SpecNamespace::Foo.new do |f| + validate_default_arguments(f) + end + end + + def validate_default_arguments(object) + expect(object.simple).to eq(53) + expect(object.words).to eq("words") + expect(object.hello).to eq(SpecNamespace::Hello.new(:greeting => 'hello, world!')) + expect(object.ints).to eq([1, 2, 2, 3]) + expect(object.complex).to be_nil + expect(object.shorts).to eq(Set.new([5, 17, 239])) + end + + it "should not share default values between instances" do + begin + struct = SpecNamespace::Foo.new + struct.ints << 17 + expect(SpecNamespace::Foo.new.ints).to eq([1,2,2,3]) + ensure + # ensure no leakage to other tests + SpecNamespace::Foo::FIELDS[4][:default] = [1,2,2,3] + end + end + + it "should properly initialize boolean values" do + struct = SpecNamespace::BoolStruct.new(:yesno => false) + expect(struct.yesno).to be_falsey + end + + it "should have proper == semantics" do + expect(SpecNamespace::Foo.new).not_to eq(SpecNamespace::Hello.new) + expect(SpecNamespace::Foo.new).to eq(SpecNamespace::Foo.new) + expect(SpecNamespace::Foo.new(:simple => 52)).not_to eq(SpecNamespace::Foo.new) + end + + it "should print enum value names in inspect" do + expect(SpecNamespace::StructWithSomeEnum.new(:some_enum => SpecNamespace::SomeEnum::ONE).inspect).to eq("<SpecNamespace::StructWithSomeEnum some_enum:ONE (0)>") + + expect(SpecNamespace::StructWithEnumMap.new(:my_map => {SpecNamespace::SomeEnum::ONE => [SpecNamespace::SomeEnum::TWO]}).inspect).to eq("<SpecNamespace::StructWithEnumMap my_map:{ONE (0): [TWO (1)]}>") + end + + it "should pretty print binary fields" do + expect(SpecNamespace::Foo2.new(:my_binary => "\001\002\003").inspect).to eq("<SpecNamespace::Foo2 my_binary:010203>") + end + + it "should offer field? methods" do + expect(SpecNamespace::Foo.new.opt_string?).to be_falsey + expect(SpecNamespace::Foo.new(:simple => 52).simple?).to be_truthy + expect(SpecNamespace::Foo.new(:my_bool => false).my_bool?).to be_truthy + expect(SpecNamespace::Foo.new(:my_bool => true).my_bool?).to be_truthy + end + + it "should be comparable" do + s1 = SpecNamespace::StructWithSomeEnum.new(:some_enum => SpecNamespace::SomeEnum::ONE) + s2 = SpecNamespace::StructWithSomeEnum.new(:some_enum => SpecNamespace::SomeEnum::TWO) + + expect(s1 <=> s2).to eq(-1) + expect(s2 <=> s1).to eq(1) + expect(s1 <=> s1).to eq(0) + expect(s1 <=> SpecNamespace::StructWithSomeEnum.new()).to eq(-1) + end + + it "should read itself off the wire" do + struct = SpecNamespace::Foo.new + prot = Thrift::BaseProtocol.new(double("transport")) + expect(prot).to receive(:read_struct_begin).twice + expect(prot).to receive(:read_struct_end).twice + expect(prot).to receive(:read_field_begin).and_return( + ['complex', Thrift::Types::MAP, 5], # Foo + ['words', Thrift::Types::STRING, 2], # Foo + ['hello', Thrift::Types::STRUCT, 3], # Foo + ['greeting', Thrift::Types::STRING, 1], # Hello + [nil, Thrift::Types::STOP, 0], # Hello + ['simple', Thrift::Types::I32, 1], # Foo + ['ints', Thrift::Types::LIST, 4], # Foo + ['shorts', Thrift::Types::SET, 6], # Foo + [nil, Thrift::Types::STOP, 0] # Hello + ) + expect(prot).to receive(:read_field_end).exactly(7).times + expect(prot).to receive(:read_map_begin).and_return( + [Thrift::Types::I32, Thrift::Types::MAP, 2], # complex + [Thrift::Types::STRING, Thrift::Types::DOUBLE, 2], # complex/1/value + [Thrift::Types::STRING, Thrift::Types::DOUBLE, 1] # complex/2/value + ) + expect(prot).to receive(:read_map_end).exactly(3).times + expect(prot).to receive(:read_list_begin).and_return([Thrift::Types::I32, 4]) + expect(prot).to receive(:read_list_end) + expect(prot).to receive(:read_set_begin).and_return([Thrift::Types::I16, 2]) + expect(prot).to receive(:read_set_end) + expect(prot).to receive(:read_i32).and_return( + 1, 14, # complex keys + 42, # simple + 4, 23, 4, 29 # ints + ) + expect(prot).to receive(:read_string).and_return("pi", "e", "feigenbaum", "apple banana", "what's up?") + expect(prot).to receive(:read_double).and_return(Math::PI, Math::E, 4.669201609) + expect(prot).to receive(:read_i16).and_return(2, 3) + expect(prot).not_to receive(:skip) + struct.read(prot) + + expect(struct.simple).to eq(42) + expect(struct.complex).to eq({1 => {"pi" => Math::PI, "e" => Math::E}, 14 => {"feigenbaum" => 4.669201609}}) + expect(struct.hello).to eq(SpecNamespace::Hello.new(:greeting => "what's up?")) + expect(struct.words).to eq("apple banana") + expect(struct.ints).to eq([4, 23, 4, 29]) + expect(struct.shorts).to eq(Set.new([3, 2])) + end + + it "should serialize false boolean fields correctly" do + b = SpecNamespace::BoolStruct.new(:yesno => false) + prot = Thrift::BinaryProtocol.new(Thrift::MemoryBufferTransport.new) + expect(prot).to receive(:write_bool).with(false) + b.write(prot) + end + + it "should skip unexpected fields in structs and use default values" do + struct = SpecNamespace::Foo.new + prot = Thrift::BaseProtocol.new(double("transport")) + expect(prot).to receive(:read_struct_begin) + expect(prot).to receive(:read_struct_end) + expect(prot).to receive(:read_field_begin).and_return( + ['simple', Thrift::Types::I32, 1], + ['complex', Thrift::Types::STRUCT, 5], + ['thinz', Thrift::Types::MAP, 7], + ['foobar', Thrift::Types::I32, 3], + ['words', Thrift::Types::STRING, 2], + [nil, Thrift::Types::STOP, 0] + ) + expect(prot).to receive(:read_field_end).exactly(5).times + expect(prot).to receive(:read_i32).and_return(42) + expect(prot).to receive(:read_string).and_return("foobar") + expect(prot).to receive(:skip).with(Thrift::Types::STRUCT) + expect(prot).to receive(:skip).with(Thrift::Types::MAP) + # prot.should_receive(:read_map_begin).and_return([Thrift::Types::I32, Thrift::Types::I32, 0]) + # prot.should_receive(:read_map_end) + expect(prot).to receive(:skip).with(Thrift::Types::I32) + struct.read(prot) + + expect(struct.simple).to eq(42) + expect(struct.complex).to be_nil + expect(struct.words).to eq("foobar") + expect(struct.hello).to eq(SpecNamespace::Hello.new(:greeting => 'hello, world!')) + expect(struct.ints).to eq([1, 2, 2, 3]) + expect(struct.shorts).to eq(Set.new([5, 17, 239])) + end + + it "should write itself to the wire" do + prot = Thrift::BaseProtocol.new(double("transport")) #mock("Protocol") + expect(prot).to receive(:write_struct_begin).with("SpecNamespace::Foo") + expect(prot).to receive(:write_struct_begin).with("SpecNamespace::Hello") + expect(prot).to receive(:write_struct_end).twice + expect(prot).to receive(:write_field_begin).with('ints', Thrift::Types::LIST, 4) + expect(prot).to receive(:write_i32).with(1) + expect(prot).to receive(:write_i32).with(2).twice + expect(prot).to receive(:write_i32).with(3) + expect(prot).to receive(:write_field_begin).with('complex', Thrift::Types::MAP, 5) + expect(prot).to receive(:write_i32).with(5) + expect(prot).to receive(:write_string).with('foo') + expect(prot).to receive(:write_double).with(1.23) + expect(prot).to receive(:write_field_begin).with('shorts', Thrift::Types::SET, 6) + expect(prot).to receive(:write_i16).with(5) + expect(prot).to receive(:write_i16).with(17) + expect(prot).to receive(:write_i16).with(239) + expect(prot).to receive(:write_field_stop).twice + expect(prot).to receive(:write_field_end).exactly(6).times + expect(prot).to receive(:write_field_begin).with('simple', Thrift::Types::I32, 1) + expect(prot).to receive(:write_i32).with(53) + expect(prot).to receive(:write_field_begin).with('hello', Thrift::Types::STRUCT, 3) + expect(prot).to receive(:write_field_begin).with('greeting', Thrift::Types::STRING, 1) + expect(prot).to receive(:write_string).with('hello, world!') + expect(prot).to receive(:write_map_begin).with(Thrift::Types::I32, Thrift::Types::MAP, 1) + expect(prot).to receive(:write_map_begin).with(Thrift::Types::STRING, Thrift::Types::DOUBLE, 1) + expect(prot).to receive(:write_map_end).twice + expect(prot).to receive(:write_list_begin).with(Thrift::Types::I32, 4) + expect(prot).to receive(:write_list_end) + expect(prot).to receive(:write_set_begin).with(Thrift::Types::I16, 3) + expect(prot).to receive(:write_set_end) + + struct = SpecNamespace::Foo.new + struct.words = nil + struct.complex = {5 => {"foo" => 1.23}} + struct.write(prot) + end + + it "should raise an exception if presented with an unknown container" do + # yeah this is silly, but I'm going for code coverage here + struct = SpecNamespace::Foo.new + expect { struct.send :write_container, nil, nil, {:type => "foo"} }.to raise_error(StandardError, "Not a container type: foo") + end + + it "should support optional type-checking in Thrift::Struct.new" do + Thrift.type_checking = true + begin + expect { SpecNamespace::Hello.new(:greeting => 3) }.to raise_error(Thrift::TypeError, /Expected Types::STRING, received (Integer|Fixnum) for field greeting/) + ensure + Thrift.type_checking = false + end + expect { SpecNamespace::Hello.new(:greeting => 3) }.not_to raise_error + end + + it "should support optional type-checking in field accessors" do + Thrift.type_checking = true + begin + hello = SpecNamespace::Hello.new + expect { hello.greeting = 3 }.to raise_error(Thrift::TypeError, /Expected Types::STRING, received (Integer|Fixnum) for field greeting/) + ensure + Thrift.type_checking = false + end + expect { hello.greeting = 3 }.not_to raise_error + end + + it "should raise an exception when unknown types are given to Thrift::Struct.new" do + expect { SpecNamespace::Hello.new(:fish => 'salmon') }.to raise_error(Exception, "Unknown key given to SpecNamespace::Hello.new: fish") + end + + it "should support `raise Xception, 'message'` for Exception structs" do + begin + raise SpecNamespace::Xception, "something happened" + rescue Thrift::Exception => e + expect(e.message).to eq("something happened") + expect(e.code).to eq(1) + # ensure it gets serialized properly, this is the really important part + prot = Thrift::BaseProtocol.new(double("trans")) + expect(prot).to receive(:write_struct_begin).with("SpecNamespace::Xception") + expect(prot).to receive(:write_struct_end) + expect(prot).to receive(:write_field_begin).with('message', Thrift::Types::STRING, 1)#, "something happened") + expect(prot).to receive(:write_string).with("something happened") + expect(prot).to receive(:write_field_begin).with('code', Thrift::Types::I32, 2)#, 1) + expect(prot).to receive(:write_i32).with(1) + expect(prot).to receive(:write_field_stop) + expect(prot).to receive(:write_field_end).twice + + e.write(prot) + end + end + + it "should support the regular initializer for exception structs" do + begin + raise SpecNamespace::Xception, :message => "something happened", :code => 5 + rescue Thrift::Exception => e + expect(e.message).to eq("something happened") + expect(e.code).to eq(5) + prot = Thrift::BaseProtocol.new(double("trans")) + expect(prot).to receive(:write_struct_begin).with("SpecNamespace::Xception") + expect(prot).to receive(:write_struct_end) + expect(prot).to receive(:write_field_begin).with('message', Thrift::Types::STRING, 1) + expect(prot).to receive(:write_string).with("something happened") + expect(prot).to receive(:write_field_begin).with('code', Thrift::Types::I32, 2) + expect(prot).to receive(:write_i32).with(5) + expect(prot).to receive(:write_field_stop) + expect(prot).to receive(:write_field_end).twice + + e.write(prot) + end + end + end +end diff --git a/src/jaegertracing/thrift/lib/rb/spec/thin_http_server_spec.rb b/src/jaegertracing/thrift/lib/rb/spec/thin_http_server_spec.rb new file mode 100644 index 000000000..665391b7d --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/spec/thin_http_server_spec.rb @@ -0,0 +1,141 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +require 'spec_helper' +require 'rack/test' +require 'thrift/server/thin_http_server' + +describe Thrift::ThinHTTPServer do + + let(:processor) { double('processor') } + + describe "#initialize" do + + context "when using the defaults" do + + it "binds to port 80, with host 0.0.0.0, a path of '/'" do + expect(Thin::Server).to receive(:new).with('0.0.0.0', 80, an_instance_of(Rack::Builder)) + Thrift::ThinHTTPServer.new(processor) + end + + it 'creates a ThinHTTPServer::RackApplicationContext' do + expect(Thrift::ThinHTTPServer::RackApplication).to receive(:for).with("/", processor, an_instance_of(Thrift::BinaryProtocolFactory)).and_return(anything) + Thrift::ThinHTTPServer.new(processor) + end + + it "uses the BinaryProtocolFactory" do + expect(Thrift::BinaryProtocolFactory).to receive(:new) + Thrift::ThinHTTPServer.new(processor) + end + + end + + context "when using the options" do + + it 'accepts :ip, :port, :path' do + ip = "192.168.0.1" + port = 3000 + path = "/thin" + expect(Thin::Server).to receive(:new).with(ip, port, an_instance_of(Rack::Builder)) + Thrift::ThinHTTPServer.new(processor, + :ip => ip, + :port => port, + :path => path) + end + + it 'creates a ThinHTTPServer::RackApplicationContext with a different protocol factory' do + expect(Thrift::ThinHTTPServer::RackApplication).to receive(:for).with("/", processor, an_instance_of(Thrift::JsonProtocolFactory)).and_return(anything) + Thrift::ThinHTTPServer.new(processor, + :protocol_factory => Thrift::JsonProtocolFactory.new) + end + + end + + end + + describe "#serve" do + + it 'starts the Thin server' do + underlying_thin_server = double('thin server', :start => true) + allow(Thin::Server).to receive(:new).and_return(underlying_thin_server) + + thin_thrift_server = Thrift::ThinHTTPServer.new(processor) + + expect(underlying_thin_server).to receive(:start) + thin_thrift_server.serve + end + end + +end + +describe Thrift::ThinHTTPServer::RackApplication do + include Rack::Test::Methods + + let(:processor) { double('processor') } + let(:protocol_factory) { double('protocol factory') } + + def app + Thrift::ThinHTTPServer::RackApplication.for("/", processor, protocol_factory) + end + + context "404 response" do + + it 'receives a non-POST' do + header('Content-Type', "application/x-thrift") + get "/" + expect(last_response.status).to be 404 + end + + it 'receives a header other than application/x-thrift' do + header('Content-Type', "application/json") + post "/" + expect(last_response.status).to be 404 + end + + end + + context "200 response" do + + before do + allow(protocol_factory).to receive(:get_protocol) + allow(processor).to receive(:process) + end + + it 'creates an IOStreamTransport' do + header('Content-Type', "application/x-thrift") + expect(Thrift::IOStreamTransport).to receive(:new).with(an_instance_of(Rack::Lint::InputWrapper), an_instance_of(Rack::Response)) + post "/" + end + + it 'fetches the right protocol based on the Transport' do + header('Content-Type', "application/x-thrift") + expect(protocol_factory).to receive(:get_protocol).with(an_instance_of(Thrift::IOStreamTransport)) + post "/" + end + + it 'status code 200' do + header('Content-Type', "application/x-thrift") + post "/" + expect(last_response.ok?).to be_truthy + end + + end + +end + diff --git a/src/jaegertracing/thrift/lib/rb/spec/types_spec.rb b/src/jaegertracing/thrift/lib/rb/spec/types_spec.rb new file mode 100644 index 000000000..d595ab563 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/spec/types_spec.rb @@ -0,0 +1,118 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +require 'spec_helper' + +describe Thrift::Types do + + before(:each) do + Thrift.type_checking = true + end + + after(:each) do + Thrift.type_checking = false + end + + context 'type checking' do + it "should return the proper name for each type" do + expect(Thrift.type_name(Thrift::Types::I16)).to eq("Types::I16") + expect(Thrift.type_name(Thrift::Types::VOID)).to eq("Types::VOID") + expect(Thrift.type_name(Thrift::Types::LIST)).to eq("Types::LIST") + expect(Thrift.type_name(42)).to be_nil + end + + it "should check types properly" do + # lambda { Thrift.check_type(nil, Thrift::Types::STOP) }.should raise_error(Thrift::TypeError) + expect { Thrift.check_type(3, {:type => Thrift::Types::STOP}, :foo) }.to raise_error(Thrift::TypeError) + expect { Thrift.check_type(nil, {:type => Thrift::Types::VOID}, :foo) }.not_to raise_error + expect { Thrift.check_type(3, {:type => Thrift::Types::VOID}, :foo) }.to raise_error(Thrift::TypeError) + expect { Thrift.check_type(true, {:type => Thrift::Types::BOOL}, :foo) }.not_to raise_error + expect { Thrift.check_type(3, {:type => Thrift::Types::BOOL}, :foo) }.to raise_error(Thrift::TypeError) + expect { Thrift.check_type(42, {:type => Thrift::Types::BYTE}, :foo) }.not_to raise_error + expect { Thrift.check_type(42, {:type => Thrift::Types::I16}, :foo) }.not_to raise_error + expect { Thrift.check_type(42, {:type => Thrift::Types::I32}, :foo) }.not_to raise_error + expect { Thrift.check_type(42, {:type => Thrift::Types::I64}, :foo) }.not_to raise_error + expect { Thrift.check_type(3.14, {:type => Thrift::Types::I32}, :foo) }.to raise_error(Thrift::TypeError) + expect { Thrift.check_type(3.14, {:type => Thrift::Types::DOUBLE}, :foo) }.not_to raise_error + expect { Thrift.check_type(3, {:type => Thrift::Types::DOUBLE}, :foo) }.to raise_error(Thrift::TypeError) + expect { Thrift.check_type("3", {:type => Thrift::Types::STRING}, :foo) }.not_to raise_error + expect { Thrift.check_type(3, {:type => Thrift::Types::STRING}, :foo) }.to raise_error(Thrift::TypeError) + hello = SpecNamespace::Hello.new + expect { Thrift.check_type(hello, {:type => Thrift::Types::STRUCT, :class => SpecNamespace::Hello}, :foo) }.not_to raise_error + expect { Thrift.check_type("foo", {:type => Thrift::Types::STRUCT}, :foo) }.to raise_error(Thrift::TypeError) + field = {:type => Thrift::Types::MAP, :key => {:type => Thrift::Types::I32}, :value => {:type => Thrift::Types::STRING}} + expect { Thrift.check_type({1 => "one"}, field, :foo) }.not_to raise_error + expect { Thrift.check_type([1], field, :foo) }.to raise_error(Thrift::TypeError) + field = {:type => Thrift::Types::LIST, :element => {:type => Thrift::Types::I32}} + expect { Thrift.check_type([1], field, :foo) }.not_to raise_error + expect { Thrift.check_type({:foo => 1}, field, :foo) }.to raise_error(Thrift::TypeError) + field = {:type => Thrift::Types::SET, :element => {:type => Thrift::Types::I32}} + expect { Thrift.check_type(Set.new([1,2]), field, :foo) }.not_to raise_error + expect { Thrift.check_type([1,2], field, :foo) }.to raise_error(Thrift::TypeError) + expect { Thrift.check_type({:foo => true}, field, :foo) }.to raise_error(Thrift::TypeError) + end + + it "should error out if nil is passed and skip_types is false" do + expect { Thrift.check_type(nil, {:type => Thrift::Types::BOOL}, :foo, false) }.to raise_error(Thrift::TypeError) + expect { Thrift.check_type(nil, {:type => Thrift::Types::BYTE}, :foo, false) }.to raise_error(Thrift::TypeError) + expect { Thrift.check_type(nil, {:type => Thrift::Types::I16}, :foo, false) }.to raise_error(Thrift::TypeError) + expect { Thrift.check_type(nil, {:type => Thrift::Types::I32}, :foo, false) }.to raise_error(Thrift::TypeError) + expect { Thrift.check_type(nil, {:type => Thrift::Types::I64}, :foo, false) }.to raise_error(Thrift::TypeError) + expect { Thrift.check_type(nil, {:type => Thrift::Types::DOUBLE}, :foo, false) }.to raise_error(Thrift::TypeError) + expect { Thrift.check_type(nil, {:type => Thrift::Types::STRING}, :foo, false) }.to raise_error(Thrift::TypeError) + expect { Thrift.check_type(nil, {:type => Thrift::Types::STRUCT}, :foo, false) }.to raise_error(Thrift::TypeError) + expect { Thrift.check_type(nil, {:type => Thrift::Types::LIST}, :foo, false) }.to raise_error(Thrift::TypeError) + expect { Thrift.check_type(nil, {:type => Thrift::Types::SET}, :foo, false) }.to raise_error(Thrift::TypeError) + expect { Thrift.check_type(nil, {:type => Thrift::Types::MAP}, :foo, false) }.to raise_error(Thrift::TypeError) + end + + it "should check element types on containers" do + field = {:type => Thrift::Types::LIST, :element => {:type => Thrift::Types::I32}} + expect { Thrift.check_type([1, 2], field, :foo) }.not_to raise_error + expect { Thrift.check_type([1, nil, 2], field, :foo) }.to raise_error(Thrift::TypeError) + field = {:type => Thrift::Types::MAP, :key => {:type => Thrift::Types::I32}, :value => {:type => Thrift::Types::STRING}} + expect { Thrift.check_type({1 => "one", 2 => "two"}, field, :foo) }.not_to raise_error + expect { Thrift.check_type({1 => "one", nil => "nil"}, field, :foo) }.to raise_error(Thrift::TypeError) + expect { Thrift.check_type({1 => nil, 2 => "two"}, field, :foo) }.to raise_error(Thrift::TypeError) + field = {:type => Thrift::Types::SET, :element => {:type => Thrift::Types::I32}} + expect { Thrift.check_type(Set.new([1, 2]), field, :foo) }.not_to raise_error + expect { Thrift.check_type(Set.new([1, nil, 2]), field, :foo) }.to raise_error(Thrift::TypeError) + expect { Thrift.check_type(Set.new([1, 2.3, 2]), field, :foo) }.to raise_error(Thrift::TypeError) + + field = {:type => Thrift::Types::STRUCT, :class => SpecNamespace::Hello} + expect { Thrift.check_type(SpecNamespace::BoolStruct, field, :foo) }.to raise_error(Thrift::TypeError) + end + + it "should give the Thrift::TypeError a readable message" do + msg = /Expected Types::STRING, received (Integer|Fixnum) for field foo/ + expect { Thrift.check_type(3, {:type => Thrift::Types::STRING}, :foo) }.to raise_error(Thrift::TypeError, msg) + msg = /Expected Types::STRING, received (Integer|Fixnum) for field foo.element/ + field = {:type => Thrift::Types::LIST, :element => {:type => Thrift::Types::STRING}} + expect { Thrift.check_type([3], field, :foo) }.to raise_error(Thrift::TypeError, msg) + msg = "Expected Types::I32, received NilClass for field foo.element.key" + field = {:type => Thrift::Types::LIST, + :element => {:type => Thrift::Types::MAP, + :key => {:type => Thrift::Types::I32}, + :value => {:type => Thrift::Types::I32}}} + expect { Thrift.check_type([{nil => 3}], field, :foo) }.to raise_error(Thrift::TypeError, msg) + msg = "Expected Types::I32, received NilClass for field foo.element.value" + expect { Thrift.check_type([{1 => nil}], field, :foo) }.to raise_error(Thrift::TypeError, msg) + end + end +end diff --git a/src/jaegertracing/thrift/lib/rb/spec/union_spec.rb b/src/jaegertracing/thrift/lib/rb/spec/union_spec.rb new file mode 100644 index 000000000..efb385346 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/spec/union_spec.rb @@ -0,0 +1,214 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +require 'spec_helper' + +describe 'Union' do + + describe Thrift::Union do + it "should return nil value in unset union" do + union = SpecNamespace::My_union.new + expect(union.get_set_field).to eq(nil) + expect(union.get_value).to eq(nil) + end + + it "should set a field and be accessible through get_value and the named field accessor" do + union = SpecNamespace::My_union.new + union.integer32 = 25 + expect(union.get_set_field).to eq(:integer32) + expect(union.get_value).to eq(25) + expect(union.integer32).to eq(25) + end + + it "should work correctly when instantiated with static field constructors" do + union = SpecNamespace::My_union.integer32(5) + expect(union.get_set_field).to eq(:integer32) + expect(union.integer32).to eq(5) + end + + it "should raise for wrong set field" do + union = SpecNamespace::My_union.new + union.integer32 = 25 + expect { union.some_characters }.to raise_error(RuntimeError, "some_characters is not union's set field.") + end + + it "should raise for wrong set field when hash initialized and type checking is off" do + Thrift.type_checking = false + union = SpecNamespace::My_union.new({incorrect_field: :incorrect}) + expect { Thrift::Serializer.new.serialize(union) }.to raise_error(RuntimeError, "set_field is not valid for this union!") + end + + it "should not be equal to nil" do + union = SpecNamespace::My_union.new + expect(union).not_to eq(nil) + end + + it "should not be equal with an empty String" do + union = SpecNamespace::My_union.new + expect(union).not_to eq('') + end + + it "should not equate two different unions, i32 vs. string" do + union = SpecNamespace::My_union.new(:integer32, 25) + other_union = SpecNamespace::My_union.new(:some_characters, "blah!") + expect(union).not_to eq(other_union) + end + + it "should properly reset setfield and setvalue" do + union = SpecNamespace::My_union.new(:integer32, 25) + expect(union.get_set_field).to eq(:integer32) + union.some_characters = "blah!" + expect(union.get_set_field).to eq(:some_characters) + expect(union.get_value).to eq("blah!") + expect { union.integer32 }.to raise_error(RuntimeError, "integer32 is not union's set field.") + end + + it "should not equate two different unions with different values" do + union = SpecNamespace::My_union.new(:integer32, 25) + other_union = SpecNamespace::My_union.new(:integer32, 400) + expect(union).not_to eq(other_union) + end + + it "should not equate two different unions with different fields" do + union = SpecNamespace::My_union.new(:integer32, 25) + other_union = SpecNamespace::My_union.new(:other_i32, 25) + expect(union).not_to eq(other_union) + end + + it "should inspect properly" do + union = SpecNamespace::My_union.new(:integer32, 25) + expect(union.inspect).to eq("<SpecNamespace::My_union integer32: 25>") + end + + it "should not allow setting with instance_variable_set" do + union = SpecNamespace::My_union.new(:integer32, 27) + union.instance_variable_set(:@some_characters, "hallo!") + expect(union.get_set_field).to eq(:integer32) + expect(union.get_value).to eq(27) + expect { union.some_characters }.to raise_error(RuntimeError, "some_characters is not union's set field.") + end + + it "should serialize to binary correctly" do + trans = Thrift::MemoryBufferTransport.new + proto = Thrift::BinaryProtocol.new(trans) + + union = SpecNamespace::My_union.new(:integer32, 25) + union.write(proto) + + other_union = SpecNamespace::My_union.new(:integer32, 25) + other_union.read(proto) + expect(other_union).to eq(union) + end + + it "should serialize to json correctly" do + trans = Thrift::MemoryBufferTransport.new + proto = Thrift::JsonProtocol.new(trans) + + union = SpecNamespace::My_union.new(:integer32, 25) + union.write(proto) + + other_union = SpecNamespace::My_union.new(:integer32, 25) + other_union.read(proto) + expect(other_union).to eq(union) + end + + it "should raise when validating unset union" do + union = SpecNamespace::My_union.new + expect { union.validate }.to raise_error(StandardError, "Union fields are not set.") + + other_union = SpecNamespace::My_union.new(:integer32, 1) + expect { other_union.validate }.not_to raise_error + end + + it "should validate an enum field properly" do + union = SpecNamespace::TestUnion.new(:enum_field, 3) + expect(union.get_set_field).to eq(:enum_field) + expect { union.validate }.to raise_error(Thrift::ProtocolException, "Invalid value of field enum_field!") + + other_union = SpecNamespace::TestUnion.new(:enum_field, 1) + expect { other_union.validate }.not_to raise_error + end + + it "should properly serialize and match structs with a union" do + union = SpecNamespace::My_union.new(:integer32, 26) + swu = SpecNamespace::Struct_with_union.new(:fun_union => union) + + trans = Thrift::MemoryBufferTransport.new + proto = Thrift::CompactProtocol.new(trans) + + swu.write(proto) + + other_union = SpecNamespace::My_union.new(:some_characters, "hello there") + swu2 = SpecNamespace::Struct_with_union.new(:fun_union => other_union) + + expect(swu2).not_to eq(swu) + + swu2.read(proto) + expect(swu2).to eq(swu) + end + + it "should support old style constructor" do + union = SpecNamespace::My_union.new(:integer32 => 26) + expect(union.get_set_field).to eq(:integer32) + expect(union.get_value).to eq(26) + end + + it "should not throw an error when inspected and unset" do + expect{SpecNamespace::TestUnion.new().inspect}.not_to raise_error + end + + it "should print enum value name when inspected" do + expect(SpecNamespace::My_union.new(:some_enum => SpecNamespace::SomeEnum::ONE).inspect).to eq("<SpecNamespace::My_union some_enum: ONE (0)>") + + expect(SpecNamespace::My_union.new(:my_map => {SpecNamespace::SomeEnum::ONE => [SpecNamespace::SomeEnum::TWO]}).inspect).to eq("<SpecNamespace::My_union my_map: {ONE (0): [TWO (1)]}>") + end + + it "should offer field? methods" do + expect(SpecNamespace::My_union.new.some_enum?).to be_falsey + expect(SpecNamespace::My_union.new(:some_enum => SpecNamespace::SomeEnum::ONE).some_enum?).to be_truthy + expect(SpecNamespace::My_union.new(:im_true => false).im_true?).to be_truthy + expect(SpecNamespace::My_union.new(:im_true => true).im_true?).to be_truthy + end + + it "should pretty print binary fields" do + expect(SpecNamespace::TestUnion.new(:binary_field => "\001\002\003").inspect).to eq("<SpecNamespace::TestUnion binary_field: 010203>") + end + + it "should be comparable" do + relationships = [ + [0, -1, -1, -1], + [1, 0, -1, -1], + [1, 1, 0, -1], + [1, 1, 1, 0]] + + objs = [ + SpecNamespace::TestUnion.new(:string_field, "blah"), + SpecNamespace::TestUnion.new(:string_field, "blahblah"), + SpecNamespace::TestUnion.new(:i32_field, 1), + SpecNamespace::TestUnion.new()] + + for y in 0..3 + for x in 0..3 + # puts "#{objs[y].inspect} <=> #{objs[x].inspect} should == #{relationships[y][x]}" + expect(objs[y] <=> objs[x]).to eq(relationships[y][x]) + end + end + end + end +end diff --git a/src/jaegertracing/thrift/lib/rb/spec/unix_socket_spec.rb b/src/jaegertracing/thrift/lib/rb/spec/unix_socket_spec.rb new file mode 100644 index 000000000..8623e95a0 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/spec/unix_socket_spec.rb @@ -0,0 +1,116 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +require 'spec_helper' +require File.expand_path("#{File.dirname(__FILE__)}/socket_spec_shared") + +describe 'UNIXSocket' do + + describe Thrift::UNIXSocket do + before(:each) do + @path = '/tmp/thrift_spec_socket' + @socket = Thrift::UNIXSocket.new(@path) + @handle = double("Handle", :closed? => false) + allow(@handle).to receive(:close) + allow(::UNIXSocket).to receive(:new).and_return(@handle) + end + + it_should_behave_like "a socket" + + it "should raise a TransportException when it cannot open a socket" do + expect(::UNIXSocket).to receive(:new).and_raise(StandardError) + expect { @socket.open }.to raise_error(Thrift::TransportException) { |e| expect(e.type).to eq(Thrift::TransportException::NOT_OPEN) } + end + + it "should accept an optional timeout" do + allow(::UNIXSocket).to receive(:new) + expect(Thrift::UNIXSocket.new(@path, 5).timeout).to eq(5) + end + + it "should provide a reasonable to_s" do + allow(::UNIXSocket).to receive(:new) + expect(Thrift::UNIXSocket.new(@path).to_s).to eq("domain(#{@path})") + end + end + + describe Thrift::UNIXServerSocket do + before(:each) do + @path = '/tmp/thrift_spec_socket' + @socket = Thrift::UNIXServerSocket.new(@path) + end + + it "should create a handle when calling listen" do + expect(UNIXServer).to receive(:new).with(@path) + @socket.listen + end + + it "should create a Thrift::UNIXSocket to wrap accepted sockets" do + handle = double("UNIXServer") + expect(UNIXServer).to receive(:new).with(@path).and_return(handle) + @socket.listen + sock = double("sock") + expect(handle).to receive(:accept).and_return(sock) + trans = double("UNIXSocket") + expect(Thrift::UNIXSocket).to receive(:new).and_return(trans) + expect(trans).to receive(:handle=).with(sock) + expect(@socket.accept).to eq(trans) + end + + it "should close the handle when closed" do + handle = double("UNIXServer", :closed? => false) + expect(UNIXServer).to receive(:new).with(@path).and_return(handle) + @socket.listen + expect(handle).to receive(:close) + allow(File).to receive(:delete) + @socket.close + end + + it "should delete the socket when closed" do + handle = double("UNIXServer", :closed? => false) + expect(UNIXServer).to receive(:new).with(@path).and_return(handle) + @socket.listen + allow(handle).to receive(:close) + expect(File).to receive(:delete).with(@path) + @socket.close + end + + it "should return nil when accepting if there is no handle" do + expect(@socket.accept).to be_nil + end + + it "should return true for closed? when appropriate" do + handle = double("UNIXServer", :closed? => false) + allow(UNIXServer).to receive(:new).and_return(handle) + allow(File).to receive(:delete) + @socket.listen + expect(@socket).not_to be_closed + allow(handle).to receive(:close) + @socket.close + expect(@socket).to be_closed + @socket.listen + expect(@socket).not_to be_closed + allow(handle).to receive(:closed?).and_return(true) + expect(@socket).to be_closed + end + + it "should provide a reasonable to_s" do + expect(@socket.to_s).to eq("domain(#{@path})") + end + end +end |