Re-define the semantics of deprecate! and add deprecate_class! Include full specs for deprecate!/deprecate_class!


git-svn-id: https://svn.apache.org/repos/asf/incubator/thrift/trunk@668906 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/lib/rb/lib/thrift/deprecation.rb b/lib/rb/lib/thrift/deprecation.rb
index 7456f1c..0ee4e26 100644
--- a/lib/rb/lib/thrift/deprecation.rb
+++ b/lib/rb/lib/thrift/deprecation.rb
@@ -1,19 +1,53 @@
-class Module
-   def deprecate!(*method_names)
-     method_names.each do |method_name|
-       module_eval <<-END
-         alias_method :deprecated_#{method_name}, :#{method_name}
-         def #{method_name}(*args, &block)
-           $stderr.puts "Warning: calling deprecated method: #{self}.#{method_name}"
-           deprecated_#{method_name}(*args, &block)
-         end
-       END
-      end
-   end
+# provide a backwards-compatible wrapper API and deprecate it
+
+unless Thrift.const_defined?(:DEPRECATION)
+  Thrift::DEPRECATION = true
 end
 
-require 'thrift/transport/ttransport'
+class Module
+  # Wraps the given methods to print a warning and call the real method
+  # Example:
+  #   deprecate! :readAll => :read_all
+  def deprecate!(methods)
+    methods.each_pair do |old, new|
+      module_eval <<-EOF
+        def #{old}(*args, &block)
+          old, new = #{[old,new].inspect}
+          STDERR.puts "Warning: calling deprecated method \#{self.is_a?(Module) ? "\#{self}." : "\#{self.class}#"}\#{old}"
+          target = (self.is_a?(Module) ? (class << self;self;end) : self.class)
+          target.send :define_method, old, target.instance_method(new) # unwrap
+          target.instance_method(new).bind(self).call(*args, &block)
+        end
+      EOF
+    end
+  end
+end
 
-class TTransport
-  deprecate! :isOpen, :readAll
-end
\ No newline at end of file
+module Kernel
+  # Provides an alternate name for the class for deprecation purposes
+  # Example:
+  #   deprecate_class! :TBinaryProtocol => Thrift::BinaryProtocol
+  #--
+  # at the moment this only works for creating top-level constants
+  # if necessary, this can be extended to take something like :'Thrift::TBinaryProtocol'
+  # alternately, Module can be extended with a similar method
+  #
+  # another idea is to not make the old name a pointer to the new, but rather
+  # a pointer to a proxy class that logs deprecation warnings and forwards methods
+  def deprecate_class!(klasses)
+    klasses.each_pair do |old, new|
+      Object.const_set old, new
+    end
+  end
+end
+
+# TProtocol = Thrift::Protocol
+# TProtocolException = Thrift::ProtocolException
+# ThriftStruct = Thrift::Struct
+# ThriftClient = Thrift::Client
+# TProcessor = Thrift::Processor
+# TType = Thrift::Types
+# TMessageType = Thrift::MessageTypes
+# TException = Thrift::Exception
+# TApplicationException = Thrift::ApplicationException
+# TBinaryProtocol = Thrift::BinaryProtocol
diff --git a/lib/rb/spec/deprecation_spec.rb b/lib/rb/spec/deprecation_spec.rb
new file mode 100644
index 0000000..62ff396
--- /dev/null
+++ b/lib/rb/spec/deprecation_spec.rb
@@ -0,0 +1,152 @@
+require File.dirname(__FILE__) + '/spec_helper'
+
+describe 'deprecate!' do
+  def stub_stderr(callstr)
+    STDERR.should_receive(:puts).with("Warning: calling deprecated method #{callstr}")
+  end
+
+  it "should work for Module methods" do
+    mod = Module.new do
+      class << self
+        def new
+          "new"
+        end
+        deprecate! :old => :new
+      end
+    end
+    stub_stderr "#{mod.inspect}.old"
+    mod.old.should == "new"
+  end
+
+  it "should work with Modules that extend themselves" do
+    mod = Module.new do
+      extend self
+      def new
+        "new"
+      end
+      deprecate! :old => :new
+    end
+    stub_stderr "#{mod.inspect}.old"
+    mod.old.should == "new"
+  end
+
+  it "should work wtih Class methods" do
+    klass = Class.new do
+      class << self
+        def new
+          "new"
+        end
+        deprecate! :old => :new
+      end
+    end
+    stub_stderr "#{klass.inspect}.old"
+    klass.old.should == "new"
+  end
+
+  it "should work with Classes that include Modules" do
+    mod = Module.new do
+      def new
+        "new"
+      end
+      deprecate! :old => :new
+    end
+    klass = Class.new do
+      include mod
+    end
+    stub_stderr "#{klass.inspect}#old"
+    klass.new.old.should == "new"
+  end
+
+  it "should work with instance methods" do
+    klass = Class.new do
+      def new
+        "new"
+      end
+      deprecate! :old => :new
+    end
+    stub_stderr "#{klass.inspect}#old"
+    klass.new.old.should == "new"
+  end
+
+  it "should work with multiple method names" do
+    klass = Class.new do
+      def new1
+        "new 1"
+      end
+      def new2
+        "new 2"
+      end
+      deprecate! :old1 => :new1, :old2 => :new2
+    end
+    stub_stderr("#{klass.inspect}#old1").ordered
+    stub_stderr("#{klass.inspect}#old2").ordered
+    inst = klass.new
+    inst.old1.should == "new 1"
+    inst.old2.should == "new 2"
+  end
+
+  it "should only log a message once, even across multiple instances" do
+    klass = Class.new do
+      def new
+        "new"
+      end
+      deprecate! :old => :new
+    end
+    stub_stderr("#{klass.inspect}#old").once
+    klass.new.old.should == "new"
+    klass.new.old.should == "new"
+  end
+
+  it "should pass arguments" do
+    klass = Class.new do
+      def new(a, b)
+        "new: #{a + b}"
+      end
+      deprecate! :old => :new
+    end
+    stub_stderr("#{klass.inspect}#old")
+    klass.new.old(3, 5).should == "new: 8"
+  end
+
+  it "should pass blocks" do
+    klass = Class.new do
+      def new
+        "new #{yield}"
+      end
+      deprecate! :old => :new
+    end
+    stub_stderr("#{klass.inspect}#old")
+    klass.new.old { "block" }.should == "new block"
+  end
+
+  it "should not freeze the definition of the new method" do
+    klass = Class.new do
+      def new
+        "new"
+      end
+      deprecate! :old => :new
+    end
+    klass.send :define_method, :new do
+      "new 2"
+    end
+    stub_stderr("#{klass.inspect}#old")
+    klass.new.old.should == "new 2"
+  end
+end
+
+describe "deprecate_class!" do
+  it "should create a new global constant that points to the old one" do
+    begin
+      klass = Class.new do
+        def foo
+          "foo"
+        end
+      end
+      deprecate_class! :DeprecationSpecOldClass => klass
+      DeprecationSpecOldClass.should eql(klass)
+      DeprecationSpecOldClass.new.foo.should == "foo"
+    ensure
+      Object.send :remove_const, :DeprecationSpecOldClass
+    end
+  end
+end
diff --git a/lib/rb/spec/spec_helper.rb b/lib/rb/spec/spec_helper.rb
new file mode 100644
index 0000000..1c67cb2
--- /dev/null
+++ b/lib/rb/spec/spec_helper.rb
@@ -0,0 +1,4 @@
+require 'rubygems'
+require 'spec'
+
+require File.dirname(__FILE__) + '/../lib/thrift'