require 'test/unit' require_relative 'envutil' class TestRefinement < Test::Unit::TestCase class Foo def x return "Foo#x" end def y return "Foo#y" 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 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 class FooExtClient using FooExt 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 class FooExtClient2 using FooExt using FooExt2 def self.invoke_y_on(foo) return foo.y end end 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_chain foo = Foo.new assert_equal("Foo#y", foo.y) assert_equal("FooExt2#y FooExt#y Foo#y", FooExtClient2.invoke_y_on(foo)) assert_equal("Foo#y", foo.y) 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 def test_new_method_by_send foo = Foo.new assert_raise(NoMethodError) { foo.send(:z) } assert_equal("FooExt#z", FooExtClient.send_z_on(foo)) assert_raise(NoMethodError) { foo.send(:z) } end def test_new_method_by_method_object foo = Foo.new assert_raise(NoMethodError) { foo.send(:z) } assert_equal("FooExt#z", FooExtClient.method_z(foo).call) assert_raise(NoMethodError) { foo.send(: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("FooExt#x", FooExt.module_eval { foo.x }) assert_equal("Foo#x", foo.x) end def test_instance_eval foo = Foo.new ext_client = FooExtClient.new assert_equal("Foo#x", foo.x) assert_equal("FooExt#x", ext_client.instance_eval { foo.x }) assert_equal("Foo#x", foo.x) end def test_override_builtin_method m = Module.new { refine Fixnum do def /(other) quo(other) end end } assert_equal(0, 1 / 2) assert_equal(Rational(1, 2), m.module_eval { 1 / 2 }) assert_equal(0, 1 / 2) end def test_override_builtin_method_with_method_added m = Module.new { refine Fixnum do def self.method_added(*args); end def +(other) "overriden" end end } assert_equal(3, 1 + 2) assert_equal("overriden", m.module_eval { 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 def test_refine_same_class_twice result1 = nil result2 = nil result3 = nil m = Module.new { result1 = refine(Fixnum) { def foo; return "foo" end } result2 = refine(Fixnum) { def bar; return "bar" end } result3 = refine(String) { def baz; return "baz" end } } assert_equal("foo", m.module_eval { 1.foo }) assert_equal("bar", m.module_eval { 1.bar }) assert_equal(result1, result2) assert_not_equal(result1, result3) end def test_respond_to? m = Module.new { refine Fixnum do def foo; "foo"; end end } assert_equal(false, 1.respond_to?(:foo)) assert_equal(true, m.module_eval { 1.respond_to?(:foo) }) assert_equal(false, 1.respond_to?(:foo)) end def test_builtin_method_no_local_rebinding m = Module.new { refine String do def <=>(other) return 0 end end } assert_equal(false, m.module_eval { "1" >= "2" }) m2 = Module.new { refine Array do def each super do |i| yield 2 * i end end end } a = [1, 2, 3] assert_equal(1, m2.module_eval { a.min }) end def test_module_inclusion m1 = Module.new { def foo "m1#foo" end def bar "m1#bar" end } m2 = Module.new { def bar "m2#bar" end def baz "m2#baz" end } m3 = Module.new { def baz "m3#baz" end } include_proc = Proc.new { include m3, m2 } m = Module.new { refine String do include m1 module_eval(&include_proc) def call_foo foo end def call_bar bar end def call_baz baz end end def self.call_foo(s) s.foo end def self.call_bar(s) s.bar end def self.call_baz(s) s.baz end } assert_equal("m1#foo", m.module_eval { "abc".foo }) assert_equal("m2#bar", m.module_eval { "abc".bar }) assert_equal("m3#baz", m.module_eval { "abc".baz }) assert_equal("m1#foo", m.module_eval { "abc".call_foo }) assert_equal("m2#bar", m.module_eval { "abc".call_bar }) assert_equal("m3#baz", m.module_eval { "abc".call_baz }) assert_equal("m1#foo", m.call_foo("abc")) assert_equal("m2#bar", m.call_bar("abc")) assert_equal("m3#baz", m.call_baz("abc")) end def test_refine_prepended_class m1 = Module.new { def foo super << :m1 end } c = Class.new { prepend m1 def foo [:c] end } m2 = Module.new { refine c do def foo super << :m2 end end } obj = c.new assert_equal([:c, :m1, :m2], m2.module_eval { obj.foo }) end def test_refine_module_without_overriding m1 = Module.new c = Class.new { include m1 } m2 = Module.new { refine m1 do def foo :m2 end end } obj = c.new assert_equal(:m2, m2.module_eval { obj.foo }) end def test_refine_module_with_overriding m1 = Module.new { def foo [:m1] end } c = Class.new { include m1 } m2 = Module.new { refine m1 do def foo super << :m2 end end } obj = c.new assert_equal([:m1, :m2], m2.module_eval { obj.foo }) end def test_refine_module_with_double_overriding m1 = Module.new { def foo [:m1] end } c = Class.new { include m1 } m2 = Module.new { refine m1 do def foo super << :m2 end end } m3 = Module.new { using m2 refine m1 do def foo super << :m3 end end } obj = c.new assert_equal([:m1, :m2, :m3], m3.module_eval { obj.foo }) end def test_refine_module_and_call_superclass_method m1 = Module.new c1 = Class.new { def foo "c1#foo" end } c2 = Class.new(c1) { include m1 } m2 = Module.new { refine m1 do end } obj = c2.new assert_equal("c1#foo", m2.module_eval { obj.foo }) 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_and_class_eval c = Class.new { refine Fixnum do def foo "c" end end } assert_equal("c", c.class_eval { 123.foo }) end def test_main_using assert_in_out_err([], <<-INPUT, %w(:C :M), []) 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_no_kernel_using assert_raise(NoMethodError) do using Module.new end end def test_module_using_class c = Class.new m = Module.new assert_raise(TypeError) do m.module_eval do using c end end end def test_refinements_empty m = Module.new assert(m.refinements.empty?) end def test_refinements_one c = Class.new c_ext = nil m = Module.new { refine c do c_ext = self end } assert_equal({c => c_ext}, m.refinements) end def test_refinements_two c1 = Class.new c1_ext = nil c2 = Class.new c2_ext = nil m = Module.new { refine c1 do c1_ext = self end refine c2 do c2_ext = self end } assert_equal({c1 => c1_ext, c2 => c2_ext}, m.refinements) end def test_refinements_duplicate_refine c = Class.new c_ext = nil m = Module.new { refine c do c_ext = self end refine c do end } assert_equal({c => c_ext}, m.refinements) end def test_refinements_no_recursion c1 = Class.new c1_ext = nil m1 = Module.new { refine c1 do c1_ext = self end } c2 = Class.new c2_ext = nil m2 = Module.new { using m1 refine c2 do c2_ext = self end } assert_equal({c2 => c2_ext}, m2.refinements) 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 IndirectUsing class C end module M1 refine C do def m1 :m1 end end end module M2 refine C do def m2 :m2 end end end module M3 using M1 using M2 end module M using M3 def self.call_m1 C.new.m1 end def self.call_m2 C.new.m2 end end end def test_indirect_using assert_equal(:m1, IndirectUsing::M.call_m1) assert_equal(:m2, IndirectUsing::M.call_m2) end def test_indirect_using_module_eval c = Class.new m1 = Module.new { refine c do def m1 :m1 end end } m2 = Module.new { refine c do def m2 :m2 end end } m3 = Module.new { using m1 using m2 } m = Module.new { using m3 } assert_equal(:m1, m.module_eval { c.new.m1 }) assert_equal(:m2, m.module_eval { c.new.m2 }) end module SymbolToProc class C end module M refine C do def foo "foo" end end def self.call_foo c = C.new :foo.to_proc.call(c) end def self.foo_proc :foo.to_proc end end end def test_symbol_to_proc assert_equal("foo", SymbolToProc::M.call_foo) assert_equal("foo", SymbolToProc::M.foo_proc.call(SymbolToProc::C.new)) end module Inspect module M refine Fixnum do end end end def test_inspect assert_equal("#", Inspect::M.refinements[Fixnum].inspect) c = Class.new m = Module.new { refine String do end refine c do end } assert_equal("#", m.refinements[String].inspect) assert_equal("#", m.refinements[c].inspect) end module InlineMethodCache class C def foo "original" end end module M refine C do def foo "refined" end end end end def test_inline_method_cache skip "can't implement efficiently with the current implementation of refinements" c = InlineMethodCache::C.new f = Proc.new { c.foo } assert_equal("original", f.call) assert_equal("refined", InlineMethodCache::M.module_eval(&f)) assert_equal("original", f.call) end module UsingMethodCache 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 module M c = C.new ORIGINAL_FOO = c.foo using M1 c.foo using M2 M2_FOO = c.foo end end def test_using_method_cache assert_equal("original", UsingMethodCache::M::ORIGINAL_FOO) assert_equal("M2", UsingMethodCache::M::M2_FOO) assert_in_out_err([], <<-INPUT, %w(:M1 :M2), []) 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 def test_circular_using_is_not_allowed a = Module.new b = Module.new assert_raise ArgumentError do a.module_eval do using a end end b.module_eval do using a end assert_raise ArgumentError do a.module_eval do using b end end end end