sidekiq 2.17.8 → 3.0.0

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

Potentially problematic release.


This version of sidekiq might be problematic. Click here for more details.

Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/3.0-Upgrade.md +63 -0
  3. data/Changes.md +66 -3
  4. data/Contributing.md +1 -3
  5. data/Pro-Changes.md +18 -0
  6. data/README.md +2 -2
  7. data/bin/sidekiqctl +19 -6
  8. data/lib/sidekiq.rb +53 -11
  9. data/lib/sidekiq/actor.rb +1 -0
  10. data/lib/sidekiq/api.rb +145 -58
  11. data/lib/sidekiq/cli.rb +22 -18
  12. data/lib/sidekiq/client.rb +44 -14
  13. data/lib/sidekiq/core_ext.rb +5 -8
  14. data/lib/sidekiq/exception_handler.rb +19 -28
  15. data/lib/sidekiq/fetch.rb +3 -3
  16. data/lib/sidekiq/launcher.rb +30 -3
  17. data/lib/sidekiq/logging.rb +2 -2
  18. data/lib/sidekiq/manager.rb +19 -16
  19. data/lib/sidekiq/middleware/chain.rb +1 -1
  20. data/lib/sidekiq/middleware/i18n.rb +1 -1
  21. data/lib/sidekiq/middleware/server/retry_jobs.rb +23 -7
  22. data/lib/sidekiq/processor.rb +36 -54
  23. data/lib/sidekiq/redis_connection.rb +1 -3
  24. data/lib/sidekiq/util.rb +4 -4
  25. data/lib/sidekiq/version.rb +1 -1
  26. data/lib/sidekiq/web.rb +57 -8
  27. data/lib/sidekiq/web_helpers.rb +6 -15
  28. data/lib/sidekiq/worker.rb +3 -1
  29. data/sidekiq.gemspec +5 -5
  30. data/test/test_api.rb +59 -19
  31. data/test/test_cli.rb +1 -1
  32. data/test/test_client.rb +44 -5
  33. data/test/test_exception_handler.rb +4 -87
  34. data/test/test_middleware.rb +3 -2
  35. data/test/test_redis_connection.rb +0 -6
  36. data/test/test_retry.rb +13 -68
  37. data/test/test_scheduled.rb +1 -1
  38. data/test/test_scheduling.rb +5 -0
  39. data/test/test_sidekiq.rb +18 -0
  40. data/test/test_web.rb +98 -58
  41. data/web/assets/stylesheets/application.css +5 -0
  42. data/web/locales/cs.yml +68 -0
  43. data/web/locales/da.yml +9 -1
  44. data/web/locales/de.yml +15 -7
  45. data/web/locales/el.yml +68 -0
  46. data/web/locales/en.yml +8 -3
  47. data/web/locales/es.yml +9 -1
  48. data/web/locales/fr.yml +34 -26
  49. data/web/locales/it.yml +26 -18
  50. data/web/locales/ja.yml +8 -2
  51. data/web/locales/ko.yml +0 -2
  52. data/web/locales/nl.yml +8 -3
  53. data/web/locales/no.yml +9 -3
  54. data/web/locales/pl.yml +0 -1
  55. data/web/locales/pt-br.yml +11 -4
  56. data/web/locales/pt.yml +8 -1
  57. data/web/locales/ru.yml +29 -22
  58. data/web/locales/sv.yml +68 -0
  59. data/web/locales/zh-tw.yml +68 -0
  60. data/web/views/_job_info.erb +8 -2
  61. data/web/views/_summary.erb +13 -7
  62. data/web/views/busy.erb +55 -0
  63. data/web/views/dead.erb +30 -0
  64. data/web/views/layout.erb +1 -0
  65. data/web/views/morgue.erb +66 -0
  66. metadata +29 -30
  67. data/config.ru +0 -18
  68. data/lib/sidekiq/capistrano.rb +0 -5
  69. data/lib/sidekiq/capistrano2.rb +0 -54
  70. data/lib/sidekiq/tasks/sidekiq.rake +0 -119
  71. data/lib/sidekiq/yaml_patch.rb +0 -21
  72. data/test/test_util.rb +0 -18
  73. data/web/views/_workers.erb +0 -22
  74. data/web/views/workers.erb +0 -16
@@ -109,7 +109,7 @@ class TestCli < Sidekiq::Test
109
109
  after do
110
110
  Sidekiq.logger = @old_logger
111
111
  Sidekiq.options.delete(:logfile)
112
- File.unlink @tmp_log_path if File.exists?(@tmp_log_path)
112
+ File.unlink @tmp_log_path if File.exist?(@tmp_log_path)
113
113
  end
114
114
 
115
115
  it 'sets the logfile path' do
@@ -20,10 +20,12 @@ class TestClient < Sidekiq::Test
20
20
  def @redis.with; yield self; end
