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.
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