sidekiq-unique-jobs 3.0.14 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of sidekiq-unique-jobs might be problematic. Click here for more details.

Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. data/.editorconfig +14 -0
  3. data/.gitignore +2 -0
  4. data/.rubocop.yml +8 -0
  5. data/.simplecov +12 -0
  6. data/.travis.yml +16 -8
  7. data/Appraisals +10 -10
  8. data/CHANGELOG.md +11 -0
  9. data/Gemfile +11 -1
  10. data/README.md +58 -4
  11. data/Rakefile +2 -1
  12. data/circle.yml +36 -0
  13. data/gemfiles/sidekiq_2.17.gemfile +8 -1
  14. data/gemfiles/sidekiq_3.0.gemfile +8 -1
  15. data/gemfiles/sidekiq_3.1.gemfile +8 -1
  16. data/gemfiles/sidekiq_3.2.gemfile +8 -1
  17. data/gemfiles/sidekiq_3.3.gemfile +8 -1
  18. data/gemfiles/sidekiq_develop.gemfile +8 -1
  19. data/lib/sidekiq-unique-jobs.rb +44 -9
  20. data/lib/sidekiq_unique_jobs/client/middleware.rb +47 -0
  21. data/lib/sidekiq_unique_jobs/config.rb +9 -33
  22. data/lib/sidekiq_unique_jobs/core_ext.rb +46 -0
  23. data/lib/sidekiq_unique_jobs/lock.rb +10 -0
  24. data/lib/sidekiq_unique_jobs/lock/time_calculator.rb +44 -0
  25. data/lib/sidekiq_unique_jobs/lock/until_executed.rb +56 -0
  26. data/lib/sidekiq_unique_jobs/lock/until_executing.rb +6 -0
  27. data/lib/sidekiq_unique_jobs/lock/until_timeout.rb +10 -0
  28. data/lib/sidekiq_unique_jobs/lock/while_executing.rb +31 -0
  29. data/lib/sidekiq_unique_jobs/middleware.rb +30 -14
  30. data/lib/sidekiq_unique_jobs/normalizer.rb +7 -0
  31. data/lib/sidekiq_unique_jobs/options_with_fallback.rb +36 -0
  32. data/lib/sidekiq_unique_jobs/run_lock_failed.rb +1 -0
  33. data/lib/sidekiq_unique_jobs/scripts.rb +50 -0
  34. data/lib/sidekiq_unique_jobs/server/middleware.rb +73 -0
  35. data/lib/sidekiq_unique_jobs/sidekiq_unique_ext.rb +71 -9
  36. data/lib/sidekiq_unique_jobs/testing.rb +34 -0
  37. data/lib/sidekiq_unique_jobs/testing/sidekiq_overrides.rb +63 -0
  38. data/lib/sidekiq_unique_jobs/unique_args.rb +132 -0
  39. data/lib/sidekiq_unique_jobs/unlockable.rb +26 -0
  40. data/lib/sidekiq_unique_jobs/version.rb +1 -1
  41. data/redis/aquire_lock.lua +9 -0
  42. data/redis/release_lock.lua +14 -0
  43. data/redis/synchronize.lua +15 -0
  44. data/sidekiq-unique-jobs.gemspec +2 -4
  45. data/spec/lib/sidekiq_unique_jobs/client/middleware_spec.rb +195 -0
  46. data/spec/lib/sidekiq_unique_jobs/core_ext_spec.rb +25 -0
  47. data/spec/lib/sidekiq_unique_jobs/lock/time_calculator_spec.rb +81 -0
  48. data/spec/lib/sidekiq_unique_jobs/lock/while_executing_spec.rb +48 -0
  49. data/spec/lib/sidekiq_unique_jobs/normalizer_spec.rb +21 -0
  50. data/spec/lib/sidekiq_unique_jobs/scripts_spec.rb +74 -0
  51. data/spec/lib/sidekiq_unique_jobs/server/middleware_spec.rb +100 -0
  52. data/spec/lib/{sidekiq_testing_enabled_spec.rb → sidekiq_unique_jobs/sidekiq_testing_enabled_spec.rb} +29 -68
  53. data/spec/lib/sidekiq_unique_jobs/sidekiq_unique_ext_spec.rb +79 -0
  54. data/spec/lib/sidekiq_unique_jobs/sidekiq_unique_jobs_spec.rb +36 -0
  55. data/spec/lib/sidekiq_unique_jobs/unique_args_spec.rb +106 -0
  56. data/spec/spec_helper.rb +40 -10
  57. data/spec/support/matchers/redis_matchers.rb +19 -0
  58. data/spec/support/ruby_meta.rb +10 -0
  59. data/spec/support/sidekiq_meta.rb +11 -2
  60. data/spec/support/unique_macros.rb +52 -0
  61. data/spec/workers/after_unlock_worker.rb +13 -0
  62. data/spec/{support → workers}/after_yield_worker.rb +6 -2
  63. data/spec/{support → workers}/another_unique_worker.rb +1 -1
  64. data/spec/workers/before_yield_worker.rb +9 -0
  65. data/spec/workers/expiring_worker.rb +4 -0
  66. data/spec/workers/inline_expiration_worker.rb +8 -0
  67. data/spec/workers/inline_unlock_order_worker.rb +8 -0
  68. data/spec/workers/inline_worker.rb +8 -0
  69. data/spec/workers/just_a_worker.rb +8 -0
  70. data/spec/workers/main_job.rb +8 -0
  71. data/spec/workers/my_unique_worker.rb +8 -0
  72. data/spec/{support → workers}/my_worker.rb +0 -0
  73. data/spec/workers/plain_class.rb +4 -0
  74. data/spec/workers/queue_worker.rb +6 -0
  75. data/spec/workers/queue_worker_with_filter_method.rb +7 -0
  76. data/spec/workers/queue_worker_with_filter_proc.rb +11 -0
  77. data/spec/workers/run_lock_with_retries_worker.rb +12 -0
  78. data/spec/workers/run_lock_worker.rb +7 -0
  79. data/spec/workers/test_class.rb +4 -0
  80. data/spec/workers/unique_job_with_filter_method.rb +18 -0
  81. data/spec/workers/unique_on_all_queues_worker.rb +13 -0
  82. data/spec/{support → workers}/unique_worker.rb +1 -1
  83. data/spec/workers/while_executing_worker.rb +13 -0
  84. metadata +65 -39
  85. data/lib/sidekiq_unique_jobs/connectors.rb +0 -16
  86. data/lib/sidekiq_unique_jobs/connectors/redis_pool.rb +0 -11
  87. data/lib/sidekiq_unique_jobs/connectors/sidekiq_redis.rb +0 -9
  88. data/lib/sidekiq_unique_jobs/connectors/testing.rb +0 -11
  89. data/lib/sidekiq_unique_jobs/inline_testing.rb +0 -12
  90. data/lib/sidekiq_unique_jobs/middleware/client/strategies/testing_inline.rb +0 -25
  91. data/lib/sidekiq_unique_jobs/middleware/client/strategies/unique.rb +0 -105
  92. data/lib/sidekiq_unique_jobs/middleware/client/unique_jobs.rb +0 -43
  93. data/lib/sidekiq_unique_jobs/middleware/server/unique_jobs.rb +0 -69
  94. data/lib/sidekiq_unique_jobs/payload_helper.rb +0 -42
  95. data/lib/sidekiq_unique_jobs/sidekiq_test_overrides.rb +0 -101
  96. data/spec/lib/client_spec.rb +0 -193
  97. data/spec/lib/middleware/server/unique_jobs_spec.rb +0 -112
  98. data/spec/lib/sidekiq_unique_ext_spec.rb +0 -70
  99. data/spec/lib/unlock_order_spec.rb +0 -64
