
#
# testing ruote
#
# Sat Sep 20 23:40:10 JST 2008
#

require 'fileutils'

require File.expand_path('../../test_helper', __FILE__)
require File.expand_path('../storage_helper', __FILE__)
require File.expand_path('../signals', __FILE__)

require 'ruote'


#
# Most of the functional tests extend this class.
#
module FunctionalBase

  # For functional tests that want to provide their own setup or teardown
  # and still have an opportunity to call this base's setup/teardown
  #
  def self.included(target)

    target.class_eval do
      alias base_setup setup
      alias base_teardown teardown
    end
  end

  def setup

    if ARGV.include?('-T') || ARGV.include?('-N') || ENV['NOISY'] == 'true'
      p self.class
    end

    #require 'ruote/util/look'
    #Ruote::Look.dump_lsof
    #Ruote::Look.dump_lsof_count
      #
      # uncomment this when "too many open files"

    sto = determine_storage({})

    @dashboard = Ruote::Dashboard.new(
      sto.class.name.match(/Worker$/) ? sto : Ruote::Worker.new(sto))

    @engine = @dashboard
      # for 'backward compatibility'

    $_test = self
    $_dashboard = @dashboard
      #
      # handy when hijacking (https://github.com/ileitch/hijack)
      # or flinging USR2 at the test process

    @tracer = Tracer.new

    Ruote::BlockParticipant.class_eval do
      def tracer
        @context.tracer
      end
      def stash
        @context.stash
      end
    end

    @dashboard.add_service('tracer', @tracer)
    @dashboard.add_service('stash', {})

    noisy if ARGV.include?('-N') || ENV['NOISY'].to_s == 'true'
  end

  def teardown

    return if @dashboard.nil?

    @dashboard.shutdown
    @dashboard.context.storage.purge!
    @dashboard.context.storage.close if @dashboard.context.storage.respond_to?(:close)
  end

  def stash

    @dashboard.context.stash
  end

  def assert_log_count(count, &block)

    c = @dashboard.context.logger.log.select(&block).size

    #logger.to_stdout if ( ! @dashboard.context[:noisy]) && c != count

    assert_equal count, c
  end

  #   assert_trace(*expected_traces, pdef)
  #   assert_trace(*expected_traces, fields, pdef)
  #
  def assert_trace(*args)

    if args.last == :clear
      args.pop
      @tracer.clear
    end

    pdef = args.pop
    fields = args.last.is_a?(Hash) ? args.pop : {}
    expected_traces = args.collect { |et| et.is_a?(Array) ? et.join("\n") : et }

    wfid = @dashboard.launch(pdef, fields)

    r = wait_for(wfid)

    assert_engine_clean(wfid)

    trace = r['workitem'] ? r['workitem']['fields']['_trace'] : nil
    trace = trace ? trace.join('') : @tracer.to_s

    if expected_traces.length > 0
      ok, nok = expected_traces.partition { |et| trace == et }
      assert_equal(nok.first, trace) if ok.empty?
    end

    assert(true)
      # so that the assertion count matches

    wfid
  end

  def logger

    @dashboard.context.logger
  end

  protected

  def noisy(on=true)

    @dashboard.context.logger.noisy = true
  end

  def wait_for(*wfid_or_part)

    @dashboard.wait_for(*wfid_or_part)
  end

  def assert_engine_clean(wfid)

    assert_no_errors(wfid)
    assert_no_remaining_expressions(wfid)
  end

  def assert_no_errors(wfid)

    errors = @dashboard.storage.get_many('errors', /#{wfid}$/)

    return if errors.size == 0

    puts
    puts '-' * 80
    puts 'remaining process error(s)'
    puts
    errors.each do |e|
      puts "  ** #{e['message']}"
      puts e['trace']
    end
    puts '-' * 80

    puts_trace_so_far

    flunk 'remaining process error(s)'
  end

  def assert_no_remaining_expressions(wfid)

    expcount = @dashboard.storage.get_many('expressions').size
    return if expcount == 0

    tf, _, tn = caller[2].split(':')

    puts
    puts '-' * 80
    puts 'too many expressions left in storage'
    puts
    puts "this test : #{tf}"
    puts "            #{tn}"
    puts
    puts "this test's wfid : #{wfid}"
    puts
    puts 'left :'
    puts
    puts @dashboard.context.storage.dump('expressions')
    puts
    puts '-' * 80

    puts_trace_so_far

    flunk 'too many expressions left in storage'
  end

  def puts_trace_so_far

    #puts '. ' * 40
    puts 'trace so far'
    puts '---8<---'
    puts @tracer.to_s
    puts '--->8---'
    puts
  end
end

#
# Re-opening workitem for a shortcut to a '_trace' field
#
class Ruote::Workitem
  def trace
    @h['fields']['_trace'] ||= []
  end
end

#
# Our tracer class.
#
class Tracer
  def to_s
    s.to_s.strip
  end
  def to_a
    to_s.split("\n")
  end
  def << s
    self.s << s
  end
  def clear
    self.s.clear
  end
  def puts(s)
    self.s << "#{s}\n"
  end
  def s
    @s ||= ''
  end
end

