##
# Proc ISO Test

assert('Proc', '15.2.17') do
  assert_equal Class, Proc.class
end

assert('Proc.new', '15.2.17.3.1') do
  assert_raise ArgumentError do
    Proc.new
  end

  assert_equal (Proc.new {}).class, Proc

  assert_raise LocalJumpError do
    Proc.new{ break }.call
  end
end

assert('Proc#[]', '15.2.17.4.1') do
  a = 0
  b = Proc.new { a += 1 }
  b.[]

  a2 = 0
  b2 = Proc.new { |i| a2 += i }
  b2.[](5)

  assert_equal 1, a
  assert_equal 5, a2
end

assert('Proc#arity', '15.2.17.4.2') do
  a = Proc.new {|x, y|}.arity
  b = Proc.new {|x, *y, z|}.arity
  c = Proc.new {|x=0, y|}.arity
  d = Proc.new {|(x, y), z=0|}.arity

  assert_equal  2, a
  assert_equal(-3, b)
  assert_equal  1, c
  assert_equal  1, d

  e = ->(x=0, y){}.arity
  f = ->((x, y), z=0){}.arity
  g = ->(x=0){}.arity

  assert_equal(-2, e)
  assert_equal(-2, f)
  assert_equal(-1, g)
end

assert('Proc#call', '15.2.17.4.3') do
  a = 0
  b = Proc.new { a += 1 }
  b.call

  a2 = 0
  b2 = Proc.new { |i| a2 += i }
  b2.call(5)

  assert_equal 1, a
  assert_equal 5, a2
end

assert('Proc#call proc args pos block') do
  pr = Proc.new {|a,b,&c|
    [a, b, c.class, c&&c.call(:x)]
  }
  assert_equal [nil, nil, Proc, :proc], (pr.call(){ :proc })
  assert_equal [1, nil, Proc, :proc], (pr.call(1){ :proc })
  assert_equal [1, 2, Proc, :proc], (pr.call(1, 2){ :proc })
  assert_equal [1, 2, Proc, :proc], (pr.call(1, 2, 3){ :proc })
  assert_equal [1, 2, Proc, :proc], (pr.call(1, 2, 3, 4){ :proc })

  assert_equal [nil, nil, Proc, :x], (pr.call(){|x| x})
  assert_equal [1, nil, Proc, :x], (pr.call(1){|x| x})
  assert_equal [1, 2, Proc, :x], (pr.call(1, 2){|x| x})
  assert_equal [1, 2, Proc, :x], (pr.call(1, 2, 3){|x| x})
  assert_equal [1, 2, Proc, :x], (pr.call(1, 2, 3, 4){|x| x})
end

assert('Proc#call proc args pos rest post') do
  pr = Proc.new {|a,b,*c,d,e|
    [a,b,c,d,e]
  }
  assert_equal [nil, nil, [], nil, nil], pr.call()
  assert_equal [1, nil, [], nil, nil], pr.call(1)
  assert_equal [1, 2, [], nil, nil], pr.call(1,2)
  assert_equal [1, 2, [], 3, nil], pr.call(1,2,3)
  assert_equal [1, 2, [], 3, 4], pr.call(1,2,3,4)
  assert_equal [1, 2, [3], 4, 5], pr.call(1,2,3,4,5)
  assert_equal [1, 2, [3, 4], 5, 6], pr.call(1,2,3,4,5,6)
  assert_equal [1, 2, [3, 4, 5], 6,7], pr.call(1,2,3,4,5,6,7)

  assert_equal [nil, nil, [], nil, nil], pr.call([])
  assert_equal [1, nil, [], nil, nil], pr.call([1])
  assert_equal [1, 2, [], nil, nil], pr.call([1,2])
  assert_equal [1, 2, [], 3, nil], pr.call([1,2,3])
  assert_equal [1, 2, [], 3, 4], pr.call([1,2,3,4])
  assert_equal [1, 2, [3], 4, 5], pr.call([1,2,3,4,5])
  assert_equal [1, 2, [3, 4], 5, 6], pr.call([1,2,3,4,5,6])
  assert_equal [1, 2, [3, 4, 5], 6,7], pr.call([1,2,3,4,5,6,7])
end

assert('Proc#return_does_not_break_self') do
  class TestClass
    attr_accessor :block
    def initialize
    end
    def return_array
      @block = Proc.new { self }
      return []
    end
    def return_instance_variable
      @block = Proc.new { self }
      return @block
    end
    def return_const_fixnum
      @block = Proc.new { self }
      return 123
    end
    def return_nil
      @block = Proc.new { self }
      return nil
    end
  end

  c = TestClass.new
  assert_equal [], c.return_array
  assert_equal c, c.block.call

  c.return_instance_variable
  assert_equal c, c.block.call

  assert_equal 123, c.return_const_fixnum
  assert_equal c, c.block.call

  assert_equal nil, c.return_nil
  assert_equal c, c.block.call
end

assert('call Proc#initialize if defined') do
  a = []
  c = Class.new(Proc) do
    define_method(:initialize) do
      a << :ok
    end
  end

  assert_kind_of c, c.new{}
  assert_equal [:ok], a
end

assert('&obj call to_proc if defined') do
  pr = Proc.new{}
  def mock(&b)
    b
  end
  assert_same pr, mock(&pr)
  assert_equal pr, mock(&pr)

  obj = Object.new
  def obj.to_proc
    Proc.new{ :from_to_proc }
  end
  assert_equal :from_to_proc, mock(&obj).call

  assert_raise(TypeError){ mock(&(Object.new)) }
end

assert('Creation of a proc through the block of a method') do
  def m(&b) b end

  assert_equal m{}.class, Proc

  assert_raise LocalJumpError do
    m{ break }.call
  end
end

assert('#6345: dup of a block from method is treated as orphan') do
  def m(&b) b.dup end

  # The dup is orphan, so calling it raises LocalJumpError on break.
  assert_raise LocalJumpError do
    m { break 1 }.call
  end

  # A dup of a block without break still returns normally.
  assert_equal 42, m { 42 }.call
end

assert('identity check for proc object') do
  b = []
  t = 2
  while t > 0
    b << ->{}
    t -= 1
  end
  assert_true b[0] == b[1]
  assert_equal b[0].hash, b[1].hash

  b = []
  2.times {
    b << ->{}
  }
  assert_false b[0] == b[1]
  assert_not_equal b[0].hash, b[1].hash
end