@@ -1,3 +1,3 @@
1
1
  module SidekiqUniqueJobs
2
- VERSION = '3.0.14'
2
+ VERSION = '4.0.0'
3
3
  end
@@ -0,0 +1,9 @@
1
+ local unique_key = KEYS[1]
2
+ local job_id = ARGV[1]
3
+ local expires = ARGV[2]
4
+
5
+ if redis.pcall('set', unique_key, job_id, 'nx', 'ex', expires) then
6
+ return 1
7
+ else
8
+ return 0
9
+ end
@@ -0,0 +1,14 @@
1
+ local unique_key = KEYS[1]
2
+ local job_id = ARGV[1]
3
+ local stored_jid = redis.pcall('get', unique_key)
4
+
5
+ if stored_jid then
6
+ if stored_jid == job_id then
7
+ return redis.pcall('del', unique_key)
8
+ else
9
+ return 0
10
+ end
11
+ else
12
+ return -1
13
+ end
14
+
@@ -0,0 +1,15 @@
1
+ local unique_key = KEYS[1]
2
+ local time = ARGV[1]
3
+
4
+ if redis.pcall('set', unique_key, time + 60, 'nx', 'ex', 60) then
5
+ return 1
6
+ end
7
+
8
+ local stored_time = redis.pcall('get', unique_key)
9
+ if stored_time and stored_time < time then
10
+ if redis.call('set', unique_key, time + 60, 'nx', 'ex') then
11
+ return 1
12
+ end
13
+ end
14
+
15
+ return 0
@@ -13,11 +13,9 @@ Gem::Specification.new do |gem|
13
13
  gem.test_files = `git ls-files -- test/*`.split("\n")