21
21
  def @redis.exec; true; end
22
22
  Sidekiq.instance_variable_set(:@redis, @redis)
23
+ Sidekiq::Client.instance_variable_set(:@default, nil)
23
24
  end
24
25
 
25
26
  after do
26
27
  Sidekiq.instance_variable_set(:@redis, REDIS)
28
+ Sidekiq::Client.instance_variable_set(:@default, nil)
27
29
  end
28
30
 
29
31
  it 'raises ArgumentError with invalid params' do
@@ -59,7 +61,7 @@ class TestClient < Sidekiq::Test
59
61
  it 'allows local middleware modification' do
60
62
  @redis.expect :lpush, 1, ['queue:default', Array]
61
63
  $called = false
62
- mware = Class.new { def call(worker_klass,msg,q); $called = true; msg;end }
64
+ mware = Class.new { def call(worker_klass,msg,q,r); $called = true; msg;end }
63
65
  client = Sidekiq::Client.new
64
66
  client.middleware do |chain|
65
67
  chain.add mware
@@ -177,7 +179,7 @@ class TestClient < Sidekiq::Test
177
179
  end
178
180
  it 'returns the jids for the jobs' do
179
181
  Sidekiq::Client.push_bulk('class' => 'QueuedWorker', 'args' => (1..2).to_a.map { |x| Array(x) }).each do |jid|
180
- assert_match /[0-9a-f]{12}/, jid
182
+ assert_match(/[0-9a-f]{12}/, jid)
181
183
  end
182
184
  end
183
185
  end
@@ -198,7 +200,8 @@ class TestClient < Sidekiq::Test
198
200
  describe 'client middleware' do
199
201
 
200
202
  class Stopper
201
- def call(worker_class, message, queue)
203
+ def call(worker_class, message, queue, r)
204
+ raise ArgumentError unless r
202
205
  yield if message['args'].first.odd?
203
206
  end
204
207
  end
@@ -207,9 +210,9 @@ class TestClient < Sidekiq::Test
207
210
  Sidekiq.client_middleware.add Stopper
208
211
  begin
209
212
  assert_equal nil, Sidekiq::Client.push('class' => MyWorker, 'args' => [0])
210
- assert_match /[0-9a-f]{12}/, Sidekiq::Client.push('class' => MyWorker, 'args' => [1])
213
+ assert_match(/[0-9a-f]{12}/, Sidekiq::Client.push('class' => MyWorker, 'args' => [1]))
211
214
  Sidekiq::Client.push_bulk('class' => MyWorker, 'args' => [[0], [1]]).each do |jid|
212
- assert_match /[0-9a-f]{12}/, jid
215
+ assert_match(/[0-9a-f]{12}/, jid)
213
216
  end
214
217
  ensure
215
218
  Sidekiq.client_middleware.remove Stopper
@@ -233,4 +236,40 @@ class TestClient < Sidekiq::Test
233
236
  assert_equal 2, Sidekiq::Client.new.__send__(:normalize_item, 'class' => CWorker, 'args' => [])['retry']
234
237
  end
235
238
  end
239
+
240
+ describe 'sharding' do
241
+ class DWorker < BaseWorker
242
+ end
243
+ it 'allows sidekiq_options to point to different Redi' do
244
+ conn = MiniTest::Mock.new
245
+ conn.expect(:multi, [0, 1])
246
+ DWorker.sidekiq_options('pool' => ConnectionPool.new(size: 1) { conn })
247
+ DWorker.perform_async(1,2,3)
248
+ conn.verify
249
+ end
250
+ it 'allows #via to point to different Redi' do
251
+ conn = MiniTest::Mock.new
252
+ conn.expect(:multi, [0, 1])
253
+ default = Sidekiq::Client.new.redis_pool
254
+ sharded_pool = ConnectionPool.new(size: 1) { conn }
255
+ Sidekiq::Client.via(sharded_pool) do
256
+ CWorker.perform_async(1,2,3)
257
+ assert_equal sharded_pool, Sidekiq::Client.new.redis_pool
258
+ assert_raises RuntimeError do
259
+ Sidekiq::Client.via(default) do
260
+ # nothing
261
+ end
262
+ end
263
+ end
264
+ assert_equal default, Sidekiq::Client.new.redis_pool
265
+ conn.verify
266
+ end
267
+ it 'allows Resque helpers to point to different Redi' do
268
+ conn = MiniTest::Mock.new
269
+ conn.expect(:zadd, 1, [String, Array])
270
+ DWorker.sidekiq_options('pool' => ConnectionPool.new(size: 1) { conn })
271
+ Sidekiq::Client.enqueue_in(10, DWorker, 3)
272
+ conn.verify
273
+ end
274
+ end
236
275
  end
@@ -1,5 +1,4 @@
1
1
  require 'helper'
