summaryrefslogtreecommitdiff
path: root/lib/rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/rb')
-rw-r--r--lib/rb/CHANGELOG1
-rw-r--r--lib/rb/Makefile.am47
-rw-r--r--lib/rb/Manifest81
-rw-r--r--lib/rb/README43
-rw-r--r--lib/rb/Rakefile103
-rw-r--r--lib/rb/benchmark/Benchmark.thrift24
-rw-r--r--lib/rb/benchmark/benchmark.rb271
-rw-r--r--lib/rb/benchmark/client.rb74
-rw-r--r--lib/rb/benchmark/server.rb82
-rw-r--r--lib/rb/benchmark/thin_server.rb44
-rw-r--r--lib/rb/ext/binary_protocol_accelerated.c474
-rw-r--r--lib/rb/ext/binary_protocol_accelerated.h20
-rw-r--r--lib/rb/ext/compact_protocol.c665
-rw-r--r--lib/rb/ext/compact_protocol.h20
-rw-r--r--lib/rb/ext/constants.h95
-rw-r--r--lib/rb/ext/extconf.rb26
-rw-r--r--lib/rb/ext/macros.h41
-rw-r--r--lib/rb/ext/memory_buffer.c72
-rw-r--r--lib/rb/ext/memory_buffer.h20
-rw-r--r--lib/rb/ext/protocol.c185
-rw-r--r--lib/rb/ext/protocol.h20
-rw-r--r--lib/rb/ext/struct.c605
-rw-r--r--lib/rb/ext/struct.h67
-rw-r--r--lib/rb/ext/thrift_native.c194
-rw-r--r--lib/rb/lib/thrift.rb59
-rw-r--r--lib/rb/lib/thrift/client.rb62
-rw-r--r--lib/rb/lib/thrift/core_ext.rb23
-rw-r--r--lib/rb/lib/thrift/core_ext/fixnum.rb29
-rw-r--r--lib/rb/lib/thrift/exceptions.rb82
-rw-r--r--lib/rb/lib/thrift/processor.rb57
-rw-r--r--lib/rb/lib/thrift/protocol/base_protocol.rb290
-rw-r--r--lib/rb/lib/thrift/protocol/binary_protocol.rb225
-rw-r--r--lib/rb/lib/thrift/protocol/binary_protocol_accelerated.rb35
-rw-r--r--lib/rb/lib/thrift/protocol/compact_protocol.rb422
-rw-r--r--lib/rb/lib/thrift/serializer/deserializer.rb33
-rw-r--r--lib/rb/lib/thrift/serializer/serializer.rb34
-rw-r--r--lib/rb/lib/thrift/server/base_server.rb31
-rw-r--r--lib/rb/lib/thrift/server/mongrel_http_server.rb58
-rw-r--r--lib/rb/lib/thrift/server/nonblocking_server.rb296
-rw-r--r--lib/rb/lib/thrift/server/simple_server.rb43
-rw-r--r--lib/rb/lib/thrift/server/thread_pool_server.rb75
-rw-r--r--lib/rb/lib/thrift/server/threaded_server.rb47
-rw-r--r--lib/rb/lib/thrift/struct.rb294
-rw-r--r--lib/rb/lib/thrift/thrift_native.rb24
-rw-r--r--lib/rb/lib/thrift/transport/base_server_transport.rb37
-rw-r--r--lib/rb/lib/thrift/transport/base_transport.rb70
-rw-r--r--lib/rb/lib/thrift/transport/buffered_transport.rb77
-rw-r--r--lib/rb/lib/thrift/transport/framed_transport.rb90
-rw-r--r--lib/rb/lib/thrift/transport/http_client_transport.rb45
-rw-r--r--lib/rb/lib/thrift/transport/io_stream_transport.rb39
-rw-r--r--lib/rb/lib/thrift/transport/memory_buffer_transport.rb93
-rw-r--r--lib/rb/lib/thrift/transport/server_socket.rb63
-rw-r--r--lib/rb/lib/thrift/transport/socket.rb136
-rw-r--r--lib/rb/lib/thrift/transport/unix_server_socket.rb60
-rw-r--r--lib/rb/lib/thrift/transport/unix_socket.rb40
-rw-r--r--lib/rb/lib/thrift/types.rb101
-rw-r--r--lib/rb/script/proto_benchmark.rb121
-rw-r--r--lib/rb/script/read_struct.rb43
-rw-r--r--lib/rb/script/write_struct.rb30
-rw-r--r--lib/rb/setup.rb1585
-rw-r--r--lib/rb/spec/ThriftSpec.thrift84
-rw-r--r--lib/rb/spec/base_protocol_spec.rb160
-rw-r--r--lib/rb/spec/base_transport_spec.rb344
-rw-r--r--lib/rb/spec/binary_protocol_accelerated_spec.rb42
-rw-r--r--lib/rb/spec/binary_protocol_spec.rb63
-rw-r--r--lib/rb/spec/binary_protocol_spec_shared.rb375
-rw-r--r--lib/rb/spec/client_spec.rb100
-rw-r--r--lib/rb/spec/compact_protocol_spec.rb117
-rw-r--r--lib/rb/spec/exception_spec.rb142
-rw-r--r--lib/rb/spec/http_client_spec.rb49
-rw-r--r--lib/rb/spec/mongrel_http_server_spec.rb117
-rw-r--r--lib/rb/spec/nonblocking_server_spec.rb266
-rw-r--r--lib/rb/spec/processor_spec.rb83
-rw-r--r--lib/rb/spec/serializer_spec.rb70
-rw-r--r--lib/rb/spec/server_socket_spec.rb80
-rw-r--r--lib/rb/spec/server_spec.rb160
-rw-r--r--lib/rb/spec/socket_spec.rb61
-rw-r--r--lib/rb/spec/socket_spec_shared.rb104
-rw-r--r--lib/rb/spec/spec_helper.rb55
-rw-r--r--lib/rb/spec/struct_spec.rb253
-rw-r--r--lib/rb/spec/types_spec.rb117
-rw-r--r--lib/rb/spec/unix_socket_spec.rb108
82 files changed, 11073 insertions, 0 deletions
diff --git a/lib/rb/CHANGELOG b/lib/rb/CHANGELOG
new file mode 100644
index 000000000..b5dce2aee
--- /dev/null
+++ b/lib/rb/CHANGELOG
@@ -0,0 +1 @@
+v0.0.1. Initial release
diff --git a/lib/rb/Makefile.am b/lib/rb/Makefile.am
new file mode 100644
index 000000000..9cfffc71c
--- /dev/null
+++ b/lib/rb/Makefile.am
@@ -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.
+#
+
+EXTRA_DIST = \
+ CHANGELOG \
+ Rakefile \
+ Manifest \
+ setup.rb \
+ lib \
+ ext \
+ benchmark \
+ script \
+ spec
+
+all-local:
+ $(RUBY) setup.rb config
+ $(RUBY) setup.rb setup
+
+install-exec-hook:
+ $(RUBY) setup.rb install
+
+# Make sure this doesn't fail if Ruby is not configured.
+clean-local:
+ RUBY=$(RUBY) ; if test -z "$$RUBY" ; then RUBY=: ; fi ; \
+ $$RUBY setup.rb clean
+
+check-local: all
+if HAVE_RSPEC
+ rake spec
+endif
+
diff --git a/lib/rb/Manifest b/lib/rb/Manifest
new file mode 100644
index 000000000..7b4503fb4
--- /dev/null
+++ b/lib/rb/Manifest
@@ -0,0 +1,81 @@
+CHANGELOG
+Manifest
+Rakefile
+README
+setup.rb
+benchmark/benchmark.rb
+benchmark/Benchmark.thrift
+benchmark/client.rb
+benchmark/server.rb
+benchmark/thin_server.rb
+ext/binary_protocol_accelerated.c
+ext/binary_protocol_accelerated.h
+ext/compact_protocol.c
+ext/compact_protocol.h
+ext/constants.h
+ext/extconf.rb
+ext/macros.h
+ext/memory_buffer.c
+ext/memory_buffer.h
+ext/protocol.c
+ext/protocol.h
+ext/struct.c
+ext/struct.h
+ext/thrift_native.c
+lib/thrift.rb
+lib/thrift/client.rb
+lib/thrift/core_ext.rb
+lib/thrift/exceptions.rb
+lib/thrift/processor.rb
+lib/thrift/struct.rb
+lib/thrift/thrift_native.rb
+lib/thrift/types.rb
+lib/thrift/core_ext/fixnum.rb
+lib/thrift/protocol/base_protocol.rb
+lib/thrift/protocol/binary_protocol.rb
+lib/thrift/protocol/binary_protocol_accelerated.rb
+lib/thrift/protocol/compact_protocol.rb
+lib/thrift/serializer/deserializer.rb
+lib/thrift/serializer/serializer.rb
+lib/thrift/server/base_server.rb
+lib/thrift/server/mongrel_http_server.rb
+lib/thrift/server/nonblocking_server.rb
+lib/thrift/server/simple_server.rb
+lib/thrift/server/thread_pool_server.rb
+lib/thrift/server/threaded_server.rb
+lib/thrift/transport/base_server_transport.rb
+lib/thrift/transport/base_transport.rb
+lib/thrift/transport/buffered_transport.rb
+lib/thrift/transport/framed_transport.rb
+lib/thrift/transport/http_client_transport.rb
+lib/thrift/transport/io_stream_transport.rb
+lib/thrift/transport/memory_buffer_transport.rb
+lib/thrift/transport/server_socket.rb
+lib/thrift/transport/socket.rb
+lib/thrift/transport/unix_server_socket.rb
+lib/thrift/transport/unix_socket.rb
+script/proto_benchmark.rb
+script/read_struct.rb
+script/write_struct.rb
+spec/base_protocol_spec.rb
+spec/base_transport_spec.rb
+spec/binary_protocol_accelerated_spec.rb
+spec/binary_protocol_spec.rb
+spec/binary_protocol_spec_shared.rb
+spec/client_spec.rb
+spec/compact_protocol_spec.rb
+spec/exception_spec.rb
+spec/http_client_spec.rb
+spec/mongrel_http_server_spec.rb
+spec/nonblocking_server_spec.rb
+spec/processor_spec.rb
+spec/serializer_spec.rb
+spec/server_socket_spec.rb
+spec/server_spec.rb
+spec/socket_spec.rb
+spec/socket_spec_shared.rb
+spec/spec_helper.rb
+spec/struct_spec.rb
+spec/ThriftSpec.thrift
+spec/types_spec.rb
+spec/unix_socket_spec.rb
diff --git a/lib/rb/README b/lib/rb/README
new file mode 100644
index 000000000..d78e35272
--- /dev/null
+++ b/lib/rb/README
@@ -0,0 +1,43 @@
+Thrift Ruby Software Library
+ http://incubator.apache.org/thrift/
+
+== 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/lib/rb/Rakefile b/lib/rb/Rakefile
new file mode 100644
index 000000000..1a9467a57
--- /dev/null
+++ b/lib/rb/Rakefile
@@ -0,0 +1,103 @@
+#
+# 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 'spec/rake/spectask'
+
+THRIFT = '../../compiler/cpp/thrift'
+
+task :default => [:spec]
+
+task :spec => [:'gen-rb', :realspec]
+
+Spec::Rake::SpecTask.new(:realspec) do |t|
+ t.spec_files = FileList['spec/**/*_spec.rb']
+ t.spec_opts = ['--color']
+end
+
+Spec::Rake::SpecTask.new(:'spec:rcov') do |t|
+ t.spec_files = FileList['spec/**/*_spec.rb']
+ t.spec_opts = ['--color']
+ t.rcov = true
+ t.rcov_opts = ['--exclude', '^spec,/gems/']
+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 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 'Compile the .thrift files for the specs'
+task :'gen-rb' => [:'gen-rb: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 :'benchmark' do
+ dir = File.dirname(__FILE__) + '/benchmark'
+ sh THRIFT, '--gen', 'rb', '-o', dir, "#{dir}/Benchmark.thrift"
+ end
+
+ task :'debug_proto' do
+ sh "mkdir", "-p", "debug_proto_test"
+ sh THRIFT, '--gen', 'rb', "-o", "debug_proto_test", "../../test/DebugProtoTest.thrift"
+ end
+end
+
+desc 'Run benchmarking of NonblockingServer'
+task :benchmark do
+ ruby 'benchmark/benchmark.rb'
+end
+
+
+begin
+ require 'echoe'
+
+ Echoe.new('thrift') do |p|
+ p.author = ['Kevin Ballard', 'Kevin Clark', 'Mark Slee']
+ p.email = ['kevin@sb.org', 'kevin.clark@gmail.com', 'mcslee@facebook.com']
+ p.summary = "Ruby libraries for Thrift (a language-agnostic RPC system)"
+ p.url = "http://incubator.apache.org/thrift/"
+ p.include_rakefile = true
+ p.version = "0.1.0"
+ end
+
+ task :install => [:check_site_lib]
+
+ require 'rbconfig'
+ task :check_site_lib do
+ if File.exist?(File.join(Config::CONFIG['sitelibdir'], 'thrift.rb'))
+ fail "thrift is already installed in site_ruby"
+ end
+ end
+rescue LoadError
+ [:install, :package].each do |t|
+ desc "Stub for #{t}"
+ task t do
+ fail "The Echoe gem is required for this task"
+ end
+ end
+end
diff --git a/lib/rb/benchmark/Benchmark.thrift b/lib/rb/benchmark/Benchmark.thrift
new file mode 100644
index 000000000..eb5ae38e6
--- /dev/null
+++ b/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/lib/rb/benchmark/benchmark.rb b/lib/rb/benchmark/benchmark.rb
new file mode 100644
index 000000000..3dc67dd8c
--- /dev/null
+++ b/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/lib/rb/benchmark/client.rb b/lib/rb/benchmark/client.rb
new file mode 100644
index 000000000..703dc8f52
--- /dev/null
+++ b/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/lib/rb/benchmark/server.rb b/lib/rb/benchmark/server.rb
new file mode 100644
index 000000000..74e13f414
--- /dev/null
+++ b/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/lib/rb/benchmark/thin_server.rb b/lib/rb/benchmark/thin_server.rb
new file mode 100644
index 000000000..4de2eef38
--- /dev/null
+++ b/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/lib/rb/ext/binary_protocol_accelerated.c b/lib/rb/ext/binary_protocol_accelerated.c
new file mode 100644
index 000000000..728a05725
--- /dev/null
+++ b/lib/rb/ext/binary_protocol_accelerated.c
@@ -0,0 +1,474 @@
+/**
+ * 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"
+
+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 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) {
+ 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;
+}
+
+//---------------------------------------
+// interface reading methods
+//---------------------------------------
+
+VALUE rb_thrift_binary_proto_read_string(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 buf = READ(self, 1);
+ return RSTRING_PTR(buf)[0];
+}
+
+static int16_t read_i16_direct(VALUE self) {
+ VALUE buf = READ(self, 2);
+ return (int16_t)(((uint8_t)(RSTRING_PTR(buf)[1])) | ((uint16_t)((RSTRING_PTR(buf)[0]) << 8)));
+}
+
+static int32_t read_i32_direct(VALUE self) {
+ VALUE buf = READ(self, 4);
+ return ((uint8_t)(RSTRING_PTR(buf)[3])) |
+ (((uint8_t)(RSTRING_PTR(buf)[2])) << 8) |
+ (((uint8_t)(RSTRING_PTR(buf)[1])) << 16) |
+ (((uint8_t)(RSTRING_PTR(buf)[0])) << 24);
+}
+
+static int64_t read_i64_direct(VALUE self) {
+ uint64_t hi = read_i32_direct(self);
+ uint32_t lo = read_i32_direct(self);
+ 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_thift_binary_proto_read_struct_begin(VALUE self) {
+ return Qnil;
+}
+
+VALUE rb_thift_binary_proto_read_struct_end(VALUE self) {
+ return Qnil;
+}
+
+VALUE rb_thift_binary_proto_read_field_end(VALUE self) {
+ return Qnil;
+}
+
+VALUE rb_thift_binary_proto_read_map_end(VALUE self) {
+ return Qnil;
+}
+
+VALUE rb_thift_binary_proto_read_list_end(VALUE self) {
+ return Qnil;
+}
+
+VALUE rb_thift_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) {
+ 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);
+ // 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);
+ // 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_thift_binary_proto_read_struct_begin, 0);
+ rb_define_method(bpa_class, "read_struct_end", rb_thift_binary_proto_read_struct_end, 0);
+ rb_define_method(bpa_class, "read_field_end", rb_thift_binary_proto_read_field_end, 0);
+ rb_define_method(bpa_class, "read_map_end", rb_thift_binary_proto_read_map_end, 0);
+ rb_define_method(bpa_class, "read_list_end", rb_thift_binary_proto_read_list_end, 0);
+ rb_define_method(bpa_class, "read_set_end", rb_thift_binary_proto_read_set_end, 0);
+
+ // set up native method table
+ native_proto_method_table *npmt;
+ npmt = ALLOC(native_proto_method_table);
+
+ npmt->write_field_begin = rb_thrift_binary_proto_write_field_begin;
+ npmt->write_field_stop = rb_thrift_binary_proto_write_field_stop;
+ npmt->write_map_begin = rb_thrift_binary_proto_write_map_begin;
+ npmt->write_list_begin = rb_thrift_binary_proto_write_list_begin;
+ npmt->write_set_begin = rb_thrift_binary_proto_write_set_begin;
+ npmt->write_byte = rb_thrift_binary_proto_write_byte;
+ npmt->write_bool = rb_thrift_binary_proto_write_bool;
+ npmt->write_i16 = rb_thrift_binary_proto_write_i16;
+ npmt->write_i32 = rb_thrift_binary_proto_write_i32;
+ npmt->write_i64 = rb_thrift_binary_proto_write_i64;
+ npmt->write_double = rb_thrift_binary_proto_write_double;
+ npmt->write_string = rb_thrift_binary_proto_write_string;
+ npmt->write_message_end = rb_thrift_binary_proto_write_message_end;
+ npmt->write_struct_begin = rb_thrift_binary_proto_write_struct_begin;
+ npmt->write_struct_end = rb_thrift_binary_proto_write_struct_end;
+ npmt->write_field_end = rb_thrift_binary_proto_write_field_end;
+ npmt->write_map_end = rb_thrift_binary_proto_write_map_end;
+ npmt->write_list_end = rb_thrift_binary_proto_write_list_end;
+ npmt->write_set_end = rb_thrift_binary_proto_write_set_end;
+
+ npmt->read_message_begin = rb_thrift_binary_proto_read_message_begin;
+ npmt->read_field_begin = rb_thrift_binary_proto_read_field_begin;
+ npmt->read_map_begin = rb_thrift_binary_proto_read_map_begin;
+ npmt->read_list_begin = rb_thrift_binary_proto_read_list_begin;
+ npmt->read_set_begin = rb_thrift_binary_proto_read_set_begin;
+ npmt->read_byte = rb_thrift_binary_proto_read_byte;
+ npmt->read_bool = rb_thrift_binary_proto_read_bool;
+ npmt->read_i16 = rb_thrift_binary_proto_read_i16;
+ npmt->read_i32 = rb_thrift_binary_proto_read_i32;
+ npmt->read_i64 = rb_thrift_binary_proto_read_i64;
+ npmt->read_double = rb_thrift_binary_proto_read_double;
+ npmt->read_string = rb_thrift_binary_proto_read_string;
+ npmt->read_message_end = rb_thrift_binary_proto_read_message_end;
+ npmt->read_struct_begin = rb_thift_binary_proto_read_struct_begin;
+ npmt->read_struct_end = rb_thift_binary_proto_read_struct_end;
+ npmt->read_field_end = rb_thift_binary_proto_read_field_end;
+ npmt->read_map_end = rb_thift_binary_proto_read_map_end;
+ npmt->read_list_end = rb_thift_binary_proto_read_list_end;
+ npmt->read_set_end = rb_thift_binary_proto_read_set_end;
+
+ VALUE method_table_object = Data_Wrap_Struct(rb_cObject, 0, free, npmt);
+ rb_const_set(bpa_class, rb_intern("@native_method_table"), method_table_object);
+}
diff --git a/lib/rb/ext/binary_protocol_accelerated.h b/lib/rb/ext/binary_protocol_accelerated.h
new file mode 100644
index 000000000..37baf4142
--- /dev/null
+++ b/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/lib/rb/ext/compact_protocol.c b/lib/rb/ext/compact_protocol.c
new file mode 100644
index 000000000..7966d3e3f
--- /dev/null
+++ b/lib/rb/ext/compact_protocol.c
@@ -0,0 +1,665 @@
+/**
+ * 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"
+
+#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 int VERSION;
+static int VERSION_MASK;
+static int TYPE_MASK;
+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, 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_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) {
+ VALUE transport = GET_TRANSPORT(self);
+ write_varint32(transport, RSTRING_LEN(str));
+ WRITE(transport, RSTRING_PTR(str), RSTRING_LEN(str));
+ 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_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, str);
+ return 0;
+ }
+}
+
+static char read_byte_direct(VALUE self) {
+ VALUE buf = READ(self, 1);
+ return RSTRING_PTR(buf)[0];
+}
+
+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) & 0x03;
+ 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.
+ 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 bytes = READ(self, 8);
+ uint32_t lo = ((uint8_t)(RSTRING_PTR(bytes)[0]))
+ | (((uint8_t)(RSTRING_PTR(bytes)[1])) << 8)
+ | (((uint8_t)(RSTRING_PTR(bytes)[2])) << 16)
+ | (((uint8_t)(RSTRING_PTR(bytes)[3])) << 24);
+ uint64_t hi = (((uint8_t)(RSTRING_PTR(bytes)[4])))
+ | (((uint8_t)(RSTRING_PTR(bytes)[5])) << 8)
+ | (((uint8_t)(RSTRING_PTR(bytes)[6])) << 16)
+ | (((uint8_t)(RSTRING_PTR(bytes)[7])) << 24);
+ transfer.l = (hi << 32) | lo;
+
+ return rb_float_new(transfer.f);
+}
+
+VALUE rb_thrift_compact_proto_read_string(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_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");
+}
+
+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_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_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);
+}
+
+static void Init_npmt() {
+ native_proto_method_table *npmt;
+ npmt = ALLOC(native_proto_method_table);
+
+ npmt->write_field_begin = rb_thrift_compact_proto_write_field_begin;
+ npmt->write_field_stop = rb_thrift_compact_proto_write_field_stop;
+ npmt->write_map_begin = rb_thrift_compact_proto_write_map_begin;
+ npmt->write_list_begin = rb_thrift_compact_proto_write_list_begin;
+ npmt->write_set_begin = rb_thrift_compact_proto_write_set_begin;
+ npmt->write_byte = rb_thrift_compact_proto_write_byte;
+ npmt->write_bool = rb_thrift_compact_proto_write_bool;
+ npmt->write_i16 = rb_thrift_compact_proto_write_i16;
+ npmt->write_i32 = rb_thrift_compact_proto_write_i32;
+ npmt->write_i64 = rb_thrift_compact_proto_write_i64;
+ npmt->write_double = rb_thrift_compact_proto_write_double;
+ npmt->write_string = rb_thrift_compact_proto_write_string;
+ npmt->write_message_end = rb_thrift_compact_proto_write_message_end;
+ npmt->write_struct_begin = rb_thrift_compact_proto_write_struct_begin;
+ npmt->write_struct_end = rb_thrift_compact_proto_write_struct_end;
+ npmt->write_field_end = rb_thrift_compact_proto_write_field_end;
+ npmt->write_map_end = rb_thrift_compact_proto_write_map_end;
+ npmt->write_list_end = rb_thrift_compact_proto_write_list_end;
+ npmt->write_set_end = rb_thrift_compact_proto_write_set_end;
+
+ npmt->read_message_begin = rb_thrift_compact_proto_read_message_begin;
+ npmt->read_field_begin = rb_thrift_compact_proto_read_field_begin;
+ npmt->read_map_begin = rb_thrift_compact_proto_read_map_begin;
+ npmt->read_list_begin = rb_thrift_compact_proto_read_list_begin;
+ npmt->read_set_begin = rb_thrift_compact_proto_read_set_begin;
+ npmt->read_byte = rb_thrift_compact_proto_read_byte;
+ npmt->read_bool = rb_thrift_compact_proto_read_bool;
+ npmt->read_i16 = rb_thrift_compact_proto_read_i16;
+ npmt->read_i32 = rb_thrift_compact_proto_read_i32;
+ npmt->read_i64 = rb_thrift_compact_proto_read_i64;
+ npmt->read_double = rb_thrift_compact_proto_read_double;
+ npmt->read_string = rb_thrift_compact_proto_read_string;
+ npmt->read_message_end = rb_thrift_compact_proto_read_message_end;
+ npmt->read_struct_begin = rb_thrift_compact_proto_read_struct_begin;
+ npmt->read_struct_end = rb_thrift_compact_proto_read_struct_end;
+ npmt->read_field_end = rb_thrift_compact_proto_read_field_end;
+ npmt->read_map_end = rb_thrift_compact_proto_read_map_end;
+ npmt->read_list_end = rb_thrift_compact_proto_read_list_end;
+ npmt->read_set_end = rb_thrift_compact_proto_read_set_end;
+
+ VALUE method_table_object = Data_Wrap_Struct(rb_cObject, 0, free, npmt);
+ rb_const_set(thrift_compact_protocol_class, rb_intern("@native_method_table"), method_table_object);
+}
+
+
+
+void Init_compact_protocol() {
+ Init_constants();
+ Init_rb_methods();
+ Init_npmt();
+}
diff --git a/lib/rb/ext/compact_protocol.h b/lib/rb/ext/compact_protocol.h
new file mode 100644
index 000000000..163915e94
--- /dev/null
+++ b/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/lib/rb/ext/constants.h b/lib/rb/ext/constants.h
new file mode 100644
index 000000000..57df544b5
--- /dev/null
+++ b/lib/rb/ext/constants.h
@@ -0,0 +1,95 @@
+/*
+ * 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_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 size_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_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 name_method_id;
+extern ID sort_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 native_qmark_method_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 rb_cSet;
+extern VALUE thrift_module;
+extern VALUE thrift_types_module;
+extern VALUE class_thrift_protocol;
+extern VALUE protocol_exception_class;
diff --git a/lib/rb/ext/extconf.rb b/lib/rb/ext/extconf.rb
new file mode 100644
index 000000000..07558b8aa
--- /dev/null
+++ b/lib/rb/ext/extconf.rb
@@ -0,0 +1,26 @@
+#
+# 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 'mkmf'
+
+$CFLAGS = "-g -O2 -Wall -Werror"
+
+have_func("strlcpy", "string.h")
+
+create_makefile 'thrift_native'
diff --git a/lib/rb/ext/macros.h b/lib/rb/ext/macros.h
new file mode 100644
index 000000000..265f6930d
--- /dev/null
+++ b/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/lib/rb/ext/memory_buffer.c b/lib/rb/ext/memory_buffer.c
new file mode 100644
index 000000000..624012d4b
--- /dev/null
+++ b/lib/rb/ext/memory_buffer.c
@@ -0,0 +1,72 @@
+/**
+ * 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 "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 buf = GET_BUF(self);
+ rb_str_buf_cat(buf, RSTRING_PTR(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));
+ return data;
+}
+
+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);
+
+ 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/lib/rb/ext/memory_buffer.h b/lib/rb/ext/memory_buffer.h
new file mode 100644
index 000000000..b277fa6f6
--- /dev/null
+++ b/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/lib/rb/ext/protocol.c b/lib/rb/ext/protocol.c
new file mode 100644
index 000000000..c1876541c
--- /dev/null
+++ b/lib/rb/ext/protocol.c
@@ -0,0 +1,185 @@
+/**
+ * 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 <protocol.h>
+#include <stdbool.h>
+#include <constants.h>
+#include <struct.h>
+
+static VALUE skip(VALUE self, int ttype) {
+ if (ttype == TTYPE_STOP) {
+ return Qnil;
+ } else if (ttype == TTYPE_BOOL) {
+ rb_funcall(self, read_bool_method_id, 0);
+ } else if (ttype == TTYPE_BYTE) {
+ rb_funcall(self, read_byte_method_id, 0);
+ } else if (ttype == TTYPE_I16) {
+ rb_funcall(self, read_i16_method_id, 0);
+ } else if (ttype == TTYPE_I32) {
+ rb_funcall(self, read_i32_method_id, 0);
+ } else if (ttype == TTYPE_I64) {
+ rb_funcall(self, read_i64_method_id, 0);
+ } else if (ttype == TTYPE_DOUBLE) {
+ rb_funcall(self, read_double_method_id, 0);
+ } else if (ttype == TTYPE_STRING) {
+ rb_funcall(self, read_string_method_id, 0);
+ } else if (ttype == TTYPE_STRUCT) {
+ rb_funcall(self, read_struct_begin_method_id, 0);
+ while (true) {
+ VALUE field_header = rb_funcall(self, read_field_begin_method_id, 0);
+ if (NIL_P(field_header) || FIX2INT(rb_ary_entry(field_header, 1)) == TTYPE_STOP ) {
+ break;
+ }
+ skip(self, FIX2INT(rb_ary_entry(field_header, 1)));
+ rb_funcall(self, read_field_end_method_id, 0);
+ }
+ rb_funcall(self, read_struct_end_method_id, 0);
+ } else if (ttype == TTYPE_MAP) {
+ int i;
+ VALUE map_header = rb_funcall(self, read_map_begin_method_id, 0);
+ int ktype = FIX2INT(rb_ary_entry(map_header, 0));
+ int vtype = FIX2INT(rb_ary_entry(map_header, 1));
+ int size = FIX2INT(rb_ary_entry(map_header, 2));
+
+ for (i = 0; i < size; i++) {
+ skip(self, ktype);
+ skip(self, vtype);
+ }
+ rb_funcall(self, read_map_end_method_id, 0);
+ } else if (ttype == TTYPE_LIST || ttype == TTYPE_SET) {
+ int i;
+ VALUE collection_header = rb_funcall(self, ttype == TTYPE_LIST ? read_list_begin_method_id : read_set_begin_method_id, 0);
+ int etype = FIX2INT(rb_ary_entry(collection_header, 0));
+ int size = FIX2INT(rb_ary_entry(collection_header, 1));
+ for (i = 0; i < size; i++) {
+ skip(self, etype);
+ }
+ rb_funcall(self, ttype == TTYPE_LIST ? read_list_end_method_id : read_set_end_method_id, 0);
+ } else {
+ rb_raise(rb_eNotImpError, "don't know how to skip type %d", ttype);
+ }
+
+ return Qnil;
+}
+
+VALUE rb_thrift_protocol_native_qmark(VALUE self) {
+ return Qfalse;
+}
+
+VALUE rb_thrift_protocol_skip(VALUE protocol, VALUE ttype) {
+ return skip(protocol, FIX2INT(ttype));
+}
+
+VALUE rb_thrift_write_message_end(VALUE self) {
+ return Qnil;
+}
+
+VALUE rb_thrift_write_struct_begin(VALUE self, VALUE name) {
+ return Qnil;
+}
+
+VALUE rb_thrift_write_struct_end(VALUE self) {
+ return Qnil;
+}
+
+VALUE rb_thrift_write_field_end(VALUE self) {
+ return Qnil;
+}
+
+VALUE rb_thrift_write_map_end(VALUE self) {
+ return Qnil;
+}
+
+VALUE rb_thrift_write_list_end(VALUE self) {
+ return Qnil;
+}
+
+VALUE rb_thrift_write_set_end(VALUE self) {
+ return Qnil;
+}
+
+VALUE rb_thrift_read_message_end(VALUE self) {
+ return Qnil;
+}
+
+VALUE rb_thift_read_struct_begin(VALUE self) {
+ return Qnil;
+}
+
+VALUE rb_thift_read_struct_end(VALUE self) {
+ return Qnil;
+}
+
+VALUE rb_thift_read_field_end(VALUE self) {
+ return Qnil;
+}
+
+VALUE rb_thift_read_map_end(VALUE self) {
+ return Qnil;
+}
+
+VALUE rb_thift_read_list_end(VALUE self) {
+ return Qnil;
+}
+
+VALUE rb_thift_read_set_end(VALUE self) {
+ return Qnil;
+}
+
+void Init_protocol() {
+ VALUE c_protocol = rb_const_get(thrift_module, rb_intern("BaseProtocol"));
+
+ rb_define_method(c_protocol, "skip", rb_thrift_protocol_skip, 1);
+ rb_define_method(c_protocol, "write_message_end", rb_thrift_write_message_end, 0);
+ rb_define_method(c_protocol, "write_struct_begin", rb_thrift_write_struct_begin, 1);
+ rb_define_method(c_protocol, "write_struct_end", rb_thrift_write_struct_end, 0);
+ rb_define_method(c_protocol, "write_field_end", rb_thrift_write_field_end, 0);
+ rb_define_method(c_protocol, "write_map_end", rb_thrift_write_map_end, 0);
+ rb_define_method(c_protocol, "write_list_end", rb_thrift_write_list_end, 0);
+ rb_define_method(c_protocol, "write_set_end", rb_thrift_write_set_end, 0);
+ rb_define_method(c_protocol, "read_message_end", rb_thrift_read_message_end, 0);
+ rb_define_method(c_protocol, "read_struct_begin", rb_thift_read_struct_begin, 0);
+ rb_define_method(c_protocol, "read_struct_end", rb_thift_read_struct_end, 0);
+ rb_define_method(c_protocol, "read_field_end", rb_thift_read_field_end, 0);
+ rb_define_method(c_protocol, "read_map_end", rb_thift_read_map_end, 0);
+ rb_define_method(c_protocol, "read_list_end", rb_thift_read_list_end, 0);
+ rb_define_method(c_protocol, "read_set_end", rb_thift_read_set_end, 0);
+ rb_define_method(c_protocol, "native?", rb_thrift_protocol_native_qmark, 0);
+
+ // native_proto_method_table *npmt;
+ // npmt = ALLOC(native_proto_method_table);
+ // npmt->write_message_end = rb_thrift_write_message_end;
+ // npmt->write_struct_begin = rb_thrift_write_struct_begin;
+ // npmt->write_struct_end = rb_thrift_write_struct_end;
+ // npmt->write_field_end = rb_thrift_write_field_end;
+ // npmt->write_map_end = rb_thrift_write_map_end;
+ // npmt->write_list_end = rb_thrift_write_list_end;
+ // npmt->write_set_end = rb_thrift_write_set_end;
+ // npmt->read_message_end = rb_thrift_read_message_end;
+ // npmt->read_struct_begin = rb_thift_read_struct_begin;
+ // npmt->read_struct_end = rb_thift_read_struct_end;
+ // npmt->read_field_end = rb_thift_read_field_end;
+ // npmt->read_map_end = rb_thift_read_map_end;
+ // npmt->read_list_end = rb_thift_read_list_end;
+ // npmt->read_set_end = rb_thift_read_set_end;
+ //
+ // VALUE method_table_object = Data_Wrap_Struct(rb_cObject, 0, free, npmt);
+ // rb_const_set(c_protocol, rb_intern("@native_method_table"), method_table_object);
+}
diff --git a/lib/rb/ext/protocol.h b/lib/rb/ext/protocol.h
new file mode 100644
index 000000000..536953036
--- /dev/null
+++ b/lib/rb/ext/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_protocol();
diff --git a/lib/rb/ext/struct.c b/lib/rb/ext/struct.c
new file mode 100644
index 000000000..fee285e97
--- /dev/null
+++ b/lib/rb/ext/struct.c
@@ -0,0 +1,605 @@
+/**
+ * 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"
+
+#ifndef HAVE_STRLCPY
+
+static
+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
+
+static native_proto_method_table *mt;
+static native_proto_method_table *default_mt;
+static VALUE last_proto_class = Qnil;
+
+#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)
+
+static void set_native_proto_function_pointers(VALUE protocol) {
+ VALUE method_table_object = rb_const_get(CLASS_OF(protocol), rb_intern("@native_method_table"));
+ // TODO: check nil?
+ Data_Get_Struct(method_table_object, native_proto_method_table, mt);
+}
+
+static void check_native_proto_method_table(VALUE protocol) {
+ VALUE protoclass = CLASS_OF(protocol);
+ if (protoclass != last_proto_class) {
+ last_proto_class = protoclass;
+ if (rb_funcall(protocol, native_qmark_method_id, 0) == Qtrue) {
+ set_native_proto_function_pointers(protocol);
+ } else {
+ mt = default_mt;
+ }
+ }
+}
+
+//-------------------------------------------
+// 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_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_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);
+}
+
+static void set_default_proto_function_pointers() {
+ default_mt = ALLOC(native_proto_method_table);
+
+ default_mt->write_field_begin = default_write_field_begin;
+ default_mt->write_field_stop = default_write_field_stop;
+ default_mt->write_map_begin = default_write_map_begin;
+ default_mt->write_map_end = default_write_map_end;
+ default_mt->write_list_begin = default_write_list_begin;
+ default_mt->write_list_end = default_write_list_end;
+ default_mt->write_set_begin = default_write_set_begin;
+ default_mt->write_set_end = default_write_set_end;
+ default_mt->write_byte = default_write_byte;
+ default_mt->write_bool = default_write_bool;
+ default_mt->write_i16 = default_write_i16;
+ default_mt->write_i32 = default_write_i32;
+ default_mt->write_i64 = default_write_i64;
+ default_mt->write_double = default_write_double;
+ default_mt->write_string = default_write_string;
+ default_mt->write_struct_begin = default_write_struct_begin;
+ default_mt->write_struct_end = default_write_struct_end;
+ default_mt->write_field_end = default_write_field_end;
+
+ default_mt->read_struct_begin = default_read_struct_begin;
+ default_mt->read_struct_end = default_read_struct_end;
+ default_mt->read_field_begin = default_read_field_begin;
+ default_mt->read_field_end = default_read_field_end;
+ default_mt->read_map_begin = default_read_map_begin;
+ default_mt->read_map_end = default_read_map_end;
+ default_mt->read_list_begin = default_read_list_begin;
+ default_mt->read_list_end = default_read_list_end;
+ default_mt->read_set_begin = default_read_set_begin;
+ default_mt->read_set_end = default_read_set_end;
+ default_mt->read_byte = default_read_byte;
+ default_mt->read_bool = default_read_bool;
+ default_mt->read_i16 = default_read_i16;
+ default_mt->read_i32 = default_read_i32;
+ default_mt->read_i64 = default_read_i64;
+ default_mt->read_double = default_read_double;
+ default_mt->read_string = default_read_string;
+}
+
+// end default protocol methods
+
+
+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) + 1];
+
+ name_buf[0] = '@';
+ strlcpy(&name_buf[1], RSTRING_PTR(field_name), sizeof(name_buf));
+
+ 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);
+
+ mt->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);
+ }
+ }
+
+ mt->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);
+
+ mt->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);
+ }
+ }
+ mt->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);
+
+ mt->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);
+ }
+ }
+
+ mt->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) {
+ mt->write_bool(protocol, value);
+ } else if (ttype == TTYPE_BYTE) {
+ mt->write_byte(protocol, value);
+ } else if (ttype == TTYPE_I16) {
+ mt->write_i16(protocol, value);
+ } else if (ttype == TTYPE_I32) {
+ mt->write_i32(protocol, value);
+ } else if (ttype == TTYPE_I64) {
+ mt->write_i64(protocol, value);
+ } else if (ttype == TTYPE_DOUBLE) {
+ mt->write_double(protocol, value);
+ } else if (ttype == TTYPE_STRING) {
+ mt->write_string(protocol, value);
+ } else if (IS_CONTAINER(ttype)) {
+ write_container(ttype, field_info, value, protocol);
+ } else if (ttype == TTYPE_STRUCT) {
+ 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);
+
+ check_native_proto_method_table(protocol);
+
+ // write struct begin
+ mt->write_struct_begin(protocol, rb_class_name(CLASS_OF(self)));
+
+ // iterate through all the fields here
+ VALUE struct_fields = STRUCT_FIELDS(self);
+ VALUE struct_field_ids_unordered = rb_funcall(struct_fields, keys_method_id, 0);
+ VALUE struct_field_ids_ordered = rb_funcall(struct_field_ids_unordered, sort_method_id, 0);
+
+ int i = 0;
+ for (i=0; i < RARRAY_LEN(struct_field_ids_ordered); i++) {
+ VALUE field_id = rb_ary_entry(struct_field_ids_ordered, 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)) {
+ mt->write_field_begin(protocol, field_name, ttype_value, field_id);
+
+ write_anything(ttype, field_value, protocol, field_info);
+
+ mt->write_field_end(protocol);
+ }
+ }
+
+ mt->write_field_stop(protocol);
+
+ // write struct end
+ mt->write_struct_end(protocol);
+
+ return Qnil;
+}
+
+//-------------------------------------------
+// Reading section
+//-------------------------------------------
+
+static VALUE rb_thrift_struct_read(VALUE self, VALUE protocol);
+
+static void set_field_value(VALUE obj, VALUE field_name, VALUE value) {
+ char name_buf[RSTRING_LEN(field_name) + 1];
+
+ name_buf[0] = '@';
+ strlcpy(&name_buf[1], RSTRING_PTR(field_name), sizeof(name_buf));
+
+ rb_ivar_set(obj, rb_intern(name_buf), value);
+}
+
+static VALUE read_anything(VALUE protocol, int ttype, VALUE field_info) {
+ VALUE result = Qnil;
+
+ if (ttype == TTYPE_BOOL) {
+ result = mt->read_bool(protocol);
+ } else if (ttype == TTYPE_BYTE) {
+ result = mt->read_byte(protocol);
+ } else if (ttype == TTYPE_I16) {
+ result = mt->read_i16(protocol);
+ } else if (ttype == TTYPE_I32) {
+ result = mt->read_i32(protocol);
+ } else if (ttype == TTYPE_I64) {
+ result = mt->read_i64(protocol);
+ } else if (ttype == TTYPE_STRING) {
+ result = mt->read_string(protocol);
+ } else if (ttype == TTYPE_DOUBLE) {
+ result = mt->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);
+ rb_thrift_struct_read(result, protocol);
+ } else if (ttype == TTYPE_MAP) {
+ int i;
+
+ VALUE map_header = mt->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));
+
+ VALUE key_info = rb_hash_aref(field_info, key_sym);
+ VALUE value_info = rb_hash_aref(field_info, value_sym);
+
+ 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);
+ }
+
+ mt->read_map_end(protocol);
+ } else if (ttype == TTYPE_LIST) {
+ int i;
+
+ VALUE list_header = mt->read_list_begin(protocol);
+ int element_ttype = FIX2INT(rb_ary_entry(list_header, 0));
+ int num_elements = FIX2INT(rb_ary_entry(list_header, 1));
+ 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)));
+ }
+
+
+ mt->read_list_end(protocol);
+ } else if (ttype == TTYPE_SET) {
+ VALUE items;
+ int i;
+
+ VALUE set_header = mt->read_set_begin(protocol);
+ int element_ttype = FIX2INT(rb_ary_entry(set_header, 0));
+ int num_elements = FIX2INT(rb_ary_entry(set_header, 1));
+ 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)));
+ }
+
+
+ mt->read_set_end(protocol);
+
+ result = rb_class_new_instance(1, &items, rb_cSet);
+ } 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) {
+ check_native_proto_method_table(protocol);
+
+ // read struct begin
+ mt->read_struct_begin(protocol);
+
+ VALUE struct_fields = STRUCT_FIELDS(self);
+
+ // read each field
+ while (true) {
+ VALUE field_header = mt->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
+ mt->read_field_end(protocol);
+ }
+
+ // read struct end
+ mt->read_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);
+
+ set_default_proto_function_pointers();
+}
+
diff --git a/lib/rb/ext/struct.h b/lib/rb/ext/struct.h
new file mode 100644
index 000000000..37b1b35b2
--- /dev/null
+++ b/lib/rb/ext/struct.h
@@ -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.
+ */
+
+#include <stdbool.h>
+#include <ruby.h>
+
+typedef struct native_proto_method_table {
+ VALUE (*write_bool)(VALUE, VALUE);
+ VALUE (*write_byte)(VALUE, VALUE);
+ VALUE (*write_i16)(VALUE, VALUE);
+ VALUE (*write_i32)(VALUE, VALUE);
+ VALUE (*write_i64)(VALUE, VALUE);
+ VALUE (*write_double)(VALUE, VALUE);
+ VALUE (*write_string)(VALUE, VALUE);
+ VALUE (*write_list_begin)(VALUE, VALUE, VALUE);
+ VALUE (*write_list_end)(VALUE);
+ VALUE (*write_set_begin)(VALUE, VALUE, VALUE);
+ VALUE (*write_set_end)(VALUE);
+ VALUE (*write_map_begin)(VALUE, VALUE, VALUE, VALUE);
+ VALUE (*write_map_end)(VALUE);
+ VALUE (*write_struct_begin)(VALUE, VALUE);
+ VALUE (*write_struct_end)(VALUE);
+ VALUE (*write_field_begin)(VALUE, VALUE, VALUE, VALUE);
+ VALUE (*write_field_end)(VALUE);
+ VALUE (*write_field_stop)(VALUE);
+ VALUE (*write_message_begin)(VALUE, VALUE, VALUE, VALUE);
+ VALUE (*write_message_end)(VALUE);
+
+ VALUE (*read_message_begin)(VALUE);
+ VALUE (*read_message_end)(VALUE);
+ VALUE (*read_field_begin)(VALUE);
+ VALUE (*read_field_end)(VALUE);
+ VALUE (*read_map_begin)(VALUE);
+ VALUE (*read_map_end)(VALUE);
+ VALUE (*read_list_begin)(VALUE);
+ VALUE (*read_list_end)(VALUE);
+ VALUE (*read_set_begin)(VALUE);
+ VALUE (*read_set_end)(VALUE);
+ VALUE (*read_byte)(VALUE);
+ VALUE (*read_bool)(VALUE);
+ VALUE (*read_i16)(VALUE);
+ VALUE (*read_i32)(VALUE);
+ VALUE (*read_i64)(VALUE);
+ VALUE (*read_double)(VALUE);
+ VALUE (*read_string)(VALUE);
+ VALUE (*read_struct_begin)(VALUE);
+ VALUE (*read_struct_end)(VALUE);
+
+} native_proto_method_table;
+
+void Init_struct();
diff --git a/lib/rb/ext/thrift_native.c b/lib/rb/ext/thrift_native.c
new file mode 100644
index 000000000..effa202c4
--- /dev/null
+++ b/lib/rb/ext/thrift_native.c
@@ -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.
+ */
+
+#include <ruby.h>
+#include <struct.h>
+#include <binary_protocol_accelerated.h>
+#include <compact_protocol.h>
+#include <protocol.h>
+#include <memory_buffer.h>
+
+// cached classes/modules
+VALUE rb_cSet;
+VALUE thrift_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_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 size_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_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 name_method_id;
+ID sort_method_id;
+ID write_field_stop_method_id;
+ID skip_method_id;
+ID write_method_id;
+ID read_all_method_id;
+ID native_qmark_method_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 protocol_exception_class;
+
+void Init_thrift_native() {
+ // cached classes
+ thrift_module = rb_const_get(rb_cObject, rb_intern("Thrift"));
+ 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_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");
+ size_method_id = rb_intern("size");
+ 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_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");
+ name_method_id = rb_intern("name");
+ sort_method_id = rb_intern("sort");
+ 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");
+ native_qmark_method_id = rb_intern("native?");
+
+ // 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"));
+
+ Init_protocol();
+ Init_struct();
+ Init_binary_protocol_accelerated();
+ Init_compact_protocol();
+ Init_memory_buffer();
+}
diff --git a/lib/rb/lib/thrift.rb b/lib/rb/lib/thrift.rb
new file mode 100644
index 000000000..88562e137
--- /dev/null
+++ b/lib/rb/lib/thrift.rb
@@ -0,0 +1,59 @@
+#
+# 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__)
+
+require 'thrift/core_ext'
+require 'thrift/exceptions'
+require 'thrift/types'
+require 'thrift/processor'
+require 'thrift/client'
+require 'thrift/struct'
+
+# 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'
+
+# transport
+require 'thrift/transport/base_transport'
+require 'thrift/transport/base_server_transport'
+require 'thrift/transport/socket'
+require 'thrift/transport/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' \ No newline at end of file
diff --git a/lib/rb/lib/thrift/client.rb b/lib/rb/lib/thrift/client.rb
new file mode 100644
index 000000000..5b30f0150
--- /dev/null
+++ b/lib/rb/lib/thrift/client.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.
+#
+
+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)
+ 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/lib/rb/lib/thrift/core_ext.rb b/lib/rb/lib/thrift/core_ext.rb
new file mode 100644
index 000000000..f763cd534
--- /dev/null
+++ b/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/lib/rb/lib/thrift/core_ext/fixnum.rb b/lib/rb/lib/thrift/core_ext/fixnum.rb
new file mode 100644
index 000000000..b4fc90dd6
--- /dev/null
+++ b/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/lib/rb/lib/thrift/exceptions.rb b/lib/rb/lib/thrift/exceptions.rb
new file mode 100644
index 000000000..dda70894b
--- /dev/null
+++ b/lib/rb/lib/thrift/exceptions.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.
+#
+
+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
+
+ 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 \ No newline at end of file
diff --git a/lib/rb/lib/thrift/processor.rb b/lib/rb/lib/thrift/processor.rb
new file mode 100644
index 000000000..5d9e0a11c
--- /dev/null
+++ b/lib/rb/lib/thrift/processor.rb
@@ -0,0 +1,57 @@
+#
+# 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 Processor
+ def initialize(handler)
+ @handler = handler
+ end
+
+ def process(iprot, oprot)
+ name, type, seqid = iprot.read_message_begin
+ if respond_to?("process_#{name}")
+ send("process_#{name}", seqid, iprot, oprot)
+ true
+ else
+ iprot.skip(Types::STRUCT)
+ iprot.read_message_end
+ x = ApplicationException.new(ApplicationException::UNKNOWN_METHOD, 'Unknown function '+name)
+ oprot.write_message_begin(name, MessageTypes::EXCEPTION, seqid)
+ x.write(oprot)
+ oprot.write_message_end
+ oprot.trans.flush
+ 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
+ end
+end
diff --git a/lib/rb/lib/thrift/protocol/base_protocol.rb b/lib/rb/lib/thrift/protocol/base_protocol.rb
new file mode 100644
index 000000000..b19909d5f
--- /dev/null
+++ b/lib/rb/lib/thrift/protocol/base_protocol.rb
@@ -0,0 +1,290 @@
+#
+# 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
+
+ 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
+
+ def write_string(str)
+ 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
+
+ def read_string
+ raise NotImplementedError
+ end
+
+ def write_field(name, type, fid, value)
+ write_field_begin(name, type, fid)
+ write_type(type, value)
+ write_field_end
+ end
+
+ def write_type(type, value)
+ case 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
+ write_string(value)
+ when Types::STRUCT
+ value.write(self)
+ else
+ raise NotImplementedError
+ end
+ end
+
+ def read_type(type)
+ case 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
+ read_string
+ else
+ raise NotImplementedError
+ end
+ end
+
+ def skip(type)
+ case type
+ when Types::STOP
+ nil
+ 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
+ end
+ end
+ end
+
+ class BaseProtocolFactory
+ def get_protocol(trans)
+ raise NotImplementedError
+ end
+ end
+end \ No newline at end of file
diff --git a/lib/rb/lib/thrift/protocol/binary_protocol.rb b/lib/rb/lib/thrift/protocol/binary_protocol.rb
new file mode 100644
index 000000000..04d149ac2
--- /dev/null
+++ b/lib/rb/lib/thrift/protocol/binary_protocol.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.
+#
+
+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
+ 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)
+ write_i32(str.length)
+ trans.write(str)
+ 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
+ dat = trans.read_all(1)
+ val = dat[0].ord
+ if (val > 0x7f)
+ val = 0 - ((val - 1) ^ 0xff)
+ end
+ val
+ end
+
+ def read_i16
+ dat = trans.read_all(2)
+ val, = dat.unpack('n')
+ if (val > 0x7fff)
+ val = 0 - ((val - 1) ^ 0xffff)
+ end
+ val
+ end
+
+ def read_i32
+ dat = trans.read_all(4)
+ val, = dat.unpack('N')
+ if (val > 0x7fffffff)
+ val = 0 - ((val - 1) ^ 0xffffffff)
+ end
+ val
+ end
+
+ def read_i64
+ dat = trans.read_all(8)
+ hi, lo = dat.unpack('N2')
+ if (hi > 0x7fffffff)
+ hi ^= 0xffffffff
+ lo ^= 0xffffffff
+ 0 - (hi << 32) - lo - 1
+ else
+ (hi << 32) + lo
+ end
+ end
+
+ def read_double
+ dat = trans.read_all(8)
+ val = dat.unpack('G').first
+ val
+ end
+
+ def read_string
+ sz = read_i32
+ dat = trans.read_all(sz)
+ dat
+ end
+
+ end
+
+ class BinaryProtocolFactory < BaseProtocolFactory
+ def get_protocol(trans)
+ return Thrift::BinaryProtocol.new(trans)
+ end
+ end
+end
diff --git a/lib/rb/lib/thrift/protocol/binary_protocol_accelerated.rb b/lib/rb/lib/thrift/protocol/binary_protocol_accelerated.rb
new file mode 100644
index 000000000..eaf64f6be
--- /dev/null
+++ b/lib/rb/lib/thrift/protocol/binary_protocol_accelerated.rb
@@ -0,0 +1,35 @@
+#
+# 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)
+ BinaryProtocolAccelerated.new(trans)
+ end
+ end
+end
diff --git a/lib/rb/lib/thrift/protocol/compact_protocol.rb b/lib/rb/lib/thrift/protocol/compact_protocol.rb
new file mode 100644
index 000000000..c8f436559
--- /dev/null
+++ b/lib/rb/lib/thrift/protocol/compact_protocol.rb
@@ -0,0 +1,422 @@
+#
+# 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_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
+ 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)
+ write_varint32(str.length)
+ @trans.write(str)
+ 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) & 0x03
+ 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.
+ 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
+ dat = trans.read_all(1)
+ val = dat[0]
+ 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
+ dat = trans.read_all(8)
+ val = dat.reverse.unpack('G').first
+ val
+ end
+
+ def read_string
+ size = read_varint32()
+ trans.read_all(size)
+ 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
+ end
+end
diff --git a/lib/rb/lib/thrift/serializer/deserializer.rb b/lib/rb/lib/thrift/serializer/deserializer.rb
new file mode 100644
index 000000000..d2ee325a5
--- /dev/null
+++ b/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/lib/rb/lib/thrift/serializer/serializer.rb b/lib/rb/lib/thrift/serializer/serializer.rb
new file mode 100644
index 000000000..22316395d
--- /dev/null
+++ b/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/lib/rb/lib/thrift/server/base_server.rb b/lib/rb/lib/thrift/server/base_server.rb
new file mode 100644
index 000000000..1ee121333
--- /dev/null
+++ b/lib/rb/lib/thrift/server/base_server.rb
@@ -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.
+#
+
+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; nil; end
+ end
+end \ No newline at end of file
diff --git a/lib/rb/lib/thrift/server/mongrel_http_server.rb b/lib/rb/lib/thrift/server/mongrel_http_server.rb
new file mode 100644
index 000000000..84eacf0dc
--- /dev/null
+++ b/lib/rb/lib/thrift/server/mongrel_http_server.rb
@@ -0,0 +1,58 @@
+#
+# 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
+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={})
+ 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/lib/rb/lib/thrift/server/nonblocking_server.rb b/lib/rb/lib/thrift/server/nonblocking_server.rb
new file mode 100644
index 000000000..5425f6de1
--- /dev/null
+++ b/lib/rb/lib/thrift/server/nonblocking_server.rb
@@ -0,0 +1,296 @@
+#
+# 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?
+ rd, = select([@server_transport], nil, nil, 0.1)
+ 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|
+ if fd.handle.eof?
+ remove_connection fd
+ else
+ read_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)
+ frame = slice_frame!(@buffers[fd])
+ if frame
+ @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 \ No newline at end of file
diff --git a/lib/rb/lib/thrift/server/simple_server.rb b/lib/rb/lib/thrift/server/simple_server.rb
new file mode 100644
index 000000000..21e865926
--- /dev/null
+++ b/lib/rb/lib/thrift/server/simple_server.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.
+#
+
+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
+ end
+end \ No newline at end of file
diff --git a/lib/rb/lib/thrift/server/thread_pool_server.rb b/lib/rb/lib/thrift/server/thread_pool_server.rb
new file mode 100644
index 000000000..8cec805a9
--- /dev/null
+++ b/lib/rb/lib/thrift/server/thread_pool_server.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 '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
+ end
+end \ No newline at end of file
diff --git a/lib/rb/lib/thrift/server/threaded_server.rb b/lib/rb/lib/thrift/server/threaded_server.rb
new file mode 100644
index 000000000..a2c917cb8
--- /dev/null
+++ b/lib/rb/lib/thrift/server/threaded_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.
+#
+
+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
+ end
+end \ No newline at end of file
diff --git a/lib/rb/lib/thrift/struct.rb b/lib/rb/lib/thrift/struct.rb
new file mode 100644
index 000000000..01aae56b7
--- /dev/null
+++ b/lib/rb/lib/thrift/struct.rb
@@ -0,0 +1,294 @@
+#
+# 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={})
+ # 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
+ 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 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 each_field
+ struct_fields.keys.sort.each do |fid|
+ data = struct_fields[fid]
+ yield fid, data
+ end
+ 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}:#{value.inspect}"
+ 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]
+ if (value = instance_variable_get("@#{name}"))
+ 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(name, type, fid, value)
+ end
+ end
+ end
+ oprot.write_field_stop
+ oprot.write_struct_end
+ end
+
+ def ==(other)
+ each_field do |fid, field_info|
+ name = field_info[:name]
+ return false unless self.instance_variable_get("@#{name}") == other.instance_variable_get("@#{name}")
+ end
+ true
+ end
+
+ def eql?(other)
+ self.class == other.class && self == other
+ end
+
+ # for the time being, we're ok with a naive hash. this could definitely be improved upon.
+ def hash
+ 0
+ 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, *fields)
+ fields.each do |field|
+ klass.send :attr_reader, field
+ klass.send :define_method, "#{field}=" do |value|
+ Thrift.check_type(value, klass::FIELDS.values.find { |f| f[:name].to_s == field.to_s }, field) if Thrift.type_checking
+ instance_variable_set("@#{field}", value)
+ end
+ 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
+
+ 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
+ value = {}
+ size.times do
+ k = read_field(iprot, field_info(field[:key]))
+ v = read_field(iprot, field_info(field[:value]))
+ value[k] = v
+ end
+ iprot.read_map_end
+ when Types::LIST
+ e_type, size = iprot.read_list_begin
+ value = Array.new(size) do |n|
+ read_field(iprot, field_info(field[:element]))
+ end
+ iprot.read_list_end
+ when Types::SET
+ e_type, size = iprot.read_set_begin
+ value = Set.new
+ size.times do
+ element = read_field(iprot, field_info(field[:element]))
+ value << element
+ end
+ iprot.read_set_end
+ else
+ value = iprot.read_type(field[:type])
+ end
+ value
+ end
+
+ def write_data(oprot, value, field)
+ if is_container? field[:type]
+ write_container(oprot, value, field)
+ else
+ oprot.write_type(field[:type], 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
+ end
+end
diff --git a/lib/rb/lib/thrift/thrift_native.rb b/lib/rb/lib/thrift/thrift_native.rb
new file mode 100644
index 000000000..4d8df61ff
--- /dev/null
+++ b/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/lib/rb/lib/thrift/transport/base_server_transport.rb b/lib/rb/lib/thrift/transport/base_server_transport.rb
new file mode 100644
index 000000000..68c5af076
--- /dev/null
+++ b/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 \ No newline at end of file
diff --git a/lib/rb/lib/thrift/transport/base_transport.rb b/lib/rb/lib/thrift/transport/base_transport.rb
new file mode 100644
index 000000000..08a71dab7
--- /dev/null
+++ b/lib/rb/lib/thrift/transport/base_transport.rb
@@ -0,0 +1,70 @@
+# 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
+
+ class BaseTransport
+ def open?; end
+
+ def open; end
+
+ def close; end
+
+ def read(sz)
+ raise NotImplementedError
+ end
+
+ def read_all(size)
+ buf = ''
+
+ while (buf.length < size)
+ chunk = read(size - buf.length)
+ buf << chunk
+ end
+
+ buf
+ end
+
+ def write(buf); end
+ alias_method :<<, :write
+
+ def flush; end
+ end
+
+ class BaseTransportFactory
+ def get_transport(trans)
+ return trans
+ end
+ end
+end \ No newline at end of file
diff --git a/lib/rb/lib/thrift/transport/buffered_transport.rb b/lib/rb/lib/thrift/transport/buffered_transport.rb
new file mode 100644
index 000000000..8dead4e0b
--- /dev/null
+++ b/lib/rb/lib/thrift/transport/buffered_transport.rb
@@ -0,0 +1,77 @@
+# 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 = ''
+ @rbuf = ''
+ @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) || ''
+
+ if ret.length == 0
+ @rbuf = @transport.read([sz, DEFAULT_BUFFER].max)
+ @index = sz
+ ret = @rbuf.slice(0, sz) || ''
+ end
+
+ ret
+ end
+
+ def write(buf)
+ @wbuf << buf
+ end
+
+ def flush
+ if @wbuf != ''
+ @transport.write(@wbuf)
+ @wbuf = ''
+ end
+
+ @transport.flush
+ end
+ end
+
+ class BufferedTransportFactory < BaseTransportFactory
+ def get_transport(transport)
+ return BufferedTransport.new(transport)
+ end
+ end
+end \ No newline at end of file
diff --git a/lib/rb/lib/thrift/transport/framed_transport.rb b/lib/rb/lib/thrift/transport/framed_transport.rb
new file mode 100644
index 000000000..558af7444
--- /dev/null
+++ b/lib/rb/lib/thrift/transport/framed_transport.rb
@@ -0,0 +1,90 @@
+# 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 = ''
+ @wbuf = ''
+ @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 '' if sz <= 0
+
+ read_frame if @index >= @rbuf.length
+
+ @index += sz
+ @rbuf.slice(@index - sz, sz) || ''
+ end
+
+ def write(buf,sz=nil)
+ return @transport.write(buf) unless @write
+
+ @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')
+ out << @wbuf
+ @transport.write(out)
+ @transport.flush
+ @wbuf = ''
+ 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
+ end
+end \ No newline at end of file
diff --git a/lib/rb/lib/thrift/transport/http_client_transport.rb b/lib/rb/lib/thrift/transport/http_client_transport.rb
new file mode 100644
index 000000000..a190a9830
--- /dev/null
+++ b/lib/rb/lib/thrift/transport/http_client_transport.rb
@@ -0,0 +1,45 @@
+# 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 'uri'
+require 'stringio'
+
+module Thrift
+ class HTTPClientTransport < BaseTransport
+ def initialize(url)
+ @url = URI url
+ @outbuf = ""
+ end
+
+ def open?; true end
+ def read(sz); @inbuf.read sz end
+ def write(buf); @outbuf << buf end
+ def flush
+ http = Net::HTTP.new @url.host, @url.port
+ http.use_ssl = @url.scheme == "https"
+ headers = { 'Content-Type' => 'application/x-thrift' }
+ resp, data = http.post(@url.path, @outbuf, headers)
+ @inbuf = StringIO.new data
+ @outbuf = ""
+ end
+ end
+end
diff --git a/lib/rb/lib/thrift/transport/io_stream_transport.rb b/lib/rb/lib/thrift/transport/io_stream_transport.rb
new file mode 100644
index 000000000..be348aa09
--- /dev/null
+++ b/lib/rb/lib/thrift/transport/io_stream_transport.rb
@@ -0,0 +1,39 @@
+# 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(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
+ end
+end \ No newline at end of file
diff --git a/lib/rb/lib/thrift/transport/memory_buffer_transport.rb b/lib/rb/lib/thrift/transport/memory_buffer_transport.rb
new file mode 100644
index 000000000..33d732d13
--- /dev/null
+++ b/lib/rb/lib/thrift/transport/memory_buffer_transport.rb
@@ -0,0 +1,93 @@
+# 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 || ''
+ @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 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
+ data
+ end
+
+ def write(wbuf)
+ @buf << 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].to_s(16)
+ end
+ out.join(" ")
+ end
+ end
+end \ No newline at end of file
diff --git a/lib/rb/lib/thrift/transport/server_socket.rb b/lib/rb/lib/thrift/transport/server_socket.rb
new file mode 100644
index 000000000..7feb9ab0d
--- /dev/null
+++ b/lib/rb/lib/thrift/transport/server_socket.rb
@@ -0,0 +1,63 @@
+# 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
+ end
+end \ No newline at end of file
diff --git a/lib/rb/lib/thrift/transport/socket.rb b/lib/rb/lib/thrift/transport/socket.rb
new file mode 100644
index 000000000..06c937e58
--- /dev/null
+++ b/lib/rb/lib/thrift/transport/socket.rb
@@ -0,0 +1,136 @@
+# 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
+ begin
+ addrinfo = ::Socket::getaddrinfo(@host, @port).first
+ @handle = ::Socket.new(addrinfo[4], ::Socket::SOCK_STREAM, 0)
+ sockaddr = ::Socket.sockaddr_in(addrinfo[1], addrinfo[3])
+ begin
+ @handle.connect_nonblock(sockaddr)
+ rescue Errno::EINPROGRESS
+ unless IO.select(nil, [ @handle ], nil, @timeout)
+ raise TransportException.new(TransportException::NOT_OPEN, "Connection timeout to #{@desc}")
+ end
+ begin
+ @handle.connect_nonblock(sockaddr)
+ rescue Errno::EISCONN
+ end
+ end
+ @handle
+ rescue StandardError => e
+ raise TransportException.new(TransportException::NOT_OPEN, "Could not connect to #{@desc}: #{e}")
+ end
+ end
+
+ def open?
+ !@handle.nil? and !@handle.closed?
+ end
+
+ def write(str)
+ raise IOError, "closed stream" unless open?
+ 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
+ start = Time.now
+ rd = nil # scoping
+ loop do
+ rd, = IO.select([@handle], nil, nil, @timeout)
+ break if (rd and not rd.empty?) or Time.now - start >= @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
+
+ def to_io
+ @handle
+ end
+ end
+end \ No newline at end of file
diff --git a/lib/rb/lib/thrift/transport/unix_server_socket.rb b/lib/rb/lib/thrift/transport/unix_server_socket.rb
new file mode 100644
index 000000000..a135d25f2
--- /dev/null
+++ b/lib/rb/lib/thrift/transport/unix_server_socket.rb
@@ -0,0 +1,60 @@
+# 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
+ end
+end \ No newline at end of file
diff --git a/lib/rb/lib/thrift/transport/unix_socket.rb b/lib/rb/lib/thrift/transport/unix_socket.rb
new file mode 100644
index 000000000..8f692e4c8
--- /dev/null
+++ b/lib/rb/lib/thrift/transport/unix_socket.rb
@@ -0,0 +1,40 @@
+# 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
+ end
+end \ No newline at end of file
diff --git a/lib/rb/lib/thrift/types.rb b/lib/rb/lib/thrift/types.rb
new file mode 100644
index 000000000..20e4ca2c1
--- /dev/null
+++ b/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
+ 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/lib/rb/script/proto_benchmark.rb b/lib/rb/script/proto_benchmark.rb
new file mode 100644
index 000000000..bb49e2e42
--- /dev/null
+++ b/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/lib/rb/script/read_struct.rb b/lib/rb/script/read_struct.rb
new file mode 100644
index 000000000..831fcec90
--- /dev/null
+++ b/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/lib/rb/script/write_struct.rb b/lib/rb/script/write_struct.rb
new file mode 100644
index 000000000..da1421975
--- /dev/null
+++ b/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/lib/rb/setup.rb b/lib/rb/setup.rb
new file mode 100644
index 000000000..9f0c8267a
--- /dev/null
+++ b/lib/rb/setup.rb
@@ -0,0 +1,1585 @@
+#
+# setup.rb
+#
+# Copyright (c) 2000-2005 Minero Aoki
+#
+# This program is free software.
+# You can distribute/modify this program under the terms of
+# the GNU LGPL, Lesser General Public License version 2.1.
+#
+
+unless Enumerable.method_defined?(:map) # Ruby 1.4.6
+ module Enumerable
+ alias map collect
+ end
+end
+
+unless File.respond_to?(:read) # Ruby 1.6
+ def File.read(fname)
+ open(fname) {|f|
+ return f.read
+ }
+ end
+end
+
+unless Errno.const_defined?(:ENOTEMPTY) # Windows?
+ module Errno
+ class ENOTEMPTY
+ # We do not raise this exception, implementation is not needed.
+ end
+ end
+end
+
+def File.binread(fname)
+ open(fname, 'rb') {|f|
+ return f.read
+ }
+end
+
+# for corrupted Windows' stat(2)
+def File.dir?(path)
+ File.directory?((path[-1,1] == '/') ? path : path + '/')
+end
+
+
+class ConfigTable
+
+ include Enumerable
+
+ def initialize(rbconfig)
+ @rbconfig = rbconfig
+ @items = []
+ @table = {}
+ # options
+ @install_prefix = nil
+ @config_opt = nil
+ @verbose = true
+ @no_harm = false
+ end
+
+ attr_accessor :install_prefix
+ attr_accessor :config_opt
+
+ attr_writer :verbose
+
+ def verbose?
+ @verbose
+ end
+
+ attr_writer :no_harm
+
+ def no_harm?
+ @no_harm
+ end
+
+ def [](key)
+ lookup(key).resolve(self)
+ end
+
+ def []=(key, val)
+ lookup(key).set val
+ end
+
+ def names
+ @items.map {|i| i.name }
+ end
+
+ def each(&block)
+ @items.each(&block)
+ end
+
+ def key?(name)
+ @table.key?(name)
+ end
+
+ def lookup(name)
+ @table[name] or setup_rb_error "no such config item: #{name}"
+ end
+
+ def add(item)
+ @items.push item
+ @table[item.name] = item
+ end
+
+ def remove(name)
+ item = lookup(name)
+ @items.delete_if {|i| i.name == name }
+ @table.delete_if {|name, i| i.name == name }
+ item
+ end
+
+ def load_script(path, inst = nil)
+ if File.file?(path)
+ MetaConfigEnvironment.new(self, inst).instance_eval File.read(path), path
+ end
+ end
+
+ def savefile
+ '.config'
+ end
+
+ def load_savefile
+ begin
+ File.foreach(savefile()) do |line|
+ k, v = *line.split(/=/, 2)
+ self[k] = v.strip
+ end
+ rescue Errno::ENOENT
+ setup_rb_error $!.message + "\n#{File.basename($0)} config first"
+ end
+ end
+
+ def save
+ @items.each {|i| i.value }
+ File.open(savefile(), 'w') {|f|
+ @items.each do |i|
+ f.printf "%s=%s\n", i.name, i.value if i.value? and i.value
+ end
+ }
+ end
+
+ def load_standard_entries
+ standard_entries(@rbconfig).each do |ent|
+ add ent
+ end
+ end
+
+ def standard_entries(rbconfig)
+ c = rbconfig
+
+ rubypath = File.join(c['bindir'], c['ruby_install_name'] + c['EXEEXT'])
+
+ major = c['MAJOR'].to_i
+ minor = c['MINOR'].to_i
+ teeny = c['TEENY'].to_i
+ version = "#{major}.#{minor}"
+
+ # ruby ver. >= 1.4.4?
+ newpath_p = ((major >= 2) or
+ ((major == 1) and
+ ((minor >= 5) or
+ ((minor == 4) and (teeny >= 4)))))
+
+ if c['rubylibdir']
+ # V > 1.6.3
+ libruby = "#{c['prefix']}/lib/ruby"
+ librubyver = c['rubylibdir']
+ librubyverarch = c['archdir']
+ siteruby = c['sitedir']
+ siterubyver = c['sitelibdir']
+ siterubyverarch = c['sitearchdir']
+ elsif newpath_p
+ # 1.4.4 <= V <= 1.6.3
+ libruby = "#{c['prefix']}/lib/ruby"
+ librubyver = "#{c['prefix']}/lib/ruby/#{version}"
+ librubyverarch = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}"
+ siteruby = c['sitedir']
+ siterubyver = "$siteruby/#{version}"
+ siterubyverarch = "$siterubyver/#{c['arch']}"
+ else
+ # V < 1.4.4
+ libruby = "#{c['prefix']}/lib/ruby"
+ librubyver = "#{c['prefix']}/lib/ruby/#{version}"
+ librubyverarch = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}"
+ siteruby = "#{c['prefix']}/lib/ruby/#{version}/site_ruby"
+ siterubyver = siteruby
+ siterubyverarch = "$siterubyver/#{c['arch']}"
+ end
+ parameterize = lambda {|path|
+ path.sub(/\A#{Regexp.quote(c['prefix'])}/, '$prefix')
+ }
+
+ if arg = c['configure_args'].split.detect {|arg| /--with-make-prog=/ =~ arg }
+ makeprog = arg.sub(/'/, '').split(/=/, 2)[1]
+ else
+ makeprog = 'make'
+ end
+
+ [
+ ExecItem.new('installdirs', 'std/site/home',
+ 'std: install under libruby; site: install under site_ruby; home: install under $HOME')\
+ {|val, table|
+ case val
+ when 'std'
+ table['rbdir'] = '$librubyver'
+ table['sodir'] = '$librubyverarch'
+ when 'site'
+ table['rbdir'] = '$siterubyver'
+ table['sodir'] = '$siterubyverarch'
+ when 'home'
+ setup_rb_error '$HOME was not set' unless ENV['HOME']
+ table['prefix'] = ENV['HOME']
+ table['rbdir'] = '$libdir/ruby'
+ table['sodir'] = '$libdir/ruby'
+ end
+ },
+ PathItem.new('prefix', 'path', c['prefix'],
+ 'path prefix of target environment'),
+ PathItem.new('bindir', 'path', parameterize.call(c['bindir']),
+ 'the directory for commands'),
+ PathItem.new('libdir', 'path', parameterize.call(c['libdir']),
+ 'the directory for libraries'),
+ PathItem.new('datadir', 'path', parameterize.call(c['datadir']),
+ 'the directory for shared data'),
+ PathItem.new('mandir', 'path', parameterize.call(c['mandir']),
+ 'the directory for man pages'),
+ PathItem.new('sysconfdir', 'path', parameterize.call(c['sysconfdir']),
+ 'the directory for system configuration files'),
+ PathItem.new('localstatedir', 'path', parameterize.call(c['localstatedir']),
+ 'the directory for local state data'),
+ PathItem.new('libruby', 'path', libruby,
+ 'the directory for ruby libraries'),
+ PathItem.new('librubyver', 'path', librubyver,
+ 'the directory for standard ruby libraries'),
+ PathItem.new('librubyverarch', 'path', librubyverarch,
+ 'the directory for standard ruby extensions'),
+ PathItem.new('siteruby', 'path', siteruby,
+ 'the directory for version-independent aux ruby libraries'),
+ PathItem.new('siterubyver', 'path', siterubyver,
+ 'the directory for aux ruby libraries'),
+ PathItem.new('siterubyverarch', 'path', siterubyverarch,
+ 'the directory for aux ruby binaries'),
+ PathItem.new('rbdir', 'path', '$siterubyver',
+ 'the directory for ruby scripts'),
+ PathItem.new('sodir', 'path', '$siterubyverarch',
+ 'the directory for ruby extentions'),
+ PathItem.new('rubypath', 'path', rubypath,
+ 'the path to set to #! line'),
+ ProgramItem.new('rubyprog', 'name', rubypath,
+ 'the ruby program using for installation'),
+ ProgramItem.new('makeprog', 'name', makeprog,
+ 'the make program to compile ruby extentions'),
+ SelectItem.new('shebang', 'all/ruby/never', 'ruby',
+ 'shebang line (#!) editing mode'),
+ BoolItem.new('without-ext', 'yes/no', 'no',
+ 'does not compile/install ruby extentions')
+ ]
+ end
+ private :standard_entries
+
+ def load_multipackage_entries
+ multipackage_entries().each do |ent|
+ add ent
+ end
+ end
+
+ def multipackage_entries
+ [
+ PackageSelectionItem.new('with', 'name,name...', '', 'ALL',
+ 'package names that you want to install'),
+ PackageSelectionItem.new('without', 'name,name...', '', 'NONE',
+ 'package names that you do not want to install')
+ ]
+ end
+ private :multipackage_entries
+
+ ALIASES = {
+ 'std-ruby' => 'librubyver',
+ 'stdruby' => 'librubyver',
+ 'rubylibdir' => 'librubyver',
+ 'archdir' => 'librubyverarch',
+ 'site-ruby-common' => 'siteruby', # For backward compatibility
+ 'site-ruby' => 'siterubyver', # For backward compatibility
+ 'bin-dir' => 'bindir',
+ 'bin-dir' => 'bindir',
+ 'rb-dir' => 'rbdir',
+ 'so-dir' => 'sodir',
+ 'data-dir' => 'datadir',
+ 'ruby-path' => 'rubypath',
+ 'ruby-prog' => 'rubyprog',
+ 'ruby' => 'rubyprog',
+ 'make-prog' => 'makeprog',
+ 'make' => 'makeprog'
+ }
+
+ def fixup
+ ALIASES.each do |ali, name|
+ @table[ali] = @table[name]
+ end
+ @items.freeze
+ @table.freeze
+ @options_re = /\A--(#{@table.keys.join('|')})(?:=(.*))?\z/
+ end
+
+ def parse_opt(opt)
+ m = @options_re.match(opt) or setup_rb_error "config: unknown option #{opt}"
+ m.to_a[1,2]
+ end
+
+ def dllext
+ @rbconfig['DLEXT']
+ end
+
+ def value_config?(name)
+ lookup(name).value?
+ end
+
+ class Item
+ def initialize(name, template, default, desc)
+ @name = name.freeze
+ @template = template
+ @value = default
+ @default = default
+ @description = desc
+ end
+
+ attr_reader :name
+ attr_reader :description
+
+ attr_accessor :default
+ alias help_default default
+
+ def help_opt
+ "--#{@name}=#{@template}"
+ end
+
+ def value?
+ true
+ end
+
+ def value
+ @value
+ end
+
+ def resolve(table)
+ @value.gsub(%r<\$([^/]+)>) { table[$1] }
+ end
+
+ def set(val)
+ @value = check(val)
+ end
+
+ private
+
+ def check(val)
+ setup_rb_error "config: --#{name} requires argument" unless val
+ val
+ end
+ end
+
+ class BoolItem < Item
+ def config_type
+ 'bool'
+ end
+
+ def help_opt
+ "--#{@name}"
+ end
+
+ private
+
+ def check(val)
+ return 'yes' unless val
+ case val
+ when /\Ay(es)?\z/i, /\At(rue)?\z/i then 'yes'
+ when /\An(o)?\z/i, /\Af(alse)\z/i then 'no'
+ else
+ setup_rb_error "config: --#{@name} accepts only yes/no for argument"
+ end
+ end
+ end
+
+ class PathItem < Item
+ def config_type
+ 'path'
+ end
+
+ private
+
+ def check(path)
+ setup_rb_error "config: --#{@name} requires argument" unless path
+ path[0,1] == '$' ? path : File.expand_path(path)
+ end
+ end
+
+ class ProgramItem < Item
+ def config_type
+ 'program'
+ end
+ end
+
+ class SelectItem < Item
+ def initialize(name, selection, default, desc)
+ super
+ @ok = selection.split('/')
+ end
+
+ def config_type
+ 'select'
+ end
+
+ private
+
+ def check(val)
+ unless @ok.include?(val.strip)
+ setup_rb_error "config: use --#{@name}=#{@template} (#{val})"
+ end
+ val.strip
+ end
+ end
+
+ class ExecItem < Item
+ def initialize(name, selection, desc, &block)
+ super name, selection, nil, desc
+ @ok = selection.split('/')
+ @action = block
+ end
+
+ def config_type
+ 'exec'
+ end
+
+ def value?
+ false
+ end
+
+ def resolve(table)
+ setup_rb_error "$#{name()} wrongly used as option value"
+ end
+
+ undef set
+
+ def evaluate(val, table)
+ v = val.strip.downcase
+ unless @ok.include?(v)
+ setup_rb_error "invalid option --#{@name}=#{val} (use #{@template})"
+ end
+ @action.call v, table
+ end
+ end
+
+ class PackageSelectionItem < Item
+ def initialize(name, template, default, help_default, desc)
+ super name, template, default, desc
+ @help_default = help_default
+ end
+
+ attr_reader :help_default
+
+ def config_type
+ 'package'
+ end
+
+ private
+
+ def check(val)
+ unless File.dir?("packages/#{val}")
+ setup_rb_error "config: no such package: #{val}"
+ end
+ val
+ end
+ end
+
+ class MetaConfigEnvironment
+ def initialize(config, installer)
+ @config = config
+ @installer = installer
+ end
+
+ def config_names
+ @config.names
+ end
+
+ def config?(name)
+ @config.key?(name)
+ end
+
+ def bool_config?(name)
+ @config.lookup(name).config_type == 'bool'
+ end
+
+ def path_config?(name)
+ @config.lookup(name).config_type == 'path'
+ end
+
+ def value_config?(name)
+ @config.lookup(name).config_type != 'exec'
+ end
+
+ def add_config(item)
+ @config.add item
+ end
+
+ def add_bool_config(name, default, desc)
+ @config.add BoolItem.new(name, 'yes/no', default ? 'yes' : 'no', desc)
+ end
+
+ def add_path_config(name, default, desc)
+ @config.add PathItem.new(name, 'path', default, desc)
+ end
+
+ def set_config_default(name, default)
+ @config.lookup(name).default = default
+ end
+
+ def remove_config(name)
+ @config.remove(name)
+ end
+
+ # For only multipackage
+ def packages
+ raise '[setup.rb fatal] multi-package metaconfig API packages() called for single-package; contact application package vendor' unless @installer
+ @installer.packages
+ end
+
+ # For only multipackage
+ def declare_packages(list)
+ raise '[setup.rb fatal] multi-package metaconfig API declare_packages() called for single-package; contact application package vendor' unless @installer
+ @installer.packages = list
+ end
+ end
+
+end # class ConfigTable
+
+
+# This module requires: #verbose?, #no_harm?
+module FileOperations
+
+ def mkdir_p(dirname, prefix = nil)
+ dirname = prefix + File.expand_path(dirname) if prefix
+ $stderr.puts "mkdir -p #{dirname}" if verbose?
+ return if no_harm?
+
+ # Does not check '/', it's too abnormal.
+ dirs = File.expand_path(dirname).split(%r<(?=/)>)
+ if /\A[a-z]:\z/i =~ dirs[0]
+ disk = dirs.shift
+ dirs[0] = disk + dirs[0]
+ end
+ dirs.each_index do |idx|
+ path = dirs[0..idx].join('')
+ Dir.mkdir path unless File.dir?(path)
+ end
+ end
+
+ def rm_f(path)
+ $stderr.puts "rm -f #{path}" if verbose?
+ return if no_harm?
+ force_remove_file path
+ end
+
+ def rm_rf(path)
+ $stderr.puts "rm -rf #{path}" if verbose?
+ return if no_harm?
+ remove_tree path
+ end
+
+ def remove_tree(path)
+ if File.symlink?(path)
+ remove_file path
+ elsif File.dir?(path)
+ remove_tree0 path
+ else
+ force_remove_file path
+ end
+ end
+
+ def remove_tree0(path)
+ Dir.foreach(path) do |ent|
+ next if ent == '.'
+ next if ent == '..'
+ entpath = "#{path}/#{ent}"
+ if File.symlink?(entpath)
+ remove_file entpath
+ elsif File.dir?(entpath)
+ remove_tree0 entpath
+ else
+ force_remove_file entpath
+ end
+ end
+ begin
+ Dir.rmdir path
+ rescue Errno::ENOTEMPTY
+ # directory may not be empty
+ end
+ end
+
+ def move_file(src, dest)
+ force_remove_file dest
+ begin
+ File.rename src, dest
+ rescue
+ File.open(dest, 'wb') {|f|
+ f.write File.binread(src)
+ }
+ File.chmod File.stat(src).mode, dest
+ File.unlink src
+ end
+ end
+
+ def force_remove_file(path)
+ begin
+ remove_file path
+ rescue
+ end
+ end
+
+ def remove_file(path)
+ File.chmod 0777, path
+ File.unlink path
+ end
+
+ def install(from, dest, mode, prefix = nil)
+ $stderr.puts "install #{from} #{dest}" if verbose?
+ return if no_harm?
+
+ realdest = prefix ? prefix + File.expand_path(dest) : dest
+ realdest = File.join(realdest, File.basename(from)) if File.dir?(realdest)
+ str = File.binread(from)
+ if diff?(str, realdest)
+ verbose_off {
+ rm_f realdest if File.exist?(realdest)
+ }
+ File.open(realdest, 'wb') {|f|
+ f.write str
+ }
+ File.chmod mode, realdest
+
+ File.open("#{objdir_root()}/InstalledFiles", 'a') {|f|
+ if prefix
+ f.puts realdest.sub(prefix, '')
+ else
+ f.puts realdest
+ end
+ }
+ end
+ end
+
+ def diff?(new_content, path)
+ return true unless File.exist?(path)
+ new_content != File.binread(path)
+ end
+
+ def command(*args)
+ $stderr.puts args.join(' ') if verbose?
+ system(*args) or raise RuntimeError,
+ "system(#{args.map{|a| a.inspect }.join(' ')}) failed"
+ end
+
+ def ruby(*args)
+ command config('rubyprog'), *args
+ end
+
+ def make(task = nil)
+ command(*[config('makeprog'), task].compact)
+ end
+
+ def extdir?(dir)
+ File.exist?("#{dir}/MANIFEST") or File.exist?("#{dir}/extconf.rb")
+ end
+
+ def files_of(dir)
+ Dir.open(dir) {|d|
+ return d.select {|ent| File.file?("#{dir}/#{ent}") }
+ }
+ end
+
+ DIR_REJECT = %w( . .. CVS SCCS RCS CVS.adm .svn )
+
+ def directories_of(dir)
+ Dir.open(dir) {|d|
+ return d.select {|ent| File.dir?("#{dir}/#{ent}") } - DIR_REJECT
+ }
+ end
+
+end
+
+
+# This module requires: #srcdir_root, #objdir_root, #relpath
+module HookScriptAPI
+
+ def get_config(key)
+ @config[key]
+ end
+
+ alias config get_config
+
+ # obsolete: use metaconfig to change configuration
+ def set_config(key, val)
+ @config[key] = val
+ end
+
+ #
+ # srcdir/objdir (works only in the package directory)
+ #
+
+ def curr_srcdir
+ "#{srcdir_root()}/#{relpath()}"
+ end
+
+ def curr_objdir
+ "#{objdir_root()}/#{relpath()}"
+ end
+
+ def srcfile(path)
+ "#{curr_srcdir()}/#{path}"
+ end
+
+ def srcexist?(path)
+ File.exist?(srcfile(path))
+ end
+
+ def srcdirectory?(path)
+ File.dir?(srcfile(path))
+ end
+
+ def srcfile?(path)
+ File.file?(srcfile(path))
+ end
+
+ def srcentries(path = '.')
+ Dir.open("#{curr_srcdir()}/#{path}") {|d|
+ return d.to_a - %w(. ..)
+ }
+ end
+
+ def srcfiles(path = '.')
+ srcentries(path).select {|fname|
+ File.file?(File.join(curr_srcdir(), path, fname))
+ }
+ end
+
+ def srcdirectories(path = '.')
+ srcentries(path).select {|fname|
+ File.dir?(File.join(curr_srcdir(), path, fname))
+ }
+ end
+
+end
+
+
+class ToplevelInstaller
+
+ Version = '3.4.1'
+ Copyright = 'Copyright (c) 2000-2005 Minero Aoki'
+
+ TASKS = [
+ [ 'all', 'do config, setup, then install' ],
+ [ 'config', 'saves your configurations' ],
+ [ 'show', 'shows current configuration' ],
+ [ 'setup', 'compiles ruby extentions and others' ],
+ [ 'install', 'installs files' ],
+ [ 'test', 'run all tests in test/' ],
+ [ 'clean', "does `make clean' for each extention" ],
+ [ 'distclean',"does `make distclean' for each extention" ]
+ ]
+
+ def ToplevelInstaller.invoke
+ config = ConfigTable.new(load_rbconfig())
+ config.load_standard_entries
+ config.load_multipackage_entries if multipackage?
+ config.fixup
+ klass = (multipackage?() ? ToplevelInstallerMulti : ToplevelInstaller)
+ klass.new(File.dirname($0), config).invoke
+ end
+
+ def ToplevelInstaller.multipackage?
+ File.dir?(File.dirname($0) + '/packages')
+ end
+
+ def ToplevelInstaller.load_rbconfig
+ if arg = ARGV.detect {|arg| /\A--rbconfig=/ =~ arg }
+ ARGV.delete(arg)
+ load File.expand_path(arg.split(/=/, 2)[1])
+ $".push 'rbconfig.rb'
+ else
+ require 'rbconfig'
+ end
+ ::Config::CONFIG
+ end
+
+ def initialize(ardir_root, config)
+ @ardir = File.expand_path(ardir_root)
+ @config = config
+ # cache
+ @valid_task_re = nil
+ end
+
+ def config(key)
+ @config[key]
+ end
+
+ def inspect
+ "#<#{self.class} #{__id__()}>"
+ end
+
+ def invoke
+ run_metaconfigs
+ case task = parsearg_global()
+ when nil, 'all'
+ parsearg_config
+ init_installers
+ exec_config
+ exec_setup
+ exec_install
+ else
+ case task
+ when 'config', 'test'
+ ;
+ when 'clean', 'distclean'
+ @config.load_savefile if File.exist?(@config.savefile)
+ else
+ @config.load_savefile
+ end
+ __send__ "parsearg_#{task}"
+ init_installers
+ __send__ "exec_#{task}"
+ end
+ end
+
+ def run_metaconfigs
+ @config.load_script "#{@ardir}/metaconfig"
+ end
+
+ def init_installers
+ @installer = Installer.new(@config, @ardir, File.expand_path('.'))
+ end
+
+ #
+ # Hook Script API bases
+ #
+
+ def srcdir_root
+ @ardir
+ end
+
+ def objdir_root
+ '.'
+ end
+
+ def relpath
+ '.'
+ end
+
+ #
+ # Option Parsing
+ #
+
+ def parsearg_global
+ while arg = ARGV.shift
+ case arg
+ when /\A\w+\z/
+ setup_rb_error "invalid task: #{arg}" unless valid_task?(arg)
+ return arg
+ when '-q', '--quiet'
+ @config.verbose = false
+ when '--verbose'
+ @config.verbose = true
+ when '--help'
+ print_usage $stdout
+ exit 0
+ when '--version'
+ puts "#{File.basename($0)} version #{Version}"
+ exit 0
+ when '--copyright'
+ puts Copyright
+ exit 0
+ else
+ setup_rb_error "unknown global option '#{arg}'"
+ end
+ end
+ nil
+ end
+
+ def valid_task?(t)
+ valid_task_re() =~ t
+ end
+
+ def valid_task_re
+ @valid_task_re ||= /\A(?:#{TASKS.map {|task,desc| task }.join('|')})\z/
+ end
+
+ def parsearg_no_options
+ unless ARGV.empty?
+ task = caller(0).first.slice(%r<`parsearg_(\w+)'>, 1)
+ setup_rb_error "#{task}: unknown options: #{ARGV.join(' ')}"
+ end
+ end
+
+ alias parsearg_show parsearg_no_options
+ alias parsearg_setup parsearg_no_options
+ alias parsearg_test parsearg_no_options
+ alias parsearg_clean parsearg_no_options
+ alias parsearg_distclean parsearg_no_options
+
+ def parsearg_config
+ evalopt = []
+ set = []
+ @config.config_opt = []
+ while i = ARGV.shift
+ if /\A--?\z/ =~ i
+ @config.config_opt = ARGV.dup
+ break
+ end
+ name, value = *@config.parse_opt(i)
+ if @config.value_config?(name)
+ @config[name] = value
+ else
+ evalopt.push [name, value]
+ end
+ set.push name
+ end
+ evalopt.each do |name, value|
+ @config.lookup(name).evaluate value, @config
+ end
+ # Check if configuration is valid
+ set.each do |n|
+ @config[n] if @config.value_config?(n)
+ end
+ end
+
+ def parsearg_install
+ @config.no_harm = false
+ @config.install_prefix = ''
+ while a = ARGV.shift
+ case a
+ when '--no-harm'
+ @config.no_harm = true
+ when /\A--prefix=/
+ path = a.split(/=/, 2)[1]
+ path = File.expand_path(path) unless path[0,1] == '/'
+ @config.install_prefix = path
+ else
+ setup_rb_error "install: unknown option #{a}"
+ end
+ end
+ end
+
+ def print_usage(out)
+ out.puts 'Typical Installation Procedure:'
+ out.puts " $ ruby #{File.basename $0} config"
+ out.puts " $ ruby #{File.basename $0} setup"
+ out.puts " # ruby #{File.basename $0} install (may require root privilege)"
+ out.puts
+ out.puts 'Detailed Usage:'
+ out.puts " ruby #{File.basename $0} <global option>"
+ out.puts " ruby #{File.basename $0} [<global options>] <task> [<task options>]"
+
+ fmt = " %-24s %s\n"
+ out.puts
+ out.puts 'Global options:'
+ out.printf fmt, '-q,--quiet', 'suppress message outputs'
+ out.printf fmt, ' --verbose', 'output messages verbosely'
+ out.printf fmt, ' --help', 'print this message'
+ out.printf fmt, ' --version', 'print version and quit'
+ out.printf fmt, ' --copyright', 'print copyright and quit'
+ out.puts
+ out.puts 'Tasks:'
+ TASKS.each do |name, desc|
+ out.printf fmt, name, desc
+ end
+
+ fmt = " %-24s %s [%s]\n"
+ out.puts
+ out.puts 'Options for CONFIG or ALL:'
+ @config.each do |item|
+ out.printf fmt, item.help_opt, item.description, item.help_default
+ end
+ out.printf fmt, '--rbconfig=path', 'rbconfig.rb to load',"running ruby's"
+ out.puts
+ out.puts 'Options for INSTALL:'
+ out.printf fmt, '--no-harm', 'only display what to do if given', 'off'
+ out.printf fmt, '--prefix=path', 'install path prefix', ''
+ out.puts
+ end
+
+ #
+ # Task Handlers
+ #
+
+ def exec_config
+ @installer.exec_config
+ @config.save # must be final
+ end
+
+ def exec_setup
+ @installer.exec_setup
+ end
+
+ def exec_install
+ @installer.exec_install
+ end
+
+ def exec_test
+ @installer.exec_test
+ end
+
+ def exec_show
+ @config.each do |i|
+ printf "%-20s %s\n", i.name, i.value if i.value?
+ end
+ end
+
+ def exec_clean
+ @installer.exec_clean
+ end
+
+ def exec_distclean
+ @installer.exec_distclean
+ end
+
+end # class ToplevelInstaller
+
+
+class ToplevelInstallerMulti < ToplevelInstaller
+
+ include FileOperations
+
+ def initialize(ardir_root, config)
+ super
+ @packages = directories_of("#{@ardir}/packages")
+ raise 'no package exists' if @packages.empty?
+ @root_installer = Installer.new(@config, @ardir, File.expand_path('.'))
+ end
+
+ def run_metaconfigs
+ @config.load_script "#{@ardir}/metaconfig", self
+ @packages.each do |name|
+ @config.load_script "#{@ardir}/packages/#{name}/metaconfig"
+ end
+ end
+
+ attr_reader :packages
+
+ def packages=(list)
+ raise 'package list is empty' if list.empty?
+ list.each do |name|
+ raise "directory packages/#{name} does not exist"\
+ unless File.dir?("#{@ardir}/packages/#{name}")
+ end
+ @packages = list
+ end
+
+ def init_installers
+ @installers = {}
+ @packages.each do |pack|
+ @installers[pack] = Installer.new(@config,
+ "#{@ardir}/packages/#{pack}",
+ "packages/#{pack}")
+ end
+ with = extract_selection(config('with'))
+ without = extract_selection(config('without'))
+ @selected = @installers.keys.select {|name|
+ (with.empty? or with.include?(name)) \
+ and not without.include?(name)
+ }
+ end
+
+ def extract_selection(list)
+ a = list.split(/,/)
+ a.each do |name|
+ setup_rb_error "no such package: #{name}" unless @installers.key?(name)
+ end
+ a
+ end
+
+ def print_usage(f)
+ super
+ f.puts 'Inluded packages:'
+ f.puts ' ' + @packages.sort.join(' ')
+ f.puts
+ end
+
+ #
+ # Task Handlers
+ #
+
+ def exec_config
+ run_hook 'pre-config'
+ each_selected_installers {|inst| inst.exec_config }
+ run_hook 'post-config'
+ @config.save # must be final
+ end
+
+ def exec_setup
+ run_hook 'pre-setup'
+ each_selected_installers {|inst| inst.exec_setup }
+ run_hook 'post-setup'
+ end
+
+ def exec_install
+ run_hook 'pre-install'
+ each_selected_installers {|inst| inst.exec_install }
+ run_hook 'post-install'
+ end
+
+ def exec_test
+ run_hook 'pre-test'
+ each_selected_installers {|inst| inst.exec_test }
+ run_hook 'post-test'
+ end
+
+ def exec_clean
+ rm_f @config.savefile
+ run_hook 'pre-clean'
+ each_selected_installers {|inst| inst.exec_clean }
+ run_hook 'post-clean'
+ end
+
+ def exec_distclean
+ rm_f @config.savefile
+ run_hook 'pre-distclean'
+ each_selected_installers {|inst| inst.exec_distclean }
+ run_hook 'post-distclean'
+ end
+
+ #
+ # lib
+ #
+
+ def each_selected_installers
+ Dir.mkdir 'packages' unless File.dir?('packages')
+ @selected.each do |pack|
+ $stderr.puts "Processing the package `#{pack}' ..." if verbose?
+ Dir.mkdir "packages/#{pack}" unless File.dir?("packages/#{pack}")
+ Dir.chdir "packages/#{pack}"
+ yield @installers[pack]
+ Dir.chdir '../..'
+ end
+ end
+
+ def run_hook(id)
+ @root_installer.run_hook id
+ end
+
+ # module FileOperations requires this
+ def verbose?
+ @config.verbose?
+ end
+
+ # module FileOperations requires this
+ def no_harm?
+ @config.no_harm?
+ end
+
+end # class ToplevelInstallerMulti
+
+
+class Installer
+
+ FILETYPES = %w( bin lib ext data conf man )
+
+ include FileOperations
+ include HookScriptAPI
+
+ def initialize(config, srcroot, objroot)
+ @config = config
+ @srcdir = File.expand_path(srcroot)
+ @objdir = File.expand_path(objroot)
+ @currdir = '.'
+ end
+
+ def inspect
+ "#<#{self.class} #{File.basename(@srcdir)}>"
+ end
+
+ def noop(rel)
+ end
+
+ #
+ # Hook Script API base methods
+ #
+
+ def srcdir_root
+ @srcdir
+ end
+
+ def objdir_root
+ @objdir
+ end
+
+ def relpath
+ @currdir
+ end
+
+ #
+ # Config Access
+ #
+
+ # module FileOperations requires this
+ def verbose?
+ @config.verbose?
+ end
+
+ # module FileOperations requires this
+ def no_harm?
+ @config.no_harm?
+ end
+
+ def verbose_off
+ begin
+ save, @config.verbose = @config.verbose?, false
+ yield
+ ensure
+ @config.verbose = save
+ end
+ end
+
+ #
+ # TASK config
+ #
+
+ def exec_config
+ exec_task_traverse 'config'
+ end
+
+ alias config_dir_bin noop
+ alias config_dir_lib noop
+
+ def config_dir_ext(rel)
+ extconf if extdir?(curr_srcdir())
+ end
+
+ alias config_dir_data noop
+ alias config_dir_conf noop
+ alias config_dir_man noop
+
+ def extconf
+ ruby "#{curr_srcdir()}/extconf.rb", *@config.config_opt
+ end
+
+ #
+ # TASK setup
+ #
+
+ def exec_setup
+ exec_task_traverse 'setup'
+ end
+
+ def setup_dir_bin(rel)
+ files_of(curr_srcdir()).each do |fname|
+ update_shebang_line "#{curr_srcdir()}/#{fname}"
+ end
+ end
+
+ alias setup_dir_lib noop
+
+ def setup_dir_ext(rel)
+ make if extdir?(curr_srcdir())
+ end
+
+ alias setup_dir_data noop
+ alias setup_dir_conf noop
+ alias setup_dir_man noop
+
+ def update_shebang_line(path)
+ return if no_harm?
+ return if config('shebang') == 'never'
+ old = Shebang.load(path)
+ if old
+ $stderr.puts "warning: #{path}: Shebang line includes too many args. It is not portable and your program may not work." if old.args.size > 1
+ new = new_shebang(old)
+ return if new.to_s == old.to_s
+ else
+ return unless config('shebang') == 'all'
+ new = Shebang.new(config('rubypath'))
+ end
+ $stderr.puts "updating shebang: #{File.basename(path)}" if verbose?
+ open_atomic_writer(path) {|output|
+ File.open(path, 'rb') {|f|
+ f.gets if old # discard
+ output.puts new.to_s
+ output.print f.read
+ }
+ }
+ end
+
+ def new_shebang(old)
+ if /\Aruby/ =~ File.basename(old.cmd)
+ Shebang.new(config('rubypath'), old.args)
+ elsif File.basename(old.cmd) == 'env' and old.args.first == 'ruby'
+ Shebang.new(config('rubypath'), old.args[1..-1])
+ else
+ return old unless config('shebang') == 'all'
+ Shebang.new(config('rubypath'))
+ end
+ end
+
+ def open_atomic_writer(path, &block)
+ tmpfile = File.basename(path) + '.tmp'
+ begin
+ File.open(tmpfile, 'wb', &block)
+ File.rename tmpfile, File.basename(path)
+ ensure
+ File.unlink tmpfile if File.exist?(tmpfile)
+ end
+ end
+
+ class Shebang
+ def Shebang.load(path)
+ line = nil
+ File.open(path) {|f|
+ line = f.gets
+ }
+ return nil unless /\A#!/ =~ line
+ parse(line)
+ end
+
+ def Shebang.parse(line)
+ cmd, *args = *line.strip.sub(/\A\#!/, '').split(' ')
+ new(cmd, args)
+ end
+
+ def initialize(cmd, args = [])
+ @cmd = cmd
+ @args = args
+ end
+
+ attr_reader :cmd
+ attr_reader :args
+
+ def to_s
+ "#! #{@cmd}" + (@args.empty? ? '' : " #{@args.join(' ')}")
+ end
+ end
+
+ #
+ # TASK install
+ #
+
+ def exec_install
+ rm_f 'InstalledFiles'
+ exec_task_traverse 'install'
+ end
+
+ def install_dir_bin(rel)
+ install_files targetfiles(), "#{config('bindir')}/#{rel}", 0755
+ end
+
+ def install_dir_lib(rel)
+ install_files libfiles(), "#{config('rbdir')}/#{rel}", 0644
+ end
+
+ def install_dir_ext(rel)
+ return unless extdir?(curr_srcdir())
+ install_files rubyextentions('.'),
+ "#{config('sodir')}/#{File.dirname(rel)}",
+ 0555
+ end
+
+ def install_dir_data(rel)
+ install_files targetfiles(), "#{config('datadir')}/#{rel}", 0644
+ end
+
+ def install_dir_conf(rel)
+ # FIXME: should not remove current config files
+ # (rename previous file to .old/.org)
+ install_files targetfiles(), "#{config('sysconfdir')}/#{rel}", 0644
+ end
+
+ def install_dir_man(rel)
+ install_files targetfiles(), "#{config('mandir')}/#{rel}", 0644
+ end
+
+ def install_files(list, dest, mode)
+ mkdir_p dest, @config.install_prefix
+ list.each do |fname|
+ install fname, dest, mode, @config.install_prefix
+ end
+ end
+
+ def libfiles
+ glob_reject(%w(*.y *.output), targetfiles())
+ end
+
+ def rubyextentions(dir)
+ ents = glob_select("*.#{@config.dllext}", targetfiles())
+ if ents.empty?
+ setup_rb_error "no ruby extention exists: 'ruby #{$0} setup' first"
+ end
+ ents
+ end
+
+ def targetfiles
+ mapdir(existfiles() - hookfiles())
+ end
+
+ def mapdir(ents)
+ ents.map {|ent|
+ if File.exist?(ent)
+ then ent # objdir
+ else "#{curr_srcdir()}/#{ent}" # srcdir
+ end
+ }
+ end
+
+ # picked up many entries from cvs-1.11.1/src/ignore.c
+ JUNK_FILES = %w(
+ core RCSLOG tags TAGS .make.state
+ .nse_depinfo #* .#* cvslog.* ,* .del-* *.olb
+ *~ *.old *.bak *.BAK *.orig *.rej _$* *$
+
+ *.org *.in .*
+ )
+
+ def existfiles
+ glob_reject(JUNK_FILES, (files_of(curr_srcdir()) | files_of('.')))
+ end
+
+ def hookfiles
+ %w( pre-%s post-%s pre-%s.rb post-%s.rb ).map {|fmt|
+ %w( config setup install clean ).map {|t| sprintf(fmt, t) }
+ }.flatten
+ end
+
+ def glob_select(pat, ents)
+ re = globs2re([pat])
+ ents.select {|ent| re =~ ent }
+ end
+
+ def glob_reject(pats, ents)
+ re = globs2re(pats)
+ ents.reject {|ent| re =~ ent }
+ end
+
+ GLOB2REGEX = {
+ '.' => '\.',
+ '$' => '\$',
+ '#' => '\#',
+ '*' => '.*'
+ }
+
+ def globs2re(pats)
+ /\A(?:#{
+ pats.map {|pat| pat.gsub(/[\.\$\#\*]/) {|ch| GLOB2REGEX[ch] } }.join('|')
+ })\z/
+ end
+
+ #
+ # TASK test
+ #
+
+ TESTDIR = 'test'
+
+ def exec_test
+ unless File.directory?('test')
+ $stderr.puts 'no test in this package' if verbose?
+ return
+ end
+ $stderr.puts 'Running tests...' if verbose?
+ begin
+ require 'test/unit'
+ rescue LoadError
+ setup_rb_error 'test/unit cannot loaded. You need Ruby 1.8 or later to invoke this task.'
+ end
+ runner = Test::Unit::AutoRunner.new(true)
+ runner.to_run << TESTDIR
+ runner.run
+ end
+
+ #
+ # TASK clean
+ #
+
+ def exec_clean
+ exec_task_traverse 'clean'
+ rm_f @config.savefile
+ rm_f 'InstalledFiles'
+ end
+
+ alias clean_dir_bin noop
+ alias clean_dir_lib noop
+ alias clean_dir_data noop
+ alias clean_dir_conf noop
+ alias clean_dir_man noop
+
+ def clean_dir_ext(rel)
+ return unless extdir?(curr_srcdir())
+ make 'clean' if File.file?('Makefile')
+ end
+
+ #
+ # TASK distclean
+ #
+
+ def exec_distclean
+ exec_task_traverse 'distclean'
+ rm_f @config.savefile
+ rm_f 'InstalledFiles'
+ end
+
+ alias distclean_dir_bin noop
+ alias distclean_dir_lib noop
+
+ def distclean_dir_ext(rel)
+ return unless extdir?(curr_srcdir())
+ make 'distclean' if File.file?('Makefile')
+ end
+
+ alias distclean_dir_data noop
+ alias distclean_dir_conf noop
+ alias distclean_dir_man noop
+
+ #
+ # Traversing
+ #
+
+ def exec_task_traverse(task)
+ run_hook "pre-#{task}"
+ FILETYPES.each do |type|
+ if type == 'ext' and config('without-ext') == 'yes'
+ $stderr.puts 'skipping ext/* by user option' if verbose?
+ next
+ end
+ traverse task, type, "#{task}_dir_#{type}"
+ end
+ run_hook "post-#{task}"
+ end
+
+ def traverse(task, rel, mid)
+ dive_into(rel) {
+ run_hook "pre-#{task}"
+ __send__ mid, rel.sub(%r[\A.*?(?:/|\z)], '')
+ directories_of(curr_srcdir()).each do |d|
+ traverse task, "#{rel}/#{d}", mid
+ end
+ run_hook "post-#{task}"
+ }
+ end
+
+ def dive_into(rel)
+ return unless File.dir?("#{@srcdir}/#{rel}")
+
+ dir = File.basename(rel)
+ Dir.mkdir dir unless File.dir?(dir)
+ prevdir = Dir.pwd
+ Dir.chdir dir
+ $stderr.puts '---> ' + rel if verbose?
+ @currdir = rel
+ yield
+ Dir.chdir prevdir
+ $stderr.puts '<--- ' + rel if verbose?
+ @currdir = File.dirname(rel)
+ end
+
+ def run_hook(id)
+ path = [ "#{curr_srcdir()}/#{id}",
+ "#{curr_srcdir()}/#{id}.rb" ].detect {|cand| File.file?(cand) }
+ return unless path
+ begin
+ instance_eval File.read(path), path, 1
+ rescue
+ raise if $DEBUG
+ setup_rb_error "hook #{path} failed:\n" + $!.message
+ end
+ end
+
+end # class Installer
+
+
+class SetupError < StandardError; end
+
+def setup_rb_error(msg)
+ raise SetupError, msg
+end
+
+if $0 == __FILE__
+ begin
+ ToplevelInstaller.invoke
+ rescue SetupError
+ raise if $DEBUG
+ $stderr.puts $!.message
+ $stderr.puts "Try 'ruby #{$0} --help' for detailed usage."
+ exit 1
+ end
+end
diff --git a/lib/rb/spec/ThriftSpec.thrift b/lib/rb/spec/ThriftSpec.thrift
new file mode 100644
index 000000000..fe5a8aae4
--- /dev/null
+++ b/lib/rb/spec/ThriftSpec.thrift
@@ -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.
+ */
+
+#
+# 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"
+}
+
+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
+}
+
+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)
+}
diff --git a/lib/rb/spec/base_protocol_spec.rb b/lib/rb/spec/base_protocol_spec.rb
new file mode 100644
index 000000000..efb16d8c8
--- /dev/null
+++ b/lib/rb/spec/base_protocol_spec.rb
@@ -0,0 +1,160 @@
+#
+# 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_helper'
+
+class ThriftBaseProtocolSpec < Spec::ExampleGroup
+ include Thrift
+
+ before(:each) do
+ @trans = mock("MockTransport")
+ @prot = BaseProtocol.new(@trans)
+ end
+
+ describe BaseProtocol do
+ # most of the methods are stubs, so we can ignore them
+
+ it "should make trans accessible" do
+ @prot.trans.should eql(@trans)
+ end
+
+ it "should write out a field nicely" do
+ @prot.should_receive(:write_field_begin).with('field', 'type', 'fid').ordered
+ @prot.should_receive(:write_type).with('type', 'value').ordered
+ @prot.should_receive(:write_field_end).ordered
+ @prot.write_field('field', 'type', 'fid', 'value')
+ end
+
+ it "should write out the different types" do
+ @prot.should_receive(:write_bool).with('bool').ordered
+ @prot.should_receive(:write_byte).with('byte').ordered
+ @prot.should_receive(:write_double).with('double').ordered
+ @prot.should_receive(:write_i16).with('i16').ordered
+ @prot.should_receive(:write_i32).with('i32').ordered
+ @prot.should_receive(:write_i64).with('i64').ordered
+ @prot.should_receive(:write_string).with('string').ordered
+ struct = mock('Struct')
+ struct.should_receive(:write).with(@prot).ordered
+ @prot.write_type(Types::BOOL, 'bool')
+ @prot.write_type(Types::BYTE, 'byte')
+ @prot.write_type(Types::DOUBLE, 'double')
+ @prot.write_type(Types::I16, 'i16')
+ @prot.write_type(Types::I32, 'i32')
+ @prot.write_type(Types::I64, 'i64')
+ @prot.write_type(Types::STRING, 'string')
+ @prot.write_type(Types::STRUCT, struct)
+ # all other types are not implemented
+ [Types::STOP, Types::VOID, Types::MAP, Types::SET, Types::LIST].each do |type|
+ lambda { @prot.write_type(type, type.to_s) }.should raise_error(NotImplementedError)
+ end
+ end
+
+ it "should read the different types" do
+ @prot.should_receive(:read_bool).ordered
+ @prot.should_receive(:read_byte).ordered
+ @prot.should_receive(:read_i16).ordered
+ @prot.should_receive(:read_i32).ordered
+ @prot.should_receive(:read_i64).ordered
+ @prot.should_receive(:read_double).ordered
+ @prot.should_receive(:read_string).ordered
+ @prot.read_type(Types::BOOL)
+ @prot.read_type(Types::BYTE)
+ @prot.read_type(Types::I16)
+ @prot.read_type(Types::I32)
+ @prot.read_type(Types::I64)
+ @prot.read_type(Types::DOUBLE)
+ @prot.read_type(Types::STRING)
+ # all other types are not implemented
+ [Types::STOP, Types::VOID, Types::MAP, Types::SET, Types::LIST].each do |type|
+ lambda { @prot.read_type(type) }.should raise_error(NotImplementedError)
+ end
+ end
+
+ it "should skip the basic types" do
+ @prot.should_receive(:read_bool).ordered
+ @prot.should_receive(:read_byte).ordered
+ @prot.should_receive(:read_i16).ordered
+ @prot.should_receive(:read_i32).ordered
+ @prot.should_receive(:read_i64).ordered
+ @prot.should_receive(:read_double).ordered
+ @prot.should_receive(:read_string).ordered
+ @prot.skip(Types::BOOL)
+ @prot.skip(Types::BYTE)
+ @prot.skip(Types::I16)
+ @prot.skip(Types::I32)
+ @prot.skip(Types::I64)
+ @prot.skip(Types::DOUBLE)
+ @prot.skip(Types::STRING)
+ @prot.skip(Types::STOP) # should do absolutely nothing
+ end
+
+ it "should skip structs" do
+ real_skip = @prot.method(:skip)
+ @prot.should_receive(:read_struct_begin).ordered
+ @prot.should_receive(:read_field_begin).exactly(4).times.and_return(
+ ['field 1', Types::STRING, 1],
+ ['field 2', Types::I32, 2],
+ ['field 3', Types::MAP, 3],
+ [nil, Types::STOP, 0]
+ )
+ @prot.should_receive(:read_field_end).exactly(3).times
+ @prot.should_receive(:read_string).exactly(3).times
+ @prot.should_receive(:read_i32).ordered
+ @prot.should_receive(:read_map_begin).ordered.and_return([Types::STRING, Types::STRING, 1])
+ # @prot.should_receive(:read_string).exactly(2).times
+ @prot.should_receive(:read_map_end).ordered
+ @prot.should_receive(:read_struct_end).ordered
+ real_skip.call(Types::STRUCT)
+ end
+
+ it "should skip maps" do
+ real_skip = @prot.method(:skip)
+ @prot.should_receive(:read_map_begin).ordered.and_return([Types::STRING, Types::STRUCT, 1])
+ @prot.should_receive(:read_string).ordered
+ @prot.should_receive(:read_struct_begin).ordered.and_return(["some_struct"])
+ @prot.should_receive(:read_field_begin).ordered.and_return([nil, Types::STOP, nil]);
+ @prot.should_receive(:read_struct_end).ordered
+ @prot.should_receive(:read_map_end).ordered
+ real_skip.call(Types::MAP)
+ end
+
+ it "should skip sets" do
+ real_skip = @prot.method(:skip)
+ @prot.should_receive(:read_set_begin).ordered.and_return([Types::I64, 9])
+ @prot.should_receive(:read_i64).ordered.exactly(9).times
+ @prot.should_receive(:read_set_end)
+ real_skip.call(Types::SET)
+ end
+
+ it "should skip lists" do
+ real_skip = @prot.method(:skip)
+ @prot.should_receive(:read_list_begin).ordered.and_return([Types::DOUBLE, 11])
+ @prot.should_receive(:read_double).ordered.exactly(11).times
+ @prot.should_receive(:read_list_end)
+ real_skip.call(Types::LIST)
+ end
+ end
+
+ describe BaseProtocolFactory do
+ it "should raise NotImplementedError" do
+ # returning nil since Protocol is just an abstract class
+ lambda {BaseProtocolFactory.new.get_protocol(mock("MockTransport"))}.should raise_error(NotImplementedError)
+ end
+ end
+end
diff --git a/lib/rb/spec/base_transport_spec.rb b/lib/rb/spec/base_transport_spec.rb
new file mode 100644
index 000000000..71897759a
--- /dev/null
+++ b/lib/rb/spec/base_transport_spec.rb
@@ -0,0 +1,344 @@
+#
+# 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_helper'
+
+class ThriftBaseTransportSpec < Spec::ExampleGroup
+ include Thrift
+
+ describe TransportException do
+ it "should make type accessible" do
+ exc = TransportException.new(TransportException::ALREADY_OPEN, "msg")
+ exc.type.should == TransportException::ALREADY_OPEN
+ exc.message.should == "msg"
+ end
+ end
+
+ describe BaseTransport do
+ it "should read the specified size" do
+ transport = BaseTransport.new
+ transport.should_receive(:read).with(40).ordered.and_return("10 letters")
+ transport.should_receive(:read).with(30).ordered.and_return("fifteen letters")
+ transport.should_receive(:read).with(15).ordered.and_return("more characters")
+ transport.read_all(40).should == "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|
+ BaseTransport.method_defined?(sym).should be_true
+ end
+ end
+
+ it "should alias << to write" do
+ BaseTransport.instance_method(:<<).should == BaseTransport.instance_method(:write)
+ end
+ end
+
+ describe BaseServerTransport do
+ it "should stub out its methods" do
+ [:listen, :accept, :close].each do |sym|
+ BaseServerTransport.method_defined?(sym).should be_true
+ end
+ end
+ end
+
+ describe BaseTransportFactory do
+ it "should return the transport it's given" do
+ transport = mock("Transport")
+ BaseTransportFactory.new.get_transport(transport).should eql(transport)
+ end
+ end
+
+ describe BufferedTransport do
+ it "should pass through everything but write/flush/read" do
+ trans = mock("Transport")
+ trans.should_receive(:open?).ordered.and_return("+ open?")
+ trans.should_receive(:open).ordered.and_return("+ open")
+ trans.should_receive(:flush).ordered # from the close
+ trans.should_receive(:close).ordered.and_return("+ close")
+ btrans = BufferedTransport.new(trans)
+ btrans.open?.should == "+ open?"
+ btrans.open.should == "+ open"
+ btrans.close.should == "+ close"
+ end
+
+ it "should buffer reads in chunks of #{BufferedTransport::DEFAULT_BUFFER}" do
+ trans = mock("Transport")
+ trans.should_receive(:read).with(BufferedTransport::DEFAULT_BUFFER).and_return("lorum ipsum dolor emet")
+ btrans = BufferedTransport.new(trans)
+ btrans.read(6).should == "lorum "
+ btrans.read(6).should == "ipsum "
+ btrans.read(6).should == "dolor "
+ btrans.read(6).should == "emet"
+ end
+
+ it "should buffer writes and send them on flush" do
+ trans = mock("Transport")
+ btrans = BufferedTransport.new(trans)
+ btrans.write("one/")
+ btrans.write("two/")
+ btrans.write("three/")
+ trans.should_receive(:write).with("one/two/three/").ordered
+ trans.should_receive(:flush).ordered
+ btrans.flush
+ end
+
+ it "should only send buffered data once" do
+ trans = mock("Transport")
+ btrans = BufferedTransport.new(trans)
+ btrans.write("one/")
+ btrans.write("two/")
+ btrans.write("three/")
+ trans.should_receive(:write).with("one/two/three/")
+ trans.stub!(:flush)
+ btrans.flush
+ # Nothing to flush with no data
+ btrans.flush
+ end
+
+ it "should flush on close" do
+ trans = mock("Transport")
+ trans.should_receive(:close)
+ btrans = BufferedTransport.new(trans)
+ btrans.should_receive(:flush)
+ btrans.close
+ end
+
+ it "should not write to socket if there's no data" do
+ trans = mock("Transport")
+ trans.should_receive(:flush)
+ btrans = BufferedTransport.new(trans)
+ btrans.flush
+ end
+ end
+
+ describe BufferedTransportFactory do
+ it "should wrap the given transport in a BufferedTransport" do
+ trans = mock("Transport")
+ btrans = mock("BufferedTransport")
+ BufferedTransport.should_receive(:new).with(trans).and_return(btrans)
+ BufferedTransportFactory.new.get_transport(trans).should == btrans
+ end
+ end
+
+ describe FramedTransport do
+ before(:each) do
+ @trans = mock("Transport")
+ end
+
+ it "should pass through open?/open/close" do
+ ftrans = FramedTransport.new(@trans)
+ @trans.should_receive(:open?).ordered.and_return("+ open?")
+ @trans.should_receive(:open).ordered.and_return("+ open")
+ @trans.should_receive(:close).ordered.and_return("+ close")
+ ftrans.open?.should == "+ open?"
+ ftrans.open.should == "+ open"
+ ftrans.close.should == "+ close"
+ end
+
+ it "should pass through read when read is turned off" do
+ ftrans = FramedTransport.new(@trans, false, true)
+ @trans.should_receive(:read).with(17).ordered.and_return("+ read")
+ ftrans.read(17).should == "+ read"
+ end
+
+ it "should pass through write/flush when write is turned off" do
+ ftrans = FramedTransport.new(@trans, true, false)
+ @trans.should_receive(:write).with("foo").ordered.and_return("+ write")
+ @trans.should_receive(:flush).ordered.and_return("+ flush")
+ ftrans.write("foo").should == "+ write"
+ ftrans.flush.should == "+ flush"
+ end
+
+ it "should return a full frame if asked for >= the frame's length" do
+ frame = "this is a frame"
+ @trans.should_receive(:read_all).with(4).and_return("\000\000\000\017")
+ @trans.should_receive(:read_all).with(frame.length).and_return(frame)
+ FramedTransport.new(@trans).read(frame.length + 10).should == frame
+ end
+
+ it "should return slices of the frame when asked for < the frame's length" do
+ frame = "this is a frame"
+ @trans.should_receive(:read_all).with(4).and_return("\000\000\000\017")
+ @trans.should_receive(:read_all).with(frame.length).and_return(frame)
+ ftrans = FramedTransport.new(@trans)
+ ftrans.read(4).should == "this"
+ ftrans.read(4).should == " is "
+ ftrans.read(16).should == "a frame"
+ end
+
+ it "should return nothing if asked for <= 0" do
+ FramedTransport.new(@trans).read(-2).should == ""
+ end
+
+ it "should pull a new frame when the first is exhausted" do
+ frame = "this is a frame"
+ frame2 = "yet another frame"
+ @trans.should_receive(:read_all).with(4).and_return("\000\000\000\017", "\000\000\000\021")
+ @trans.should_receive(:read_all).with(frame.length).and_return(frame)
+ @trans.should_receive(:read_all).with(frame2.length).and_return(frame2)
+ ftrans = FramedTransport.new(@trans)
+ ftrans.read(4).should == "this"
+ ftrans.read(8).should == " is a fr"
+ ftrans.read(6).should == "ame"
+ ftrans.read(4).should == "yet "
+ ftrans.read(16).should == "another frame"
+ end
+
+ it "should buffer writes" do
+ ftrans = FramedTransport.new(@trans)
+ @trans.should_not_receive(:write)
+ ftrans.write("foo")
+ ftrans.write("bar")
+ ftrans.write("this is a frame")
+ end
+
+ it "should write slices of the buffer" do
+ ftrans = FramedTransport.new(@trans)
+ ftrans.write("foobar", 3)
+ ftrans.write("barfoo", 1)
+ @trans.stub!(:flush)
+ @trans.should_receive(:write).with("\000\000\000\004foob")
+ ftrans.flush
+ end
+
+ it "should flush frames with a 4-byte header" do
+ ftrans = FramedTransport.new(@trans)
+ @trans.should_receive(:write).with("\000\000\000\035one/two/three/this is a frame").ordered
+ @trans.should_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 = FramedTransport.new(@trans)
+ @trans.should_receive(:write).with("\000\000\000\007foo/bar")
+ @trans.stub!(:flush)
+ ftrans.write("foo")
+ ftrans.write("/bar")
+ ftrans.flush
+ @trans.should_receive(:write).with("\000\000\000\000")
+ ftrans.flush
+ end
+ end
+
+ describe FramedTransportFactory do
+ it "should wrap the given transport in a FramedTransport" do
+ trans = mock("Transport")
+ FramedTransport.should_receive(:new).with(trans)
+ FramedTransportFactory.new.get_transport(trans)
+ end
+ end
+
+ describe MemoryBufferTransport do
+ before(:each) do
+ @buffer = MemoryBufferTransport.new
+ end
+
+ it "should accept a buffer on input and use it directly" do
+ s = "this is a test"
+ @buffer = MemoryBufferTransport.new(s)
+ @buffer.read(4).should == "this"
+ s.slice!(-4..-1)
+ @buffer.read(@buffer.available).should == " is a "
+ end
+
+ it "should always remain open" do
+ @buffer.should be_open
+ @buffer.close
+ @buffer.should be_open
+ end
+
+ it "should respond to peek and available" do
+ @buffer.write "some data"
+ @buffer.peek.should be_true
+ @buffer.available.should == 9
+ @buffer.read(4)
+ @buffer.peek.should be_true
+ @buffer.available.should == 5
+ @buffer.read(16)
+ @buffer.peek.should be_false
+ @buffer.available.should == 0
+ end
+
+ it "should be able to reset the buffer" do
+ @buffer.write "test data"
+ @buffer.reset_buffer("foobar")
+ @buffer.available.should == 6
+ @buffer.read(10).should == "foobar"
+ @buffer.reset_buffer
+ @buffer.available.should == 0
+ end
+
+ it "should copy the given string whne resetting the buffer" do
+ s = "this is a test"
+ @buffer.reset_buffer(s)
+ @buffer.available.should == 14
+ @buffer.read(10)
+ @buffer.available.should == 4
+ s.should == "this is a test"
+ end
+
+ it "should return from read what was given in write" do
+ @buffer.write "test data"
+ @buffer.read(4).should == "test"
+ @buffer.read(10).should == " data"
+ @buffer.read(10).should == ""
+ @buffer.write "foo"
+ @buffer.write " bar"
+ @buffer.read(10).should == "foo bar"
+ end
+ end
+
+ describe IOStreamTransport do
+ before(:each) do
+ @input = mock("Input", :closed? => false)
+ @output = mock("Output", :closed? => false)
+ @trans = IOStreamTransport.new(@input, @output)
+ end
+
+ it "should be open as long as both input or output are open" do
+ @trans.should be_open
+ @input.stub!(:closed?).and_return(true)
+ @trans.should be_open
+ @input.stub!(:closed?).and_return(false)
+ @output.stub!(:closed?).and_return(true)
+ @trans.should be_open
+ @input.stub!(:closed?).and_return(true)
+ @trans.should_not be_open
+ end
+
+ it "should pass through read/write to input/output" do
+ @input.should_receive(:read).with(17).and_return("+ read")
+ @output.should_receive(:write).with("foobar").and_return("+ write")
+ @trans.read(17).should == "+ read"
+ @trans.write("foobar").should == "+ write"
+ end
+
+ it "should close both input and output when closed" do
+ @input.should_receive(:close)
+ @output.should_receive(:close)
+ @trans.close
+ end
+ end
+end
diff --git a/lib/rb/spec/binary_protocol_accelerated_spec.rb b/lib/rb/spec/binary_protocol_accelerated_spec.rb
new file mode 100644
index 000000000..0306cf5f2
--- /dev/null
+++ b/lib/rb/spec/binary_protocol_accelerated_spec.rb
@@ -0,0 +1,42 @@
+#
+# 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_helper'
+require File.dirname(__FILE__) + '/binary_protocol_spec_shared'
+require File.dirname(__FILE__) + '/gen-rb/thrift_spec_types'
+
+class ThriftBinaryProtocolAcceleratedSpec < Spec::ExampleGroup
+ include Thrift
+
+ describe Thrift::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
+ BinaryProtocolAccelerated
+ end
+ end
+
+ describe BinaryProtocolAcceleratedFactory do
+ it "should create a BinaryProtocolAccelerated" do
+ BinaryProtocolAcceleratedFactory.new.get_protocol(mock("MockTransport")).should be_instance_of(BinaryProtocolAccelerated)
+ end
+ end
+end
diff --git a/lib/rb/spec/binary_protocol_spec.rb b/lib/rb/spec/binary_protocol_spec.rb
new file mode 100644
index 000000000..0abccb892
--- /dev/null
+++ b/lib/rb/spec/binary_protocol_spec.rb
@@ -0,0 +1,63 @@
+#
+# 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_helper'
+require File.dirname(__FILE__) + '/binary_protocol_spec_shared'
+
+class ThriftBinaryProtocolSpec < Spec::ExampleGroup
+ include Thrift
+
+ describe BinaryProtocol do
+ it_should_behave_like 'a binary protocol'
+
+ def protocol_class
+ BinaryProtocol
+ end
+
+ it "should read a message header" do
+ @trans.should_receive(:read_all).exactly(2).times.and_return(
+ [protocol_class.const_get(:VERSION_1) | Thrift::MessageTypes::REPLY].pack('N'),
+ [42].pack('N')
+ )
+ @prot.should_receive(:read_string).and_return('testMessage')
+ @prot.read_message_begin.should == ['testMessage', Thrift::MessageTypes::REPLY, 42]
+ end
+
+ it "should raise an exception if the message header has the wrong version" do
+ @prot.should_receive(:read_i32).and_return(-1)
+ lambda { @prot.read_message_begin }.should 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
+ @prot.should_receive(:read_i32).and_return(42)
+ @prot.should_receive(:strict_read).and_return(true)
+ lambda { @prot.read_message_begin }.should raise_error(Thrift::ProtocolException, 'No version identifier, old protocol client?') do |e|
+ e.type == Thrift::ProtocolException::BAD_VERSION
+ end
+ end
+ end
+
+ describe BinaryProtocolFactory do
+ it "should create a BinaryProtocol" do
+ BinaryProtocolFactory.new.get_protocol(mock("MockTransport")).should be_instance_of(BinaryProtocol)
+ end
+ end
+end
diff --git a/lib/rb/spec/binary_protocol_spec_shared.rb b/lib/rb/spec/binary_protocol_spec_shared.rb
new file mode 100644
index 000000000..c6608e01a
--- /dev/null
+++ b/lib/rb/spec/binary_protocol_spec_shared.rb
@@ -0,0 +1,375 @@
+#
+# 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_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
+ protocol_class.const_get(:VERSION_MASK).should == 0xffff0000
+ protocol_class.const_get(:VERSION_1).should == 0x80010000
+ protocol_class.const_get(:TYPE_MASK).should == 0x000000ff
+ end
+
+ it "should make strict_read readable" do
+ @prot.strict_read.should eql(true)
+ end
+
+ it "should make strict_write readable" do
+ @prot.strict_write.should eql(true)
+ end
+
+ it "should write the message header" do
+ @prot.write_message_begin('testMessage', Thrift::MessageTypes::CALL, 17)
+ @trans.read(1000).should == [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)
+ @trans.read(1000).should == "\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)
+ @trans.read(1000).should == "\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)
+ @trans.read(1000).should == [Thrift::Types::DOUBLE, 3].pack("cn")
+ end
+
+ # field footer is a noop
+
+ it "should write the STOP field" do
+ @prot.write_field_stop
+ @trans.read(1).should == "\000"
+ end
+
+ it "should write the map header" do
+ @prot.write_map_begin(Thrift::Types::STRING, Thrift::Types::LIST, 17)
+ @trans.read(1000).should == [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)
+ @trans.read(1000).should == [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)
+ @trans.read(1000).should == [Thrift::Types::I16, 42].pack("cN")
+ end
+
+ it "should write a bool" do
+ @prot.write_bool(true)
+ @prot.write_bool(false)
+ @trans.read(1000).should == "\001\000"
+ end
+
+ it "should treat a nil bool as false" do
+ @prot.write_bool(nil)
+ @trans.read(1).should == "\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)
+ @trans.read(1).should == [i].pack('c')
+ end
+ # handing it numbers out of signed range should clip
+ @trans.rspec_verify
+ (128..255).each do |i|
+ @prot.write_byte(i)
+ @trans.read(1).should == [i].pack('c')
+ end
+ # and lastly, a Bignum is going to error out
+ lambda { @prot.write_byte(2**65) }.should raise_error(RangeError)
+ end
+
+ it "should error gracefully when trying to write a nil byte" do
+ lambda { @prot.write_byte(nil) }.should 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)
+
+ @trans.read(1000).should == "\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
+ lambda { @prot.write_i16(nil) }.should 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
+ @trans.read(1000).should == "\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|
+ lambda { @prot.write_i32(i) }.should raise_error(RangeError)
+ end
+ end
+
+ it "should error gracefully when trying to write a nil i32" do
+ lambda { @prot.write_i32(nil) }.should 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
+ @trans.read(1000).should == ["\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("")
+ lambda { @prot.write_i64(2 ** 65 + 5) }.should raise_error(RangeError)
+ end
+
+ it "should error gracefully when trying to write a nil i64" do
+ lambda { @prot.write_i64(nil) }.should 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)
+ @trans.read(1000).should == [f].pack("G")
+ end
+ end
+
+ it "should error gracefully when trying to write a nil double" do
+ lambda { @prot.write_double(nil) }.should raise_error
+ end
+
+ it "should write a string" do
+ str = "hello world"
+ @prot.write_string(str)
+ @trans.read(1000).should == [str.size].pack("N") + str
+ end
+
+ it "should error gracefully when trying to write a nil string" do
+ lambda { @prot.write_string(nil) }.should 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)
+ @trans.read(1000).should == "\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)
+ @trans.read(1000).should == "\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"))
+ @prot.read_field_begin.should == [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"));
+ @prot.read_field_begin.should == [nil, Thrift::Types::STOP, 0]
+ end
+
+ it "should read a map header" do
+ @trans.write([Thrift::Types::DOUBLE, Thrift::Types::I64, 42].pack("ccN"))
+ @prot.read_map_begin.should == [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"))
+ @prot.read_list_begin.should == [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"))
+ @prot.read_set_begin.should == [Thrift::Types::STRING, 17]
+ end
+
+ # set footer is a noop
+
+ it "should read a bool" do
+ @trans.write("\001\000");
+ @prot.read_bool.should == true
+ @prot.read_bool.should == false
+ end
+
+ it "should read a byte" do
+ [-128, -57, -3, 0, 17, 24, 127].each do |i|
+ @trans.write([i].pack("c"))
+ @prot.read_byte.should == 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"));
+ @prot.read_i16.should == 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"))
+ @prot.read_i32.should == 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"))
+ @prot.read_i64.should == 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"));
+ @prot.read_double.should == f
+ end
+ end
+
+ it "should read a string" do
+ str = "hello world"
+ @trans.write([str.size].pack("N") + str)
+ @prot.read_string.should == str
+ end
+
+ it "should perform a complete rpc with no args or return" do
+ srv_test(
+ proc {|client| client.send_voidMethod()},
+ proc {|client| client.recv_voidMethod.should == nil}
+ )
+ end
+
+ it "should perform a complete rpc with a primitive return type" do
+ srv_test(
+ proc {|client| client.send_primitiveMethod()},
+ proc {|client| client.recv_primitiveMethod.should == 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
+ result.should == 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 = Srv::Processor.new(SrvHandler.new)
+
+ client = 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/lib/rb/spec/client_spec.rb b/lib/rb/spec/client_spec.rb
new file mode 100644
index 000000000..e707d8163
--- /dev/null
+++ b/lib/rb/spec/client_spec.rb
@@ -0,0 +1,100 @@
+#
+# 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_helper'
+
+class ThriftClientSpec < Spec::ExampleGroup
+ include Thrift
+
+ class ClientSpec
+ include Thrift::Client
+ end
+
+ before(:each) do
+ @prot = mock("MockProtocol")
+ @client = ClientSpec.new(@prot)
+ end
+
+ describe Client do
+ it "should re-use iprot for oprot if not otherwise specified" do
+ @client.instance_variable_get(:'@iprot').should eql(@prot)
+ @client.instance_variable_get(:'@oprot').should eql(@prot)
+ end
+
+ it "should send a test message" do
+ @prot.should_receive(:write_message_begin).with('testMessage', MessageTypes::CALL, 0)
+ mock_args = mock('#<TestMessage_args:mock>')
+ mock_args.should_receive(:foo=).with('foo')
+ mock_args.should_receive(:bar=).with(42)
+ mock_args.should_receive(:write).with(@prot)
+ @prot.should_receive(:write_message_end)
+ @prot.should_receive(:trans) do
+ mock('trans').tee do |trans|
+ trans.should_receive(:flush)
+ end
+ end
+ klass = stub("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" do
+ @prot.should_receive(:write_message_begin).with('testMessage', MessageTypes::CALL, 0).ordered
+ @prot.should_receive(:write_message_begin).with('testMessage2', MessageTypes::CALL, 1).ordered
+ @prot.should_receive(:write_message_begin).with('testMessage3', MessageTypes::CALL, 2).ordered
+ @prot.stub!(:write_message_end)
+ @prot.stub!(:trans).and_return mock("trans").as_null_object
+ @client.send_message('testMessage', mock("args class").as_null_object)
+ @client.send_message('testMessage2', mock("args class").as_null_object)
+ @client.send_message('testMessage3', mock("args class").as_null_object)
+ end
+ end
+
+ it "should receive a test message" do
+ @prot.should_receive(:read_message_begin).and_return [nil, MessageTypes::CALL, 0]
+ @prot.should_receive(:read_message_end)
+ mock_klass = mock("#<MockClass:mock>")
+ mock_klass.should_receive(:read).with(@prot)
+ @client.receive_message(stub("MockClass", :new => mock_klass))
+ end
+
+ it "should handle received exceptions" do
+ @prot.should_receive(:read_message_begin).and_return [nil, MessageTypes::EXCEPTION, 0]
+ @prot.should_receive(:read_message_end)
+ ApplicationException.should_receive(:new).and_return do
+ StandardError.new.tee do |mock_exc|
+ mock_exc.should_receive(:read).with(@prot)
+ end
+ end
+ lambda { @client.receive_message(nil) }.should raise_error(StandardError)
+ end
+
+ it "should close the transport if an error occurs while sending a message" do
+ @prot.stub!(:write_message_begin)
+ @prot.should_not_receive(:write_message_end)
+ mock_args = mock("#<TestMessage_args:mock>")
+ mock_args.should_receive(:write).with(@prot).and_raise(StandardError)
+ trans = mock("MockTransport")
+ @prot.stub!(:trans).and_return(trans)
+ trans.should_receive(:close)
+ klass = mock("TestMessage_args", :new => mock_args)
+ lambda { @client.send_message("testMessage", klass) }.should raise_error(StandardError)
+ end
+ end
+end
diff --git a/lib/rb/spec/compact_protocol_spec.rb b/lib/rb/spec/compact_protocol_spec.rb
new file mode 100644
index 000000000..b9a798103
--- /dev/null
+++ b/lib/rb/spec/compact_protocol_spec.rb
@@ -0,0 +1,117 @@
+#
+# 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_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", "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))
+ read_back.should == 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
+ type.should == thrift_type
+ id.should == 15
+ read_back = proto.send(reader(primitive_type))
+ read_back.should == 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 = CompactProtoTestStruct.new
+ # sets and maps don't hash well... not sure what to do here.
+ struct.write(proto)
+
+ struct2 = CompactProtoTestStruct.new
+ struct2.read(proto)
+ struct2.should == 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 = Srv::Processor.new(JankyHandler.new)
+
+ client = 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)
+ client.recv_Janky.should == 2
+ end
+
+ class JankyHandler
+ def Janky(i32arg)
+ i32arg * 2
+ end
+ end
+
+ def writer(sym)
+ sym = sym == :binary ? :string : sym
+ "write_#{sym.to_s}"
+ end
+
+ def reader(sym)
+ sym = sym == :binary ? :string : sym
+ "read_#{sym.to_s}"
+ end
+end \ No newline at end of file
diff --git a/lib/rb/spec/exception_spec.rb b/lib/rb/spec/exception_spec.rb
new file mode 100644
index 000000000..fc3213781
--- /dev/null
+++ b/lib/rb/spec/exception_spec.rb
@@ -0,0 +1,142 @@
+#
+# 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_helper'
+
+class ThriftExceptionSpec < Spec::ExampleGroup
+ include Thrift
+
+ describe Exception do
+ it "should have an accessible message" do
+ e = Exception.new("test message")
+ e.message.should == "test message"
+ end
+ end
+
+ describe ApplicationException do
+ it "should inherit from Thrift::Exception" do
+ ApplicationException.superclass.should == Exception
+ end
+
+ it "should have an accessible type and message" do
+ e = ApplicationException.new
+ e.type.should == ApplicationException::UNKNOWN
+ e.message.should be_nil
+ e = ApplicationException.new(ApplicationException::UNKNOWN_METHOD, "test message")
+ e.type.should == ApplicationException::UNKNOWN_METHOD
+ e.message.should == "test message"
+ end
+
+ it "should read a struct off of a protocol" do
+ prot = mock("MockProtocol")
+ prot.should_receive(:read_struct_begin).ordered
+ prot.should_receive(:read_field_begin).exactly(3).times.and_return(
+ ["message", Types::STRING, 1],
+ ["type", Types::I32, 2],
+ [nil, Types::STOP, 0]
+ )
+ prot.should_receive(:read_string).ordered.and_return "test message"
+ prot.should_receive(:read_i32).ordered.and_return ApplicationException::BAD_SEQUENCE_ID
+ prot.should_receive(:read_field_end).exactly(2).times
+ prot.should_receive(:read_struct_end).ordered
+
+ e = ApplicationException.new
+ e.read(prot)
+ e.message.should == "test message"
+ e.type.should == ApplicationException::BAD_SEQUENCE_ID
+ end
+
+ it "should skip bad fields when reading a struct" do
+ prot = mock("MockProtocol")
+ prot.should_receive(:read_struct_begin).ordered
+ prot.should_receive(:read_field_begin).exactly(5).times.and_return(
+ ["type", Types::I32, 2],
+ ["type", Types::STRING, 2],
+ ["message", Types::MAP, 1],
+ ["message", Types::STRING, 3],
+ [nil, Types::STOP, 0]
+ )
+ prot.should_receive(:read_i32).and_return ApplicationException::INVALID_MESSAGE_TYPE
+ prot.should_receive(:skip).with(Types::STRING).twice
+ prot.should_receive(:skip).with(Types::MAP)
+ prot.should_receive(:read_field_end).exactly(4).times
+ prot.should_receive(:read_struct_end).ordered
+
+ e = ApplicationException.new
+ e.read(prot)
+ e.message.should be_nil
+ e.type.should == ApplicationException::INVALID_MESSAGE_TYPE
+ end
+
+ it "should write a Thrift::ApplicationException struct to the oprot" do
+ prot = mock("MockProtocol")
+ prot.should_receive(:write_struct_begin).with("Thrift::ApplicationException").ordered
+ prot.should_receive(:write_field_begin).with("message", Types::STRING, 1).ordered
+ prot.should_receive(:write_string).with("test message").ordered
+ prot.should_receive(:write_field_begin).with("type", Types::I32, 2).ordered
+ prot.should_receive(:write_i32).with(ApplicationException::UNKNOWN_METHOD).ordered
+ prot.should_receive(:write_field_end).twice
+ prot.should_receive(:write_field_stop).ordered
+ prot.should_receive(:write_struct_end).ordered
+
+ e = ApplicationException.new(ApplicationException::UNKNOWN_METHOD, "test message")
+ e.write(prot)
+ end
+
+ it "should skip nil fields when writing to the oprot" do
+ prot = mock("MockProtocol")
+ prot.should_receive(:write_struct_begin).with("Thrift::ApplicationException").ordered
+ prot.should_receive(:write_field_begin).with("message", Types::STRING, 1).ordered
+ prot.should_receive(:write_string).with("test message").ordered
+ prot.should_receive(:write_field_end).ordered
+ prot.should_receive(:write_field_stop).ordered
+ prot.should_receive(:write_struct_end).ordered
+
+ e = ApplicationException.new(nil, "test message")
+ e.write(prot)
+
+ prot = mock("MockProtocol")
+ prot.should_receive(:write_struct_begin).with("Thrift::ApplicationException").ordered
+ prot.should_receive(:write_field_begin).with("type", Types::I32, 2).ordered
+ prot.should_receive(:write_i32).with(ApplicationException::BAD_SEQUENCE_ID).ordered
+ prot.should_receive(:write_field_end).ordered
+ prot.should_receive(:write_field_stop).ordered
+ prot.should_receive(:write_struct_end).ordered
+
+ e = ApplicationException.new(ApplicationException::BAD_SEQUENCE_ID)
+ e.write(prot)
+
+ prot = mock("MockProtocol")
+ prot.should_receive(:write_struct_begin).with("Thrift::ApplicationException").ordered
+ prot.should_receive(:write_field_stop).ordered
+ prot.should_receive(:write_struct_end).ordered
+
+ e = ApplicationException.new(nil)
+ e.write(prot)
+ end
+ end
+
+ describe ProtocolException do
+ it "should have an accessible type" do
+ prot = ProtocolException.new(ProtocolException::SIZE_LIMIT, "message")
+ prot.type.should == ProtocolException::SIZE_LIMIT
+ prot.message.should == "message"
+ end
+ end
+end
diff --git a/lib/rb/spec/http_client_spec.rb b/lib/rb/spec/http_client_spec.rb
new file mode 100644
index 000000000..94526deb7
--- /dev/null
+++ b/lib/rb/spec/http_client_spec.rb
@@ -0,0 +1,49 @@
+#
+# 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_helper'
+
+class ThriftHTTPClientTransportSpec < Spec::ExampleGroup
+ include Thrift
+
+ describe HTTPClientTransport do
+ before(:each) do
+ @client = HTTPClientTransport.new("http://my.domain.com/path/to/service")
+ end
+
+ it "should always be open" do
+ @client.should be_open
+ @client.close
+ @client.should be_open
+ end
+
+ it "should post via HTTP and return the results" do
+ @client.write "a test"
+ @client.write " frame"
+ Net::HTTP.should_receive(:new).with("my.domain.com", 80).and_return do
+ mock("Net::HTTP").tee do |http|
+ http.should_receive(:use_ssl=).with(false)
+ http.should_receive(:post).with("/path/to/service", "a test frame", {"Content-Type"=>"application/x-thrift"}).and_return([nil, "data"])
+ end
+ end
+ @client.flush
+ @client.read(10).should == "data"
+ end
+ end
+end
diff --git a/lib/rb/spec/mongrel_http_server_spec.rb b/lib/rb/spec/mongrel_http_server_spec.rb
new file mode 100644
index 000000000..c994491cb
--- /dev/null
+++ b/lib/rb/spec/mongrel_http_server_spec.rb
@@ -0,0 +1,117 @@
+#
+# 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_helper'
+require 'thrift/server/mongrel_http_server'
+
+class ThriftHTTPServerSpec < Spec::ExampleGroup
+ include Thrift
+
+ Handler = MongrelHTTPServer::Handler
+
+ describe MongrelHTTPServer do
+ it "should have appropriate defaults" do
+ mock_factory = mock("BinaryProtocolFactory")
+ mock_proc = mock("Processor")
+ BinaryProtocolFactory.should_receive(:new).and_return(mock_factory)
+ Mongrel::HttpServer.should_receive(:new).with("0.0.0.0", 80).and_return do
+ mock("Mongrel::HttpServer").tee do |mock|
+ handler = mock("Handler")
+ Handler.should_receive(:new).with(mock_proc, mock_factory).and_return(handler)
+ mock.should_receive(:register).with("/", handler)
+ end
+ end
+ MongrelHTTPServer.new(mock_proc)
+ end
+
+ it "should understand :ip, :port, :path, and :protocol_factory" do
+ mock_proc = mock("Processor")
+ mock_factory = mock("ProtocolFactory")
+ Mongrel::HttpServer.should_receive(:new).with("1.2.3.4", 1234).and_return do
+ mock("Mongrel::HttpServer").tee do |mock|
+ handler = mock("Handler")
+ Handler.should_receive(:new).with(mock_proc, mock_factory).and_return(handler)
+ mock.should_receive(:register).with("/foo", handler)
+ end
+ end
+ MongrelHTTPServer.new(mock_proc, :ip => "1.2.3.4", :port => 1234, :path => "foo",
+ :protocol_factory => mock_factory)
+ end
+
+ it "should serve using Mongrel::HttpServer" do
+ BinaryProtocolFactory.stub!(:new)
+ Mongrel::HttpServer.should_receive(:new).and_return do
+ mock("Mongrel::HttpServer").tee do |mock|
+ Handler.stub!(:new)
+ mock.stub!(:register)
+ mock.should_receive(:run).and_return do
+ mock("Mongrel::HttpServer.run").tee do |runner|
+ runner.should_receive(:join)
+ end
+ end
+ end
+ end
+ MongrelHTTPServer.new(nil).serve
+ end
+ end
+
+ describe MongrelHTTPServer::Handler do
+ before(:each) do
+ @processor = mock("Processor")
+ @factory = mock("ProtocolFactory")
+ @handler = Handler.new(@processor, @factory)
+ end
+
+ it "should return 404 for non-POST requests" do
+ request = mock("request", :params => {"REQUEST_METHOD" => "GET"})
+ response = mock("response")
+ response.should_receive(:start).with(404)
+ response.should_not_receive(:start).with(200)
+ @handler.process(request, response)
+ end
+
+ it "should serve using application/x-thrift" do
+ request = mock("request", :params => {"REQUEST_METHOD" => "POST"}, :body => nil)
+ response = mock("response")
+ head = mock("head")
+ head.should_receive(:[]=).with("Content-Type", "application/x-thrift")
+ IOStreamTransport.stub!(:new)
+ @factory.stub!(:get_protocol)
+ @processor.stub!(:process)
+ response.should_receive(:start).with(200).and_yield(head, nil)
+ @handler.process(request, response)
+ end
+
+ it "should use the IOStreamTransport" do
+ body = mock("body")
+ request = mock("request", :params => {"REQUEST_METHOD" => "POST"}, :body => body)
+ response = mock("response")
+ head = mock("head")
+ head.stub!(:[]=)
+ out = mock("out")
+ protocol = mock("protocol")
+ transport = mock("transport")
+ IOStreamTransport.should_receive(:new).with(body, out).and_return(transport)
+ @factory.should_receive(:get_protocol).with(transport).and_return(protocol)
+ @processor.should_receive(:process).with(protocol, protocol)
+ response.should_receive(:start).with(200).and_yield(head, out)
+ @handler.process(request, response)
+ end
+ end
+end
diff --git a/lib/rb/spec/nonblocking_server_spec.rb b/lib/rb/spec/nonblocking_server_spec.rb
new file mode 100644
index 000000000..a0e86cf2e
--- /dev/null
+++ b/lib/rb/spec/nonblocking_server_spec.rb
@@ -0,0 +1,266 @@
+#
+# 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_helper'
+require File.dirname(__FILE__) + '/gen-rb/nonblocking_service'
+
+class ThriftNonblockingServerSpec < Spec::ExampleGroup
+ include Thrift
+ include SpecNamespace
+
+ 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 < 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 < 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 = NonblockingService::Processor.new(handler)
+ queue = Queue.new
+ @transport = SpecServerSocket.new('localhost', @port, queue)
+ transport_factory = FramedTransportFactory.new
+ logger = Logger.new(STDERR)
+ logger.level = Logger::WARN
+ @server = 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(FramedTransport.new(Socket.new('localhost', @port)), queue)
+ protocol = BinaryProtocol.new(transport)
+ client = 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
+ client.greeting(true).should == Hello.new
+ client.greeting(false).should == 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 { queue.pop.should be_true }
+ @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 { result.pop.should == Hello.new }
+ client.greeting(true).should == Hello.new
+ queues[5] << [:unblock, 4]
+ 4.times { result.pop.should be_true }
+ queues[2] << :hello
+ result.pop.should == Hello.new
+ client.greeting(false).should == Hello.new(:greeting => 'Aloha!')
+ 7.times { queues.shift << :exit }
+ client.greeting(true).should == 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
+ @server_thread.join(2).should 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
+ @server_thread.join(2).should be_an_instance_of(Thread)
+ result.pop.should == :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
+ @server_thread.join(3).should_not be_nil
+ result.should be_empty
+ end
+
+ it "should allow shutting down in response to a message" do
+ client = setup_client
+ client.greeting(true).should == Hello.new
+ client.shutdown
+ @server_thread.join(2).should_not be_nil
+ end
+ end
+end
diff --git a/lib/rb/spec/processor_spec.rb b/lib/rb/spec/processor_spec.rb
new file mode 100644
index 000000000..d35f65284
--- /dev/null
+++ b/lib/rb/spec/processor_spec.rb
@@ -0,0 +1,83 @@
+#
+# 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_helper'
+
+class ThriftProcessorSpec < Spec::ExampleGroup
+ include Thrift
+
+ class ProcessorSpec
+ include Thrift::Processor
+ end
+
+ describe "Processor" do
+ before(:each) do
+ @processor = ProcessorSpec.new(mock("MockHandler"))
+ @prot = mock("MockProtocol")
+ end
+
+ def mock_trans(obj)
+ obj.should_receive(:trans).ordered.and_return do
+ mock("trans").tee do |trans|
+ trans.should_receive(:flush).ordered
+ end
+ end
+ end
+
+ it "should call process_<message> when it receives that message" do
+ @prot.should_receive(:read_message_begin).ordered.and_return ['testMessage', MessageTypes::CALL, 17]
+ @processor.should_receive(:process_testMessage).with(17, @prot, @prot).ordered
+ @processor.process(@prot, @prot).should == true
+ end
+
+ it "should raise an ApplicationException when the received message cannot be processed" do
+ @prot.should_receive(:read_message_begin).ordered.and_return ['testMessage', MessageTypes::CALL, 4]
+ @prot.should_receive(:skip).with(Types::STRUCT).ordered
+ @prot.should_receive(:read_message_end).ordered
+ @prot.should_receive(:write_message_begin).with('testMessage', MessageTypes::EXCEPTION, 4).ordered
+ ApplicationException.should_receive(:new).with(ApplicationException::UNKNOWN_METHOD, "Unknown function testMessage").and_return do
+ mock(ApplicationException).tee do |e|
+ e.should_receive(:write).with(@prot).ordered
+ end
+ end
+ @prot.should_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 = mock("MockArgsClass")
+ args = mock("#<MockArgsClass:mock>").tee do |args|
+ args.should_receive(:read).with(@prot).ordered
+ end
+ args_class.should_receive(:new).and_return args
+ @prot.should_receive(:read_message_end).ordered
+ @processor.read_args(@prot, args_class).should eql(args)
+ end
+
+ it "should write out a reply when asked" do
+ @prot.should_receive(:write_message_begin).with('testMessage', MessageTypes::REPLY, 23).ordered
+ result = mock("MockResult")
+ result.should_receive(:write).with(@prot).ordered
+ @prot.should_receive(:write_message_end).ordered
+ mock_trans(@prot)
+ @processor.write_result(result, @prot, 'testMessage', 23)
+ end
+ end
+end
diff --git a/lib/rb/spec/serializer_spec.rb b/lib/rb/spec/serializer_spec.rb
new file mode 100644
index 000000000..82f374b13
--- /dev/null
+++ b/lib/rb/spec/serializer_spec.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.
+#
+
+require File.dirname(__FILE__) + '/spec_helper'
+require File.dirname(__FILE__) + '/gen-rb/thrift_spec_types'
+
+class ThriftSerializerSpec < Spec::ExampleGroup
+ include Thrift
+ include SpecNamespace
+
+ describe Serializer do
+ it "should serialize structs to binary by default" do
+ serializer = Serializer.new(Thrift::BinaryProtocolAcceleratedFactory.new)
+ data = serializer.serialize(Hello.new(:greeting => "'Ello guv'nor!"))
+ data.should == "\x0B\x00\x01\x00\x00\x00\x0E'Ello guv'nor!\x00"
+ end
+
+ it "should serialize structs to the given protocol" do
+ protocol = BaseProtocol.new(mock("transport"))
+ protocol.should_receive(:write_struct_begin).with("SpecNamespace::Hello")
+ protocol.should_receive(:write_field_begin).with("greeting", Types::STRING, 1)
+ protocol.should_receive(:write_string).with("Good day")
+ protocol.should_receive(:write_field_end)
+ protocol.should_receive(:write_field_stop)
+ protocol.should_receive(:write_struct_end)
+ protocol_factory = mock("ProtocolFactory")
+ protocol_factory.stub!(:get_protocol).and_return(protocol)
+ serializer = Serializer.new(protocol_factory)
+ serializer.serialize(Hello.new(:greeting => "Good day"))
+ end
+ end
+
+ describe Deserializer do
+ it "should deserialize structs from binary by default" do
+ deserializer = Deserializer.new
+ data = "\x0B\x00\x01\x00\x00\x00\x0E'Ello guv'nor!\x00"
+ deserializer.deserialize(Hello.new, data).should == Hello.new(:greeting => "'Ello guv'nor!")
+ end
+
+ it "should deserialize structs from the given protocol" do
+ protocol = BaseProtocol.new(mock("transport"))
+ protocol.should_receive(:read_struct_begin).and_return("SpecNamespace::Hello")
+ protocol.should_receive(:read_field_begin).and_return(["greeting", Types::STRING, 1],
+ [nil, Types::STOP, 0])
+ protocol.should_receive(:read_string).and_return("Good day")
+ protocol.should_receive(:read_field_end)
+ protocol.should_receive(:read_struct_end)
+ protocol_factory = mock("ProtocolFactory")
+ protocol_factory.stub!(:get_protocol).and_return(protocol)
+ deserializer = Deserializer.new(protocol_factory)
+ deserializer.deserialize(Hello.new, "").should == Hello.new(:greeting => "Good day")
+ end
+ end
+end
diff --git a/lib/rb/spec/server_socket_spec.rb b/lib/rb/spec/server_socket_spec.rb
new file mode 100644
index 000000000..fce501342
--- /dev/null
+++ b/lib/rb/spec/server_socket_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 File.dirname(__FILE__) + '/spec_helper'
+require File.dirname(__FILE__) + "/socket_spec_shared"
+
+class ThriftServerSocketSpec < Spec::ExampleGroup
+ include Thrift
+
+ describe ServerSocket do
+ before(:each) do
+ @socket = ServerSocket.new(1234)
+ end
+
+ it "should create a handle when calling listen" do
+ TCPServer.should_receive(:new).with(nil, 1234)
+ @socket.listen
+ end
+
+ it "should accept an optional host argument" do
+ @socket = ServerSocket.new('localhost', 1234)
+ TCPServer.should_receive(:new).with('localhost', 1234)
+ @socket.listen
+ end
+
+ it "should create a Thrift::Socket to wrap accepted sockets" do
+ handle = mock("TCPServer")
+ TCPServer.should_receive(:new).with(nil, 1234).and_return(handle)
+ @socket.listen
+ sock = mock("sock")
+ handle.should_receive(:accept).and_return(sock)
+ trans = mock("Socket")
+ Socket.should_receive(:new).and_return(trans)
+ trans.should_receive(:handle=).with(sock)
+ @socket.accept.should == trans
+ end
+
+ it "should close the handle when closed" do
+ handle = mock("TCPServer", :closed? => false)
+ TCPServer.should_receive(:new).with(nil, 1234).and_return(handle)
+ @socket.listen
+ handle.should_receive(:close)
+ @socket.close
+ end
+
+ it "should return nil when accepting if there is no handle" do
+ @socket.accept.should be_nil
+ end
+
+ it "should return true for closed? when appropriate" do
+ handle = mock("TCPServer", :closed? => false)
+ TCPServer.stub!(:new).and_return(handle)
+ @socket.listen
+ @socket.should_not be_closed
+ handle.stub!(:close)
+ @socket.close
+ @socket.should be_closed
+ @socket.listen
+ @socket.should_not be_closed
+ handle.stub!(:closed?).and_return(true)
+ @socket.should be_closed
+ end
+ end
+end
diff --git a/lib/rb/spec/server_spec.rb b/lib/rb/spec/server_spec.rb
new file mode 100644
index 000000000..ffe9bffa9
--- /dev/null
+++ b/lib/rb/spec/server_spec.rb
@@ -0,0 +1,160 @@
+#
+# 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_helper'
+
+class ThriftServerSpec < Spec::ExampleGroup
+ include Thrift
+
+ describe BaseServer do
+ it "should default to BaseTransportFactory and BinaryProtocolFactory when not specified" do
+ server = BaseServer.new(mock("Processor"), mock("BaseServerTransport"))
+ server.instance_variable_get(:'@transport_factory').should be_an_instance_of(BaseTransportFactory)
+ server.instance_variable_get(:'@protocol_factory').should be_an_instance_of(BinaryProtocolFactory)
+ end
+
+ # serve is a noop, so can't test that
+ end
+
+ shared_examples_for "servers" do
+ before(:each) do
+ @processor = mock("Processor")
+ @serverTrans = mock("ServerTransport")
+ @trans = mock("BaseTransport")
+ @prot = mock("BaseProtocol")
+ @client = mock("Client")
+ @server = server_type.new(@processor, @serverTrans, @trans, @prot)
+ end
+ end
+
+ describe SimpleServer do
+ it_should_behave_like "servers"
+
+ def server_type
+ SimpleServer
+ end
+
+ it "should serve in the main thread" do
+ @serverTrans.should_receive(:listen).ordered
+ @serverTrans.should_receive(:accept).exactly(3).times.and_return(@client)
+ @trans.should_receive(:get_transport).exactly(3).times.with(@client).and_return(@trans)
+ @prot.should_receive(:get_protocol).exactly(3).times.with(@trans).and_return(@prot)
+ x = 0
+ @processor.should_receive(:process).exactly(3).times.with(@prot, @prot).and_return do
+ case (x += 1)
+ when 1 then raise Thrift::TransportException
+ when 2 then raise Thrift::ProtocolException
+ when 3 then throw :stop
+ end
+ end
+ @trans.should_receive(:close).exactly(3).times
+ @serverTrans.should_receive(:close).ordered
+ lambda { @server.serve }.should throw_symbol(:stop)
+ end
+ end
+
+ describe ThreadedServer do
+ it_should_behave_like "servers"
+
+ def server_type
+ ThreadedServer
+ end
+
+ it "should serve using threads" do
+ @serverTrans.should_receive(:listen).ordered
+ @serverTrans.should_receive(:accept).exactly(3).times.and_return(@client)
+ @trans.should_receive(:get_transport).exactly(3).times.with(@client).and_return(@trans)
+ @prot.should_receive(:get_protocol).exactly(3).times.with(@trans).and_return(@prot)
+ Thread.should_receive(:new).with(@prot, @trans).exactly(3).times.and_yield(@prot, @trans)
+ x = 0
+ @processor.should_receive(:process).exactly(3).times.with(@prot, @prot).and_return do
+ case (x += 1)
+ when 1 then raise Thrift::TransportException
+ when 2 then raise Thrift::ProtocolException
+ when 3 then throw :stop
+ end
+ end
+ @trans.should_receive(:close).exactly(3).times
+ @serverTrans.should_receive(:close).ordered
+ lambda { @server.serve }.should throw_symbol(:stop)
+ end
+ end
+
+ describe ThreadPoolServer do
+ it_should_behave_like "servers"
+
+ def server_type
+ # put this stuff here so it runs before the server is created
+ @threadQ = mock("SizedQueue")
+ SizedQueue.should_receive(:new).with(20).and_return(@threadQ)
+ @excQ = mock("Queue")
+ Queue.should_receive(:new).and_return(@excQ)
+ ThreadPoolServer
+ end
+
+ it "should set up the queues" do
+ @server.instance_variable_get(:'@thread_q').should be(@threadQ)
+ @server.instance_variable_get(:'@exception_q').should be(@excQ)
+ end
+
+ it "should serve inside a thread" do
+ Thread.should_receive(:new).and_return do |block|
+ @server.should_receive(:serve)
+ block.call
+ @server.rspec_verify
+ end
+ @excQ.should_receive(:pop).and_throw(:popped)
+ lambda { @server.rescuable_serve }.should throw_symbol(:popped)
+ end
+
+ it "should avoid running the server twice when retrying rescuable_serve" do
+ Thread.should_receive(:new).and_return do |block|
+ @server.should_receive(:serve)
+ block.call
+ @server.rspec_verify
+ end
+ @excQ.should_receive(:pop).twice.and_throw(:popped)
+ lambda { @server.rescuable_serve }.should throw_symbol(:popped)
+ lambda { @server.rescuable_serve }.should throw_symbol(:popped)
+ end
+
+ it "should serve using a thread pool" do
+ @serverTrans.should_receive(:listen).ordered
+ @threadQ.should_receive(:push).with(:token)
+ @threadQ.should_receive(:pop)
+ Thread.should_receive(:new).and_yield
+ @serverTrans.should_receive(:accept).exactly(3).times.and_return(@client)
+ @trans.should_receive(:get_transport).exactly(3).times.and_return(@trans)
+ @prot.should_receive(:get_protocol).exactly(3).times.and_return(@prot)
+ x = 0
+ error = RuntimeError.new("Stopped")
+ @processor.should_receive(:process).exactly(3).times.with(@prot, @prot).and_return do
+ case (x += 1)
+ when 1 then raise Thrift::TransportException
+ when 2 then raise Thrift::ProtocolException
+ when 3 then raise error
+ end
+ end
+ @trans.should_receive(:close).exactly(3).times
+ @excQ.should_receive(:push).with(error).and_throw(:stop)
+ @serverTrans.should_receive(:close)
+ lambda { @server.serve }.should throw_symbol(:stop)
+ end
+ end
+end
diff --git a/lib/rb/spec/socket_spec.rb b/lib/rb/spec/socket_spec.rb
new file mode 100644
index 000000000..dd8b0f924
--- /dev/null
+++ b/lib/rb/spec/socket_spec.rb
@@ -0,0 +1,61 @@
+#
+# 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_helper'
+require File.dirname(__FILE__) + "/socket_spec_shared"
+
+class ThriftSocketSpec < Spec::ExampleGroup
+ include Thrift
+
+ describe Socket do
+ before(:each) do
+ @socket = Socket.new
+ @handle = mock("Handle", :closed? => false)
+ @handle.stub!(:close)
+ @handle.stub!(:connect_nonblock)
+ ::Socket.stub!(:new).and_return(@handle)
+ end
+
+ it_should_behave_like "a socket"
+
+ it "should raise a TransportException when it cannot open a socket" do
+ ::Socket.should_receive(:new).and_raise(StandardError)
+ lambda { @socket.open }.should raise_error(Thrift::TransportException) { |e| e.type.should == Thrift::TransportException::NOT_OPEN }
+ end
+
+ it "should open a ::Socket with default args" do
+ ::Socket.should_receive(:new).and_return(mock("Handle", :connect_nonblock => true))
+ ::Socket.should_receive(:getaddrinfo).with("localhost", 9090).and_return([[]])
+ ::Socket.should_receive(:sockaddr_in)
+ @socket.open
+ end
+
+ it "should accept host/port options" do
+ ::Socket.should_receive(:new).and_return(mock("Handle", :connect_nonblock => true))
+ ::Socket.should_receive(:getaddrinfo).with("my.domain", 1234).and_return([[]])
+ ::Socket.should_receive(:sockaddr_in)
+ Socket.new('my.domain', 1234).open
+ end
+
+ it "should accept an optional timeout" do
+ ::Socket.stub!(:new)
+ Socket.new('localhost', 8080, 5).timeout.should == 5
+ end
+ end
+end
diff --git a/lib/rb/spec/socket_spec_shared.rb b/lib/rb/spec/socket_spec_shared.rb
new file mode 100644
index 000000000..96b433b8c
--- /dev/null
+++ b/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 File.dirname(__FILE__) + '/spec_helper'
+
+shared_examples_for "a socket" do
+ it "should open a socket" do
+ @socket.open.should == @handle
+ end
+
+ it "should be open whenever it has a handle" do
+ @socket.should_not be_open
+ @socket.open
+ @socket.should be_open
+ @socket.handle = nil
+ @socket.should_not be_open
+ @socket.handle = @handle
+ @socket.close
+ @socket.should_not be_open
+ end
+
+ it "should write data to the handle" do
+ @socket.open
+ @handle.should_receive(:write).with("foobar")
+ @socket.write("foobar")
+ @handle.should_receive(:write).with("fail").and_raise(StandardError)
+ lambda { @socket.write("fail") }.should raise_error(Thrift::TransportException) { |e| e.type.should == Thrift::TransportException::NOT_OPEN }
+ end
+
+ it "should raise an error when it cannot read from the handle" do
+ @socket.open
+ @handle.should_receive(:readpartial).with(17).and_raise(StandardError)
+ lambda { @socket.read(17) }.should raise_error(Thrift::TransportException) { |e| e.type.should == Thrift::TransportException::NOT_OPEN }
+ end
+
+ it "should return the data read when reading from the handle works" do
+ @socket.open
+ @handle.should_receive(:readpartial).with(17).and_return("test data")
+ @socket.read(17).should == "test data"
+ end
+
+ it "should declare itself as closed when it has an error" do
+ @socket.open
+ @handle.should_receive(:write).with("fail").and_raise(StandardError)
+ @socket.should be_open
+ lambda { @socket.write("fail") }.should raise_error
+ @socket.should_not be_open
+ end
+
+ it "should raise an error when the stream is closed" do
+ @socket.open
+ @handle.stub!(:closed?).and_return(true)
+ @socket.should_not be_open
+ lambda { @socket.write("fail") }.should raise_error(IOError, "closed stream")
+ lambda { @socket.read(10) }.should raise_error(IOError, "closed stream")
+ end
+
+ it "should support the timeout accessor for read" do
+ @socket.timeout = 3
+ @socket.open
+ IO.should_receive(:select).with([@handle], nil, nil, 3).and_return([[@handle], [], []])
+ @handle.should_receive(:readpartial).with(17).and_return("test data")
+ @socket.read(17).should == "test data"
+ end
+
+ it "should support the timeout accessor for write" do
+ @socket.timeout = 3
+ @socket.open
+ IO.should_receive(:select).with(nil, [@handle], nil, 3).twice.and_return([[], [@handle], []])
+ @handle.should_receive(:write_nonblock).with("test data").and_return(4)
+ @handle.should_receive(:write_nonblock).with(" data").and_return(5)
+ @socket.write("test data").should == 9
+ end
+
+ it "should raise an error when read times out" do
+ @socket.timeout = 0.5
+ @socket.open
+ IO.should_receive(:select).with([@handle], nil, nil, 0.5).at_least(1).times.and_return(nil)
+ lambda { @socket.read(17) }.should raise_error(Thrift::TransportException) { |e| e.type.should == Thrift::TransportException::TIMED_OUT }
+ end
+
+ it "should raise an error when write times out" do
+ @socket.timeout = 0.5
+ @socket.open
+ IO.should_receive(:select).with(nil, [@handle], nil, 0.5).any_number_of_times.and_return(nil)
+ lambda { @socket.write("test data") }.should raise_error(Thrift::TransportException) { |e| e.type.should == Thrift::TransportException::TIMED_OUT }
+ end
+end
diff --git a/lib/rb/spec/spec_helper.rb b/lib/rb/spec/spec_helper.rb
new file mode 100644
index 000000000..3c20bf999
--- /dev/null
+++ b/lib/rb/spec/spec_helper.rb
@@ -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.
+#
+
+require 'rubygems'
+# require at least 1.1.4 to fix a bug with describing Modules
+gem 'rspec', '>= 1.1.4'
+require 'spec'
+
+$:.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 File.dirname(__FILE__) + '/../lib/thrift'
+
+class Object
+ # tee is a useful method, so let's let our tests have it
+ def tee(&block)
+ block.call(self)
+ self
+ end
+end
+
+Spec::Runner.configure do |configuration|
+ configuration.before(:each) do
+ Thrift.type_checking = true
+ end
+end
+
+require File.dirname(__FILE__) + "/../debug_proto_test/gen-rb/Srv"
+require File.dirname(__FILE__) + "/../debug_proto_test/gen-rb/debug_proto_test_constants"
+
+module Fixtures
+ COMPACT_PROTOCOL_TEST_STRUCT = 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 \ No newline at end of file
diff --git a/lib/rb/spec/struct_spec.rb b/lib/rb/spec/struct_spec.rb
new file mode 100644
index 000000000..23a701ecf
--- /dev/null
+++ b/lib/rb/spec/struct_spec.rb
@@ -0,0 +1,253 @@
+#
+# 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_helper'
+require File.dirname(__FILE__) + '/gen-rb/thrift_spec_types'
+
+class ThriftStructSpec < Spec::ExampleGroup
+ include Thrift
+ include SpecNamespace
+
+ describe Struct do
+ it "should iterate over all fields properly" do
+ fields = {}
+ Foo.new.each_field { |fid,field_info| fields[fid] = field_info }
+ fields.should == Foo::FIELDS
+ end
+
+ it "should initialize all fields to defaults" do
+ struct = Foo.new
+ struct.simple.should == 53
+ struct.words.should == "words"
+ struct.hello.should == Hello.new(:greeting => 'hello, world!')
+ struct.ints.should == [1, 2, 2, 3]
+ struct.complex.should be_nil
+ struct.shorts.should == Set.new([5, 17, 239])
+ end
+
+ it "should not share default values between instances" do
+ begin
+ struct = Foo.new
+ struct.ints << 17
+ Foo.new.ints.should == [1,2,2,3]
+ ensure
+ # ensure no leakage to other tests
+ Foo::FIELDS[4][:default] = [1,2,2,3]
+ end
+ end
+
+ it "should properly initialize boolean values" do
+ struct = BoolStruct.new(:yesno => false)
+ struct.yesno.should be_false
+ end
+
+ it "should have proper == semantics" do
+ Foo.new.should_not == Hello.new
+ Foo.new.should == Foo.new
+ Foo.new(:simple => 52).should_not == Foo.new
+ end
+
+ it "should read itself off the wire" do
+ struct = Foo.new
+ prot = BaseProtocol.new(mock("transport"))
+ prot.should_receive(:read_struct_begin).twice
+ prot.should_receive(:read_struct_end).twice
+ prot.should_receive(:read_field_begin).and_return(
+ ['complex', Types::MAP, 5], # Foo
+ ['words', Types::STRING, 2], # Foo
+ ['hello', Types::STRUCT, 3], # Foo
+ ['greeting', Types::STRING, 1], # Hello
+ [nil, Types::STOP, 0], # Hello
+ ['simple', Types::I32, 1], # Foo
+ ['ints', Types::LIST, 4], # Foo
+ ['shorts', Types::SET, 6], # Foo
+ [nil, Types::STOP, 0] # Hello
+ )
+ prot.should_receive(:read_field_end).exactly(7).times
+ prot.should_receive(:read_map_begin).and_return(
+ [Types::I32, Types::MAP, 2], # complex
+ [Types::STRING, Types::DOUBLE, 2], # complex/1/value
+ [Types::STRING, Types::DOUBLE, 1] # complex/2/value
+ )
+ prot.should_receive(:read_map_end).exactly(3).times
+ prot.should_receive(:read_list_begin).and_return([Types::I32, 4])
+ prot.should_receive(:read_list_end)
+ prot.should_receive(:read_set_begin).and_return([Types::I16, 2])
+ prot.should_receive(:read_set_end)
+ prot.should_receive(:read_i32).and_return(
+ 1, 14, # complex keys
+ 42, # simple
+ 4, 23, 4, 29 # ints
+ )
+ prot.should_receive(:read_string).and_return("pi", "e", "feigenbaum", "apple banana", "what's up?")
+ prot.should_receive(:read_double).and_return(Math::PI, Math::E, 4.669201609)
+ prot.should_receive(:read_i16).and_return(2, 3)
+ prot.should_not_receive(:skip)
+ struct.read(prot)
+
+ struct.simple.should == 42
+ struct.complex.should == {1 => {"pi" => Math::PI, "e" => Math::E}, 14 => {"feigenbaum" => 4.669201609}}
+ struct.hello.should == Hello.new(:greeting => "what's up?")
+ struct.words.should == "apple banana"
+ struct.ints.should == [4, 23, 4, 29]
+ struct.shorts.should == Set.new([3, 2])
+ end
+
+ it "should skip unexpected fields in structs and use default values" do
+ struct = Foo.new
+ prot = BaseProtocol.new(mock("transport"))
+ prot.should_receive(:read_struct_begin)
+ prot.should_receive(:read_struct_end)
+ prot.should_receive(:read_field_begin).and_return(
+ ['simple', Types::I32, 1],
+ ['complex', Types::STRUCT, 5],
+ ['thinz', Types::MAP, 7],
+ ['foobar', Types::I32, 3],
+ ['words', Types::STRING, 2],
+ [nil, Types::STOP, 0]
+ )
+ prot.should_receive(:read_field_end).exactly(5).times
+ prot.should_receive(:read_i32).and_return(42)
+ prot.should_receive(:read_string).and_return("foobar")
+ prot.should_receive(:skip).with(Types::STRUCT)
+ prot.should_receive(:skip).with(Types::MAP)
+ # prot.should_receive(:read_map_begin).and_return([Types::I32, Types::I32, 0])
+ # prot.should_receive(:read_map_end)
+ prot.should_receive(:skip).with(Types::I32)
+ struct.read(prot)
+
+ struct.simple.should == 42
+ struct.complex.should be_nil
+ struct.words.should == "foobar"
+ struct.hello.should == Hello.new(:greeting => 'hello, world!')
+ struct.ints.should == [1, 2, 2, 3]
+ struct.shorts.should == Set.new([5, 17, 239])
+ end
+
+ it "should write itself to the wire" do
+ prot = BaseProtocol.new(mock("transport")) #mock("Protocol")
+ prot.should_receive(:write_struct_begin).with("SpecNamespace::Foo")
+ prot.should_receive(:write_struct_begin).with("SpecNamespace::Hello")
+ prot.should_receive(:write_struct_end).twice
+ prot.should_receive(:write_field_begin).with('ints', Types::LIST, 4)
+ prot.should_receive(:write_i32).with(1)
+ prot.should_receive(:write_i32).with(2).twice
+ prot.should_receive(:write_i32).with(3)
+ prot.should_receive(:write_field_begin).with('complex', Types::MAP, 5)
+ prot.should_receive(:write_i32).with(5)
+ prot.should_receive(:write_string).with('foo')
+ prot.should_receive(:write_double).with(1.23)
+ prot.should_receive(:write_field_begin).with('shorts', Types::SET, 6)
+ prot.should_receive(:write_i16).with(5)
+ prot.should_receive(:write_i16).with(17)
+ prot.should_receive(:write_i16).with(239)
+ prot.should_receive(:write_field_stop).twice
+ prot.should_receive(:write_field_end).exactly(6).times
+ prot.should_receive(:write_field_begin).with('simple', Types::I32, 1)
+ prot.should_receive(:write_i32).with(53)
+ prot.should_receive(:write_field_begin).with('hello', Types::STRUCT, 3)
+ prot.should_receive(:write_field_begin).with('greeting', Types::STRING, 1)
+ prot.should_receive(:write_string).with('hello, world!')
+ prot.should_receive(:write_map_begin).with(Types::I32, Types::MAP, 1)
+ prot.should_receive(:write_map_begin).with(Types::STRING, Types::DOUBLE, 1)
+ prot.should_receive(:write_map_end).twice
+ prot.should_receive(:write_list_begin).with(Types::I32, 4)
+ prot.should_receive(:write_list_end)
+ prot.should_receive(:write_set_begin).with(Types::I16, 3)
+ prot.should_receive(:write_set_end)
+
+ struct = 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 = Foo.new
+ lambda { struct.send :write_container, nil, nil, {:type => "foo"} }.should 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
+ lambda { Hello.new(:greeting => 3) }.should raise_error(TypeError, "Expected Types::STRING, received Fixnum for field greeting")
+ ensure
+ Thrift.type_checking = false
+ end
+ lambda { Hello.new(:greeting => 3) }.should_not raise_error(TypeError)
+ end
+
+ it "should support optional type-checking in field accessors" do
+ Thrift.type_checking = true
+ begin
+ hello = Hello.new
+ lambda { hello.greeting = 3 }.should raise_error(TypeError, "Expected Types::STRING, received Fixnum for field greeting")
+ ensure
+ Thrift.type_checking = false
+ end
+ lambda { hello.greeting = 3 }.should_not raise_error(TypeError)
+ end
+
+ it "should raise an exception when unknown types are given to Thrift::Struct.new" do
+ lambda { Hello.new(:fish => 'salmon') }.should raise_error(Exception, "Unknown key given to SpecNamespace::Hello.new: fish")
+ end
+
+ it "should support `raise Xception, 'message'` for Exception structs" do
+ begin
+ raise Xception, "something happened"
+ rescue Thrift::Exception => e
+ e.message.should == "something happened"
+ e.code.should == 1
+ # ensure it gets serialized properly, this is the really important part
+ prot = BaseProtocol.new(mock("trans"))
+ prot.should_receive(:write_struct_begin).with("SpecNamespace::Xception")
+ prot.should_receive(:write_struct_end)
+ prot.should_receive(:write_field_begin).with('message', Types::STRING, 1)#, "something happened")
+ prot.should_receive(:write_string).with("something happened")
+ prot.should_receive(:write_field_begin).with('code', Types::I32, 2)#, 1)
+ prot.should_receive(:write_i32).with(1)
+ prot.should_receive(:write_field_stop)
+ prot.should_receive(:write_field_end).twice
+
+ e.write(prot)
+ end
+ end
+
+ it "should support the regular initializer for exception structs" do
+ begin
+ raise Xception, :message => "something happened", :code => 5
+ rescue Thrift::Exception => e
+ e.message.should == "something happened"
+ e.code.should == 5
+ prot = BaseProtocol.new(mock("trans"))
+ prot.should_receive(:write_struct_begin).with("SpecNamespace::Xception")
+ prot.should_receive(:write_struct_end)
+ prot.should_receive(:write_field_begin).with('message', Types::STRING, 1)
+ prot.should_receive(:write_string).with("something happened")
+ prot.should_receive(:write_field_begin).with('code', Types::I32, 2)
+ prot.should_receive(:write_i32).with(5)
+ prot.should_receive(:write_field_stop)
+ prot.should_receive(:write_field_end).twice
+
+ e.write(prot)
+ end
+ end
+ end
+end
diff --git a/lib/rb/spec/types_spec.rb b/lib/rb/spec/types_spec.rb
new file mode 100644
index 000000000..d979cfb10
--- /dev/null
+++ b/lib/rb/spec/types_spec.rb
@@ -0,0 +1,117 @@
+#
+# 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_helper'
+require File.dirname(__FILE__) + '/gen-rb/thrift_spec_types'
+
+class ThriftTypesSpec < Spec::ExampleGroup
+ include Thrift
+
+ before(:each) do
+ Thrift.type_checking = true
+ end
+
+ after(:each) do
+ Thrift.type_checking = false
+ end
+
+ describe "Type checking" do
+ it "should return the proper name for each type" do
+ Thrift.type_name(Types::I16).should == "Types::I16"
+ Thrift.type_name(Types::VOID).should == "Types::VOID"
+ Thrift.type_name(Types::LIST).should == "Types::LIST"
+ Thrift.type_name(42).should be_nil
+ end
+
+ it "should check types properly" do
+ # lambda { Thrift.check_type(nil, Types::STOP) }.should raise_error(TypeError)
+ lambda { Thrift.check_type(3, {:type => Types::STOP}, :foo) }.should raise_error(TypeError)
+ lambda { Thrift.check_type(nil, {:type => Types::VOID}, :foo) }.should_not raise_error(TypeError)
+ lambda { Thrift.check_type(3, {:type => Types::VOID}, :foo) }.should raise_error(TypeError)
+ lambda { Thrift.check_type(true, {:type => Types::BOOL}, :foo) }.should_not raise_error(TypeError)
+ lambda { Thrift.check_type(3, {:type => Types::BOOL}, :foo) }.should raise_error(TypeError)
+ lambda { Thrift.check_type(42, {:type => Types::BYTE}, :foo) }.should_not raise_error(TypeError)
+ lambda { Thrift.check_type(42, {:type => Types::I16}, :foo) }.should_not raise_error(TypeError)
+ lambda { Thrift.check_type(42, {:type => Types::I32}, :foo) }.should_not raise_error(TypeError)
+ lambda { Thrift.check_type(42, {:type => Types::I64}, :foo) }.should_not raise_error(TypeError)
+ lambda { Thrift.check_type(3.14, {:type => Types::I32}, :foo) }.should raise_error(TypeError)
+ lambda { Thrift.check_type(3.14, {:type => Types::DOUBLE}, :foo) }.should_not raise_error(TypeError)
+ lambda { Thrift.check_type(3, {:type => Types::DOUBLE}, :foo) }.should raise_error(TypeError)
+ lambda { Thrift.check_type("3", {:type => Types::STRING}, :foo) }.should_not raise_error(TypeError)
+ lambda { Thrift.check_type(3, {:type => Types::STRING}, :foo) }.should raise_error(TypeError)
+ hello = SpecNamespace::Hello.new
+ lambda { Thrift.check_type(hello, {:type => Types::STRUCT, :class => SpecNamespace::Hello}, :foo) }.should_not raise_error(TypeError)
+ lambda { Thrift.check_type("foo", {:type => Types::STRUCT}, :foo) }.should raise_error(TypeError)
+ lambda { Thrift.check_type({:foo => 1}, {:type => Types::MAP}, :foo) }.should_not raise_error(TypeError)
+ lambda { Thrift.check_type([1], {:type => Types::MAP}, :foo) }.should raise_error(TypeError)
+ lambda { Thrift.check_type([1], {:type => Types::LIST}, :foo) }.should_not raise_error(TypeError)
+ lambda { Thrift.check_type({:foo => 1}, {:type => Types::LIST}, :foo) }.should raise_error(TypeError)
+ lambda { Thrift.check_type(Set.new([1,2]), {:type => Types::SET}, :foo) }.should_not raise_error(TypeError)
+ lambda { Thrift.check_type([1,2], {:type => Types::SET}, :foo) }.should raise_error(TypeError)
+ lambda { Thrift.check_type({:foo => true}, {:type => Types::SET}, :foo) }.should raise_error(TypeError)
+ end
+
+ it "should error out if nil is passed and skip_types is false" do
+ lambda { Thrift.check_type(nil, {:type => Types::BOOL}, :foo, false) }.should raise_error(TypeError)
+ lambda { Thrift.check_type(nil, {:type => Types::BYTE}, :foo, false) }.should raise_error(TypeError)
+ lambda { Thrift.check_type(nil, {:type => Types::I16}, :foo, false) }.should raise_error(TypeError)
+ lambda { Thrift.check_type(nil, {:type => Types::I32}, :foo, false) }.should raise_error(TypeError)
+ lambda { Thrift.check_type(nil, {:type => Types::I64}, :foo, false) }.should raise_error(TypeError)
+ lambda { Thrift.check_type(nil, {:type => Types::DOUBLE}, :foo, false) }.should raise_error(TypeError)
+ lambda { Thrift.check_type(nil, {:type => Types::STRING}, :foo, false) }.should raise_error(TypeError)
+ lambda { Thrift.check_type(nil, {:type => Types::STRUCT}, :foo, false) }.should raise_error(TypeError)
+ lambda { Thrift.check_type(nil, {:type => Types::LIST}, :foo, false) }.should raise_error(TypeError)
+ lambda { Thrift.check_type(nil, {:type => Types::SET}, :foo, false) }.should raise_error(TypeError)
+ lambda { Thrift.check_type(nil, {:type => Types::MAP}, :foo, false) }.should raise_error(TypeError)
+ end
+
+ it "should check element types on containers" do
+ field = {:type => Types::LIST, :element => {:type => Types::I32}}
+ lambda { Thrift.check_type([1, 2], field, :foo) }.should_not raise_error(TypeError)
+ lambda { Thrift.check_type([1, nil, 2], field, :foo) }.should raise_error(TypeError)
+ field = {:type => Types::MAP, :key => {:type => Types::I32}, :value => {:type => Types::STRING}}
+ lambda { Thrift.check_type({1 => "one", 2 => "two"}, field, :foo) }.should_not raise_error(TypeError)
+ lambda { Thrift.check_type({1 => "one", nil => "nil"}, field, :foo) }.should raise_error(TypeError)
+ lambda { Thrift.check_type({1 => nil, 2 => "two"}, field, :foo) }.should raise_error(TypeError)
+ field = {:type => Types::SET, :element => {:type => Types::I32}}
+ lambda { Thrift.check_type(Set.new([1, 2]), field, :foo) }.should_not raise_error(TypeError)
+ lambda { Thrift.check_type(Set.new([1, nil, 2]), field, :foo) }.should raise_error(TypeError)
+ lambda { Thrift.check_type(Set.new([1, 2.3, 2]), field, :foo) }.should raise_error(TypeError)
+
+ field = {:type => Types::STRUCT, :class => SpecNamespace::Hello}
+ lambda { Thrift.check_type(SpecNamespace::BoolStruct, field, :foo) }.should raise_error(TypeError)
+ end
+
+ it "should give the TypeError a readable message" do
+ msg = "Expected Types::STRING, received Fixnum for field foo"
+ lambda { Thrift.check_type(3, {:type => Types::STRING}, :foo) }.should raise_error(TypeError, msg)
+ msg = "Expected Types::STRING, received Fixnum for field foo.element"
+ field = {:type => Types::LIST, :element => {:type => Types::STRING}}
+ lambda { Thrift.check_type([3], field, :foo) }.should raise_error(TypeError, msg)
+ msg = "Expected Types::I32, received NilClass for field foo.element.key"
+ field = {:type => Types::LIST,
+ :element => {:type => Types::MAP,
+ :key => {:type => Types::I32},
+ :value => {:type => Types::I32}}}
+ lambda { Thrift.check_type([{nil => 3}], field, :foo) }.should raise_error(TypeError, msg)
+ msg = "Expected Types::I32, received NilClass for field foo.element.value"
+ lambda { Thrift.check_type([{1 => nil}], field, :foo) }.should raise_error(TypeError, msg)
+ end
+ end
+end
diff --git a/lib/rb/spec/unix_socket_spec.rb b/lib/rb/spec/unix_socket_spec.rb
new file mode 100644
index 000000000..df239d7ab
--- /dev/null
+++ b/lib/rb/spec/unix_socket_spec.rb
@@ -0,0 +1,108 @@
+#
+# 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_helper'
+require File.dirname(__FILE__) + "/socket_spec_shared"
+
+class ThriftUNIXSocketSpec < Spec::ExampleGroup
+ include Thrift
+
+ describe UNIXSocket do
+ before(:each) do
+ @path = '/tmp/thrift_spec_socket'
+ @socket = UNIXSocket.new(@path)
+ @handle = mock("Handle", :closed? => false)
+ @handle.stub!(:close)
+ ::UNIXSocket.stub!(:new).and_return(@handle)
+ end
+
+ it_should_behave_like "a socket"
+
+ it "should raise a TransportException when it cannot open a socket" do
+ ::UNIXSocket.should_receive(:new).and_raise(StandardError)
+ lambda { @socket.open }.should raise_error(Thrift::TransportException) { |e| e.type.should == Thrift::TransportException::NOT_OPEN }
+ end
+
+ it "should accept an optional timeout" do
+ ::UNIXSocket.stub!(:new)
+ UNIXSocket.new(@path, 5).timeout.should == 5
+ end
+ end
+
+ describe UNIXServerSocket do
+ before(:each) do
+ @path = '/tmp/thrift_spec_socket'
+ @socket = UNIXServerSocket.new(@path)
+ end
+
+ it "should create a handle when calling listen" do
+ UNIXServer.should_receive(:new).with(@path)
+ @socket.listen
+ end
+
+ it "should create a Thrift::UNIXSocket to wrap accepted sockets" do
+ handle = mock("UNIXServer")
+ UNIXServer.should_receive(:new).with(@path).and_return(handle)
+ @socket.listen
+ sock = mock("sock")
+ handle.should_receive(:accept).and_return(sock)
+ trans = mock("UNIXSocket")
+ UNIXSocket.should_receive(:new).and_return(trans)
+ trans.should_receive(:handle=).with(sock)
+ @socket.accept.should == trans
+ end
+
+ it "should close the handle when closed" do
+ handle = mock("UNIXServer", :closed? => false)
+ UNIXServer.should_receive(:new).with(@path).and_return(handle)
+ @socket.listen
+ handle.should_receive(:close)
+ File.stub!(:delete)
+ @socket.close
+ end
+
+ it "should delete the socket when closed" do
+ handle = mock("UNIXServer", :closed? => false)
+ UNIXServer.should_receive(:new).with(@path).and_return(handle)
+ @socket.listen
+ handle.stub!(:close)
+ File.should_receive(:delete).with(@path)
+ @socket.close
+ end
+
+ it "should return nil when accepting if there is no handle" do
+ @socket.accept.should be_nil
+ end
+
+ it "should return true for closed? when appropriate" do
+ handle = mock("UNIXServer", :closed? => false)
+ UNIXServer.stub!(:new).and_return(handle)
+ File.stub!(:delete)
+ @socket.listen
+ @socket.should_not be_closed
+ handle.stub!(:close)
+ @socket.close
+ @socket.should be_closed
+ @socket.listen
+ @socket.should_not be_closed
+ handle.stub!(:closed?).and_return(true)
+ @socket.should be_closed
+ end
+ end
+end