# frozen_string_literal: false
require 'test/unit'

class TestRefinement < Test::Unit::TestCase
  module Sandbox
    BINDING = binding
  end

  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

  class FooExtClient
    using TestRefinement::FooExt

    begin
      def self.map_x_on(foo)
        [foo].map(&:x)[0]
      end

      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

      def self.return_proc(&block)
        block
      end
    end
  end

  class TestRefinement::FooExtClient2
    using TestRefinement::FooExt
    using TestRefinement::FooExt2

    begin
      def self.invoke_y_on(foo)
        return foo.y
      end

      def self.invoke_a_on(foo)
        return foo.a
      end
    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_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_use_refinements
    foo = Foo.new
    assert_raise(NoMethodError) { foo.send(:z) }
    assert_equal("FooExt#z", 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 IntegerSlashExt
    refine Integer 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(IntegerSlashExt, "1 / 2"))
    assert_equal(0, 1 / 2)
  end

  module IntegerPlusExt
    refine Integer do
      def self.method_added(*args); end
      def +(other) "overridden" end
    end
  end

  def test_override_builtin_method_with_method_added
    assert_equal(3, 1 + 2)
    assert_equal("overridden", eval_using(IntegerPlusExt, "1 + 2"))
    assert_equal(3, 1 + 2)
  end

  def test_return_value_of_refine
    mod = nil
    result = nil
    Module.new {
      result = refine(Object) {
        mod = self
      }
    }
    assert_equal mod, result
  end

  module RefineSameClass
    REFINEMENT1 = refine(Integer) {
      def foo; return "foo" end
    }
    REFINEMENT2 = refine(Integer) {
      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 IntegerFooExt
    refine Integer 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(IntegerFooExt, "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

  module RefineModule
    module M
      def foo
        "M#foo"
      end

      def bar
        "M#bar"
      end

      def baz
        "M#baz"
      end
    end

    class C
      include M

      def baz
        "#{super} C#baz"
      end
    end

    module M2
      refine M do
        def foo
          "M@M2#foo"
        end

        def bar
          "#{super} M@M2#bar"
        end

        def baz
          "#{super} M@M2#baz"
        end
      end
    end

    using M2

    def self.call_foo
      C.new.foo
    end

    def self.call_bar
      C.new.bar
    end

    def self.call_baz
      C.new.baz
    end
  end

  def test_refine_module
    assert_equal("M@M2#foo", RefineModule.call_foo)
    assert_equal("M#bar M@M2#bar", RefineModule.call_bar)
    assert_equal("M#baz C#baz", RefineModule.call_baz)
  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 Integer do
          def foo
            "c"
          end
        end
      }
    end
  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_main_using_is_private
    assert_raise(NoMethodError) do
      eval("self.using Module.new", Sandbox::BINDING)
    end
  end

  def test_no_kernel_using
    assert_raise(NoMethodError) do
      using Module.new
    end
  end

  class UsingClass
  end

  def test_module_using_class
    assert_raise(TypeError) do
      eval("using TestRefinement::UsingClass", Sandbox::BINDING)
    end
  end

  def test_refine_without_block
    c1 = Class.new
    assert_raise_with_message(ArgumentError, "no block given") {
      Module.new do
        refine c1
      end
    }
  end

  module Inspect
    module M
      Integer = refine(Integer) {}
    end
  end

  def test_inspect
    assert_equal("#<refinement:Integer@TestRefinement::Inspect::M>",
                 Inspect::M::Integer.inspect)
  end

  def test_using_method_cache
    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

  module RedefineRefinedMethod
    class C
      def foo
        "original"
      end
    end

    module M
      refine C do
        def foo
          "refined"
        end
      end
    end

    class C
      EnvUtil.suppress_warning do
        def foo
          "redefined"
        end
      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, Sandbox::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, Sandbox::BINDING)
        $main = self
        module M
        end
        class C
          def call_using_in_method
            $main.send(:using, M)
          end
        end
        C.new.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, Sandbox::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, Sandbox::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

  PrependAfterRefine_CODE = <<-EOC
  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
  EOC
  eval PrependAfterRefine_CODE

  def test_prepend_after_refine_wb_miss
    if /\A(arm|mips)/ =~ RUBY_PLATFORM
      skip "too slow cpu"
    end
    assert_normal_exit %Q{
      GC.stress = true
      10.times{
        #{PrependAfterRefine_CODE}
        undef PrependAfterRefine
      }
    }
  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

  module SuperInBlock
    class C
      def foo(*args)
        [:foo, *args]
      end
    end

    module R
      refine C do
        def foo(*args)
          tap do
            return super(:ref, *args)
          end
        end
      end
    end
  end

  def test_super_in_block
    bug7925 = '[ruby-core:52750] [Bug #7925]'
    x = eval_using(SuperInBlock::R,
                   "TestRefinement:: SuperInBlock::C.new.foo(#{bug7925.dump})")
    assert_equal([:foo, :ref, bug7925], x, bug7925)
  end

  module ModuleUsing
    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

  def test_module_using
    foo = Foo.new
    assert_equal("Foo#x", foo.x)
    assert_equal("Foo#y", foo.y)
    assert_raise(NoMethodError) { foo.z }
    assert_equal("FooExt#x", ModuleUsing.invoke_x_on(foo))
    assert_equal("FooExt#y Foo#y", ModuleUsing.invoke_y_on(foo))
    assert_equal("FooExt#z", ModuleUsing.invoke_z_on(foo))
    assert_equal("Foo#x", foo.x)
    assert_equal("Foo#y", foo.y)
    assert_raise(NoMethodError) { foo.z }
  end

  def test_module_using_in_method
    assert_raise(RuntimeError) do
      Module.new.send(:using, FooExt)
    end
  end

  def test_module_using_invalid_self
    assert_raise(RuntimeError) do
      eval <<-EOF, Sandbox::BINDING
        module TestRefinement::TestModuleUsingInvalidSelf
          Module.new.send(:using, TestRefinement::FooExt)
        end
      EOF
    end
  end

  class Bar
  end

  module BarExt
    refine Bar do
      def x
        return "BarExt#x"
      end
    end
  end

  module FooBarExt
    include FooExt
    include BarExt
  end

  module FooBarExtClient
    using FooBarExt

    def self.invoke_x_on(foo)
      return foo.x
    end
  end

  def test_module_inclusion
    foo = Foo.new
    assert_equal("FooExt#x", FooBarExtClient.invoke_x_on(foo))
    bar = Bar.new
    assert_equal("BarExt#x", FooBarExtClient.invoke_x_on(bar))
  end

  module FooFoo2Ext
    include FooExt
    include FooExt2
  end

  module FooFoo2ExtClient
    using FooFoo2Ext

    def self.invoke_x_on(foo)
      return foo.x
    end

    def self.invoke_y_on(foo)
      return foo.y
    end
  end

  def test_module_inclusion2
    foo = Foo.new
    assert_equal("FooExt2#x", FooFoo2ExtClient.invoke_x_on(foo))
    assert_equal("FooExt2#y Foo#y", FooFoo2ExtClient.invoke_y_on(foo))
  end

  def test_eval_scoping
    assert_in_out_err([], <<-INPUT, ["HELLO WORLD", "dlrow olleh", "HELLO WORLD"], [])
      module M
        refine String do
          def upcase
            reverse
          end
        end
      end

      puts "hello world".upcase
      puts eval(%{using M; "hello world".upcase})
      puts "hello world".upcase
    INPUT
  end

  def test_eval_with_binding_scoping
    assert_in_out_err([], <<-INPUT, ["HELLO WORLD", "dlrow olleh", "dlrow olleh"], [])
      module M
        refine String do
          def upcase
            reverse
          end
        end
      end

      puts "hello world".upcase
      b = binding
      puts eval(%{using M; "hello world".upcase}, b)
      puts eval(%{"hello world".upcase}, b)
    INPUT
  end

  def test_case_dispatch_is_aware_of_refinements
    assert_in_out_err([], <<-RUBY, ["refinement used"], [])
      module RefineSymbol
        refine Symbol do
          def ===(other)
            true
          end
        end
      end

      using RefineSymbol

      case :a
      when :b
        puts "refinement used"
      else
        puts "refinement not used"
      end
    RUBY
  end

  def test_refine_after_using
    assert_separately([], <<-"end;")
      bug8880 = '[ruby-core:57079] [Bug #8880]'
      module Test
        refine(String) do
        end
      end
      using Test
      def t
        'Refinements are broken!'.chop!
      end
      t
      module Test
        refine(String) do
          def chop!
            self.sub!(/broken/, 'fine')
          end
        end
      end
      assert_equal('Refinements are fine!', t, bug8880)
    end;
  end

  def test_instance_methods
    bug8881 = '[ruby-core:57080] [Bug #8881]'
    assert_not_include(Foo.instance_methods(false), :z, bug8881)
    assert_not_include(FooSub.instance_methods(true), :z, bug8881)
  end

  def test_method_defined
    assert_not_send([Foo, :method_defined?, :z])
    assert_not_send([FooSub, :method_defined?, :z])
  end

  def test_undef_refined_method
    bug8966 = '[ruby-core:57466] [Bug #8966]'

    assert_in_out_err([], <<-INPUT, ["NameError"], [], bug8966)
      module Foo
        refine Object do
          def foo
            puts "foo"
          end
        end
      end

      using Foo

      class Object
        begin
          undef foo
        rescue Exception => e
          p e.class
        end
      end
    INPUT

    assert_in_out_err([], <<-INPUT, ["NameError"], [], bug8966)
      module Foo
        refine Object do
          def foo
            puts "foo"
          end
        end
      end

      # without `using Foo'

      class Object
        begin
          undef foo
        rescue Exception => e
          p e.class
        end
      end
    INPUT
  end

  def test_refine_undefed_method_and_call
    assert_in_out_err([], <<-INPUT, ["NoMethodError"], [])
      class Foo
        def foo
        end

        undef foo
      end

      module FooExt
        refine Foo do
          def foo
          end
        end
      end

      begin
        Foo.new.foo
      rescue => e
        p e.class
      end
    INPUT
  end

  def test_refine_undefed_method_and_send
    assert_in_out_err([], <<-INPUT, ["NoMethodError"], [])
      class Foo
        def foo
        end

        undef foo
      end

      module FooExt
        refine Foo do
          def foo
          end
        end
      end

      begin
        Foo.new.send(:foo)
      rescue => e
        p e.class
      end
    INPUT
  end

  def test_adding_private_method
    bug9452 = '[ruby-core:60111] [Bug #9452]'

    assert_in_out_err([], <<-INPUT, ["Success!", "NoMethodError"], [], bug9452)
      module R
        refine Object do
          def m
            puts "Success!"
          end

          private(:m)
        end
      end

      using R

      m
      42.m rescue p($!.class)
    INPUT
  end

  def test_making_private_method_public
    bug9452 = '[ruby-core:60111] [Bug #9452]'

    assert_in_out_err([], <<-INPUT, ["Success!", "Success!"], [], bug9452)
        class Object
          private
          def m
          end
        end

        module R
          refine Object do
            def m
              puts "Success!"
            end
          end
        end

        using R
        m
        42.m
    INPUT
  end

  def test_refine_basic_object
    assert_separately([], <<-"end;")
    bug10106 = '[ruby-core:64166] [Bug #10106]'
    module RefinementBug
      refine BasicObject do
        def foo
          1
        end
      end
    end

    assert_raise(NoMethodError, bug10106) {Object.new.foo}
    end;

    assert_separately([], <<-"end;")
    bug10707 = '[ruby-core:67389] [Bug #10707]'
    module RefinementBug
      refine BasicObject do
        def foo
        end
      end
    end

    assert(methods, bug10707)
    assert_raise(NameError, bug10707) {method(:foo)}
    end;
  end

  def test_change_refined_new_method_visibility
    assert_separately([], <<-"end;")
    bug10706 = '[ruby-core:67387] [Bug #10706]'
    module RefinementBug
      refine Object do
        def foo
        end
      end
    end

    assert_raise(NameError, bug10706) {private(:foo)}
    end;
  end

  def test_alias_refined_method
    assert_separately([], <<-"end;")
    bug10731 = '[ruby-core:67523] [Bug #10731]'

    class C
    end

    module RefinementBug
      refine C do
        def foo
        end

        def bar
        end
      end
    end

    assert_raise(NameError, bug10731) do
      class C
        alias foo bar
      end
    end
    end;
  end

  def test_singleton_method_should_not_use_refinements
    assert_separately([], <<-"end;")
    bug10744 = '[ruby-core:67603] [Bug #10744]'

    class C
    end

    module RefinementBug
      refine C.singleton_class do
        def foo
        end
      end
    end

    assert_raise(NameError, bug10744) { C.singleton_method(:foo) }
    end;
  end

  def test_refined_method_defined
    assert_separately([], <<-"end;")
    bug10753 = '[ruby-core:67656] [Bug #10753]'

    c = Class.new do
      def refined_public; end
      def refined_protected; end
      def refined_private; end

      public :refined_public
      protected :refined_protected
      private :refined_private
    end

    m = Module.new do
      refine(c) do
        def refined_public; end
        def refined_protected; end
        def refined_private; end

        public :refined_public
        protected :refined_protected
        private :refined_private
      end
    end

    using m

    assert_equal(true, c.public_method_defined?(:refined_public), bug10753)
    assert_equal(false, c.public_method_defined?(:refined_protected), bug10753)
    assert_equal(false, c.public_method_defined?(:refined_private), bug10753)

    assert_equal(false, c.protected_method_defined?(:refined_public), bug10753)
    assert_equal(true, c.protected_method_defined?(:refined_protected), bug10753)
    assert_equal(false, c.protected_method_defined?(:refined_private), bug10753)

    assert_equal(false, c.private_method_defined?(:refined_public), bug10753)
    assert_equal(false, c.private_method_defined?(:refined_protected), bug10753)
    assert_equal(true, c.private_method_defined?(:refined_private), bug10753)
    end;
  end

  def test_undefined_refined_method_defined
    assert_separately([], <<-"end;")
    bug10753 = '[ruby-core:67656] [Bug #10753]'

    c = Class.new

    m = Module.new do
      refine(c) do
        def undefined_refined_public; end
        def undefined_refined_protected; end
        def undefined_refined_private; end
        public :undefined_refined_public
        protected :undefined_refined_protected
        private :undefined_refined_private
      end
    end

    using m

    assert_equal(false, c.public_method_defined?(:undefined_refined_public), bug10753)
    assert_equal(false, c.public_method_defined?(:undefined_refined_protected), bug10753)
    assert_equal(false, c.public_method_defined?(:undefined_refined_private), bug10753)

    assert_equal(false, c.protected_method_defined?(:undefined_refined_public), bug10753)
    assert_equal(false, c.protected_method_defined?(:undefined_refined_protected), bug10753)
    assert_equal(false, c.protected_method_defined?(:undefined_refined_private), bug10753)

    assert_equal(false, c.private_method_defined?(:undefined_refined_public), bug10753)
    assert_equal(false, c.private_method_defined?(:undefined_refined_protected), bug10753)
    assert_equal(false, c.private_method_defined?(:undefined_refined_private), bug10753)
    end;
  end

  def test_remove_refined_method
    assert_separately([], <<-"end;")
    bug10765 = '[ruby-core:67722] [Bug #10765]'

    class C
      def foo
        "C#foo"
      end
    end

    module RefinementBug
      refine C do
        def foo
          "RefinementBug#foo"
        end
      end
    end

    using RefinementBug

    class C
      remove_method :foo
    end

    assert_equal("RefinementBug#foo", C.new.foo, bug10765)
    end;
  end

  def test_remove_undefined_refined_method
    assert_separately([], <<-"end;")
    bug10765 = '[ruby-core:67722] [Bug #10765]'

    class C
    end

    module RefinementBug
      refine C do
        def foo
        end
      end
    end

    using RefinementBug

    assert_raise(NameError, bug10765) {
      class C
        remove_method :foo
      end
    }
    end;
  end

  module NotIncludeSuperclassMethod
    class X
      def foo
      end
    end

    class Y < X
    end

    module Bar
      refine Y do
        def foo
        end
      end
    end
  end

  def test_instance_methods_not_include_superclass_method
    bug10826 = '[ruby-dev:48854] [Bug #10826]'
    assert_not_include(NotIncludeSuperclassMethod::Y.instance_methods(false),
                       :foo, bug10826)
    assert_include(NotIncludeSuperclassMethod::Y.instance_methods(true),
                   :foo, bug10826)
  end

  def test_undef_original_method
    assert_in_out_err([], <<-INPUT, ["NoMethodError"], [])
      module NoPlus
        refine String do
          undef +
        end
      end

      using NoPlus
      "a" + "b" rescue p($!.class)
    INPUT
  end

  def test_undef_prepended_method
    bug13096 = '[ruby-core:78944] [Bug #13096]'
    klass = EnvUtil.labeled_class("X") do
      def foo; end
    end
    klass.prepend(Module.new)
    ext = EnvUtil.labeled_module("Ext") do
      refine klass do
        def foo
        end
      end
    end
    assert_nothing_raised(NameError, bug13096) do
      klass.class_eval do
        undef :foo
      end
    end
  end

  def test_call_refined_method_in_duplicate_module
    bug10885 = '[ruby-dev:48878]'
    assert_in_out_err([], <<-INPUT, [], [], bug10885)
      module M
        refine Object do
          def raise
            # do nothing
          end
        end

        class << self
          using M
          def m0
            raise
          end
        end

        using M
        def M.m1
          raise
        end
      end

      M.dup.m0
      M.dup.m1
    INPUT
  end

  def test_check_funcall_undefined
    bug11117 = '[ruby-core:69064] [Bug #11117]'

    x = Class.new
    Module.new do
      refine x do
        def to_regexp
          //
        end
      end
    end

    assert_nothing_raised(NoMethodError, bug11117) {
      assert_nil(Regexp.try_convert(x.new))
    }
  end

  def test_funcall_inherited
    bug11117 = '[ruby-core:69064] [Bug #11117]'

    Module.new {refine(Dir) {def to_s; end}}
    x = Class.new(Dir).allocate
    assert_nothing_raised(NoMethodError, bug11117) {
      x.inspect
    }
  end

  def test_alias_refined_method2
    bug11182 = '[ruby-core:69360]'
    assert_in_out_err([], <<-INPUT, ["C"], [], bug11182)
      class C
        def foo
          puts "C"
        end
      end

      module M
        refine C do
          def foo
            puts "Refined C"
          end
        end
      end

      class D < C
        alias bar foo
      end

      using M
      D.new.bar
    INPUT
  end

  def test_reopen_refinement_module
    assert_separately([], <<-"end;")
      $VERBOSE = nil
      class C
      end

      module R
        refine C do
          def m
            :foo
          end
        end
      end

      using R
      assert_equal(:foo, C.new.m)

      module R
        refine C do
          def m
            :bar
          end
        end
      end

      assert_equal(:bar, C.new.m, "[ruby-core:71423] [Bug #11672]")
    end;
  end

  module MixedUsing1
    class C
      def foo
        :orig_foo
      end
    end

    module R1
      refine C do
        def foo
          [:R1, super]
        end
      end
    end

    module_function

    def foo
      [:foo, C.new.foo]
    end

    using R1

    def bar
      [:bar, C.new.foo]
    end
  end

  module MixedUsing2
    class C
      def foo
        :orig_foo
      end
    end

    module R1
      refine C do
        def foo
          [:R1_foo, super]
        end
      end
    end

    module R2
      refine C do
        def bar
          [:R2_bar, C.new.foo]
        end

        using R1

        def baz
          [:R2_baz, C.new.foo]
        end
      end
    end

    using R2
    module_function
    def f1; C.new.bar; end
    def f2; C.new.baz; end
  end

  def test_mixed_using
    assert_equal([:foo, :orig_foo], MixedUsing1.foo)
    assert_equal([:bar, [:R1, :orig_foo]], MixedUsing1.bar)

    assert_equal([:R2_bar, :orig_foo], MixedUsing2.f1)
    assert_equal([:R2_baz, [:R1_foo, :orig_foo]], MixedUsing2.f2)
  end

  module MethodMissing
    class Foo
    end

    module Bar
      refine Foo  do
        def method_missing(mid, *args)
          "method_missing refined"
        end
      end
    end

    using Bar

    def self.call_undefined_method
      Foo.new.foo
    end
  end

  def test_method_missing
    assert_raise(NoMethodError) do
      MethodMissing.call_undefined_method
    end
  end

  module VisibleRefinements
    module RefA
      refine Object do
        def in_ref_a
        end
      end
    end

    module RefB
      refine Object do
        def in_ref_b
        end
      end
    end

    module RefC
      using RefA

      refine Object do
        def in_ref_c
        end
      end
    end

    module Foo
      using RefB
      USED_MODS = Module.used_modules
    end

    module Bar
      using RefC
      USED_MODS = Module.used_modules
    end

    module Combined
      using RefA
      using RefB
      USED_MODS = Module.used_modules
    end
  end

  def test_used_modules
    ref = VisibleRefinements
    assert_equal [], Module.used_modules
    assert_equal [ref::RefB], ref::Foo::USED_MODS
    assert_equal [ref::RefC], ref::Bar::USED_MODS
    assert_equal [ref::RefB, ref::RefA], ref::Combined::USED_MODS
  end

  def test_warn_setconst_in_refinmenet
    bug10103 = '[ruby-core:64143] [Bug #10103]'
    warnings = [
      "-:3: warning: not defined at the refinement, but at the outer class/module",
      "-:4: warning: not defined at the refinement, but at the outer class/module"
    ]
    assert_in_out_err([], <<-INPUT, [], warnings, bug10103)
      module M
        refine String do
          FOO = 123
          @@foo = 456
        end
      end
    INPUT
  end

  def test_symbol_proc
    assert_equal("FooExt#x", FooExtClient.map_x_on(Foo.new))
    assert_equal("Foo#x", FooExtClient.return_proc(&:x).(Foo.new))
  end

  def test_symbol_proc_with_block
    assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
    bug = '[ruby-core:80219] [Bug #13325]'
    begin;
      module M
        refine Class.new do
        end
      end
      class C
        def call(a, x, &b)
          b.call(a, &x)
        end
      end
      o = C.new
      r = nil
      x = ->(z){r = z}
      assert_equal(42, o.call(42, x, &:tap))
      assert_equal(42, r)
      using M
      r = nil
      assert_equal(42, o.call(42, x, &:tap), bug)
      assert_equal(42, r, bug)
    end;
  end

  module AliasInSubclass
    class C
      def foo
        :original
      end
    end

    class D < C
      alias bar foo
    end

    module M
      refine D do
        def bar
          :refined
        end
      end
    end
  end

  def test_refine_alias_in_subclass
    assert_equal(:refined,
                 eval_using(AliasInSubclass::M, "AliasInSubclass::D.new.bar"))
  end

  def test_refine_with_prepend
    assert_separately([], "#{<<-"begin;"}\n#{<<-"end;"}")
    begin;
      bug = '[ruby-core:78073] [Bug #12920]'
      Integer.prepend(Module.new)
      Module.new do
        refine Integer do
          define_method(:+) {}
        end
      end
      assert_kind_of(Time, Time.now, bug)
    end;
  end

  def test_public_in_refine
    assert_separately([], "#{<<-"begin;"}\n#{<<-"end;"}")
    begin;
      bug12729 = '[ruby-core:77161] [Bug #12729]'

      class Cow
        private
        def moo() "Moo"; end
      end

      module PublicCows
        refine(Cow) {
          public :moo
        }
      end

      using PublicCows
      assert_equal("Moo", Cow.new.moo, bug12729)
    end;
  end

  module SuperToModule
    class Parent
    end

    class Child < Parent
    end

    module FooBar
      refine Parent do
        def to_s
          "Parent"
        end
      end

      refine Child do
        def to_s
          super + " -> Child"
        end
      end
    end

    using FooBar
    def Child.test
      new.to_s
    end
  end

  def test_super_to_module
    bug = '[ruby-core:79588] [Bug #13227]'
    assert_equal("Parent -> Child", SuperToModule::Child.test, bug)
  end

  def test_include_refinement
    bug = '[ruby-core:79632] [Bug #13236] cannot include refinement module'
    r = nil
    m = Module.new do
      r = refine(String) {def test;:ok end}
    end
    assert_raise_with_message(ArgumentError, /refinement/, bug) do
      m.module_eval {include r}
    end
    assert_raise_with_message(ArgumentError, /refinement/, bug) do
      m.module_eval {prepend r}
    end
  end

  def test_refined_method_alias_warning
    c = Class.new do
      def t; :t end
      def f; :f end
    end
    Module.new do
      refine(c) do
        alias foo t
      end
    end
    assert_warning('', '[ruby-core:82385] [Bug #13817] refined method is not redefined') do
      c.class_eval do
        alias foo f
      end
    end
  end

  class ParentDefiningPrivateMethod
    private
    def some_inherited_method
    end
  end

  module MixinDefiningPrivateMethod
    private
    def some_included_method
    end
  end

  class SomeChildClassToRefine < ParentDefiningPrivateMethod
    include MixinDefiningPrivateMethod

    private
    def some_method
    end
  end

  def test_refine_inherited_method_with_visibility_changes
    Module.new do
      refine(SomeChildClassToRefine) do
        def some_inherited_method; end
        def some_included_method; end
        def some_method; end
      end
    end

    obj = SomeChildClassToRefine.new

    assert_raise_with_message(NoMethodError, /private/) do
      obj.some_inherited_method
    end

    assert_raise_with_message(NoMethodError, /private/) do
      obj.some_included_method
    end

    assert_raise_with_message(NoMethodError, /private/) do
      obj.some_method
    end
  end

  def test_refining_module_repeatedly
    bug14070 = '[ruby-core:83617] [Bug #14070]'
    assert_in_out_err([], <<-INPUT, ["ok"], [], bug14070)
      1000.times do
        Class.new do
          include Enumerable
        end

        Module.new do
          refine Enumerable do
            def foo
            end
          end
        end
      end
      puts "ok"
    INPUT
  end

  def test_super_from_refined_module
    a = EnvUtil.labeled_module("A") do
      def foo;"[A#{super}]";end
    end
    b = EnvUtil.labeled_class("B") do
      def foo;"[B]";end
    end
    c = EnvUtil.labeled_class("C", b) do
      include a
      def foo;"[C#{super}]";end
    end
    d = EnvUtil.labeled_module("D") do
      refine(a) do
        def foo;end
      end
    end
    assert_equal("[C[A[B]]]", c.new.foo, '[ruby-dev:50390] [Bug #14232]')
  end

  private

  def eval_using(mod, s)
    eval("using #{mod}; #{s}", Sandbox::BINDING)
  end
end
