resque-integration 3.4.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.drone.yml +28 -0
- data/.gitignore +21 -0
- data/.rspec +3 -0
- data/Appraisals +27 -0
- data/CHANGELOG.md +311 -0
- data/Gemfile +4 -0
- data/README.md +281 -0
- data/Rakefile +1 -0
- data/app/assets/javascripts/standalone/progress_bar.js +47 -0
- data/app/controllers/resque/jobs_controller.rb +42 -0
- data/app/controllers/resque/queues/info_controller.rb +9 -0
- data/app/controllers/resque/queues/status_controller.rb +38 -0
- data/app/views/shared/job_progress_bar.html.haml +17 -0
- data/bin/resque-status +59 -0
- data/config/routes.rb +13 -0
- data/config.ru +9 -0
- data/dip.yml +44 -0
- data/docker-compose.development.yml +12 -0
- data/docker-compose.drone.yml +6 -0
- data/docker-compose.yml +10 -0
- data/lib/generators/resque/integration/install/install_generator.rb +38 -0
- data/lib/resque/integration/backtrace.rb +29 -0
- data/lib/resque/integration/configuration.rb +238 -0
- data/lib/resque/integration/continuous.rb +75 -0
- data/lib/resque/integration/engine.rb +103 -0
- data/lib/resque/integration/extensions/job.rb +17 -0
- data/lib/resque/integration/extensions/worker.rb +17 -0
- data/lib/resque/integration/extensions.rb +8 -0
- data/lib/resque/integration/failure_backends/queues_totals.rb +37 -0
- data/lib/resque/integration/failure_backends.rb +7 -0
- data/lib/resque/integration/god.erb +99 -0
- data/lib/resque/integration/hooks.rb +72 -0
- data/lib/resque/integration/logs_rotator.rb +95 -0
- data/lib/resque/integration/monkey_patch/verbose_formatter.rb +10 -0
- data/lib/resque/integration/ordered.rb +142 -0
- data/lib/resque/integration/priority.rb +89 -0
- data/lib/resque/integration/queues_info/age.rb +53 -0
- data/lib/resque/integration/queues_info/config.rb +96 -0
- data/lib/resque/integration/queues_info/size.rb +33 -0
- data/lib/resque/integration/queues_info.rb +55 -0
- data/lib/resque/integration/tasks/hooks.rake +49 -0
- data/lib/resque/integration/tasks/lock.rake +37 -0
- data/lib/resque/integration/tasks/resque.rake +101 -0
- data/lib/resque/integration/unique.rb +218 -0
- data/lib/resque/integration/version.rb +5 -0
- data/lib/resque/integration.rb +146 -0
- data/lib/resque-integration.rb +1 -0
- data/resque-integration.gemspec +40 -0
- data/spec/fixtures/resque_queues.yml +45 -0
- data/spec/resque/controllers/jobs_controller_spec.rb +65 -0
- data/spec/resque/integration/configuration_spec.rb +147 -0
- data/spec/resque/integration/continuous_spec.rb +122 -0
- data/spec/resque/integration/failure_backends/queues_totals_spec.rb +105 -0
- data/spec/resque/integration/ordered_spec.rb +87 -0
- data/spec/resque/integration/priority_spec.rb +94 -0
- data/spec/resque/integration/queues_info_spec.rb +402 -0
- data/spec/resque/integration/unique_spec.rb +184 -0
- data/spec/resque/integration_spec.rb +105 -0
- data/spec/shared/resque_inline.rb +10 -0
- data/spec/spec_helper.rb +28 -0
- data/vendor/assets/images/progressbar/white.gif +0 -0
- data/vendor/assets/javascripts/jquery.progressbar.js +177 -0
- data/vendor/assets/stylesheets/jquery.progressbar.css.erb +33 -0
- data/vendor/assets/stylesheets/jquery.progressbar.no_pipeline.css +33 -0
- metadata +402 -0
@@ -0,0 +1,402 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Resque::Integration::QueuesInfo do
|
4
|
+
let(:queue_info) { described_class.new(config: 'spec/fixtures/resque_queues.yml') }
|
5
|
+
|
6
|
+
before do
|
7
|
+
Timecop.freeze Time.current
|
8
|
+
end
|
9
|
+
|
10
|
+
after do
|
11
|
+
Timecop.return
|
12
|
+
end
|
13
|
+
|
14
|
+
context 'age' do
|
15
|
+
before do
|
16
|
+
allow(Resque).to receive(:workers).and_return(workers)
|
17
|
+
end
|
18
|
+
|
19
|
+
context 'age_for_queue' do
|
20
|
+
context 'when old job running' do
|
21
|
+
let(:workers) do
|
22
|
+
[double(job: {'run_at' => 4.hours.ago.utc.iso8601, 'queue' => 'first'}, 'idle?' => false)]
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'returns time for job ' do
|
26
|
+
expect(queue_info.age_for_queue('first')).to eq 4.hours.to_i
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context 'when job running normal time' do
|
31
|
+
let(:workers) do
|
32
|
+
[double(job: {'run_at' => 4.seconds.ago.utc.iso8601, 'queue' => 'first'}, 'idle?' => false)]
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'returns time for job ' do
|
36
|
+
expect(queue_info.age_for_queue('first')).to eq 4
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context 'when queue is empty' do
|
41
|
+
let(:workers) { [] }
|
42
|
+
|
43
|
+
it 'returns 0' do
|
44
|
+
expect(queue_info.age_for_queue('first')).to eq 0
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context 'when worker is iddling' do
|
49
|
+
let(:workers) do
|
50
|
+
[double('idle?' => true, job: nil)]
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'returns 0' do
|
54
|
+
expect(queue_info.age_for_queue('first')).to eq 0
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe '#age_overall' do
|
60
|
+
context 'when there is old job for its queue' do
|
61
|
+
let(:workers) do
|
62
|
+
[
|
63
|
+
double(job: {'run_at' => 4.seconds.ago.utc.iso8601, 'queue' => 'second'}, 'idle?' => false),
|
64
|
+
double(job: {'run_at' => 20.seconds.ago.utc.iso8601, 'queue' => 'first'}, 'idle?' => false)
|
65
|
+
]
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'returns time for job ' do
|
69
|
+
expect(queue_info.age_overall).to eq 20
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context 'when there is a several old jobs' do
|
74
|
+
let(:workers) do
|
75
|
+
[
|
76
|
+
double(job: {'run_at' => 100.seconds.ago.utc.iso8601, 'queue' => 'first'}, 'idle?' => false),
|
77
|
+
double(job: {'run_at' => 20.seconds.ago.utc.iso8601, 'queue' => 'second'}, 'idle?' => false)
|
78
|
+
]
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'returns time for the oldest job ' do
|
82
|
+
expect(queue_info.age_overall).to eq 100
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
context 'when the is no old job' do
|
87
|
+
let(:workers) do
|
88
|
+
[
|
89
|
+
double(job: {'run_at' => 4.seconds.ago.utc.iso8601, 'queue' => 'first'}, 'idle?' => false),
|
90
|
+
double(job: {'run_at' => 2.seconds.ago.utc.iso8601, 'queue' => 'second'}, 'idle?' => false)
|
91
|
+
]
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'returns 0' do
|
95
|
+
expect(queue_info.age_overall).to eq 0
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
context 'when old job running in unknown queue' do
|
100
|
+
let(:workers) do
|
101
|
+
[double(job: {'run_at' => 11.seconds.ago.utc.iso8601, 'queue' => 'unknown'}, 'idle?' => false)]
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'checks time of job with defaults thresholds' do
|
105
|
+
expect(queue_info.age_overall).to eq 11
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
context "when one job have problem and other with older age doesn't" do
|
110
|
+
let(:workers) do
|
111
|
+
[
|
112
|
+
double(job: {'run_at' => 16.seconds.ago.utc.iso8601, 'queue' => 'first'}, 'idle?' => false),
|
113
|
+
double(job: {'run_at' => 14.seconds.ago.utc.iso8601, 'queue' => 'second'}, 'idle?' => false)
|
114
|
+
]
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'returns size for queue with problem' do
|
118
|
+
expect(queue_info.age_overall).to eq 14
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
context 'when there is no job running' do
|
123
|
+
let(:workers) do
|
124
|
+
[
|
125
|
+
double(job: nil, 'idle?' => true)
|
126
|
+
]
|
127
|
+
end
|
128
|
+
|
129
|
+
it 'returns 0' do
|
130
|
+
expect(queue_info.age_overall).to eq 0
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
context 'size' do
|
137
|
+
describe '#size_for_queue' do
|
138
|
+
before do
|
139
|
+
allow(Resque).to receive(:size).with('first').and_return(size)
|
140
|
+
end
|
141
|
+
|
142
|
+
context 'when too much jobs in queue' do
|
143
|
+
let(:size) { 100 }
|
144
|
+
|
145
|
+
it 'returns queue size' do
|
146
|
+
expect(queue_info.size_for_queue('first')).to eq 100
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
context 'when queue have normal size' do
|
151
|
+
let(:size) { 1 }
|
152
|
+
|
153
|
+
it 'returns time for job ' do
|
154
|
+
expect(queue_info.size_for_queue('first')).to eq 1
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
context 'when queue is empty' do
|
159
|
+
let(:size) { nil }
|
160
|
+
|
161
|
+
it 'returns 0' do
|
162
|
+
expect(queue_info.size_for_queue('first')).to eq 0
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
describe '#size_overall' do
|
168
|
+
before do
|
169
|
+
allow(Resque).to receive(:queues).and_return(%w(first second))
|
170
|
+
allow(Resque).to receive(:size).with('first').and_return(size_first)
|
171
|
+
allow(Resque).to receive(:size).with('second').and_return(size_second)
|
172
|
+
end
|
173
|
+
|
174
|
+
context 'when there is one big queue' do
|
175
|
+
let(:size_first) { 100 }
|
176
|
+
let(:size_second) { nil }
|
177
|
+
|
178
|
+
it 'returns size for queue with problem' do
|
179
|
+
expect(queue_info.size_overall).to eq 100
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
context 'when there is a several problem queues' do
|
184
|
+
let(:size_first) { 1000 }
|
185
|
+
let(:size_second) { 200 }
|
186
|
+
|
187
|
+
it 'returns size for the lagrest queue ' do
|
188
|
+
expect(queue_info.size_overall).to eq 1000
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
context 'when the is no large queus' do
|
193
|
+
let(:size_first) { 1 }
|
194
|
+
let(:size_second) { 2 }
|
195
|
+
|
196
|
+
it 'returns 0' do
|
197
|
+
expect(queue_info.size_overall).to eq 0
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
context 'when lagre queue is unknown' do
|
202
|
+
let(:size_first) { nil }
|
203
|
+
let(:size_second) { 11 }
|
204
|
+
|
205
|
+
it 'checks size of queue with defaults thresholds' do
|
206
|
+
expect(queue_info.size_overall).to eq 11
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
context "when one queue have problem and other with bigger size doesn't" do
|
211
|
+
let(:size_first) { 30 }
|
212
|
+
let(:size_second) { 20 }
|
213
|
+
|
214
|
+
it 'returns size for queue with problem' do
|
215
|
+
expect(queue_info.size_overall).to eq 20
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
describe '#threshold_age' do
|
222
|
+
context 'when queue defined in config' do
|
223
|
+
let(:queue_name) { 'first' }
|
224
|
+
|
225
|
+
it 'returns threshold for age' do
|
226
|
+
expect(queue_info.threshold_age(queue_name)).to eq 20
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
context 'when queue not defined in config' do
|
231
|
+
let(:queue_name) { 'second' }
|
232
|
+
|
233
|
+
it 'returns default threshold' do
|
234
|
+
expect(queue_info.threshold_age(queue_name)).to eq 10
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
describe '#threshold_size' do
|
240
|
+
context 'when queue defined in config' do
|
241
|
+
let(:queue_name) { 'first' }
|
242
|
+
|
243
|
+
it 'returns threshold for size' do
|
244
|
+
expect(queue_info.threshold_size(queue_name)).to eq 100
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
context 'when queue not defined in config' do
|
249
|
+
let(:queue_name) { 'second' }
|
250
|
+
|
251
|
+
it 'returns default threshold' do
|
252
|
+
expect(queue_info.threshold_size(queue_name)).to eq 10
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
describe '#channel' do
|
258
|
+
context 'when queue defined in config' do
|
259
|
+
context 'when returns the one channel' do
|
260
|
+
let(:queue_name) { 'first' }
|
261
|
+
|
262
|
+
it { expect(queue_info.channel(queue_name)).to eq 'first' }
|
263
|
+
end
|
264
|
+
|
265
|
+
context 'when returns several channels' do
|
266
|
+
let(:queue_name) { 'second_queue' }
|
267
|
+
|
268
|
+
it do
|
269
|
+
expect(queue_info.channel(queue_name)).to eq 'first second'
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
context 'when channel not specified' do
|
274
|
+
let(:queue_name) { 'without_channel' }
|
275
|
+
|
276
|
+
it { expect(queue_info.channel(queue_name)).to eq 'default' }
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
context 'when queue not defined in config' do
|
281
|
+
let(:queue_name) { 'other_queue' }
|
282
|
+
|
283
|
+
it 'returns channel' do
|
284
|
+
expect(queue_info.channel(queue_name)).to eq 'default'
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
describe '#threshold_failures_count' do
|
290
|
+
context 'when queue is defined in config' do
|
291
|
+
let(:queue_name) { 'first' }
|
292
|
+
|
293
|
+
it 'returns failures count threshold for specified queue and time period' do
|
294
|
+
expect(queue_info.threshold_failures_count(queue_name, '5m')).to eq 15
|
295
|
+
expect(queue_info.threshold_failures_count(queue_name, '1h')).to eq 90
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
context 'when queue is not defined in config' do
|
300
|
+
let(:queue_name) { 'second' }
|
301
|
+
|
302
|
+
it 'returns default failures count threshold for specified time period' do
|
303
|
+
expect(queue_info.threshold_failures_count(queue_name, '5m')).to eq 5
|
304
|
+
expect(queue_info.threshold_failures_count(queue_name, '1h')).to eq 60
|
305
|
+
end
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
describe '#failures_count_for_queue' do
|
310
|
+
before do
|
311
|
+
allow(Resque::Integration::FailureBackends::QueuesTotals).to receive(:count).with('first').and_return(14)
|
312
|
+
end
|
313
|
+
|
314
|
+
it 'returns total failures count for specified queue' do
|
315
|
+
expect(queue_info.failures_count_for_queue('first')).to eq 14
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
describe 'configuration merging' do
|
320
|
+
let(:first_queue_name) { 'first' }
|
321
|
+
let(:third_queue_name) { 'third' }
|
322
|
+
|
323
|
+
it 'merges configs for queue in order of appearance' do
|
324
|
+
expect(queue_info.threshold_age(first_queue_name)).to eq 20
|
325
|
+
expect(queue_info.threshold_size(first_queue_name)).to eq 100
|
326
|
+
expect(queue_info.threshold_failures_count(first_queue_name, '5m')).to eq 15
|
327
|
+
expect(queue_info.threshold_failures_count(first_queue_name, '1h')).to eq 90
|
328
|
+
|
329
|
+
expect(queue_info.threshold_age(third_queue_name)).to eq 30
|
330
|
+
expect(queue_info.threshold_size(third_queue_name)).to eq 100
|
331
|
+
expect(queue_info.threshold_failures_count(third_queue_name, '5m')).to eq 15
|
332
|
+
expect(queue_info.threshold_failures_count(third_queue_name, '1h')).to eq 70
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
describe '#data' do
|
337
|
+
it do
|
338
|
+
expect(queue_info.data).to eq [
|
339
|
+
{
|
340
|
+
'{#QUEUE}' => 'first',
|
341
|
+
|
342
|
+
'{#CHANNEL}' => 'first',
|
343
|
+
'{#THRESHOLD_AGE}' => 20,
|
344
|
+
'{#THRESHOLD_SIZE}' => 100,
|
345
|
+
'{#THRESHOLD_FAILURES_PER_5M}' => 15,
|
346
|
+
'{#THRESHOLD_FAILURES_PER_1H}' => 90,
|
347
|
+
|
348
|
+
'{#WARNING_CHANNEL}' => 'first_warnings',
|
349
|
+
'{#WARNING_AGE}' => 10,
|
350
|
+
'{#WARNING_SIZE}' => 50,
|
351
|
+
'{#WARNING_FAILURES_PER_5M}' => 7,
|
352
|
+
'{#WARNING_FAILURES_PER_1H}' => 45
|
353
|
+
},
|
354
|
+
{
|
355
|
+
'{#QUEUE}' => 'third',
|
356
|
+
|
357
|
+
'{#CHANNEL}' => 'first',
|
358
|
+
'{#THRESHOLD_AGE}' => 30,
|
359
|
+
'{#THRESHOLD_SIZE}' => 100,
|
360
|
+
'{#THRESHOLD_FAILURES_PER_5M}' => 15,
|
361
|
+
'{#THRESHOLD_FAILURES_PER_1H}' => 70,
|
362
|
+
|
363
|
+
'{#WARNING_CHANNEL}' => 'first_warnings',
|
364
|
+
'{#WARNING_AGE}' => 15,
|
365
|
+
'{#WARNING_SIZE}' => 50,
|
366
|
+
'{#WARNING_FAILURES_PER_5M}' => 7,
|
367
|
+
'{#WARNING_FAILURES_PER_1H}' => 35
|
368
|
+
},
|
369
|
+
{
|
370
|
+
'{#QUEUE}' => 'second_queue',
|
371
|
+
|
372
|
+
'{#CHANNEL}' => 'first second',
|
373
|
+
'{#THRESHOLD_AGE}' => 10,
|
374
|
+
'{#THRESHOLD_SIZE}' => 10,
|
375
|
+
'{#THRESHOLD_FAILURES_PER_5M}' => 5,
|
376
|
+
'{#THRESHOLD_FAILURES_PER_1H}' => 60,
|
377
|
+
|
378
|
+
'{#WARNING_CHANNEL}' => 'first_warnings second_warnings',
|
379
|
+
'{#WARNING_AGE}' => 8,
|
380
|
+
'{#WARNING_SIZE}' => 7,
|
381
|
+
'{#WARNING_FAILURES_PER_5M}' => 3,
|
382
|
+
'{#WARNING_FAILURES_PER_1H}' => 30
|
383
|
+
},
|
384
|
+
{
|
385
|
+
'{#QUEUE}' => 'without_channel',
|
386
|
+
|
387
|
+
'{#CHANNEL}' => 'default',
|
388
|
+
'{#THRESHOLD_AGE}' => 10,
|
389
|
+
'{#THRESHOLD_SIZE}' => 10,
|
390
|
+
'{#THRESHOLD_FAILURES_PER_5M}' => 5,
|
391
|
+
'{#THRESHOLD_FAILURES_PER_1H}' => 60,
|
392
|
+
|
393
|
+
'{#WARNING_CHANNEL}' => 'default_warnings',
|
394
|
+
'{#WARNING_AGE}' => 8,
|
395
|
+
'{#WARNING_SIZE}' => 7,
|
396
|
+
'{#WARNING_FAILURES_PER_5M}' => 3,
|
397
|
+
'{#WARNING_FAILURES_PER_1H}' => 30
|
398
|
+
}
|
399
|
+
]
|
400
|
+
end
|
401
|
+
end
|
402
|
+
end
|
@@ -0,0 +1,184 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Resque::Integration::Unique, '#meta_id, #lock' do
|
4
|
+
let(:job) { Class.new }
|
5
|
+
before { job.extend Resque::Integration::Unique }
|
6
|
+
|
7
|
+
context 'when unique arguments are not set' do
|
8
|
+
it 'returns same results when equal arguments given' do
|
9
|
+
job.meta_id('a', 'b').should eq job.meta_id('a', 'b')
|
10
|
+
job.lock_id('a', 'b').should eq job.lock_id('a', 'b')
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'returns different results when different arguments given' do
|
14
|
+
job.meta_id('a', 'b').should_not eq job.meta_id('a', 'c')
|
15
|
+
job.lock_id('a', 'b').should_not eq job.lock_id('a', 'c')
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context 'when unique arguments are set' do
|
20
|
+
# mark second argument as unique
|
21
|
+
before { job.lock_on &->(a, b) { b } }
|
22
|
+
|
23
|
+
it 'returns same results when equal arguments given' do
|
24
|
+
job.meta_id('a', 'b').should eq job.meta_id('a', 'b')
|
25
|
+
job.lock_id('a', 'b').should eq job.lock_id('a', 'b')
|
26
|
+
job.lock_id('a', foo: 1).should eq job.lock_id('a', foo: 1)
|
27
|
+
|
28
|
+
job.meta_id('a', 'b').should eq job.meta_id('c', 'b')
|
29
|
+
job.lock_id('a', 'b').should eq job.lock_id('c', 'b')
|
30
|
+
job.lock_id('a', foo: 1).should eq job.lock_id('c', foo: 1)
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'returns different results when different arguments given' do
|
34
|
+
job.meta_id('a', 'b').should_not eq job.meta_id('a', 'c')
|
35
|
+
job.lock_id('a', 'b').should_not eq job.lock_id('a', 'c')
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe Resque::Integration::Unique, '#enqueue, #enqueued?' do
|
41
|
+
class JobEnqueueTest
|
42
|
+
extend Resque::Integration::Unique
|
43
|
+
|
44
|
+
@queue = :queue1
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'returns false when job is not enqueued' do
|
48
|
+
JobEnqueueTest.should_not be_enqueued(0)
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'returns new meta when job is enqueued' do
|
52
|
+
meta = JobEnqueueTest.enqueue(1)
|
53
|
+
meta.should be_a Resque::Plugins::Meta::Metadata
|
54
|
+
|
55
|
+
JobEnqueueTest.should be_enqueued(1)
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'returns new meta when job is enqueued to specific queue' do
|
59
|
+
meta = JobEnqueueTest.enqueue_to(:fast, 1)
|
60
|
+
meta.should be_a Resque::Plugins::Meta::Metadata
|
61
|
+
|
62
|
+
expect(JobEnqueueTest.enqueued?(1)).to_not be_nil
|
63
|
+
expect(Resque.size(:fast)).to eq 1
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'returns new meta when job is scheduled' do
|
67
|
+
meta = JobEnqueueTest.scheduled(:fast, 'JobEnqueueTest', 1)
|
68
|
+
meta.should be_a Resque::Plugins::Meta::Metadata
|
69
|
+
|
70
|
+
expect(JobEnqueueTest.enqueued?(1)).to_not be_nil
|
71
|
+
expect(Resque.size(:fast)).to eq 1
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'returns the same meta if job already in queue' do
|
75
|
+
meta1 = JobEnqueueTest.enqueue(2)
|
76
|
+
meta2 = JobEnqueueTest.enqueue(2)
|
77
|
+
|
78
|
+
meta1.meta_id.should eq meta2.meta_id
|
79
|
+
meta1.enqueued_at.should eq meta2.enqueued_at
|
80
|
+
|
81
|
+
JobEnqueueTest.should be_enqueued(2)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
describe Resque::Integration::Unique, '#dequeue' do
|
86
|
+
class JobDequeueTest
|
87
|
+
extend Resque::Integration::Unique
|
88
|
+
|
89
|
+
@queue = :queue2
|
90
|
+
|
91
|
+
def self.execute(x)
|
92
|
+
sleep 0.1
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'dequeues and unlocks job if job is not in work now' do
|
97
|
+
JobDequeueTest.enqueue(1)
|
98
|
+
JobDequeueTest.should be_enqueued(1)
|
99
|
+
|
100
|
+
JobDequeueTest.dequeue(1)
|
101
|
+
JobDequeueTest.should_not be_enqueued(1)
|
102
|
+
JobDequeueTest.should_not be_locked(1)
|
103
|
+
|
104
|
+
meta = JobDequeueTest.get_meta(JobDequeueTest.meta_id(1))
|
105
|
+
meta.should be_failed
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'does not dequeue jobs in progress' do
|
109
|
+
meta = JobDequeueTest.enqueue(1)
|
110
|
+
|
111
|
+
job = Resque.reserve(:queue2)
|
112
|
+
|
113
|
+
worker = Thread.new {
|
114
|
+
job.perform
|
115
|
+
}
|
116
|
+
|
117
|
+
sleep 0.01 # give a worker some time to start
|
118
|
+
meta.reload!
|
119
|
+
meta.should be_working
|
120
|
+
|
121
|
+
JobDequeueTest.dequeue(1)
|
122
|
+
JobDequeueTest.should be_locked(1)
|
123
|
+
JobDequeueTest.should be_enqueued(1)
|
124
|
+
|
125
|
+
worker.join
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
describe Resque::Integration::Unique, '#on_failure_retry' do
|
130
|
+
class JobUniqueWithRetry
|
131
|
+
include Resque::Integration
|
132
|
+
extend Resque::Plugins::Retry
|
133
|
+
|
134
|
+
@retry_limit = 2
|
135
|
+
@retry_delay = 1
|
136
|
+
@retry_exceptions = [IOError]
|
137
|
+
@expire_retry_key_after = 300
|
138
|
+
|
139
|
+
unique do |foo_var, params|
|
140
|
+
params[:foo]
|
141
|
+
end
|
142
|
+
|
143
|
+
queue :default
|
144
|
+
|
145
|
+
def self.execute(foo_var, params)
|
146
|
+
sleep 0.2
|
147
|
+
Resque.logger.info 'Hello, world'
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
class JobOnlyUnique
|
152
|
+
include Resque::Integration
|
153
|
+
|
154
|
+
unique
|
155
|
+
|
156
|
+
def self.execute
|
157
|
+
raise ArgumentError.new('Some exception in Job')
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
let(:worker) { Resque::Worker.new(:default) }
|
162
|
+
|
163
|
+
context 'when unique with retry' do
|
164
|
+
let(:job) { Resque::Job.new(:default, 'class' => 'JobUniqueWithRetry', 'args' => ['abcd', 1, {foo: 'bar'}]) }
|
165
|
+
|
166
|
+
before { worker.working_on(job) }
|
167
|
+
|
168
|
+
it do
|
169
|
+
expect { worker.unregister_worker }.not_to raise_error
|
170
|
+
|
171
|
+
expect(Resque::Failure.count).to eq 1
|
172
|
+
expect(Resque::Failure.all['exception']).to eq 'Resque::DirtyExit'
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
context 'when only unique' do
|
177
|
+
let(:job) { Resque::Job.new(:default, 'class' => 'JobOnlyUnique', 'args' => ['abcd']) }
|
178
|
+
|
179
|
+
it do
|
180
|
+
expect { job.perform }.not_to raise_error(RuntimeError, /no superclass method `on_failure_retry'/)
|
181
|
+
expect { job.perform }.to raise_error(ArgumentError)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
RSpec.describe Resque::Integration do
|
5
|
+
describe '#unique?' do
|
6
|
+
let(:job) { Class.new }
|
7
|
+
before { job.send :include, Resque::Integration }
|
8
|
+
|
9
|
+
subject { job }
|
10
|
+
|
11
|
+
context 'when #unique is not called' do
|
12
|
+
it { should_not be_unique }
|
13
|
+
end
|
14
|
+
|
15
|
+
context 'when #unique is called' do
|
16
|
+
before { job.unique }
|
17
|
+
|
18
|
+
it { should be_unique }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe 'enqueue' do
|
23
|
+
context 'when job is uniq' do
|
24
|
+
class DummyService
|
25
|
+
def self.call
|
26
|
+
# no-op
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class UniqueJob
|
31
|
+
include Resque::Integration
|
32
|
+
|
33
|
+
queue :test
|
34
|
+
unique
|
35
|
+
|
36
|
+
def self.execute(id, params)
|
37
|
+
DummyService.call
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'enqueues only one job' do
|
42
|
+
UniqueJob.enqueue(1, param: 'one')
|
43
|
+
|
44
|
+
Timecop.travel(10.hours.since) do
|
45
|
+
UniqueJob.enqueue(1, param: 'one')
|
46
|
+
|
47
|
+
expect(Resque.peek(:test, 0, 100).size).to eq(1)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'enqueues two jobs with differ args' do
|
52
|
+
UniqueJob.enqueue(1, param: 'one')
|
53
|
+
|
54
|
+
Timecop.travel(10.hours.since) do
|
55
|
+
UniqueJob.enqueue(1, param: 'two')
|
56
|
+
|
57
|
+
expect(Resque.peek(:test, 0, 100).size).to eq(2)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'enqueues two jobs after expire lock timeout' do
|
62
|
+
UniqueJob.enqueue(1, param: 'one')
|
63
|
+
|
64
|
+
Timecop.travel(4.days.since) do
|
65
|
+
UniqueJob.enqueue(1, param: 'one')
|
66
|
+
|
67
|
+
expect(Resque.peek(:test, 0, 100).size).to eq(2)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe 'unlock' do
|
72
|
+
include_context 'resque inline'
|
73
|
+
|
74
|
+
class UniqueJobWithBlock
|
75
|
+
include Resque::Integration
|
76
|
+
|
77
|
+
queue :test_with_block
|
78
|
+
unique { |id, params| [id, params[:one], params[:two]] }
|
79
|
+
|
80
|
+
def self.execute(id, params)
|
81
|
+
DummyService.call
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'unlocks uniq job with args and without block' do
|
86
|
+
expect(DummyService).to receive(:call).twice
|
87
|
+
|
88
|
+
UniqueJob.enqueue(1, one: 1, two: 2)
|
89
|
+
UniqueJob.enqueue(1, one: 1, two: 2)
|
90
|
+
|
91
|
+
expect(UniqueJob.locked?(1, one: 1, two: 2)).to eq(false)
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'unlocks uniq job with args and block' do
|
95
|
+
expect(DummyService).to receive(:call).twice
|
96
|
+
|
97
|
+
UniqueJobWithBlock.enqueue(1, one: 1, two: 2)
|
98
|
+
UniqueJobWithBlock.enqueue(1, one: 1, two: 2)
|
99
|
+
|
100
|
+
expect(UniqueJobWithBlock.locked?(1, one: 1, two: 2)).to eq(false)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|