sidekiq-status 0.6.0 → 3.0.3

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.
Files changed (39) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/ci.yaml +53 -0
  3. data/.gitignore +4 -1
  4. data/.gitlab-ci.yml +17 -0
  5. data/Appraisals +11 -0
  6. data/CHANGELOG.md +36 -18
  7. data/Gemfile +0 -4
  8. data/README.md +129 -50
  9. data/Rakefile +2 -0
  10. data/gemfiles/sidekiq_6.1.gemfile +7 -0
  11. data/gemfiles/sidekiq_6.x.gemfile +7 -0
  12. data/gemfiles/sidekiq_7.x.gemfile +7 -0
  13. data/lib/sidekiq-status/client_middleware.rb +46 -8
  14. data/lib/sidekiq-status/redis_adapter.rb +18 -0
  15. data/lib/sidekiq-status/redis_client_adapter.rb +14 -0
  16. data/lib/sidekiq-status/server_middleware.rb +76 -20
  17. data/lib/sidekiq-status/sidekiq_extensions.rb +7 -0
  18. data/lib/sidekiq-status/storage.rb +19 -9
  19. data/lib/sidekiq-status/testing/inline.rb +10 -0
  20. data/lib/sidekiq-status/version.rb +1 -1
  21. data/lib/sidekiq-status/web.rb +116 -25
  22. data/lib/sidekiq-status/worker.rb +12 -5
  23. data/lib/sidekiq-status.rb +40 -5
  24. data/sidekiq-status.gemspec +7 -4
  25. data/spec/environment.rb +1 -0
  26. data/spec/lib/sidekiq-status/client_middleware_spec.rb +15 -12
  27. data/spec/lib/sidekiq-status/server_middleware_spec.rb +66 -22
  28. data/spec/lib/sidekiq-status/web_spec.rb +62 -15
  29. data/spec/lib/sidekiq-status/worker_spec.rb +19 -1
  30. data/spec/lib/sidekiq-status_spec.rb +94 -21
  31. data/spec/spec_helper.rb +104 -26
  32. data/spec/support/test_jobs.rb +71 -6
  33. data/web/sidekiq-status-single-web.png +0 -0
  34. data/web/views/status.erb +118 -0
  35. data/web/views/status_not_found.erb +6 -0
  36. data/web/views/statuses.erb +108 -22
  37. metadata +74 -13
  38. data/.travis.yml +0 -16
  39. data/gemfiles/Gemfile.sidekiq-2 +0 -5
@@ -5,20 +5,19 @@ describe Sidekiq::Status::ServerMiddleware do
5
5
  let!(:redis) { Sidekiq.redis { |conn| conn } }
6
6
  let!(:job_id) { SecureRandom.hex(12) }
7
7
 
8
- # Clean Redis before each test
9
- # Seems like flushall has no effect on recently published messages,
10
- # so we should wait till they expire
11
- before { redis.flushall; sleep 0.1 }
12
-
13
- describe "#call" do
8
+ describe "without :expiration parameter" do
14
9
  it "sets working/complete status" do
15
- thread = confirmations_thread 4, "status_updates", "job_messages_#{job_id}"
16
10
  allow(SecureRandom).to receive(:hex).once.and_return(job_id)
17
11
  start_server do
18
- expect(ConfirmationJob.perform_async(:arg1 => 'val1')).to eq(job_id)
19
- expect(thread.value).to eq([job_id, job_id,
20
- "while in #perform, status = working",
21
- job_id])
12
+ thread = branched_redis_thread 4, "status_updates", "job_messages_#{job_id}" do
13
+ expect(ConfirmationJob.perform_async 'arg1' => 'val1').to eq(job_id)
14
+ end
15
+ expect(thread.value).to eq([
16
+ job_id,
17
+ job_id,
18
+ "while in #perform, status = working",
19
+ job_id
20
+ ])
22
21
  end
23
22
  expect(redis.hget("sidekiq:status:#{job_id}", :status)).to eq('complete')
24
23
  expect(Sidekiq::Status::complete?(job_id)).to be_truthy
@@ -35,19 +34,64 @@ describe Sidekiq::Status::ServerMiddleware do
35
34
  expect(Sidekiq::Status::failed?(job_id)).to be_truthy
