brianjlandau-resque-scheduler 1.10.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,156 @@
1
+ require 'rubygems'
2
+ require 'resque'
3
+ require 'resque/server'
4
+ require 'resque_scheduler/version'
5
+ require 'resque/scheduler'
6
+ require 'resque_scheduler/server'
7
+
8
+ module ResqueScheduler
9
+
10
+ #
11
+ # Accepts a new schedule configuration of the form:
12
+ #
13
+ # {some_name => {"cron" => "5/* * * *",
14
+ # "class" => DoSomeWork,
15
+ # "args" => "work on this string",
16
+ # "description" => "this thing works it"s butter off"},
17
+ # ...}
18
+ #
19
+ # :name can be anything and is used only to describe the scheduled job
20
+ # :cron can be any cron scheduling string :job can be any resque job class
21
+ # :class must be a resque worker class
22
+ # :args can be any yaml which will be converted to a ruby literal and passed
23
+ # in a params. (optional)
24
+ # :rails_envs is the list of envs where the job gets loaded. Envs are comma separated (optional)
25
+ # :description is just that, a description of the job (optional). If params is
26
+ # an array, each element in the array is passed as a separate param,
27
+ # otherwise params is passed in as the only parameter to perform.
28
+ def schedule=(schedule_hash)
29
+ @schedule = schedule_hash
30
+ end
31
+
32
+ # Returns the schedule hash
33
+ def schedule
34
+ @schedule ||= {}
35
+ end
36
+
37
+ # This method is nearly identical to +enqueue+ only it also
38
+ # takes a timestamp which will be used to schedule the job
39
+ # for queueing. Until timestamp is in the past, the job will
40
+ # sit in the schedule list.
41
+ def enqueue_at(timestamp, klass, *args)
42
+ delayed_push(timestamp, job_to_hash(klass, args))
43
+ end
44
+
45
+ # Identical to enqueue_at but takes number_of_seconds_from_now
46
+ # instead of a timestamp.
47
+ def enqueue_in(number_of_seconds_from_now, klass, *args)
48
+ enqueue_at(Time.now + number_of_seconds_from_now, klass, *args)
49
+ end
50
+
51
+ # Used internally to stuff the item into the schedule sorted list.
52
+ # +timestamp+ can be either in seconds or a datetime object
53
+ # Insertion if O(log(n)).
54
+ # Returns true if it's the first job to be scheduled at that time, else false
55
+ def delayed_push(timestamp, item)
56
+ # First add this item to the list for this timestamp
57
+ redis.rpush("delayed:#{timestamp.to_i}", encode(item))
58
+
59
+ # Now, add this timestamp to the zsets. The score and the value are
60
+ # the same since we'll be querying by timestamp, and we don't have
61
+ # anything else to store.
62
+ redis.zadd :delayed_queue_schedule, timestamp.to_i, timestamp.to_i
63
+ end
64
+
65
+ # Returns an array of timestamps based on start and count
66
+ def delayed_queue_peek(start, count)
67
+ Array(redis.zrange(:delayed_queue_schedule, start, start+count)).collect{|x| x.to_i}
68
+ end
69
+
70
+ # Returns the size of the delayed queue schedule
71
+ def delayed_queue_schedule_size
72
+ redis.zcard :delayed_queue_schedule
73
+ end
74
+
75
+ # Returns the number of jobs for a given timestamp in the delayed queue schedule
76
+ def delayed_timestamp_size(timestamp)
77
+ redis.llen("delayed:#{timestamp.to_i}").to_i
78
+ end
79
+
80
+ # Returns an array of delayed items for the given timestamp
81
+ def delayed_timestamp_peek(timestamp, start, count)
82
+ if 1 == count
83
+ r = list_range "delayed:#{timestamp.to_i}", start, count
84
+ r.nil? ? [] : [r]
85
+ else
86
+ list_range "delayed:#{timestamp.to_i}", start, count
87
+ end
88
+ end
89
+
90
+ # Returns the next delayed queue timestamp
91
+ # (don't call directly)
92
+ def next_delayed_timestamp
93
+ items = redis.zrangebyscore :delayed_queue_schedule, '-inf', Time.now.to_i, :limit => [0, 1]
94
+ timestamp = items.nil? ? nil : Array(items).first
95
+ timestamp.to_i unless timestamp.nil?
96
+ end
97
+
98
+ # Returns the next item to be processed for a given timestamp, nil if
99
+ # done. (don't call directly)
100
+ # +timestamp+ can either be in seconds or a datetime
101
+ def next_item_for_timestamp(timestamp)
102
+ key = "delayed:#{timestamp.to_i}"
103
+
104
+ item = decode redis.lpop(key)
105
+
106
+ # If the list is empty, remove it.
107
+ clean_up_timestamp(key, timestamp)
108
+ item
109
+ end
110
+
111
+ # Clears all jobs created with enqueue_at or enqueue_in
112
+ def reset_delayed_queue
113
+ Array(redis.zrange(:delayed_queue_schedule, 0, -1)).each do |item|
114
+ redis.del "delayed:#{item}"
115
+ end
116
+
117
+ redis.del :delayed_queue_schedule
118
+ end
119
+
120
+ # given an encoded item, remove it from the delayed_queue
121
+ def remove_delayed(klass, *args)
122
+ destroyed = 0
123
+ search = encode(job_to_hash(klass, args))
124
+ Array(redis.keys("delayed:*")).each do |key|
125
+ destroyed += redis.lrem key, 0, search
126
+ end
127
+ destroyed
128
+ end
129
+
130
+ def count_all_scheduled_jobs
131
+ total_jobs = 0
132
+ Array(redis.zrange(:delayed_queue_schedule, 0, -1)).each do |timestamp|
133
+ total_jobs += redis.llen("delayed:#{timestamp}").to_i
134
+ end
135
+ total_jobs
136
+ end
137
+
138
+ private
139
+ def job_to_hash(klass, args)
140
+ {:class => klass.to_s, :args => args, :queue => queue_from_class(klass)}
141
+ end
142
+
143
+ def clean_up_timestamp(key, timestamp)
144
+ # If the list is empty, remove it.
145
+ if 0 == redis.llen(key).to_i
146
+ redis.del key
147
+ redis.zrem :delayed_queue_schedule, timestamp.to_i
148
+ end
149
+ end
150
+
151
+ end
152
+
153
+ Resque.extend ResqueScheduler
154
+ Resque::Server.class_eval do
155
+ include ResqueScheduler::Server
156
+ end
@@ -0,0 +1,58 @@
1
+
2
+ # Extend Resque::Server to add tabs
3
+ module ResqueScheduler
4
+
5
+ module Server
6
+
7
+ def self.included(base)
8
+
9
+ base.class_eval do
10
+
11
+ helpers do
12
+ def format_time(t)
13
+ t.strftime("%Y-%m-%d %H:%M:%S")
14
+ end
15
+
16
+ def queue_from_class_name(class_name)
17
+ Resque.queue_from_class(Resque.constantize(class_name))
18
+ end
19
+ end
20
+
21
+ get "/schedule" do
22
+ Resque.reload_schedule! if Resque.respond_to?(:reload_schedule!)
23
+ # Is there a better way to specify alternate template locations with sinatra?
24
+ erb File.read(File.join(File.dirname(__FILE__), 'server/views/scheduler.erb'))
25
+ end
26
+
27
+ post "/schedule/requeue" do
28
+ config = Resque.schedule[params['job_name']]
29
+ Resque::Scheduler.enqueue_from_config(config)
30
+ redirect url("/overview")
31
+ end
32
+
33
+ get "/delayed" do
34
+ # Is there a better way to specify alternate template locations with sinatra?
35
+ erb File.read(File.join(File.dirname(__FILE__), 'server/views/delayed.erb'))
36
+ end
37
+
38
+ get "/delayed/:timestamp" do
39
+ # Is there a better way to specify alternate template locations with sinatra?
40
+ erb File.read(File.join(File.dirname(__FILE__), 'server/views/delayed_timestamp.erb'))
41
+ end
42
+
43
+ post "/delayed/queue_now" do
44
+ timestamp = params['timestamp']
45
+ Resque::Scheduler.enqueue_delayed_items_for_timestamp(timestamp.to_i) if timestamp.to_i > 0
46
+ redirect url("/overview")
47
+ end
48
+
49
+ end
50
+
51
+ end
52
+
53
+ Resque::Server.tabs << 'Schedule'
54
+ Resque::Server.tabs << 'Delayed'
55
+
56
+ end
57
+
58
+ end
@@ -0,0 +1,42 @@
1
+ <h1>Delayed Jobs</h1>
2
+
3
+ <p class='intro'>
4
+ This list below contains the timestamps for scheduled delayed jobs.
5
+ </p>
6
+
7
+ <p class='sub'>
8
+ Showing <%= start = params[:start].to_i %> to <%= start + 20 %> of <b><%=size = resque.delayed_queue_schedule_size %></b> timestamps
9
+ </p>
10
+
11
+ <table>
12
+ <tr>
13
+ <th></th>
14
+ <th>Timestamp</th>
15
+ <th>Job count</th>
16
+ <th>Class</th>
17
+ <th>Args</th>
18
+ </tr>
19
+ <% resque.delayed_queue_peek(start, start+20).each do |timestamp| %>
20
+ <tr>
21
+ <td>
22
+ <form action="<%= url "/delayed/queue_now" %>" method="post">
23
+ <input type="hidden" name="timestamp" value="<%= timestamp.to_i %>">
24
+ <input type="submit" value="Queue now">
25
+ </form>
26
+ </td>
27
+ <td><a href="<%= url "delayed/#{timestamp}" %>"><%= format_time(Time.at(timestamp)) %></a></td>
28
+ <td><%= delayed_timestamp_size = resque.delayed_timestamp_size(timestamp) %></td>
29
+ <% job = resque.delayed_timestamp_peek(timestamp, 0, 1).first %>
30
+ <td>
31
+ <% if job && delayed_timestamp_size == 1 %>
32
+ <%= h(job['class']) %>
33
+ <% else %>
34
+ <a href="<%= url "delayed/#{timestamp}" %>">see details</a>
35
+ <% end %>
36
+ </td>
37
+ <td><%= h(job['args'].inspect) if job && delayed_timestamp_size == 1 %></td>
38
+ </tr>
39
+ <% end %>
40
+ </table>
41
+
42
+ <%= partial :next_more, :start => start, :size => size %>
@@ -0,0 +1,26 @@
1
+ <% timestamp = params[:timestamp].to_i %>
2
+
3
+ <h1>Delayed jobs scheduled for <%= format_time(Time.at(timestamp)) %></h1>
4
+
5
+ <p class='sub'>Showing <%= start = params[:start].to_i %> to <%= start + 20 %> of <b><%=size = resque.delayed_timestamp_size(timestamp)%></b> jobs</p>
6
+
7
+ <table class='jobs'>
8
+ <tr>
9
+ <th>Class</th>
10
+ <th>Args</th>
11
+ </tr>
12
+ <% jobs = resque.delayed_timestamp_peek(timestamp, start, 20) %>
13
+ <% jobs.each do |job| %>
14
+ <tr>
15
+ <td class='class'><%= job['class'] %></td>
16
+ <td class='args'><%=h job['args'].inspect %></td>
17
+ </tr>
18
+ <% end %>
19
+ <% if jobs.empty? %>
20
+ <tr>
21
+ <td class='no-data' colspan='2'>There are no pending jobs scheduled for this time.</td>
22
+ </tr>
23
+ <% end %>
24
+ </table>
25
+
26
+ <%= partial :next_more, :start => start, :size => size %>
@@ -0,0 +1,35 @@
1
+ <h1>Schedule</h1>
2
+
3
+ <p class='intro'>
4
+ The list below contains all scheduled jobs. Click &quot;Queue now&quot; to queue
5
+ a job immediately.
6
+ </p>
7
+
8
+ <table>
9
+ <tr>
10
+ <th></th>
11
+ <th>Name</th>
12
+ <th>Description</th>
13
+ <th>Cron</th>
14
+ <th>Class</th>
15
+ <th>Queue</th>
16
+ <th>Arguments</th>
17
+ </tr>
18
+ <% Resque.schedule.keys.sort.each do |name| %>
19
+ <% config = Resque.schedule[name] %>
20
+ <tr>
21
+ <td>
22
+ <form action="<%= url "/schedule/requeue" %>" method="post">
23
+ <input type="hidden" name="job_name" value="<%= h name %>">
24
+ <input type="submit" value="Queue now">
25
+ </form>
26
+ </td>
27
+ <td><%= h name %></td>
28
+ <td><%= h config['description'] %></td>
29
+ <td style="white-space:nowrap"><%= h config['cron'] %></td>
30
+ <td><%= h config['class'] %></td>
31
+ <td><%= h config['queue'] || queue_from_class_name(config['class']) %></td>
32
+ <td><%= h config['args'].inspect %></td>
33
+ </tr>
34
+ <% end %>
35
+ </table>
@@ -0,0 +1,24 @@
1
+ # require 'resque/tasks'
2
+ # will give you the resque tasks
3
+
4
+ namespace :resque do
5
+ task :setup
6
+
7
+ desc "Start Resque Scheduler"
8
+ task :scheduler => :scheduler_setup do
9
+ require 'resque'
10
+ require 'resque_scheduler'
11
+
12
+ Resque::Scheduler.verbose = true if ENV['VERBOSE']
13
+ Resque::Scheduler.run
14
+ end
15
+
16
+ task :scheduler_setup do
17
+ if ENV['INITIALIZER_PATH']
18
+ load ENV['INITIALIZER_PATH'].to_s.strip
19
+ else
20
+ Rake::Task['resque:setup'].invoke
21
+ end
22
+ end
23
+
24
+ end
@@ -0,0 +1,3 @@
1
+ module ResqueScheduler
2
+ Version = '1.10.0'
3
+ end
@@ -0,0 +1,83 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{resque-scheduler}
8
+ s.version = "1.9.6"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Ben VandenBos"]
12
+ s.date = %q{2010-10-08}
13
+ s.description = %q{Light weight job scheduling on top of Resque.
14
+ Adds methods enqueue_at/enqueue_in to schedule jobs in the future.
15
+ Also supports queueing jobs on a fixed, cron-like schedule.}
16
+ s.email = %q{bvandenbos@gmail.com}
17
+ s.extra_rdoc_files = [
18
+ "LICENSE",
19
+ "README.markdown"
20
+ ]
21
+ s.files = [
22
+ ".gitignore",
23
+ "HISTORY.md",
24
+ "LICENSE",
25
+ "README.markdown",
26
+ "Rakefile",
27
+ "lib/resque/scheduler.rb",
28
+ "lib/resque_scheduler.rb",
29
+ "lib/resque_scheduler/server.rb",
30
+ "lib/resque_scheduler/server/views/delayed.erb",
31
+ "lib/resque_scheduler/server/views/delayed_timestamp.erb",
32
+ "lib/resque_scheduler/server/views/scheduler.erb",
33
+ "lib/resque_scheduler/tasks.rb",
34
+ "lib/resque_scheduler/version.rb",
35
+ "resque-scheduler.gemspec",
36
+ "tasks/resque_scheduler.rake",
37
+ "test/delayed_queue_test.rb",
38
+ "test/redis-test.conf",
39
+ "test/resque-web_test.rb",
40
+ "test/scheduler_test.rb",
41
+ "test/test_helper.rb"
42
+ ]
43
+ s.homepage = %q{http://github.com/bvandenbos/resque-scheduler}
44
+ s.rdoc_options = ["--charset=UTF-8"]
45
+ s.require_paths = ["lib"]
46
+ s.rubygems_version = %q{1.3.7}
47
+ s.summary = %q{Light weight job scheduling on top of Resque}
48
+ s.test_files = [
49
+ "test/delayed_queue_test.rb",
50
+ "test/resque-web_test.rb",
51
+ "test/scheduler_test.rb",
52
+ "test/test_helper.rb"
53
+ ]
54
+
55
+ if s.respond_to? :specification_version then
56
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
57
+ s.specification_version = 3
58
+
59
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
60
+ s.add_runtime_dependency(%q<redis>, [">= 2.0.1"])
61
+ s.add_runtime_dependency(%q<resque>, [">= 1.8.0"])
62
+ s.add_runtime_dependency(%q<rufus-scheduler>, [">= 0"])
63
+ s.add_development_dependency(%q<jeweler>, [">= 0"])
64
+ s.add_development_dependency(%q<mocha>, [">= 0"])
65
+ s.add_development_dependency(%q<rack-test>, [">= 0"])
66
+ else
67
+ s.add_dependency(%q<redis>, [">= 2.0.1"])
68
+ s.add_dependency(%q<resque>, [">= 1.8.0"])
69
+ s.add_dependency(%q<rufus-scheduler>, [">= 0"])
70
+ s.add_dependency(%q<jeweler>, [">= 0"])
71
+ s.add_dependency(%q<mocha>, [">= 0"])
72
+ s.add_dependency(%q<rack-test>, [">= 0"])
73
+ end
74
+ else
75
+ s.add_dependency(%q<redis>, [">= 2.0.1"])
76
+ s.add_dependency(%q<resque>, [">= 1.8.0"])
77
+ s.add_dependency(%q<rufus-scheduler>, [">= 0"])
78
+ s.add_dependency(%q<jeweler>, [">= 0"])
79
+ s.add_dependency(%q<mocha>, [">= 0"])
80
+ s.add_dependency(%q<rack-test>, [">= 0"])
81
+ end
82
+ end
83
+
@@ -0,0 +1,2 @@
1
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
2
+ require 'resque_scheduler/tasks'
@@ -0,0 +1,199 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class Resque::DelayedQueueTest < Test::Unit::TestCase
4
+
5
+ def setup
6
+ Resque::Scheduler.mute = true
7
+ Resque.redis.flushall
8
+ end
9
+
10
+ def test_enqueue_at_adds_correct_list_and_zset
11
+
12
+ timestamp = Time.now - 1 # 1 second ago (in the past, should come out right away)
13
+
14
+ assert_equal(0, Resque.redis.llen("delayed:#{timestamp.to_i}").to_i, "delayed queue should be empty to start")
15
+
16
+ Resque.enqueue_at(timestamp, SomeIvarJob, "path")
17
+
18
+ # Confirm the correct keys were added
19
+ assert_equal(1, Resque.redis.llen("delayed:#{timestamp.to_i}").to_i, "delayed queue should have one entry now")
20
+ assert_equal(1, Resque.redis.zcard(:delayed_queue_schedule), "The delayed_queue_schedule should have 1 entry now")
21
+
22
+ read_timestamp = Resque.next_delayed_timestamp
23
+
24
+ # Confirm the timestamp came out correctly
25
+ assert_equal(timestamp.to_i, read_timestamp, "The timestamp we pull out of redis should match the one we put in")
26
+ item = Resque.next_item_for_timestamp(read_timestamp)
27
+
28
+ # Confirm the item came out correctly
29
+ assert_equal('SomeIvarJob', item['class'], "Should be the same class that we queued")
30
+ assert_equal(["path"], item['args'], "Should have the same arguments that we queued")
31
+
32
+ # And now confirm the keys are gone
33
+ assert(!Resque.redis.exists("delayed:#{timestamp.to_i}"))
34
+ assert_equal(0, Resque.redis.zcard(:delayed_queue_schedule), "delayed queue should be empty")
35
+ end
36
+
37
+ def test_something_in_the_future_doesnt_come_out
38
+ timestamp = Time.now + 600 # 10 minutes from now (in the future, shouldn't come out)
39
+
40
+ assert_equal(0, Resque.redis.llen("delayed:#{timestamp.to_i}").to_i, "delayed queue should be empty to start")
41
+
42
+ Resque.enqueue_at(timestamp, SomeIvarJob, "path")
43
+
44
+ # Confirm the correct keys were added
45
+ assert_equal(1, Resque.redis.llen("delayed:#{timestamp.to_i}").to_i, "delayed queue should have one entry now")
46
+ assert_equal(1, Resque.redis.zcard(:delayed_queue_schedule), "The delayed_queue_schedule should have 1 entry now")
47
+
48
+ read_timestamp = Resque.next_delayed_timestamp
49
+
50
+ assert_nil(read_timestamp, "No timestamps should be ready for queueing")
51
+ end
52
+
53
+ def test_enqueue_at_and_enqueue_in_are_equivelent
54
+ timestamp = Time.now + 60
55
+
56
+ Resque.enqueue_at(timestamp, SomeIvarJob, "path")
57
+ Resque.enqueue_in(timestamp - Time.now, SomeIvarJob, "path")
58
+
59
+ assert_equal(1, Resque.redis.zcard(:delayed_queue_schedule), "should have one timestamp in the delayed queue")
60
+ assert_equal(2, Resque.redis.llen("delayed:#{timestamp.to_i}"), "should have 2 items in the timestamp queue")
61
+ end
62
+
63
+ def test_empty_delayed_queue_peek
64
+ assert_equal([], Resque.delayed_queue_peek(0,20))
65
+ end
66
+
67
+ def test_delayed_queue_peek
68
+ t = Time.now
69
+ expected_timestamps = (1..5).to_a.map do |i|
70
+ (t + 60 + i).to_i
71
+ end
72
+
73
+ expected_timestamps.each do |timestamp|
74
+ Resque.delayed_push(timestamp, {:class => SomeIvarJob, :args => 'blah1'})
75
+ end
76
+
77
+ timestamps = Resque.delayed_queue_peek(2,3)
78
+
79
+ assert_equal(expected_timestamps[2,3], timestamps)
80
+ end
81
+
82
+ def test_delayed_queue_schedule_size
83
+ assert_equal(0, Resque.delayed_queue_schedule_size)
84
+ Resque.enqueue_at(Time.now+60, SomeIvarJob)
85
+ assert_equal(1, Resque.delayed_queue_schedule_size)
86
+ end
87
+
88
+ def test_delayed_timestamp_size
89
+ t = Time.now + 60
90
+ assert_equal(0, Resque.delayed_timestamp_size(t))
91
+ Resque.enqueue_at(t, SomeIvarJob)
92
+ assert_equal(1, Resque.delayed_timestamp_size(t))
93
+ assert_equal(0, Resque.delayed_timestamp_size(t.to_i+1))
94
+ end
95
+
96
+ def test_delayed_timestamp_peek
97
+ t = Time.now + 60
98
+ assert_equal([], Resque.delayed_timestamp_peek(t, 0, 1), "make sure it's an empty array, not nil")
99
+ Resque.enqueue_at(t, SomeIvarJob)
100
+ assert_equal(1, Resque.delayed_timestamp_peek(t, 0, 1).length)
101
+ Resque.enqueue_at(t, SomeIvarJob)
102
+ assert_equal(1, Resque.delayed_timestamp_peek(t, 0, 1).length)
103
+ assert_equal(2, Resque.delayed_timestamp_peek(t, 0, 3).length)
104
+
105
+ assert_equal({'args' => [], 'class' => 'SomeIvarJob', 'queue' => 'ivar'}, Resque.delayed_timestamp_peek(t, 0, 1).first)
106
+ end
107
+
108
+ def test_handle_delayed_items_with_no_items
109
+ Resque::Scheduler.expects(:enqueue).never
110
+ Resque::Scheduler.handle_delayed_items
111
+ end
112
+
113
+ def test_handle_delayed_items_with_items
114
+ t = Time.now - 60 # in the past
115
+ Resque.enqueue_at(t, SomeIvarJob)
116
+ Resque.enqueue_at(t, SomeIvarJob)
117
+
118
+ # 2 SomeIvarJob jobs should be created in the "ivar" queue
119
+ Resque::Job.expects(:create).twice.with('ivar', 'SomeIvarJob', nil)
120
+ Resque.expects(:queue_from_class).never # Should NOT need to load the class
121
+ Resque::Scheduler.handle_delayed_items
122
+ end
123
+
124
+ def test_enqueue_delayed_items_for_timestamp
125
+ t = Time.now + 60
126
+
127
+ Resque.enqueue_at(t, SomeIvarJob)
128
+ Resque.enqueue_at(t, SomeIvarJob)
129
+
130
+ # 2 SomeIvarJob jobs should be created in the "ivar" queue
131
+ Resque::Job.expects(:create).twice.with('ivar', 'SomeIvarJob', nil)
132
+ Resque.expects(:queue_from_class).never # Should NOT need to load the class
133
+
134
+ Resque::Scheduler.enqueue_delayed_items_for_timestamp(t)
135
+
136
+ # delayed queue for timestamp should be empty
137
+ assert_equal(0, Resque.delayed_timestamp_peek(t, 0, 3).length)
138
+ end
139
+
140
+ def test_works_with_out_specifying_queue__upgrade_case
141
+ t = Time.now - 60
142
+ Resque.delayed_push(t, :class => 'SomeIvarJob')
143
+
144
+ # Since we didn't specify :queue when calling delayed_push, it will be forced
145
+ # to load the class to figure out the queue. This is the upgrade case from 1.0.4
146
+ # to 1.0.5.
147
+ Resque::Job.expects(:create).once.with(:ivar, 'SomeIvarJob', nil)
148
+
149
+ Resque::Scheduler.handle_delayed_items
150
+ end
151
+
152
+ def test_clearing_delayed_queue
153
+ t = Time.now + 120
154
+ 4.times { Resque.enqueue_at(t, SomeIvarJob) }
155
+ 4.times { Resque.enqueue_at(Time.now + rand(100), SomeIvarJob) }
156
+
157
+ Resque.reset_delayed_queue
158
+ assert_equal(0, Resque.delayed_queue_schedule_size)
159
+ end
160
+
161
+ def test_remove_specific_item
162
+ t = Time.now + 120
163
+ Resque.enqueue_at(t, SomeIvarJob)
164
+
165
+ assert_equal(1, Resque.remove_delayed(SomeIvarJob))
166
+ end
167
+
168
+ def test_remove_bogus_item_leaves_the_rest_alone
169
+ t = Time.now + 120
170
+ Resque.enqueue_at(t, SomeIvarJob, "foo")
171
+ Resque.enqueue_at(t, SomeIvarJob, "bar")
172
+ Resque.enqueue_at(t, SomeIvarJob, "bar")
173
+ Resque.enqueue_at(t, SomeIvarJob, "baz")
174
+
175
+ assert_equal(0, Resque.remove_delayed(SomeIvarJob))
176
+ end
177
+
178
+ def test_remove_specific_item_in_group_of_other_items_at_same_timestamp
179
+ t = Time.now + 120
180
+ Resque.enqueue_at(t, SomeIvarJob, "foo")
181
+ Resque.enqueue_at(t, SomeIvarJob, "bar")
182
+ Resque.enqueue_at(t, SomeIvarJob, "bar")
183
+ Resque.enqueue_at(t, SomeIvarJob, "baz")
184
+
185
+ assert_equal(2, Resque.remove_delayed(SomeIvarJob, "bar"))
186
+ assert_equal(1, Resque.delayed_queue_schedule_size)
187
+ end
188
+
189
+ def test_remove_specific_item_in_group_of_other_items_at_different_timestamps
190
+ t = Time.now + 120
191
+ Resque.enqueue_at(t, SomeIvarJob, "foo")
192
+ Resque.enqueue_at(t + 1, SomeIvarJob, "bar")
193
+ Resque.enqueue_at(t + 2, SomeIvarJob, "bar")
194
+ Resque.enqueue_at(t + 3, SomeIvarJob, "baz")
195
+
196
+ assert_equal(2, Resque.remove_delayed(SomeIvarJob, "bar"))
197
+ assert_equal(2, Resque.count_all_scheduled_jobs)
198
+ end
199
+ end