2
- require 'sidekiq'
3
2
  require 'sidekiq/exception_handler'
4
3
  require 'stringio'
5
4
  require 'logger'
@@ -33,9 +32,9 @@ class TestExceptionHandler < Sidekiq::Test
33
32
  Component.new.invoke_exception(:a => 1)
34
33
  @str_logger.rewind
35
34
  log = @str_logger.readlines
36
- assert_match /a=>1/, log[0], "didn't include the context"
37
- assert_match /Something didn't work!/, log[1], "didn't include the exception message"
38
- assert_match /test\/test_exception_handler.rb/, log[2], "didn't include the backtrace"
35
+ assert_match(/a=>1/, log[0], "didn't include the context")
36
+ assert_match(/Something didn't work!/, log[1], "didn't include the exception message")
37
+ assert_match(/test\/test_exception_handler.rb/, log[2], "didn't include the backtrace")
39
38
  end
40
39
 
41
40
  describe "when the exception does not have a backtrace" do
@@ -46,93 +45,11 @@ class TestExceptionHandler < Sidekiq::Test
46
45
  begin
47
46
  Component.new.handle_exception exception
48
47
  pass
49
- rescue => e
48
+ rescue StandardError
50
49
  flunk "failed handling a nil backtrace"
51
50
  end
52
51
  end
53
52
  end
54
53
  end
55
54
 
56
- describe "with fake Airbrake" do
57
- before do
58
- ::Airbrake = Minitest::Mock.new
59
- end
60
-
61
- after do
62
- Object.__send__(:remove_const, "Airbrake") # HACK should probably inject Airbrake etc into this class in the future
63
- end
64
-
65
- it "notifies Airbrake" do
66
- ::Airbrake.expect(:notify_or_ignore,nil,[TEST_EXCEPTION,:parameters => { :a => 1 }])
67
- Component.new.invoke_exception(:a => 1)
68
- ::Airbrake.verify
69
- end
70
- end
71
-
72
- describe "with fake Honeybadger" do
73
- before do
74
- ::Honeybadger = Minitest::Mock.new
75
- end
76
-
77
- after do
78
- Object.__send__(:remove_const, "Honeybadger") # HACK should probably inject Honeybadger etc into this class in the future
79
- end
80
-
81
- it "notifies Honeybadger" do
82
- ::Honeybadger.expect(:notify_or_ignore,nil,[TEST_EXCEPTION,:parameters => { :a => 1 }])
83
- Component.new.invoke_exception(:a => 1)
84
- ::Honeybadger.verify
85
- end
86
- end
87
-
88
- describe "with fake ExceptionNotifier" do
89
- before do
90
- ::ExceptionNotifier = MiniTest::Mock.new
91
- end
92
-
93
- after do
94
- Object.__send__(:remove_const, "ExceptionNotifier")
95
- end
96
-
97
- it "notifies ExceptionNotifier" do
98
- ::ExceptionNotifier.expect(:notify_exception,true,[TEST_EXCEPTION, :data => { :message => { :b => 2 } }])
99
- Component.new.invoke_exception(:b => 2)
100
- ::ExceptionNotifier.verify
101
- end
102
- end
103
-
104
- describe "with fake Exceptional" do
105
- before do
106
- ::Exceptional = Class.new do
107
-
108
- def self.context(msg)
109
- @msg = msg
110
- end
111
-
112
- def self.check_context
113
- @msg
114
- end
115
- end
116
-
117
- ::Exceptional::Config = Minitest::Mock.new
118
- ::Exceptional::Remote = Minitest::Mock.new
119
- ::Exceptional::ExceptionData = Minitest::Mock.new
120
- end
121
-
122
- after do
123
- Object.__send__(:remove_const, "Exceptional")
124
- end
125
-
126
- it "notifies Exceptional" do
127
- ::Exceptional::Config.expect(:should_send_to_api?,true)
128
- exception_data = Object.new
129
- ::Exceptional::Remote.expect(:error,nil,[exception_data])
130
- ::Exceptional::ExceptionData.expect(:new,exception_data,[TEST_EXCEPTION])
131
- Component.new.invoke_exception(:c => 3)
132
- assert_equal({:c => 3},::Exceptional.check_context,"did not record arguments properly")
133
- ::Exceptional::Config.verify
134
- ::Exceptional::Remote.verify
135
- ::Exceptional::ExceptionData.verify
136
- end
137
- end
138
55
  end
@@ -112,6 +112,7 @@ class TestMiddleware < Sidekiq::Test
112
112
  describe 'i18n' do
113
113
  before do
114
114
  require 'i18n'
115
+ I18n.enforce_available_locales = false
115
116
  require 'sidekiq/middleware/i18n'
116
117
  end
117
118
 
@@ -119,7 +120,7 @@ class TestMiddleware < Sidekiq::Test
119
120
  I18n.locale = 'fr'
120
121
  msg = {}
121
122
  mw = Sidekiq::Middleware::I18n::Client.new
122
- mw.call(nil, msg, nil) { }
123
+ mw.call(nil, msg, nil, nil) { }
123
124
  assert_equal :fr, msg['locale']
124
125
 
125
126
  msg['locale'] = 'jp'
@@ -142,7 +143,7 @@ class TestMiddleware < Sidekiq::Test
142
143
  assert_equal :jp, I18n.locale
143
144
  end
144
145
 
145
- I18n.enforce_available_locales = nil
146
+ I18n.enforce_available_locales = false
146
147
  I18n.available_locales = nil
147
148
  end
148
149
  end
@@ -81,12 +81,6 @@ class TestRedisConnection < Sidekiq::Test
81
81
  ENV[var] = nil
82
82
  end
83
83
 
84
- describe "with REDISTOGO_URL set" do
85
- it "sets connection URI to RedisToGo" do
86
- with_env_var 'REDISTOGO_URL', 'redis://redis-to-go:6379/0'
87
- end
88
- end
89
-
90
84
  describe "with REDISTOGO_URL and a parallel REDIS_PROVIDER set" do
91
85
  it "sets connection URI to the provider" do
92
86
  uri = 'redis://sidekiq-redis-provider:6379/0'
@@ -10,6 +10,7 @@ class TestRetry < Sidekiq::Test
10
10
  Sidekiq.instance_variable_set(:@redis, @redis)
11
11
 
12
12
  def @redis.with; yield self; end
13
+ def @redis.multi; yield self; end
13
14
  end
14
15
 
15
16
  let(:worker) do
@@ -50,6 +51,9 @@ class TestRetry < Sidekiq::Test
50
51
  1.upto(max_retries) do
51
52
  @redis.expect :zadd, 1, ['retry', String, String]
52
53
  end
54
+ @redis.expect :zadd, 1, ['dead', Float, String]
55
+ @redis.expect :zremrangebyscore, 0, ['dead', String, Float]
56
+ @redis.expect :zremrangebyrank, 0, ['dead', Numeric, Numeric]
53
57
  msg = { 'class' => 'Bob', 'args' => [1,2,'foo'], 'retry' => true }
54
58
  handler = Sidekiq::Middleware::Server::RetryJobs.new({:max_retries => max_retries})
55
59
  1.upto(max_retries + 1) do
@@ -166,90 +170,31 @@ class TestRetry < Sidekiq::Test
166
170
  it 'throws away old messages after too many retries (using the default)' do
167
171
  now = Time.now.to_f
168
172
  msg = {"class"=>"Bob", "args"=>[1, 2, "foo"], "queue"=>"default", "error_message"=>"kerblammo!", "error_class"=>"RuntimeError", "failed_at"=>now, "retry"=>true, "retry_count"=>25}
169
- @redis.expect :zadd, 1, [ 'retry', String, String ]
173
+ @redis.expect :zadd, 1, ['dead', Float, String]
174
+ @redis.expect :zremrangebyscore, 0, ['dead', String, Float]
175
+ @redis.expect :zremrangebyrank, 0, ['dead', Numeric, Numeric]
170
176
  handler = Sidekiq::Middleware::Server::RetryJobs.new
171
177
  assert_raises RuntimeError do
172
178
  handler.call(worker, msg, 'default') do
173
179
  raise "kerblammo!"
174
180
  end
175
181
  end
176
- # Minitest can't assert that a method call did NOT happen!?
177
- assert_raises(MockExpectationError) { @redis.verify }
182
+ @redis.verify
178
183
  end
179
184
 
180
185
  it 'throws away old messages after too many retries (using user-specified max)' do
181
186
  now = Time.now.to_f
182
187
  msg = {"class"=>"Bob", "args"=>[1, 2, "foo"], "queue"=>"default", "error_message"=>"kerblammo!", "error_class"=>"RuntimeError", "failed_at"=>now, "retry"=>3, "retry_count"=>3}
183
- @redis.expect :zadd, 1, [ 'retry', String, String ]
188
+ @redis.expect :zadd, 1, ['dead', Float, String]
189
+ @redis.expect :zremrangebyscore, 0, ['dead', String, Float]
190
+ @redis.expect :zremrangebyrank, 0, ['dead', Numeric, Numeric]
184
191
  handler = Sidekiq::Middleware::Server::RetryJobs.new
185
192
  assert_raises RuntimeError do
186
193
  handler.call(worker, msg, 'default') do
187
194
  raise "kerblammo!"
188
195
  end
189
196
  end
190
- # Minitest can't assert that a method call did NOT happen!?
191
- assert_raises(MockExpectationError) { @redis.verify }
192
- end
193
-
194
- describe "retry exhaustion" do
195
- let(:handler){ Sidekiq::Middleware::Server::RetryJobs.new }
196
- let(:worker) { Minitest::Mock.new }
197
- let(:msg){ {"class"=>"Bob", "args"=>[1, 2, "foo"], "queue"=>"default", "error_message"=>"kerblammo!", "error_class"=>"RuntimeError", "failed_at"=>Time.now.to_f, "retry"=>3, "retry_count"=>3} }
198
-
199
- describe "worker method" do
200
- let(:worker) do
201
- klass = Class.new do
202
- include Sidekiq::Worker
203
-
204
- def self.name; "Worker"; end
205
-
206
- def retries_exhausted(*args)
207
- args << "retried_method"
208
- end
209
- end
210
- end
211
-
212
- it 'calls worker.retries_exhausted after too many retries' do
213
- assert_equal [1,2, "foo", "retried_method"], handler.__send__(:retries_exhausted, worker.new, msg)
214
- end
215
- end
216
-
217
- describe "worker block" do
218
- let(:worker) do
219
- Class.new do
220
- include Sidekiq::Worker
221
-
222
- sidekiq_retries_exhausted do |msg|
223
- msg.tap {|m| m['called_by_callback'] = true }
224
- end
225
- end
226
- end
227
-
228
- it 'calls worker sidekiq_retries_exhausted_block after too many retries' do
229
- new_msg = handler.__send__(:retries_exhausted, worker.new, msg)
230
- expected_msg = msg.merge('called_by_callback' => true)
231
-
232
- assert_equal expected_msg, new_msg, "sidekiq_retries_exhausted block not called"
233
- end
234
- end
235
-
236
- it 'handles and logs retries_exhausted failures gracefully (drops them)' do
237
- def worker.retries_exhausted(*args)
238
- raise 'bam!'
239
- end
240
-
241
- e = task_misbehaving_worker
242
- assert_equal e.message, "kerblammo!"
243
- worker.verify
244
- end
245
-
246
- def task_misbehaving_worker
247
- assert_raises RuntimeError do
248
- handler.call(worker, msg, 'default') do
249
- raise 'kerblammo!'
250
- end
251
- end
252
- end
197
+ @redis.verify
253
198
  end
254
199
 
255
200
  describe "custom retry delay" do
@@ -262,7 +207,7 @@ class TestRetry < Sidekiq::Test
262
207
  after do
263
208
  Sidekiq.logger = @old_logger
264
209
  Sidekiq.options.delete(:logfile)
265
- File.unlink @tmp_log_path if File.exists?(@tmp_log_path)
210
+ File.unlink @tmp_log_path if File.exist?(@tmp_log_path)
266
211
  end
267
212
 
268
213
  let(:custom_worker) do
@@ -28,7 +28,7 @@ class TestScheduled < Sidekiq::Test
28
28
  end
29
29
 
30
30
  class Stopper
31
- def call(worker_class, message, queue)
31
+ def call(worker_class, message, queue, r)
32
32
  yield if message['args'].first.odd?
33
33
  end
34
34
  end
@@ -4,6 +4,7 @@ require 'sidekiq/scheduled'
4
4
  class TestScheduling < Sidekiq::Test
5
5
  describe 'middleware' do
6
6
  before do
7
+ Sidekiq::Client.instance_variable_set(:@default, nil)
7
8
  @redis = Minitest::Mock.new
8
9
  # Ugh, this is terrible.
9
10
  Sidekiq.instance_variable_set(:@redis, @redis)
@@ -11,6 +12,10 @@ class TestScheduling < Sidekiq::Test
11
12
  def @redis.with; yield self; end
12
13
  end
13
14
 
15
+ after do
16
+ Sidekiq::Client.instance_variable_set(:@default, nil)
17
+ end
18
+
14
19
  class ScheduledWorker
15
20
  include Sidekiq::Worker
16
21
  sidekiq_options :queue => :custom_queue
@@ -34,4 +34,22 @@ class TestSidekiq < Sidekiq::Test
34
34
  assert_equal "Calm down, bro\n", $stdout.string
35
35
  end
36
36
  end
37
+
38
+ describe 'lifecycle events' do
39
+ it 'handles invalid input' do
40
+ e = assert_raises ArgumentError do
41
+ Sidekiq.on(:startp)
42
+ end
43
+ assert_match(/Invalid event name/, e.message)
44
+ e = assert_raises ArgumentError do
45
+ Sidekiq.on('startup')
46
+ end
47
+ assert_match(/Symbols only/, e.message)
48
+ Sidekiq.on(:startup) do
49
+ 1 + 1
50
+ end
51
+
52
+ assert_equal 2, Sidekiq.options[:lifecycle_events][:startup].first.call
53
+ end
54
+ end
37
55
  end
@@ -4,6 +4,7 @@ require 'sidekiq/web'
4
4
  require 'rack/test'
5
5
 
6
6
  class TestWeb < Sidekiq::Test
7
+
7
8
  describe 'sidekiq web' do
8
9
  include Rack::Test::Methods
9
10
 
@@ -30,18 +31,20 @@ class TestWeb < Sidekiq::Test
30
31
 
31
32
  it 'can display workers' do
32
33
  Sidekiq.redis do |conn|
33
- identity = 'foo:1234-123abc:default'
34
- conn.sadd('workers', identity)
35
- conn.setex("worker:#{identity}:started", 10, Time.now.to_s)
34
+ conn.incr('busy')
35
+ conn.sadd('processes', 'foo:1234')
36
+ conn.hmset('foo:1234', 'info', Sidekiq.dump_json('hostname' => 'foo', 'started_at' => Time.now.to_f), 'at', Time.now.to_f, 'busy', 4)
37
+ identity = 'foo:1234:workers'
36
38
  hash = {:queue => 'critical', :payload => { 'class' => WebWorker.name, 'args' => [1,'abc'] }, :run_at => Time.now.to_i }
37
- conn.setex("worker:#{identity}", 10, Sidekiq.dump_json(hash))
39
+ conn.hmset(identity, 1001, Sidekiq.dump_json(hash))
38
40
  end
41
+ assert_equal ['1001'], Sidekiq::Workers.new.map { |pid, tid, data| tid }
39
42
 
40
- get '/workers'
43
+ get '/busy'
41
44
  assert_equal 200, last_response.status
42
- assert_match /status-active/, last_response.body
43
- assert_match /critical/, last_response.body
44
- assert_match /WebWorker/, last_response.body
45
+ assert_match(/status-active/, last_response.body)
46
+ assert_match(/critical/, last_response.body)
47
+ assert_match(/WebWorker/, last_response.body)
45
48
  end
46
49
 
47
50
  it 'can display queues' do
@@ -49,8 +52,8 @@ class TestWeb < Sidekiq::Test
49
52
 
50
53
  get '/queues'
51
54
  assert_equal 200, last_response.status
52
- assert_match /foo/, last_response.body
53
- refute_match /HardWorker/, last_response.body
55
+ assert_match(/foo/, last_response.body)
56
+ refute_match(/HardWorker/, last_response.body)
54
57
  end
55
58
 
56
59
  it 'handles queue view' do
@@ -76,26 +79,6 @@ class TestWeb < Sidekiq::Test
76
79
  end
77
80
  end
78
81
 
79
- it 'can clear an empty worker list' do
80
- post '/reset'
81
- assert_equal 302, last_response.status
82
- end
83
-
84
- it 'can clear a non-empty worker list' do
85
- Sidekiq.redis do |conn|
86
- identity = 'foo'
87
- conn.sadd('workers', identity)
88
- end
89
-
90
- post '/reset'
91
-
92
- assert_equal 302, last_response.status
93
-
94
- Sidekiq.redis do |conn|
95
- refute conn.smembers('workers').any?
96
- end
97
- end
98
-
99
82
  it 'can delete a job' do
100
83
  Sidekiq.redis do |conn|
101
84
  conn.rpush('queue:foo', "{}")
@@ -117,15 +100,15 @@ class TestWeb < Sidekiq::Test
117
100
  it 'can display retries' do
118
101
  get '/retries'
119
102
  assert_equal 200, last_response.status
120
- assert_match /found/, last_response.body
121
- refute_match /HardWorker/, last_response.body
103
+ assert_match(/found/, last_response.body)
104
+ refute_match(/HardWorker/, last_response.body)
122
105
 
123
106
  add_retry
124
107
 
125
108
  get '/retries'
126
109
  assert_equal 200, last_response.status
127
- refute_match /found/, last_response.body
128
- assert_match /HardWorker/, last_response.body
110
+ refute_match(/found/, last_response.body)
111
+ assert_match(/HardWorker/, last_response.body)
129
112
  end
130
113
 
131
114
  it 'can display a single retry' do
@@ -134,7 +117,7 @@ class TestWeb < Sidekiq::Test
134
117
  assert_equal 302, last_response.status
135
118
  get "/retries/#{job_params(*params)}"
136
119
  assert_equal 200, last_response.status
137
- assert_match /HardWorker/, last_response.body
120
+ assert_match(/HardWorker/, last_response.body)
138
121
  end
139
122
 
140
123
  it 'handles missing retry' do
@@ -150,7 +133,7 @@ class TestWeb < Sidekiq::Test
150
133
 
151
134
  get "/retries"
152
135
  assert_equal 200, last_response.status
153
- refute_match /#{params.first['args'][2]}/, last_response.body
136
+ refute_match(/#{params.first['args'][2]}/, last_response.body)
154
137
  end
155
138
 
156
139
  it 'can delete all retries' do
@@ -170,21 +153,21 @@ class TestWeb < Sidekiq::Test
170
153
 
171
154
  get '/queues/default'
172
155
  assert_equal 200, last_response.status
173
- assert_match /#{params.first['args'][2]}/, last_response.body
156
+ assert_match(/#{params.first['args'][2]}/, last_response.body)
174
157
  end
175
158
 
176
159
  it 'can display scheduled' do
177
160
  get '/scheduled'
178
161
  assert_equal 200, last_response.status
179
- assert_match /found/, last_response.body
180
- refute_match /HardWorker/, last_response.body
162
+ assert_match(/found/, last_response.body)
163
+ refute_match(/HardWorker/, last_response.body)
181
164
 
182
165
  add_scheduled
183
166
 
184
167
  get '/scheduled'
185
168
  assert_equal 200, last_response.status
186
- refute_match /found/, last_response.body
187
- assert_match /HardWorker/, last_response.body
169
+ refute_match(/found/, last_response.body)
170
+ assert_match(/HardWorker/, last_response.body)
188
171
  end
189
172
 
190
173
  it 'can display a single scheduled job' do
@@ -193,7 +176,7 @@ class TestWeb < Sidekiq::Test
193
176
  assert_equal 302, last_response.status
194
177
  get "/scheduled/#{job_params(*params)}"
195
178
  assert_equal 200, last_response.status
196
- assert_match /HardWorker/, last_response.body
179
+ assert_match(/HardWorker/, last_response.body)
197
180
  end
198
181
 
199
182
  it 'handles missing scheduled job' do
@@ -209,7 +192,7 @@ class TestWeb < Sidekiq::Test
209
192
 
210
193
  get '/queues/default'
211
194
  assert_equal 200, last_response.status
212
- assert_match /#{params.first['args'][2]}/, last_response.body
195
+ assert_match(/#{params.first['args'][2]}/, last_response.body)
213
196
  end
214
197
 
215
198
  it 'can delete a single scheduled job' do
@@ -220,7 +203,7 @@ class TestWeb < Sidekiq::Test
220
203
 
221
204
  get "/scheduled"
222
205
  assert_equal 200, last_response.status
223
- refute_match /#{params.first['args'][2]}/, last_response.body
206
+ refute_match(/#{params.first['args'][2]}/, last_response.body)
224
207
  end
225
208
 
226
209
  it 'can delete scheduled' do
@@ -247,12 +230,12 @@ class TestWeb < Sidekiq::Test
247
230
  assert_equal 1, q.size
248
231
  get '/queues/default'
249
232
  assert_equal 200, last_response.status
250
- assert_match /#{params[0]['args'][2]}/, last_response.body
233
+ assert_match(/#{params[0]['args'][2]}/, last_response.body)
251
234
  end
252
235
  end
253
236
 
254
237
  it 'can retry all retries' do
255
- msg, score = add_retry
238
+ msg = add_retry.first
256
239
  add_retry
257
240
 
258
241
  post "/retries/all/retry", 'retry' => 'Retry'
@@ -270,7 +253,7 @@ class TestWeb < Sidekiq::Test
270
253
  params = add_xss_retry
271
254
  get '/retries'
272
255
  assert_equal 200, last_response.status
273
- assert_match /FailWorker/, last_response.body
256
+ assert_match(/FailWorker/, last_response.body)
274
257
 
275
258
  assert last_response.body.include?( "fail message: &lt;a&gt;hello&lt;&#x2F;a&gt;" )
276
259
  assert !last_response.body.include?( "fail message: <a>hello</a>" )
@@ -278,19 +261,20 @@ class TestWeb < Sidekiq::Test
278
261
  assert last_response.body.include?( "args\">&quot;&lt;a&gt;hello&lt;&#x2F;a&gt;&quot;<" )
279
262
  assert !last_response.body.include?( "args\"><a>hello</a><" )
280
263
 
281
-
282
264
  # on /workers page
283
265
  Sidekiq.redis do |conn|
284
- identity = 'foo:1234-123abc:default'
285
- conn.sadd('workers', identity)
286
- conn.setex("worker:#{identity}:started", 10, Time.now.to_s)
266
+ pro = 'foo:1234'
267
+ conn.sadd('processes', pro)
268
+ conn.hmset(pro, 'info', Sidekiq.dump_json('started_at' => Time.now.to_f), 'busy', 1, 'beat', Time.now.to_f)
269
+ identity = "#{pro}:workers"
287
270
  hash = {:queue => 'critical', :payload => { 'class' => "FailWorker", 'args' => ["<a>hello</a>"] }, :run_at => Time.now.to_i }
288
- conn.setex("worker:#{identity}", 10, Sidekiq.dump_json(hash))
271
+ conn.hmset(identity, 100001, Sidekiq.dump_json(hash))
272
+ conn.incr('busy')
289
273
  end
290
274
 
291
- get '/workers'
275
+ get '/busy'
292
276
  assert_equal 200, last_response.status
293
- assert_match /FailWorker/, last_response.body
277
+ assert_match(/FailWorker/, last_response.body)
294
278
  assert last_response.body.include?( "&lt;a&gt;hello&lt;&#x2F;a&gt;" )
295
279
  assert !last_response.body.include?( "<a>hello</a>" )
296
280
 
@@ -332,7 +316,7 @@ class TestWeb < Sidekiq::Test
332
316
  end
333
317
 
334
318
  get '/custom'
335
- assert_match /Changed text/, last_response.body
319
+ assert_match(/Changed text/, last_response.body)
336
320
 
337
321
  ensure
338
322
  Sidekiq::Web.tabs.delete 'Custom Tab'
@@ -340,6 +324,8 @@ class TestWeb < Sidekiq::Test
340
324
  end
341
325
 
342
326
  describe 'stats' do
327
+ include Sidekiq::Util
328
+
343
329
  before do
344
330
  Sidekiq.redis do |conn|
345
331
  conn.set("stat:processed", 5)
@@ -394,6 +380,41 @@ class TestWeb < Sidekiq::Test
394
380
  end
395
381
  end
396
382
 
383
+ describe 'dead jobs' do
384
+ it 'shows empty index' do
385
+ get 'morgue'
386
+ assert_equal 200, last_response.status
387
+ end
388
+
389
+ it 'shows index with jobs' do
390
+ (_, score) = add_dead
391
+ get 'morgue'
392
+ assert_equal 200, last_response.status
393
+ assert_match(/#{score}/, last_response.body)
394
+ end
395
+
396
+ it 'can delete all dead' do
397
+ 3.times { add_dead }
398
+
399
+ assert_equal 3, Sidekiq::DeadSet.new.size
400
+ post "/morgue/all/delete", 'delete' => 'Delete'
401
+ assert_equal 0, Sidekiq::DeadSet.new.size
402
+ assert_equal 302, last_response.status
403
+ assert_equal 'http://example.org/morgue', last_response.header['Location']
404
+ end
405
+
406
+ it 'can retry a dead job' do
407
+ params = add_dead
408
+ post "/morgue/#{job_params(*params)}", 'retry' => 'Retry'
409
+ assert_equal 302, last_response.status
410
+ assert_equal 'http://example.org/morgue', last_response.header['Location']
411
+
412
+ get '/queues/foo'
413
+ assert_equal 200, last_response.status
414
+ assert_match(/#{params.first['args'][2]}/, last_response.body)
415
+ end
416
+ end
417
+
397
418
  def add_scheduled
398
419
  score = Time.now.to_f
399
420
  msg = { 'class' => 'HardWorker',
@@ -421,6 +442,22 @@ class TestWeb < Sidekiq::Test
421
442
  [msg, score]
422
443
  end
423
444
 
445
+ def add_dead
446
+ msg = { 'class' => 'HardWorker',
447
+ 'args' => ['bob', 1, Time.now.to_f],
448
+ 'queue' => 'foo',
449
+ 'error_message' => 'Some fake message',
450
+ 'error_class' => 'RuntimeError',
451
+ 'retry_count' => 0,
452
+ 'failed_at' => Time.now.utc,
453
+ 'jid' => SecureRandom.hex(12) }
454
+ score = Time.now.to_f
455
+ Sidekiq.redis do |conn|
456
+ conn.zadd('dead', score, Sidekiq.dump_json(msg))
457
+ end
458
+ [msg, score]
459
+ end
460
+
424
461
  def add_xss_retry
425
462
  msg = { 'class' => 'FailWorker',
426
463
  'args' => ['<a>hello</a>'],
@@ -438,11 +475,14 @@ class TestWeb < Sidekiq::Test
438
475
  end
439
476
 
440
477
  def add_worker
441
- process_id = rand(1000)
478
+ key = "#{hostname}:#{$$}"
442
479
  msg = "{\"queue\":\"default\",\"payload\":{\"retry\":true,\"queue\":\"default\",\"timeout\":20,\"backtrace\":5,\"class\":\"HardWorker\",\"args\":[\"bob\",10,5],\"jid\":\"2b5ad2b016f5e063a1c62872\"},\"run_at\":1361208995}"
443
480
  Sidekiq.redis do |conn|
444
- conn.sadd("workers", "mercury.home:#{process_id}-70215157189060:started")
445
- conn.set("worker:mercury.home:#{process_id}-70215157189060:started", msg)
481
+ conn.multi do
482
+ conn.sadd("processes", key)
483
+ conn.hmset(key, 'busy', 4)
484
+ conn.hmset("#{key}:workers", Time.now.to_f, msg)
485
+ end
446
486
  end
447
487
  end
448
488
  end