14
14
  gem.name = 'sidekiq-unique-jobs'
15
15
  gem.require_paths = ['lib']
16
- gem.post_install_message = 'If you are relying on `mock_redis` you will now have to add' \
17
- 'gem "mock_redis" to your desired bundler group.'
18
- gem.version = SidekiqUniqueJobs::VERSION
16
+ gem.post_install_message = 'WARNING: VERSION 4.0.0 HAS BREAKING CHANGES! Please see Readme for info'
17
+ gem.version = SidekiqUniqueJobs::VERSION
19
18
  gem.add_dependency 'sidekiq', '>= 2.6'
20
- gem.add_development_dependency 'mock_redis'
21
19
  gem.add_development_dependency 'rspec', '~> 3.1.0'
22
20
  gem.add_development_dependency 'rake'
23
21
  gem.add_development_dependency 'rspec-sidekiq'
@@ -0,0 +1,195 @@
1
+ require 'spec_helper'
2
+ require 'celluloid/current'
3
+ require 'sidekiq/worker'
4
+ require 'sidekiq-unique-jobs'
5
+ require 'sidekiq/scheduled'
6
+
7
+ RSpec.describe SidekiqUniqueJobs::Client::Middleware do
8
+ def digest_for(item)
9
+ SidekiqUniqueJobs::UniqueArgs.digest(item)
10
+ end
11
+
12
+ describe 'with real redis' do
13
+ before do
14
+ Sidekiq.redis = REDIS
15
+ Sidekiq.redis(&:flushdb)
16
+ QueueWorker.sidekiq_options unique: nil, unique_expiration: nil
17
+ end
18
+
19
+ describe 'when a job is already scheduled' do
20
+ context '#new_unique_for' do
21
+ it 'rejects new scheduled jobs with the same argument' do
22
+ MyUniqueWorker.perform_in(3600, 1)
23
+ expect(MyUniqueWorker.perform_in(3600, 1)).to eq(nil)
24
+ end
25
+
26
+ it 'will run a job in real time with the same arguments' do
27
+ WhileExecutingWorker.perform_in(3600, 1)
28
+ expect(WhileExecutingWorker.perform_async(1)).not_to eq(nil)
29
+ end
30
+
31
+ it 'schedules new jobs when arguments differ' do
32
+ [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20].each do |x|
33
+ MainJob.perform_in(x.seconds.from_now, x)
34
+ end
35
+
36
+ Sidekiq.redis do |c|
37
+ count = c.zcount('schedule', -1, Time.now.to_f + 2 * 60)
38
+ expect(count).to eq(20)
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ it 'does not push duplicate messages when configured for unique only' do
45
+ item = { 'class' => MyUniqueWorker, 'queue' => 'customqueue', 'args' => [1, 2] }
46
+ 10.times { Sidekiq::Client.push(item) }
47
+ Sidekiq.redis do |c|
48
+ expect(c.llen('queue:customqueue')).to eq(1)
49
+ end
50
+ end
51
+
52
+ it 'does push duplicate messages to different queues' do
53
+ Sidekiq::Client.push('class' => MyUniqueWorker, 'queue' => 'customqueue', 'args' => [1, 2])
54
+ Sidekiq::Client.push('class' => MyUniqueWorker, 'queue' => 'customqueue2', 'args' => [1, 2])
55
+ Sidekiq.redis do |c|
56
+ expect(c.llen('queue:customqueue')).to eq 1
57
+ expect(c.llen('queue:customqueue2')).to eq 1
58
+ end
59
+ end
60
+
61
+ it 'does not queue duplicates when when calling delay' do
62
+ 10.times { PlainClass.delay(unique_lock: :until_executed, unique: true, queue: 'customqueue').run(1) }
63
+ Sidekiq.redis do |c|
64
+ expect(c.llen('queue:customqueue')).to eq(1)
65
+ end
66
+ end
67
+
68
+ it 'does not schedule duplicates when calling perform_in' do
69
+ 10.times { MyUniqueWorker.perform_in(60, [1, 2]) }
70
+ Sidekiq.redis do |c|
71
+ expect(c.zcount('schedule', -1, Time.now.to_f + 2 * 60))
72
+ .to eq(1)
73
+ end
74
+ end
75
+
76
+ it 'enqueues previously scheduled job' do
77
+ jid = WhileExecutingWorker.perform_in(60 * 60, 1, 2)
78
+ item = { 'class' => WhileExecutingWorker, 'queue' => 'customqueue', 'args' => [1, 2], 'jid' => jid }
79
+
80
+ # time passes and the job is pulled off the schedule:
81
+ Sidekiq::Client.push(item)
82
+
83
+ Sidekiq.redis do |c|
84
+ expect(c.llen('queue:customqueue')).to eq 1
85
+ end
86
+ end
87
+
88
+ it 'sets an expiration when provided by sidekiq options' do
89
+ item = { 'class' => ExpiringWorker, 'queue' => 'customqueue', 'args' => [1, 2] }
90
+ Sidekiq::Client.push(item)
91
+
92
+ Sidekiq.redis do |c|
93
+ expect(c.llen('queue:customqueue')).to eq(1)
94
+ expect(c.ttl(digest_for(item)))
95
+ .to eq(ExpiringWorker.get_sidekiq_options['unique_expiration'])
96
+ end
97
+ end
98
+
99
+ it 'does push duplicate messages when not configured for unique only' do
100
+ 10.times { Sidekiq::Client.push('class' => QueueWorker, 'queue' => 'customqueue', 'args' => [1, 2]) }
101
+
102
+ Sidekiq.redis do |c|
103
+ expect(c.llen('queue:customqueue')).to eq(10)
104
+ end
105
+ end
106
+
107
+ describe 'when unique_args is defined' do
108
+ before(:all) { SidekiqUniqueJobs.config.unique_args_enabled = true }
109
+ after(:all) { SidekiqUniqueJobs.config.unique_args_enabled = false }
110
+
111
+ it 'does not push duplicate messages based on args filter method' do
112
+ expect(QueueWorkerWithFilterMethod).to respond_to(:args_filter)
113
+ expect(QueueWorkerWithFilterMethod.get_sidekiq_options['unique_args']).to eq :args_filter
114
+
115
+ (0..10).each do |i|
116
+ Sidekiq::Client.push(
117
+ 'class' => QueueWorkerWithFilterMethod,
118
+ 'queue' => 'customqueue',
119
+ 'args' => [1, i]
120
+ )
121
+ end
122
+
123
+ Sidekiq.redis do |c|
124
+ expect(c.llen('queue:customqueue')).to eq(1)
125
+ end
126
+ end
127
+
128
+ it 'does not push duplicate messages based on args filter proc' do
129
+ expect(QueueWorkerWithFilterProc.get_sidekiq_options['unique_args']).to be_a(Proc)
130
+
131
+ 100.times do
132
+ Sidekiq::Client.push(
133
+ 'class' => QueueWorkerWithFilterProc,
134
+ 'queue' => 'customqueue',
135
+ 'args' => [1, { random: rand, name: 'foobar' }]
136
+ )
137
+ end
138
+
139
+ Sidekiq.redis do |c|
140
+ expect(c.llen('queue:customqueue')).to eq(1)
141
+ end
142
+ end
143
+
144
+ describe 'when unique_on_all_queues is set' do
145
+ it 'does not push duplicate messages on different queues' do
146
+ item = { 'class' => UniqueOnAllQueuesWorker, 'args' => [1, 2] }
147
+ Sidekiq::Client.push(item.merge('queue' => 'customqueue'))
148
+ Sidekiq::Client.push(item.merge('queue' => 'customqueue2'))
149
+ Sidekiq.redis do |c|
150
+ expect(c.llen('queue:customqueue')).to eq(1)
151
+ expect(c.llen('queue:customqueue2')).to eq(0)
152
+ end
153
+ end
154
+ end
155
+ end
156
+
157
+ # TODO: If anyone know of a better way to check that the expiration for scheduled
158
+ # jobs are set around the same time as the scheduled job itself feel free to improve.
159
+ it 'expires the digest when a scheduled job is scheduled at' do
160
+ expected_expires_at =
161
+ (Time.at(15.minutes.from_now) - Time.now.utc) + SidekiqUniqueJobs.config.default_expiration
162
+ jid = MyUniqueWorker.perform_in(expected_expires_at, 'mike')
163
+ item = { 'class' => MyUniqueWorker,
164
+ 'queue' => 'customqueue',
165
+ 'args' => ['mike'],
166
+ 'at' => expected_expires_at }
167
+ digest = digest_for(item.merge('jid' => jid))
168
+ Sidekiq.redis do |c|
169
+ expect(c.ttl(digest)).to eq(9_899)
170
+ end
171
+ end
172
+
173
+ it 'logs duplicate payload when config turned on' do
174
+ expect(Sidekiq.logger).to receive(:warn).with(/^payload is not unique/)
175
+ UniqueWorker.sidekiq_options log_duplicate_payload: true
176
+ 2.times { Sidekiq::Client.push('class' => UniqueWorker, 'queue' => 'customqueue', 'args' => [1, 2]) }
177
+ Sidekiq.redis do |c|
178
+ expect(c.llen('queue:customqueue')).to eq 1
179
+ end
180
+ UniqueWorker.sidekiq_options log_duplicate_payload: true
181
+ end
182
+
183
+ it 'does not log duplicate payload when config turned off' do
184
+ expect(Sidekiq.logger).to_not receive(:warn).with(/^payload is not unique/)
185
+
186
+ UniqueWorker.sidekiq_options log_duplicate_payload: false
187
+
188
+ 2.times { Sidekiq::Client.push('class' => UniqueWorker, 'queue' => 'customqueue', 'args' => [1, 2]) }
189
+ Sidekiq.redis do |c|
190
+ expect(c.llen('queue:customqueue')).to eq 1
191
+ end
192
+ UniqueWorker.sidekiq_options log_duplicate_payload: true
193
+ end
194
+ end
195
+ end
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Hash do
4
+ subject { { test: :me, not: :me } }
5
+
6
+ describe '#slice' do
7
+ specify { expect(subject.slice(:test)).to eq(test: :me) }
8
+ end
9
+
10
+ describe '#slice!' do
11
+ specify { expect { subject.slice!(:test) }.to change { subject }.to(test: :me) }
12
+ end
13
+ end
14
+
15
+ RSpec.describe String do
16
+ describe '#classify' do
17
+ subject { 'under_scored_string' }
18
+ its(:classify) { is_expected.to eq('UnderScoredString') }
19
+ end
20
+
21
+ describe '#camelize' do
22
+ subject { 'under_scored_string' }
23
+ its(:camelize) { is_expected.to eq('UnderScoredString') }
24
+ end
25
+ end
@@ -0,0 +1,81 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe SidekiqUniqueJobs::Lock::TimeCalculator do
4
+ include ActiveSupport::Testing::TimeHelpers
5
+ shared_context 'undefined worker class' do
6
+ subject { described_class.new('class' => 'test') }
7
+ end
8
+
9
+ shared_context 'item not scheduled' do
10
+ subject { described_class.new('class' => 'MyUniqueWorker') }
11
+ end
12
+
13
+ describe 'public api' do
14
+ subject { described_class.new(nil) }
15
+ it { is_expected.to respond_to(:time_until_scheduled) }
16
+ it { is_expected.to respond_to(:unique_expiration) }
17
+ it { is_expected.to respond_to(:worker_class_unique_expiration) }
18
+ it { is_expected.to respond_to(:worker_class) }
19
+ it { is_expected.to respond_to(:seconds) }
20
+ end
21
+
22
+ describe '.for_item' do
23
+ it 'initializes a new calculator' do
24
+ expect(described_class).to receive(:new).with('WAT')
25
+ described_class.for_item('WAT')
26
+ end
27
+ end
28
+
29
+ describe '#seconds' do
30
+ subject { described_class.new(nil) }
31
+
32
+ before do
33
+ allow(subject).to receive(:time_until_scheduled).and_return(10)
34
+ allow(subject).to receive(:unique_expiration).and_return(9)
35
+ end
36
+ its(:seconds) { is_expected.to eq(19) }
37
+ end
38
+
39
+ describe '#time_until_scheduled' do
40
+ it_behaves_like 'item not scheduled' do
41
+ its(:time_until_scheduled) { is_expected.to eq(0) }
42
+ end
43
+
44
+ subject { described_class.new('class' => 'MyUniqueWorker', 'at' => schedule_time) }
45
+ let(:schedule_time) { 1.day.from_now.to_i }
46
+ let(:now_in_utc) { Time.now.utc.to_i }
47
+
48
+ its(:time_until_scheduled) do
49
+ travel_to(Time.at(now_in_utc)) do
50
+ is_expected.to eq(schedule_time - now_in_utc)
51
+ end
52
+ end
53
+ end
54
+
55
+ describe '#unique_expiration' do
56
+ it_behaves_like 'undefined worker class' do
57
+ its(:unique_expiration) { is_expected.to eq(SidekiqUniqueJobs.config.default_expiration) }
58
+ end
59
+
60
+ subject { described_class.new('class' => 'MyUniqueWorker') }
61
+ its(:unique_expiration) { is_expected.to eq(7_200) }
62
+ end
63
+
64
+ describe '#worker_class_unique_expiration' do
65
+ it_behaves_like 'undefined worker class' do
66
+ its(:worker_class_unique_expiration) { is_expected.to eq(nil) }
67
+ end
68
+
69
+ subject { described_class.new('class' => 'MyUniqueWorker') }
70
+ its(:worker_class_unique_expiration) { is_expected.to eq(7_200) }
71
+ end
72
+
73
+ describe '#worker_class' do
74
+ it_behaves_like 'undefined worker class' do
75
+ its(:worker_class) { is_expected.to eq('test') }
76
+ end
77
+
78
+ subject { described_class.new('class' => 'MyWorker') }
79
+ its(:worker_class) { is_expected.to eq(MyWorker) }
80
+ end
81
+ end
@@ -0,0 +1,48 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe SidekiqUniqueJobs::Lock::WhileExecuting do
4
+ it 'allows only one mutex object to have the lock at a time' do
5
+ mutexes = (1..10).map do
6
+ described_class.new('test_mutex_key')
7
+ end
8
+
9
+ x = 0
10
+ mutexes.map do |m|
11
+ Thread.new do
12
+ m.synchronize do
13
+ y = x
14
+ sleep 0.001
15
+ x = y + 1
16
+ end
17
+ end
18
+ end.map(&:join)
19
+
20
+ expect(x).to eq(10)
21
+ end
22
+
23
+ it 'handles auto cleanup correctly' do
24
+ m = described_class.new('test_mutex_key')
25
+
26
+ SidekiqUniqueJobs.connection do |conn|
27
+ conn.set 'test_mutex_key', Time.now.to_i - 1, nx: true
28
+ end
29
+
30
+ start = Time.now.to_i
31
+ m.synchronize do
32
+ 'nop'
33
+ end
34
+
35
+ # no longer than a second
36
+ expect(Time.now.to_i).to be <= start + 1
37
+ end
38
+
39
+ it 'maintains mutex semantics' do
40
+ m = described_class.new('test_mutex_key')
41
+
42
+ expect do
43
+ m.synchronize do
44
+ m.synchronize {}
45
+ end
46
+ end.to raise_error(ThreadError)
47
+ end
48
+ end
@@ -0,0 +1,21 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe SidekiqUniqueJobs::Normalizer do
4
+ def jsonify(args)
5
+ described_class.jsonify(args)
6
+ end
7
+
8
+ describe '.jsonify' do
9
+ specify do
10
+ original = [1, :test, [test: :test]]
11
+ expected = [1, 'test', ['test' => 'test']]
12
+ expect(jsonify(original)).to eq(expected)
13
+ end
14
+
15
+ specify do
16
+ original = [1, :test, [test: [test: :test]]]
17
+ expected = [1, 'test', ['test' => ['test' => 'test']]]
18
+ expect(jsonify(original)).to eq(expected)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,74 @@
1
+ require 'spec_helper'
2
+ RSpec.describe SidekiqUniqueJobs::Scripts do
3
+ include ActiveSupport::Testing::TimeHelpers
4
+ MD5_DIGEST ||= 'unique'.freeze
5
+ UNIQUE_KEY ||= 'uniquejobs:unique'.freeze
6
+ JID ||= 'fuckit'.freeze
7
+ ANOTHER_JID ||= 'anotherjid'.freeze
8
+
9
+ context 'class methods' do
10
+ subject { SidekiqUniqueJobs::Scripts }
11
+
12
+ it { is_expected.to respond_to(:call).with(3).arguments }
13
+ it { is_expected.to respond_to(:logger) }
14
+ it { is_expected.to respond_to(:script_shas) }
15
+ it { is_expected.to respond_to(:connection).with(1).arguments }
16
+ it { is_expected.to respond_to(:script_source).with(1).arguments }
17
+ it { is_expected.to respond_to(:script_path).with(1).arguments }
18
+
19
+ describe '.script_shas' do
20
+ its(:script_shas) { is_expected.to be_a(Hash) }
21
+ end
22
+
23
+ describe '.logger' do
24
+ its(:logger) { is_expected.to eq(Sidekiq.logger) }
25
+ end
26
+
27
+ def lock_for(seconds = 1, jid = JID, key = UNIQUE_KEY)
28
+ subject.call(:aquire_lock, nil, keys: [key], argv: [jid, seconds])
29
+ end
30
+
31
+ def unlock(key = UNIQUE_KEY, jid = JID)
32
+ subject.call(:release_lock, nil, keys: [key], argv: [jid])
33
+ end
34
+
35
+ describe '.aquire_lock' do
36
+ context 'when job is unique' do
37
+ specify { expect(lock_for).to eq(1) }
38
+ specify do
39
+ expect(lock_for(0.5.seconds)).to eq(1)
40
+ expect(Redis)
41
+ .to have_key(UNIQUE_KEY)
42
+ .for_seconds(1)
43
+ .with_value('fuckit')
44
+ sleep 0.5
45
+ expect(lock_for).to eq(1)
46
+ end
47
+
48
+ context 'when job is locked' do
49
+ before { expect(lock_for).to eq(1) }
50
+ specify { expect(lock_for).to eq(0) }
51
+ end
52
+ end
53
+ end
54
+
55
+ describe '.release_lock' do
56
+ context 'when job is locked by another jid' do
57
+ before { expect(lock_for(10.seconds, 'anotherjid')).to eq(1) }
58
+ specify { expect(unlock).to eq(0) }
59
+ after { unlock(UNIQUE_KEY, ANOTHER_JID) }
60
+ end
61
+
62
+ context 'when job is not locked at all' do
63
+ specify { expect(unlock).to eq(-1) }
64
+ end
65
+
66
+ context 'when job is locked by the same jid' do
67
+ specify do
68
+ expect(lock_for(10.seconds)).to eq(1)
69
+ expect(unlock).to eq(1)
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end