require 'test/unit'

$m0 = Module.nesting


class TestModule < Test::Unit::TestCase

    #
    # Check that two arrays contain the same "bag" of elements.
    # A mathematical bag differs from a "set" by counting the
    # occurences of each element. So as a bag [1,2,1] differs from
    # [2,1] (but is equal to [1,1,2]).
    #
    # The method only relies on the == operator to match objects
    # from the two arrays. The elements of the arrays may contain
    # objects that are not "Comparable".
    # 
    # FIXME: This should be moved to common location.
    def assert_bag_equal(expected, actual)
      # For each object in "actual" we remove an equal object
      # from "expected". If we can match objects pairwise from the
      # two arrays we have two equal "bags". The method Array#index
      # uses == internally. We operate on a copy of "expected" to
      # avoid destructively changing the argument.
      #
      expected_left = expected.dup
      actual.each do |x|
        if j = expected_left.index(x)
          expected_left.slice!(j)
        end
      end
      assert( expected.length == actual.length && expected_left.length == 0,
             "Expected: #{expected.inspect}, Actual: #{actual.inspect}")
    end

  # Support stuff

  module Mixin
    MIXIN = 1
    def mixin
    end
  end

  module User
    USER = 2
    include Mixin
    def user
    end
  end

  module Other
    def other
    end
  end

  module Unrelated1
    def unrelated1_method
    end
  end

  module Unrelated2
    def unrelated2_method
    end
  end

  class AClass
    def AClass.cm1
      "cm1"
    end
    def AClass.cm2
      cm1 + "cm2" + cm3
    end
    def AClass.cm3
      "cm3"
    end
    
    private_class_method :cm1, "cm3"

    def aClass
    end

    def aClass1
    end

    def aClass2
    end

    private :aClass1
    protected :aClass2
  end

  class BClass < AClass
    def bClass1
    end

    private

    def bClass2
    end

    protected
    def bClass3
    end
  end

  MyClass = AClass.clone
  class MyClass
    public_class_method :cm1
  end

  # -----------------------------------------------------------

  def test_CMP # '<=>'
    assert_equal( 0, Mixin <=> Mixin)
    assert_equal(-1, User <=> Mixin)
    assert_equal( 1, Mixin <=> User)

    assert_equal( 0, Object <=> Object)
    assert_equal(-1, String <=> Object)
    assert_equal( 1, Object <=> String)

    # unrelated modules give 'nil'
    assert_equal(nil,  Unrelated1 <=> Unrelated2)
    assert_equal(nil,  Unrelated2 <=> Unrelated1)
  end

  def test_GE # '>='
    assert_equal(true,  Mixin >= User)
    assert_equal(true,  Mixin >= Mixin)
    assert_equal(false, User >= Mixin)

    assert_equal(true,  Object >= String)
    assert_equal(true,  String >= String)
    assert_equal(false, String >= Object)

    # unrelated modules give 'nil'
    assert_equal(nil,  Unrelated1 >= Unrelated2)
    assert_equal(nil,  Unrelated2 >= Unrelated1)
  end

  def test_GT # '>'
    assert_equal(true,   Mixin > User)
    assert_equal(false,  Mixin > Mixin)
    assert_equal(false,  User  > Mixin)

    assert_equal(true,   Object > String)
    assert_equal(false,  String > String)
    assert_equal(false,  String > Object)

    # unrelated modules give 'nil'
    assert_equal(nil,  Unrelated1 > Unrelated2)
    assert_equal(nil,  Unrelated2 > Unrelated1)
  end

  def test_LE # '<='
    assert_equal(true,  User <= Mixin)
    assert_equal(true,  Mixin <= Mixin)
    assert_equal(false,  Mixin <= User)

    assert_equal(true,  String <= Object)
    assert_equal(true,  String <= String)
    assert_equal(false, Object <= String)

    # unrelated modules give 'nil'
    assert_equal(nil,  Unrelated1 <= Unrelated2)
    assert_equal(nil,  Unrelated2 <= Unrelated1)
  end

  def test_LT # '<'
    assert_equal(true,   User  < Mixin)
    assert_equal(false,  Mixin < Mixin)
    assert_equal(false,  Mixin < User)

    assert_equal(true,   String < Object)
    assert_equal(false,  String < String)
    assert_equal(false,  Object < String)

    # unrelated modules give 'nil'
    assert_equal(nil,  Unrelated1 < Unrelated2)
    assert_equal(nil,  Unrelated2 < Unrelated1)
  end

  def test_EQUAL # '=='
    assert_equal(true,   User  == User)
    assert_equal(false,  User  == Mixin)
    assert_equal(false,  Mixin == User)
    assert_equal(true,   Mixin == Mixin)

    assert_equal(true,   String == String)
    assert_equal(false,  String == Object)
    assert_equal(false,  Object == String)
    assert_equal(true,   Object == Object)

    # unrelated modules give 'false' too
    assert_equal(true,   Unrelated1 == Unrelated1)
    assert_equal(false,  Unrelated1 == Unrelated2)
    assert_equal(false,  Unrelated2 == Unrelated1)
    assert_equal(true,   Unrelated2 == Unrelated2)
  end

  def test_VERY_EQUAL # '==='
    assert(Object === self)
    assert(Test::Unit::TestCase === self)
    assert(TestModule === self)
    assert(!(String === self))
  end

  def test_ancestors
    assert_equal([User, Mixin],      User.ancestors)
    assert_equal([Mixin],            Mixin.ancestors)

    ancestors_obj = Object.ancestors
    ancestors_str = String.ancestors
    if defined?(PP) # when issuing 'AllTests.rb' then PP gets added
      ancestors_obj.delete(PP::ObjectMixin)
      ancestors_str.delete(PP::ObjectMixin)
    end
    assert_equal([Object, Kernel],   ancestors_obj)
    assert_equal([String, 
                   Enumerable, 
                   Comparable,
                   Object, Kernel],  ancestors_str)
  end

  def test_class_eval
    Other.class_eval("CLASS_EVAL = 1")
    assert_equal(1, Other::CLASS_EVAL)
    assert(Other.constants.include?("CLASS_EVAL"))
  end

  def test_const_defined?
    assert(Math.const_defined?(:PI))
    assert(Math.const_defined?("PI"))
    assert(!Math.const_defined?(:IP))
    assert(!Math.const_defined?("IP"))
  end

  def test_const_get
    assert_equal(Math::PI, Math.const_get("PI"))
    assert_equal(Math::PI, Math.const_get(:PI))
  end

  def test_const_set
    assert(!Other.const_defined?(:KOALA))
    Other.const_set(:KOALA, 99)
    assert(Other.const_defined?(:KOALA))
    assert_equal(99, Other::KOALA)
    Other.const_set("WOMBAT", "Hi")
    assert_equal("Hi", Other::WOMBAT)
  end

  def test_constants
    assert_equal(["MIXIN"], Mixin.constants)
    assert_equal(["MIXIN", "USER"], User.constants.sort)
  end

  def test_included_modules
    assert_equal([], Mixin.included_modules)
    assert_equal([Mixin], User.included_modules)
    incmod_obj = Object.included_modules
    incmod_str = String.included_modules
    if defined?(PP)  # when issuing 'AllTests.rb' then PP gets added
      incmod_obj.delete(PP::ObjectMixin)
      incmod_str.delete(PP::ObjectMixin)
    end
    assert_equal([Kernel], incmod_obj)
    assert_equal([Enumerable, Comparable, Kernel], incmod_str)
  end

  def test_instance_methods
      # default value is true
      ary_user = User.instance_methods
      assert_equal(true, ary_user.include?("user"))
      # we expect more than our 'user' method to be returned.
      assert_equal(true, ary_user.size > 1) 
      
      # we expect ONLY than our 'mixin' method to be returned.
      ary_mixin = Mixin.instance_methods
      assert_equal(["mixin"], ary_mixin)
      
      ary_class = AClass.instance_methods
      assert_equal(true, ary_class.include?("aClass"))
      # we expect more than our 'aClass' method to be returned.
      assert_equal(true, ary_class.size > 1) 
    assert_equal(["mixin", "user"], User.instance_methods(true).sort)
    assert_equal(["mixin"], Mixin.instance_methods(true))
      assert_bag_equal(["aClass", "aClass2"], 
                       AClass.instance_methods(true) - 
                       Object.instance_methods(true)
                       )
  end

  def test_method_defined?
    assert(!User.method_defined?(:wombat))
    assert(User.method_defined?(:user))
    assert(User.method_defined?(:mixin))
    assert(!User.method_defined?("wombat"))
    assert(User.method_defined?("user"))
    assert(User.method_defined?("mixin"))
  end

  def test_module_eval
    User.module_eval("MODULE_EVAL = 1")
    assert_equal(1, User::MODULE_EVAL)
    assert(User.constants.include?("MODULE_EVAL"))
    User.instance_eval("remove_const(:MODULE_EVAL)")
    assert(!User.constants.include?("MODULE_EVAL"))
  end

  def test_name
    assert_equal("Fixnum", Fixnum.name)
    assert_equal("TestModule::Mixin",  Mixin.name)
    assert_equal("TestModule::User",   User.name)
  end

  def test_private_class_method
    assert_raise(NoMethodError) { AClass.cm1 }
    assert_raise(NoMethodError) { AClass.cm3 }
    assert_equal("cm1cm2cm3", AClass.cm2)
  end

  def test_private_instance_methods
      # default value is true
      a = AClass.private_instance_methods
      assert_equal(true, a.include?("aClass1"))
      # we expect more than our 'aClass1' method to be returned.
      assert_equal(true, a.size > 1) 
      
      b = BClass.private_instance_methods
      assert_equal(true, b.include?("bClass2"))
      # we expect more than our 'bClass2' method to be returned.
      assert_equal(true, b.size > 1) 
    assert_bag_equal(["bClass2", "aClass1"],
                     BClass.private_instance_methods(true) -
                     Object.private_instance_methods(true))
  end

  def test_protected_instance_methods
      # default value is true
      a = AClass.protected_instance_methods
      assert_equal(true, a.include?("aClass2"))
      # we expect more than our 'aClass2' method to be returned.
      assert_equal(true, a.size == 1) 
      
      b = BClass.protected_instance_methods
      assert_equal(true, b.include?("bClass3"))
      # we expect more than our 'bClass3' method to be returned.
      assert_equal(true, b.size > 1) 
    assert_bag_equal(["bClass3", "aClass2"],
                     BClass.protected_instance_methods(true) -
                     Object.protected_instance_methods(true))
  end

  def test_public_class_method
    assert_equal("cm1",       MyClass.cm1)
    assert_equal("cm1cm2cm3", MyClass.cm2)
    assert_raise(NoMethodError) { eval "MyClass.cm3" }
  end

  def test_public_instance_methods
      # default value is true
      a = AClass.public_instance_methods
      assert_equal(true, a.include?("aClass"))
      # we expect more than our 'aClass' method to be returned.
      assert_equal(true, a.size > 1) 
      
      b = BClass.public_instance_methods
      assert_equal(true, b.include?("bClass1"))
      # we expect more than our 'bClass1' method to be returned.
      assert_equal(true, b.size > 1) 
  end

  def test_s_constants
    c1 = Module.constants
    Object.module_eval "WALTER = 99"
    c2 = Module.constants
    assert_equal(["WALTER"], c2 - c1)
  end

  module M1
    $m1 = Module.nesting
    module M2
      $m2 = Module.nesting
    end
  end

  def test_s_nesting
    assert_equal([],                               $m0)
    assert_equal([TestModule::M1, TestModule],     $m1)
    assert_equal([TestModule::M1::M2,
                  TestModule::M1, TestModule],     $m2)
  end

  def test_s_new
    m = Module.new
    assert_instance_of(Module, m)
  end

end
