resque-integration 3.4.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +7 -0
  2. data/.drone.yml +28 -0
  3. data/.gitignore +21 -0
  4. data/.rspec +3 -0
  5. data/Appraisals +27 -0
  6. data/CHANGELOG.md +311 -0
  7. data/Gemfile +4 -0
  8. data/README.md +281 -0
  9. data/Rakefile +1 -0
  10. data/app/assets/javascripts/standalone/progress_bar.js +47 -0
  11. data/app/controllers/resque/jobs_controller.rb +42 -0
  12. data/app/controllers/resque/queues/info_controller.rb +9 -0
  13. data/app/controllers/resque/queues/status_controller.rb +38 -0
  14. data/app/views/shared/job_progress_bar.html.haml +17 -0
  15. data/bin/resque-status +59 -0
  16. data/config/routes.rb +13 -0
  17. data/config.ru +9 -0
  18. data/dip.yml +44 -0
  19. data/docker-compose.development.yml +12 -0
  20. data/docker-compose.drone.yml +6 -0
  21. data/docker-compose.yml +10 -0
  22. data/lib/generators/resque/integration/install/install_generator.rb +38 -0
  23. data/lib/resque/integration/backtrace.rb +29 -0
  24. data/lib/resque/integration/configuration.rb +238 -0
  25. data/lib/resque/integration/continuous.rb +75 -0
  26. data/lib/resque/integration/engine.rb +103 -0
  27. data/lib/resque/integration/extensions/job.rb +17 -0
  28. data/lib/resque/integration/extensions/worker.rb +17 -0
  29. data/lib/resque/integration/extensions.rb +8 -0
  30. data/lib/resque/integration/failure_backends/queues_totals.rb +37 -0
  31. data/lib/resque/integration/failure_backends.rb +7 -0
  32. data/lib/resque/integration/god.erb +99 -0
  33. data/lib/resque/integration/hooks.rb +72 -0
  34. data/lib/resque/integration/logs_rotator.rb +95 -0
  35. data/lib/resque/integration/monkey_patch/verbose_formatter.rb +10 -0
  36. data/lib/resque/integration/ordered.rb +142 -0
  37. data/lib/resque/integration/priority.rb +89 -0
  38. data/lib/resque/integration/queues_info/age.rb +53 -0
  39. data/lib/resque/integration/queues_info/config.rb +96 -0
  40. data/lib/resque/integration/queues_info/size.rb +33 -0
  41. data/lib/resque/integration/queues_info.rb +55 -0
  42. data/lib/resque/integration/tasks/hooks.rake +49 -0
  43. data/lib/resque/integration/tasks/lock.rake +37 -0
  44. data/lib/resque/integration/tasks/resque.rake +101 -0
  45. data/lib/resque/integration/unique.rb +218 -0
  46. data/lib/resque/integration/version.rb +5 -0
  47. data/lib/resque/integration.rb +146 -0
  48. data/lib/resque-integration.rb +1 -0
  49. data/resque-integration.gemspec +40 -0
  50. data/spec/fixtures/resque_queues.yml +45 -0
  51. data/spec/resque/controllers/jobs_controller_spec.rb +65 -0
  52. data/spec/resque/integration/configuration_spec.rb +147 -0
  53. data/spec/resque/integration/continuous_spec.rb +122 -0
  54. data/spec/resque/integration/failure_backends/queues_totals_spec.rb +105 -0
  55. data/spec/resque/integration/ordered_spec.rb +87 -0
  56. data/spec/resque/integration/priority_spec.rb +94 -0
  57. data/spec/resque/integration/queues_info_spec.rb +402 -0
  58. data/spec/resque/integration/unique_spec.rb +184 -0
  59. data/spec/resque/integration_spec.rb +105 -0
  60. data/spec/shared/resque_inline.rb +10 -0
  61. data/spec/spec_helper.rb +28 -0
  62. data/vendor/assets/images/progressbar/white.gif +0 -0
  63. data/vendor/assets/javascripts/jquery.progressbar.js +177 -0
  64. data/vendor/assets/stylesheets/jquery.progressbar.css.erb +33 -0
  65. data/vendor/assets/stylesheets/jquery.progressbar.no_pipeline.css +33 -0
  66. 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
@@ -0,0 +1,10 @@
1
+ RSpec.shared_context 'resque inline' do
2
+ around do |example|
3
+ inline = Resque.inline
4
+ Resque.inline = true
5
+
6
+ example.run
7
+
8
+ Resque.inline = inline
9
+ end
10
+ end