sskirby-resque-scheduler 1.10.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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,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::Scheduler.dynamic
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,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.8'
3
+ end
@@ -0,0 +1,189 @@
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
+ # reloads the schedule from redis
38
+ def reload_schedule!
39
+ @schedule = get_schedules
40
+ end
41
+
42
+ # gets the schedule as it exists in redis
43
+ def get_schedules
44
+ if redis.exists(:schedules)
45
+ redis.hgetall(:schedules).tap do |h|
46
+ h.each do |name, config|
47
+ h[name] = decode(config)
48
+ end
49
+ end
50
+ else
51
+ nil
52
+ end
53
+ end
54
+
55
+ # create or update a schedule with the provided name and configuration
56
+ def set_schedule(name, config)
57
+ redis.hset(:schedules, name, encode(config))
58
+ end
59
+
60
+ # retrive the schedule configuration for the given name
61
+ def get_schedule(name)
62
+ decode(redis.hget(:schedules, name))
63
+ end
64
+
65
+ # remove a given schedule by name
66
+ def remove_schedule(name)
67
+ redis.hdel(:schedules, name)
68
+ end
69
+
70
+ # This method is nearly identical to +enqueue+ only it also
71
+ # takes a timestamp which will be used to schedule the job
72
+ # for queueing. Until timestamp is in the past, the job will
73
+ # sit in the schedule list.
74
+ def enqueue_at(timestamp, klass, *args)
75
+ delayed_push(timestamp, job_to_hash(klass, args))
76
+ end
77
+
78
+ # Identical to enqueue_at but takes number_of_seconds_from_now
79
+ # instead of a timestamp.
80
+ def enqueue_in(number_of_seconds_from_now, klass, *args)
81
+ enqueue_at(Time.now + number_of_seconds_from_now, klass, *args)
82
+ end
83
+
84
+ # Used internally to stuff the item into the schedule sorted list.
85
+ # +timestamp+ can be either in seconds or a datetime object
86
+ # Insertion if O(log(n)).
87
+ # Returns true if it's the first job to be scheduled at that time, else false
88
+ def delayed_push(timestamp, item)
89
+ # First add this item to the list for this timestamp
90
+ redis.rpush("delayed:#{timestamp.to_i}", encode(item))
91
+
92
+ # Now, add this timestamp to the zsets. The score and the value are
93
+ # the same since we'll be querying by timestamp, and we don't have
94
+ # anything else to store.
95
+ redis.zadd :delayed_queue_schedule, timestamp.to_i, timestamp.to_i
96
+ end
97
+
98
+ # Returns an array of timestamps based on start and count
99
+ def delayed_queue_peek(start, count)
100
+ Array(redis.zrange(:delayed_queue_schedule, start, start+count)).collect{|x| x.to_i}
101
+ end
102
+
103
+ # Returns the size of the delayed queue schedule
104
+ def delayed_queue_schedule_size
105
+ redis.zcard :delayed_queue_schedule
106
+ end
107
+
108
+ # Returns the number of jobs for a given timestamp in the delayed queue schedule
109
+ def delayed_timestamp_size(timestamp)
110
+ redis.llen("delayed:#{timestamp.to_i}").to_i
111
+ end
112
+
113
+ # Returns an array of delayed items for the given timestamp
114
+ def delayed_timestamp_peek(timestamp, start, count)
115
+ if 1 == count
116
+ r = list_range "delayed:#{timestamp.to_i}", start, count
117
+ r.nil? ? [] : [r]
118
+ else
119
+ list_range "delayed:#{timestamp.to_i}", start, count
120
+ end
121
+ end
122
+
123
+ # Returns the next delayed queue timestamp
124
+ # (don't call directly)
125
+ def next_delayed_timestamp
126
+ items = redis.zrangebyscore :delayed_queue_schedule, '-inf', Time.now.to_i, :limit => [0, 1]
127
+ timestamp = items.nil? ? nil : Array(items).first
128
+ timestamp.to_i unless timestamp.nil?
129
+ end
130
+
131
+ # Returns the next item to be processed for a given timestamp, nil if
132
+ # done. (don't call directly)
133
+ # +timestamp+ can either be in seconds or a datetime
134
+ def next_item_for_timestamp(timestamp)
135
+ key = "delayed:#{timestamp.to_i}"
136
+
137
+ item = decode redis.lpop(key)
138
+
139
+ # If the list is empty, remove it.
140
+ clean_up_timestamp(key, timestamp)
141
+ item
142
+ end
143
+
144
+ # Clears all jobs created with enqueue_at or enqueue_in
145
+ def reset_delayed_queue
146
+ Array(redis.zrange(:delayed_queue_schedule, 0, -1)).each do |item|
147
+ redis.del "delayed:#{item}"
148
+ end
149
+
150
+ redis.del :delayed_queue_schedule
151
+ end
152
+
153
+ # given an encoded item, remove it from the delayed_queue
154
+ def remove_delayed(klass, *args)
155
+ destroyed = 0
156
+ search = encode(job_to_hash(klass, args))
157
+ Array(redis.keys("delayed:*")).each do |key|
158
+ destroyed += redis.lrem key, 0, search
159
+ end
160
+ destroyed
161
+ end
162
+
163
+ def count_all_scheduled_jobs
164
+ total_jobs = 0
165
+ Array(redis.zrange(:delayed_queue_schedule, 0, -1)).each do |timestamp|
166
+ total_jobs += redis.llen("delayed:#{timestamp}").to_i
167
+ end
168
+ total_jobs
169
+ end
170
+
171
+ private
172
+ def job_to_hash(klass, args)
173
+ {:class => klass.to_s, :args => args, :queue => queue_from_class(klass)}
174
+ end
175
+
176
+ def clean_up_timestamp(key, timestamp)
177
+ # If the list is empty, remove it.
178
+ if 0 == redis.llen(key).to_i
179
+ redis.del key
180
+ redis.zrem :delayed_queue_schedule, timestamp.to_i
181
+ end
182
+ end
183
+
184
+ end
185
+
186
+ Resque.extend ResqueScheduler
187
+ Resque::Server.class_eval do
188
+ include ResqueScheduler::Server
189
+ 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{sskirby-resque-scheduler}
8
+ s.version = "1.10.8"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Ben VandenBos", "Brian Landau", "Sean Kirby", "Tanzeeb Khalili"]
12
+ s.date = %q{2010-10-26}
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{sskirby@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
+ "sskirby-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/sskirby/resque-scheduler}
44
+ s.rdoc_options = ["--charset=UTF-8"]
45
+ s.require_paths = ["lib"]
46
+ s.rubygems_version = %q{1.3.6}
47
+ s.summary = %q{Light weight job scheduling on top of Resque}
48
+ s.test_files = [
49
+ "test/scheduler_test.rb",
50
+ "test/test_helper.rb",
51
+ "test/resque-web_test.rb",
52
+ "test/delayed_queue_test.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::RubyGemsVersion) >= 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