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 | |
parent | Initial commit. (diff) | |
download | ceph-19fcec84d8d7d21e796c7624e521b60d28ee21ed.tar.xz ceph-19fcec84d8d7d21e796c7624e521b60d28ee21ed.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')
108 files changed, 13716 insertions, 0 deletions
diff --git a/src/jaegertracing/thrift/lib/rb/Gemfile b/src/jaegertracing/thrift/lib/rb/Gemfile new file mode 100644 index 000000000..1c86af95d --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/Gemfile @@ -0,0 +1,4 @@ +source "http://rubygems.org" + +gemspec + diff --git a/src/jaegertracing/thrift/lib/rb/Makefile.am b/src/jaegertracing/thrift/lib/rb/Makefile.am new file mode 100755 index 000000000..1841065f5 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/Makefile.am @@ -0,0 +1,55 @@ +# +# 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. +# + +DESTDIR ?= / + +if HAVE_BUNDLER + +all-local: + $(BUNDLER) install + $(BUNDLER) exec rake build_ext + +install-exec-hook: + $(BUNDLER) exec rake install + +clean-local: + $(BUNDLER) install + $(BUNDLER) exec rake clean + $(RM) -r spec/gen-rb/ + +check-local: all + $(BUNDLER) install + $(BUNDLER) exec rake + +endif + +dist-hook: + $(RM) -r $(distdir)/spec/gen-rb/ + +EXTRA_DIST = \ + coding_standards.md \ + Rakefile \ + Gemfile \ + thrift.gemspec \ + lib \ + ext \ + benchmark \ + script \ + spec \ + README.md diff --git a/src/jaegertracing/thrift/lib/rb/README.md b/src/jaegertracing/thrift/lib/rb/README.md new file mode 100644 index 000000000..b6e9a0792 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/README.md @@ -0,0 +1,43 @@ +Thrift Ruby Software Library + http://thrift.apache.org + +== 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. + +== DESCRIPTION: + +Thrift is a strongly-typed language-agnostic RPC system. +This library is the ruby implementation for both clients and servers. + +== INSTALL: + + $ gem install thrift + +== CAVEATS: + +This library provides the client and server implementations of thrift. +It does <em>not</em> provide the compiler for the .thrift files. To compile +.thrift files into language-specific implementations, please download the full +thrift software package. + +== USAGE: + +This section should get written by someone with the time and inclination. +In the meantime, look at existing code, such as the benchmark or the tutorial +in the full thrift distribution. diff --git a/src/jaegertracing/thrift/lib/rb/Rakefile b/src/jaegertracing/thrift/lib/rb/Rakefile new file mode 100644 index 000000000..5e5e5acaa --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/Rakefile @@ -0,0 +1,120 @@ +# +# 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 'rake' +require 'rake/clean' +require 'rspec/core/rake_task' + +THRIFT = '../../compiler/cpp/thrift' + +task :default => [:gem] +task :spec => [:'gen-rb', :build_ext, :realspec] + +RSpec::Core::RakeTask.new(:realspec) do |t| + t.rspec_opts = ['--color', '--format d'] +end + +RSpec::Core::RakeTask.new(:'spec:rcov') do |t| + t.rspec_opts = ['--color', '--format d'] + t.rcov = true + t.rcov_opts = ['--exclude', '^spec,/gems/'] +end + +desc 'Compile the .thrift files for the specs' +task :'gen-rb' => [:'gen-rb:spec', :'gen-rb:namespaced_spec', :'gen-rb:flat_spec', :'gen-rb:benchmark', :'gen-rb:debug_proto'] +namespace :'gen-rb' do + task :'spec' do + dir = File.dirname(__FILE__) + '/spec' + sh THRIFT, '--gen', 'rb', '-o', dir, "#{dir}/ThriftSpec.thrift" + end + + task :'namespaced_spec' do + dir = File.dirname(__FILE__) + '/spec' + sh THRIFT, '--gen', 'rb:namespaced', '--recurse', '-o', dir, "#{dir}/ThriftNamespacedSpec.thrift" + sh THRIFT, '--gen', 'rb:namespaced', '--recurse', '-o', dir, "#{dir}/BaseService.thrift" + sh THRIFT, '--gen', 'rb:namespaced', '--recurse', '-o', dir, "#{dir}/ExtendedService.thrift" + end + + task :'flat_spec' do + dir = File.dirname(__FILE__) + '/spec' + mkdir_p("#{dir}/gen-rb/flat") + sh THRIFT, '--gen', 'rb', '--recurse', '-out', "#{dir}/gen-rb/flat", "#{dir}/ThriftNamespacedSpec.thrift" + end + + task :'benchmark' do + dir = File.dirname(__FILE__) + '/benchmark' + sh THRIFT, '--gen', 'rb', '-o', dir, "#{dir}/Benchmark.thrift" + end + + task :'debug_proto' do + sh "mkdir", "-p", "test/debug_proto" + sh THRIFT, '--gen', 'rb', "-o", "test/debug_proto", "../../test/DebugProtoTest.thrift" + end +end + +desc "Build the native library" +task :build_ext => :'gen-rb' do + next if defined?(RUBY_ENGINE) && RUBY_ENGINE =~ /jruby/ + Dir::chdir(File::dirname('ext/extconf.rb')) do + unless sh "ruby #{File::basename('ext/extconf.rb')}" + $stderr.puts "Failed to run extconf" + break + end + unless sh "make" + $stderr.puts "make failed" + break + end + end +end + +desc 'Run the compiler tests (requires full thrift checkout)' +task :test do + # ensure this is a full thrift checkout and not a tarball of the ruby libs + cmd = 'head -1 ../../README.md 2>/dev/null | grep Thrift >/dev/null 2>/dev/null' + system(cmd) or fail "rake test requires a full thrift checkout" + sh 'make', '-C', File.dirname(__FILE__) + "/../../test/rb", "check" +end + +desc 'Run benchmarking of NonblockingServer' +task :benchmark do + ruby 'benchmark/benchmark.rb' +end + +desc 'Builds the thrift gem' +task :gem => [:spec, :build_ext] do + unless sh 'gem', 'build', 'thrift.gemspec' + $stderr.puts "Failed to build thrift gem" + break + end +end + +desc 'Install the thrift gem' +task :install => [:gem] do + unless sh 'gem', 'install', Dir.glob('thrift-*.gem').last + $stderr.puts "Failed to install thrift gem" + break + end +end + +CLEAN.include [ + '.bundle', 'benchmark/gen-rb', 'coverage', 'ext/*.{o,bundle,so,dll}', 'ext/mkmf.log', + 'ext/Makefile', 'ext/conftest.dSYM', 'Gemfile.lock', 'mkmf.log', 'pkg', 'spec/gen-rb', + 'test', 'thrift-*.gem' +] diff --git a/src/jaegertracing/thrift/lib/rb/benchmark/Benchmark.thrift b/src/jaegertracing/thrift/lib/rb/benchmark/Benchmark.thrift new file mode 100644 index 000000000..eb5ae38e6 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/benchmark/Benchmark.thrift @@ -0,0 +1,24 @@ +# +# 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 ThriftBenchmark + +service BenchmarkService { + i32 fibonacci(1:byte n) +} diff --git a/src/jaegertracing/thrift/lib/rb/benchmark/benchmark.rb b/src/jaegertracing/thrift/lib/rb/benchmark/benchmark.rb new file mode 100644 index 000000000..3dc67dd8c --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/benchmark/benchmark.rb @@ -0,0 +1,271 @@ +# +# 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' +$:.unshift File.dirname(__FILE__) + '/../lib' +require 'thrift' +require 'stringio' + +HOST = '127.0.0.1' +PORT = 42587 + +############### +## Server +############### + +class Server + attr_accessor :serverclass + attr_accessor :interpreter + attr_accessor :host + attr_accessor :port + + def initialize(opts) + @serverclass = opts.fetch(:class, Thrift::NonblockingServer) + @interpreter = opts.fetch(:interpreter, "ruby") + @host = opts.fetch(:host, ::HOST) + @port = opts.fetch(:port, ::PORT) + end + + def start + return if @serverclass == Object + args = (File.basename(@interpreter) == "jruby" ? "-J-server" : "") + @pipe = IO.popen("#{@interpreter} #{args} #{File.dirname(__FILE__)}/server.rb #{@host} #{@port} #{@serverclass.name}", "r+") + Marshal.load(@pipe) # wait until the server has started + sleep 0.4 # give the server time to actually start spawning sockets + end + + def shutdown + return unless @pipe + Marshal.dump(:shutdown, @pipe) + begin + @pipe.read(10) # block until the server shuts down + rescue EOFError + end + @pipe.close + @pipe = nil + end +end + +class BenchmarkManager + def initialize(opts, server) + @socket = opts.fetch(:socket) do + @host = opts.fetch(:host, 'localhost') + @port = opts.fetch(:port) + nil + end + @num_processes = opts.fetch(:num_processes, 40) + @clients_per_process = opts.fetch(:clients_per_process, 10) + @calls_per_client = opts.fetch(:calls_per_client, 50) + @interpreter = opts.fetch(:interpreter, "ruby") + @server = server + @log_exceptions = opts.fetch(:log_exceptions, false) + end + + def run + @pool = [] + @benchmark_start = Time.now + puts "Spawning benchmark processes..." + @num_processes.times do + spawn + sleep 0.02 # space out spawns + end + collect_output + @benchmark_end = Time.now # we know the procs are done here + translate_output + analyze_output + report_output + end + + def spawn + pipe = IO.popen("#{@interpreter} #{File.dirname(__FILE__)}/client.rb #{"-log-exceptions" if @log_exceptions} #{@host} #{@port} #{@clients_per_process} #{@calls_per_client}") + @pool << pipe + end + + def socket_class + if @socket + Thrift::UNIXSocket + else + Thrift::Socket + end + end + + def collect_output + puts "Collecting output..." + # read from @pool until all sockets are closed + @buffers = Hash.new { |h,k| h[k] = '' } + until @pool.empty? + rd, = select(@pool) + next if rd.nil? + rd.each do |fd| + begin + @buffers[fd] << fd.readpartial(4096) + rescue EOFError + @pool.delete fd + end + end + end + end + + def translate_output + puts "Translating output..." + @output = [] + @buffers.each do |fd, buffer| + strio = StringIO.new(buffer) + logs = [] + begin + loop do + logs << Marshal.load(strio) + end + rescue EOFError + @output << logs + end + end + end + + def analyze_output + puts "Analyzing output..." + call_times = [] + client_times = [] + connection_failures = [] + connection_errors = [] + shortest_call = 0 + shortest_client = 0 + longest_call = 0 + longest_client = 0 + @output.each do |logs| + cur_call, cur_client = nil + logs.each do |tok, time| + case tok + when :start + cur_client = time + when :call_start + cur_call = time + when :call_end + delta = time - cur_call + call_times << delta + longest_call = delta unless longest_call > delta + shortest_call = delta if shortest_call == 0 or delta < shortest_call + cur_call = nil + when :end + delta = time - cur_client + client_times << delta + longest_client = delta unless longest_client > delta + shortest_client = delta if shortest_client == 0 or delta < shortest_client + cur_client = nil + when :connection_failure + connection_failures << time + when :connection_error + connection_errors << time + end + end + end + @report = {} + @report[:total_calls] = call_times.inject(0.0) { |a,t| a += t } + @report[:avg_calls] = @report[:total_calls] / call_times.size + @report[:total_clients] = client_times.inject(0.0) { |a,t| a += t } + @report[:avg_clients] = @report[:total_clients] / client_times.size + @report[:connection_failures] = connection_failures.size + @report[:connection_errors] = connection_errors.size + @report[:shortest_call] = shortest_call + @report[:shortest_client] = shortest_client + @report[:longest_call] = longest_call + @report[:longest_client] = longest_client + @report[:total_benchmark_time] = @benchmark_end - @benchmark_start + @report[:fastthread] = $".include?('fastthread.bundle') + end + + def report_output + fmt = "%.4f seconds" + puts + tabulate "%d", + [["Server class", "%s"], @server.serverclass == Object ? "" : @server.serverclass], + [["Server interpreter", "%s"], @server.interpreter], + [["Client interpreter", "%s"], @interpreter], + [["Socket class", "%s"], socket_class], + ["Number of processes", @num_processes], + ["Clients per process", @clients_per_process], + ["Calls per client", @calls_per_client], + [["Using fastthread", "%s"], @report[:fastthread] ? "yes" : "no"] + puts + failures = (@report[:connection_failures] > 0) + tabulate fmt, + [["Connection failures", "%d", [:red, :bold]], @report[:connection_failures]], + [["Connection errors", "%d", [:red, :bold]], @report[:connection_errors]], + ["Average time per call", @report[:avg_calls]], + ["Average time per client (%d calls)" % @calls_per_client, @report[:avg_clients]], + ["Total time for all calls", @report[:total_calls]], + ["Real time for benchmarking", @report[:total_benchmark_time]], + ["Shortest call time", @report[:shortest_call]], + ["Longest call time", @report[:longest_call]], + ["Shortest client time (%d calls)" % @calls_per_client, @report[:shortest_client]], + ["Longest client time (%d calls)" % @calls_per_client, @report[:longest_client]] + end + + ANSI = { + :reset => 0, + :bold => 1, + :black => 30, + :red => 31, + :green => 32, + :yellow => 33, + :blue => 34, + :magenta => 35, + :cyan => 36, + :white => 37 + } + + def tabulate(fmt, *labels_and_values) + labels = labels_and_values.map { |l| Array === l ? l.first : l } + label_width = labels.inject(0) { |w,l| l.size > w ? l.size : w } + labels_and_values.each do |(l,v)| + f = fmt + l, f, c = l if Array === l + fmtstr = "%-#{label_width+1}s #{f}" + if STDOUT.tty? and c and v.to_i > 0 + fmtstr = "\e[#{[*c].map { |x| ANSI[x] } * ";"}m" + fmtstr + "\e[#{ANSI[:reset]}m" + end + puts fmtstr % [l+":", v] + end + end +end + +def resolve_const(const) + const and const.split('::').inject(Object) { |k,c| k.const_get(c) } +end + +puts "Starting server..." +args = {} +args[:interpreter] = ENV['THRIFT_SERVER_INTERPRETER'] || ENV['THRIFT_INTERPRETER'] || "ruby" +args[:class] = resolve_const(ENV['THRIFT_SERVER']) || Thrift::NonblockingServer +args[:host] = ENV['THRIFT_HOST'] || HOST +args[:port] = (ENV['THRIFT_PORT'] || PORT).to_i +server = Server.new(args) +server.start + +args = {} +args[:host] = ENV['THRIFT_HOST'] || HOST +args[:port] = (ENV['THRIFT_PORT'] || PORT).to_i +args[:num_processes] = (ENV['THRIFT_NUM_PROCESSES'] || 40).to_i +args[:clients_per_process] = (ENV['THRIFT_NUM_CLIENTS'] || 5).to_i +args[:calls_per_client] = (ENV['THRIFT_NUM_CALLS'] || 50).to_i +args[:interpreter] = ENV['THRIFT_CLIENT_INTERPRETER'] || ENV['THRIFT_INTERPRETER'] || "ruby" +args[:log_exceptions] = !!ENV['THRIFT_LOG_EXCEPTIONS'] +BenchmarkManager.new(args, server).run + +server.shutdown diff --git a/src/jaegertracing/thrift/lib/rb/benchmark/client.rb b/src/jaegertracing/thrift/lib/rb/benchmark/client.rb new file mode 100644 index 000000000..703dc8f52 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/benchmark/client.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. +# + +$:.unshift File.dirname(__FILE__) + '/../lib' +require 'thrift' +$:.unshift File.dirname(__FILE__) + "/gen-rb" +require 'benchmark_service' + +class Client + def initialize(host, port, clients_per_process, calls_per_client, log_exceptions) + @host = host + @port = port + @clients_per_process = clients_per_process + @calls_per_client = calls_per_client + @log_exceptions = log_exceptions + end + + def run + @clients_per_process.times do + socket = Thrift::Socket.new(@host, @port) + transport = Thrift::FramedTransport.new(socket) + protocol = Thrift::BinaryProtocol.new(transport) + client = ThriftBenchmark::BenchmarkService::Client.new(protocol) + begin + start = Time.now + transport.open + Marshal.dump [:start, start], STDOUT + rescue => e + Marshal.dump [:connection_failure, Time.now], STDOUT + print_exception e if @log_exceptions + else + begin + @calls_per_client.times do + Marshal.dump [:call_start, Time.now], STDOUT + client.fibonacci(15) + Marshal.dump [:call_end, Time.now], STDOUT + end + transport.close + Marshal.dump [:end, Time.now], STDOUT + rescue Thrift::TransportException => e + Marshal.dump [:connection_error, Time.now], STDOUT + print_exception e if @log_exceptions + end + end + end + end + + def print_exception(e) + STDERR.puts "ERROR: #{e.message}" + STDERR.puts "\t#{e.backtrace * "\n\t"}" + end +end + +log_exceptions = true if ARGV[0] == '-log-exceptions' and ARGV.shift + +host, port, clients_per_process, calls_per_client = ARGV + +Client.new(host, port.to_i, clients_per_process.to_i, calls_per_client.to_i, log_exceptions).run diff --git a/src/jaegertracing/thrift/lib/rb/benchmark/server.rb b/src/jaegertracing/thrift/lib/rb/benchmark/server.rb new file mode 100644 index 000000000..74e13f414 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/benchmark/server.rb @@ -0,0 +1,82 @@ +# +# 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. +# + +$:.unshift File.dirname(__FILE__) + '/../lib' +require 'thrift' +$:.unshift File.dirname(__FILE__) + "/gen-rb" +require 'benchmark_service' + +module Server + include Thrift + + class BenchmarkHandler + # 1-based index into the fibonacci sequence + def fibonacci(n) + seq = [1, 1] + 3.upto(n) do + seq << seq[-1] + seq[-2] + end + seq[n-1] # n is 1-based + end + end + + def self.start_server(host, port, serverClass) + handler = BenchmarkHandler.new + processor = ThriftBenchmark::BenchmarkService::Processor.new(handler) + transport = ServerSocket.new(host, port) + transport_factory = FramedTransportFactory.new + args = [processor, transport, transport_factory, nil, 20] + if serverClass == NonblockingServer + logger = Logger.new(STDERR) + logger.level = Logger::WARN + args << logger + end + server = serverClass.new(*args) + @server_thread = Thread.new do + server.serve + end + @server = server + end + + def self.shutdown + return if @server.nil? + if @server.respond_to? :shutdown + @server.shutdown + else + @server_thread.kill + end + end +end + +def resolve_const(const) + const and const.split('::').inject(Object) { |k,c| k.const_get(c) } +end + +host, port, serverklass = ARGV + +Server.start_server(host, port.to_i, resolve_const(serverklass)) + +# let our host know that the interpreter has started +# ideally we'd wait until the server was serving, but we don't have a hook for that +Marshal.dump(:started, STDOUT) +STDOUT.flush + +Marshal.load(STDIN) # wait until we're instructed to shut down + +Server.shutdown diff --git a/src/jaegertracing/thrift/lib/rb/benchmark/thin_server.rb b/src/jaegertracing/thrift/lib/rb/benchmark/thin_server.rb new file mode 100644 index 000000000..4de2eef38 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/benchmark/thin_server.rb @@ -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. +# + +$:.unshift File.dirname(__FILE__) + '/../lib' +require 'thrift' +$:.unshift File.dirname(__FILE__) + "/gen-rb" +require 'benchmark_service' +HOST = 'localhost' +PORT = 42587 + +class BenchmarkHandler + # 1-based index into the fibonacci sequence + def fibonacci(n) + seq = [1, 1] + 3.upto(n) do + seq << seq[-1] + seq[-2] + end + seq[n-1] # n is 1-based + end +end + +handler = BenchmarkHandler.new +processor = ThriftBenchmark::BenchmarkService::Processor.new(handler) +transport = Thrift::ServerSocket.new(HOST, PORT) +transport_factory = Thrift::FramedTransportFactory.new +logger = Logger.new(STDERR) +logger.level = Logger::WARN +Thrift::NonblockingServer.new(processor, transport, transport_factory, nil, 20, logger).serve diff --git a/src/jaegertracing/thrift/lib/rb/coding_standards.md b/src/jaegertracing/thrift/lib/rb/coding_standards.md new file mode 100644 index 000000000..fa0390bb5 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/coding_standards.md @@ -0,0 +1 @@ +Please follow [General Coding Standards](/doc/coding_standards.md) diff --git a/src/jaegertracing/thrift/lib/rb/ext/binary_protocol_accelerated.c b/src/jaegertracing/thrift/lib/rb/ext/binary_protocol_accelerated.c new file mode 100644 index 000000000..65cbe5ffe --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/ext/binary_protocol_accelerated.c @@ -0,0 +1,460 @@ +/** + * 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. + */ + +#include <ruby.h> +#include <stdbool.h> +#include <stdint.h> +#include <constants.h> +#include <struct.h> +#include <macros.h> +#include <bytes.h> + +VALUE rb_thrift_binary_proto_native_qmark(VALUE self) { + return Qtrue; +} + + + +static int VERSION_1; +static int VERSION_MASK; +static int TYPE_MASK; +static int BAD_VERSION; +static ID rbuf_ivar_id; + +static void write_byte_direct(VALUE trans, int8_t b) { + WRITE(trans, (char*)&b, 1); +} + +static void write_i16_direct(VALUE trans, int16_t value) { + char data[2]; + + data[1] = value; + data[0] = (value >> 8); + + WRITE(trans, data, 2); +} + +static void write_i32_direct(VALUE trans, int32_t value) { + char data[4]; + + data[3] = value; + data[2] = (value >> 8); + data[1] = (value >> 16); + data[0] = (value >> 24); + + WRITE(trans, data, 4); +} + + +static void write_i64_direct(VALUE trans, int64_t value) { + char data[8]; + + data[7] = value; + data[6] = (value >> 8); + data[5] = (value >> 16); + data[4] = (value >> 24); + data[3] = (value >> 32); + data[2] = (value >> 40); + data[1] = (value >> 48); + data[0] = (value >> 56); + + WRITE(trans, data, 8); +} + +static void write_string_direct(VALUE trans, VALUE str) { + if (TYPE(str) != T_STRING) { + rb_raise(rb_eStandardError, "Value should be a string"); + } + str = convert_to_utf8_byte_buffer(str); + write_i32_direct(trans, RSTRING_LEN(str)); + rb_funcall(trans, write_method_id, 1, str); +} + +//-------------------------------- +// interface writing methods +//-------------------------------- + +VALUE rb_thrift_binary_proto_write_message_end(VALUE self) { + return Qnil; +} + +VALUE rb_thrift_binary_proto_write_struct_begin(VALUE self, VALUE name) { + return Qnil; +} + +VALUE rb_thrift_binary_proto_write_struct_end(VALUE self) { + return Qnil; +} + +VALUE rb_thrift_binary_proto_write_field_end(VALUE self) { + return Qnil; +} + +VALUE rb_thrift_binary_proto_write_map_end(VALUE self) { + return Qnil; +} + +VALUE rb_thrift_binary_proto_write_list_end(VALUE self) { + return Qnil; +} + +VALUE rb_thrift_binary_proto_write_set_end(VALUE self) { + return Qnil; +} + +VALUE rb_thrift_binary_proto_write_message_begin(VALUE self, VALUE name, VALUE type, VALUE seqid) { + VALUE trans = GET_TRANSPORT(self); + VALUE strict_write = GET_STRICT_WRITE(self); + + if (strict_write == Qtrue) { + write_i32_direct(trans, VERSION_1 | FIX2INT(type)); + write_string_direct(trans, name); + write_i32_direct(trans, FIX2INT(seqid)); + } else { + write_string_direct(trans, name); + write_byte_direct(trans, FIX2INT(type)); + write_i32_direct(trans, FIX2INT(seqid)); + } + + return Qnil; +} + +VALUE rb_thrift_binary_proto_write_field_begin(VALUE self, VALUE name, VALUE type, VALUE id) { + VALUE trans = GET_TRANSPORT(self); + write_byte_direct(trans, FIX2INT(type)); + write_i16_direct(trans, FIX2INT(id)); + + return Qnil; +} + +VALUE rb_thrift_binary_proto_write_field_stop(VALUE self) { + write_byte_direct(GET_TRANSPORT(self), TTYPE_STOP); + return Qnil; +} + +VALUE rb_thrift_binary_proto_write_map_begin(VALUE self, VALUE ktype, VALUE vtype, VALUE size) { + VALUE trans = GET_TRANSPORT(self); + write_byte_direct(trans, FIX2INT(ktype)); + write_byte_direct(trans, FIX2INT(vtype)); + write_i32_direct(trans, FIX2INT(size)); + + return Qnil; +} + +VALUE rb_thrift_binary_proto_write_list_begin(VALUE self, VALUE etype, VALUE size) { + VALUE trans = GET_TRANSPORT(self); + write_byte_direct(trans, FIX2INT(etype)); + write_i32_direct(trans, FIX2INT(size)); + + return Qnil; +} + +VALUE rb_thrift_binary_proto_write_set_begin(VALUE self, VALUE etype, VALUE size) { + rb_thrift_binary_proto_write_list_begin(self, etype, size); + return Qnil; +} + +VALUE rb_thrift_binary_proto_write_bool(VALUE self, VALUE b) { + write_byte_direct(GET_TRANSPORT(self), RTEST(b) ? 1 : 0); + return Qnil; +} + +VALUE rb_thrift_binary_proto_write_byte(VALUE self, VALUE byte) { + CHECK_NIL(byte); + write_byte_direct(GET_TRANSPORT(self), NUM2INT(byte)); + return Qnil; +} + +VALUE rb_thrift_binary_proto_write_i16(VALUE self, VALUE i16) { + CHECK_NIL(i16); + write_i16_direct(GET_TRANSPORT(self), FIX2INT(i16)); + return Qnil; +} + +VALUE rb_thrift_binary_proto_write_i32(VALUE self, VALUE i32) { + CHECK_NIL(i32); + write_i32_direct(GET_TRANSPORT(self), NUM2INT(i32)); + return Qnil; +} + +VALUE rb_thrift_binary_proto_write_i64(VALUE self, VALUE i64) { + CHECK_NIL(i64); + write_i64_direct(GET_TRANSPORT(self), NUM2LL(i64)); + return Qnil; +} + +VALUE rb_thrift_binary_proto_write_double(VALUE self, VALUE dub) { + CHECK_NIL(dub); + // Unfortunately, bitwise_cast doesn't work in C. Bad C! + union { + double f; + int64_t t; + } transfer; + transfer.f = RFLOAT_VALUE(rb_Float(dub)); + write_i64_direct(GET_TRANSPORT(self), transfer.t); + + return Qnil; +} + +VALUE rb_thrift_binary_proto_write_string(VALUE self, VALUE str) { + CHECK_NIL(str); + VALUE trans = GET_TRANSPORT(self); + write_string_direct(trans, str); + return Qnil; +} + +VALUE rb_thrift_binary_proto_write_binary(VALUE self, VALUE buf) { + CHECK_NIL(buf); + VALUE trans = GET_TRANSPORT(self); + buf = force_binary_encoding(buf); + write_i32_direct(trans, RSTRING_LEN(buf)); + rb_funcall(trans, write_method_id, 1, buf); + return Qnil; +} + +//--------------------------------------- +// interface reading methods +//--------------------------------------- + +VALUE rb_thrift_binary_proto_read_string(VALUE self); +VALUE rb_thrift_binary_proto_read_binary(VALUE self); +VALUE rb_thrift_binary_proto_read_byte(VALUE self); +VALUE rb_thrift_binary_proto_read_i32(VALUE self); +VALUE rb_thrift_binary_proto_read_i16(VALUE self); + +static char read_byte_direct(VALUE self) { + VALUE byte = rb_funcall(GET_TRANSPORT(self), read_byte_method_id, 0); + return (char)(FIX2INT(byte)); +} + +static int16_t read_i16_direct(VALUE self) { + VALUE rbuf = rb_ivar_get(self, rbuf_ivar_id); + rb_funcall(GET_TRANSPORT(self), read_into_buffer_method_id, 2, rbuf, INT2FIX(2)); + return (int16_t)(((uint8_t)(RSTRING_PTR(rbuf)[1])) | ((uint16_t)((RSTRING_PTR(rbuf)[0]) << 8))); +} + +static int32_t read_i32_direct(VALUE self) { + VALUE rbuf = rb_ivar_get(self, rbuf_ivar_id); + rb_funcall(GET_TRANSPORT(self), read_into_buffer_method_id, 2, rbuf, INT2FIX(4)); + return ((uint8_t)(RSTRING_PTR(rbuf)[3])) | + (((uint8_t)(RSTRING_PTR(rbuf)[2])) << 8) | + (((uint8_t)(RSTRING_PTR(rbuf)[1])) << 16) | + (((uint8_t)(RSTRING_PTR(rbuf)[0])) << 24); +} + +static int64_t read_i64_direct(VALUE self) { + VALUE rbuf = rb_ivar_get(self, rbuf_ivar_id); + rb_funcall(GET_TRANSPORT(self), read_into_buffer_method_id, 2, rbuf, INT2FIX(8)); + uint64_t hi = ((uint8_t)(RSTRING_PTR(rbuf)[3])) | + (((uint8_t)(RSTRING_PTR(rbuf)[2])) << 8) | + (((uint8_t)(RSTRING_PTR(rbuf)[1])) << 16) | + (((uint8_t)(RSTRING_PTR(rbuf)[0])) << 24); + uint32_t lo = ((uint8_t)(RSTRING_PTR(rbuf)[7])) | + (((uint8_t)(RSTRING_PTR(rbuf)[6])) << 8) | + (((uint8_t)(RSTRING_PTR(rbuf)[5])) << 16) | + (((uint8_t)(RSTRING_PTR(rbuf)[4])) << 24); + return (hi << 32) | lo; +} + +static VALUE get_protocol_exception(VALUE code, VALUE message) { + VALUE args[2]; + args[0] = code; + args[1] = message; + return rb_class_new_instance(2, (VALUE*)&args, protocol_exception_class); +} + +VALUE rb_thrift_binary_proto_read_message_end(VALUE self) { + return Qnil; +} + +VALUE rb_thrift_binary_proto_read_struct_begin(VALUE self) { + return Qnil; +} + +VALUE rb_thrift_binary_proto_read_struct_end(VALUE self) { + return Qnil; +} + +VALUE rb_thrift_binary_proto_read_field_end(VALUE self) { + return Qnil; +} + +VALUE rb_thrift_binary_proto_read_map_end(VALUE self) { + return Qnil; +} + +VALUE rb_thrift_binary_proto_read_list_end(VALUE self) { + return Qnil; +} + +VALUE rb_thrift_binary_proto_read_set_end(VALUE self) { + return Qnil; +} + +VALUE rb_thrift_binary_proto_read_message_begin(VALUE self) { + VALUE strict_read = GET_STRICT_READ(self); + VALUE name, seqid; + int type; + + int version = read_i32_direct(self); + + if (version < 0) { + if ((version & VERSION_MASK) != VERSION_1) { + rb_exc_raise(get_protocol_exception(INT2FIX(BAD_VERSION), rb_str_new2("Missing version identifier"))); + } + type = version & TYPE_MASK; + name = rb_thrift_binary_proto_read_string(self); + seqid = rb_thrift_binary_proto_read_i32(self); + } else { + if (strict_read == Qtrue) { + rb_exc_raise(get_protocol_exception(INT2FIX(BAD_VERSION), rb_str_new2("No version identifier, old protocol client?"))); + } + name = READ(self, version); + type = read_byte_direct(self); + seqid = rb_thrift_binary_proto_read_i32(self); + } + + return rb_ary_new3(3, name, INT2FIX(type), seqid); +} + +VALUE rb_thrift_binary_proto_read_field_begin(VALUE self) { + int type = read_byte_direct(self); + if (type == TTYPE_STOP) { + return rb_ary_new3(3, Qnil, INT2FIX(type), INT2FIX(0)); + } else { + VALUE id = rb_thrift_binary_proto_read_i16(self); + return rb_ary_new3(3, Qnil, INT2FIX(type), id); + } +} + +VALUE rb_thrift_binary_proto_read_map_begin(VALUE self) { + VALUE ktype = rb_thrift_binary_proto_read_byte(self); + VALUE vtype = rb_thrift_binary_proto_read_byte(self); + VALUE size = rb_thrift_binary_proto_read_i32(self); + return rb_ary_new3(3, ktype, vtype, size); +} + +VALUE rb_thrift_binary_proto_read_list_begin(VALUE self) { + VALUE etype = rb_thrift_binary_proto_read_byte(self); + VALUE size = rb_thrift_binary_proto_read_i32(self); + return rb_ary_new3(2, etype, size); +} + +VALUE rb_thrift_binary_proto_read_set_begin(VALUE self) { + return rb_thrift_binary_proto_read_list_begin(self); +} + +VALUE rb_thrift_binary_proto_read_bool(VALUE self) { + char byte = read_byte_direct(self); + return byte != 0 ? Qtrue : Qfalse; +} + +VALUE rb_thrift_binary_proto_read_byte(VALUE self) { + return INT2FIX(read_byte_direct(self)); +} + +VALUE rb_thrift_binary_proto_read_i16(VALUE self) { + return INT2FIX(read_i16_direct(self)); +} + +VALUE rb_thrift_binary_proto_read_i32(VALUE self) { + return INT2NUM(read_i32_direct(self)); +} + +VALUE rb_thrift_binary_proto_read_i64(VALUE self) { + return LL2NUM(read_i64_direct(self)); +} + +VALUE rb_thrift_binary_proto_read_double(VALUE self) { + union { + double f; + int64_t t; + } transfer; + transfer.t = read_i64_direct(self); + return rb_float_new(transfer.f); +} + +VALUE rb_thrift_binary_proto_read_string(VALUE self) { + VALUE buffer = rb_thrift_binary_proto_read_binary(self); + return convert_to_string(buffer); +} + +VALUE rb_thrift_binary_proto_read_binary(VALUE self) { + int size = read_i32_direct(self); + return READ(self, size); +} + +void Init_binary_protocol_accelerated() { + VALUE thrift_binary_protocol_class = rb_const_get(thrift_module, rb_intern("BinaryProtocol")); + + VERSION_1 = rb_num2ll(rb_const_get(thrift_binary_protocol_class, rb_intern("VERSION_1"))); + VERSION_MASK = rb_num2ll(rb_const_get(thrift_binary_protocol_class, rb_intern("VERSION_MASK"))); + TYPE_MASK = rb_num2ll(rb_const_get(thrift_binary_protocol_class, rb_intern("TYPE_MASK"))); + + VALUE bpa_class = rb_define_class_under(thrift_module, "BinaryProtocolAccelerated", thrift_binary_protocol_class); + + rb_define_method(bpa_class, "native?", rb_thrift_binary_proto_native_qmark, 0); + + rb_define_method(bpa_class, "write_message_begin", rb_thrift_binary_proto_write_message_begin, 3); + rb_define_method(bpa_class, "write_field_begin", rb_thrift_binary_proto_write_field_begin, 3); + rb_define_method(bpa_class, "write_field_stop", rb_thrift_binary_proto_write_field_stop, 0); + rb_define_method(bpa_class, "write_map_begin", rb_thrift_binary_proto_write_map_begin, 3); + rb_define_method(bpa_class, "write_list_begin", rb_thrift_binary_proto_write_list_begin, 2); + rb_define_method(bpa_class, "write_set_begin", rb_thrift_binary_proto_write_set_begin, 2); + rb_define_method(bpa_class, "write_byte", rb_thrift_binary_proto_write_byte, 1); + rb_define_method(bpa_class, "write_bool", rb_thrift_binary_proto_write_bool, 1); + rb_define_method(bpa_class, "write_i16", rb_thrift_binary_proto_write_i16, 1); + rb_define_method(bpa_class, "write_i32", rb_thrift_binary_proto_write_i32, 1); + rb_define_method(bpa_class, "write_i64", rb_thrift_binary_proto_write_i64, 1); + rb_define_method(bpa_class, "write_double", rb_thrift_binary_proto_write_double, 1); + rb_define_method(bpa_class, "write_string", rb_thrift_binary_proto_write_string, 1); + rb_define_method(bpa_class, "write_binary", rb_thrift_binary_proto_write_binary, 1); + // unused methods + rb_define_method(bpa_class, "write_message_end", rb_thrift_binary_proto_write_message_end, 0); + rb_define_method(bpa_class, "write_struct_begin", rb_thrift_binary_proto_write_struct_begin, 1); + rb_define_method(bpa_class, "write_struct_end", rb_thrift_binary_proto_write_struct_end, 0); + rb_define_method(bpa_class, "write_field_end", rb_thrift_binary_proto_write_field_end, 0); + rb_define_method(bpa_class, "write_map_end", rb_thrift_binary_proto_write_map_end, 0); + rb_define_method(bpa_class, "write_list_end", rb_thrift_binary_proto_write_list_end, 0); + rb_define_method(bpa_class, "write_set_end", rb_thrift_binary_proto_write_set_end, 0); + + rb_define_method(bpa_class, "read_message_begin", rb_thrift_binary_proto_read_message_begin, 0); + rb_define_method(bpa_class, "read_field_begin", rb_thrift_binary_proto_read_field_begin, 0); + rb_define_method(bpa_class, "read_map_begin", rb_thrift_binary_proto_read_map_begin, 0); + rb_define_method(bpa_class, "read_list_begin", rb_thrift_binary_proto_read_list_begin, 0); + rb_define_method(bpa_class, "read_set_begin", rb_thrift_binary_proto_read_set_begin, 0); + rb_define_method(bpa_class, "read_byte", rb_thrift_binary_proto_read_byte, 0); + rb_define_method(bpa_class, "read_bool", rb_thrift_binary_proto_read_bool, 0); + rb_define_method(bpa_class, "read_i16", rb_thrift_binary_proto_read_i16, 0); + rb_define_method(bpa_class, "read_i32", rb_thrift_binary_proto_read_i32, 0); + rb_define_method(bpa_class, "read_i64", rb_thrift_binary_proto_read_i64, 0); + rb_define_method(bpa_class, "read_double", rb_thrift_binary_proto_read_double, 0); + rb_define_method(bpa_class, "read_string", rb_thrift_binary_proto_read_string, 0); + rb_define_method(bpa_class, "read_binary", rb_thrift_binary_proto_read_binary, 0); + // unused methods + rb_define_method(bpa_class, "read_message_end", rb_thrift_binary_proto_read_message_end, 0); + rb_define_method(bpa_class, "read_struct_begin", rb_thrift_binary_proto_read_struct_begin, 0); + rb_define_method(bpa_class, "read_struct_end", rb_thrift_binary_proto_read_struct_end, 0); + rb_define_method(bpa_class, "read_field_end", rb_thrift_binary_proto_read_field_end, 0); + rb_define_method(bpa_class, "read_map_end", rb_thrift_binary_proto_read_map_end, 0); + rb_define_method(bpa_class, "read_list_end", rb_thrift_binary_proto_read_list_end, 0); + rb_define_method(bpa_class, "read_set_end", rb_thrift_binary_proto_read_set_end, 0); + + rbuf_ivar_id = rb_intern("@rbuf"); +} diff --git a/src/jaegertracing/thrift/lib/rb/ext/binary_protocol_accelerated.h b/src/jaegertracing/thrift/lib/rb/ext/binary_protocol_accelerated.h new file mode 100644 index 000000000..37baf4142 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/ext/binary_protocol_accelerated.h @@ -0,0 +1,20 @@ +/* + * 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. + */ + +void Init_binary_protocol_accelerated(); diff --git a/src/jaegertracing/thrift/lib/rb/ext/bytes.c b/src/jaegertracing/thrift/lib/rb/ext/bytes.c new file mode 100644 index 000000000..8a6fac4ac --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/ext/bytes.c @@ -0,0 +1,36 @@ +/** + * 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. + */ + +#include <ruby.h> +#ifdef HAVE_RUBY_ENCODING_H +#include <ruby/encoding.h> +#endif +#include <constants.h> + +VALUE force_binary_encoding(VALUE buffer) { + return rb_funcall(thrift_bytes_module, force_binary_encoding_id, 1, buffer); +} + +VALUE convert_to_utf8_byte_buffer(VALUE string) { + return rb_funcall(thrift_bytes_module, convert_to_utf8_byte_buffer_id, 1, string); +} + +VALUE convert_to_string(VALUE utf8_buffer) { + return rb_funcall(thrift_bytes_module, convert_to_string_id, 1, utf8_buffer); +} diff --git a/src/jaegertracing/thrift/lib/rb/ext/bytes.h b/src/jaegertracing/thrift/lib/rb/ext/bytes.h new file mode 100644 index 000000000..7108d83ff --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/ext/bytes.h @@ -0,0 +1,31 @@ +/** + * 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. + */ + +#include <ruby.h> + +/* + * A collection of utilities for working with bytes and byte buffers. + * + * These methods are the native analogies to some of the methods in + * Thrift::Bytes (thrift/bytes.rb). + */ + +VALUE force_binary_encoding(VALUE buffer); +VALUE convert_to_utf8_byte_buffer(VALUE string); +VALUE convert_to_string(VALUE utf8_buffer); diff --git a/src/jaegertracing/thrift/lib/rb/ext/compact_protocol.c b/src/jaegertracing/thrift/lib/rb/ext/compact_protocol.c new file mode 100644 index 000000000..c0f46b958 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/ext/compact_protocol.c @@ -0,0 +1,637 @@ +/** + * 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. + */ + +#include <ruby.h> +#include <stdbool.h> +#include <stdint.h> +#include <constants.h> +#include <struct.h> +#include <macros.h> +#include <bytes.h> + +#define LAST_ID(obj) FIX2INT(rb_ary_pop(rb_ivar_get(obj, last_field_id))) +#define SET_LAST_ID(obj, val) rb_ary_push(rb_ivar_get(obj, last_field_id), val) + +VALUE rb_thrift_compact_proto_native_qmark(VALUE self) { + return Qtrue; +} + +static ID last_field_id; +static ID boolean_field_id; +static ID bool_value_id; +static ID rbuf_ivar_id; + +static int VERSION; +static int VERSION_MASK; +static int TYPE_MASK; +static int TYPE_BITS; +static int TYPE_SHIFT_AMOUNT; +static int PROTOCOL_ID; + +static VALUE thrift_compact_protocol_class; + +static int CTYPE_BOOLEAN_TRUE = 0x01; +static int CTYPE_BOOLEAN_FALSE = 0x02; +static int CTYPE_BYTE = 0x03; +static int CTYPE_I16 = 0x04; +static int CTYPE_I32 = 0x05; +static int CTYPE_I64 = 0x06; +static int CTYPE_DOUBLE = 0x07; +static int CTYPE_BINARY = 0x08; +static int CTYPE_LIST = 0x09; +static int CTYPE_SET = 0x0A; +static int CTYPE_MAP = 0x0B; +static int CTYPE_STRUCT = 0x0C; + +VALUE rb_thrift_compact_proto_write_i16(VALUE self, VALUE i16); + +// TODO: implement this +static int get_compact_type(VALUE type_value) { + int type = FIX2INT(type_value); + if (type == TTYPE_BOOL) { + return CTYPE_BOOLEAN_TRUE; + } else if (type == TTYPE_BYTE) { + return CTYPE_BYTE; + } else if (type == TTYPE_I16) { + return CTYPE_I16; + } else if (type == TTYPE_I32) { + return CTYPE_I32; + } else if (type == TTYPE_I64) { + return CTYPE_I64; + } else if (type == TTYPE_DOUBLE) { + return CTYPE_DOUBLE; + } else if (type == TTYPE_STRING) { + return CTYPE_BINARY; + } else if (type == TTYPE_LIST) { + return CTYPE_LIST; + } else if (type == TTYPE_SET) { + return CTYPE_SET; + } else if (type == TTYPE_MAP) { + return CTYPE_MAP; + } else if (type == TTYPE_STRUCT) { + return CTYPE_STRUCT; + } else { + char str[50]; + sprintf(str, "don't know what type: %d", type); + rb_raise(rb_eStandardError, "%s", str); + return 0; + } +} + +static void write_byte_direct(VALUE transport, int8_t b) { + WRITE(transport, (char*)&b, 1); +} + +static void write_field_begin_internal(VALUE self, VALUE type, VALUE id_value, VALUE type_override) { + int id = FIX2INT(id_value); + int last_id = LAST_ID(self); + VALUE transport = GET_TRANSPORT(self); + + // if there's a type override, use that. + int8_t type_to_write = RTEST(type_override) ? FIX2INT(type_override) : get_compact_type(type); + // check if we can use delta encoding for the field id + int diff = id - last_id; + if (diff > 0 && diff <= 15) { + // write them together + write_byte_direct(transport, diff << 4 | (type_to_write & 0x0f)); + } else { + // write them separate + write_byte_direct(transport, type_to_write & 0x0f); + rb_thrift_compact_proto_write_i16(self, id_value); + } + + SET_LAST_ID(self, id_value); +} + +static int32_t int_to_zig_zag(int32_t n) { + return (n << 1) ^ (n >> 31); +} + +static uint64_t ll_to_zig_zag(int64_t n) { + return (n << 1) ^ (n >> 63); +} + +static void write_varint32(VALUE transport, uint32_t n) { + while (true) { + if ((n & ~0x7F) == 0) { + write_byte_direct(transport, n & 0x7f); + break; + } else { + write_byte_direct(transport, (n & 0x7F) | 0x80); + n = n >> 7; + } + } +} + +static void write_varint64(VALUE transport, uint64_t n) { + while (true) { + if ((n & ~0x7F) == 0) { + write_byte_direct(transport, n & 0x7f); + break; + } else { + write_byte_direct(transport, (n & 0x7F) | 0x80); + n = n >> 7; + } + } +} + +static void write_collection_begin(VALUE transport, VALUE elem_type, VALUE size_value) { + int size = FIX2INT(size_value); + if (size <= 14) { + write_byte_direct(transport, size << 4 | get_compact_type(elem_type)); + } else { + write_byte_direct(transport, 0xf0 | get_compact_type(elem_type)); + write_varint32(transport, size); + } +} + + +//-------------------------------- +// interface writing methods +//-------------------------------- + +VALUE rb_thrift_compact_proto_write_i32(VALUE self, VALUE i32); +VALUE rb_thrift_compact_proto_write_string(VALUE self, VALUE str); +VALUE rb_thrift_compact_proto_write_binary(VALUE self, VALUE buf); + +VALUE rb_thrift_compact_proto_write_message_end(VALUE self) { + return Qnil; +} + +VALUE rb_thrift_compact_proto_write_struct_begin(VALUE self, VALUE name) { + rb_ary_push(rb_ivar_get(self, last_field_id), INT2FIX(0)); + return Qnil; +} + +VALUE rb_thrift_compact_proto_write_struct_end(VALUE self) { + rb_ary_pop(rb_ivar_get(self, last_field_id)); + return Qnil; +} + +VALUE rb_thrift_compact_proto_write_field_end(VALUE self) { + return Qnil; +} + +VALUE rb_thrift_compact_proto_write_map_end(VALUE self) { + return Qnil; +} + +VALUE rb_thrift_compact_proto_write_list_end(VALUE self) { + return Qnil; +} + +VALUE rb_thrift_compact_proto_write_set_end(VALUE self) { + return Qnil; +} + +VALUE rb_thrift_compact_proto_write_message_begin(VALUE self, VALUE name, VALUE type, VALUE seqid) { + VALUE transport = GET_TRANSPORT(self); + write_byte_direct(transport, PROTOCOL_ID); + write_byte_direct(transport, (VERSION & VERSION_MASK) | ((FIX2INT(type) << TYPE_SHIFT_AMOUNT) & TYPE_MASK)); + write_varint32(transport, FIX2INT(seqid)); + rb_thrift_compact_proto_write_string(self, name); + + return Qnil; +} + +VALUE rb_thrift_compact_proto_write_field_begin(VALUE self, VALUE name, VALUE type, VALUE id) { + if (FIX2INT(type) == TTYPE_BOOL) { + // we want to possibly include the value, so we'll wait. + rb_ivar_set(self, boolean_field_id, rb_ary_new3(2, type, id)); + } else { + write_field_begin_internal(self, type, id, Qnil); + } + + return Qnil; +} + +VALUE rb_thrift_compact_proto_write_field_stop(VALUE self) { + write_byte_direct(GET_TRANSPORT(self), TTYPE_STOP); + return Qnil; +} + +VALUE rb_thrift_compact_proto_write_map_begin(VALUE self, VALUE ktype, VALUE vtype, VALUE size_value) { + int size = FIX2INT(size_value); + VALUE transport = GET_TRANSPORT(self); + if (size == 0) { + write_byte_direct(transport, 0); + } else { + write_varint32(transport, size); + write_byte_direct(transport, get_compact_type(ktype) << 4 | get_compact_type(vtype)); + } + return Qnil; +} + +VALUE rb_thrift_compact_proto_write_list_begin(VALUE self, VALUE etype, VALUE size) { + write_collection_begin(GET_TRANSPORT(self), etype, size); + return Qnil; +} + +VALUE rb_thrift_compact_proto_write_set_begin(VALUE self, VALUE etype, VALUE size) { + write_collection_begin(GET_TRANSPORT(self), etype, size); + return Qnil; +} + +VALUE rb_thrift_compact_proto_write_bool(VALUE self, VALUE b) { + int8_t type = b == Qtrue ? CTYPE_BOOLEAN_TRUE : CTYPE_BOOLEAN_FALSE; + VALUE boolean_field = rb_ivar_get(self, boolean_field_id); + if (NIL_P(boolean_field)) { + // we're not part of a field, so just write the value. + write_byte_direct(GET_TRANSPORT(self), type); + } else { + // we haven't written the field header yet + write_field_begin_internal(self, rb_ary_entry(boolean_field, 0), rb_ary_entry(boolean_field, 1), INT2FIX(type)); + rb_ivar_set(self, boolean_field_id, Qnil); + } + return Qnil; +} + +VALUE rb_thrift_compact_proto_write_byte(VALUE self, VALUE byte) { + CHECK_NIL(byte); + write_byte_direct(GET_TRANSPORT(self), FIX2INT(byte)); + return Qnil; +} + +VALUE rb_thrift_compact_proto_write_i16(VALUE self, VALUE i16) { + rb_thrift_compact_proto_write_i32(self, i16); + return Qnil; +} + +VALUE rb_thrift_compact_proto_write_i32(VALUE self, VALUE i32) { + CHECK_NIL(i32); + write_varint32(GET_TRANSPORT(self), int_to_zig_zag(NUM2INT(i32))); + return Qnil; +} + +VALUE rb_thrift_compact_proto_write_i64(VALUE self, VALUE i64) { + CHECK_NIL(i64); + write_varint64(GET_TRANSPORT(self), ll_to_zig_zag(NUM2LL(i64))); + return Qnil; +} + +VALUE rb_thrift_compact_proto_write_double(VALUE self, VALUE dub) { + CHECK_NIL(dub); + // Unfortunately, bitwise_cast doesn't work in C. Bad C! + union { + double f; + int64_t l; + } transfer; + transfer.f = RFLOAT_VALUE(rb_Float(dub)); + char buf[8]; + buf[0] = transfer.l & 0xff; + buf[1] = (transfer.l >> 8) & 0xff; + buf[2] = (transfer.l >> 16) & 0xff; + buf[3] = (transfer.l >> 24) & 0xff; + buf[4] = (transfer.l >> 32) & 0xff; + buf[5] = (transfer.l >> 40) & 0xff; + buf[6] = (transfer.l >> 48) & 0xff; + buf[7] = (transfer.l >> 56) & 0xff; + WRITE(GET_TRANSPORT(self), buf, 8); + return Qnil; +} + +VALUE rb_thrift_compact_proto_write_string(VALUE self, VALUE str) { + str = convert_to_utf8_byte_buffer(str); + rb_thrift_compact_proto_write_binary(self, str); + return Qnil; +} + +VALUE rb_thrift_compact_proto_write_binary(VALUE self, VALUE buf) { + buf = force_binary_encoding(buf); + VALUE transport = GET_TRANSPORT(self); + write_varint32(transport, RSTRING_LEN(buf)); + WRITE(transport, StringValuePtr(buf), RSTRING_LEN(buf)); + return Qnil; +} + +//--------------------------------------- +// interface reading methods +//--------------------------------------- + +#define is_bool_type(ctype) (((ctype) & 0x0F) == CTYPE_BOOLEAN_TRUE || ((ctype) & 0x0F) == CTYPE_BOOLEAN_FALSE) + +VALUE rb_thrift_compact_proto_read_string(VALUE self); +VALUE rb_thrift_compact_proto_read_binary(VALUE self); +VALUE rb_thrift_compact_proto_read_byte(VALUE self); +VALUE rb_thrift_compact_proto_read_i32(VALUE self); +VALUE rb_thrift_compact_proto_read_i16(VALUE self); + +static int8_t get_ttype(int8_t ctype) { + if (ctype == TTYPE_STOP) { + return TTYPE_STOP; + } else if (ctype == CTYPE_BOOLEAN_TRUE || ctype == CTYPE_BOOLEAN_FALSE) { + return TTYPE_BOOL; + } else if (ctype == CTYPE_BYTE) { + return TTYPE_BYTE; + } else if (ctype == CTYPE_I16) { + return TTYPE_I16; + } else if (ctype == CTYPE_I32) { + return TTYPE_I32; + } else if (ctype == CTYPE_I64) { + return TTYPE_I64; + } else if (ctype == CTYPE_DOUBLE) { + return TTYPE_DOUBLE; + } else if (ctype == CTYPE_BINARY) { + return TTYPE_STRING; + } else if (ctype == CTYPE_LIST) { + return TTYPE_LIST; + } else if (ctype == CTYPE_SET) { + return TTYPE_SET; + } else if (ctype == CTYPE_MAP) { + return TTYPE_MAP; + } else if (ctype == CTYPE_STRUCT) { + return TTYPE_STRUCT; + } else { + char str[50]; + sprintf(str, "don't know what type: %d", ctype); + rb_raise(rb_eStandardError, "%s", str); + return 0; + } +} + +static char read_byte_direct(VALUE self) { + VALUE byte = rb_funcall(GET_TRANSPORT(self), read_byte_method_id, 0); + return (char)(FIX2INT(byte)); +} + +static int64_t zig_zag_to_ll(int64_t n) { + return (((uint64_t)n) >> 1) ^ -(n & 1); +} + +static int32_t zig_zag_to_int(int32_t n) { + return (((uint32_t)n) >> 1) ^ -(n & 1); +} + +static int64_t read_varint64(VALUE self) { + int shift = 0; + int64_t result = 0; + while (true) { + int8_t b = read_byte_direct(self); + result = result | ((uint64_t)(b & 0x7f) << shift); + if ((b & 0x80) != 0x80) { + break; + } + shift += 7; + } + return result; +} + +static int16_t read_i16(VALUE self) { + return zig_zag_to_int((int32_t)read_varint64(self)); +} + +static VALUE get_protocol_exception(VALUE code, VALUE message) { + VALUE args[2]; + args[0] = code; + args[1] = message; + return rb_class_new_instance(2, (VALUE*)&args, protocol_exception_class); +} + +VALUE rb_thrift_compact_proto_read_message_end(VALUE self) { + return Qnil; +} + +VALUE rb_thrift_compact_proto_read_struct_begin(VALUE self) { + rb_ary_push(rb_ivar_get(self, last_field_id), INT2FIX(0)); + return Qnil; +} + +VALUE rb_thrift_compact_proto_read_struct_end(VALUE self) { + rb_ary_pop(rb_ivar_get(self, last_field_id)); + return Qnil; +} + +VALUE rb_thrift_compact_proto_read_field_end(VALUE self) { + return Qnil; +} + +VALUE rb_thrift_compact_proto_read_map_end(VALUE self) { + return Qnil; +} + +VALUE rb_thrift_compact_proto_read_list_end(VALUE self) { + return Qnil; +} + +VALUE rb_thrift_compact_proto_read_set_end(VALUE self) { + return Qnil; +} + +VALUE rb_thrift_compact_proto_read_message_begin(VALUE self) { + int8_t protocol_id = read_byte_direct(self); + if (protocol_id != PROTOCOL_ID) { + char buf[100]; + int len = sprintf(buf, "Expected protocol id %d but got %d", PROTOCOL_ID, protocol_id); + buf[len] = 0; + rb_exc_raise(get_protocol_exception(INT2FIX(-1), rb_str_new2(buf))); + } + + int8_t version_and_type = read_byte_direct(self); + int8_t version = version_and_type & VERSION_MASK; + if (version != VERSION) { + char buf[100]; + int len = sprintf(buf, "Expected version id %d but got %d", version, VERSION); + buf[len] = 0; + rb_exc_raise(get_protocol_exception(INT2FIX(-1), rb_str_new2(buf))); + } + + int8_t type = (version_and_type >> TYPE_SHIFT_AMOUNT) & TYPE_BITS; + int32_t seqid = read_varint64(self); + VALUE messageName = rb_thrift_compact_proto_read_string(self); + return rb_ary_new3(3, messageName, INT2FIX(type), INT2NUM(seqid)); +} + +VALUE rb_thrift_compact_proto_read_field_begin(VALUE self) { + int8_t type = read_byte_direct(self); + // if it's a stop, then we can return immediately, as the struct is over. + if ((type & 0x0f) == TTYPE_STOP) { + return rb_ary_new3(3, Qnil, INT2FIX(0), INT2FIX(0)); + } else { + int field_id = 0; + + // mask off the 4 MSB of the type header. it could contain a field id delta. + uint8_t modifier = ((type & 0xf0) >> 4); + + if (modifier == 0) { + // not a delta. look ahead for the zigzag varint field id. + (void) LAST_ID(self); + field_id = read_i16(self); + } else { + // has a delta. add the delta to the last read field id. + field_id = LAST_ID(self) + modifier; + } + + // if this happens to be a boolean field, the value is encoded in the type + if (is_bool_type(type)) { + // save the boolean value in a special instance variable. + rb_ivar_set(self, bool_value_id, (type & 0x0f) == CTYPE_BOOLEAN_TRUE ? Qtrue : Qfalse); + } + + // push the new field onto the field stack so we can keep the deltas going. + SET_LAST_ID(self, INT2FIX(field_id)); + return rb_ary_new3(3, Qnil, INT2FIX(get_ttype(type & 0x0f)), INT2FIX(field_id)); + } +} + +VALUE rb_thrift_compact_proto_read_map_begin(VALUE self) { + int32_t size = read_varint64(self); + uint8_t key_and_value_type = size == 0 ? 0 : read_byte_direct(self); + return rb_ary_new3(3, INT2FIX(get_ttype(key_and_value_type >> 4)), INT2FIX(get_ttype(key_and_value_type & 0xf)), INT2FIX(size)); +} + +VALUE rb_thrift_compact_proto_read_list_begin(VALUE self) { + uint8_t size_and_type = read_byte_direct(self); + int32_t size = (size_and_type >> 4) & 0x0f; + if (size == 15) { + size = read_varint64(self); + } + uint8_t type = get_ttype(size_and_type & 0x0f); + return rb_ary_new3(2, INT2FIX(type), INT2FIX(size)); +} + +VALUE rb_thrift_compact_proto_read_set_begin(VALUE self) { + return rb_thrift_compact_proto_read_list_begin(self); +} + +VALUE rb_thrift_compact_proto_read_bool(VALUE self) { + VALUE bool_value = rb_ivar_get(self, bool_value_id); + if (NIL_P(bool_value)) { + return read_byte_direct(self) == CTYPE_BOOLEAN_TRUE ? Qtrue : Qfalse; + } else { + rb_ivar_set(self, bool_value_id, Qnil); + return bool_value; + } +} + +VALUE rb_thrift_compact_proto_read_byte(VALUE self) { + return INT2FIX(read_byte_direct(self)); +} + +VALUE rb_thrift_compact_proto_read_i16(VALUE self) { + return INT2FIX(read_i16(self)); +} + +VALUE rb_thrift_compact_proto_read_i32(VALUE self) { + return INT2NUM(zig_zag_to_int(read_varint64(self))); +} + +VALUE rb_thrift_compact_proto_read_i64(VALUE self) { + return LL2NUM(zig_zag_to_ll(read_varint64(self))); +} + +VALUE rb_thrift_compact_proto_read_double(VALUE self) { + union { + double f; + int64_t l; + } transfer; + VALUE rbuf = rb_ivar_get(self, rbuf_ivar_id); + rb_funcall(GET_TRANSPORT(self), read_into_buffer_method_id, 2, rbuf, INT2FIX(8)); + uint32_t lo = ((uint8_t)(RSTRING_PTR(rbuf)[0])) + | (((uint8_t)(RSTRING_PTR(rbuf)[1])) << 8) + | (((uint8_t)(RSTRING_PTR(rbuf)[2])) << 16) + | (((uint8_t)(RSTRING_PTR(rbuf)[3])) << 24); + uint64_t hi = (((uint8_t)(RSTRING_PTR(rbuf)[4]))) + | (((uint8_t)(RSTRING_PTR(rbuf)[5])) << 8) + | (((uint8_t)(RSTRING_PTR(rbuf)[6])) << 16) + | (((uint8_t)(RSTRING_PTR(rbuf)[7])) << 24); + transfer.l = (hi << 32) | lo; + + return rb_float_new(transfer.f); +} + +VALUE rb_thrift_compact_proto_read_string(VALUE self) { + VALUE buffer = rb_thrift_compact_proto_read_binary(self); + return convert_to_string(buffer); +} + +VALUE rb_thrift_compact_proto_read_binary(VALUE self) { + int64_t size = read_varint64(self); + return READ(self, size); +} + +static void Init_constants() { + thrift_compact_protocol_class = rb_const_get(thrift_module, rb_intern("CompactProtocol")); + + VERSION = rb_num2ll(rb_const_get(thrift_compact_protocol_class, rb_intern("VERSION"))); + VERSION_MASK = rb_num2ll(rb_const_get(thrift_compact_protocol_class, rb_intern("VERSION_MASK"))); + TYPE_MASK = rb_num2ll(rb_const_get(thrift_compact_protocol_class, rb_intern("TYPE_MASK"))); + TYPE_BITS = rb_num2ll(rb_const_get(thrift_compact_protocol_class, rb_intern("TYPE_BITS"))); + TYPE_SHIFT_AMOUNT = FIX2INT(rb_const_get(thrift_compact_protocol_class, rb_intern("TYPE_SHIFT_AMOUNT"))); + PROTOCOL_ID = FIX2INT(rb_const_get(thrift_compact_protocol_class, rb_intern("PROTOCOL_ID"))); + + last_field_id = rb_intern("@last_field"); + boolean_field_id = rb_intern("@boolean_field"); + bool_value_id = rb_intern("@bool_value"); + rbuf_ivar_id = rb_intern("@rbuf"); +} + +static void Init_rb_methods() { + rb_define_method(thrift_compact_protocol_class, "native?", rb_thrift_compact_proto_native_qmark, 0); + + rb_define_method(thrift_compact_protocol_class, "write_message_begin", rb_thrift_compact_proto_write_message_begin, 3); + rb_define_method(thrift_compact_protocol_class, "write_field_begin", rb_thrift_compact_proto_write_field_begin, 3); + rb_define_method(thrift_compact_protocol_class, "write_field_stop", rb_thrift_compact_proto_write_field_stop, 0); + rb_define_method(thrift_compact_protocol_class, "write_map_begin", rb_thrift_compact_proto_write_map_begin, 3); + rb_define_method(thrift_compact_protocol_class, "write_list_begin", rb_thrift_compact_proto_write_list_begin, 2); + rb_define_method(thrift_compact_protocol_class, "write_set_begin", rb_thrift_compact_proto_write_set_begin, 2); + rb_define_method(thrift_compact_protocol_class, "write_byte", rb_thrift_compact_proto_write_byte, 1); + rb_define_method(thrift_compact_protocol_class, "write_bool", rb_thrift_compact_proto_write_bool, 1); + rb_define_method(thrift_compact_protocol_class, "write_i16", rb_thrift_compact_proto_write_i16, 1); + rb_define_method(thrift_compact_protocol_class, "write_i32", rb_thrift_compact_proto_write_i32, 1); + rb_define_method(thrift_compact_protocol_class, "write_i64", rb_thrift_compact_proto_write_i64, 1); + rb_define_method(thrift_compact_protocol_class, "write_double", rb_thrift_compact_proto_write_double, 1); + rb_define_method(thrift_compact_protocol_class, "write_string", rb_thrift_compact_proto_write_string, 1); + rb_define_method(thrift_compact_protocol_class, "write_binary", rb_thrift_compact_proto_write_binary, 1); + + rb_define_method(thrift_compact_protocol_class, "write_message_end", rb_thrift_compact_proto_write_message_end, 0); + rb_define_method(thrift_compact_protocol_class, "write_struct_begin", rb_thrift_compact_proto_write_struct_begin, 1); + rb_define_method(thrift_compact_protocol_class, "write_struct_end", rb_thrift_compact_proto_write_struct_end, 0); + rb_define_method(thrift_compact_protocol_class, "write_field_end", rb_thrift_compact_proto_write_field_end, 0); + rb_define_method(thrift_compact_protocol_class, "write_map_end", rb_thrift_compact_proto_write_map_end, 0); + rb_define_method(thrift_compact_protocol_class, "write_list_end", rb_thrift_compact_proto_write_list_end, 0); + rb_define_method(thrift_compact_protocol_class, "write_set_end", rb_thrift_compact_proto_write_set_end, 0); + + + rb_define_method(thrift_compact_protocol_class, "read_message_begin", rb_thrift_compact_proto_read_message_begin, 0); + rb_define_method(thrift_compact_protocol_class, "read_field_begin", rb_thrift_compact_proto_read_field_begin, 0); + rb_define_method(thrift_compact_protocol_class, "read_map_begin", rb_thrift_compact_proto_read_map_begin, 0); + rb_define_method(thrift_compact_protocol_class, "read_list_begin", rb_thrift_compact_proto_read_list_begin, 0); + rb_define_method(thrift_compact_protocol_class, "read_set_begin", rb_thrift_compact_proto_read_set_begin, 0); + rb_define_method(thrift_compact_protocol_class, "read_byte", rb_thrift_compact_proto_read_byte, 0); + rb_define_method(thrift_compact_protocol_class, "read_bool", rb_thrift_compact_proto_read_bool, 0); + rb_define_method(thrift_compact_protocol_class, "read_i16", rb_thrift_compact_proto_read_i16, 0); + rb_define_method(thrift_compact_protocol_class, "read_i32", rb_thrift_compact_proto_read_i32, 0); + rb_define_method(thrift_compact_protocol_class, "read_i64", rb_thrift_compact_proto_read_i64, 0); + rb_define_method(thrift_compact_protocol_class, "read_double", rb_thrift_compact_proto_read_double, 0); + rb_define_method(thrift_compact_protocol_class, "read_string", rb_thrift_compact_proto_read_string, 0); + rb_define_method(thrift_compact_protocol_class, "read_binary", rb_thrift_compact_proto_read_binary, 0); + + rb_define_method(thrift_compact_protocol_class, "read_message_end", rb_thrift_compact_proto_read_message_end, 0); + rb_define_method(thrift_compact_protocol_class, "read_struct_begin", rb_thrift_compact_proto_read_struct_begin, 0); + rb_define_method(thrift_compact_protocol_class, "read_struct_end", rb_thrift_compact_proto_read_struct_end, 0); + rb_define_method(thrift_compact_protocol_class, "read_field_end", rb_thrift_compact_proto_read_field_end, 0); + rb_define_method(thrift_compact_protocol_class, "read_map_end", rb_thrift_compact_proto_read_map_end, 0); + rb_define_method(thrift_compact_protocol_class, "read_list_end", rb_thrift_compact_proto_read_list_end, 0); + rb_define_method(thrift_compact_protocol_class, "read_set_end", rb_thrift_compact_proto_read_set_end, 0); +} + +void Init_compact_protocol() { + Init_constants(); + Init_rb_methods(); +} diff --git a/src/jaegertracing/thrift/lib/rb/ext/compact_protocol.h b/src/jaegertracing/thrift/lib/rb/ext/compact_protocol.h new file mode 100644 index 000000000..163915e94 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/ext/compact_protocol.h @@ -0,0 +1,20 @@ +/* + * 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. + */ + +void Init_compact_protocol(); diff --git a/src/jaegertracing/thrift/lib/rb/ext/constants.h b/src/jaegertracing/thrift/lib/rb/ext/constants.h new file mode 100644 index 000000000..e7aec4478 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/ext/constants.h @@ -0,0 +1,99 @@ +/* + * 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. + */ + +extern int TTYPE_STOP; +extern int TTYPE_BOOL; +extern int TTYPE_BYTE; +extern int TTYPE_I16; +extern int TTYPE_I32; +extern int TTYPE_I64; +extern int TTYPE_DOUBLE; +extern int TTYPE_STRING; +extern int TTYPE_MAP; +extern int TTYPE_SET; +extern int TTYPE_LIST; +extern int TTYPE_STRUCT; + +extern ID validate_method_id; +extern ID write_struct_begin_method_id; +extern ID write_struct_end_method_id; +extern ID write_field_begin_method_id; +extern ID write_field_end_method_id; +extern ID write_boolean_method_id; +extern ID write_byte_method_id; +extern ID write_i16_method_id; +extern ID write_i32_method_id; +extern ID write_i64_method_id; +extern ID write_double_method_id; +extern ID write_string_method_id; +extern ID write_binary_method_id; +extern ID write_map_begin_method_id; +extern ID write_map_end_method_id; +extern ID write_list_begin_method_id; +extern ID write_list_end_method_id; +extern ID write_set_begin_method_id; +extern ID write_set_end_method_id; +extern ID read_bool_method_id; +extern ID read_byte_method_id; +extern ID read_i16_method_id; +extern ID read_i32_method_id; +extern ID read_i64_method_id; +extern ID read_string_method_id; +extern ID read_binary_method_id; +extern ID read_double_method_id; +extern ID read_map_begin_method_id; +extern ID read_map_end_method_id; +extern ID read_list_begin_method_id; +extern ID read_list_end_method_id; +extern ID read_set_begin_method_id; +extern ID read_set_end_method_id; +extern ID read_struct_begin_method_id; +extern ID read_struct_end_method_id; +extern ID read_field_begin_method_id; +extern ID read_field_end_method_id; +extern ID keys_method_id; +extern ID entries_method_id; +extern ID write_field_stop_method_id; +extern ID skip_method_id; +extern ID write_method_id; +extern ID read_all_method_id; +extern ID read_into_buffer_method_id; +extern ID force_binary_encoding_id; +extern ID convert_to_utf8_byte_buffer_id; +extern ID convert_to_string_id; + +extern ID fields_const_id; +extern ID transport_ivar_id; +extern ID strict_read_ivar_id; +extern ID strict_write_ivar_id; + +extern VALUE type_sym; +extern VALUE name_sym; +extern VALUE key_sym; +extern VALUE value_sym; +extern VALUE element_sym; +extern VALUE class_sym; +extern VALUE binary_sym; + +extern VALUE rb_cSet; +extern VALUE thrift_module; +extern VALUE thrift_types_module; +extern VALUE thrift_bytes_module; +extern VALUE class_thrift_protocol; +extern VALUE protocol_exception_class; diff --git a/src/jaegertracing/thrift/lib/rb/ext/extconf.rb b/src/jaegertracing/thrift/lib/rb/ext/extconf.rb new file mode 100644 index 000000000..b35f60bf3 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/ext/extconf.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. +# + +if defined?(RUBY_ENGINE) && RUBY_ENGINE =~ /jruby/ + File.open('Makefile', 'w'){|f| f.puts "all:\n\ninstall:\n" } +else + require 'mkmf' + require 'rbconfig' + + $ARCH_FLAGS = RbConfig::CONFIG['CFLAGS'].scan( /(-arch )(\S+)/ ).map{|x,y| x + y + ' ' }.join('') + + + $CFLAGS = "-fsigned-char -g -O2 -Wall -Werror " + $ARCH_FLAGS + + have_func("strlcpy", "string.h") + + create_makefile 'thrift_native' +end diff --git a/src/jaegertracing/thrift/lib/rb/ext/macros.h b/src/jaegertracing/thrift/lib/rb/ext/macros.h new file mode 100644 index 000000000..265f6930d --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/ext/macros.h @@ -0,0 +1,41 @@ +/* + * 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. + */ + +#define GET_TRANSPORT(obj) rb_ivar_get(obj, transport_ivar_id) +#define GET_STRICT_READ(obj) rb_ivar_get(obj, strict_read_ivar_id) +#define GET_STRICT_WRITE(obj) rb_ivar_get(obj, strict_write_ivar_id) +#define WRITE(obj, data, length) rb_funcall(obj, write_method_id, 1, rb_str_new(data, length)) +#define CHECK_NIL(obj) if (NIL_P(obj)) { rb_raise(rb_eStandardError, "nil argument not allowed!");} +#define READ(obj, length) rb_funcall(GET_TRANSPORT(obj), read_all_method_id, 1, INT2FIX(length)) + +#ifndef RFLOAT_VALUE +# define RFLOAT_VALUE(v) RFLOAT(rb_Float(v))->value +#endif + +#ifndef RSTRING_LEN +# define RSTRING_LEN(v) RSTRING(rb_String(v))->len +#endif + +#ifndef RSTRING_PTR +# define RSTRING_PTR(v) RSTRING(rb_String(v))->ptr +#endif + +#ifndef RARRAY_LEN +# define RARRAY_LEN(v) RARRAY(rb_Array(v))->len +#endif diff --git a/src/jaegertracing/thrift/lib/rb/ext/memory_buffer.c b/src/jaegertracing/thrift/lib/rb/ext/memory_buffer.c new file mode 100644 index 000000000..8b52c4a3e --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/ext/memory_buffer.c @@ -0,0 +1,134 @@ +/** + * 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. + */ + +#include <ruby.h> +#include <constants.h> +#include <bytes.h> +#include <macros.h> + +ID buf_ivar_id; +ID index_ivar_id; + +ID slice_method_id; + +int GARBAGE_BUFFER_SIZE; + +#define GET_BUF(self) rb_ivar_get(self, buf_ivar_id) + +VALUE rb_thrift_memory_buffer_write(VALUE self, VALUE str); +VALUE rb_thrift_memory_buffer_read(VALUE self, VALUE length_value); +VALUE rb_thrift_memory_buffer_read_byte(VALUE self); +VALUE rb_thrift_memory_buffer_read_into_buffer(VALUE self, VALUE buffer_value, VALUE size_value); + +VALUE rb_thrift_memory_buffer_write(VALUE self, VALUE str) { + VALUE buf = GET_BUF(self); + str = force_binary_encoding(str); + rb_str_buf_cat(buf, StringValuePtr(str), RSTRING_LEN(str)); + return Qnil; +} + +VALUE rb_thrift_memory_buffer_read(VALUE self, VALUE length_value) { + int length = FIX2INT(length_value); + + VALUE index_value = rb_ivar_get(self, index_ivar_id); + int index = FIX2INT(index_value); + + VALUE buf = GET_BUF(self); + VALUE data = rb_funcall(buf, slice_method_id, 2, index_value, length_value); + + index += length; + if (index > RSTRING_LEN(buf)) { + index = RSTRING_LEN(buf); + } + if (index >= GARBAGE_BUFFER_SIZE) { + rb_ivar_set(self, buf_ivar_id, rb_funcall(buf, slice_method_id, 2, INT2FIX(index), INT2FIX(RSTRING_LEN(buf) - 1))); + index = 0; + } + rb_ivar_set(self, index_ivar_id, INT2FIX(index)); + + if (RSTRING_LEN(data) < length) { + rb_raise(rb_eEOFError, "Not enough bytes remain in memory buffer"); + } + + return data; +} + +VALUE rb_thrift_memory_buffer_read_byte(VALUE self) { + VALUE index_value = rb_ivar_get(self, index_ivar_id); + int index = FIX2INT(index_value); + + VALUE buf = GET_BUF(self); + if (index >= RSTRING_LEN(buf)) { + rb_raise(rb_eEOFError, "Not enough bytes remain in memory buffer"); + } + char byte = RSTRING_PTR(buf)[index++]; + + if (index >= GARBAGE_BUFFER_SIZE) { + rb_ivar_set(self, buf_ivar_id, rb_funcall(buf, slice_method_id, 2, INT2FIX(index), INT2FIX(RSTRING_LEN(buf) - 1))); + index = 0; + } + rb_ivar_set(self, index_ivar_id, INT2FIX(index)); + + int result = (int) byte; + return INT2FIX(result); +} + +VALUE rb_thrift_memory_buffer_read_into_buffer(VALUE self, VALUE buffer_value, VALUE size_value) { + int i = 0; + int size = FIX2INT(size_value); + int index; + VALUE buf = GET_BUF(self); + + index = FIX2INT(rb_ivar_get(self, index_ivar_id)); + while (i < size) { + if (index >= RSTRING_LEN(buf)) { + rb_raise(rb_eEOFError, "Not enough bytes remain in memory buffer"); + } + char byte = RSTRING_PTR(buf)[index++]; + + if (i >= RSTRING_LEN(buffer_value)) { + rb_raise(rb_eIndexError, "index %d out of string", i); + } + ((char*)RSTRING_PTR(buffer_value))[i] = byte; + i++; + } + + if (index >= GARBAGE_BUFFER_SIZE) { + rb_ivar_set(self, buf_ivar_id, rb_funcall(buf, slice_method_id, 2, INT2FIX(index), INT2FIX(RSTRING_LEN(buf) - 1))); + index = 0; + } + rb_ivar_set(self, index_ivar_id, INT2FIX(index)); + + return INT2FIX(i); +} + +void Init_memory_buffer() { + VALUE thrift_memory_buffer_class = rb_const_get(thrift_module, rb_intern("MemoryBufferTransport")); + rb_define_method(thrift_memory_buffer_class, "write", rb_thrift_memory_buffer_write, 1); + rb_define_method(thrift_memory_buffer_class, "read", rb_thrift_memory_buffer_read, 1); + rb_define_method(thrift_memory_buffer_class, "read_byte", rb_thrift_memory_buffer_read_byte, 0); + rb_define_method(thrift_memory_buffer_class, "read_into_buffer", rb_thrift_memory_buffer_read_into_buffer, 2); + + buf_ivar_id = rb_intern("@buf"); + index_ivar_id = rb_intern("@index"); + + slice_method_id = rb_intern("slice"); + + GARBAGE_BUFFER_SIZE = FIX2INT(rb_const_get(thrift_memory_buffer_class, rb_intern("GARBAGE_BUFFER_SIZE"))); +} diff --git a/src/jaegertracing/thrift/lib/rb/ext/memory_buffer.h b/src/jaegertracing/thrift/lib/rb/ext/memory_buffer.h new file mode 100644 index 000000000..b277fa6f6 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/ext/memory_buffer.h @@ -0,0 +1,20 @@ +/* + * 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. + */ + +void Init_memory_buffer(); diff --git a/src/jaegertracing/thrift/lib/rb/ext/protocol.c b/src/jaegertracing/thrift/lib/rb/ext/protocol.c new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/ext/protocol.c diff --git a/src/jaegertracing/thrift/lib/rb/ext/protocol.h b/src/jaegertracing/thrift/lib/rb/ext/protocol.h new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/ext/protocol.h diff --git a/src/jaegertracing/thrift/lib/rb/ext/strlcpy.c b/src/jaegertracing/thrift/lib/rb/ext/strlcpy.c new file mode 100644 index 000000000..6700ff2d4 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/ext/strlcpy.c @@ -0,0 +1,41 @@ +/** + * 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. + */ + +#include "strlcpy.h" + +#ifndef HAVE_STRLCPY +#define HAVE_STRLCPY +size_t +strlcpy (char *dst, const char *src, size_t dst_sz) +{ + size_t n; + + for (n = 0; n < dst_sz; n++) { + if ((*dst++ = *src++) == '\0') + break; + } + + if (n < dst_sz) + return n; + if (n > 0) + *(dst - 1) = '\0'; + return n + strlen (src); +} +#endif + diff --git a/src/jaegertracing/thrift/lib/rb/ext/strlcpy.h b/src/jaegertracing/thrift/lib/rb/ext/strlcpy.h new file mode 100644 index 000000000..f6fe0fe6b --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/ext/strlcpy.h @@ -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. + */ + +#include <sys/types.h> +#include <string.h> + +#ifndef __has_builtin +#define __has_builtin(x) 0 +#endif + +#ifndef HAVE_STRLCPY +size_t strlcpy (char *dst, const char *src, size_t dst_sz); +#else +#if !__has_builtin(strlcpy) +extern size_t strlcpy(char *, const char *, size_t); +#endif +#endif + diff --git a/src/jaegertracing/thrift/lib/rb/ext/struct.c b/src/jaegertracing/thrift/lib/rb/ext/struct.c new file mode 100644 index 000000000..e3aa855ed --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/ext/struct.c @@ -0,0 +1,711 @@ +/** + * 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. + */ + +#include "struct.h" +#include "constants.h" +#include "macros.h" +#include "strlcpy.h" + +VALUE thrift_union_class; + +ID setfield_id; +ID setvalue_id; + +ID to_s_method_id; +ID name_to_id_method_id; +static ID sorted_field_ids_method_id; + +#define IS_CONTAINER(ttype) ((ttype) == TTYPE_MAP || (ttype) == TTYPE_LIST || (ttype) == TTYPE_SET) +#define STRUCT_FIELDS(obj) rb_const_get(CLASS_OF(obj), fields_const_id) + +//------------------------------------------- +// Writing section +//------------------------------------------- + +// default fn pointers for protocol stuff here + +VALUE default_write_bool(VALUE protocol, VALUE value) { + rb_funcall(protocol, write_boolean_method_id, 1, value); + return Qnil; +} + +VALUE default_write_byte(VALUE protocol, VALUE value) { + rb_funcall(protocol, write_byte_method_id, 1, value); + return Qnil; +} + +VALUE default_write_i16(VALUE protocol, VALUE value) { + rb_funcall(protocol, write_i16_method_id, 1, value); + return Qnil; +} + +VALUE default_write_i32(VALUE protocol, VALUE value) { + rb_funcall(protocol, write_i32_method_id, 1, value); + return Qnil; +} + +VALUE default_write_i64(VALUE protocol, VALUE value) { + rb_funcall(protocol, write_i64_method_id, 1, value); + return Qnil; +} + +VALUE default_write_double(VALUE protocol, VALUE value) { + rb_funcall(protocol, write_double_method_id, 1, value); + return Qnil; +} + +VALUE default_write_string(VALUE protocol, VALUE value) { + rb_funcall(protocol, write_string_method_id, 1, value); + return Qnil; +} + +VALUE default_write_binary(VALUE protocol, VALUE value) { + rb_funcall(protocol, write_binary_method_id, 1, value); + return Qnil; +} + +VALUE default_write_list_begin(VALUE protocol, VALUE etype, VALUE length) { + rb_funcall(protocol, write_list_begin_method_id, 2, etype, length); + return Qnil; +} + +VALUE default_write_list_end(VALUE protocol) { + rb_funcall(protocol, write_list_end_method_id, 0); + return Qnil; +} + +VALUE default_write_set_begin(VALUE protocol, VALUE etype, VALUE length) { + rb_funcall(protocol, write_set_begin_method_id, 2, etype, length); + return Qnil; +} + +VALUE default_write_set_end(VALUE protocol) { + rb_funcall(protocol, write_set_end_method_id, 0); + return Qnil; +} + +VALUE default_write_map_begin(VALUE protocol, VALUE ktype, VALUE vtype, VALUE length) { + rb_funcall(protocol, write_map_begin_method_id, 3, ktype, vtype, length); + return Qnil; +} + +VALUE default_write_map_end(VALUE protocol) { + rb_funcall(protocol, write_map_end_method_id, 0); + return Qnil; +} + +VALUE default_write_struct_begin(VALUE protocol, VALUE struct_name) { + rb_funcall(protocol, write_struct_begin_method_id, 1, struct_name); + return Qnil; +} + +VALUE default_write_struct_end(VALUE protocol) { + rb_funcall(protocol, write_struct_end_method_id, 0); + return Qnil; +} + +VALUE default_write_field_begin(VALUE protocol, VALUE name, VALUE type, VALUE id) { + rb_funcall(protocol, write_field_begin_method_id, 3, name, type, id); + return Qnil; +} + +VALUE default_write_field_end(VALUE protocol) { + rb_funcall(protocol, write_field_end_method_id, 0); + return Qnil; +} + +VALUE default_write_field_stop(VALUE protocol) { + rb_funcall(protocol, write_field_stop_method_id, 0); + return Qnil; +} + +VALUE default_read_field_begin(VALUE protocol) { + return rb_funcall(protocol, read_field_begin_method_id, 0); +} + +VALUE default_read_field_end(VALUE protocol) { + return rb_funcall(protocol, read_field_end_method_id, 0); +} + +VALUE default_read_map_begin(VALUE protocol) { + return rb_funcall(protocol, read_map_begin_method_id, 0); +} + +VALUE default_read_map_end(VALUE protocol) { + return rb_funcall(protocol, read_map_end_method_id, 0); +} + +VALUE default_read_list_begin(VALUE protocol) { + return rb_funcall(protocol, read_list_begin_method_id, 0); +} + +VALUE default_read_list_end(VALUE protocol) { + return rb_funcall(protocol, read_list_end_method_id, 0); +} + +VALUE default_read_set_begin(VALUE protocol) { + return rb_funcall(protocol, read_set_begin_method_id, 0); +} + +VALUE default_read_set_end(VALUE protocol) { + return rb_funcall(protocol, read_set_end_method_id, 0); +} + +VALUE default_read_byte(VALUE protocol) { + return rb_funcall(protocol, read_byte_method_id, 0); +} + +VALUE default_read_bool(VALUE protocol) { + return rb_funcall(protocol, read_bool_method_id, 0); +} + +VALUE default_read_i16(VALUE protocol) { + return rb_funcall(protocol, read_i16_method_id, 0); +} + +VALUE default_read_i32(VALUE protocol) { + return rb_funcall(protocol, read_i32_method_id, 0); +} + +VALUE default_read_i64(VALUE protocol) { + return rb_funcall(protocol, read_i64_method_id, 0); +} + +VALUE default_read_double(VALUE protocol) { + return rb_funcall(protocol, read_double_method_id, 0); +} + +VALUE default_read_string(VALUE protocol) { + return rb_funcall(protocol, read_string_method_id, 0); +} + +VALUE default_read_binary(VALUE protocol) { + return rb_funcall(protocol, read_binary_method_id, 0); +} + +VALUE default_read_struct_begin(VALUE protocol) { + return rb_funcall(protocol, read_struct_begin_method_id, 0); +} + +VALUE default_read_struct_end(VALUE protocol) { + return rb_funcall(protocol, read_struct_end_method_id, 0); +} + +// end default protocol methods + +static VALUE rb_thrift_union_write (VALUE self, VALUE protocol); +static VALUE rb_thrift_struct_write(VALUE self, VALUE protocol); +static void write_anything(int ttype, VALUE value, VALUE protocol, VALUE field_info); + +VALUE get_field_value(VALUE obj, VALUE field_name) { + char name_buf[RSTRING_LEN(field_name) + 2]; + + name_buf[0] = '@'; + strlcpy(&name_buf[1], RSTRING_PTR(field_name), RSTRING_LEN(field_name) + 1); + + VALUE value = rb_ivar_get(obj, rb_intern(name_buf)); + + return value; +} + +static void write_container(int ttype, VALUE field_info, VALUE value, VALUE protocol) { + int sz, i; + + if (ttype == TTYPE_MAP) { + VALUE keys; + VALUE key; + VALUE val; + + Check_Type(value, T_HASH); + + VALUE key_info = rb_hash_aref(field_info, key_sym); + VALUE keytype_value = rb_hash_aref(key_info, type_sym); + int keytype = FIX2INT(keytype_value); + + VALUE value_info = rb_hash_aref(field_info, value_sym); + VALUE valuetype_value = rb_hash_aref(value_info, type_sym); + int valuetype = FIX2INT(valuetype_value); + + keys = rb_funcall(value, keys_method_id, 0); + + sz = RARRAY_LEN(keys); + + default_write_map_begin(protocol, keytype_value, valuetype_value, INT2FIX(sz)); + + for (i = 0; i < sz; i++) { + key = rb_ary_entry(keys, i); + val = rb_hash_aref(value, key); + + if (IS_CONTAINER(keytype)) { + write_container(keytype, key_info, key, protocol); + } else { + write_anything(keytype, key, protocol, key_info); + } + + if (IS_CONTAINER(valuetype)) { + write_container(valuetype, value_info, val, protocol); + } else { + write_anything(valuetype, val, protocol, value_info); + } + } + + default_write_map_end(protocol); + } else if (ttype == TTYPE_LIST) { + Check_Type(value, T_ARRAY); + + sz = RARRAY_LEN(value); + + VALUE element_type_info = rb_hash_aref(field_info, element_sym); + VALUE element_type_value = rb_hash_aref(element_type_info, type_sym); + int element_type = FIX2INT(element_type_value); + + default_write_list_begin(protocol, element_type_value, INT2FIX(sz)); + for (i = 0; i < sz; ++i) { + VALUE val = rb_ary_entry(value, i); + if (IS_CONTAINER(element_type)) { + write_container(element_type, element_type_info, val, protocol); + } else { + write_anything(element_type, val, protocol, element_type_info); + } + } + default_write_list_end(protocol); + } else if (ttype == TTYPE_SET) { + VALUE items; + + if (TYPE(value) == T_ARRAY) { + items = value; + } else { + if (rb_cSet == CLASS_OF(value)) { + items = rb_funcall(value, entries_method_id, 0); + } else { + Check_Type(value, T_HASH); + items = rb_funcall(value, keys_method_id, 0); + } + } + + sz = RARRAY_LEN(items); + + VALUE element_type_info = rb_hash_aref(field_info, element_sym); + VALUE element_type_value = rb_hash_aref(element_type_info, type_sym); + int element_type = FIX2INT(element_type_value); + + default_write_set_begin(protocol, element_type_value, INT2FIX(sz)); + + for (i = 0; i < sz; i++) { + VALUE val = rb_ary_entry(items, i); + if (IS_CONTAINER(element_type)) { + write_container(element_type, element_type_info, val, protocol); + } else { + write_anything(element_type, val, protocol, element_type_info); + } + } + + default_write_set_end(protocol); + } else { + rb_raise(rb_eNotImpError, "can't write container of type: %d", ttype); + } +} + +static void write_anything(int ttype, VALUE value, VALUE protocol, VALUE field_info) { + if (ttype == TTYPE_BOOL) { + default_write_bool(protocol, value); + } else if (ttype == TTYPE_BYTE) { + default_write_byte(protocol, value); + } else if (ttype == TTYPE_I16) { + default_write_i16(protocol, value); + } else if (ttype == TTYPE_I32) { + default_write_i32(protocol, value); + } else if (ttype == TTYPE_I64) { + default_write_i64(protocol, value); + } else if (ttype == TTYPE_DOUBLE) { + default_write_double(protocol, value); + } else if (ttype == TTYPE_STRING) { + VALUE is_binary = rb_hash_aref(field_info, binary_sym); + if (is_binary != Qtrue) { + default_write_string(protocol, value); + } else { + default_write_binary(protocol, value); + } + } else if (IS_CONTAINER(ttype)) { + write_container(ttype, field_info, value, protocol); + } else if (ttype == TTYPE_STRUCT) { + if (rb_obj_is_kind_of(value, thrift_union_class)) { + rb_thrift_union_write(value, protocol); + } else { + rb_thrift_struct_write(value, protocol); + } + } else { + rb_raise(rb_eNotImpError, "Unknown type for binary_encoding: %d", ttype); + } +} + +static VALUE rb_thrift_struct_write(VALUE self, VALUE protocol) { + // call validate + rb_funcall(self, validate_method_id, 0); + + // write struct begin + default_write_struct_begin(protocol, rb_class_name(CLASS_OF(self))); + + // iterate through all the fields here + VALUE struct_fields = STRUCT_FIELDS(self); + VALUE sorted_field_ids = rb_funcall(self, sorted_field_ids_method_id, 0); + + int i = 0; + for (i=0; i < RARRAY_LEN(sorted_field_ids); i++) { + VALUE field_id = rb_ary_entry(sorted_field_ids, i); + + VALUE field_info = rb_hash_aref(struct_fields, field_id); + + VALUE ttype_value = rb_hash_aref(field_info, type_sym); + int ttype = FIX2INT(ttype_value); + VALUE field_name = rb_hash_aref(field_info, name_sym); + + VALUE field_value = get_field_value(self, field_name); + + if (!NIL_P(field_value)) { + default_write_field_begin(protocol, field_name, ttype_value, field_id); + + write_anything(ttype, field_value, protocol, field_info); + + default_write_field_end(protocol); + } + } + + default_write_field_stop(protocol); + + // write struct end + default_write_struct_end(protocol); + + return Qnil; +} + +//------------------------------------------- +// Reading section +//------------------------------------------- + +static VALUE rb_thrift_union_read(VALUE self, VALUE protocol); +static VALUE rb_thrift_struct_read(VALUE self, VALUE protocol); +static void skip_map_contents(VALUE protocol, VALUE key_type_value, VALUE value_type_value, int size); +static void skip_list_or_set_contents(VALUE protocol, VALUE element_type_value, int size); + +static void set_field_value(VALUE obj, VALUE field_name, VALUE value) { + char name_buf[RSTRING_LEN(field_name) + 2]; + + name_buf[0] = '@'; + strlcpy(&name_buf[1], RSTRING_PTR(field_name), RSTRING_LEN(field_name)+1); + + rb_ivar_set(obj, rb_intern(name_buf), value); +} + +// Helper method to skip the contents of a map (assumes the map header has been read). +static void skip_map_contents(VALUE protocol, VALUE key_type_value, VALUE value_type_value, int size) { + int i; + for (i = 0; i < size; i++) { + rb_funcall(protocol, skip_method_id, 1, key_type_value); + rb_funcall(protocol, skip_method_id, 1, value_type_value); + } +} + +// Helper method to skip the contents of a list or set (assumes the list/set header has been read). +static void skip_list_or_set_contents(VALUE protocol, VALUE element_type_value, int size) { + int i; + for (i = 0; i < size; i++) { + rb_funcall(protocol, skip_method_id, 1, element_type_value); + } +} + +static VALUE read_anything(VALUE protocol, int ttype, VALUE field_info) { + VALUE result = Qnil; + + if (ttype == TTYPE_BOOL) { + result = default_read_bool(protocol); + } else if (ttype == TTYPE_BYTE) { + result = default_read_byte(protocol); + } else if (ttype == TTYPE_I16) { + result = default_read_i16(protocol); + } else if (ttype == TTYPE_I32) { + result = default_read_i32(protocol); + } else if (ttype == TTYPE_I64) { + result = default_read_i64(protocol); + } else if (ttype == TTYPE_STRING) { + VALUE is_binary = rb_hash_aref(field_info, binary_sym); + if (is_binary != Qtrue) { + result = default_read_string(protocol); + } else { + result = default_read_binary(protocol); + } + } else if (ttype == TTYPE_DOUBLE) { + result = default_read_double(protocol); + } else if (ttype == TTYPE_STRUCT) { + VALUE klass = rb_hash_aref(field_info, class_sym); + result = rb_class_new_instance(0, NULL, klass); + + if (rb_obj_is_kind_of(result, thrift_union_class)) { + rb_thrift_union_read(result, protocol); + } else { + rb_thrift_struct_read(result, protocol); + } + } else if (ttype == TTYPE_MAP) { + int i; + + VALUE map_header = default_read_map_begin(protocol); + int key_ttype = FIX2INT(rb_ary_entry(map_header, 0)); + int value_ttype = FIX2INT(rb_ary_entry(map_header, 1)); + int num_entries = FIX2INT(rb_ary_entry(map_header, 2)); + + // Check the declared key and value types against the expected ones and skip the map contents + // if the types don't match. + VALUE key_info = rb_hash_aref(field_info, key_sym); + VALUE value_info = rb_hash_aref(field_info, value_sym); + + if (!NIL_P(key_info) && !NIL_P(value_info)) { + int specified_key_type = FIX2INT(rb_hash_aref(key_info, type_sym)); + int specified_value_type = FIX2INT(rb_hash_aref(value_info, type_sym)); + if (num_entries == 0 || (specified_key_type == key_ttype && specified_value_type == value_ttype)) { + result = rb_hash_new(); + + for (i = 0; i < num_entries; ++i) { + VALUE key, val; + + key = read_anything(protocol, key_ttype, key_info); + val = read_anything(protocol, value_ttype, value_info); + + rb_hash_aset(result, key, val); + } + } else { + skip_map_contents(protocol, INT2FIX(key_ttype), INT2FIX(value_ttype), num_entries); + } + } else { + skip_map_contents(protocol, INT2FIX(key_ttype), INT2FIX(value_ttype), num_entries); + } + + default_read_map_end(protocol); + } else if (ttype == TTYPE_LIST) { + int i; + + VALUE list_header = default_read_list_begin(protocol); + int element_ttype = FIX2INT(rb_ary_entry(list_header, 0)); + int num_elements = FIX2INT(rb_ary_entry(list_header, 1)); + + // Check the declared element type against the expected one and skip the list contents + // if the types don't match. + VALUE element_info = rb_hash_aref(field_info, element_sym); + if (!NIL_P(element_info)) { + int specified_element_type = FIX2INT(rb_hash_aref(element_info, type_sym)); + if (specified_element_type == element_ttype) { + result = rb_ary_new2(num_elements); + + for (i = 0; i < num_elements; ++i) { + rb_ary_push(result, read_anything(protocol, element_ttype, rb_hash_aref(field_info, element_sym))); + } + } else { + skip_list_or_set_contents(protocol, INT2FIX(element_ttype), num_elements); + } + } else { + skip_list_or_set_contents(protocol, INT2FIX(element_ttype), num_elements); + } + + default_read_list_end(protocol); + } else if (ttype == TTYPE_SET) { + VALUE items; + int i; + + VALUE set_header = default_read_set_begin(protocol); + int element_ttype = FIX2INT(rb_ary_entry(set_header, 0)); + int num_elements = FIX2INT(rb_ary_entry(set_header, 1)); + + // Check the declared element type against the expected one and skip the set contents + // if the types don't match. + VALUE element_info = rb_hash_aref(field_info, element_sym); + if (!NIL_P(element_info)) { + int specified_element_type = FIX2INT(rb_hash_aref(element_info, type_sym)); + if (specified_element_type == element_ttype) { + items = rb_ary_new2(num_elements); + + for (i = 0; i < num_elements; ++i) { + rb_ary_push(items, read_anything(protocol, element_ttype, rb_hash_aref(field_info, element_sym))); + } + + result = rb_class_new_instance(1, &items, rb_cSet); + } else { + skip_list_or_set_contents(protocol, INT2FIX(element_ttype), num_elements); + } + } else { + skip_list_or_set_contents(protocol, INT2FIX(element_ttype), num_elements); + } + + default_read_set_end(protocol); + } else { + rb_raise(rb_eNotImpError, "read_anything not implemented for type %d!", ttype); + } + + return result; +} + +static VALUE rb_thrift_struct_read(VALUE self, VALUE protocol) { + // read struct begin + default_read_struct_begin(protocol); + + VALUE struct_fields = STRUCT_FIELDS(self); + + // read each field + while (true) { + VALUE field_header = default_read_field_begin(protocol); + VALUE field_type_value = rb_ary_entry(field_header, 1); + int field_type = FIX2INT(field_type_value); + + if (field_type == TTYPE_STOP) { + break; + } + + // make sure we got a type we expected + VALUE field_info = rb_hash_aref(struct_fields, rb_ary_entry(field_header, 2)); + + if (!NIL_P(field_info)) { + int specified_type = FIX2INT(rb_hash_aref(field_info, type_sym)); + if (field_type == specified_type) { + // read the value + VALUE name = rb_hash_aref(field_info, name_sym); + set_field_value(self, name, read_anything(protocol, field_type, field_info)); + } else { + rb_funcall(protocol, skip_method_id, 1, field_type_value); + } + } else { + rb_funcall(protocol, skip_method_id, 1, field_type_value); + } + + // read field end + default_read_field_end(protocol); + } + + // read struct end + default_read_struct_end(protocol); + + // call validate + rb_funcall(self, validate_method_id, 0); + + return Qnil; +} + + +// -------------------------------- +// Union section +// -------------------------------- + +static VALUE rb_thrift_union_read(VALUE self, VALUE protocol) { + // read struct begin + default_read_struct_begin(protocol); + + VALUE struct_fields = STRUCT_FIELDS(self); + + VALUE field_header = default_read_field_begin(protocol); + VALUE field_type_value = rb_ary_entry(field_header, 1); + int field_type = FIX2INT(field_type_value); + + // make sure we got a type we expected + VALUE field_info = rb_hash_aref(struct_fields, rb_ary_entry(field_header, 2)); + + if (!NIL_P(field_info)) { + int specified_type = FIX2INT(rb_hash_aref(field_info, type_sym)); + if (field_type == specified_type) { + // read the value + VALUE name = rb_hash_aref(field_info, name_sym); + rb_iv_set(self, "@setfield", rb_str_intern(name)); + rb_iv_set(self, "@value", read_anything(protocol, field_type, field_info)); + } else { + rb_funcall(protocol, skip_method_id, 1, field_type_value); + } + } else { + rb_funcall(protocol, skip_method_id, 1, field_type_value); + } + + // read field end + default_read_field_end(protocol); + + field_header = default_read_field_begin(protocol); + field_type_value = rb_ary_entry(field_header, 1); + field_type = FIX2INT(field_type_value); + + if (field_type != TTYPE_STOP) { + rb_raise(rb_eRuntimeError, "too many fields in union!"); + } + + // read struct end + default_read_struct_end(protocol); + + // call validate + rb_funcall(self, validate_method_id, 0); + + return Qnil; +} + +static VALUE rb_thrift_union_write(VALUE self, VALUE protocol) { + // call validate + rb_funcall(self, validate_method_id, 0); + + // write struct begin + default_write_struct_begin(protocol, rb_class_name(CLASS_OF(self))); + + VALUE struct_fields = STRUCT_FIELDS(self); + + VALUE setfield = rb_ivar_get(self, setfield_id); + VALUE setvalue = rb_ivar_get(self, setvalue_id); + VALUE field_id = rb_funcall(self, name_to_id_method_id, 1, rb_funcall(setfield, to_s_method_id, 0)); + + VALUE field_info = rb_hash_aref(struct_fields, field_id); + + if(NIL_P(field_info)) { + rb_raise(rb_eRuntimeError, "set_field is not valid for this union!"); + } + + VALUE ttype_value = rb_hash_aref(field_info, type_sym); + int ttype = FIX2INT(ttype_value); + + default_write_field_begin(protocol, setfield, ttype_value, field_id); + + write_anything(ttype, setvalue, protocol, field_info); + + default_write_field_end(protocol); + + default_write_field_stop(protocol); + + // write struct end + default_write_struct_end(protocol); + + return Qnil; +} + +void Init_struct() { + VALUE struct_module = rb_const_get(thrift_module, rb_intern("Struct")); + + rb_define_method(struct_module, "write", rb_thrift_struct_write, 1); + rb_define_method(struct_module, "read", rb_thrift_struct_read, 1); + + thrift_union_class = rb_const_get(thrift_module, rb_intern("Union")); + + rb_define_method(thrift_union_class, "write", rb_thrift_union_write, 1); + rb_define_method(thrift_union_class, "read", rb_thrift_union_read, 1); + + setfield_id = rb_intern("@setfield"); + setvalue_id = rb_intern("@value"); + + to_s_method_id = rb_intern("to_s"); + name_to_id_method_id = rb_intern("name_to_id"); + sorted_field_ids_method_id = rb_intern("sorted_field_ids"); +} diff --git a/src/jaegertracing/thrift/lib/rb/ext/struct.h b/src/jaegertracing/thrift/lib/rb/ext/struct.h new file mode 100644 index 000000000..4748be5cb --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/ext/struct.h @@ -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. + */ + + +#include <stdbool.h> +#include <ruby.h> + +void Init_struct(); +void Init_union(); diff --git a/src/jaegertracing/thrift/lib/rb/ext/thrift_native.c b/src/jaegertracing/thrift/lib/rb/ext/thrift_native.c new file mode 100644 index 000000000..3430b7c25 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/ext/thrift_native.c @@ -0,0 +1,201 @@ +/** + * 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. + */ + +#include <ruby.h> +#include <bytes.h> +#include <struct.h> +#include <binary_protocol_accelerated.h> +#include <compact_protocol.h> +#include <memory_buffer.h> + +// cached classes/modules +VALUE rb_cSet; +VALUE thrift_module; +VALUE thrift_bytes_module; +VALUE thrift_types_module; + +// TType constants +int TTYPE_STOP; +int TTYPE_BOOL; +int TTYPE_BYTE; +int TTYPE_I16; +int TTYPE_I32; +int TTYPE_I64; +int TTYPE_DOUBLE; +int TTYPE_STRING; +int TTYPE_MAP; +int TTYPE_SET; +int TTYPE_LIST; +int TTYPE_STRUCT; + +// method ids +ID validate_method_id; +ID write_struct_begin_method_id; +ID write_struct_end_method_id; +ID write_field_begin_method_id; +ID write_field_end_method_id; +ID write_boolean_method_id; +ID write_byte_method_id; +ID write_i16_method_id; +ID write_i32_method_id; +ID write_i64_method_id; +ID write_double_method_id; +ID write_string_method_id; +ID write_binary_method_id; +ID write_map_begin_method_id; +ID write_map_end_method_id; +ID write_list_begin_method_id; +ID write_list_end_method_id; +ID write_set_begin_method_id; +ID write_set_end_method_id; +ID read_bool_method_id; +ID read_byte_method_id; +ID read_i16_method_id; +ID read_i32_method_id; +ID read_i64_method_id; +ID read_string_method_id; +ID read_binary_method_id; +ID read_double_method_id; +ID read_map_begin_method_id; +ID read_map_end_method_id; +ID read_list_begin_method_id; +ID read_list_end_method_id; +ID read_set_begin_method_id; +ID read_set_end_method_id; +ID read_struct_begin_method_id; +ID read_struct_end_method_id; +ID read_field_begin_method_id; +ID read_field_end_method_id; +ID keys_method_id; +ID entries_method_id; +ID write_field_stop_method_id; +ID skip_method_id; +ID write_method_id; +ID read_all_method_id; +ID read_into_buffer_method_id; +ID force_binary_encoding_id; +ID convert_to_utf8_byte_buffer_id; +ID convert_to_string_id; + +// constant ids +ID fields_const_id; +ID transport_ivar_id; +ID strict_read_ivar_id; +ID strict_write_ivar_id; + +// cached symbols +VALUE type_sym; +VALUE name_sym; +VALUE key_sym; +VALUE value_sym; +VALUE element_sym; +VALUE class_sym; +VALUE binary_sym; +VALUE protocol_exception_class; + +void Init_thrift_native() { + // cached classes + thrift_module = rb_const_get(rb_cObject, rb_intern("Thrift")); + thrift_bytes_module = rb_const_get(thrift_module, rb_intern("Bytes")); + thrift_types_module = rb_const_get(thrift_module, rb_intern("Types")); + rb_cSet = rb_const_get(rb_cObject, rb_intern("Set")); + protocol_exception_class = rb_const_get(thrift_module, rb_intern("ProtocolException")); + + // Init ttype constants + TTYPE_BOOL = FIX2INT(rb_const_get(thrift_types_module, rb_intern("BOOL"))); + TTYPE_BYTE = FIX2INT(rb_const_get(thrift_types_module, rb_intern("BYTE"))); + TTYPE_I16 = FIX2INT(rb_const_get(thrift_types_module, rb_intern("I16"))); + TTYPE_I32 = FIX2INT(rb_const_get(thrift_types_module, rb_intern("I32"))); + TTYPE_I64 = FIX2INT(rb_const_get(thrift_types_module, rb_intern("I64"))); + TTYPE_DOUBLE = FIX2INT(rb_const_get(thrift_types_module, rb_intern("DOUBLE"))); + TTYPE_STRING = FIX2INT(rb_const_get(thrift_types_module, rb_intern("STRING"))); + TTYPE_MAP = FIX2INT(rb_const_get(thrift_types_module, rb_intern("MAP"))); + TTYPE_SET = FIX2INT(rb_const_get(thrift_types_module, rb_intern("SET"))); + TTYPE_LIST = FIX2INT(rb_const_get(thrift_types_module, rb_intern("LIST"))); + TTYPE_STRUCT = FIX2INT(rb_const_get(thrift_types_module, rb_intern("STRUCT"))); + + // method ids + validate_method_id = rb_intern("validate"); + write_struct_begin_method_id = rb_intern("write_struct_begin"); + write_struct_end_method_id = rb_intern("write_struct_end"); + write_field_begin_method_id = rb_intern("write_field_begin"); + write_field_end_method_id = rb_intern("write_field_end"); + write_boolean_method_id = rb_intern("write_bool"); + write_byte_method_id = rb_intern("write_byte"); + write_i16_method_id = rb_intern("write_i16"); + write_i32_method_id = rb_intern("write_i32"); + write_i64_method_id = rb_intern("write_i64"); + write_double_method_id = rb_intern("write_double"); + write_string_method_id = rb_intern("write_string"); + write_binary_method_id = rb_intern("write_binary"); + write_map_begin_method_id = rb_intern("write_map_begin"); + write_map_end_method_id = rb_intern("write_map_end"); + write_list_begin_method_id = rb_intern("write_list_begin"); + write_list_end_method_id = rb_intern("write_list_end"); + write_set_begin_method_id = rb_intern("write_set_begin"); + write_set_end_method_id = rb_intern("write_set_end"); + read_bool_method_id = rb_intern("read_bool"); + read_byte_method_id = rb_intern("read_byte"); + read_i16_method_id = rb_intern("read_i16"); + read_i32_method_id = rb_intern("read_i32"); + read_i64_method_id = rb_intern("read_i64"); + read_string_method_id = rb_intern("read_string"); + read_binary_method_id = rb_intern("read_binary"); + read_double_method_id = rb_intern("read_double"); + read_map_begin_method_id = rb_intern("read_map_begin"); + read_map_end_method_id = rb_intern("read_map_end"); + read_list_begin_method_id = rb_intern("read_list_begin"); + read_list_end_method_id = rb_intern("read_list_end"); + read_set_begin_method_id = rb_intern("read_set_begin"); + read_set_end_method_id = rb_intern("read_set_end"); + read_struct_begin_method_id = rb_intern("read_struct_begin"); + read_struct_end_method_id = rb_intern("read_struct_end"); + read_field_begin_method_id = rb_intern("read_field_begin"); + read_field_end_method_id = rb_intern("read_field_end"); + keys_method_id = rb_intern("keys"); + entries_method_id = rb_intern("entries"); + write_field_stop_method_id = rb_intern("write_field_stop"); + skip_method_id = rb_intern("skip"); + write_method_id = rb_intern("write"); + read_all_method_id = rb_intern("read_all"); + read_into_buffer_method_id = rb_intern("read_into_buffer"); + force_binary_encoding_id = rb_intern("force_binary_encoding"); + convert_to_utf8_byte_buffer_id = rb_intern("convert_to_utf8_byte_buffer"); + convert_to_string_id = rb_intern("convert_to_string"); + + // constant ids + fields_const_id = rb_intern("FIELDS"); + transport_ivar_id = rb_intern("@trans"); + strict_read_ivar_id = rb_intern("@strict_read"); + strict_write_ivar_id = rb_intern("@strict_write"); + + // cached symbols + type_sym = ID2SYM(rb_intern("type")); + name_sym = ID2SYM(rb_intern("name")); + key_sym = ID2SYM(rb_intern("key")); + value_sym = ID2SYM(rb_intern("value")); + element_sym = ID2SYM(rb_intern("element")); + class_sym = ID2SYM(rb_intern("class")); + binary_sym = ID2SYM(rb_intern("binary")); + + Init_struct(); + Init_binary_protocol_accelerated(); + Init_compact_protocol(); + Init_memory_buffer(); +} diff --git a/src/jaegertracing/thrift/lib/rb/lib/thrift.rb b/src/jaegertracing/thrift/lib/rb/lib/thrift.rb new file mode 100644 index 000000000..0f581229c --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/lib/thrift.rb @@ -0,0 +1,70 @@ +# +# 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. +# +# Contains some contributions under the Thrift Software License. +# Please see doc/old-thrift-license.txt in the Thrift distribution for +# details. + +$:.unshift File.dirname(__FILE__) + +require 'thrift/bytes' +require 'thrift/core_ext' +require 'thrift/exceptions' +require 'thrift/types' +require 'thrift/processor' +require 'thrift/multiplexed_processor' +require 'thrift/client' +require 'thrift/struct' +require 'thrift/union' +require 'thrift/struct_union' + +# serializer +require 'thrift/serializer/serializer' +require 'thrift/serializer/deserializer' + +# protocol +require 'thrift/protocol/base_protocol' +require 'thrift/protocol/binary_protocol' +require 'thrift/protocol/binary_protocol_accelerated' +require 'thrift/protocol/compact_protocol' +require 'thrift/protocol/json_protocol' +require 'thrift/protocol/multiplexed_protocol' + +# transport +require 'thrift/transport/base_transport' +require 'thrift/transport/base_server_transport' +require 'thrift/transport/socket' +require 'thrift/transport/ssl_socket' +require 'thrift/transport/server_socket' +require 'thrift/transport/ssl_server_socket' +require 'thrift/transport/unix_socket' +require 'thrift/transport/unix_server_socket' +require 'thrift/transport/buffered_transport' +require 'thrift/transport/framed_transport' +require 'thrift/transport/http_client_transport' +require 'thrift/transport/io_stream_transport' +require 'thrift/transport/memory_buffer_transport' + +# server +require 'thrift/server/base_server' +require 'thrift/server/nonblocking_server' +require 'thrift/server/simple_server' +require 'thrift/server/threaded_server' +require 'thrift/server/thread_pool_server' + +require 'thrift/thrift_native' diff --git a/src/jaegertracing/thrift/lib/rb/lib/thrift/bytes.rb b/src/jaegertracing/thrift/lib/rb/lib/thrift/bytes.rb new file mode 100644 index 000000000..efd4f6440 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/lib/thrift/bytes.rb @@ -0,0 +1,131 @@ +# 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. +# + +module Thrift + # A collection of utilities for working with bytes and byte buffers. + module Bytes + if RUBY_VERSION >= '1.9' + # Creates and empty byte buffer (String with BINARY encoding) + # + # size - The Integer size of the buffer (default: nil) to create + # + # Returns a String with BINARY encoding, filled with null characters + # if size is greater than zero + def self.empty_byte_buffer(size = nil) + if (size && size > 0) + "\0".force_encoding(Encoding::BINARY) * size + else + ''.force_encoding(Encoding::BINARY) + end + end + + # Forces the encoding of the buffer to BINARY. If the buffer + # passed is frozen, then it will be duplicated. + # + # buffer - The String to force the encoding of. + # + # Returns the String passed with an encoding of BINARY; returned + # String may be a duplicate. + def self.force_binary_encoding(buffer) + buffer = buffer.dup if buffer.frozen? + buffer.force_encoding(Encoding::BINARY) + end + + # Gets the byte value of a given position in a String. + # + # string - The String to retrive the byte value from. + # index - The Integer location of the byte value to retrieve. + # + # Returns an Integer value between 0 and 255. + def self.get_string_byte(string, index) + string.getbyte(index) + end + + # Sets the byte value given to a given index in a String. + # + # string - The String to set the byte value in. + # index - The Integer location to set the byte value at. + # byte - The Integer value (0 to 255) to set in the string. + # + # Returns an Integer value of the byte value to set. + def self.set_string_byte(string, index, byte) + string.setbyte(index, byte) + end + + # Converts the given String to a UTF-8 byte buffer. + # + # string - The String to convert. + # + # Returns a new String with BINARY encoding, containing the UTF-8 + # bytes of the original string. + def self.convert_to_utf8_byte_buffer(string) + if string.encoding != Encoding::UTF_8 + # transcode to UTF-8 + string = string.encode(Encoding::UTF_8) + else + # encoding is already UTF-8, but a duplicate is needed + string = string.dup + end + string.force_encoding(Encoding::BINARY) + end + + # Converts the given UTF-8 byte buffer into a String + # + # utf8_buffer - A String, with BINARY encoding, containing UTF-8 bytes + # + # Returns a new String with UTF-8 encoding, + def self.convert_to_string(utf8_buffer) + # duplicate the buffer, force encoding to UTF-8 + utf8_buffer.dup.force_encoding(Encoding::UTF_8) + end + else + def self.empty_byte_buffer(size = nil) + if (size && size > 0) + "\0" * size + else + '' + end + end + + def self.force_binary_encoding(buffer) + buffer + end + + def self.get_string_byte(string, index) + string[index] + end + + def self.set_string_byte(string, index, byte) + string[index] = byte + end + + def self.convert_to_utf8_byte_buffer(string) + # This assumes $KCODE is 'UTF8'/'U', which would mean the String is already a UTF-8 byte buffer + # TODO consider handling other $KCODE values and transcoding with iconv + string + end + + def self.convert_to_string(utf8_buffer) + # See comment in 'convert_to_utf8_byte_buffer' for relevant assumptions. + utf8_buffer + end + end + end +end diff --git a/src/jaegertracing/thrift/lib/rb/lib/thrift/client.rb b/src/jaegertracing/thrift/lib/rb/lib/thrift/client.rb new file mode 100644 index 000000000..64ef05956 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/lib/thrift/client.rb @@ -0,0 +1,71 @@ +# +# 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. +# + +module Thrift + module Client + def initialize(iprot, oprot=nil) + @iprot = iprot + @oprot = oprot || iprot + @seqid = 0 + end + + def send_message(name, args_class, args = {}) + @oprot.write_message_begin(name, MessageTypes::CALL, @seqid) + send_message_args(args_class, args) + end + + def send_oneway_message(name, args_class, args = {}) + @oprot.write_message_begin(name, MessageTypes::ONEWAY, @seqid) + send_message_args(args_class, args) + end + + def send_message_args(args_class, args) + data = args_class.new + args.each do |k, v| + data.send("#{k.to_s}=", v) + end + begin + data.write(@oprot) + rescue StandardError => e + @oprot.trans.close + raise e + end + @oprot.write_message_end + @oprot.trans.flush + end + + def receive_message(result_klass) + fname, mtype, rseqid = @iprot.read_message_begin + handle_exception(mtype) + result = result_klass.new + result.read(@iprot) + @iprot.read_message_end + result + end + + def handle_exception(mtype) + if mtype == MessageTypes::EXCEPTION + x = ApplicationException.new + x.read(@iprot) + @iprot.read_message_end + raise x + end + end + end +end diff --git a/src/jaegertracing/thrift/lib/rb/lib/thrift/core_ext.rb b/src/jaegertracing/thrift/lib/rb/lib/thrift/core_ext.rb new file mode 100644 index 000000000..f763cd534 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/lib/thrift/core_ext.rb @@ -0,0 +1,23 @@ +# +# 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. +# + +Dir[File.dirname(__FILE__) + "/core_ext/*.rb"].each do |file| + name = File.basename(file, '.rb') + require "thrift/core_ext/#{name}" +end diff --git a/src/jaegertracing/thrift/lib/rb/lib/thrift/core_ext/fixnum.rb b/src/jaegertracing/thrift/lib/rb/lib/thrift/core_ext/fixnum.rb new file mode 100644 index 000000000..b4fc90dd6 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/lib/thrift/core_ext/fixnum.rb @@ -0,0 +1,29 @@ +# +# 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. +# + +# Versions of ruby pre 1.8.7 do not have an .ord method available in the Fixnum +# class. +# +if RUBY_VERSION < "1.8.7" + class Fixnum + def ord + self + end + end +end
\ No newline at end of file diff --git a/src/jaegertracing/thrift/lib/rb/lib/thrift/exceptions.rb b/src/jaegertracing/thrift/lib/rb/lib/thrift/exceptions.rb new file mode 100644 index 000000000..68cb9e03a --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/lib/thrift/exceptions.rb @@ -0,0 +1,87 @@ +# +# 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. +# + +module Thrift + class Exception < StandardError + def initialize(message) + super + @message = message + end + + attr_reader :message + end + + class ApplicationException < Exception + + UNKNOWN = 0 + UNKNOWN_METHOD = 1 + INVALID_MESSAGE_TYPE = 2 + WRONG_METHOD_NAME = 3 + BAD_SEQUENCE_ID = 4 + MISSING_RESULT = 5 + INTERNAL_ERROR = 6 + PROTOCOL_ERROR = 7 + INVALID_TRANSFORM = 8 + INVALID_PROTOCOL = 9 + UNSUPPORTED_CLIENT_TYPE = 10 + + attr_reader :type + + def initialize(type=UNKNOWN, message=nil) + super(message) + @type = type + end + + def read(iprot) + iprot.read_struct_begin + while true + fname, ftype, fid = iprot.read_field_begin + if ftype == Types::STOP + break + end + if fid == 1 and ftype == Types::STRING + @message = iprot.read_string + elsif fid == 2 and ftype == Types::I32 + @type = iprot.read_i32 + else + iprot.skip(ftype) + end + iprot.read_field_end + end + iprot.read_struct_end + end + + def write(oprot) + oprot.write_struct_begin('Thrift::ApplicationException') + unless @message.nil? + oprot.write_field_begin('message', Types::STRING, 1) + oprot.write_string(@message) + oprot.write_field_end + end + unless @type.nil? + oprot.write_field_begin('type', Types::I32, 2) + oprot.write_i32(@type) + oprot.write_field_end + end + oprot.write_field_stop + oprot.write_struct_end + end + + end +end diff --git a/src/jaegertracing/thrift/lib/rb/lib/thrift/multiplexed_processor.rb b/src/jaegertracing/thrift/lib/rb/lib/thrift/multiplexed_processor.rb new file mode 100644 index 000000000..c734c04ba --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/lib/thrift/multiplexed_processor.rb @@ -0,0 +1,76 @@ +# +# 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 'thrift/protocol/protocol_decorator' +require 'thrift/protocol/base_protocol' + +module Thrift + class MultiplexedProcessor + def initialize + @actual_processors = {} + end + + def register_processor(service_name, processor) + @actual_processors[service_name] = processor + end + + def process(iprot, oprot) + name, type, seqid = iprot.read_message_begin + check_type(type) + check_separator(name) + service_name, method = name.split(':') + processor(service_name).process(StoredMessageProtocol.new(iprot, [method, type, seqid]), oprot) + end + + protected + + def processor(service_name) + if @actual_processors.has_key?(service_name) + @actual_processors[service_name] + else + raise Thrift::Exception.new("Service name not found: #{service_name}. Did you forget to call #{self.class.name}#register_processor?") + end + end + + def check_type(type) + unless [MessageTypes::CALL, MessageTypes::ONEWAY].include?(type) + raise Thrift::Exception.new('This should not have happened!?') + end + end + + def check_separator(name) + if name.count(':') < 1 + raise Thrift::Exception.new("Service name not found in message name: #{name}. Did you forget to use a Thrift::Protocol::MultiplexedProtocol in your client?") + end + end + end + + class StoredMessageProtocol < BaseProtocol + + include ProtocolDecorator + + def initialize(protocol, message_begin) + super(protocol) + @message_begin = message_begin + end + + def read_message_begin + @message_begin + end + end +end diff --git a/src/jaegertracing/thrift/lib/rb/lib/thrift/processor.rb b/src/jaegertracing/thrift/lib/rb/lib/thrift/processor.rb new file mode 100644 index 000000000..ce21e120a --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/lib/thrift/processor.rb @@ -0,0 +1,75 @@ +# +# 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 'logger' + +module Thrift + module Processor + def initialize(handler, logger=nil) + @handler = handler + if logger.nil? + @logger = Logger.new(STDERR) + @logger.level = Logger::WARN + else + @logger = logger + end + end + + def process(iprot, oprot) + name, type, seqid = iprot.read_message_begin + if respond_to?("process_#{name}") + begin + send("process_#{name}", seqid, iprot, oprot) + rescue => e + x = ApplicationException.new(ApplicationException::INTERNAL_ERROR, 'Internal error') + @logger.debug "Internal error : #{e.message}\n#{e.backtrace.join("\n")}" + write_error(x, oprot, name, seqid) + end + true + else + iprot.skip(Types::STRUCT) + iprot.read_message_end + x = ApplicationException.new(ApplicationException::UNKNOWN_METHOD, 'Unknown function '+name) + write_error(x, oprot, name, seqid) + false + end + end + + def read_args(iprot, args_class) + args = args_class.new + args.read(iprot) + iprot.read_message_end + args + end + + def write_result(result, oprot, name, seqid) + oprot.write_message_begin(name, MessageTypes::REPLY, seqid) + result.write(oprot) + oprot.write_message_end + oprot.trans.flush + end + + def write_error(err, oprot, name, seqid) + oprot.write_message_begin(name, MessageTypes::EXCEPTION, seqid) + err.write(oprot) + oprot.write_message_end + oprot.trans.flush + end + end +end diff --git a/src/jaegertracing/thrift/lib/rb/lib/thrift/protocol/base_protocol.rb b/src/jaegertracing/thrift/lib/rb/lib/thrift/protocol/base_protocol.rb new file mode 100644 index 000000000..4d83a21dd --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/lib/thrift/protocol/base_protocol.rb @@ -0,0 +1,387 @@ +# +# 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. +# + +# this require is to make generated struct definitions happy +require 'set' + +module Thrift + class ProtocolException < Exception + + UNKNOWN = 0 + INVALID_DATA = 1 + NEGATIVE_SIZE = 2 + SIZE_LIMIT = 3 + BAD_VERSION = 4 + NOT_IMPLEMENTED = 5 + DEPTH_LIMIT = 6 + + attr_reader :type + + def initialize(type=UNKNOWN, message=nil) + super(message) + @type = type + end + end + + class BaseProtocol + + attr_reader :trans + + def initialize(trans) + @trans = trans + end + + def native? + puts "wrong method is being called!" + false + end + + def write_message_begin(name, type, seqid) + raise NotImplementedError + end + + def write_message_end; nil; end + + def write_struct_begin(name) + raise NotImplementedError + end + + def write_struct_end; nil; end + + def write_field_begin(name, type, id) + raise NotImplementedError + end + + def write_field_end; nil; end + + def write_field_stop + raise NotImplementedError + end + + def write_map_begin(ktype, vtype, size) + raise NotImplementedError + end + + def write_map_end; nil; end + + def write_list_begin(etype, size) + raise NotImplementedError + end + + def write_list_end; nil; end + + def write_set_begin(etype, size) + raise NotImplementedError + end + + def write_set_end; nil; end + + def write_bool(bool) + raise NotImplementedError + end + + def write_byte(byte) + raise NotImplementedError + end + + def write_i16(i16) + raise NotImplementedError + end + + def write_i32(i32) + raise NotImplementedError + end + + def write_i64(i64) + raise NotImplementedError + end + + def write_double(dub) + raise NotImplementedError + end + + # Writes a Thrift String. In Ruby 1.9+, the String passed will be transcoded to UTF-8. + # + # str - The String to write. + # + # Raises EncodingError if the transcoding to UTF-8 fails. + # + # Returns nothing. + def write_string(str) + raise NotImplementedError + end + + # Writes a Thrift Binary (Thrift String with no encoding). In Ruby 1.9+, the String passed + # will forced into BINARY encoding. + # + # buf - The String to write. + # + # Returns nothing. + def write_binary(buf) + raise NotImplementedError + end + + def read_message_begin + raise NotImplementedError + end + + def read_message_end; nil; end + + def read_struct_begin + raise NotImplementedError + end + + def read_struct_end; nil; end + + def read_field_begin + raise NotImplementedError + end + + def read_field_end; nil; end + + def read_map_begin + raise NotImplementedError + end + + def read_map_end; nil; end + + def read_list_begin + raise NotImplementedError + end + + def read_list_end; nil; end + + def read_set_begin + raise NotImplementedError + end + + def read_set_end; nil; end + + def read_bool + raise NotImplementedError + end + + def read_byte + raise NotImplementedError + end + + def read_i16 + raise NotImplementedError + end + + def read_i32 + raise NotImplementedError + end + + def read_i64 + raise NotImplementedError + end + + def read_double + raise NotImplementedError + end + + # Reads a Thrift String. In Ruby 1.9+, all Strings will be returned with an Encoding of UTF-8. + # + # Returns a String. + def read_string + raise NotImplementedError + end + + # Reads a Thrift Binary (Thrift String without encoding). In Ruby 1.9+, all Strings will be returned + # with an Encoding of BINARY. + # + # Returns a String. + def read_binary + raise NotImplementedError + end + + # Writes a field based on the field information, field ID and value. + # + # field_info - A Hash containing the definition of the field: + # :name - The name of the field. + # :type - The type of the field, which must be a Thrift::Types constant. + # :binary - A Boolean flag that indicates if Thrift::Types::STRING is a binary string (string without encoding). + # fid - The ID of the field. + # value - The field's value to write; object type varies based on :type. + # + # Returns nothing. + def write_field(*args) + if args.size == 3 + # handles the documented method signature - write_field(field_info, fid, value) + field_info = args[0] + fid = args[1] + value = args[2] + elsif args.size == 4 + # handles the deprecated method signature - write_field(name, type, fid, value) + field_info = {:name => args[0], :type => args[1]} + fid = args[2] + value = args[3] + else + raise ArgumentError, "wrong number of arguments (#{args.size} for 3)" + end + + write_field_begin(field_info[:name], field_info[:type], fid) + write_type(field_info, value) + write_field_end + end + + # Writes a field value based on the field information. + # + # field_info - A Hash containing the definition of the field: + # :type - The Thrift::Types constant that determines how the value is written. + # :binary - A Boolean flag that indicates if Thrift::Types::STRING is a binary string (string without encoding). + # value - The field's value to write; object type varies based on field_info[:type]. + # + # Returns nothing. + def write_type(field_info, value) + # if field_info is a Fixnum, assume it is a Thrift::Types constant + # convert it into a field_info Hash for backwards compatibility + if field_info.is_a? Fixnum + field_info = {:type => field_info} + end + + case field_info[:type] + when Types::BOOL + write_bool(value) + when Types::BYTE + write_byte(value) + when Types::DOUBLE + write_double(value) + when Types::I16 + write_i16(value) + when Types::I32 + write_i32(value) + when Types::I64 + write_i64(value) + when Types::STRING + if field_info[:binary] + write_binary(value) + else + write_string(value) + end + when Types::STRUCT + value.write(self) + else + raise NotImplementedError + end + end + + # Reads a field value based on the field information. + # + # field_info - A Hash containing the pertinent data to write: + # :type - The Thrift::Types constant that determines how the value is written. + # :binary - A flag that indicates if Thrift::Types::STRING is a binary string (string without encoding). + # + # Returns the value read; object type varies based on field_info[:type]. + def read_type(field_info) + # if field_info is a Fixnum, assume it is a Thrift::Types constant + # convert it into a field_info Hash for backwards compatibility + if field_info.is_a? Fixnum + field_info = {:type => field_info} + end + + case field_info[:type] + when Types::BOOL + read_bool + when Types::BYTE + read_byte + when Types::DOUBLE + read_double + when Types::I16 + read_i16 + when Types::I32 + read_i32 + when Types::I64 + read_i64 + when Types::STRING + if field_info[:binary] + read_binary + else + read_string + end + else + raise NotImplementedError + end + end + + def skip(type) + case type + when Types::BOOL + read_bool + when Types::BYTE + read_byte + when Types::I16 + read_i16 + when Types::I32 + read_i32 + when Types::I64 + read_i64 + when Types::DOUBLE + read_double + when Types::STRING + read_string + when Types::STRUCT + read_struct_begin + while true + name, type, id = read_field_begin + break if type == Types::STOP + skip(type) + read_field_end + end + read_struct_end + when Types::MAP + ktype, vtype, size = read_map_begin + size.times do + skip(ktype) + skip(vtype) + end + read_map_end + when Types::SET + etype, size = read_set_begin + size.times do + skip(etype) + end + read_set_end + when Types::LIST + etype, size = read_list_begin + size.times do + skip(etype) + end + read_list_end + else + raise ProtocolException.new(ProtocolException::INVALID_DATA, 'Invalid data') + end + end + + def to_s + "#{trans.to_s}" + end + end + + class BaseProtocolFactory + def get_protocol(trans) + raise NotImplementedError + end + + def to_s + "base" + end + end +end diff --git a/src/jaegertracing/thrift/lib/rb/lib/thrift/protocol/binary_protocol.rb b/src/jaegertracing/thrift/lib/rb/lib/thrift/protocol/binary_protocol.rb new file mode 100644 index 000000000..d8279dbe6 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/lib/thrift/protocol/binary_protocol.rb @@ -0,0 +1,244 @@ +# +# 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. +# + +module Thrift + class BinaryProtocol < BaseProtocol + VERSION_MASK = 0xffff0000 + VERSION_1 = 0x80010000 + TYPE_MASK = 0x000000ff + + attr_reader :strict_read, :strict_write + + def initialize(trans, strict_read=true, strict_write=true) + super(trans) + @strict_read = strict_read + @strict_write = strict_write + + # Pre-allocated read buffer for fixed-size read methods. Needs to be at least 8 bytes long for + # read_i64() and read_double(). + @rbuf = Bytes.empty_byte_buffer(8) + end + + def write_message_begin(name, type, seqid) + # this is necessary because we added (needed) bounds checking to + # write_i32, and 0x80010000 is too big for that. + if strict_write + write_i16(VERSION_1 >> 16) + write_i16(type) + write_string(name) + write_i32(seqid) + else + write_string(name) + write_byte(type) + write_i32(seqid) + end + end + + def write_struct_begin(name); nil; end + + def write_field_begin(name, type, id) + write_byte(type) + write_i16(id) + end + + def write_field_stop + write_byte(Thrift::Types::STOP) + end + + def write_map_begin(ktype, vtype, size) + write_byte(ktype) + write_byte(vtype) + write_i32(size) + end + + def write_list_begin(etype, size) + write_byte(etype) + write_i32(size) + end + + def write_set_begin(etype, size) + write_byte(etype) + write_i32(size) + end + + def write_bool(bool) + write_byte(bool ? 1 : 0) + end + + def write_byte(byte) + raise RangeError if byte < -2**31 || byte >= 2**32 + trans.write([byte].pack('c')) + end + + def write_i16(i16) + trans.write([i16].pack('n')) + end + + def write_i32(i32) + raise RangeError if i32 < -2**31 || i32 >= 2**31 + trans.write([i32].pack('N')) + end + + def write_i64(i64) + raise RangeError if i64 < -2**63 || i64 >= 2**64 + hi = i64 >> 32 + lo = i64 & 0xffffffff + trans.write([hi, lo].pack('N2')) + end + + def write_double(dub) + trans.write([dub].pack('G')) + end + + def write_string(str) + buf = Bytes.convert_to_utf8_byte_buffer(str) + write_binary(buf) + end + + def write_binary(buf) + write_i32(buf.bytesize) + trans.write(buf) + end + + def read_message_begin + version = read_i32 + if version < 0 + if (version & VERSION_MASK != VERSION_1) + raise ProtocolException.new(ProtocolException::BAD_VERSION, 'Missing version identifier') + end + type = version & TYPE_MASK + name = read_string + seqid = read_i32 + [name, type, seqid] + else + if strict_read + raise ProtocolException.new(ProtocolException::BAD_VERSION, 'No version identifier, old protocol client?') + end + name = trans.read_all(version) + type = read_byte + seqid = read_i32 + [name, type, seqid] + end + end + + def read_struct_begin; nil; end + + def read_field_begin + type = read_byte + if (type == Types::STOP) + [nil, type, 0] + else + id = read_i16 + [nil, type, id] + end + end + + def read_map_begin + ktype = read_byte + vtype = read_byte + size = read_i32 + [ktype, vtype, size] + end + + def read_list_begin + etype = read_byte + size = read_i32 + [etype, size] + end + + def read_set_begin + etype = read_byte + size = read_i32 + [etype, size] + end + + def read_bool + byte = read_byte + byte != 0 + end + + def read_byte + val = trans.read_byte + if (val > 0x7f) + val = 0 - ((val - 1) ^ 0xff) + end + val + end + + def read_i16 + trans.read_into_buffer(@rbuf, 2) + val, = @rbuf.unpack('n') + if (val > 0x7fff) + val = 0 - ((val - 1) ^ 0xffff) + end + val + end + + def read_i32 + trans.read_into_buffer(@rbuf, 4) + val, = @rbuf.unpack('N') + if (val > 0x7fffffff) + val = 0 - ((val - 1) ^ 0xffffffff) + end + val + end + + def read_i64 + trans.read_into_buffer(@rbuf, 8) + hi, lo = @rbuf.unpack('N2') + if (hi > 0x7fffffff) + hi ^= 0xffffffff + lo ^= 0xffffffff + 0 - (hi << 32) - lo - 1 + else + (hi << 32) + lo + end + end + + def read_double + trans.read_into_buffer(@rbuf, 8) + val = @rbuf.unpack('G').first + val + end + + def read_string + buffer = read_binary + Bytes.convert_to_string(buffer) + end + + def read_binary + size = read_i32 + trans.read_all(size) + end + + def to_s + "binary(#{super.to_s})" + end + end + + class BinaryProtocolFactory < BaseProtocolFactory + def get_protocol(trans) + return Thrift::BinaryProtocol.new(trans) + end + + def to_s + "binary" + end + end +end diff --git a/src/jaegertracing/thrift/lib/rb/lib/thrift/protocol/binary_protocol_accelerated.rb b/src/jaegertracing/thrift/lib/rb/lib/thrift/protocol/binary_protocol_accelerated.rb new file mode 100644 index 000000000..09b02644d --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/lib/thrift/protocol/binary_protocol_accelerated.rb @@ -0,0 +1,47 @@ +# +# 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. +# + +=begin +The only change required for a transport to support BinaryProtocolAccelerated is to implement 2 methods: + * borrow(size), which takes an optional argument and returns atleast _size_ bytes from the transport, + or the default buffer size if no argument is given + * consume!(size), which removes size bytes from the front of the buffer + +See MemoryBuffer and BufferedTransport for examples. +=end + +module Thrift + class BinaryProtocolAcceleratedFactory < BaseProtocolFactory + def get_protocol(trans) + if (defined? BinaryProtocolAccelerated) + BinaryProtocolAccelerated.new(trans) + else + BinaryProtocol.new(trans) + end + end + + def to_s + if (defined? BinaryProtocolAccelerated) + "binary-accel" + else + "binary" + end + end + end +end diff --git a/src/jaegertracing/thrift/lib/rb/lib/thrift/protocol/compact_protocol.rb b/src/jaegertracing/thrift/lib/rb/lib/thrift/protocol/compact_protocol.rb new file mode 100644 index 000000000..1f9bd3060 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/lib/thrift/protocol/compact_protocol.rb @@ -0,0 +1,443 @@ +# +# 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. +# + +module Thrift + class CompactProtocol < BaseProtocol + + PROTOCOL_ID = [0x82].pack('c').unpack('c').first + VERSION = 1 + VERSION_MASK = 0x1f + TYPE_MASK = 0xE0 + TYPE_BITS = 0x07 + TYPE_SHIFT_AMOUNT = 5 + + TSTOP = ["", Types::STOP, 0] + + # + # All of the on-wire type codes. + # + class CompactTypes + BOOLEAN_TRUE = 0x01 + BOOLEAN_FALSE = 0x02 + BYTE = 0x03 + I16 = 0x04 + I32 = 0x05 + I64 = 0x06 + DOUBLE = 0x07 + BINARY = 0x08 + LIST = 0x09 + SET = 0x0A + MAP = 0x0B + STRUCT = 0x0C + + def self.is_bool_type?(b) + (b & 0x0f) == BOOLEAN_TRUE || (b & 0x0f) == BOOLEAN_FALSE + end + + COMPACT_TO_TTYPE = { + Types::STOP => Types::STOP, + BOOLEAN_FALSE => Types::BOOL, + BOOLEAN_TRUE => Types::BOOL, + BYTE => Types::BYTE, + I16 => Types::I16, + I32 => Types::I32, + I64 => Types::I64, + DOUBLE => Types::DOUBLE, + BINARY => Types::STRING, + LIST => Types::LIST, + SET => Types::SET, + MAP => Types::MAP, + STRUCT => Types::STRUCT + } + + TTYPE_TO_COMPACT = { + Types::STOP => Types::STOP, + Types::BOOL => BOOLEAN_TRUE, + Types::BYTE => BYTE, + Types::I16 => I16, + Types::I32 => I32, + Types::I64 => I64, + Types::DOUBLE => DOUBLE, + Types::STRING => BINARY, + Types::LIST => LIST, + Types::SET => SET, + Types::MAP => MAP, + Types::STRUCT => STRUCT + } + + def self.get_ttype(compact_type) + val = COMPACT_TO_TTYPE[compact_type & 0x0f] + raise "don't know what type: #{compact_type & 0x0f}" unless val + val + end + + def self.get_compact_type(ttype) + val = TTYPE_TO_COMPACT[ttype] + raise "don't know what type: #{ttype & 0x0f}" unless val + val + end + end + + def initialize(transport) + super(transport) + + @last_field = [0] + @boolean_value = nil + + # Pre-allocated read buffer for read_double(). + @rbuf = Bytes.empty_byte_buffer(8) + end + + def write_message_begin(name, type, seqid) + write_byte(PROTOCOL_ID) + write_byte((VERSION & VERSION_MASK) | ((type << TYPE_SHIFT_AMOUNT) & TYPE_MASK)) + write_varint32(seqid) + write_string(name) + nil + end + + def write_struct_begin(name) + @last_field.push(0) + nil + end + + def write_struct_end + @last_field.pop + nil + end + + def write_field_begin(name, type, id) + if type == Types::BOOL + # we want to possibly include the value, so we'll wait. + @boolean_field = [type, id] + else + write_field_begin_internal(type, id) + end + nil + end + + # + # The workhorse of writeFieldBegin. It has the option of doing a + # 'type override' of the type header. This is used specifically in the + # boolean field case. + # + def write_field_begin_internal(type, id, type_override=nil) + last_id = @last_field.pop + + # if there's a type override, use that. + typeToWrite = type_override || CompactTypes.get_compact_type(type) + + # check if we can use delta encoding for the field id + if id > last_id && id - last_id <= 15 + # write them together + write_byte((id - last_id) << 4 | typeToWrite) + else + # write them separate + write_byte(typeToWrite) + write_i16(id) + end + + @last_field.push(id) + nil + end + + def write_field_stop + write_byte(Types::STOP) + end + + def write_map_begin(ktype, vtype, size) + if (size == 0) + write_byte(0) + else + write_varint32(size) + write_byte(CompactTypes.get_compact_type(ktype) << 4 | CompactTypes.get_compact_type(vtype)) + end + end + + def write_list_begin(etype, size) + write_collection_begin(etype, size) + end + + def write_set_begin(etype, size) + write_collection_begin(etype, size); + end + + def write_bool(bool) + type = bool ? CompactTypes::BOOLEAN_TRUE : CompactTypes::BOOLEAN_FALSE + unless @boolean_field.nil? + # we haven't written the field header yet + write_field_begin_internal(@boolean_field.first, @boolean_field.last, type) + @boolean_field = nil + else + # we're not part of a field, so just write the value. + write_byte(type) + end + end + + def write_byte(byte) + @trans.write([byte].pack('c')) + end + + def write_i16(i16) + write_varint32(int_to_zig_zag(i16)) + end + + def write_i32(i32) + write_varint32(int_to_zig_zag(i32)) + end + + def write_i64(i64) + write_varint64(long_to_zig_zag(i64)) + end + + def write_double(dub) + @trans.write([dub].pack("G").reverse) + end + + def write_string(str) + buf = Bytes.convert_to_utf8_byte_buffer(str) + write_binary(buf) + end + + def write_binary(buf) + write_varint32(buf.bytesize) + @trans.write(buf) + end + + def read_message_begin + protocol_id = read_byte() + if protocol_id != PROTOCOL_ID + raise ProtocolException.new("Expected protocol id #{PROTOCOL_ID} but got #{protocol_id}") + end + + version_and_type = read_byte() + version = version_and_type & VERSION_MASK + if (version != VERSION) + raise ProtocolException.new("Expected version #{VERSION} but got #{version}"); + end + + type = (version_and_type >> TYPE_SHIFT_AMOUNT) & TYPE_BITS + seqid = read_varint32() + messageName = read_string() + [messageName, type, seqid] + end + + def read_struct_begin + @last_field.push(0) + "" + end + + def read_struct_end + @last_field.pop() + nil + end + + def read_field_begin + type = read_byte() + + # if it's a stop, then we can return immediately, as the struct is over. + if (type & 0x0f) == Types::STOP + TSTOP + else + field_id = nil + + # mask off the 4 MSB of the type header. it could contain a field id delta. + modifier = (type & 0xf0) >> 4 + if modifier == 0 + # not a delta. look ahead for the zigzag varint field id. + @last_field.pop + field_id = read_i16() + else + # has a delta. add the delta to the last read field id. + field_id = @last_field.pop + modifier + end + + # if this happens to be a boolean field, the value is encoded in the type + if CompactTypes.is_bool_type?(type) + # save the boolean value in a special instance variable. + @bool_value = (type & 0x0f) == CompactTypes::BOOLEAN_TRUE + end + + # push the new field onto the field stack so we can keep the deltas going. + @last_field.push(field_id) + ["", CompactTypes.get_ttype(type & 0x0f), field_id] + end + end + + def read_map_begin + size = read_varint32() + key_and_value_type = size == 0 ? 0 : read_byte() + [CompactTypes.get_ttype(key_and_value_type >> 4), CompactTypes.get_ttype(key_and_value_type & 0xf), size] + end + + def read_list_begin + size_and_type = read_byte() + size = (size_and_type >> 4) & 0x0f + if size == 15 + size = read_varint32() + end + type = CompactTypes.get_ttype(size_and_type) + [type, size] + end + + def read_set_begin + read_list_begin + end + + def read_bool + unless @bool_value.nil? + bv = @bool_value + @bool_value = nil + bv + else + read_byte() == CompactTypes::BOOLEAN_TRUE + end + end + + def read_byte + val = trans.read_byte + if (val > 0x7f) + val = 0 - ((val - 1) ^ 0xff) + end + val + end + + def read_i16 + zig_zag_to_int(read_varint32()) + end + + def read_i32 + zig_zag_to_int(read_varint32()) + end + + def read_i64 + zig_zag_to_long(read_varint64()) + end + + def read_double + trans.read_into_buffer(@rbuf, 8) + val = @rbuf.reverse.unpack('G').first + val + end + + def read_string + buffer = read_binary + Bytes.convert_to_string(buffer) + end + + def read_binary + size = read_varint32() + trans.read_all(size) + end + + def to_s + "compact(#{super.to_s})" + end + + private + + # + # Abstract method for writing the start of lists and sets. List and sets on + # the wire differ only by the type indicator. + # + def write_collection_begin(elem_type, size) + if size <= 14 + write_byte(size << 4 | CompactTypes.get_compact_type(elem_type)) + else + write_byte(0xf0 | CompactTypes.get_compact_type(elem_type)) + write_varint32(size) + end + end + + def write_varint32(n) + # int idx = 0; + while true + if (n & ~0x7F) == 0 + # i32buf[idx++] = (byte)n; + write_byte(n) + break + # return; + else + # i32buf[idx++] = (byte)((n & 0x7F) | 0x80); + write_byte((n & 0x7F) | 0x80) + n = n >> 7 + end + end + # trans_.write(i32buf, 0, idx); + end + + SEVEN_BIT_MASK = 0x7F + EVERYTHING_ELSE_MASK = ~SEVEN_BIT_MASK + + def write_varint64(n) + while true + if (n & EVERYTHING_ELSE_MASK) == 0 #TODO need to find a way to make this into a long... + write_byte(n) + break + else + write_byte((n & SEVEN_BIT_MASK) | 0x80) + n >>= 7 + end + end + end + + def read_varint32() + read_varint64() + end + + def read_varint64() + shift = 0 + result = 0 + while true + b = read_byte() + result |= (b & 0x7f) << shift + break if (b & 0x80) != 0x80 + shift += 7 + end + result + end + + def int_to_zig_zag(n) + (n << 1) ^ (n >> 31) + end + + def long_to_zig_zag(l) + # puts "zz encoded #{l} to #{(l << 1) ^ (l >> 63)}" + (l << 1) ^ (l >> 63) + end + + def zig_zag_to_int(n) + (n >> 1) ^ -(n & 1) + end + + def zig_zag_to_long(n) + (n >> 1) ^ -(n & 1) + end + end + + class CompactProtocolFactory < BaseProtocolFactory + def get_protocol(trans) + CompactProtocol.new(trans) + end + + def to_s + "compact" + end + end +end diff --git a/src/jaegertracing/thrift/lib/rb/lib/thrift/protocol/json_protocol.rb b/src/jaegertracing/thrift/lib/rb/lib/thrift/protocol/json_protocol.rb new file mode 100644 index 000000000..91e74e46b --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/lib/thrift/protocol/json_protocol.rb @@ -0,0 +1,786 @@ +# 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 'base64' + +module Thrift + class LookaheadReader + def initialize(trans) + @trans = trans + @hasData = false + @data = nil + end + + def read + if @hasData + @hasData = false + else + @data = @trans.read(1) + end + + return @data + end + + def peek + if !@hasData + @data = @trans.read(1) + end + @hasData = true + return @data + end + end + + # + # Class to serve as base JSON context and as base class for other context + # implementations + # + class JSONContext + @@kJSONElemSeparator = ',' + # + # Write context data to the trans. Default is to do nothing. + # + def write(trans) + end + + # + # Read context data from the trans. Default is to do nothing. + # + def read(reader) + end + + # + # Return true if numbers need to be escaped as strings in this context. + # Default behavior is to return false. + # + def escapeNum + return false + end + end + + # Context class for object member key-value pairs + class JSONPairContext < JSONContext + @@kJSONPairSeparator = ':' + + def initialize + @first = true + @colon = true + end + + def write(trans) + if (@first) + @first = false + @colon = true + else + trans.write(@colon ? @@kJSONPairSeparator : @@kJSONElemSeparator) + @colon = !@colon + end + end + + def read(reader) + if (@first) + @first = false + @colon = true + else + ch = (@colon ? @@kJSONPairSeparator : @@kJSONElemSeparator) + @colon = !@colon + JsonProtocol::read_syntax_char(reader, ch) + end + end + + # Numbers must be turned into strings if they are the key part of a pair + def escapeNum + return @colon + end + end + + # Context class for lists + class JSONListContext < JSONContext + + def initialize + @first = true + end + + def write(trans) + if (@first) + @first = false + else + trans.write(@@kJSONElemSeparator) + end + end + + def read(reader) + if (@first) + @first = false + else + JsonProtocol::read_syntax_char(reader, @@kJSONElemSeparator) + end + end + end + + class JsonProtocol < BaseProtocol + + @@kJSONObjectStart = '{' + @@kJSONObjectEnd = '}' + @@kJSONArrayStart = '[' + @@kJSONArrayEnd = ']' + @@kJSONNewline = '\n' + @@kJSONBackslash = '\\' + @@kJSONStringDelimiter = '"' + + @@kThriftVersion1 = 1 + + @@kThriftNan = "NaN" + @@kThriftInfinity = "Infinity" + @@kThriftNegativeInfinity = "-Infinity" + + def initialize(trans) + super(trans) + @context = JSONContext.new + @contexts = Array.new + @reader = LookaheadReader.new(trans) + end + + def get_type_name_for_type_id(id) + case id + when Types::BOOL + "tf" + when Types::BYTE + "i8" + when Types::I16 + "i16" + when Types::I32 + "i32" + when Types::I64 + "i64" + when Types::DOUBLE + "dbl" + when Types::STRING + "str" + when Types::STRUCT + "rec" + when Types::MAP + "map" + when Types::SET + "set" + when Types::LIST + "lst" + else + raise NotImplementedError + end + end + + def get_type_id_for_type_name(name) + if (name == "tf") + result = Types::BOOL + elsif (name == "i8") + result = Types::BYTE + elsif (name == "i16") + result = Types::I16 + elsif (name == "i32") + result = Types::I32 + elsif (name == "i64") + result = Types::I64 + elsif (name == "dbl") + result = Types::DOUBLE + elsif (name == "str") + result = Types::STRING + elsif (name == "rec") + result = Types::STRUCT + elsif (name == "map") + result = Types::MAP + elsif (name == "set") + result = Types::SET + elsif (name == "lst") + result = Types::LIST + else + result = Types::STOP + end + if (result == Types::STOP) + raise NotImplementedError + end + return result + end + + # Static helper functions + + # Read 1 character from the trans and verify that it is the expected character ch. + # Throw a protocol exception if it is not. + def self.read_syntax_char(reader, ch) + ch2 = reader.read + if (ch2 != ch) + raise ProtocolException.new(ProtocolException::INVALID_DATA, "Expected \'#{ch}\' got \'#{ch2}\'.") + end + end + + # Return true if the character ch is in [-+0-9.Ee]; false otherwise + def is_json_numeric(ch) + case ch + when '+', '-', '.', '0' .. '9', 'E', "e" + return true + else + return false + end + end + + def push_context(context) + @contexts.push(@context) + @context = context + end + + def pop_context + @context = @contexts.pop + end + + # Write the character ch as a JSON escape sequence ("\u00xx") + def write_json_escape_char(ch) + trans.write('\\u') + ch_value = ch[0] + if (ch_value.kind_of? String) + ch_value = ch.bytes.first + end + trans.write(ch_value.to_s(16).rjust(4,'0')) + end + + # Write the character ch as part of a JSON string, escaping as appropriate. + def write_json_char(ch) + # This table describes the handling for the first 0x30 characters + # 0 : escape using "\u00xx" notation + # 1 : just output index + # <other> : escape using "\<other>" notation + kJSONCharTable = [ + # 0 1 2 3 4 5 6 7 8 9 A B C D E F + 0, 0, 0, 0, 0, 0, 0, 0,'b','t','n', 0,'f','r', 0, 0, # 0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # 1 + 1, 1,'"', 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, # 2 + ] + + ch_value = ch[0] + if (ch_value.kind_of? String) + ch_value = ch.bytes.first + end + if (ch_value >= 0x30) + if (ch == @@kJSONBackslash) # Only special character >= 0x30 is '\' + trans.write(@@kJSONBackslash) + trans.write(@@kJSONBackslash) + else + trans.write(ch) + end + else + outCh = kJSONCharTable[ch_value]; + # Check if regular character, backslash escaped, or JSON escaped + if outCh.kind_of? String + trans.write(@@kJSONBackslash) + trans.write(outCh) + elsif outCh == 1 + trans.write(ch) + else + write_json_escape_char(ch) + end + end + end + + # Write out the contents of the string str as a JSON string, escaping characters as appropriate. + def write_json_string(str) + @context.write(trans) + trans.write(@@kJSONStringDelimiter) + str.split('').each do |ch| + write_json_char(ch) + end + trans.write(@@kJSONStringDelimiter) + end + + # Write out the contents of the string as JSON string, base64-encoding + # the string's contents, and escaping as appropriate + def write_json_base64(str) + @context.write(trans) + trans.write(@@kJSONStringDelimiter) + trans.write(Base64.strict_encode64(str)) + trans.write(@@kJSONStringDelimiter) + end + + # Convert the given integer type to a JSON number, or a string + # if the context requires it (eg: key in a map pair). + def write_json_integer(num) + @context.write(trans) + escapeNum = @context.escapeNum + if (escapeNum) + trans.write(@@kJSONStringDelimiter) + end + trans.write(num.to_s); + if (escapeNum) + trans.write(@@kJSONStringDelimiter) + end + end + + # Convert the given double to a JSON string, which is either the number, + # "NaN" or "Infinity" or "-Infinity". + def write_json_double(num) + @context.write(trans) + # Normalize output of thrift::to_string for NaNs and Infinities + special = false; + if (num.nan?) + special = true; + val = @@kThriftNan; + elsif (num.infinite?) + special = true; + val = @@kThriftInfinity; + if (num < 0.0) + val = @@kThriftNegativeInfinity; + end + else + val = num.to_s + end + + escapeNum = special || @context.escapeNum + if (escapeNum) + trans.write(@@kJSONStringDelimiter) + end + trans.write(val) + if (escapeNum) + trans.write(@@kJSONStringDelimiter) + end + end + + def write_json_object_start + @context.write(trans) + trans.write(@@kJSONObjectStart) + push_context(JSONPairContext.new); + end + + def write_json_object_end + pop_context + trans.write(@@kJSONObjectEnd) + end + + def write_json_array_start + @context.write(trans) + trans.write(@@kJSONArrayStart) + push_context(JSONListContext.new); + end + + def write_json_array_end + pop_context + trans.write(@@kJSONArrayEnd) + end + + def write_message_begin(name, type, seqid) + write_json_array_start + write_json_integer(@@kThriftVersion1) + write_json_string(name) + write_json_integer(type) + write_json_integer(seqid) + end + + def write_message_end + write_json_array_end + end + + def write_struct_begin(name) + write_json_object_start + end + + def write_struct_end + write_json_object_end + end + + def write_field_begin(name, type, id) + write_json_integer(id) + write_json_object_start + write_json_string(get_type_name_for_type_id(type)) + end + + def write_field_end + write_json_object_end + end + + def write_field_stop; nil; end + + def write_map_begin(ktype, vtype, size) + write_json_array_start + write_json_string(get_type_name_for_type_id(ktype)) + write_json_string(get_type_name_for_type_id(vtype)) + write_json_integer(size) + write_json_object_start + end + + def write_map_end + write_json_object_end + write_json_array_end + end + + def write_list_begin(etype, size) + write_json_array_start + write_json_string(get_type_name_for_type_id(etype)) + write_json_integer(size) + end + + def write_list_end + write_json_array_end + end + + def write_set_begin(etype, size) + write_json_array_start + write_json_string(get_type_name_for_type_id(etype)) + write_json_integer(size) + end + + def write_set_end + write_json_array_end + end + + def write_bool(bool) + write_json_integer(bool ? 1 : 0) + end + + def write_byte(byte) + write_json_integer(byte) + end + + def write_i16(i16) + write_json_integer(i16) + end + + def write_i32(i32) + write_json_integer(i32) + end + + def write_i64(i64) + write_json_integer(i64) + end + + def write_double(dub) + write_json_double(dub) + end + + def write_string(str) + write_json_string(str) + end + + def write_binary(str) + write_json_base64(str) + end + + ## + # Reading functions + ## + + # Reads 1 byte and verifies that it matches ch. + def read_json_syntax_char(ch) + JsonProtocol::read_syntax_char(@reader, ch) + end + + # Decodes the four hex parts of a JSON escaped string character and returns + # the character via out. + # + # Note - this only supports Unicode characters in the BMP (U+0000 to U+FFFF); + # characters above the BMP are encoded as two escape sequences (surrogate pairs), + # which is not yet implemented + def read_json_escape_char + str = @reader.read + str += @reader.read + str += @reader.read + str += @reader.read + if RUBY_VERSION >= '1.9' + str.hex.chr(Encoding::UTF_8) + else + str.hex.chr + end + end + + # Decodes a JSON string, including unescaping, and returns the string via str + def read_json_string(skipContext = false) + # This string's characters must match up with the elements in escape_char_vals. + # I don't have '/' on this list even though it appears on www.json.org -- + # it is not in the RFC -> it is. See RFC 4627 + escape_chars = "\"\\/bfnrt" + + # The elements of this array must match up with the sequence of characters in + # escape_chars + escape_char_vals = [ + "\"", "\\", "\/", "\b", "\f", "\n", "\r", "\t", + ] + + if !skipContext + @context.read(@reader) + end + read_json_syntax_char(@@kJSONStringDelimiter) + ch = "" + str = "" + while (true) + ch = @reader.read + if (ch == @@kJSONStringDelimiter) + break + end + if (ch == @@kJSONBackslash) + ch = @reader.read + if (ch == 'u') + ch = read_json_escape_char + else + pos = escape_chars.index(ch); + if (pos.nil?) # not found + raise ProtocolException.new(ProtocolException::INVALID_DATA, "Expected control char, got \'#{ch}\'.") + end + ch = escape_char_vals[pos] + end + end + str += ch + end + return str + end + + # Reads a block of base64 characters, decoding it, and returns via str + def read_json_base64 + str = read_json_string + m = str.length % 4 + if m != 0 + # Add missing padding + (4 - m).times do + str += '=' + end + end + Base64.strict_decode64(str) + end + + # Reads a sequence of characters, stopping at the first one that is not + # a valid JSON numeric character. + def read_json_numeric_chars + str = "" + while (true) + ch = @reader.peek + if (!is_json_numeric(ch)) + break; + end + ch = @reader.read + str += ch + end + return str + end + + # Reads a sequence of characters and assembles them into a number, + # returning them via num + def read_json_integer + @context.read(@reader) + if (@context.escapeNum) + read_json_syntax_char(@@kJSONStringDelimiter) + end + str = read_json_numeric_chars + + begin + num = Integer(str); + rescue + raise ProtocolException.new(ProtocolException::INVALID_DATA, "Expected numeric value; got \"#{str}\"") + end + + if (@context.escapeNum) + read_json_syntax_char(@@kJSONStringDelimiter) + end + + return num + end + + # Reads a JSON number or string and interprets it as a double. + def read_json_double + @context.read(@reader) + num = 0 + if (@reader.peek == @@kJSONStringDelimiter) + str = read_json_string(true) + # Check for NaN, Infinity and -Infinity + if (str == @@kThriftNan) + num = (+1.0/0.0)/(+1.0/0.0) + elsif (str == @@kThriftInfinity) + num = +1.0/0.0 + elsif (str == @@kThriftNegativeInfinity) + num = -1.0/0.0 + else + if (!@context.escapeNum) + # Raise exception -- we should not be in a string in this case + raise ProtocolException.new(ProtocolException::INVALID_DATA, "Numeric data unexpectedly quoted") + end + begin + num = Float(str) + rescue + raise ProtocolException.new(ProtocolException::INVALID_DATA, "Expected numeric value; got \"#{str}\"") + end + end + else + if (@context.escapeNum) + # This will throw - we should have had a quote if escapeNum == true + read_json_syntax_char(@@kJSONStringDelimiter) + end + str = read_json_numeric_chars + begin + num = Float(str) + rescue + raise ProtocolException.new(ProtocolException::INVALID_DATA, "Expected numeric value; got \"#{str}\"") + end + end + return num + end + + def read_json_object_start + @context.read(@reader) + read_json_syntax_char(@@kJSONObjectStart) + push_context(JSONPairContext.new) + nil + end + + def read_json_object_end + read_json_syntax_char(@@kJSONObjectEnd) + pop_context + nil + end + + def read_json_array_start + @context.read(@reader) + read_json_syntax_char(@@kJSONArrayStart) + push_context(JSONListContext.new) + nil + end + + def read_json_array_end + read_json_syntax_char(@@kJSONArrayEnd) + pop_context + nil + end + + def read_message_begin + read_json_array_start + version = read_json_integer + if (version != @@kThriftVersion1) + raise ProtocolException.new(ProtocolException::BAD_VERSION, 'Message contained bad version.') + end + name = read_json_string + message_type = read_json_integer + seqid = read_json_integer + [name, message_type, seqid] + end + + def read_message_end + read_json_array_end + nil + end + + def read_struct_begin + read_json_object_start + nil + end + + def read_struct_end + read_json_object_end + nil + end + + def read_field_begin + # Check if we hit the end of the list + ch = @reader.peek + if (ch == @@kJSONObjectEnd) + field_type = Types::STOP + else + field_id = read_json_integer + read_json_object_start + field_type = get_type_id_for_type_name(read_json_string) + end + [nil, field_type, field_id] + end + + def read_field_end + read_json_object_end + end + + def read_map_begin + read_json_array_start + key_type = get_type_id_for_type_name(read_json_string) + val_type = get_type_id_for_type_name(read_json_string) + size = read_json_integer + read_json_object_start + [key_type, val_type, size] + end + + def read_map_end + read_json_object_end + read_json_array_end + end + + def read_list_begin + read_json_array_start + [get_type_id_for_type_name(read_json_string), read_json_integer] + end + + def read_list_end + read_json_array_end + end + + def read_set_begin + read_json_array_start + [get_type_id_for_type_name(read_json_string), read_json_integer] + end + + def read_set_end + read_json_array_end + end + + def read_bool + byte = read_byte + byte != 0 + end + + def read_byte + read_json_integer + end + + def read_i16 + read_json_integer + end + + def read_i32 + read_json_integer + end + + def read_i64 + read_json_integer + end + + def read_double + read_json_double + end + + def read_string + read_json_string + end + + def read_binary + read_json_base64 + end + + def to_s + "json(#{super.to_s})" + end + end + + class JsonProtocolFactory < BaseProtocolFactory + def get_protocol(trans) + return Thrift::JsonProtocol.new(trans) + end + + def to_s + "json" + end + end +end diff --git a/src/jaegertracing/thrift/lib/rb/lib/thrift/protocol/multiplexed_protocol.rb b/src/jaegertracing/thrift/lib/rb/lib/thrift/protocol/multiplexed_protocol.rb new file mode 100644 index 000000000..b4428a734 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/lib/thrift/protocol/multiplexed_protocol.rb @@ -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. + +require 'thrift/protocol/protocol_decorator' + +module Thrift + class MultiplexedProtocol < BaseProtocol + + include ProtocolDecorator + + def initialize(protocol, service_name) + super(protocol) + @service_name = service_name + end + + def write_message_begin(name, type, seqid) + case type + when MessageTypes::CALL, MessageTypes::ONEWAY + @protocol.write_message_begin("#{@service_name}:#{name}", type, seqid) + else + @protocol.write_message_begin(name, type, seqid) + end + end + + def to_s + "multiplexed(#{@service_name=@protocol.to_s})" + end + end +end diff --git a/src/jaegertracing/thrift/lib/rb/lib/thrift/protocol/protocol_decorator.rb b/src/jaegertracing/thrift/lib/rb/lib/thrift/protocol/protocol_decorator.rb new file mode 100644 index 000000000..b1e3c155d --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/lib/thrift/protocol/protocol_decorator.rb @@ -0,0 +1,194 @@ +# +# 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. + +module Thrift + module ProtocolDecorator + + def initialize(protocol) + @protocol = protocol + end + + def trans + @protocol.trans + end + + def write_message_begin(name, type, seqid) + @protocol.write_message_begin + end + + def write_message_end + @protocol.write_message_end + end + + def write_struct_begin(name) + @protocol.write_struct_begin(name) + end + + def write_struct_end + @protocol.write_struct_end + end + + def write_field_begin(name, type, id) + @protocol.write_field_begin(name, type, id) + end + + def write_field_end + @protocol.write_field_end + end + + def write_field_stop + @protocol.write_field_stop + end + + def write_map_begin(ktype, vtype, size) + @protocol.write_map_begin(ktype, vtype, size) + end + + def write_map_end + @protocol.write_map_end + end + + def write_list_begin(etype, size) + @protocol.write_list_begin(etype, size) + end + + def write_list_end + @protocol.write_list_end + end + + def write_set_begin(etype, size) + @protocol.write_set_begin(etype, size) + end + + def write_set_end + @protocol.write_set_end + end + + def write_bool(bool) + @protocol.write_bool(bool) + end + + def write_byte(byte) + @protocol.write_byte(byte) + end + + def write_i16(i16) + @protocol.write_i16(i16) + end + + def write_i32(i32) + @protocol.write_i32(i32) + end + + def write_i64(i64) + @protocol.write_i64(i64) + end + + def write_double(dub) + @protocol.write_double(dub) + end + + def write_string(str) + @protocol.write_string(str) + end + + def write_binary(buf) + @protocol.write_binary(buf) + end + + def read_message_begin + @protocol.read_message_begin + end + + def read_message_end + @protocol.read_message_end + end + + def read_struct_begin + @protocol.read_struct_begin + end + + def read_struct_end + @protocol.read_struct_end + end + + def read_field_begin + @protocol.read_field_begin + end + + def read_field_end + @protocol.read_field_end + end + + def read_map_begin + @protocol.read_map_begin + end + + def read_map_end + @protocol.read_map_end + end + + def read_list_begin + @protocol.read_list_begin + end + + def read_list_end + @protocol.read_list_end + end + + def read_set_begin + @protocol.read_set_begin + end + + def read_set_end + @protocol.read_set_end + end + + def read_bool + @protocol.read_bool + end + + def read_byte + @protocol.read_byte + end + + def read_i16 + @protocol.read_i16 + end + + def read_i32 + @protocol.read_i32 + end + + def read_i64 + @protocol.read_i64 + end + + def read_double + @protocol.read_double + end + + def read_string + @protocol.read_string + end + + def read_binary + @protocol.read_binary + end + end +end
\ No newline at end of file diff --git a/src/jaegertracing/thrift/lib/rb/lib/thrift/serializer/deserializer.rb b/src/jaegertracing/thrift/lib/rb/lib/thrift/serializer/deserializer.rb new file mode 100644 index 000000000..d2ee325a5 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/lib/thrift/serializer/deserializer.rb @@ -0,0 +1,33 @@ +# +# 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. +# + +module Thrift + class Deserializer + def initialize(protocol_factory = BinaryProtocolFactory.new) + @transport = MemoryBufferTransport.new + @protocol = protocol_factory.get_protocol(@transport) + end + + def deserialize(base, buffer) + @transport.reset_buffer(buffer) + base.read(@protocol) + base + end + end +end
\ No newline at end of file diff --git a/src/jaegertracing/thrift/lib/rb/lib/thrift/serializer/serializer.rb b/src/jaegertracing/thrift/lib/rb/lib/thrift/serializer/serializer.rb new file mode 100644 index 000000000..22316395d --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/lib/thrift/serializer/serializer.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. +# + +module Thrift + class Serializer + def initialize(protocol_factory = BinaryProtocolFactory.new) + @transport = MemoryBufferTransport.new + @protocol = protocol_factory.get_protocol(@transport) + end + + def serialize(base) + @transport.reset_buffer + base.write(@protocol) + @transport.read(@transport.available) + end + end +end + diff --git a/src/jaegertracing/thrift/lib/rb/lib/thrift/server/base_server.rb b/src/jaegertracing/thrift/lib/rb/lib/thrift/server/base_server.rb new file mode 100644 index 000000000..aa4d09ce4 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/lib/thrift/server/base_server.rb @@ -0,0 +1,37 @@ +# +# 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. +# + +module Thrift + class BaseServer + def initialize(processor, server_transport, transport_factory=nil, protocol_factory=nil) + @processor = processor + @server_transport = server_transport + @transport_factory = transport_factory ? transport_factory : Thrift::BaseTransportFactory.new + @protocol_factory = protocol_factory ? protocol_factory : Thrift::BinaryProtocolFactory.new + end + + def serve + raise NotImplementedError + end + + def to_s + "server(#{@protocol_factory.to_s}(#{@transport_factory.to_s}(#{@server_transport.to_s})))" + end + end +end diff --git a/src/jaegertracing/thrift/lib/rb/lib/thrift/server/mongrel_http_server.rb b/src/jaegertracing/thrift/lib/rb/lib/thrift/server/mongrel_http_server.rb new file mode 100644 index 000000000..de354c8f9 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/lib/thrift/server/mongrel_http_server.rb @@ -0,0 +1,60 @@ +# +# 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 'mongrel' + +## Sticks a service on a URL, using mongrel to do the HTTP work +# <b>DEPRECATED:</b> Please use <tt>Thrift::ThinHTTPServer</tt> instead. +module Thrift + class MongrelHTTPServer < BaseServer + class Handler < Mongrel::HttpHandler + def initialize(processor, protocol_factory) + @processor = processor + @protocol_factory = protocol_factory + end + + def process(request, response) + if request.params["REQUEST_METHOD"] == "POST" + response.start(200) do |head, out| + head["Content-Type"] = "application/x-thrift" + transport = IOStreamTransport.new request.body, out + protocol = @protocol_factory.get_protocol transport + @processor.process protocol, protocol + end + else + response.start(404) { } + end + end + end + + def initialize(processor, opts={}) + Kernel.warn "[DEPRECATION WARNING] `Thrift::MongrelHTTPServer` is deprecated. Please use `Thrift::ThinHTTPServer` instead." + port = opts[:port] || 80 + ip = opts[:ip] || "0.0.0.0" + path = opts[:path] || "" + protocol_factory = opts[:protocol_factory] || BinaryProtocolFactory.new + @server = Mongrel::HttpServer.new ip, port + @server.register "/#{path}", Handler.new(processor, protocol_factory) + end + + def serve + @server.run.join + end + end +end diff --git a/src/jaegertracing/thrift/lib/rb/lib/thrift/server/nonblocking_server.rb b/src/jaegertracing/thrift/lib/rb/lib/thrift/server/nonblocking_server.rb new file mode 100644 index 000000000..740f3417e --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/lib/thrift/server/nonblocking_server.rb @@ -0,0 +1,305 @@ +# +# 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 'logger' +require 'thread' + +module Thrift + # this class expects to always use a FramedTransport for reading messages + class NonblockingServer < BaseServer + def initialize(processor, server_transport, transport_factory=nil, protocol_factory=nil, num=20, logger=nil) + super(processor, server_transport, transport_factory, protocol_factory) + @num_threads = num + if logger.nil? + @logger = Logger.new(STDERR) + @logger.level = Logger::WARN + else + @logger = logger + end + @shutdown_semaphore = Mutex.new + @transport_semaphore = Mutex.new + end + + def serve + @logger.info "Starting #{self}" + @server_transport.listen + @io_manager = start_io_manager + + begin + loop do + break if @server_transport.closed? + begin + rd, = select([@server_transport], nil, nil, 0.1) + rescue Errno::EBADF => e + # In Ruby 1.9, calling @server_transport.close in shutdown paths causes the select() to raise an + # Errno::EBADF. If this happens, ignore it and retry the loop. + break + end + next if rd.nil? + socket = @server_transport.accept + @logger.debug "Accepted socket: #{socket.inspect}" + @io_manager.add_connection socket + end + rescue IOError => e + end + # we must be shutting down + @logger.info "#{self} is shutting down, goodbye" + ensure + @transport_semaphore.synchronize do + @server_transport.close + end + @io_manager.ensure_closed unless @io_manager.nil? + end + + def shutdown(timeout = 0, block = true) + @shutdown_semaphore.synchronize do + return if @is_shutdown + @is_shutdown = true + end + # nonblocking is intended for calling from within a Handler + # but we can't change the order of operations here, so lets thread + shutdown_proc = lambda do + @io_manager.shutdown(timeout) + @transport_semaphore.synchronize do + @server_transport.close # this will break the accept loop + end + end + if block + shutdown_proc.call + else + Thread.new &shutdown_proc + end + end + + private + + def start_io_manager + iom = IOManager.new(@processor, @server_transport, @transport_factory, @protocol_factory, @num_threads, @logger) + iom.spawn + iom + end + + class IOManager # :nodoc: + DEFAULT_BUFFER = 2**20 + + def initialize(processor, server_transport, transport_factory, protocol_factory, num, logger) + @processor = processor + @server_transport = server_transport + @transport_factory = transport_factory + @protocol_factory = protocol_factory + @num_threads = num + @logger = logger + @connections = [] + @buffers = Hash.new { |h,k| h[k] = '' } + @signal_queue = Queue.new + @signal_pipes = IO.pipe + @signal_pipes[1].sync = true + @worker_queue = Queue.new + @shutdown_queue = Queue.new + end + + def add_connection(socket) + signal [:connection, socket] + end + + def spawn + @iom_thread = Thread.new do + @logger.debug "Starting #{self}" + run + end + end + + def shutdown(timeout = 0) + @logger.debug "#{self} is shutting down workers" + @worker_queue.clear + @num_threads.times { @worker_queue.push [:shutdown] } + signal [:shutdown, timeout] + @shutdown_queue.pop + @signal_pipes[0].close + @signal_pipes[1].close + @logger.debug "#{self} is shutting down, goodbye" + end + + def ensure_closed + kill_worker_threads if @worker_threads + @iom_thread.kill + end + + private + + def run + spin_worker_threads + + loop do + rd, = select([@signal_pipes[0], *@connections]) + if rd.delete @signal_pipes[0] + break if read_signals == :shutdown + end + rd.each do |fd| + begin + if fd.handle.eof? + remove_connection fd + else + read_connection fd + end + rescue Errno::ECONNRESET + remove_connection fd + end + end + end + join_worker_threads(@shutdown_timeout) + ensure + @shutdown_queue.push :shutdown + end + + def read_connection(fd) + @buffers[fd] << fd.read(DEFAULT_BUFFER) + while(frame = slice_frame!(@buffers[fd])) + @logger.debug "#{self} is processing a frame" + @worker_queue.push [:frame, fd, frame] + end + end + + def spin_worker_threads + @logger.debug "#{self} is spinning up worker threads" + @worker_threads = [] + @num_threads.times do + @worker_threads << spin_thread + end + end + + def spin_thread + Worker.new(@processor, @transport_factory, @protocol_factory, @logger, @worker_queue).spawn + end + + def signal(msg) + @signal_queue << msg + @signal_pipes[1].write " " + end + + def read_signals + # clear the signal pipe + # note that since read_nonblock is broken in jruby, + # we can only read up to a set number of signals at once + sigstr = @signal_pipes[0].readpartial(1024) + # now read the signals + begin + sigstr.length.times do + signal, obj = @signal_queue.pop(true) + case signal + when :connection + @connections << obj + when :shutdown + @shutdown_timeout = obj + return :shutdown + end + end + rescue ThreadError + # out of signals + # note that in a perfect world this would never happen, since we're + # only reading the number of signals pushed on the pipe, but given the lack + # of locks, in theory we could clear the pipe/queue while a new signal is being + # placed on the pipe, at which point our next read_signals would hit this error + end + end + + def remove_connection(fd) + # don't explicitly close it, a thread may still be writing to it + @connections.delete fd + @buffers.delete fd + end + + def join_worker_threads(shutdown_timeout) + start = Time.now + @worker_threads.each do |t| + if shutdown_timeout > 0 + timeout = (start + shutdown_timeout) - Time.now + break if timeout <= 0 + t.join(timeout) + else + t.join + end + end + kill_worker_threads + end + + def kill_worker_threads + @worker_threads.each do |t| + t.kill if t.status + end + @worker_threads.clear + end + + def slice_frame!(buf) + if buf.length >= 4 + size = buf.unpack('N').first + if buf.length >= size + 4 + buf.slice!(0, size + 4) + else + nil + end + else + nil + end + end + + class Worker # :nodoc: + def initialize(processor, transport_factory, protocol_factory, logger, queue) + @processor = processor + @transport_factory = transport_factory + @protocol_factory = protocol_factory + @logger = logger + @queue = queue + end + + def spawn + Thread.new do + @logger.debug "#{self} is spawning" + run + end + end + + private + + def run + loop do + cmd, *args = @queue.pop + case cmd + when :shutdown + @logger.debug "#{self} is shutting down, goodbye" + break + when :frame + fd, frame = args + begin + otrans = @transport_factory.get_transport(fd) + oprot = @protocol_factory.get_protocol(otrans) + membuf = MemoryBufferTransport.new(frame) + itrans = @transport_factory.get_transport(membuf) + iprot = @protocol_factory.get_protocol(itrans) + @processor.process(iprot, oprot) + rescue => e + @logger.error "#{Thread.current.inspect} raised error: #{e.inspect}\n#{e.backtrace.join("\n")}" + end + end + end + end + end + end + end +end diff --git a/src/jaegertracing/thrift/lib/rb/lib/thrift/server/simple_server.rb b/src/jaegertracing/thrift/lib/rb/lib/thrift/server/simple_server.rb new file mode 100644 index 000000000..905fe9bd8 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/lib/thrift/server/simple_server.rb @@ -0,0 +1,47 @@ +# +# 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. +# + +module Thrift + class SimpleServer < BaseServer + def serve + begin + @server_transport.listen + loop do + client = @server_transport.accept + trans = @transport_factory.get_transport(client) + prot = @protocol_factory.get_protocol(trans) + begin + loop do + @processor.process(prot, prot) + end + rescue Thrift::TransportException, Thrift::ProtocolException + ensure + trans.close + end + end + ensure + @server_transport.close + end + end + + def to_s + "simple(#{super.to_s})" + end + end +end diff --git a/src/jaegertracing/thrift/lib/rb/lib/thrift/server/thin_http_server.rb b/src/jaegertracing/thrift/lib/rb/lib/thrift/server/thin_http_server.rb new file mode 100644 index 000000000..4a81c6d17 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/lib/thrift/server/thin_http_server.rb @@ -0,0 +1,91 @@ +# +# 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 'rack' +require 'thin' + +## +# Wraps the Thin web server to provide a Thrift server over HTTP. +module Thrift + class ThinHTTPServer < BaseServer + + ## + # Accepts a Thrift::Processor + # Options include: + # * :port + # * :ip + # * :path + # * :protocol_factory + def initialize(processor, options={}) + port = options[:port] || 80 + ip = options[:ip] || "0.0.0.0" + path = options[:path] || "/" + protocol_factory = options[:protocol_factory] || BinaryProtocolFactory.new + app = RackApplication.for(path, processor, protocol_factory) + @server = Thin::Server.new(ip, port, app) + end + + ## + # Starts the server + def serve + @server.start + end + + class RackApplication + + THRIFT_HEADER = "application/x-thrift" + + def self.for(path, processor, protocol_factory) + Rack::Builder.new do + use Rack::CommonLogger + use Rack::ShowExceptions + use Rack::Lint + map path do + run lambda { |env| + request = Rack::Request.new(env) + if RackApplication.valid_thrift_request?(request) + RackApplication.successful_request(request, processor, protocol_factory) + else + RackApplication.failed_request + end + } + end + end + end + + def self.successful_request(rack_request, processor, protocol_factory) + response = Rack::Response.new([], 200, {'Content-Type' => THRIFT_HEADER}) + transport = IOStreamTransport.new rack_request.body, response + protocol = protocol_factory.get_protocol transport + processor.process protocol, protocol + response + end + + def self.failed_request + Rack::Response.new(['Not Found'], 404, {'Content-Type' => THRIFT_HEADER}) + end + + def self.valid_thrift_request?(rack_request) + rack_request.post? && rack_request.env["CONTENT_TYPE"] == THRIFT_HEADER + end + + end + + end +end diff --git a/src/jaegertracing/thrift/lib/rb/lib/thrift/server/thread_pool_server.rb b/src/jaegertracing/thrift/lib/rb/lib/thrift/server/thread_pool_server.rb new file mode 100644 index 000000000..bb754ad2b --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/lib/thrift/server/thread_pool_server.rb @@ -0,0 +1,79 @@ +# +# 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 'thread' + +module Thrift + class ThreadPoolServer < BaseServer + def initialize(processor, server_transport, transport_factory=nil, protocol_factory=nil, num=20) + super(processor, server_transport, transport_factory, protocol_factory) + @thread_q = SizedQueue.new(num) + @exception_q = Queue.new + @running = false + end + + ## exceptions that happen in worker threads will be relayed here and + ## must be caught. 'retry' can be used to continue. (threads will + ## continue to run while the exception is being handled.) + def rescuable_serve + Thread.new { serve } unless @running + @running = true + raise @exception_q.pop + end + + ## exceptions that happen in worker threads simply cause that thread + ## to die and another to be spawned in its place. + def serve + @server_transport.listen + + begin + loop do + @thread_q.push(:token) + Thread.new do + begin + loop do + client = @server_transport.accept + trans = @transport_factory.get_transport(client) + prot = @protocol_factory.get_protocol(trans) + begin + loop do + @processor.process(prot, prot) + end + rescue Thrift::TransportException, Thrift::ProtocolException => e + ensure + trans.close + end + end + rescue => e + @exception_q.push(e) + ensure + @thread_q.pop # thread died! + end + end + end + ensure + @server_transport.close + end + end + + def to_s + "threadpool(#{super.to_s})" + end + end +end diff --git a/src/jaegertracing/thrift/lib/rb/lib/thrift/server/threaded_server.rb b/src/jaegertracing/thrift/lib/rb/lib/thrift/server/threaded_server.rb new file mode 100644 index 000000000..88ee1833f --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/lib/thrift/server/threaded_server.rb @@ -0,0 +1,51 @@ +# +# 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 'thread' + +module Thrift + class ThreadedServer < BaseServer + def serve + begin + @server_transport.listen + loop do + client = @server_transport.accept + trans = @transport_factory.get_transport(client) + prot = @protocol_factory.get_protocol(trans) + Thread.new(prot, trans) do |p, t| + begin + loop do + @processor.process(p, p) + end + rescue Thrift::TransportException, Thrift::ProtocolException + ensure + t.close + end + end + end + ensure + @server_transport.close + end + end + + def to_s + "threaded(#{super.to_s})" + end + end +end diff --git a/src/jaegertracing/thrift/lib/rb/lib/thrift/struct.rb b/src/jaegertracing/thrift/lib/rb/lib/thrift/struct.rb new file mode 100644 index 000000000..df9d6561c --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/lib/thrift/struct.rb @@ -0,0 +1,237 @@ +# +# 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 'set' + +module Thrift + module Struct + def initialize(d={}, &block) + # get a copy of the default values to work on, removing defaults in favor of arguments + fields_with_defaults = fields_with_default_values.dup + + # check if the defaults is empty, or if there are no parameters for this + # instantiation, and if so, don't bother overriding defaults. + unless fields_with_defaults.empty? || d.empty? + d.each_key do |name| + fields_with_defaults.delete(name.to_s) + end + end + + # assign all the user-specified arguments + unless d.empty? + d.each do |name, value| + unless name_to_id(name.to_s) + raise Exception, "Unknown key given to #{self.class}.new: #{name}" + end + Thrift.check_type(value, struct_fields[name_to_id(name.to_s)], name) if Thrift.type_checking + instance_variable_set("@#{name}", value) + end + end + + # assign all the default values + unless fields_with_defaults.empty? + fields_with_defaults.each do |name, default_value| + instance_variable_set("@#{name}", (default_value.dup rescue default_value)) + end + end + + yield self if block_given? + end + + def fields_with_default_values + fields_with_default_values = self.class.instance_variable_get(:@fields_with_default_values) + unless fields_with_default_values + fields_with_default_values = {} + struct_fields.each do |fid, field_def| + unless field_def[:default].nil? + fields_with_default_values[field_def[:name]] = field_def[:default] + end + end + self.class.instance_variable_set(:@fields_with_default_values, fields_with_default_values) + end + fields_with_default_values + end + + def inspect(skip_optional_nulls = true) + fields = [] + each_field do |fid, field_info| + name = field_info[:name] + value = instance_variable_get("@#{name}") + unless skip_optional_nulls && field_info[:optional] && value.nil? + fields << "#{name}:#{inspect_field(value, field_info)}" + end + end + "<#{self.class} #{fields.join(", ")}>" + end + + def read(iprot) + iprot.read_struct_begin + loop do + fname, ftype, fid = iprot.read_field_begin + break if (ftype == Types::STOP) + handle_message(iprot, fid, ftype) + iprot.read_field_end + end + iprot.read_struct_end + validate + end + + def write(oprot) + validate + oprot.write_struct_begin(self.class.name) + each_field do |fid, field_info| + name = field_info[:name] + type = field_info[:type] + value = instance_variable_get("@#{name}") + unless value.nil? + if is_container? type + oprot.write_field_begin(name, type, fid) + write_container(oprot, value, field_info) + oprot.write_field_end + else + oprot.write_field(field_info, fid, value) + end + end + end + oprot.write_field_stop + oprot.write_struct_end + end + + def ==(other) + return false if other.nil? + each_field do |fid, field_info| + name = field_info[:name] + return false unless other.respond_to?(name) && self.send(name) == other.send(name) + end + true + end + + def eql?(other) + self.class == other.class && self == other + end + + # This implementation of hash() is inspired by Apache's Java HashCodeBuilder class. + def hash + total = 17 + each_field do |fid, field_info| + name = field_info[:name] + value = self.send(name) + total = (total * 37 + value.hash) & 0xffffffff + end + total + end + + def differences(other) + diffs = [] + unless other.is_a?(self.class) + diffs << "Different class!" + else + each_field do |fid, field_info| + name = field_info[:name] + diffs << "#{name} differs!" unless self.instance_variable_get("@#{name}") == other.instance_variable_get("@#{name}") + end + end + diffs + end + + def self.field_accessor(klass, field_info) + field_name_sym = field_info[:name].to_sym + klass.send :attr_reader, field_name_sym + klass.send :define_method, "#{field_info[:name]}=" do |value| + Thrift.check_type(value, field_info, field_info[:name]) if Thrift.type_checking + instance_variable_set("@#{field_name_sym}", value) + end + end + + def self.generate_accessors(klass) + klass::FIELDS.values.each do |field_info| + field_accessor(klass, field_info) + qmark_isset_method(klass, field_info) + end + end + + def self.qmark_isset_method(klass, field_info) + klass.send :define_method, "#{field_info[:name]}?" do + !self.send(field_info[:name].to_sym).nil? + end + end + + def <=>(other) + if self.class == other.class + each_field do |fid, field_info| + v1 = self.send(field_info[:name]) + v1_set = !v1.nil? + v2 = other.send(field_info[:name]) + v2_set = !v2.nil? + if v1_set && !v2_set + return -1 + elsif !v1_set && v2_set + return 1 + elsif v1_set && v2_set + cmp = v1 <=> v2 + if cmp != 0 + return cmp + end + end + end + 0 + else + self.class <=> other.class + end + end + + protected + + def self.append_features(mod) + if mod.ancestors.include? ::Exception + mod.send :class_variable_set, :'@@__thrift_struct_real_initialize', mod.instance_method(:initialize) + super + # set up our custom initializer so `raise Xception, 'message'` works + mod.send :define_method, :struct_initialize, mod.instance_method(:initialize) + mod.send :define_method, :initialize, mod.instance_method(:exception_initialize) + else + super + end + end + + def exception_initialize(*args, &block) + if args.size == 1 and args.first.is_a? Hash + # looks like it's a regular Struct initialize + method(:struct_initialize).call(args.first) + else + # call the Struct initializer first with no args + # this will set our field default values + method(:struct_initialize).call() + # now give it to the exception + self.class.send(:class_variable_get, :'@@__thrift_struct_real_initialize').bind(self).call(*args, &block) if args.size > 0 + # self.class.instance_method(:initialize).bind(self).call(*args, &block) + end + end + + def handle_message(iprot, fid, ftype) + field = struct_fields[fid] + if field and field[:type] == ftype + value = read_field(iprot, field) + instance_variable_set("@#{field[:name]}", value) + else + iprot.skip(ftype) + end + end + end +end diff --git a/src/jaegertracing/thrift/lib/rb/lib/thrift/struct_union.rb b/src/jaegertracing/thrift/lib/rb/lib/thrift/struct_union.rb new file mode 100644 index 000000000..e21b39ebf --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/lib/thrift/struct_union.rb @@ -0,0 +1,192 @@ +# +# 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 'set' + +module Thrift + module Struct_Union + def name_to_id(name) + names_to_ids = self.class.instance_variable_get(:@names_to_ids) + unless names_to_ids + names_to_ids = {} + struct_fields.each do |fid, field_def| + names_to_ids[field_def[:name]] = fid + end + self.class.instance_variable_set(:@names_to_ids, names_to_ids) + end + names_to_ids[name] + end + + def sorted_field_ids + sorted_field_ids = self.class.instance_variable_get(:@sorted_field_ids) + unless sorted_field_ids + sorted_field_ids = struct_fields.keys.sort + self.class.instance_variable_set(:@sorted_field_ids, sorted_field_ids) + end + sorted_field_ids + end + + def each_field + sorted_field_ids.each do |fid| + data = struct_fields[fid] + yield fid, data + end + end + + def read_field(iprot, field = {}) + case field[:type] + when Types::STRUCT + value = field[:class].new + value.read(iprot) + when Types::MAP + key_type, val_type, size = iprot.read_map_begin + # Skip the map contents if the declared key or value types don't match the expected ones. + if (size != 0 && (key_type != field[:key][:type] || val_type != field[:value][:type])) + size.times do + iprot.skip(key_type) + iprot.skip(val_type) + end + value = nil + else + value = {} + size.times do + k = read_field(iprot, field_info(field[:key])) + v = read_field(iprot, field_info(field[:value])) + value[k] = v + end + end + iprot.read_map_end + when Types::LIST + e_type, size = iprot.read_list_begin + # Skip the list contents if the declared element type doesn't match the expected one. + if (e_type != field[:element][:type]) + size.times do + iprot.skip(e_type) + end + value = nil + else + value = Array.new(size) do |n| + read_field(iprot, field_info(field[:element])) + end + end + iprot.read_list_end + when Types::SET + e_type, size = iprot.read_set_begin + # Skip the set contents if the declared element type doesn't match the expected one. + if (e_type != field[:element][:type]) + size.times do + iprot.skip(e_type) + end + else + value = Set.new + size.times do + element = read_field(iprot, field_info(field[:element])) + value << element + end + end + iprot.read_set_end + else + value = iprot.read_type(field) + end + value + end + + def write_data(oprot, value, field) + if is_container? field[:type] + write_container(oprot, value, field) + else + oprot.write_type(field, value) + end + end + + def write_container(oprot, value, field = {}) + case field[:type] + when Types::MAP + oprot.write_map_begin(field[:key][:type], field[:value][:type], value.size) + value.each do |k, v| + write_data(oprot, k, field[:key]) + write_data(oprot, v, field[:value]) + end + oprot.write_map_end + when Types::LIST + oprot.write_list_begin(field[:element][:type], value.size) + value.each do |elem| + write_data(oprot, elem, field[:element]) + end + oprot.write_list_end + when Types::SET + oprot.write_set_begin(field[:element][:type], value.size) + value.each do |v,| # the , is to preserve compatibility with the old Hash-style sets + write_data(oprot, v, field[:element]) + end + oprot.write_set_end + else + raise "Not a container type: #{field[:type]}" + end + end + + CONTAINER_TYPES = [] + CONTAINER_TYPES[Types::LIST] = true + CONTAINER_TYPES[Types::MAP] = true + CONTAINER_TYPES[Types::SET] = true + def is_container?(type) + CONTAINER_TYPES[type] + end + + def field_info(field) + { :type => field[:type], + :class => field[:class], + :key => field[:key], + :value => field[:value], + :element => field[:element] } + end + + def inspect_field(value, field_info) + if enum_class = field_info[:enum_class] + "#{enum_class.const_get(:VALUE_MAP)[value]} (#{value})" + elsif value.is_a? Hash + if field_info[:type] == Types::MAP + map_buf = [] + value.each do |k, v| + map_buf << inspect_field(k, field_info[:key]) + ": " + inspect_field(v, field_info[:value]) + end + "{" + map_buf.join(", ") + "}" + else + # old-style set + inspect_collection(value.keys, field_info) + end + elsif value.is_a? Array + inspect_collection(value, field_info) + elsif value.is_a? Set + inspect_collection(value, field_info) + elsif value.is_a?(String) && field_info[:binary] + value.unpack("H*").first + else + value.inspect + end + end + + def inspect_collection(collection, field_info) + buf = [] + collection.each do |k| + buf << inspect_field(k, field_info[:element]) + end + "[" + buf.join(", ") + "]" + end + end +end
\ No newline at end of file diff --git a/src/jaegertracing/thrift/lib/rb/lib/thrift/thrift_native.rb b/src/jaegertracing/thrift/lib/rb/lib/thrift/thrift_native.rb new file mode 100644 index 000000000..4d8df61ff --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/lib/thrift/thrift_native.rb @@ -0,0 +1,24 @@ +# +# 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. +# + +begin + require "thrift_native" +rescue LoadError + puts "Unable to load thrift_native extension. Defaulting to pure Ruby libraries." +end
\ No newline at end of file diff --git a/src/jaegertracing/thrift/lib/rb/lib/thrift/transport/base_server_transport.rb b/src/jaegertracing/thrift/lib/rb/lib/thrift/transport/base_server_transport.rb new file mode 100644 index 000000000..0105463f8 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/lib/thrift/transport/base_server_transport.rb @@ -0,0 +1,37 @@ +# 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. +# + +module Thrift + class BaseServerTransport + def listen + raise NotImplementedError + end + + def accept + raise NotImplementedError + end + + def close; nil; end + + def closed? + raise NotImplementedError + end + end +end diff --git a/src/jaegertracing/thrift/lib/rb/lib/thrift/transport/base_transport.rb b/src/jaegertracing/thrift/lib/rb/lib/thrift/transport/base_transport.rb new file mode 100644 index 000000000..97e59352a --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/lib/thrift/transport/base_transport.rb @@ -0,0 +1,117 @@ +# 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. +# + +module Thrift + class TransportException < Exception + UNKNOWN = 0 + NOT_OPEN = 1 + ALREADY_OPEN = 2 + TIMED_OUT = 3 + END_OF_FILE = 4 + + attr_reader :type + + def initialize(type=UNKNOWN, message=nil) + super(message) + @type = type + end + end + + module TransportUtils + # Deprecated: Use Thrift::Bytes instead + def self.get_string_byte(string, index) + Bytes.get_string_byte(string, index) + end + + # Deprecated: Use Thrift::Bytes instead + def self.set_string_byte(string, index, byte) + Bytes.set_string_byte(string, index, byte) + end + end + + class BaseTransport + def open?; end + + def open; end + + def close; end + + # Reads a number of bytes from the transports. In Ruby 1.9+, the String returned will have a BINARY (aka ASCII8BIT) encoding. + # + # sz - The number of bytes to read from the transport. + # + # Returns a String acting as a byte buffer. + def read(sz) + raise NotImplementedError + end + + # Returns an unsigned byte as a Fixnum in the range (0..255). + def read_byte + buf = read_all(1) + return Bytes.get_string_byte(buf, 0) + end + + # Reads size bytes and copies them into buffer[0..size]. + def read_into_buffer(buffer, size) + tmp = read_all(size) + i = 0 + tmp.each_byte do |byte| + Bytes.set_string_byte(buffer, i, byte) + i += 1 + end + i + end + + def read_all(size) + return Bytes.empty_byte_buffer if size <= 0 + buf = read(size) + while (buf.length < size) + chunk = read(size - buf.length) + buf << chunk + end + + buf + end + + # Writes the byte buffer to the transport. In Ruby 1.9+, the buffer will be forced into BINARY encoding. + # + # buf - A String acting as a byte buffer. + # + # Returns nothing. + def write(buf); end + alias_method :<<, :write + + def flush; end + + def to_s + "base" + end + end + + class BaseTransportFactory + def get_transport(trans) + return trans + end + + def to_s + "base" + end + end +end diff --git a/src/jaegertracing/thrift/lib/rb/lib/thrift/transport/buffered_transport.rb b/src/jaegertracing/thrift/lib/rb/lib/thrift/transport/buffered_transport.rb new file mode 100644 index 000000000..4fe9c41a5 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/lib/thrift/transport/buffered_transport.rb @@ -0,0 +1,122 @@ +# 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. +# + +module Thrift + class BufferedTransport < BaseTransport + DEFAULT_BUFFER = 4096 + + def initialize(transport) + @transport = transport + @wbuf = Bytes.empty_byte_buffer + @rbuf = Bytes.empty_byte_buffer + @index = 0 + end + + def open? + return @transport.open? + end + + def open + @transport.open + end + + def close + flush + @transport.close + end + + def read(sz) + @index += sz + ret = @rbuf.slice(@index - sz, sz) || Bytes.empty_byte_buffer + + if ret.length == 0 + @rbuf = @transport.read([sz, DEFAULT_BUFFER].max) + @index = sz + ret = @rbuf.slice(0, sz) || Bytes.empty_byte_buffer + end + + ret + end + + def read_byte + # If the read buffer is exhausted, try to read up to DEFAULT_BUFFER more bytes into it. + if @index >= @rbuf.size + @rbuf = @transport.read(DEFAULT_BUFFER) + @index = 0 + end + + # The read buffer has some data now, read a single byte. Using get_string_byte() avoids + # allocating a temp string of size 1 unnecessarily. + @index += 1 + return Bytes.get_string_byte(@rbuf, @index - 1) + end + + # Reads a number of bytes from the transport into the buffer passed. + # + # buffer - The String (byte buffer) to write data to; this is assumed to have a BINARY encoding. + # size - The number of bytes to read from the transport and write to the buffer. + # + # Returns the number of bytes read. + def read_into_buffer(buffer, size) + i = 0 + while i < size + # If the read buffer is exhausted, try to read up to DEFAULT_BUFFER more bytes into it. + if @index >= @rbuf.size + @rbuf = @transport.read(DEFAULT_BUFFER) + @index = 0 + end + + # The read buffer has some data now, so copy bytes over to the output buffer. + byte = Bytes.get_string_byte(@rbuf, @index) + Bytes.set_string_byte(buffer, i, byte) + @index += 1 + i += 1 + end + i + end + + def write(buf) + @wbuf << Bytes.force_binary_encoding(buf) + end + + def flush + unless @wbuf.empty? + @transport.write(@wbuf) + @wbuf = Bytes.empty_byte_buffer + end + + @transport.flush + end + + def to_s + "buffered(#{@transport.to_s})" + end + end + + class BufferedTransportFactory < BaseTransportFactory + def get_transport(transport) + return BufferedTransport.new(transport) + end + + def to_s + "buffered" + end + end +end diff --git a/src/jaegertracing/thrift/lib/rb/lib/thrift/transport/framed_transport.rb b/src/jaegertracing/thrift/lib/rb/lib/thrift/transport/framed_transport.rb new file mode 100644 index 000000000..953177821 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/lib/thrift/transport/framed_transport.rb @@ -0,0 +1,125 @@ +# 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. +# + +module Thrift + class FramedTransport < BaseTransport + def initialize(transport, read=true, write=true) + @transport = transport + @rbuf = Bytes.empty_byte_buffer + @wbuf = Bytes.empty_byte_buffer + @read = read + @write = write + @index = 0 + end + + def open? + @transport.open? + end + + def open + @transport.open + end + + def close + @transport.close + end + + def read(sz) + return @transport.read(sz) unless @read + + return Bytes.empty_byte_buffer if sz <= 0 + + read_frame if @index >= @rbuf.length + + @index += sz + @rbuf.slice(@index - sz, sz) || Bytes.empty_byte_buffer + end + + def read_byte + return @transport.read_byte() unless @read + + read_frame if @index >= @rbuf.length + + # The read buffer has some data now, read a single byte. Using get_string_byte() avoids + # allocating a temp string of size 1 unnecessarily. + @index += 1 + return Bytes.get_string_byte(@rbuf, @index - 1) + end + + def read_into_buffer(buffer, size) + i = 0 + while i < size + read_frame if @index >= @rbuf.length + + # The read buffer has some data now, so copy bytes over to the output buffer. + byte = Bytes.get_string_byte(@rbuf, @index) + Bytes.set_string_byte(buffer, i, byte) + @index += 1 + i += 1 + end + i + end + + def write(buf, sz=nil) + return @transport.write(buf) unless @write + + buf = Bytes.force_binary_encoding(buf) + @wbuf << (sz ? buf[0...sz] : buf) + end + + # + # Writes the output buffer to the stream in the format of a 4-byte length + # followed by the actual data. + # + def flush + return @transport.flush unless @write + + out = [@wbuf.length].pack('N') + # Array#pack should return a BINARY encoded String, so it shouldn't be necessary to force encoding + out << @wbuf + @transport.write(out) + @transport.flush + @wbuf = Bytes.empty_byte_buffer + end + + def to_s + "framed(#{@transport.to_s})" + end + + private + + def read_frame + sz = @transport.read_all(4).unpack('N').first + + @index = 0 + @rbuf = @transport.read_all(sz) + end + end + + class FramedTransportFactory < BaseTransportFactory + def get_transport(transport) + return FramedTransport.new(transport) + end + + def to_s + "framed" + end + end +end diff --git a/src/jaegertracing/thrift/lib/rb/lib/thrift/transport/http_client_transport.rb b/src/jaegertracing/thrift/lib/rb/lib/thrift/transport/http_client_transport.rb new file mode 100644 index 000000000..5c1dd5c8a --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/lib/thrift/transport/http_client_transport.rb @@ -0,0 +1,61 @@ +# 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 'net/http' +require 'net/https' +require 'openssl' +require 'uri' +require 'stringio' + +module Thrift + class HTTPClientTransport < BaseTransport + + def initialize(url, opts = {}) + @url = URI url + @headers = {'Content-Type' => 'application/x-thrift'} + @outbuf = Bytes.empty_byte_buffer + @ssl_verify_mode = opts.fetch(:ssl_verify_mode, OpenSSL::SSL::VERIFY_PEER) + end + + def open?; true end + def read(sz); @inbuf.read sz end + def write(buf); @outbuf << Bytes.force_binary_encoding(buf) end + + def add_headers(headers) + @headers = @headers.merge(headers) + end + + def flush + http = Net::HTTP.new @url.host, @url.port + http.use_ssl = @url.scheme == 'https' + http.verify_mode = @ssl_verify_mode if @url.scheme == 'https' + resp = http.post(@url.request_uri, @outbuf, @headers) + data = resp.body + data = Bytes.force_binary_encoding(data) + @inbuf = StringIO.new data + ensure + @outbuf = Bytes.empty_byte_buffer + end + + def to_s + "@{self.url}" + end + end +end diff --git a/src/jaegertracing/thrift/lib/rb/lib/thrift/transport/io_stream_transport.rb b/src/jaegertracing/thrift/lib/rb/lib/thrift/transport/io_stream_transport.rb new file mode 100644 index 000000000..ccec68f25 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/lib/thrift/transport/io_stream_transport.rb @@ -0,0 +1,42 @@ +# 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. +# + +# Very very simple implementation of wrapping two objects, one with a #read +# method and one with a #write method, into a transport for thrift. +# +# Assumes both objects are open, remain open, don't require flushing, etc. +# +module Thrift + class IOStreamTransport < BaseTransport + def initialize(input, output) + @input = input + @output = output + end + + def open?; not @input.closed? or not @output.closed? end + def read(sz); @input.read(sz) end + def write(buf); @output.write(Bytes.force_binary_encoding(buf)) end + def close; @input.close; @output.close end + def to_io; @input end # we're assuming this is used in a IO.select for reading + def to_s + "iostream(input=#{@input.to_s},output=#{@output.to_s})" + end + end +end diff --git a/src/jaegertracing/thrift/lib/rb/lib/thrift/transport/memory_buffer_transport.rb b/src/jaegertracing/thrift/lib/rb/lib/thrift/transport/memory_buffer_transport.rb new file mode 100644 index 000000000..469ea7396 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/lib/thrift/transport/memory_buffer_transport.rb @@ -0,0 +1,129 @@ +# 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. +# + +module Thrift + class MemoryBufferTransport < BaseTransport + GARBAGE_BUFFER_SIZE = 4*(2**10) # 4kB + + # If you pass a string to this, you should #dup that string + # unless you want it to be modified by #read and #write + #-- + # this behavior is no longer required. If you wish to change it + # go ahead, just make sure the specs pass + def initialize(buffer = nil) + @buf = buffer ? Bytes.force_binary_encoding(buffer) : Bytes.empty_byte_buffer + @index = 0 + end + + def open? + return true + end + + def open + end + + def close + end + + def peek + @index < @buf.size + end + + # this method does not use the passed object directly but copies it + def reset_buffer(new_buf = '') + @buf.replace Bytes.force_binary_encoding(new_buf) + @index = 0 + end + + def available + @buf.length - @index + end + + def read(len) + data = @buf.slice(@index, len) + @index += len + @index = @buf.size if @index > @buf.size + if @index >= GARBAGE_BUFFER_SIZE + @buf = @buf.slice(@index..-1) + @index = 0 + end + if data.size < len + raise EOFError, "Not enough bytes remain in buffer" + end + data + end + + def read_byte + raise EOFError.new("Not enough bytes remain in buffer") if @index >= @buf.size + val = Bytes.get_string_byte(@buf, @index) + @index += 1 + if @index >= GARBAGE_BUFFER_SIZE + @buf = @buf.slice(@index..-1) + @index = 0 + end + val + end + + def read_into_buffer(buffer, size) + i = 0 + while i < size + raise EOFError.new("Not enough bytes remain in buffer") if @index >= @buf.size + + # The read buffer has some data now, so copy bytes over to the output buffer. + byte = Bytes.get_string_byte(@buf, @index) + Bytes.set_string_byte(buffer, i, byte) + @index += 1 + i += 1 + end + if @index >= GARBAGE_BUFFER_SIZE + @buf = @buf.slice(@index..-1) + @index = 0 + end + i + end + + def write(wbuf) + @buf << Bytes.force_binary_encoding(wbuf) + end + + def flush + end + + def inspect_buffer + out = [] + for idx in 0...(@buf.size) + # if idx != 0 + # out << " " + # end + + if idx == @index + out << ">" + end + + out << @buf[idx].ord.to_s(16) + end + out.join(" ") + end + + def to_s + "memory" + end + end +end diff --git a/src/jaegertracing/thrift/lib/rb/lib/thrift/transport/server_socket.rb b/src/jaegertracing/thrift/lib/rb/lib/thrift/transport/server_socket.rb new file mode 100644 index 000000000..50002324e --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/lib/thrift/transport/server_socket.rb @@ -0,0 +1,68 @@ +# 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 'socket' + +module Thrift + class ServerSocket < BaseServerTransport + # call-seq: initialize(host = nil, port) + def initialize(host_or_port, port = nil) + if port + @host = host_or_port + @port = port + else + @host = nil + @port = host_or_port + end + @handle = nil + end + + attr_reader :handle + + def listen + @handle = TCPServer.new(@host, @port) + end + + def accept + unless @handle.nil? + sock = @handle.accept + trans = Socket.new + trans.handle = sock + trans + end + end + + def close + @handle.close unless @handle.nil? or @handle.closed? + @handle = nil + end + + def closed? + @handle.nil? or @handle.closed? + end + + alias to_io handle + + def to_s + "socket(#{@host}:#{@port})" + end + + end +end diff --git a/src/jaegertracing/thrift/lib/rb/lib/thrift/transport/socket.rb b/src/jaegertracing/thrift/lib/rb/lib/thrift/transport/socket.rb new file mode 100644 index 000000000..f5e6f3b85 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/lib/thrift/transport/socket.rb @@ -0,0 +1,143 @@ +# 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 'socket' + +module Thrift + class Socket < BaseTransport + def initialize(host='localhost', port=9090, timeout=nil) + @host = host + @port = port + @timeout = timeout + @desc = "#{host}:#{port}" + @handle = nil + end + + attr_accessor :handle, :timeout + + def open + for addrinfo in ::Socket::getaddrinfo(@host, @port, nil, ::Socket::SOCK_STREAM) do + begin + socket = ::Socket.new(addrinfo[4], ::Socket::SOCK_STREAM, 0) + socket.setsockopt(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, 1) + sockaddr = ::Socket.sockaddr_in(addrinfo[1], addrinfo[3]) + begin + socket.connect_nonblock(sockaddr) + rescue Errno::EINPROGRESS + unless IO.select(nil, [ socket ], nil, @timeout) + next + end + begin + socket.connect_nonblock(sockaddr) + rescue Errno::EISCONN + end + end + return @handle = socket + rescue StandardError => e + next + end + end + raise TransportException.new(TransportException::NOT_OPEN, "Could not connect to #{@desc}: #{e}") + end + + def open? + !@handle.nil? and !@handle.closed? + end + + def write(str) + raise IOError, "closed stream" unless open? + str = Bytes.force_binary_encoding(str) + begin + if @timeout.nil? or @timeout == 0 + @handle.write(str) + else + len = 0 + start = Time.now + while Time.now - start < @timeout + rd, wr, = IO.select(nil, [@handle], nil, @timeout) + if wr and not wr.empty? + len += @handle.write_nonblock(str[len..-1]) + break if len >= str.length + end + end + if len < str.length + raise TransportException.new(TransportException::TIMED_OUT, "Socket: Timed out writing #{str.length} bytes to #{@desc}") + else + len + end + end + rescue TransportException => e + # pass this on + raise e + rescue StandardError => e + @handle.close + @handle = nil + raise TransportException.new(TransportException::NOT_OPEN, e.message) + end + end + + def read(sz) + raise IOError, "closed stream" unless open? + + begin + if @timeout.nil? or @timeout == 0 + data = @handle.readpartial(sz) + else + # it's possible to interrupt select for something other than the timeout + # so we need to ensure we've waited long enough, but not too long + start = Time.now + timespent = 0 + rd = loop do + rd, = IO.select([@handle], nil, nil, @timeout - timespent) + timespent = Time.now - start + break rd if (rd and not rd.empty?) or timespent >= @timeout + end + if rd.nil? or rd.empty? + raise TransportException.new(TransportException::TIMED_OUT, "Socket: Timed out reading #{sz} bytes from #{@desc}") + else + data = @handle.readpartial(sz) + end + end + rescue TransportException => e + # don't let this get caught by the StandardError handler + raise e + rescue StandardError => e + @handle.close unless @handle.closed? + @handle = nil + raise TransportException.new(TransportException::NOT_OPEN, e.message) + end + if (data.nil? or data.length == 0) + raise TransportException.new(TransportException::UNKNOWN, "Socket: Could not read #{sz} bytes from #{@desc}") + end + data + end + + def close + @handle.close unless @handle.nil? or @handle.closed? + @handle = nil + end + + alias to_io handle + + def to_s + "socket(#{@host}:#{@port})" + end + end +end diff --git a/src/jaegertracing/thrift/lib/rb/lib/thrift/transport/ssl_server_socket.rb b/src/jaegertracing/thrift/lib/rb/lib/thrift/transport/ssl_server_socket.rb new file mode 100644 index 000000000..3abd5ec3d --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/lib/thrift/transport/ssl_server_socket.rb @@ -0,0 +1,41 @@ +# 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 'socket' + +module Thrift + class SSLServerSocket < ServerSocket + def initialize(host_or_port, port = nil, ssl_context = nil) + super(host_or_port, port) + @ssl_context = ssl_context + end + + attr_accessor :ssl_context + + def listen + socket = TCPServer.new(@host, @port) + @handle = OpenSSL::SSL::SSLServer.new(socket, @ssl_context) + end + + def to_s + "ssl(#{super.to_s})" + end + end +end diff --git a/src/jaegertracing/thrift/lib/rb/lib/thrift/transport/ssl_socket.rb b/src/jaegertracing/thrift/lib/rb/lib/thrift/transport/ssl_socket.rb new file mode 100644 index 000000000..7ab96ab45 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/lib/thrift/transport/ssl_socket.rb @@ -0,0 +1,51 @@ +# 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. + +module Thrift + class SSLSocket < Socket + def initialize(host='localhost', port=9090, timeout=nil, ssl_context=nil) + super(host, port, timeout) + @ssl_context = ssl_context + end + + attr_accessor :ssl_context + + def open + socket = super + @handle = OpenSSL::SSL::SSLSocket.new(socket, @ssl_context) + begin + @handle.connect_nonblock + @handle.post_connection_check(@host) + @handle + rescue IO::WaitReadable + IO.select([ @handle ], nil, nil, @timeout) + retry + rescue IO::WaitWritable + IO.select(nil, [ @handle ], nil, @timeout) + retry + rescue StandardError => e + raise TransportException.new(TransportException::NOT_OPEN, "Could not connect to #{@desc}: #{e}") + end + end + + def to_s + "ssl(#{super.to_s})" + end + end +end diff --git a/src/jaegertracing/thrift/lib/rb/lib/thrift/transport/unix_server_socket.rb b/src/jaegertracing/thrift/lib/rb/lib/thrift/transport/unix_server_socket.rb new file mode 100644 index 000000000..057d122e7 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/lib/thrift/transport/unix_server_socket.rb @@ -0,0 +1,64 @@ +# 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 'socket' + +module Thrift + class UNIXServerSocket < BaseServerTransport + def initialize(path) + @path = path + @handle = nil + end + + attr_accessor :handle + + def listen + @handle = ::UNIXServer.new(@path) + end + + def accept + unless @handle.nil? + sock = @handle.accept + trans = UNIXSocket.new(nil) + trans.handle = sock + trans + end + end + + def close + if @handle + @handle.close unless @handle.closed? + @handle = nil + # UNIXServer doesn't delete the socket file, so we have to do it ourselves + File.delete(@path) + end + end + + def closed? + @handle.nil? or @handle.closed? + end + + alias to_io handle + + def to_s + "domain(#{@path})" + end + end +end diff --git a/src/jaegertracing/thrift/lib/rb/lib/thrift/transport/unix_socket.rb b/src/jaegertracing/thrift/lib/rb/lib/thrift/transport/unix_socket.rb new file mode 100644 index 000000000..5dffd59f2 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/lib/thrift/transport/unix_socket.rb @@ -0,0 +1,44 @@ +# 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 'socket' + +module Thrift + class UNIXSocket < Socket + def initialize(path, timeout=nil) + @path = path + @timeout = timeout + @desc = @path # for read()'s error + @handle = nil + end + + def open + begin + @handle = ::UNIXSocket.new(@path) + rescue StandardError + raise TransportException.new(TransportException::NOT_OPEN, "Could not open UNIX socket at #{@path}") + end + end + + def to_s + "domain(#{@path})" + end + end +end diff --git a/src/jaegertracing/thrift/lib/rb/lib/thrift/types.rb b/src/jaegertracing/thrift/lib/rb/lib/thrift/types.rb new file mode 100644 index 000000000..cac52691a --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/lib/thrift/types.rb @@ -0,0 +1,101 @@ +# +# 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 'set' + +module Thrift + module Types + STOP = 0 + VOID = 1 + BOOL = 2 + BYTE = 3 + DOUBLE = 4 + I16 = 6 + I32 = 8 + I64 = 10 + STRING = 11 + STRUCT = 12 + MAP = 13 + SET = 14 + LIST = 15 + end + + class << self + attr_accessor :type_checking + end + + class TypeError < Exception + end + + def self.check_type(value, field, name, skip_nil=true) + return if value.nil? and skip_nil + klasses = case field[:type] + when Types::VOID + NilClass + when Types::BOOL + [TrueClass, FalseClass] + when Types::BYTE, Types::I16, Types::I32, Types::I64 + Integer + when Types::DOUBLE + Float + when Types::STRING + String + when Types::STRUCT + [Struct, Union] + when Types::MAP + Hash + when Types::SET + Set + when Types::LIST + Array + end + valid = klasses && [*klasses].any? { |klass| klass === value } + raise TypeError, "Expected #{type_name(field[:type])}, received #{value.class} for field #{name}" unless valid + # check elements now + case field[:type] + when Types::MAP + value.each_pair do |k,v| + check_type(k, field[:key], "#{name}.key", false) + check_type(v, field[:value], "#{name}.value", false) + end + when Types::SET, Types::LIST + value.each do |el| + check_type(el, field[:element], "#{name}.element", false) + end + when Types::STRUCT + raise TypeError, "Expected #{field[:class]}, received #{value.class} for field #{name}" unless field[:class] == value.class + end + end + + def self.type_name(type) + Types.constants.each do |const| + return "Types::#{const}" if Types.const_get(const) == type + end + nil + end + + module MessageTypes + CALL = 1 + REPLY = 2 + EXCEPTION = 3 + ONEWAY = 4 + end +end + +Thrift.type_checking = false if Thrift.type_checking.nil? diff --git a/src/jaegertracing/thrift/lib/rb/lib/thrift/union.rb b/src/jaegertracing/thrift/lib/rb/lib/thrift/union.rb new file mode 100644 index 000000000..490c55c40 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/lib/thrift/union.rb @@ -0,0 +1,176 @@ +# +# 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. +# + +module Thrift + class Union + def initialize(name=nil, value=nil) + if name + if name.is_a? Hash + if name.size > 1 + raise "#{self.class} cannot be instantiated with more than one field!" + end + + name, value = name.keys.first, name.values.first + end + + if Thrift.type_checking + raise Exception, "#{self.class} does not contain a field named #{name}!" unless name_to_id(name.to_s) + end + + if value.nil? + raise Exception, "Union #{self.class} cannot be instantiated with setfield and nil value!" + end + + Thrift.check_type(value, struct_fields[name_to_id(name.to_s)], name) if Thrift.type_checking + elsif !value.nil? + raise Exception, "Value provided, but no name!" + end + @setfield = name + @value = value + end + + def inspect + if get_set_field + "<#{self.class} #{@setfield}: #{inspect_field(@value, struct_fields[name_to_id(@setfield.to_s)])}>" + else + "<#{self.class} >" + end + end + + def read(iprot) + iprot.read_struct_begin + fname, ftype, fid = iprot.read_field_begin + handle_message(iprot, fid, ftype) + iprot.read_field_end + + fname, ftype, fid = iprot.read_field_begin + raise "Too many fields for union" unless (ftype == Types::STOP) + + iprot.read_struct_end + validate + end + + def write(oprot) + validate + oprot.write_struct_begin(self.class.name) + + fid = self.name_to_id(@setfield.to_s) + + field_info = struct_fields[fid] + type = field_info[:type] + if is_container? type + oprot.write_field_begin(@setfield, type, fid) + write_container(oprot, @value, field_info) + oprot.write_field_end + else + oprot.write_field(@setfield, type, fid, @value) + end + + oprot.write_field_stop + oprot.write_struct_end + end + + def ==(other) + other.equal?(self) || other.instance_of?(self.class) && @setfield == other.get_set_field && @value == other.get_value + end + alias_method :eql?, :== + + def hash + [self.class.name, @setfield, @value].hash + end + + def self.field_accessor(klass, field_info) + klass.send :define_method, field_info[:name] do + if field_info[:name].to_sym == @setfield + @value + else + raise RuntimeError, "#{field_info[:name]} is not union's set field." + end + end + + klass.send :define_method, "#{field_info[:name]}=" do |value| + Thrift.check_type(value, field_info, field_info[:name]) if Thrift.type_checking + @setfield = field_info[:name].to_sym + @value = value + end + end + + def self.qmark_isset_method(klass, field_info) + klass.send :define_method, "#{field_info[:name]}?" do + get_set_field == field_info[:name].to_sym && !get_value.nil? + end + end + + def self.generate_accessors(klass) + klass::FIELDS.values.each do |field_info| + field_accessor(klass, field_info) + qmark_isset_method(klass, field_info) + end + end + + # get the symbol that indicates what the currently set field type is. + def get_set_field + @setfield + end + + # get the current value of this union, regardless of what the set field is. + # generally, you should only use this method when you don't know in advance + # what field to expect. + def get_value + @value + end + + def <=>(other) + if self.class == other.class + if get_set_field == other.get_set_field + if get_set_field.nil? + 0 + else + get_value <=> other.get_value + end + else + if get_set_field && other.get_set_field.nil? + -1 + elsif get_set_field.nil? && other.get_set_field + 1 + elsif get_set_field.nil? && other.get_set_field.nil? + 0 + else + name_to_id(get_set_field.to_s) <=> name_to_id(other.get_set_field.to_s) + end + end + else + self.class <=> other.class + end + end + + protected + + def handle_message(iprot, fid, ftype) + field = struct_fields[fid] + if field and field[:type] == ftype + @value = read_field(iprot, field) + name = field[:name].to_sym + @setfield = name + else + iprot.skip(ftype) + end + end + end +end diff --git a/src/jaegertracing/thrift/lib/rb/script/proto_benchmark.rb b/src/jaegertracing/thrift/lib/rb/script/proto_benchmark.rb new file mode 100644 index 000000000..bb49e2e42 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/script/proto_benchmark.rb @@ -0,0 +1,121 @@ +# +# 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 File.dirname(__FILE__) + "/../spec/spec_helper.rb" + +require "benchmark" +# require "ruby-prof" + +obj = Fixtures::COMPACT_PROTOCOL_TEST_STRUCT + +HOW_MANY = 1_000 + +binser = Thrift::Serializer.new +bin_data = binser.serialize(obj) +bindeser = Thrift::Deserializer.new +accel_bin_ser = Thrift::Serializer.new(Thrift::BinaryProtocolAcceleratedFactory.new) +accel_bin_deser = Thrift::Deserializer.new(Thrift::BinaryProtocolAcceleratedFactory.new) + +compact_ser = Thrift::Serializer.new(Thrift::CompactProtocolFactory.new) +compact_data = compact_ser.serialize(obj) +compact_deser = Thrift::Deserializer.new(Thrift::CompactProtocolFactory.new) + +Benchmark.bm(60) do |reporter| + reporter.report("binary protocol, write") do + HOW_MANY.times do + binser.serialize(obj) + end + end + + reporter.report("accelerated binary protocol, write") do + HOW_MANY.times do + accel_bin_ser.serialize(obj) + end + end + + reporter.report("compact protocol, write") do + # RubyProf.start + HOW_MANY.times do + compact_ser.serialize(obj) + end + # result = RubyProf.stop + # printer = RubyProf::GraphHtmlPrinter.new(result) + # file = File.open("profile.html", "w+") + # printer.print(file, 0) + # file.close + end + + reporter.report("binary protocol, read") do + HOW_MANY.times do + bindeser.deserialize(obj, bin_data) + end + end + + reporter.report("accelerated binary protocol, read") do + HOW_MANY.times do + accel_bin_deser.deserialize(obj, bin_data) + end + end + + reporter.report("compact protocol, read") do + HOW_MANY.times do + compact_deser.deserialize(obj, compact_data) + end + end + + + # f = File.new("/tmp/testfile", "w") + # proto = Thrift::BinaryProtocolAccelerated.new(Thrift::IOStreamTransport.new(Thrift::MemoryBufferTransport.new, f)) + # reporter.report("accelerated binary protocol, write (to disk)") do + # HOW_MANY.times do + # obj.write(proto) + # end + # f.flush + # end + # f.close + # + # f = File.new("/tmp/testfile", "r") + # proto = Thrift::BinaryProtocolAccelerated.new(Thrift::IOStreamTransport.new(f, Thrift::MemoryBufferTransport.new)) + # reporter.report("accelerated binary protocol, read (from disk)") do + # HOW_MANY.times do + # obj.read(proto) + # end + # end + # f.close + # + # f = File.new("/tmp/testfile", "w") + # reporter.report("compact protocol, write (to disk)") do + # proto = Thrift::CompactProtocol.new(Thrift::IOStreamTransport.new(Thrift::MemoryBufferTransport.new, f)) + # HOW_MANY.times do + # obj.write(proto) + # end + # f.flush + # end + # f.close + # + # f = File.new("/tmp/testfile", "r") + # reporter.report("compact protocol, read (from disk)") do + # proto = Thrift::CompactProtocol.new(Thrift::IOStreamTransport.new(f, Thrift::MemoryBufferTransport.new)) + # HOW_MANY.times do + # obj.read(proto) + # end + # end + # f.close + +end diff --git a/src/jaegertracing/thrift/lib/rb/script/read_struct.rb b/src/jaegertracing/thrift/lib/rb/script/read_struct.rb new file mode 100644 index 000000000..831fcec90 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/script/read_struct.rb @@ -0,0 +1,43 @@ +# +# 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/spec_helper" + +path, factory_class = ARGV + +factory = eval(factory_class).new + +deser = Thrift::Deserializer.new(factory) + +cpts = CompactProtoTestStruct.new +CompactProtoTestStruct.constants.each do |const| + cpts.instance_variable_set("@#{const}", nil) +end + +data = File.read(path) + +deser.deserialize(cpts, data) + +if cpts == Fixtures::COMPACT_PROTOCOL_TEST_STRUCT + puts "Object verified successfully!" +else + puts "Object failed verification! Expected #{Fixtures::COMPACT_PROTOCOL_TEST_STRUCT.inspect} but got #{cpts.inspect}" + + puts cpts.differences(Fixtures::COMPACT_PROTOCOL_TEST_STRUCT) +end diff --git a/src/jaegertracing/thrift/lib/rb/script/write_struct.rb b/src/jaegertracing/thrift/lib/rb/script/write_struct.rb new file mode 100644 index 000000000..da1421975 --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/script/write_struct.rb @@ -0,0 +1,30 @@ +# +# 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/spec_helper" + +path, factory_class = ARGV + +factory = eval(factory_class).new + +ser = Thrift::Serializer.new(factory) + +File.open(path, "w") do |file| + file.write(ser.serialize(Fixtures::COMPACT_PROTOCOL_TEST_STRUCT)) +end
\ No newline at end of file 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 diff --git a/src/jaegertracing/thrift/lib/rb/thrift.gemspec b/src/jaegertracing/thrift/lib/rb/thrift.gemspec new file mode 100644 index 000000000..869e5d95d --- /dev/null +++ b/src/jaegertracing/thrift/lib/rb/thrift.gemspec @@ -0,0 +1,40 @@ +# -*- encoding: utf-8 -*- +$:.push File.expand_path("../lib", __FILE__) + +Gem::Specification.new do |s| + s.name = 'thrift' + s.version = '0.13.0' + s.authors = ['Apache Thrift Developers'] + s.email = ['dev@thrift.apache.org'] + s.homepage = 'http://thrift.apache.org' + s.summary = %q{Ruby bindings for Apache Thrift} + s.description = %q{Ruby bindings for the Apache Thrift RPC system} + s.license = 'Apache-2.0' + s.extensions = ['ext/extconf.rb'] + + s.has_rdoc = true + s.rdoc_options = %w[--line-numbers --inline-source --title Thrift --main README] + + s.rubyforge_project = 'thrift' + + dir = File.expand_path(File.dirname(__FILE__)) + + s.files = Dir.glob("{lib,spec}/**/*") + s.test_files = Dir.glob("{test,spec,benchmark}/**/*") + s.executables = Dir.glob("{bin}/**/*") + + s.extra_rdoc_files = %w[README.md] + Dir.glob("{ext,lib}/**/*.{c,h,rb}") + + s.require_paths = %w[lib ext] + + s.add_development_dependency 'bundler', '~> 1.11' + s.add_development_dependency 'pry', '~> 0.11.3' + s.add_development_dependency 'pry-byebug', '~> 3.6' + s.add_development_dependency 'pry-stack_explorer', '~> 0.4.9.2' + s.add_development_dependency 'rack', '~> 2.0' + s.add_development_dependency 'rack-test', '~> 0.8.3' + s.add_development_dependency 'rake', '~> 12.3' + s.add_development_dependency 'rspec', '~> 3.7' + s.add_development_dependency 'thin', '~> 1.7' +end + |