
#
# testing ruote
#
# Fri May 15 09:51:28 JST 2009
#

require File.expand_path('../base', __FILE__)

require 'ruote/participant'


class FtErrorsTest < Test::Unit::TestCase
  include FunctionalBase

  def test_error

    pdef = Ruote.process_definition do
      nada
    end

    wfid = @dashboard.launch(pdef)
    @dashboard.wait_for(wfid)

    ps = @dashboard.process(wfid)

    assert_equal 1, ps.errors.size

    pss = @dashboard.processes

    assert_equal 1, pss.size
    assert_equal 1, pss.first.errors.size
  end

  # asm and jpr5 use that sometimes
  #
  def test_error_reapply

    pdef = Ruote.process_definition do
      nada
    end

    wfid = @dashboard.launch(pdef)
    wait_for(wfid)

    ps = @dashboard.process(wfid)

    exp = ps.expressions.find { |fe| fe.class == Ruote::Exp::RefExpression }

    assert_not_nil exp

    @dashboard.register_participant :nada do |workitem|
      tracer << 'done.'
    end

    @dashboard.re_apply(exp.fei)
    wait_for(wfid)

    assert_equal 'done.', @tracer.to_s

    assert_equal 0, @dashboard.storage.get_many('errors').size
  end

  def test_error_replay

    pdef = Ruote.process_definition do
      nada
    end

    wfid = @dashboard.launch(pdef)
    wait_for(wfid)

    ps = @dashboard.process(wfid)

    err = ps.errors.first

    @dashboard.replay_at_error(err)

    wait_for(wfid)
    ps = @dashboard.process(wfid)

    #p ps

    assert_equal 1, ps.errors.size
      # only one error

    err1 = ps.errors.first

    assert_not_equal err.at, err1.at
      # not the same error

    assert_equal 1, @dashboard.storage.get_many('errors').size
  end

  def test_error_fix_then_replay

    pdef = Ruote.process_definition do
      nada
    end

    @dashboard.register_participant :alpha do
      tracer << "alpha\n"
    end

    wfid = @dashboard.launch(pdef)
    wait_for(wfid)
    ps = @dashboard.process(wfid)

    err = ps.errors.first
    assert_equal [ 'nada', { 'ref' => 'nada' }, [] ], err.tree

    err.tree = [ 'alpha', {}, [] ]
    @dashboard.replay_at_error(err)
    wait_for(wfid)

    assert_nil @dashboard.process(wfid)

    assert_equal 'alpha', @tracer.to_s

    # check if error is really gone from error journal...

    assert_equal [], @dashboard.storage.get_many('errors')
  end

  def test_error_in_participant

    pdef = Ruote.process_definition do
      sequence do
        alpha
        echo 'done.'
      end
    end

    @dashboard.context.stash[:count] = 0

    @dashboard.register_participant :alpha do
      stash[:count] += 1
      tracer << "alpha\n"
      raise "something went wrong" if stash[:count] == 1
    end

    wfid = @dashboard.launch(pdef)

    wait_for(wfid)

    sleep 0.250 # grrr...

    ps = @dashboard.process(wfid)

    assert_equal 1, ps.errors.size

    @dashboard.replay_at_error(ps.errors.first)

    wait_for(wfid)

    assert_equal %w[ alpha alpha done. ].join("\n"), @tracer.to_s
  end

  def test_error_in_do_no_thread_participant

    pdef = Ruote.process_definition do
      sequence do
        alpha
        echo 'done.'
      end
    end

    @dashboard.context.stash[:count] = 0

    alpha = @dashboard.register_participant :alpha, 'do_not_thread' => true do
      stash[:count] += 1
      tracer << "alpha\n"
      raise "something went wrong" if stash[:count] == 1
    end

    wfid = @dashboard.launch(pdef)

    wait_for(wfid)

    ps = @dashboard.process(wfid)

    assert_equal 1, ps.errors.size

    @dashboard.replay_at_error(ps.errors.first)

    wait_for(wfid)

    assert_equal %w[ alpha alpha done. ].join("\n"), @tracer.to_s
  end

  class WeakCancelParticipant
    include Ruote::LocalParticipant

    def initialize(opts)
    end
    def consume(workitem)
      # losing it
    end
    def do_not_thread
      true
    end
    def cancel(fei, flavour)
      raise "failure in #cancel"
    end
  end

  def test_error_in_participant_cancel

    pdef = Ruote.process_definition do
      alpha
    end

    @dashboard.register_participant 'alpha', WeakCancelParticipant

    wfid = @dashboard.launch(pdef)

    wait_for(:alpha)

    @dashboard.cancel_process(wfid)

    wait_for(wfid)

    ps = @dashboard.process(wfid)

    #puts ps.errors.first.trace
    assert_equal 1, ps.errors.size
    assert_equal 2, ps.expressions.size

    @dashboard.kill_process(wfid)

    wait_for(wfid)

    assert_nil @dashboard.process(wfid)
  end

  def test_errors_and_subprocesses

    pdef = Ruote.process_definition do
      sequence do
        sub0
        echo 'done.'
      end
      define 'sub0' do
        alpha
      end
    end

    @dashboard.context.stash[:count] = 0

    alpha = @dashboard.register_participant :alpha, :do_not_thread => true do
      stash[:count] += 1
      tracer << "alpha\n"
      raise "something went wrong" if stash[:count] == 1
    end

    wfid = @dashboard.launch(pdef)

    wait_for(wfid)

    ps = @dashboard.process(wfid)

    assert_equal 1, ps.errors.size

    err = ps.errors.first

    assert_equal wfid, err.fei.wfid
    assert_not_nil err.fei.subid

    @dashboard.replay_at_error(err)

    wait_for(wfid)

    assert_equal %w[ alpha alpha done. ].join("\n"), @tracer.to_s
  end

  def test_process_cancellation

    pdef = Ruote.process_definition do
      nada
    end

    wfid = @dashboard.launch(pdef)

    wait_for(wfid)

    assert_equal 1, @dashboard.process(wfid).errors.size

    @dashboard.cancel_process(wfid)

    wait_for(wfid)

    assert_nil @dashboard.process(wfid)
    assert_equal [], @dashboard.storage.get_many('errors')
  end

  def test_forgotten_subprocess

    pdef = Ruote.process_definition do
      sequence do
        sub0 :forget => true
        echo 'done.'
      end
      define 'sub0' do
        error 'broken wing'
      end
    end

    wfid = @dashboard.launch(pdef)
    wait_for(wfid)
    wait_for(wfid)

    assert_equal 'done.', @tracer.to_s

    ps = @dashboard.process(wfid)
    assert_equal 3, ps.expressions.size
    assert_equal 1, ps.errors.size

    @dashboard.replay_at_error(ps.errors.first)
    wait_for(wfid)

    assert_nil @dashboard.process(wfid)
  end

  def test_error_intercepted

    pdef = Ruote.process_definition do
      nada
    end

    wfid = @dashboard.launch(pdef)

    r = @dashboard.wait_for(wfid)

    assert_equal 'error_intercepted', r['action']
    assert_equal 'RuntimeError', r['error']['class']
    assert_equal "unknown participant or subprocess 'nada'", r['error']['message']
    assert_equal Array, r['error']['trace'].class
    assert_equal [ 'nada', { 'ref' => 'nada' }, [] ], r['error']['tree']
  end

  def test_replay_at_error_fei

    @dashboard.register { catchall }

    wfid = @dashboard.launch(Ruote.define do
      error 'alpha'
      error 'bravo'
    end)

    @dashboard.wait_for(wfid)

    err = @dashboard.ps(wfid).errors.first
    assert_match /alpha/, err.message
    fei = err.fei

    @dashboard.replay_at_error(fei)

    @dashboard.wait_for(wfid)

    err = @dashboard.ps(wfid).errors.first
    assert_match /bravo/, err.message
  end

  class MyError < RuntimeError
    def ruote_details
      'where the devil is'
    end
  end

  def test_error_details

    @dashboard.register :alpha do |workitem|
      raise FtErrorsTest::MyError
    end

    wfid = @dashboard.launch(Ruote.define do
      alpha
    end)

    @dashboard.wait_for('error_intercepted')

    assert_equal 'where the devil is',  @dashboard.ps(wfid).errors.first.details

    #p @dashboard.ps(wfid)
  end

  # fighting https://github.com/jmettraux/ruote/issues/90
  #
  def test_error_in_error

    stderr = $stderr

    result = String.new
    err = StringIO.new(result, 'w+')

    $stderr = err

    @dashboard.register :alpha do |wi|
      raise NameError, String
    end

    wfid = @dashboard.launch(Ruote.define { alpha })

    sleep 1.0

    assert_match /Please report issue/, result
    assert_match /TypeError/, result
    assert_match /can't convert Class into String/, result

    $stderr = stderr
  end
end

