require 'em_test_helper'

class TestFutures < Test::Unit::TestCase

  def setup
  end

  def teardown
  end

  def test_future
      assert_equal(100, EM::Deferrable.future(100) )

      p1 = proc { 100 + 1 }
      assert_equal(101, EM::Deferrable.future(p1) )
  end

  class MyFuture
      include EM::Deferrable
      def initialize *args
        super
        set_deferred_status :succeeded, 40
      end
  end

  class MyErrorFuture
      include EM::Deferrable
      def initialize *args
        super
        set_deferred_status :failed, 41
      end
  end


  def test_future_1
      # Call future with one additional argument and it will be treated as a callback.
      def my_future
        MyFuture.new
      end

      value = nil
      EM::Deferrable.future my_future, proc {|v| value=v}
      assert_equal( 40, value )
  end


  def test_future_2
      # Call future with two additional arguments and they will be treated as a callback
      # and an errback.
      value = nil
      EM::Deferrable.future MyErrorFuture.new, nil, proc {|v| value=v}
      assert_equal( 41, value )
  end


  def test_future_3
      # Call future with no additional arguments but with a block, and the block will be
      # treated as a callback.
      value = nil
      EM::Deferrable.future MyFuture.new do |v|
        value=v
      end
      assert_equal( 40, value )
  end


  class RecursiveCallback
      include EM::Deferrable
  end

  # A Deferrable callback can call #set_deferred_status to change the values
  # passed to subsequent callbacks.
  #
  def test_recursive_callbacks
      n = 0 # counter assures that all the tests actually run.
      rc = RecursiveCallback.new
      rc.callback {|a|
        assert_equal(100, a)
        n += 1
        rc.set_deferred_status :succeeded, 101, 101
      }
      rc.callback {|a,b|
        assert_equal(101, a)
        assert_equal(101, b)
        n += 1
        rc.set_deferred_status :succeeded, 102, 102, 102
      }
      rc.callback {|a,b,c|
        assert_equal(102, a)
        assert_equal(102, b)
        assert_equal(102, c)
        n += 1
      }
      rc.set_deferred_status :succeeded, 100
      assert_equal(3, n)
  end

  def test_syntactic_sugar
    rc = RecursiveCallback.new
    rc.set_deferred_success 100
    rc.set_deferred_failure 200
  end

  # It doesn't raise an error to set deferred status more than once.
  # In fact, this is a desired and useful idiom when it happens INSIDE
  # a callback or errback.
  # However, it's less useful otherwise, and in fact would generally be
  # indicative of a programming error. However, we would like to be resistant
  # to such errors. So whenever we set deferred status, we also clear BOTH
  # stacks of handlers.
  #
  def test_double_calls
    s = 0
    e = 0

    d = EM::DefaultDeferrable.new
    d.callback {s += 1}
    d.errback {e += 1}

    d.succeed	# We expect the callback to be called, and the errback to be DISCARDED.
    d.fail	  # Presumably an error. We expect the errback NOT to be called.
    d.succeed	# We expect the callback to have been discarded and NOT to be called again.

    assert_equal(1, s)
    assert_equal(0, e)
  end

  # Adding a callback to a Deferrable that is already in a success state executes the callback
  # immediately. The same applies to a an errback added to an already-failed Deferrable.
  # HOWEVER, we expect NOT to be able to add errbacks to succeeded Deferrables, or callbacks
  # to failed ones.
  #
  # We illustrate this with a rather contrived test. The test calls #fail after #succeed,
  # which ordinarily would not happen in a real program.
  #
  # What we're NOT attempting to specify is what happens if a Deferrable is succeeded and then
  # failed (or vice-versa). Should we then be able to add callbacks/errbacks of the appropriate
  # type for immediate execution? For now at least, the official answer is "don't do that."
  #
  def test_delayed_callbacks
    s1 = 0
    s2 = 0
    e = 0

    d = EM::DefaultDeferrable.new
    d.callback {s1 += 1}

    d.succeed # Triggers and discards the callback.

    d.callback {s2 += 1} # This callback is executed immediately and discarded.

    d.errback {e += 1} # This errback should be DISCARDED and never execute.
    d.fail # To prove it, fail and assert e is 0

    assert_equal( [1,1], [s1,s2] )
    assert_equal( 0, e )
  end

  def test_timeout
    n = 0
    EM.run {
      d = EM::DefaultDeferrable.new
      d.callback {n = 1; EM.stop}
      d.errback {n = 2; EM.stop}
      d.timeout(0.01)
    }
    assert_equal( 2, n )
  end

end
