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