Back to Repositories

Testing Redis Multi Queue Failure Handling in Resque

This test suite validates the Redis Multi Queue failure handling functionality in Resque, focusing on proper error storage and iteration through failed jobs. It ensures robust failure tracking across multiple queues with various filtering and ordering capabilities.

Test Coverage Overview

The test suite comprehensively covers Redis Multi Queue failure handling mechanisms in Resque. Key functionality includes:

  • String sanitization for UI compatibility
  • Iteration through failed job records
  • Class-based filtering of failure records
  • Pagination and ordering of failure results

Implementation Analysis

The testing approach utilizes RSpec’s describe/it blocks with extensive setup using let blocks for dependency injection. The implementation leverages mock objects and controlled failure scenarios to validate the RedisMultiQueue backend’s behavior for job failure tracking.

Key patterns include before hooks for test isolation, nested contexts for different scenarios, and systematic validation of iteration mechanics.

Technical Details

Testing tools and configuration:

  • RSpec testing framework
  • Redis backend for failure storage
  • Mock worker and payload objects
  • Custom exception handling
  • Configurable iteration parameters for offset and limit testing

Best Practices Demonstrated

The test suite exemplifies several testing best practices:

  • Proper test isolation through before blocks
  • Comprehensive edge case coverage
  • Consistent assertion patterns
  • Clear test case organization
  • Thorough validation of ordering and pagination logic

resque/resque

test/resque_failure_multi_queue_test.rb

            
require 'test_helper'
require 'resque/failure/redis_multi_queue'