36
35
  end
37
36
 
38
- context "sets interrupted status" do
39
- it "on system exit signal" do
37
+ it "sets failed status when Exception raised" do
38
+ allow(SecureRandom).to receive(:hex).once.and_return(job_id)
39
+ start_server do
40
+ expect(capture_status_updates(3) {
41
+ expect(FailingHardJob.perform_async).to eq(job_id)
42
+ }).to eq([job_id]*3)
43
+ end
44
+ expect(redis.hget("sidekiq:status:#{job_id}", :status)).to eq('failed')
45
+ expect(Sidekiq::Status::failed?(job_id)).to be_truthy
46
+ end
47
+
48
+ context "when Sidekiq::Status::Worker is not included in the job" do
49
+ it "should not set a failed status" do
40
50
  allow(SecureRandom).to receive(:hex).once.and_return(job_id)
41
51
  start_server do
42
- expect(capture_status_updates(3) {
43
- expect(ExitedJob.perform_async).to eq(job_id)
44
- }).to eq([job_id]*3)
52
+ expect(FailingNoStatusJob.perform_async).to eq(job_id)
53
+ end
54
+ expect(redis.hget("sidekiq:status:#{job_id}", :status)).to be_nil
55
+ end
56
+
57
+ it "should not set any status when Exception raised" do
58
+ allow(SecureRandom).to receive(:hex).once.and_return(job_id)
59
+ start_server do
60
+ expect(FailingHardNoStatusJob.perform_async).to eq(job_id)
61
+ end
62
+ expect(redis.hget("sidekiq:status:#{job_id}", :status)).to be_nil
63
+ end
64
+
65
+ it "should not set any status on system exit signal" do
66
+ allow(SecureRandom).to receive(:hex).once.and_return(job_id)
67
+ start_server do
68
+ expect(ExitedNoStatusJob.perform_async).to eq(job_id)
69
+ end
70
+ expect(redis.hget("sidekiq:status:#{job_id}", :status)).to be_nil
71
+ end
72
+
73
+ it "should not set any status on interrupt signal" do
74
+ allow(SecureRandom).to receive(:hex).once.and_return(job_id)
75
+ start_server do
76
+ expect(InterruptedNoStatusJob.perform_async).to eq(job_id)
77
+ end
78
+ expect(redis.hget("sidekiq:status:#{job_id}", :status)).to be_nil
79
+ end
80
+ end
81
+
82
+ context "sets interrupted status" do
83
+ it "on system exit signal" do
84
+ allow(SecureRandom).to receive(:hex).once.and_return(job_id)
85
+ start_server do
86
+ expect(capture_status_updates(3) {
87
+ expect(ExitedJob.perform_async).to eq(job_id)
88
+ }).to eq([job_id]*3)
45
89
  end
46
90
  expect(redis.hget("sidekiq:status:#{job_id}", :status)).to eq('interrupted')
47
91
  expect(Sidekiq::Status::interrupted?(job_id)).to be_truthy
48
92
  end
49
93
 
50
- it "on interrupt signal" do
94
+ it "on interrupt signal" do
51
95
  allow(SecureRandom).to receive(:hex).once.and_return(job_id)
52
96
  start_server do
