resque-scheduler 2.0.0 → 2.3.1

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.

Potentially problematic release.


This version of resque-scheduler might be problematic. Click here for more details.

@@ -0,0 +1,180 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ module LockTestHelper
4
+ def lock_is_not_held(lock)
5
+ Resque.redis.set(lock.key, 'anothermachine:1234')
6
+ end
7
+ end
8
+
9
+ context 'Resque::SchedulerLocking' do
10
+ setup do
11
+ @subject = Class.new { extend Resque::SchedulerLocking }
12
+ end
13
+
14
+ teardown do
15
+ Resque.redis.del(@subject.master_lock.key)
16
+ end
17
+
18
+ test 'it should use the basic lock mechanism for <= Redis 2.4' do
19
+ Resque.redis.stubs(:info).returns('redis_version' => '2.4.16')
20
+
21
+ assert_equal @subject.master_lock.class, Resque::Scheduler::Lock::Basic
22
+ end
23
+
24
+ test 'it should use the resilient lock mechanism for > Redis 2.4' do
25
+ Resque.redis.stubs(:info).returns('redis_version' => '2.5.12')
26
+
27
+ assert_equal @subject.master_lock.class, Resque::Scheduler::Lock::Resilient
28
+ end
29
+
30
+ test 'it should be the master if the lock is held' do
31
+ @subject.master_lock.acquire!
32
+ assert @subject.is_master?, 'should be master'
33
+ end
34
+
35
+ test 'it should not be the master if the lock is held by someone else' do
36
+ Resque.redis.set(@subject.master_lock.key, 'somethingelse:1234')
37
+ assert !@subject.is_master?, 'should not be master'
38
+ end
39
+
40
+ test "release_master_lock should delegate to master_lock" do
41
+ @subject.master_lock.expects(:release!)
42
+ @subject.release_master_lock!
43
+ end
44
+ end
45
+
46
+ context 'Resque::Scheduler::Lock::Base' do
47
+ setup do
48
+ @lock = Resque::Scheduler::Lock::Base.new('test_lock_key')
49
+ end
50
+
51
+ test '#acquire! should be not implemented' do
52
+ assert_raise(NotImplementedError) do
53
+ @lock.acquire!
54
+ end
55
+ end
56
+
57
+ test '#locked? should be not implemented' do
58
+ assert_raise(NotImplementedError) do
59
+ @lock.locked?
60
+ end
61
+ end
62
+ end
63
+
64
+ context 'Resque::Scheduler::Lock::Basic' do
65
+ include LockTestHelper
66
+
67
+ setup do
68
+ @lock = Resque::Scheduler::Lock::Basic.new('test_lock_key')
69
+ end
70
+
71
+ teardown do
72
+ @lock.release!
73
+ end
74
+
75
+ test 'you should not have the lock if someone else holds it' do
76
+ lock_is_not_held(@lock)
77
+
78
+ assert !@lock.locked?
79
+ end
80
+
81
+ test 'you should not be able to acquire the lock if someone else holds it' do
82
+ lock_is_not_held(@lock)
83
+
84
+ assert !@lock.acquire!
85
+ end
86
+
87
+ test "the lock should receive a TTL on acquiring" do
88
+ @lock.acquire!
89
+
90
+ assert Resque.redis.ttl(@lock.key) > 0, "lock should expire"
91
+ end
92
+
93
+ test 'releasing should release the master lock' do
94
+ assert @lock.acquire!, 'should have acquired the master lock'
95
+ assert @lock.locked?, 'should be locked'
96
+
97
+ @lock.release!
98
+
99
+ assert !@lock.locked?, 'should not be locked'
100
+ end
101
+
102
+ test 'checking the lock should increase the TTL if we hold it' do
103
+ @lock.acquire!
104
+ Resque.redis.setex(@lock.key, 10, @lock.value)
105
+
106
+ @lock.locked?
107
+
108
+ assert Resque.redis.ttl(@lock.key) > 10, "TTL should have been updated"
109
+ end
110
+
111
+ test 'checking the lock should not increase the TTL if we do not hold it' do
112
+ Resque.redis.setex(@lock.key, 10, @lock.value)
113
+ lock_is_not_held(@lock)
114
+
115
+ @lock.locked?
116
+
117
+ assert Resque.redis.ttl(@lock.key) <= 10, "TTL should not have been updated"
118
+ end
119
+ end
120
+
121
+ context 'Resque::Scheduler::Lock::Resilient' do
122
+ include LockTestHelper
123
+
124
+ if !Resque::Scheduler.supports_lua?
125
+ puts "*** Skipping Resque::Scheduler::Lock::Resilient tests, as they require Redis >= 2.5."
126
+ else
127
+ setup do
128
+ @lock = Resque::Scheduler::Lock::Resilient.new('test_resilient_lock')
129
+ end
130
+
131
+ teardown do
132
+ @lock.release!
133
+ end
134
+
135
+ test 'you should not have the lock if someone else holds it' do
136
+ lock_is_not_held(@lock)
137
+
138
+ assert !@lock.locked?, 'you should not have the lock'
139
+ end
140
+
141
+ test 'you should not be able to acquire the lock if someone else holds it' do
142
+ lock_is_not_held(@lock)
143
+
144
+ assert !@lock.acquire!
145
+ end
146
+
147
+ test "the lock should receive a TTL on acquiring" do
148
+ @lock.acquire!
149
+
150
+ assert Resque.redis.ttl(@lock.key) > 0, "lock should expire"
151
+ end
152
+
153
+ test 'releasing should release the master lock' do
154
+ assert @lock.acquire!, 'should have acquired the master lock'
155
+ assert @lock.locked?, 'should be locked'
156
+
157
+ @lock.release!
158
+
159
+ assert !@lock.locked?, 'should not be locked'
160
+ end
161
+
162
+ test 'checking the lock should increase the TTL if we hold it' do
163
+ @lock.acquire!
164
+ Resque.redis.setex(@lock.key, 10, @lock.value)
165
+
166
+ @lock.locked?
167
+
168
+ assert Resque.redis.ttl(@lock.key) > 10, "TTL should have been updated"
169
+ end
170
+
171
+ test 'checking the lock should not increase the TTL if we do not hold it' do
172
+ Resque.redis.setex(@lock.key, 10, @lock.value)
173
+ lock_is_not_held(@lock)
174
+
175
+ @lock.locked?
176
+
177
+ assert Resque.redis.ttl(@lock.key) <= 10, "TTL should not have been updated"
178
+ end
179
+ end
180
+ end
@@ -0,0 +1,59 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ context "Resque::Scheduler" do
4
+
5
+ setup do
6
+ Resque::Scheduler.dynamic = false
7
+ Resque.redis.flushall
8
+ Resque::Scheduler.clear_schedule!
9
+ end
10
+
11
+ test 'set custom logger' do
12
+ custom_logger = Logger.new('/dev/null')
13
+ Resque::Scheduler.logger = custom_logger
14
+ assert_equal(custom_logger, Resque::Scheduler.logger)
15
+ end
16
+
17
+ context 'logger default settings' do
18
+ setup do
19
+ nullify_logger
20
+ end
21
+
22
+ test 'uses STDOUT' do
23
+ assert_equal(Resque::Scheduler.logger.instance_variable_get(:@logdev).dev, STDOUT)
24
+ end
25
+ test 'not verbose' do
26
+ assert Resque::Scheduler.logger.level > Logger::DEBUG
27
+ end
28
+ test 'not muted' do
29
+ assert Resque::Scheduler.logger.level < Logger::FATAL
30
+ end
31
+
32
+ teardown do
33
+ nullify_logger
34
+ end
35
+ end
36
+
37
+ context 'logger custom settings' do
38
+ setup do
39
+ nullify_logger
40
+ end
41
+
42
+ test 'uses logfile' do
43
+ Resque::Scheduler.logfile = '/dev/null'
44
+ assert_equal(Resque::Scheduler.logger.instance_variable_get(:@logdev).filename, '/dev/null')
45
+ end
46
+ test 'set verbosity' do
47
+ Resque::Scheduler.verbose = true
48
+ assert Resque::Scheduler.logger.level == Logger::DEBUG
49
+ end
50
+ test 'mute logger' do
51
+ Resque::Scheduler.mute = true
52
+ assert Resque::Scheduler.logger.level == Logger::FATAL
53
+ end
54
+
55
+ teardown do
56
+ nullify_logger
57
+ end
58
+ end
59
+ end
@@ -4,8 +4,7 @@ context "Resque::Scheduler" do
4
4
 
