class MockEM::MockEM

Fake EM suitable for unit testing. Uses Timecop to accelerate time. Should run Timecop.return after spec, just to be safe.

Public Class Methods

new(logger, timecop) click to toggle source

@param [Timecop] timecop

# File lib/mock_em/mock_em.rb, line 12
def initialize(logger, timecop)
  @log = LoggerWithPrefix.new("MockEM", logger)
  @timecop = timecop

  @next_tick_procs = []
  @scheduled_tasks = ScheduledTasks.new(@log)
  @timer_objects   = []
  @shutdown_hooks  = []
  @is_stopped      = false

  @max_timer_count = 100000  #TODO: not honored
end

Public Instance Methods

add_periodic_timer(period_seconds, proc = nil, &block) click to toggle source
# File lib/mock_em/mock_em.rb, line 92
def add_periodic_timer(period_seconds, proc = nil, &block)
  proc ||= block
  timer = MockTimer.new
  @log.info "Creating periodic timer task: id=#{timer.id}, period_seconds=#{period_seconds}"

  recursive_block = nil
  recursive_block = lambda do
    safely_run { proc.call }
    if !timer.is_cancelled
      @log.info "Rescheduling next run of periodic timer id=#{timer.id}"
      add_timer_internal(period_seconds, timer, recursive_block)
    end
  end

  add_timer_internal(period_seconds, timer, recursive_block)
end
add_shutdown_hook(&block) click to toggle source
# File lib/mock_em/mock_em.rb, line 123
def add_shutdown_hook(&block)
  @shutdown_hooks << block
end
add_timer(delay_seconds, proc = nil, &block) click to toggle source
# File lib/mock_em/mock_em.rb, line 88
def add_timer(delay_seconds, proc = nil, &block)
  add_timer_internal(delay_seconds, nil, proc, &block)
end
cancel_timer(timer) click to toggle source
# File lib/mock_em/mock_em.rb, line 109
def cancel_timer(timer)
  #TODO: support looking up by timer ID as well
  @timer_objects.delete(timer)
  timer.cancel
end
error_handler(proc = nil, &block) click to toggle source
# File lib/mock_em/mock_em.rb, line 127
def error_handler(proc = nil, &block)
  proc ||= block
  @log.info("Setting error_handler")
  @error_handler = proc
end
get_max_timer_count() click to toggle source
# File lib/mock_em/mock_em.rb, line 119
def get_max_timer_count
  @max_timer_count
end
next_tick(proc = nil, &block) click to toggle source
# File lib/mock_em/mock_em.rb, line 82
def next_tick(proc = nil, &block)
  proc ||= block
  @log.info "Adding proc to next_tick"
  @next_tick_procs << proc
end
reactor_running?() click to toggle source
# File lib/mock_em/mock_em.rb, line 115
def reactor_running?
  !!(@reactor_running)
end
run(&block) click to toggle source
# File lib/mock_em/mock_em.rb, line 25
def run(&block)
  @reactor_running = true
  @is_stopped = false
  @log.info "run called. executing run block."

  safely_run { block.call }

  @log.info("Beginning tick loop.")
  @tick_count = 0
  while (!@is_stopped)
    @tick_count += 1

    due_tasks = @scheduled_tasks.pop_due_tasks(now_millis)
    @log.info "Tick ##{@tick_count}, clock=#{now_millis}, due_tasks=#{due_tasks.count}, next_tick_procs=#{@next_tick_procs.count}"
    this_tick_procs = due_tasks + @next_tick_procs
    @next_tick_procs = []

    if this_tick_procs.empty?
      # accelerate time to next scheduled task
      next_time = @scheduled_tasks.time_of_next_task
      if next_time.nil?
        @log.info "Nothing left to do! Returning."
        break
      else
        delta = next_time - now_millis
        @log.info "Nothing in this tick. Accelerating clock by #{delta / 1000.0}s to: #{next_time}"
        set_clock(next_time)
      end
    end

    this_tick_procs.each_with_index do |proc, index|
      @log.info "Executing tick proc ##{index+1}"
      safely_run { proc.call }
    end
  end
  @log.info("Finished tick loop. Returning.")
ensure
  @reactor_running = false
  future_time = now_millis
  @timecop.return
  @log.debug "MockEM saved you #{(future_time - now_millis) / 1000} seconds."
end
stop() click to toggle source
# File lib/mock_em/mock_em.rb, line 68
def stop
  @log.info "stop called"
  @is_stopped = true
  @next_tick_procs = []
  @scheduled_tasks.clear_and_reset
  hooks = @shutdown_hooks
  @shutdown_hooks = []

  if hooks.count > 0
    @log.info "Executing #{hooks.count} shutdown hooks"
    hooks.reverse.each(&:call)
  end
end

Private Instance Methods

add_timer_internal(delay_seconds, reuse_timer, proc = nil, &block) click to toggle source

same as add_timer, but adds an optional parameter: reuse_timer

# File lib/mock_em/mock_em.rb, line 209
def add_timer_internal(delay_seconds, reuse_timer, proc = nil, &block)
  proc ||= block
  timer = reuse_timer || MockTimer.new
  @log.info "Adding timer task: id=#{timer.id}, delay_seconds=#{delay_seconds}"
  @scheduled_tasks.add_task(now_millis + (delay_seconds * 1000)) do
    if timer.is_cancelled
      @log.debug "Skipping this timer task, it's already cancelled"
    else
      safely_run { proc.call }
    end
  end
  @timer_objects << timer
  timer
end
now_millis() click to toggle source
# File lib/mock_em/mock_em.rb, line 204
def now_millis
  (Time.now.utc.to_f * 1000.0).to_i
end
safely_run(&block) click to toggle source
# File lib/mock_em/mock_em.rb, line 185
def safely_run(&block)
  begin
    block.call
  rescue => e
    if @error_handler
      @error_handler.call(e)
    else
      raise e
    end
  rescue Exception => e
    @error_handler.call(e) if @error_handler
    raise e
  end
end
set_clock(millis) click to toggle source
# File lib/mock_em/mock_em.rb, line 200
def set_clock(millis)
  @timecop.travel(Time.at(millis / 1000.0))
end