module Resqutils::Spec::ResqueHelpers

Mix this into your tests to get helpful methods to manipulate a live resque queue.

Public Instance Methods

clear_queue(queue_name) click to toggle source

Clear the queue. Useful in before blocks to make sure there's nothing hanging around in your queue from a previous test run.

queue_name

If a Class, the ivar @queue is used to determine which queue to clear. Otherwise, assumes it's a string or symbol and will clear that queue. :delayed is a special queue that represents the delayed queue provided by resque-scheduler.

# File lib/resqutils/spec/resque_helpers.rb, line 67
def clear_queue(queue_name)
  if queue_name.kind_of?(Class)
    queue_name = queue_name.instance_variable_get("@queue")
  end
  if queue_name == :delayed
    Resque.reset_delayed_queue
  else
    Resque.redis.del("queue:#{queue_name}")
    Resque.redis.del("resque:queue:#{queue_name}")
  end
end
delayed_jobs() click to toggle source

Return all jobs in a delayed queue, with each job augmented with the key scheduler_timestamp to represent what time the job was schedule to run for.

# File lib/resqutils/spec/resque_helpers.rb, line 129
def delayed_jobs
  # The double-checks here are so that we won't blow up if the config stops using redis-namespace
  timestamps = (Array(Resque.redis.zrange("resque:delayed_queue_schedule",0,-1)) + 
                Array(Resque.redis.zrange("delayed_queue_schedule",0,-1)))
  raise "Nothing on delayed schedule" if timestamps.empty?

  timestamps.map { |timestamp|
    [
      Array(Resque.redis.lrange("resque:delayed:#{timestamp}",0,-1)) + Array(Resque.redis.lrange("delayed:#{timestamp}",0,-1)),
      timestamp,
    ]
  }.map { |(job_strings,timestamp)|
    job_strings.map { |job_string|
      JSON.parse(job_string).merge('scheduler_timestamp' => timestamp.to_i)
    }
  }.flatten
end
jobs_matching(queue_name,expected_job_hash) click to toggle source

Get jobs from queue_name that match the given hash. This returns both the jobs that matched and all jobs from the queue, so you can create a useful error message in your tests.

queue_name

A String or Symbol representing the name of the queue, where :delayed is special and will cause the code to look in the delayed queue as provided by resque-scheduler.

expected_job_hash

Criteria for matching, typically a two-element hash containing the class and args of the job you are looking for.

Returns an array of size 2, where the first element is an Array of jobs matching the given criteria, and the second element is an Array of all jobs.

Example

Consider the queue “foo” has these jobs in it:

  • { “class” => “WarmCaches”, “args” => [ 1234 ] }

  • { “class” => “WarmCaches”, “args” => [ 4567 ] }

  • { “class” => “IndexPurchases”, “args” => [ 1234 ] }

    jobs_matching(:foo, class: WarmCaches, args: [ 1234 ])     # => matching jobs is the first job above
    jobs_matching(:foo, class: IndexPurchases, args: [ 1234 ]) # => matching jobs is the third job above
    jobs_matching(:foo, class: IndexPurchases, args: [ 9999 ]) # => matching jobs is nothing
    
# File lib/resqutils/spec/resque_helpers.rb, line 103
def jobs_matching(queue_name,expected_job_hash)
  jobs = if queue_name == :delayed
           delayed_jobs
         else
           (0...Resque.size(queue_name)).map { |index|
             [Resque.peek(queue_name,index),index]
           }
         end
  matching_jobs = jobs.select { |(job,index)|
    raise "No job at index #{index}" if job.nil?
    expected_job_hash.all? do |k,v|
      k = k.to_s
      v = v.to_s if v.kind_of?(Class)
      raise "Job #{job} at index #{index} was missing key #{k}" if job[k].nil?
      if k == 'scheduler_timestamp'
        ((v.to_i - 60000)..(v.to_i + 60000)).cover?(job[k])
      else
        job[k] == v
      end
    end
  }.map(&:first)
  [matching_jobs,jobs]
end
process_multiple_resque_jobs(number, expected_class, queue=nil, &block) click to toggle source
block

if provided, will be given each popped job right before it is processed.

# File lib/resqutils/spec/resque_helpers.rb, line 47
def process_multiple_resque_jobs(number, expected_class, queue=nil, &block)
  number.times do
    process_resque_job(expected_class, queue, &block)
  end
end
process_resque_job(expected_class, queue=nil, &block) click to toggle source
block

if provided, will be given the popped job right before it is processed.

# File lib/resqutils/spec/resque_helpers.rb, line 17
def process_resque_job(expected_class, queue=nil, &block)
  block ||= ->(payload) {}
  queue ||= expected_class.instance_variable_get("@queue")
  job = if queue == :delayed
          delayed_jobs.select { |job|
            job["class"] == expected_class.name
          }.first
        else
          job = Resque.pop(queue)
        end
  raise "No jobs on #{queue}" if job.nil?
  klass = job["class"].constantize
  if Gem.loaded_specs['rspec'].version >= Gem::Version.new('3')
    expect(klass).to eq(expected_class)
  else
    klass.should == expected_class
  end
  block.call(job)
  klass.perform(*job["args"])
end
queue_size(queue) click to toggle source

Get the size of the given queue. This mostly prevents your test from having direct coupling to resque's (crappy) internal API.

queue

name of the queue, as a Symbol or String

# File lib/resqutils/spec/resque_helpers.rb, line 57
def queue_size(queue)
  Resque.size(queue)
end