5
5
  setup do
6
6
  Resque::Scheduler.dynamic = false
7
- Resque.redis.del(:schedules)
8
- Resque.redis.del(:schedules_changed)
7
+ Resque.redis.flushall
9
8
  Resque::Scheduler.mute = true
10
9
  Resque::Scheduler.clear_schedule!
11
10
  Resque::Scheduler.send(:class_variable_set, :@@scheduled_jobs, {})
@@ -47,7 +46,7 @@ context "Resque::Scheduler" do
47
46
  Resque::Scheduler.load_schedule!
48
47
 
49
48
  assert_equal(1, Resque::Scheduler.rufus_scheduler.all_jobs.size)
50
- assert Resque::Scheduler.scheduled_jobs.include?(:some_ivar_job)
49
+ assert Resque::Scheduler.scheduled_jobs.include?('some_ivar_job')
51
50
  end
52
51
 
53
52
  test "can reload schedule" do
@@ -61,7 +60,7 @@ context "Resque::Scheduler" do
61
60
 
62
61
  Resque.redis.del(:schedules)
63
62
  Resque.redis.hset(:schedules, "some_ivar_job2", Resque.encode(
64
- {'cron' => "* * * * *", 'class' => 'SomeIvarJob', 'args' => "/tmp/2"}
63
+ 'cron' => "* * * * *", 'class' => 'SomeIvarJob', 'args' => "/tmp/2"
65
64
  ))