describe "Resque::Failure::RedisMultiQueue" do
  let(:bad_string) { [39, 52, 127, 86, 93, 95, 39].map { |c| c.chr }.join }
  let(:exception)  { StandardError.exception(bad_string) }
  let(:worker)     { Resque::Worker.new(:test) }
  let(:queue)      { 'queue' }
  let(:payload)    { { "class" => "Object", "args" => 3 } }

  before do
    Resque::Failure::RedisMultiQueue.clear
    @redis_backend = Resque::Failure::RedisMultiQueue.new(exception, worker, queue, payload)
  end

  it 'cleans up bad strings before saving the failure, in order to prevent errors on the resque UI' do
    # test assumption: the bad string should not be able to round trip though JSON
    @redis_backend.save
    Resque::Failure::RedisMultiQueue.all # should not raise an error
  end

  it '.each iterates correctly (does nothing) for no failures' do
    assert_equal 0, Resque::Failure::RedisMultiQueue.count
    Resque::Failure::RedisMultiQueue.each do |id, item|
      raise "Should not get here"
    end
  end

  it '.each iterates thru a single hash if there is a single failure' do
    @redis_backend.save
    count = Resque::Failure::RedisMultiQueue.count
    assert_equal 1, count
    num_iterations = 0
    queue = Resque::Failure.failure_queue_name('queue')

    Resque::Failure::RedisMultiQueue.each(0, count, queue) do |_id, item|
      num_iterations += 1
      assert_equal Hash, item.class
    end
    assert_equal count, num_iterations
  end

  it '.each iterates thru hashes if there is are multiple failures' do
    @redis_backend.save
    @redis_backend.save
    count = Resque::Failure::RedisMultiQueue.count
    assert_equal 2, count
    num_iterations = 0
    queue = Resque::Failure.failure_queue_name('queue')

    Resque::Failure::RedisMultiQueue.each(0, count, queue) do |_id, item|
      num_iterations += 1
      assert_equal Hash, item.class
    end
    assert_equal count, num_iterations
  end

  it '.each should limit based on the class_name when class_name is specified' do
    num_iterations = 0
    class_one = 'Foo'
    class_two = 'Bar'
    [ class_one,
      class_two,
      class_one,
      class_two,
      class_one,
      class_two
    ].each do |class_name|
      Resque::Failure::RedisMultiQueue.new(exception, worker, queue, payload.merge({ "class" => class_name })).save
    end
    # ensure that there are 6 failed jobs in total as configured
    count = Resque::Failure::RedisMultiQueue.count
    queue = Resque::Failure.failure_queue_name('queue')
    assert_equal 6, count
    Resque::Failure::RedisMultiQueue.each 0, 3, queue, class_one do |_id, item|
      num_iterations += 1
      # ensure it iterates only jobs with the specified class name (it was not
      # which cause we only got 1 job with class=Foo since it iterates all the
      # jobs and limit already reached)
      assert_equal class_one, item['payload']['class']
    end
    # ensure only iterates max up to the limit specified
    assert_equal 2, num_iterations
  end

  it '.each should limit normally when class_name is not specified' do
    num_iterations = 0
    class_one = 'Foo'
    class_two = 'Bar'
    [ class_one,
      class_two,
      class_one,
      class_two,
      class_one,
      class_two
    ].each do |class_name|
      Resque::Failure::RedisMultiQueue.new(exception, worker, queue, payload.merge({ "class" => class_name })).save
    end
    # ensure that there are 6 failed jobs in total as configured
    count = Resque::Failure::RedisMultiQueue.count
    queue = Resque::Failure.failure_queue_name('queue')
    assert_equal 6, count
    Resque::Failure::RedisMultiQueue.each 0, 5, queue do |id, item|
      num_iterations += 1
      assert_equal Hash, item.class
    end
    # ensure only iterates max up to the limit specified
    assert_equal 5, num_iterations
  end

  it '.each should yield the correct indices when the offset is 0 and the order is descending' do
    50.times { @redis_backend.save }
    queue = Resque::Failure.failure_queue_name('queue')
    count = Resque::Failure::RedisMultiQueue.count
    queue = Resque::Failure.failure_queue_name('queue')
    assert_equal 50, count

    offset = 0
    limit = 20
    ids = []
    expected_ids = (offset...limit).to_a.reverse

    Resque::Failure::RedisMultiQueue.each offset, limit, queue do |id, _item|
      ids << id
    end

    assert_equal expected_ids, ids
  end

  it '.each should yield the correct indices when the offset is 0 and the order is ascending' do
    50.times { @redis_backend.save }
    queue = Resque::Failure.failure_queue_name('queue')
    count = Resque::Failure::RedisMultiQueue.count
    queue = Resque::Failure.failure_queue_name('queue')
    assert_equal 50, count

    offset = 0
    limit = 20
    ids = []
    expected_ids = (offset...limit).to_a

    Resque::Failure::RedisMultiQueue.each offset, limit, queue, nil, 'asc' do |id, _item|
      ids << id
    end

    assert_equal expected_ids, ids
  end

  it '.each should yield the correct indices when the offset isn\'t 0 and the order is descending' do
    50.times { @redis_backend.save }
    queue = Resque::Failure.failure_queue_name('queue')
    count = Resque::Failure::RedisMultiQueue.count
    queue = Resque::Failure.failure_queue_name('queue')
    assert_equal 50, count

    offset = 20
    limit = 20
    ids = []
    expected_ids = (offset...offset + limit).to_a.reverse

    Resque::Failure::RedisMultiQueue.each offset, limit, queue do |id, _item|
      ids << id
    end

    assert_equal expected_ids, ids
  end

  it '.each should yield the correct indices when the offset isn\'t 0 and the order is ascending' do
    50.times { @redis_backend.save }
    queue = Resque::Failure.failure_queue_name('queue')
    count = Resque::Failure::RedisMultiQueue.count
    queue = Resque::Failure.failure_queue_name('queue')
    assert_equal 50, count

    offset = 20
    limit = 20
    ids = []
    expected_ids = (offset...offset + limit).to_a

    Resque::Failure::RedisMultiQueue.each offset, limit, queue, nil, 'asc' do |id, _item|
      ids << id
    end

    assert_equal expected_ids, ids
  end
end