resque-scheduler 2.0.0 → 2.3.1

Sign up to get free protection for your applications and to get access to all the features.

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