66
65
 
67
66
  Resque::Scheduler.reload_schedule!
@@ -72,15 +71,15 @@ context "Resque::Scheduler" do
72
71
  assert Resque::Scheduler.scheduled_jobs.include?("some_ivar_job2")
73
72
  end
74
73
 
75
- test "load_schedule_job loads a schedule" do
74
+ test "load_schedule_job loads a schedule" do
76
75
  Resque::Scheduler.load_schedule_job("some_ivar_job", {'cron' => "* * * * *", 'class' => 'SomeIvarJob', 'args' => "/tmp"})
77
76
 
78
77
  assert_equal(1, Resque::Scheduler.rufus_scheduler.all_jobs.size)
79
78
  assert_equal(1, Resque::Scheduler.scheduled_jobs.size)
80
79
  assert Resque::Scheduler.scheduled_jobs.keys.include?("some_ivar_job")
81
80
  end
82
-
83
- test "load_schedule_job with every with options" do
81
+
82
+ test "load_schedule_job with every with options" do
84
83
  Resque::Scheduler.load_schedule_job("some_ivar_job", {'every' => ['30s', {'first_in' => '60s'}], 'class' => 'SomeIvarJob', 'args' => "/tmp"})
85
84
 
86
85
  assert_equal(1, Resque::Scheduler.rufus_scheduler.all_jobs.size)
@@ -88,8 +87,8 @@ context "Resque::Scheduler" do
88
87
  assert Resque::Scheduler.scheduled_jobs.keys.include?("some_ivar_job")
89
88
  assert Resque::Scheduler.scheduled_jobs["some_ivar_job"].params.keys.include?(:first_in)
90
89
  end
91
-
92
- test "load_schedule_job with cron with options" do
90
+
91
+ test "load_schedule_job with cron with options" do
93
92
  Resque::Scheduler.load_schedule_job("some_ivar_job", {'cron' => ['* * * * *', {'allow_overlapping' => 'true'}], 'class' => 'SomeIvarJob', 'args' => "/tmp"})
94
93
 
95
94
  assert_equal(1, Resque::Scheduler.rufus_scheduler.all_jobs.size)
@@ -193,6 +192,17 @@ context "Resque::Scheduler" do
193
192
  Resque.decode(Resque.redis.hget(:schedules, "my_ivar_job")))
194
193
  end
195
194
 
195
+ test "schedule= removes schedules not present in the given schedule argument" do
196
+ Resque::Scheduler.dynamic = true
197
+
198
+ Resque.schedule = {"old_job" => {'cron' => "* * * * *", 'class' => 'OldJob'}}
199
+ assert_equal({"old_job" => {'cron' => "* * * * *", 'class' => 'OldJob'}}, Resque.schedule)
200
+
201
+ Resque.schedule = {"new_job" => {'cron' => "* * * * *", 'class' => 'NewJob'}}
202
+ Resque.reload_schedule!
203
+ assert_equal({"new_job" => {'cron' => "* * * * *", 'class' => 'NewJob'}}, Resque.schedule)
204
+ end
205
+
196
206
  test "schedule= uses job name as 'class' argument if it's missing" do
