# vim:fileencoding=utf-8 require_relative 'test_helper' context 'DelayedQueue' do setup do Resque::Scheduler.quiet = true Resque.redis.flushall end test 'enqueue_at adds correct list and zset' do timestamp = Time.now + 1 encoded_job = Resque.encode( class: SomeIvarJob.to_s, args: ['path'], queue: Resque.queue_from_class(SomeIvarJob) ) assert_equal(0, Resque.redis.llen("delayed:#{timestamp.to_i}").to_i, 'delayed queue should be empty to start') assert_equal(0, Resque.redis.scard("timestamps:#{encoded_job}"), 'job timestamps set should be empty to start') Resque.enqueue_at(timestamp, SomeIvarJob, 'path') # Confirm the correct keys were added assert_equal(1, Resque.redis.llen("delayed:#{timestamp.to_i}").to_i, 'delayed queue should have one entry now') assert_equal(1, Resque.redis.scard("timestamps:#{encoded_job}"), 'job timestamps should have one entry now') assert_equal(1, Resque.redis.zcard(:delayed_queue_schedule), 'The delayed_queue_schedule should have 1 entry now') read_timestamp = timestamp.to_i item = Resque.next_item_for_timestamp(read_timestamp) # Confirm the item came out correctly assert_equal('SomeIvarJob', item['class'], 'Should be the same class that we queued') assert_equal(['path'], item['args'], 'Should have the same arguments that we queued') # And now confirm the keys are gone assert(!Resque.redis.exists("delayed:#{timestamp.to_i}")) assert_equal(0, Resque.redis.zcard(:delayed_queue_schedule), 'delayed queue should be empty') assert_equal(0, Resque.redis.scard("timestamps:#{encoded_job}"), 'job timestamps set should be empty') end test 'enqueue_at with queue adds correct list and zset and queue' do timestamp = Time.now + 1 encoded_job = Resque.encode( class: SomeIvarJob.to_s, args: ['path'], queue: 'critical' ) assert_equal(0, Resque.redis.llen("delayed:#{timestamp.to_i}").to_i, 'delayed queue should be empty to start') assert_equal(0, Resque.redis.scard("timestamps:#{encoded_job}"), 'job timestamps set should be empty to start') Resque.enqueue_at_with_queue('critical', timestamp, SomeIvarJob, 'path') # Confirm the correct keys were added assert_equal(1, Resque.redis.llen("delayed:#{timestamp.to_i}").to_i, 'delayed queue should have one entry now') assert_equal(1, Resque.redis.scard("timestamps:#{encoded_job}"), 'job timestamps should have one entry now') assert_equal(1, Resque.redis.zcard(:delayed_queue_schedule), 'The delayed_queue_schedule should have 1 entry now') read_timestamp = timestamp.to_i item = Resque.next_item_for_timestamp(read_timestamp) # Confirm the item came out correctly assert_equal('SomeIvarJob', item['class'], 'Should be the same class that we queued') assert_equal(['path'], item['args'], 'Should have the same arguments that we queued') assert_equal('critical', item['queue'], 'Should have the queue that we asked for') # And now confirm the keys are gone assert(!Resque.redis.exists("delayed:#{timestamp.to_i}")) assert_equal(0, Resque.redis.zcard(:delayed_queue_schedule), 'delayed queue should be empty') assert_equal(0, Resque.redis.scard("timestamps:#{encoded_job}"), 'job timestamps set should be empty') end test "a job in the future doesn't come out" do timestamp = Time.now + 600 encoded_job = Resque.encode( class: SomeIvarJob.to_s, args: ['path'], queue: Resque.queue_from_class(SomeIvarJob) ) assert_equal(0, Resque.redis.llen("delayed:#{timestamp.to_i}").to_i, 'delayed queue should be empty to start') assert_equal(0, Resque.redis.scard("timestamps:#{encoded_job}"), 'job timestamps set should be empty to start') Resque.enqueue_at(timestamp, SomeIvarJob, 'path') # Confirm the correct keys were added assert_equal(1, Resque.redis.llen("delayed:#{timestamp.to_i}").to_i, 'delayed queue should have one entry now') assert_equal(1, Resque.redis.scard("timestamps:#{encoded_job}"), 'job timestamps should have one entry now') assert_equal(1, Resque.redis.zcard(:delayed_queue_schedule), 'The delayed_queue_schedule should have 1 entry now') read_timestamp = Resque.next_delayed_timestamp assert_nil(read_timestamp, 'No timestamps should be ready for queueing') end test 'a job in the future comes out if you want it to' do timestamp = Time.now + 600 # 10 minutes from now Resque.enqueue_at(timestamp, SomeIvarJob, 'path') read_timestamp = Resque.next_delayed_timestamp(timestamp) assert_equal(timestamp.to_i, read_timestamp, 'The timestamp we pull out of redis should match the ' \ 'one we put in') end test 'enqueue_at and enqueue_in are equivelent' do timestamp = Time.now + 60 encoded_job = Resque.encode( class: SomeIvarJob.to_s, args: ['path'], queue: Resque.queue_from_class(SomeIvarJob) ) Resque.enqueue_at(timestamp, SomeIvarJob, 'path') Resque.enqueue_in(timestamp - Time.now, SomeIvarJob, 'path') assert_equal(1, Resque.redis.zcard(:delayed_queue_schedule), 'should have one timestamp in the delayed queue') assert_equal(2, Resque.redis.llen("delayed:#{timestamp.to_i}"), 'should have 2 items in the timestamp queue') assert_equal(1, Resque.redis.scard("timestamps:#{encoded_job}"), 'job timestamps should have one entry now') end test 'empty delayed_queue_peek returns empty array' do assert_equal([], Resque.delayed_queue_peek(0, 20)) end test 'delayed_queue_peek returns stuff' do t = Time.now expected_timestamps = (1..5).to_a.map do |i| (t + 60 + i).to_i end expected_timestamps.each do |timestamp| Resque.delayed_push(timestamp, class: SomeIvarJob, args: 'blah1 ') end timestamps = Resque.delayed_queue_peek(1, 2) assert_equal(expected_timestamps[1, 2], timestamps) end test 'delayed_queue_schedule_size returns correct size' do assert_equal(0, Resque.delayed_queue_schedule_size) Resque.enqueue_at(Time.now + 60, SomeIvarJob) assert_equal(1, Resque.delayed_queue_schedule_size) end test 'delayed_timestamp_size returns 0 when nothing is queue' do t = Time.now + 60 assert_equal(0, Resque.delayed_timestamp_size(t)) end test 'delayed_timestamp_size returns 1 when one thing is queued' do t = Time.now + 60 Resque.enqueue_at(t, SomeIvarJob) assert_equal(1, Resque.delayed_timestamp_size(t)) end test 'delayed_timestamp_peek returns empty array when nothings in it' do t = Time.now + 60 assert_equal([], Resque.delayed_timestamp_peek(t, 0, 1), "make sure it's an empty array, not nil") end test 'delayed_timestamp_peek returns an array containing one job ' \ 'when one thing is queued' do t = Time.now + 60 Resque.enqueue_at(t, SomeIvarJob) assert_equal( [{ 'args' => [], 'class' => 'SomeIvarJob', 'queue' => 'ivar' }], Resque.delayed_timestamp_peek(t, 0, 1) ) end test 'delayed_timestamp_peek returns an array of multiple jobs ' \ 'when more than one job is queued' do t = Time.now + 60 Resque.enqueue_at(t, SomeIvarJob) Resque.enqueue_at(t, SomeIvarJob) job = { 'args' => [], 'class' => 'SomeIvarJob', 'queue' => 'ivar' } assert_equal([job, job], Resque.delayed_timestamp_peek(t, 0, 2)) end test 'delayed_timestamp_peek only returns an array of one job ' \ 'if only asked for 1' do t = Time.now + 60 Resque.enqueue_at(t, SomeIvarJob) Resque.enqueue_at(t, SomeIvarJob) job = { 'args' => [], 'class' => 'SomeIvarJob', 'queue' => 'ivar' } assert_equal([job], Resque.delayed_timestamp_peek(t, 0, 1)) end test 'handle_delayed_items with no items' do Resque::Scheduler.expects(:enqueue).never Resque::Scheduler.handle_delayed_items end test 'handle_delayed_item with items' do t = Time.now - 60 # in the past # 2 SomeIvarJob jobs should be created in the "ivar" queue Resque::Job.expects(:create).twice.with(:ivar, SomeIvarJob) Resque.enqueue_at(t, SomeIvarJob) Resque.enqueue_at(t, SomeIvarJob) end test 'handle_delayed_items with items in the future' do t = Time.now + 60 # in the future Resque.enqueue_at(t, SomeIvarJob) Resque.enqueue_at(t, SomeIvarJob) # 2 SomeIvarJob jobs should be created in the "ivar" queue Resque::Job.expects(:create).twice.with('ivar', SomeIvarJob, nil) Resque::Scheduler.handle_delayed_items(t) end test 'calls klass#scheduled when enqueuing jobs if it exists' do t = Time.now - 60 FakeCustomJobClassEnqueueAt.expects(:scheduled) .once.with(:test, FakeCustomJobClassEnqueueAt.to_s, foo: 'bar') Resque.enqueue_at(t, FakeCustomJobClassEnqueueAt, foo: 'bar') end test 'when Resque.inline = true, calls klass#scheduled ' \ 'when enqueuing jobs if it exists' do old_val = Resque.inline begin Resque.inline = true t = Time.now - 60 FakeCustomJobClassEnqueueAt.expects(:scheduled) .once.with(:test, FakeCustomJobClassEnqueueAt.to_s, foo: 'bar') Resque.enqueue_at(t, FakeCustomJobClassEnqueueAt, foo: 'bar') ensure Resque.inline = old_val end end test 'enqueue_delayed_items_for_timestamp creates jobs ' \ 'and empties the delayed queue' do t = Time.now + 60 Resque.enqueue_at(t, SomeIvarJob) Resque.enqueue_at(t, SomeIvarJob) # 2 SomeIvarJob jobs should be created in the "ivar" queue Resque::Job.expects(:create).twice.with('ivar', SomeIvarJob, nil) Resque::Scheduler.enqueue_delayed_items_for_timestamp(t) # delayed queue for timestamp should be empty assert_equal(0, Resque.delayed_timestamp_peek(t, 0, 3).length) end test 'enqueue_delayed creates jobs and empties the delayed queue' do t = Time.now + 60 Resque.enqueue_at(t, SomeIvarJob, 'foo') Resque.enqueue_at(t, SomeIvarJob, 'bar') Resque.enqueue_at(t, SomeIvarJob, 'bar') # 3 SomeIvarJob jobs should be created in the "ivar" queue Resque::Job.expects(:create).never.with(:ivar, SomeIvarJob, 'foo') Resque::Job.expects(:create).twice.with(:ivar, SomeIvarJob, 'bar') # 2 SomeIvarJob jobs should be enqueued assert_equal(2, Resque.enqueue_delayed(SomeIvarJob, 'bar')) # delayed queue for timestamp should have one remaining assert_equal(1, Resque.delayed_timestamp_peek(t, 0, 3).length) end test 'handle_delayed_items works with out specifying queue ' \ '(upgrade case)' do t = Time.now - 60 Resque.delayed_push(t, class: 'SomeIvarJob') # Since we didn't specify :queue when calling delayed_push, it will be # forced to load the class to figure out the queue. This is the upgrade # case from 1.0.4 to 1.0.5. Resque::Job.expects(:create).once.with(:ivar, SomeIvarJob, nil) Resque::Scheduler.handle_delayed_items end test 'reset_delayed_queue clears the queue' do t = Time.now + 120 4.times { Resque.enqueue_at(t, SomeIvarJob) } 4.times { Resque.enqueue_at(Time.now + rand(100), SomeIvarJob) } Resque.reset_delayed_queue assert_equal(0, Resque.delayed_queue_schedule_size) assert_equal(0, Resque.redis.keys('timestamps:*').size) end test 'remove_delayed removes job and returns the count' do t = Time.now + 120 encoded_job = Resque.encode( class: SomeIvarJob.to_s, args: [], queue: Resque.queue_from_class(SomeIvarJob) ) Resque.enqueue_at(t, SomeIvarJob) assert_equal(1, Resque.remove_delayed(SomeIvarJob)) assert_equal(0, Resque.redis.scard("timestamps:#{encoded_job}")) end test 'scheduled_at returns an array containing job schedule time' do t = Time.now + 120 Resque.enqueue_at(t, SomeIvarJob) assert_equal([t.to_i], Resque.scheduled_at(SomeIvarJob)) end test "remove_delayed doesn't remove things it shouldn't" do t = Time.now + 120 Resque.enqueue_at(t, SomeIvarJob, 'foo') Resque.enqueue_at(t, SomeIvarJob, 'bar') Resque.enqueue_at(t, SomeIvarJob, 'bar') Resque.enqueue_at(t, SomeIvarJob, 'baz') assert_equal(0, Resque.remove_delayed(SomeIvarJob)) end test 'remove_delayed respected param' do t = Time.now + 120 Resque.enqueue_at(t, SomeIvarJob, 'foo') Resque.enqueue_at(t, SomeIvarJob, 'bar') Resque.enqueue_at(t, SomeIvarJob, 'bar') Resque.enqueue_at(t, SomeIvarJob, 'baz') assert_equal(2, Resque.remove_delayed(SomeIvarJob, 'bar')) assert_equal(1, Resque.delayed_queue_schedule_size) end test 'remove_delayed removes items in different timestamps' do t = Time.now + 120 Resque.enqueue_at(t, SomeIvarJob, 'foo') Resque.enqueue_at(t + 1, SomeIvarJob, 'bar') Resque.enqueue_at(t + 2, SomeIvarJob, 'bar') Resque.enqueue_at(t + 3, SomeIvarJob, 'baz') assert_equal(2, Resque.remove_delayed(SomeIvarJob, 'bar')) assert_equal(2, Resque.count_all_scheduled_jobs) end test 'remove_delayed_selection removes multiple items matching ' \ 'arguments at same timestamp' do t = Time.now + 120 Resque.enqueue_at(t, SomeIvarJob, 'bar', 'llama') Resque.enqueue_at(t, SomeIvarJob, 'foo') Resque.enqueue_at(t, SomeIvarJob, 'bar', 'monkey') Resque.enqueue_at(t, SomeIvarJob, 'bar', 'platypus') Resque.enqueue_at(t, SomeIvarJob, 'baz') Resque.enqueue_at(t, SomeIvarJob, 'bar', 'llama') Resque.enqueue_at(t, SomeIvarJob, 'bar', 'llama') assert_equal(5, Resque.remove_delayed_selection { |a| a.first == 'bar' }) assert_equal(2, Resque.count_all_scheduled_jobs) end test 'remove_delayed_selection removes single item matching arguments' do t = Time.now + 120 Resque.enqueue_at(t, SomeIvarJob, 'foo') Resque.enqueue_at(t + 1, SomeIvarJob, 'bar') Resque.enqueue_at(t + 2, SomeIvarJob, 'bar') Resque.enqueue_at(t + 3, SomeIvarJob, 'baz') assert_equal(1, Resque.remove_delayed_selection { |a| a.first == 'foo' }) assert_equal(3, Resque.count_all_scheduled_jobs) end test 'remove_delayed_selection removes multiple items matching arguments' do t = Time.now + 120 Resque.enqueue_at(t, SomeIvarJob, 'foo') Resque.enqueue_at(t + 1, SomeIvarJob, 'bar') Resque.enqueue_at(t + 2, SomeIvarJob, 'bar') Resque.enqueue_at(t + 3, SomeIvarJob, 'baz') assert_equal(2, Resque.remove_delayed_selection { |a| a.first == 'bar' }) assert_equal(2, Resque.count_all_scheduled_jobs) end test 'remove_delayed_selection removes multiple items matching ' \ 'arguments as hash' do t = Time.now + 120 Resque.enqueue_at(t, SomeIvarJob, foo: 'foo') Resque.enqueue_at(t + 1, SomeIvarJob, foo: 'bar') Resque.enqueue_at(t + 2, SomeIvarJob, foo: 'bar') Resque.enqueue_at(t + 3, SomeIvarJob, foo: 'baz') assert_equal( 2, Resque.remove_delayed_selection { |a| a.first['foo'] == 'bar' } ) assert_equal(2, Resque.count_all_scheduled_jobs) end test 'remove_delayed_selection ignores jobs with no arguments' do t = Time.now + 120 Resque.enqueue_at(t, SomeIvarJob) Resque.enqueue_at(t + 1, SomeIvarJob) Resque.enqueue_at(t + 2, SomeIvarJob) Resque.enqueue_at(t + 3, SomeIvarJob) assert_equal(0, Resque.remove_delayed_selection { |a| a.first == 'bar' }) assert_equal(4, Resque.count_all_scheduled_jobs) end test "remove_delayed_selection doesn't remove items it shouldn't" do t = Time.now + 120 Resque.enqueue_at(t, SomeIvarJob, 'foo') Resque.enqueue_at(t + 1, SomeIvarJob, 'bar') Resque.enqueue_at(t + 2, SomeIvarJob, 'bar') Resque.enqueue_at(t + 3, SomeIvarJob, 'baz') assert_equal(0, Resque.remove_delayed_selection { |a| a.first == 'qux' }) assert_equal(4, Resque.count_all_scheduled_jobs) end test 'remove_delayed_selection ignores last_enqueued_at redis key' do t = Time.now + 120 Resque.enqueue_at(t, SomeIvarJob) Resque.last_enqueued_at(SomeIvarJob, t) assert_equal(0, Resque.remove_delayed_selection { |a| a.first == 'bar' }) assert_equal(t.to_s, Resque.get_last_enqueued_at(SomeIvarJob)) end test 'remove_delayed_selection removes item by class' do t = Time.now + 120 Resque.enqueue_at(t, SomeIvarJob, 'foo') Resque.enqueue_at(t, SomeQuickJob, 'foo') assert_equal(1, Resque.remove_delayed_selection(SomeIvarJob) do |a| a.first == 'foo' end) assert_equal(1, Resque.count_all_scheduled_jobs) end test 'remove_delayed_selection removes item by class name as a string' do t = Time.now + 120 Resque.enqueue_at(t, SomeIvarJob, 'foo') Resque.enqueue_at(t, SomeQuickJob, 'foo') assert_equal(1, Resque.remove_delayed_selection('SomeIvarJob') do |a| a.first == 'foo' end) assert_equal(1, Resque.count_all_scheduled_jobs) end test 'remove_delayed_selection removes item by class name as a symbol' do t = Time.now + 120 Resque.enqueue_at(t, SomeIvarJob, 'foo') Resque.enqueue_at(t, SomeQuickJob, 'foo') assert_equal(1, Resque.remove_delayed_selection(:SomeIvarJob) do |a| a.first == 'foo' end) assert_equal(1, Resque.count_all_scheduled_jobs) end test 'remove_delayed_selection removes items only from matching job class' do t = Time.now + 120 Resque.enqueue_at(t, SomeIvarJob, 'foo') Resque.enqueue_at(t, SomeQuickJob, 'foo') Resque.enqueue_at(t + 1, SomeIvarJob, 'bar') Resque.enqueue_at(t + 1, SomeQuickJob, 'bar') Resque.enqueue_at(t + 1, SomeIvarJob, 'foo') Resque.enqueue_at(t + 2, SomeQuickJob, 'foo') assert_equal(2, Resque.remove_delayed_selection(SomeIvarJob) do |a| a.first == 'foo' end) assert_equal(4, Resque.count_all_scheduled_jobs) end test 'remove_delayed_selection removes items from matching job class ' \ 'without params' do t = Time.now + 120 Resque.enqueue_at(t, SomeIvarJob) Resque.enqueue_at(t + 1, SomeQuickJob) Resque.enqueue_at(t + 2, SomeIvarJob) Resque.enqueue_at(t + 3, SomeQuickJob) assert_equal(2, Resque.remove_delayed_selection(SomeQuickJob) { true }) assert_equal(2, Resque.count_all_scheduled_jobs) end test 'remove_delayed_job_from_timestamp removes instances of jobs ' \ 'at a given timestamp' do t = Time.now + 120 Resque.enqueue_at(t, SomeIvarJob, 'foo') assert_equal( 1, Resque.remove_delayed_job_from_timestamp(t, SomeIvarJob, 'foo') ) assert_equal 0, Resque.delayed_timestamp_size(t) end test "remove_delayed_job_from_timestamp doesn't remove items from " \ 'other timestamps' do t1 = Time.now + 120 t2 = t1 + 1 Resque.enqueue_at(t1, SomeIvarJob, 'foo') Resque.enqueue_at(t2, SomeIvarJob, 'foo') assert_equal( 1, Resque.remove_delayed_job_from_timestamp(t2, SomeIvarJob, 'foo') ) assert_equal 1, Resque.delayed_timestamp_size(t1) assert_equal 0, Resque.delayed_timestamp_size(t2) end test 'remove_delayed_job_from_timestamp removes nothing if there ' \ 'are no matches' do t = Time.now + 120 assert_equal( 0, Resque.remove_delayed_job_from_timestamp(t, SomeIvarJob, 'foo') ) end test 'remove_delayed_job_from_timestamp only removes items that ' \ 'match args' do t = Time.now + 120 Resque.enqueue_at(t, SomeIvarJob, 'foo') Resque.enqueue_at(t, SomeIvarJob, 'bar') assert_equal( 1, Resque.remove_delayed_job_from_timestamp(t, SomeIvarJob, 'foo') ) assert_equal 1, Resque.delayed_timestamp_size(t) end test 'remove_delayed_job_from_timestamp returns the number of ' \ 'items removed' do t = Time.now + 120 Resque.enqueue_at(t, SomeIvarJob, 'foo') assert_equal( 1, Resque.remove_delayed_job_from_timestamp(t, SomeIvarJob, 'foo') ) end test 'remove_delayed_job_from_timestamp should cleanup the delayed ' \ 'timestamp list if not jobs are left' do t = Time.now + 120 Resque.enqueue_at(t, SomeIvarJob, 'foo') assert_equal( 1, Resque.remove_delayed_job_from_timestamp(t, SomeIvarJob, 'foo') ) assert !Resque.redis.exists("delayed:#{t.to_i}") assert Resque.delayed_queue_peek(0, 100).empty? end test 'invalid job class' do assert_raises Resque::NoQueueError do Resque.enqueue_in(10, String) end end test 'inlining jobs with Resque.inline config' do begin Resque.inline = true Resque::Job.expects(:create).once.with(:ivar, SomeIvarJob, 'foo', 'bar') timestamp = Time.now + 120 Resque.enqueue_at(timestamp, SomeIvarJob, 'foo', 'bar') assert_equal 0, Resque.count_all_scheduled_jobs assert !Resque.redis.exists("delayed:#{timestamp.to_i}") ensure Resque.inline = false end end test 'delayed?' do Resque.enqueue_at Time.now + 1, SomeIvarJob Resque.enqueue_at Time.now + 1, SomeIvarJob, id: 1 assert Resque.delayed?(SomeIvarJob, id: 1) assert !Resque.delayed?(SomeIvarJob, id: 2) assert Resque.delayed?(SomeIvarJob) assert !Resque.delayed?(SomeJob) end end