require 'test/unit'

########################################
# This class is used for test
########################################
class Foo
  class << self
    def set(var)
      @@class_var = var
    end

    def get
      @@class_var
    end
  end
end

########################################
# hack for class Object
########################################
class Object
  def eigenclass
    class << self
      self
    end
  end
end

########################################
# The testcase
########################################
class MetaTest < Test::Unit::TestCase
  def test_class
    str = String.new

    # str is an instance of the class String
    assert_equal String, str.class
    # the class String is an instance of the class Class
    assert_equal Class, str.class.class
    # the class Class is an instance of the class Class itself,
    # so metaclass is just normal class
    assert_equal Class, str.class.class.class
    # the class Class is also derived from the class Object
    assert_equal Object, str.class.class.superclass.superclass
  end

  def test_class_method
    # foo is an instance of class Foo
    foo = Foo.new
    # klass is the class of the object foo, i.e. Foo
    klass = foo.class
    assert_equal Foo, klass

    # class method of object foo is just instance method
    # of object klass(which is the class of foo)
    klass.set(10)
    assert_equal 10, klass.get

    # because get is not instance method of object foo
    assert_raises(NoMethodError) { foo.get }
  end

  def test_singleton_method
    foo = Foo.new

    # now we put the singleton_func in the eigenclass of foo
    def foo.singleton_func
      self
    end

    # singleton_func exists in foo, and self refer to foo
    assert_equal foo, foo.singleton_func
    # singleton_func is not available in other instance of class Foo
    assert_raise(NoMethodError) { Foo.new.singleton_func }
  end

  def test_eigenclass
    foo = Foo.new

    def foo.singleton_func
      self
    end

    # eigenclass is also an instance of the class Class
    assert_equal Class, foo.eigenclass.class
    # singleton_func is put as an instance method in the eigenclass
    assert foo.eigenclass.instance_methods.include?('singleton_func')
    # so here is the magic of the so called class method
    assert Foo.eigenclass.instance_methods.include?('get')
  end
end