53
97
  expect(capture_status_updates(3) {
@@ -63,13 +107,13 @@ describe Sidekiq::Status::ServerMiddleware do
63
107
  it "sets status hash ttl" do
64
108
  allow(SecureRandom).to receive(:hex).once.and_return(job_id)
65
109
  start_server do
66
- expect(StubJob.perform_async(:arg1 => 'val1')).to eq(job_id)
110
+ expect(StubJob.perform_async 'arg1' => 'val1').to eq(job_id)
67
111
  end
68
112
  expect(1..Sidekiq::Status::DEFAULT_EXPIRY).to cover redis.ttl("sidekiq:status:#{job_id}")
69
113
  end
70
114
  end
71
115
 
72
- describe ":expiration parameter" do
116
+ describe "with :expiration parameter" do
73
117
  let(:huge_expiration) { Sidekiq::Status::DEFAULT_EXPIRY * 100 }
74
118
  before do
75
119
  allow(SecureRandom).to receive(:hex).once.and_return(job_id)
@@ -77,16 +121,16 @@ describe Sidekiq::Status::ServerMiddleware do
77
121
 
78
122
  it "overwrites default expiry value" do
79
123
  start_server(:expiration => huge_expiration) do
80
- StubJob.perform_async(:arg1 => 'val1')
124
+ StubJob.perform_async 'arg1' => 'val1'
81
125
  end
82
- expect((Sidekiq::Status::DEFAULT_EXPIRY+1)..huge_expiration).to cover redis.ttl("sidekiq:status:#{job_id}")
126
+ expect((Sidekiq::Status::DEFAULT_EXPIRY-1)..huge_expiration).to cover redis.ttl("sidekiq:status:#{job_id}")
83
127
  end
84
128
 
85
129
  it "can be overwritten by worker expiration method" do
86
130
  overwritten_expiration = huge_expiration * 100
87
131
  allow_any_instance_of(StubJob).to receive(:expiration).and_return(overwritten_expiration)
88
132
  start_server(:expiration => huge_expiration) do
89
- StubJob.perform_async(:arg1 => 'val1')
133
+ StubJob.perform_async 'arg1' => 'val1'
90
134
  end
91
135
  expect((huge_expiration+1)..overwritten_expiration).to cover redis.ttl("sidekiq:status:#{job_id}")
92
136
  end
@@ -8,30 +8,77 @@ describe 'sidekiq status web' do
8
8
  let!(:redis) { Sidekiq.redis { |conn| conn } }
9
9
  let!(:job_id) { SecureRandom.hex(12) }
10
10
 
11
- # Clean Redis before each test
12
- # Seems like flushall has no effect on recently published messages,
13
- # so we should wait till they expire
14
- before { redis.flushall; sleep 0.1 }
15
-
16
11
  def app
17
12
  Sidekiq::Web
18
13
  end
19
14
 
20
- it 'shows a job in progress' do
15
+ before do
16
+ env 'rack.session', csrf: Base64.urlsafe_encode64('token')
21
17
  client_middleware
22
18
  allow(SecureRandom).to receive(:hex).and_return(job_id)
19
+ end
20
+
21
+ around { |example| start_server(&example) }
22
+
23
+ it 'shows the list of jobs in progress' do
24
+ capture_status_updates(2) do
25
+ expect(LongJob.perform_async(0.5)).to eq(job_id)
26
+ end
23
27
 
24
- start_server do
25
- capture_status_updates(2) do
26
- expect(LongJob.perform_async(1)).to eq(job_id)
27
- end
28
+ get '/statuses'
29
+ expect(last_response).to be_ok
30
+ expect(last_response.body).to match(/#{job_id}/)
31
+ expect(last_response.body).to match(/LongJob/)
32
+ expect(last_response.body).to match(/working/)
33
+ end
34
+
35
+ it 'allows filtering the list of jobs by status' do
36
+ capture_status_updates(2) do
37
+ LongJob.perform_async(0.5)
38
+ end
39
+
40
+ get '/statuses?status=working'
41
+ expect(last_response).to be_ok
42
+ expect(last_response.body).to match(/#{job_id}/)
43
+ expect(last_response.body).to match(/LongJob/)
44
+ expect(last_response.body).to match(/working/)
45
+ end
28
46
 
29
- get '/statuses'
30
- expect(last_response).to be_ok
31
- expect(last_response.body).to match(/#{job_id}/)
32
- expect(last_response.body).to match(/LongJob/)
33
- expect(last_response.body).to match(/working/)
47
+ it 'allows filtering the list of jobs by completed status' do
48
+ capture_status_updates(2) do
49
+ LongJob.perform_async(0.5)
34
50
  end
51
+ get '/statuses?status=completed'
52
+ expect(last_response).to be_ok
53
+ expect(last_response.body).to_not match(/LongJob/)
35
54
  end
36
55
 
56
+ it 'shows a single job in progress' do
57
+ capture_status_updates(2) do
58
+ LongJob.perform_async(1, 'another argument')
59
+ end
60
+
61
+ get "/statuses/#{job_id}"
62
+ expect(last_response).to be_ok
63
+ expect(last_response.body).to match(/#{job_id}/)
64
+ expect(last_response.body).to match(/1,"another argument"/)
65
+ expect(last_response.body).to match(/working/)
66
+ end
67
+
68
+ it 'shows custom data for a single job' do
69
+ capture_status_updates(3) do
70
+ CustomDataJob.perform_async
71
+ end
72
+
73
+ get "/statuses/#{job_id}"
74
+ expect(last_response).to be_ok
75
+ expect(last_response.body).to match(/mister_cat/)
76
+ expect(last_response.body).to match(/meow/)
77
+ end
78
+
79
+ it 'show an error when the requested job ID is not found' do
80
+ get '/statuses/12345'
81
+ expect(last_response).to be_not_found
82
+ expect(last_response.body).to match(/That job can't be found/)
83
+ end
37
84
  end
@@ -13,11 +13,29 @@ describe Sidekiq::Status::Worker do
13
13
 
14
14
  describe ".expiration" do
15
15
  subject { StubJob.new }
16
-
16
+
17
17
  it "allows to set/get expiration" do
18
18
  expect(subject.expiration).to be_nil
19
19
  subject.expiration = :val
20
20
  expect(subject.expiration).to eq(:val)
21
21
  end
22
22
  end
23
+
24
+ describe ".at" do
25
+ subject { StubJob.new }
26
+
27
+ it "records when the worker has started" do
28
+ expect { subject.at(0) }.to(change { subject.retrieve('working_at') })
29
+ end
30
+
31
+ context "when setting the total for the worker" do
32
+ it "records when the worker has started" do
33
+ expect { subject.total(100) }.to(change { subject.retrieve('working_at') })
34
+ end
35
+ end
36
+
37
+ it "records when the worker last worked" do
38
+ expect { subject.at(0) }.to(change { subject.retrieve('update_time') })
39
+ end
40
+ end
23
41
  end
@@ -8,11 +8,7 @@ describe Sidekiq::Status do
8
8
  let!(:unused_id) { SecureRandom.hex(12) }
9
9
  let!(:plain_sidekiq_job_id) { SecureRandom.hex(12) }
10
10
  let!(:retried_job_id) { SecureRandom.hex(12) }
11
-
12
- # Clean Redis before each test
13
- # Seems like flushall has no effect on recently published messages,
14
- # so we should wait till they expire
15
- before { redis.flushall; sleep 0.1 }
11
+ let!(:retry_and_fail_job_id) { SecureRandom.hex(12) }
16
12
 
17
13
  describe ".status, .working?, .complete?" do
18
14
  it "gets job status by id as symbol" do
@@ -20,11 +16,12 @@ describe Sidekiq::Status do
20
16
 
21
17
  start_server do
22
18
  expect(capture_status_updates(2) {
23
- expect(LongJob.perform_async(1)).to eq(job_id)
19
+ expect(LongJob.perform_async(0.5)).to eq(job_id)
24
20
  }).to eq([job_id]*2)
25
21
  expect(Sidekiq::Status.status(job_id)).to eq(:working)
26
22
  expect(Sidekiq::Status.working?(job_id)).to be_truthy
27
23
  expect(Sidekiq::Status::queued?(job_id)).to be_falsey
24
+ expect(Sidekiq::Status::retrying?(job_id)).to be_falsey
28
25
  expect(Sidekiq::Status::failed?(job_id)).to be_falsey
29
26
  expect(Sidekiq::Status::complete?(job_id)).to be_falsey
30
27
  expect(Sidekiq::Status::stopped?(job_id)).to be_falsey
@@ -54,9 +51,9 @@ describe Sidekiq::Status do
54
51
  allow(SecureRandom).to receive(:hex).once.and_return(job_id)
55
52
 
56
53
  start_server do
57
- expect(capture_status_updates(3) {
54
+ expect(capture_status_updates(4) {
58
55
  expect(ProgressJob.perform_async).to eq(job_id)
59
- }).to eq([job_id]*3)
56
+ }).to eq([job_id]*4)
60
57
  end
61
58
  expect(Sidekiq::Status.at(job_id)).to be(100)
62
59
  expect(Sidekiq::Status.total(job_id)).to be(500)
@@ -72,7 +69,7 @@ describe Sidekiq::Status do
72
69
 
73
70
  start_server do
74
71
  expect(capture_status_updates(2) {
75
- expect(LongJob.perform_async(1)).to eq(job_id)
72
+ expect(LongJob.perform_async(0.5)).to eq(job_id)
76
73
  }).to eq([job_id]*2)
77
74
  expect(hash = Sidekiq::Status.get_all(job_id)).to include 'status' => 'working'
78
75
  expect(hash).to include 'update_time'
@@ -82,6 +79,23 @@ describe Sidekiq::Status do
82
79
  end
83
80
  end
84
81
 
82
+ describe '.delete' do
83
+ it 'deletes the status hash for given job id' do
84
+ allow(SecureRandom).to receive(:hex).once.and_return(job_id)
85
+ start_server do
86
+ expect(capture_status_updates(2) {
87
+ expect(LongJob.perform_async(0.5)).to eq(job_id)
88
+ }).to eq([job_id]*2)
89
+ end
90
+ expect(Sidekiq::Status.delete(job_id)).to eq(1)
91
+ end
92
+
93
+ it 'should not raise error while deleting status hash if invalid job id' do
94
+ allow(SecureRandom).to receive(:hex).once.and_return(job_id)
95
+ expect(Sidekiq::Status.delete(job_id)).to eq(0)
96
+ end
97
+ end
98
+
85
99
  describe ".cancel" do
86
100
  it "cancels a job by id" do
87
101
  allow(SecureRandom).to receive(:hex).twice.and_return(job_id, job_id_1)
@@ -91,7 +105,7 @@ describe Sidekiq::Status do
91
105
  second_job = LongJob.perform_in(3600)
92
106
  expect(second_job).to eq(job_id_1)
93
107
 
94
- initial_schedule = redis.zrange "schedule", 0, -1, {withscores: true}
108
+ initial_schedule = redis.zrange "schedule", 0, -1, withscores: true
95
109
  expect(initial_schedule.size).to be(2)
96
110
  expect(initial_schedule.select {|scheduled_job| JSON.parse(scheduled_job[0])["jid"] == job_id }.size).to be(1)
97
111
 
@@ -99,7 +113,7 @@ describe Sidekiq::Status do
99
113
  # Unused, therefore unfound => false
100
114
  expect(Sidekiq::Status.cancel(unused_id)).to be_falsey
101
115
 
102
- remaining_schedule = redis.zrange "schedule", 0, -1, {withscores: true}
116
+ remaining_schedule = redis.zrange "schedule", 0, -1, withscores: true
103
117
  expect(remaining_schedule.size).to be(initial_schedule.size - 1)
104
118
  expect(remaining_schedule.select {|scheduled_job| JSON.parse(scheduled_job[0])["jid"] == job_id }.size).to be(0)
105
119
  end
@@ -112,14 +126,14 @@ describe Sidekiq::Status do
112
126
  returned_job_id = LongJob.perform_at(scheduled_time)
113
127
  expect(returned_job_id).to eq(job_id)
114
128
 
115
- initial_schedule = redis.zrange "schedule", 0, -1, {withscores: true}
129
+ initial_schedule = redis.zrange "schedule", 0, -1, withscores: true
116
130
  expect(initial_schedule.size).to be(1)
117
131
  # wrong time, therefore unfound => false
118
132
  expect(Sidekiq::Status.cancel(returned_job_id, (scheduled_time + 1))).to be_falsey
119
- expect((redis.zrange "schedule", 0, -1, {withscores: true}).size).to be(1)
133
+ expect((redis.zrange "schedule", 0, -1, withscores: true).size).to be(1)
120
134
  # same id, same time, deletes
121
135
  expect(Sidekiq::Status.cancel(returned_job_id, (scheduled_time))).to be_truthy
122
- expect(redis.zrange "schedule", 0, -1, {withscores: true}).to be_empty
136
+ expect(redis.zrange "schedule", 0, -1, withscores: true).to be_empty
123
137
  end
124
138
  end
125
139
  end
@@ -134,14 +148,63 @@ describe Sidekiq::Status do
134
148
  expect_2_jobs_ttl_covers 1..Sidekiq::Status::DEFAULT_EXPIRY
135
149
  end
136
150
 
151
+ it "does jobs without a known class" do
152
+ seed_secure_random_with_job_ids
153
+ start_server(:expiration => expiration_param) do
154
+ expect {
155
+ Sidekiq::Client.new(Sidekiq.redis_pool).
156
+ push("class" => "NotAKnownClass", "args" => [])
157
+ }.to_not raise_error
158
+ end
159
+ end
160
+
137
161
  it "retries failed jobs" do
138
- allow(SecureRandom).to receive(:hex).once.and_return(retried_job_id)
162
+ allow(SecureRandom).to receive(:hex).exactly(3).times.and_return(retried_job_id)
139
163
  start_server do
140
- expect(capture_status_updates(5) {
164
+ expect(capture_status_updates(3) {
141
165
  expect(RetriedJob.perform_async()).to eq(retried_job_id)
142
- }).to eq([retried_job_id] * 5)
166
+ }).to eq([retried_job_id] * 3)
167
+ expect(Sidekiq::Status.status(retried_job_id)).to eq(:retrying)
168
+ expect(Sidekiq::Status.working?(retried_job_id)).to be_falsey
169
+ expect(Sidekiq::Status::queued?(retried_job_id)).to be_falsey
170
+ expect(Sidekiq::Status::retrying?(retried_job_id)).to be_truthy
171
+ expect(Sidekiq::Status::failed?(retried_job_id)).to be_falsey
172
+ expect(Sidekiq::Status::complete?(retried_job_id)).to be_falsey
173
+ expect(Sidekiq::Status::stopped?(retried_job_id)).to be_falsey
174
+ expect(Sidekiq::Status::interrupted?(retried_job_id)).to be_falsey
175
+ end
176
+ expect(Sidekiq::Status.status(retried_job_id)).to eq(:retrying)
177
+ expect(Sidekiq::Status::retrying?(retried_job_id)).to be_truthy
178
+
179
+ # restarting and waiting for the job to complete
180
+ start_server do
181
+ expect(capture_status_updates(3) {}).to eq([retried_job_id] * 3)
182
+ expect(Sidekiq::Status.status(retried_job_id)).to eq(:complete)
183
+ expect(Sidekiq::Status.complete?(retried_job_id)).to be_truthy
184
+ expect(Sidekiq::Status::retrying?(retried_job_id)).to be_falsey
185
+ end
186
+ end
187
+
188
+ it "marks retried jobs as failed once they do eventually fail" do
189
+ allow(SecureRandom).to receive(:hex).and_return(retry_and_fail_job_id)
190
+ start_server do
191
+ expect(
192
+ capture_status_updates(3) {
193
+ expect(RetryAndFailJob.perform_async).to eq(retry_and_fail_job_id)
194
+ }
195
+ ).to eq([retry_and_fail_job_id] * 3)
196
+
197
+ expect(Sidekiq::Status.status(retry_and_fail_job_id)).to eq(:retrying)
198
+ end
199
+
200
+ # restarting and waiting for the job to fail
201
+ start_server do
202
+ expect(capture_status_updates(3) {}).to eq([retry_and_fail_job_id] * 3)
203
+
204
+ expect(Sidekiq::Status.status(retry_and_fail_job_id)).to eq(:failed)
205
+ expect(Sidekiq::Status.failed?(retry_and_fail_job_id)).to be_truthy
206
+ expect(Sidekiq::Status::retrying?(retry_and_fail_job_id)).to be_falsey
143
207
  end
144
- expect(Sidekiq::Status.status(retried_job_id)).to eq(:complete)
145
208
  end
146
209
 
147
210
  context ":expiration param" do
@@ -154,7 +217,7 @@ describe Sidekiq::Status do
154
217
  expect_2_jobs_ttl_covers (Sidekiq::Status::DEFAULT_EXPIRY+1)..expiration_param
155
218
  end
156
219
 
157
- it "allow to overwrite :expiration parameter by .expiration method from worker" do
220
+ it "allow to overwrite :expiration parameter by #expiration method from worker" do
158
221
  overwritten_expiration = expiration_param * 100
159
222
  allow_any_instance_of(NoStatusConfirmationJob).to receive(:expiration).
160
223
  and_return(overwritten_expiration)
@@ -164,6 +227,16 @@ describe Sidekiq::Status do
164
227
  expect_2_jobs_are_done_and_status_eq :complete
165
228
  expect_2_jobs_ttl_covers (expiration_param+1)..overwritten_expiration
166
229
  end
230
+
231
+ it "reads #expiration from a method when defined" do
232
+ allow(SecureRandom).to receive(:hex).once.and_return(job_id, job_id_1)
233
+ start_server do
234
+ expect(StubJob.perform_async).to eq(job_id)
235
+ expect(ExpiryJob.perform_async).to eq(job_id_1)
236
+ expect(redis.ttl("sidekiq:status:#{job_id}")).to eq(30 * 60)
237
+ expect(redis.ttl("sidekiq:status:#{job_id_1}")).to eq(15)
238
+ end
239
+ end
167
240
  end
168
241
 
169
242
  def seed_secure_random_with_job_ids
@@ -173,12 +246,12 @@ describe Sidekiq::Status do
173
246
 
174
247
  def run_2_jobs!
175
248
  start_server(:expiration => expiration_param) do
176
- expect(capture_status_updates(12) {
249
+ expect(capture_status_updates(6) {
177
250
  expect(StubJob.perform_async).to eq(plain_sidekiq_job_id)
178
251
  NoStatusConfirmationJob.perform_async(1)
179
252
  expect(StubJob.perform_async).to eq(job_id_1)
180
253
  NoStatusConfirmationJob.perform_async(2)
181
- }).to match_array([plain_sidekiq_job_id, job_id_1] * 6)
254
+ }).to match_array([plain_sidekiq_job_id, job_id_1] * 3)
182
255
  end
183
256
  end
184
257
 
data/spec/spec_helper.rb CHANGED
@@ -1,71 +1,149 @@
1
1
  require "rspec"
2
-
3
- require 'celluloid'
2
+ require 'colorize'
4
3
  require 'sidekiq'
4
+
5
+ # Celluloid should only be manually required before Sidekiq versions 4.+
6
+ require 'sidekiq/version'
7
+ require 'celluloid' if Gem::Version.new(Sidekiq::VERSION) < Gem::Version.new('4.0')
8
+
5
9
  require 'sidekiq/processor'
6
10
  require 'sidekiq/manager'
7
11
  require 'sidekiq-status'
8
12
 
13
+ # Clears jobs before every test
14
+ RSpec.configure do |config|
15
+ config.before(:each) do
16
+ Sidekiq.redis { |conn| conn.flushall }
17
+ client_middleware
18
+ sleep 0.05
19
+ end
20
+ end
9
21
 
10
22
  Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
11
23
 
12
- def client_middleware(client_middleware_options={})
24
+ # Configures client middleware
25
+ def client_middleware client_middleware_options = {}
13
26
  Sidekiq.configure_client do |config|
14
- config.client_middleware do |chain|
15
- chain.add Sidekiq::Status::ClientMiddleware, client_middleware_options
16
- end
27
+ Sidekiq::Status.configure_client_middleware config, client_middleware_options
17
28
  end
18
29
  end
19
30
 
20
- def confirmations_thread(messages_limit, *channels)
31
+ def redis_thread messages_limit, *channels
32
+
21
33
  parent = Thread.current
22
34
  thread = Thread.new {
23
- confirmations = []
35
+ messages = []
24
36
  Sidekiq.redis do |conn|
25
- conn.subscribe *channels do |on|
37
+ puts "Subscribing to #{channels} for #{messages_limit.to_s.bold} messages".cyan if ENV['DEBUG']
38
+ conn.subscribe_with_timeout 60, *channels do |on|
26
39
  on.subscribe do |ch, subscriptions|
40
+ puts "Subscribed to #{ch}".cyan if ENV['DEBUG']
27
41
  if subscriptions == channels.size
28
42
  sleep 0.1 while parent.status != "sleep"
29
43
  parent.run
30
44
  end
31
45
  end
32
46
  on.message do |ch, msg|
33
- confirmations << msg
34
- conn.unsubscribe if confirmations.length >= messages_limit
47
+ puts "Message received: #{ch} -> #{msg}".white if ENV['DEBUG']
48
+ messages << msg
49
+ conn.unsubscribe if messages.length >= messages_limit
35
50
  end
36
51
  end
37
52
  end
38
- confirmations
53
+ puts "Returing from thread".cyan if ENV['DEBUG']
54
+ messages
39
55
  }
56
+
40
57
  Thread.stop
41
58
  yield if block_given?
42
59
  thread
60
+
61
+ end
62
+
63
+ def redis_client_thread message_limit, *channels
64
+ thread = Thread.new {
65
+ messages = []
66
+ Sidekiq.redis do |conn|
67
+ puts "Subscribing to #{channels} for #{message_limit.to_s.bold} messages".cyan if ENV['DEBUG']
68
+ pubsub = conn.pubsub
69
+ pubsub.call("SUBSCRIBE", *channels)
70
+
71
+ timeouts = 0
72
+ loop do
73
+ type, ch, msg = pubsub.next_event(2)
74
+ next if type == "subscribe"
75
+ if msg
76
+ puts "Message received: #{ch} -> #{msg}".white if ENV['DEBUG']
77
+ messages << msg
78
+ break if messages.length >= message_limit
79
+ else
80
+ # no new message was received in the allocated timeout
81
+ timeouts += 1
82
+ break if timeouts >= 30
83
+ end
84
+ end
85
+ end
86
+ puts "Returing from thread".cyan if ENV['DEBUG']
87
+ messages
88
+ }
89
+ sleep 0.1
90
+ yield if block_given?
91
+ thread.join
92
+ end
93
+
94
+ def branched_redis_thread n, *channels, &block
95
+ if Sidekiq.major_version < 7
96
+ redis_thread(n, *channels, &block)
97
+ else
98
+ redis_client_thread(n, *channels, &block)
99
+ end
43
100
  end
44
101
 
45
- def capture_status_updates(n, &block)
46
- confirmations_thread(n, "status_updates", &block).value
102
+ def capture_status_updates n, &block
103
+ branched_redis_thread(n, "status_updates", &block).value
47
104
  end
48
105
 
49
- def start_server(server_middleware_options={})
106
+ # Configures server middleware and launches a sidekiq server
107
+ def start_server server_middleware_options = {}
108
+
109
+ # Creates a process for the Sidekiq server
50
110
  pid = Process.fork do
51
- $stdout.reopen File::NULL, 'w'
52
- $stderr.reopen File::NULL, 'w'
111
+
112
+ # Redirect the server's outputs
113
+ $stdout.reopen File::NULL, 'w' unless ENV['DEBUG']
114
+ $stderr.reopen File::NULL, 'w' unless ENV['DEBUG']
115
+
116
+ # Load and configure server options
53
117
  require 'sidekiq/cli'
54
- Sidekiq.options[:queues] << 'default'
55
- Sidekiq.options[:require] = File.expand_path('../support/test_jobs.rb', __FILE__)
118
+
119
+ # Add the server middleware
56
120
  Sidekiq.configure_server do |config|
57
- config.redis = Sidekiq::RedisConnection.create
58
- config.server_middleware do |chain|
59
- chain.add Sidekiq::Status::ServerMiddleware, server_middleware_options
60
- end
121
+ config.concurrency = 5
122
+ config.redis = Sidekiq::RedisConnection.create if Sidekiq.major_version < 7
123
+ Sidekiq::Status.configure_server_middleware config, server_middleware_options
61
124
  end
62
- Sidekiq::CLI.instance.run
125
+
126
+ # Launch
127
+ puts "Server starting".yellow if ENV['DEBUG']
128
+ instance = Sidekiq::CLI.instance
129
+ instance.parse(['-r', File.expand_path('environment.rb', File.dirname(__FILE__))])
130
+ instance.run
131
+
63
132
  end
64
133
 
134
+ # Run the client-side code
65
135
  yield
66
- sleep 0.1
136
+
137
+ # Pause to ensure all jobs are picked up & started before TERM is sent
138
+ sleep 0.2
139
+
140
+ # Attempt to shut down the server normally
67
141
  Process.kill 'TERM', pid
68
- Timeout::timeout(10) { Process.wait pid } rescue Timeout::Error
142
+ Process.wait pid
143
+
69
144
  ensure
145
+
146
+ # Ensure the server is actually dead
70
147
  Process.kill 'KILL', pid rescue "OK" # it's OK if the process is gone already
148
+
71
149
  end