197
207
  Resque::Scheduler.dynamic = true
198
208
  Resque.schedule = {"SomeIvarJob" => {
@@ -222,7 +232,7 @@ context "Resque::Scheduler" do
222
232
 
223
233
  test "get_schedule returns a schedule" do
224
234
  Resque.redis.hset(:schedules, "some_ivar_job2", Resque.encode(
225
- {'cron' => "* * * * *", 'class' => 'SomeIvarJob', 'args' => "/tmp/33"}
235
+ 'cron' => "* * * * *", 'class' => 'SomeIvarJob', 'args' => "/tmp/33"
226
236
  ))
227
237
  assert_equal({'cron' => "* * * * *", 'class' => 'SomeIvarJob', 'args' => "/tmp/33"},
228
238
  Resque.get_schedule("some_ivar_job2"))
@@ -230,7 +240,7 @@ context "Resque::Scheduler" do
230
240
 
231
241
  test "remove_schedule removes a schedule" do
232
242
  Resque.redis.hset(:schedules, "some_ivar_job3", Resque.encode(
233
- {'cron' => "* * * * *", 'class' => 'SomeIvarJob', 'args' => "/tmp/44"}
243
+ 'cron' => "* * * * *", 'class' => 'SomeIvarJob', 'args' => "/tmp/44"
234
244
  ))
235
245
  Resque.remove_schedule("some_ivar_job3")
236
246
  assert_equal nil, Resque.redis.hget(:schedules, "some_ivar_job3")
@@ -243,5 +253,4 @@ context "Resque::Scheduler" do
243
253
  Resque::Plugin.lint(ResqueScheduler)
244
254
  end
245
255
  end
246
-
247
256
  end
@@ -0,0 +1,129 @@
1
+ require 'socket'
2
+ require 'timeout'
3
+ require 'fileutils'
4
+
5
+ class RedisInstance
6
+ class << self
7
+ @running = false
8
+ @port = nil
9
+ @pid = nil
10
+
11
+ def run_if_needed!
12
+ run! unless running?
13
+ end
14
+
15
+ def run!
16
+ ensure_pid_directory
17
+ reassign_redis_clients
18
+ start_redis_server
19
+
20
+ if $?.success?
21
+ wait_for_pid
22
+ puts "Booted isolated Redis on port #{port} with PID #{pid}."
23
+
24
+ wait_for_redis_boot
25
+
26
+ # Ensure we tear down Redis on Ctrl+C / test failure.
27
+ at_exit do
28
+ RedisInstance.stop!
29
+ end
30
+ else
31
+ fail "Failed to start Redis on port #{port}."
32
+ end
33
+
34
+ @running = true
35
+ end
36
+
37
+ def stop!
38
+ $stdout.puts "Sending TERM to Redis (#{pid})..."
39
+ Process.kill('TERM', pid)
40
+
41
+ @port = nil
42
+ @running = false
43
+ @pid = nil
44
+ end
45
+
46
+ def running?
47
+ @running
48
+ end
49
+
50
+ private
51
+
52
+ def wait_for_redis_boot
53
+ Timeout::timeout(10) do
54
+ begin
55
+ while Resque.redis.ping != 'PONG'
56
+ end
57
+ rescue
58
+ # silence all errors
59
+ end
60
+ end
61
+ end
62
+
63
+ def ensure_pid_directory
64
+ FileUtils.mkdir_p(File.dirname(pid_file))
65
+ end
66
+
67
+ def reassign_redis_clients
68
+ Resque.redis = Redis.new(:hostname => '127.0.0.1', :port => port, :thread_safe => true)
69
+ end
70
+
71
+ def start_redis_server
72
+ IO.popen("redis-server -", "w+") do |server|
73
+ server.write(config)
74
+ server.close_write
75
+ end
76
+ end
77
+
78
+ def pid
79
+ @pid ||= File.read(pid_file).to_i
80
+ end
81
+
82
+ def wait_for_pid
83
+ Timeout::timeout(10) do
84
+ while !File.exist?(pid_file)
85
+ end
86
+ end
87
+ end
88
+
89
+ def port
90
+ @port ||= random_port
91
+ end
92
+
93
+ def pid_file
94
+ "/tmp/redis-scheduler-test.pid"
95
+ end
96
+
97
+ def config
98
+ <<-EOF
99
+ daemonize yes
100
+ pidfile #{pid_file}
101
+ port #{port}
102
+ EOF
103
+ end
104
+
105
+ # Returns a random port in the upper (10000-65535) range.
106
+ def random_port
107
+ ports = (10000..65535).to_a
108
+
109
+ loop do
110
+ port = ports[rand(ports.size)]
111
+ return port if port_available?('127.0.0.1', port)
112
+ end
113
+ end
114
+
115
+ def port_available?(ip, port, seconds=1)
116
+ Timeout::timeout(seconds) do
117
+ begin
118
+ TCPSocket.new(ip, port).close
119
+ false
120
+ rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
121
+ true
122
+ end
123
+ end
124
+ rescue Timeout::Error
125
+ true
126
+ end
127
+ end
128
+ end
129
+
data/test/test_helper.rb CHANGED
@@ -6,7 +6,7 @@ dir = File.dirname(File.expand_path(__FILE__))
6
6
 
7
7
  require 'rubygems'
8
8
  require 'test/unit'
9
- require 'mocha'
9
+ require 'mocha/setup'
10
10
  require 'resque'
11
11
  $LOAD_PATH.unshift File.dirname(File.expand_path(__FILE__)) + '/../lib'
12
12
  require 'resque_scheduler'
@@ -23,10 +23,10 @@ if !system("which redis-server")
23
23
  end
24
24
 
25
25
 
26
- #
27
- # start our own redis when the tests start,
28
- # kill it when they end
29
- #
26
+ # Start our own Redis when the tests start. RedisInstance will take care of
27
+ # starting and stopping.
28
+ require File.dirname(__FILE__) + '/support/redis_instance'
29
+ RedisInstance.run!
30
30
 
31
31
  at_exit do
32
32
  next if $!
@@ -37,17 +37,9 @@ at_exit do
37
37
  exit_code = Test::Unit::AutoRunner.run
38
38
  end
39
39
 
40
- pid = `ps -e -o pid,command | grep [r]edis-test`.split(" ")[0]
41
- puts "Killing test redis server..."
42
- `rm -f #{dir}/dump.rdb`
43
- Process.kill("KILL", pid.to_i)
44
40
  exit exit_code
45
41
  end
46
42
 
47
- puts "Starting redis for testing at localhost:9736..."
48
- `redis-server #{dir}/redis-test.conf`
49
- Resque.redis = 'localhost:9736'
50
-
51
43
  ##
52
44
  # test/spec/mini 3
53
45
  # http://gist.github.com/25455
@@ -65,13 +57,18 @@ def context(*args, &block)
65
57
  def self.teardown(&block) define_method(:teardown, &block) end
66
58
  end
67
59
  (class << klass; self end).send(:define_method, :name) { name.gsub(/\W/,'_') }
68
- klass.class_eval &block
60
+ klass.class_eval(&block)
69
61
  end
70
62
 
71
63
  class FakeCustomJobClass
72
64
  def self.scheduled(queue, klass, *args); end
73
65
  end
74
66
 
67
+ class FakeCustomJobClassEnqueueAt
68
+ @queue = :test
69
+ def self.scheduled(queue, klass, *args); end
70
+ end
71
+
75
72
  class SomeJob
76
73
  def self.perform(repo_id, path)
77
74
  end
@@ -86,3 +83,18 @@ class SomeRealClass
86
83
  :some_real_queue
87
84
  end
88
85
  end
86
+
87
+ class JobWithParams
88
+ def self.perform(*args)
89
+ @args = args
90
+ end
91
+ end
92
+
93
+ JobWithoutParams = Class.new(JobWithParams)
94
+
95
+ def nullify_logger
96
+ Resque::Scheduler.mute = nil
97
+ Resque::Scheduler.verbose = nil
98
+ Resque::Scheduler.logfile = nil
99
+ Resque::Scheduler.logger = nil
100
+ end