require 'test/unit' require_relative 'envutil' # to supress warnings for future calls of Module#refine EnvUtil.suppress_warning do Module.new { refine(Object) {} } end class TestRefinement < Test::Unit::TestCase class Foo def x return "Foo#x" end def y return "Foo#y" end def a return "Foo#a" end def call_x return x end end module FooExt refine Foo do def x return "FooExt#x" end def y return "FooExt#y " + super end def z return "FooExt#z" end def a return "FooExt#a" end end end module FooExt2 refine Foo do def x return "FooExt2#x" end def y return "FooExt2#y " + super end def z return "FooExt2#z" end end end class FooSub < Foo def x return "FooSub#x" end def y return "FooSub#y " + super end end eval <<-EOF, TOPLEVEL_BINDING using TestRefinement::FooExt class TestRefinement::FooExtClient def self.invoke_x_on(foo) return foo.x end def self.invoke_y_on(foo) return foo.y end def self.invoke_z_on(foo) return foo.z end def self.send_z_on(foo) return foo.send(:z) end def self.method_z(foo) return foo.method(:z) end def self.invoke_call_x_on(foo) return foo.call_x end end EOF eval <<-EOF, TOPLEVEL_BINDING using TestRefinement::FooExt using TestRefinement::FooExt2 class TestRefinement::FooExtClient2 def self.invoke_y_on(foo) return foo.y end def self.invoke_a_on(foo) return foo.a end end EOF def test_override foo = Foo.new assert_equal("Foo#x", foo.x) assert_equal("FooExt#x", FooExtClient.invoke_x_on(foo)) assert_equal("Foo#x", foo.x) end def test_super foo = Foo.new assert_equal("Foo#y", foo.y) assert_equal("FooExt#y Foo#y", FooExtClient.invoke_y_on(foo)) assert_equal("Foo#y", foo.y) end def test_super_not_chained foo = Foo.new assert_equal("Foo#y", foo.y) assert_equal("FooExt2#y Foo#y", FooExtClient2.invoke_y_on(foo)) assert_equal("Foo#y", foo.y) end def test_using_same_class_refinements foo = Foo.new assert_equal("Foo#a", foo.a) assert_equal("FooExt#a", FooExtClient2.invoke_a_on(foo)) assert_equal("Foo#a", foo.a) end def test_new_method foo = Foo.new assert_raise(NoMethodError) { foo.z } assert_equal("FooExt#z", FooExtClient.invoke_z_on(foo)) assert_raise(NoMethodError) { foo.z } end module RespondTo class Super def foo end end class Sub < Super end module M refine Sub do def foo end end end end def test_send_should_not_use_refinements foo = Foo.new assert_raise(NoMethodError) { foo.send(:z) } assert_raise(NoMethodError) { FooExtClient.send_z_on(foo) } assert_raise(NoMethodError) { foo.send(:z) } assert_equal(true, RespondTo::Sub.new.respond_to?(:foo)) end def test_method_should_not_use_refinements foo = Foo.new assert_raise(NameError) { foo.method(:z) } assert_raise(NameError) { FooExtClient.method_z(foo) } assert_raise(NameError) { foo.method(:z) } end def test_no_local_rebinding foo = Foo.new assert_equal("Foo#x", foo.call_x) assert_equal("Foo#x", FooExtClient.invoke_call_x_on(foo)) assert_equal("Foo#x", foo.call_x) end def test_subclass_is_prior sub = FooSub.new assert_equal("FooSub#x", sub.x) assert_equal("FooSub#x", FooExtClient.invoke_x_on(sub)) assert_equal("FooSub#x", sub.x) end def test_super_in_subclass sub = FooSub.new assert_equal("FooSub#y Foo#y", sub.y) # not "FooSub#y FooExt#y Foo#y" assert_equal("FooSub#y Foo#y", FooExtClient.invoke_y_on(sub)) assert_equal("FooSub#y Foo#y", sub.y) end def test_new_method_on_subclass sub = FooSub.new assert_raise(NoMethodError) { sub.z } assert_equal("FooExt#z", FooExtClient.invoke_z_on(sub)) assert_raise(NoMethodError) { sub.z } end def test_module_eval foo = Foo.new assert_equal("Foo#x", foo.x) assert_equal("Foo#x", FooExt.module_eval { foo.x }) assert_equal("Foo#x", FooExt.module_eval("foo.x")) assert_equal("Foo#x", foo.x) end def test_instance_eval_without_refinement foo = Foo.new ext_client = FooExtClient.new assert_equal("Foo#x", foo.x) assert_equal("Foo#x", ext_client.instance_eval { foo.x }) assert_equal("Foo#x", foo.x) end module FixnumSlashExt refine Fixnum do def /(other) quo(other) end end end def test_override_builtin_method assert_equal(0, 1 / 2) assert_equal(Rational(1, 2), eval_using(FixnumSlashExt, "1 / 2")) assert_equal(0, 1 / 2) end module FixnumPlusExt refine Fixnum do def self.method_added(*args); end def +(other) "overriden" end end end def test_override_builtin_method_with_method_added assert_equal(3, 1 + 2) assert_equal("overriden", eval_using(FixnumPlusExt, "1 + 2")) assert_equal(3, 1 + 2) end def test_return_value_of_refine mod = nil result = nil m = Module.new { result = refine(Object) { mod = self } } assert_equal mod, result end module RefineSameClass REFINEMENT1 = refine(Fixnum) { def foo; return "foo" end } REFINEMENT2 = refine(Fixnum) { def bar; return "bar" end } REFINEMENT3 = refine(String) { def baz; return "baz" end } end def test_refine_same_class_twice assert_equal("foo", eval_using(RefineSameClass, "1.foo")) assert_equal("bar", eval_using(RefineSameClass, "1.bar")) assert_equal(RefineSameClass::REFINEMENT1, RefineSameClass::REFINEMENT2) assert_not_equal(RefineSameClass::REFINEMENT1, RefineSameClass::REFINEMENT3) end module FixnumFooExt refine Fixnum do def foo; "foo"; end end end def test_respond_to_should_not_use_refinements assert_equal(false, 1.respond_to?(:foo)) assert_equal(false, eval_using(FixnumFooExt, "1.respond_to?(:foo)")) end module StringCmpExt refine String do def <=>(other) return 0 end end end module ArrayEachExt refine Array do def each super do |i| yield 2 * i end end end end def test_builtin_method_no_local_rebinding assert_equal(false, eval_using(StringCmpExt, '"1" >= "2"')) assert_equal(1, eval_using(ArrayEachExt, "[1, 2, 3].min")) end module RefinePrependedClass module M1 def foo super << :m1 end end class C prepend M1 def foo [:c] end end module M2 refine C do def foo super << :m2 end end end end def test_refine_prepended_class x = eval_using(RefinePrependedClass::M2, "TestRefinement::RefinePrependedClass::C.new.foo") assert_equal([:c, :m1, :m2], x) end def test_refine_module m1 = Module.new assert_raise(TypeError) do Module.new { refine m1 do def foo :m2 end end } end end def test_refine_neither_class_nor_module assert_raise(TypeError) do Module.new { refine Object.new do end } end assert_raise(TypeError) do Module.new { refine 123 do end } end assert_raise(TypeError) do Module.new { refine "foo" do end } end end def test_refine_in_class assert_raise(NoMethodError) do Class.new { refine Fixnum do def foo "c" end end } end end def test_main_using assert_in_out_err([], <<-INPUT, %w(:C :M), /Refinements are experimental/) class C def foo :C end end module M refine C do def foo :M end end end c = C.new p c.foo using M p c.foo INPUT end def test_main_using_is_private assert_raise(NoMethodError) do eval("self.using Module.new", TOPLEVEL_BINDING) end end def test_no_kernel_using assert_raise(NoMethodError) do using Module.new end end def test_no_module_using assert_raise(NoMethodError) do Module.new { using Module.new } end end class UsingClass end def test_module_using_class c = Class.new assert_raise(TypeError) do eval("using TestRefinement::UsingClass", TOPLEVEL_BINDING) end end def test_refine_without_block c1 = Class.new e = assert_raise(ArgumentError) { Module.new do refine c1 end } assert_equal("no block given", e.message) end module Inspect module M Fixnum = refine(Fixnum) {} end end def test_inspect assert_equal("#", Inspect::M::Fixnum.inspect) end def test_using_method_cache assert_in_out_err([], <<-INPUT, %w(:M1 :M2), /Refinements are experimental/) class C def foo "original" end end module M1 refine C do def foo :M1 end end end module M2 refine C do def foo :M2 end end end c = C.new using M1 p c.foo using M2 p c.foo INPUT end module RedefineRefinedMethod class C def foo "original" end end module M refine C do def foo "refined" end end end class C def foo "redefined" end end end def test_redefine_refined_method x = eval_using(RedefineRefinedMethod::M, "TestRefinement::RedefineRefinedMethod::C.new.foo") assert_equal("refined", x) end module StringExt refine String do def foo "foo" end end end module RefineScoping refine String do def foo "foo" end def RefineScoping.call_in_refine_block "".foo end end def self.call_outside_refine_block "".foo end end def test_refine_scoping assert_equal("foo", RefineScoping.call_in_refine_block) assert_raise(NoMethodError) do RefineScoping.call_outside_refine_block end end module StringRecursiveLength refine String do def recursive_length if empty? 0 else self[1..-1].recursive_length + 1 end end end end def test_refine_recursion x = eval_using(StringRecursiveLength, "'foo'.recursive_length") assert_equal(3, x) end module ToJSON refine Integer do def to_json; to_s; end end refine Array do def to_json; "[" + map { |i| i.to_json }.join(",") + "]" end end refine Hash do def to_json; "{" + map { |k, v| k.to_s.dump + ":" + v.to_json }.join(",") + "}" end end end def test_refine_mutual_recursion x = eval_using(ToJSON, "[{1=>2}, {3=>4}].to_json") assert_equal('[{"1":2},{"3":4}]', x) end def test_refine_with_proc assert_raise(ArgumentError) do Module.new { refine(String, &Proc.new {}) } end end def test_using_in_module assert_raise(RuntimeError) do eval(<<-EOF, TOPLEVEL_BINDING) $main = self module M end module M2 $main.send(:using, M) end EOF end end def test_using_in_method assert_raise(RuntimeError) do eval(<<-EOF, TOPLEVEL_BINDING) $main = self module M end def call_using_in_method $main.send(:using, M) end call_using_in_method EOF end end module IncludeIntoRefinement class C def bar return "C#bar" end def baz return "C#baz" end end module Mixin def foo return "Mixin#foo" end def bar return super << " Mixin#bar" end def baz return super << " Mixin#baz" end end module M refine C do include Mixin def baz return super << " M#baz" end end end end eval <<-EOF, TOPLEVEL_BINDING using TestRefinement::IncludeIntoRefinement::M module TestRefinement::IncludeIntoRefinement::User def self.invoke_foo_on(x) x.foo end def self.invoke_bar_on(x) x.bar end def self.invoke_baz_on(x) x.baz end end EOF def test_include_into_refinement x = IncludeIntoRefinement::C.new assert_equal("Mixin#foo", IncludeIntoRefinement::User.invoke_foo_on(x)) assert_equal("C#bar Mixin#bar", IncludeIntoRefinement::User.invoke_bar_on(x)) assert_equal("C#baz Mixin#baz M#baz", IncludeIntoRefinement::User.invoke_baz_on(x)) end module PrependIntoRefinement class C def bar return "C#bar" end def baz return "C#baz" end end module Mixin def foo return "Mixin#foo" end def bar return super << " Mixin#bar" end def baz return super << " Mixin#baz" end end module M refine C do prepend Mixin def baz return super << " M#baz" end end end end eval <<-EOF, TOPLEVEL_BINDING using TestRefinement::PrependIntoRefinement::M module TestRefinement::PrependIntoRefinement::User def self.invoke_foo_on(x) x.foo end def self.invoke_bar_on(x) x.bar end def self.invoke_baz_on(x) x.baz end end EOF def test_prepend_into_refinement x = PrependIntoRefinement::C.new assert_equal("Mixin#foo", PrependIntoRefinement::User.invoke_foo_on(x)) assert_equal("C#bar Mixin#bar", PrependIntoRefinement::User.invoke_bar_on(x)) assert_equal("C#baz M#baz Mixin#baz", PrependIntoRefinement::User.invoke_baz_on(x)) end module PrependAfterRefine class C def foo "original" end end module M refine C do def foo "refined" end def bar "refined" end end end module Mixin def foo "mixin" end def bar "mixin" end end class C prepend Mixin end end def test_prepend_after_refine x = eval_using(PrependAfterRefine::M, "TestRefinement::PrependAfterRefine::C.new.foo") assert_equal("refined", x) assert_equal("mixin", TestRefinement::PrependAfterRefine::C.new.foo) y = eval_using(PrependAfterRefine::M, "TestRefinement::PrependAfterRefine::C.new.bar") assert_equal("refined", y) assert_equal("mixin", TestRefinement::PrependAfterRefine::C.new.bar) end private def eval_using(mod, s) eval("using #{mod}; #{s}", TOPLEVEL_BINDING) end end