resque-integration 3.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|