Thrift now a TLP - INFRA-3116

git-svn-id: https://svn.apache.org/repos/asf/thrift/branches/0.1.x@1028168 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/lib/rb/spec/ThriftSpec.thrift b/lib/rb/spec/ThriftSpec.thrift
new file mode 100644
index 0000000..fe5a8aa
--- /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 0000000..efb16d8
--- /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 0000000..7189775
--- /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 0000000..0306cf5
--- /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 0000000..0abccb8
--- /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 0000000..c6608e0
--- /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 0000000..e707d81
--- /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 0000000..b9a7981
--- /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 0000000..fc32137
--- /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 0000000..94526de
--- /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 0000000..c994491
--- /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 0000000..a0e86cf
--- /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 0000000..d35f652
--- /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 0000000..82f374b
--- /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 0000000..fce5013
--- /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 0000000..ffe9bff
--- /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 0000000..dd8b0f9
--- /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 0000000..96b433b
--- /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 0000000..3c20bf9
--- /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 0000000..23a701e
--- /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 0000000..d979cfb
--- /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 0000000..df239d7
--- /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