joblin 0.1.0

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 (111) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +1 -0
  3. data/app/models/joblin/background_task/api_access.rb +148 -0
  4. data/app/models/joblin/background_task/attachments.rb +47 -0
  5. data/app/models/joblin/background_task/executor.rb +63 -0
  6. data/app/models/joblin/background_task/options.rb +75 -0
  7. data/app/models/joblin/background_task/retention_policy.rb +28 -0
  8. data/app/models/joblin/background_task.rb +72 -0
  9. data/app/models/joblin/concerns/job_working_dirs.rb +21 -0
  10. data/db/migrate/20250903184852_create_background_tasks.rb +12 -0
  11. data/joblin.gemspec +35 -0
  12. data/lib/joblin/batching/batch.rb +537 -0
  13. data/lib/joblin/batching/callback.rb +135 -0
  14. data/lib/joblin/batching/chain_builder.rb +247 -0
  15. data/lib/joblin/batching/compat/active_job.rb +108 -0
  16. data/lib/joblin/batching/compat/sidekiq/web/batches_assets/css/styles.less +182 -0
  17. data/lib/joblin/batching/compat/sidekiq/web/batches_assets/js/batch_tree.js +108 -0
  18. data/lib/joblin/batching/compat/sidekiq/web/batches_assets/js/util.js +2 -0
  19. data/lib/joblin/batching/compat/sidekiq/web/helpers.rb +41 -0
  20. data/lib/joblin/batching/compat/sidekiq/web/views/_batch_tree.erb +6 -0
  21. data/lib/joblin/batching/compat/sidekiq/web/views/_batches_table.erb +44 -0
  22. data/lib/joblin/batching/compat/sidekiq/web/views/_common.erb +13 -0
  23. data/lib/joblin/batching/compat/sidekiq/web/views/_jobs_table.erb +21 -0
  24. data/lib/joblin/batching/compat/sidekiq/web/views/_pagination.erb +26 -0
  25. data/lib/joblin/batching/compat/sidekiq/web/views/batch.erb +81 -0
  26. data/lib/joblin/batching/compat/sidekiq/web/views/batches.erb +23 -0
  27. data/lib/joblin/batching/compat/sidekiq/web/views/pool.erb +137 -0
  28. data/lib/joblin/batching/compat/sidekiq/web/views/pools.erb +47 -0
  29. data/lib/joblin/batching/compat/sidekiq/web.rb +218 -0
  30. data/lib/joblin/batching/compat/sidekiq.rb +149 -0
  31. data/lib/joblin/batching/compat.rb +20 -0
  32. data/lib/joblin/batching/context_hash.rb +157 -0
  33. data/lib/joblin/batching/hier_batch_ids.lua +25 -0
  34. data/lib/joblin/batching/jobs/base_job.rb +7 -0
  35. data/lib/joblin/batching/jobs/concurrent_batch_job.rb +20 -0
  36. data/lib/joblin/batching/jobs/managed_batch_job.rb +175 -0
  37. data/lib/joblin/batching/jobs/serial_batch_job.rb +20 -0
  38. data/lib/joblin/batching/pool.rb +254 -0
  39. data/lib/joblin/batching/pool_refill.lua +47 -0
  40. data/lib/joblin/batching/schedule_callback.lua +14 -0
  41. data/lib/joblin/batching/status.rb +89 -0
  42. data/lib/joblin/engine.rb +15 -0
  43. data/lib/joblin/lazy_access.rb +72 -0
  44. data/lib/joblin/uniqueness/compat/active_job.rb +75 -0
  45. data/lib/joblin/uniqueness/compat/sidekiq.rb +135 -0
  46. data/lib/joblin/uniqueness/compat.rb +20 -0
  47. data/lib/joblin/uniqueness/configuration.rb +25 -0
  48. data/lib/joblin/uniqueness/job_uniqueness.rb +49 -0
  49. data/lib/joblin/uniqueness/lock_context.rb +199 -0
  50. data/lib/joblin/uniqueness/locksmith.rb +92 -0
  51. data/lib/joblin/uniqueness/on_conflict/base.rb +32 -0
  52. data/lib/joblin/uniqueness/on_conflict/log.rb +13 -0
  53. data/lib/joblin/uniqueness/on_conflict/null_strategy.rb +9 -0
  54. data/lib/joblin/uniqueness/on_conflict/raise.rb +11 -0
  55. data/lib/joblin/uniqueness/on_conflict/reject.rb +21 -0
  56. data/lib/joblin/uniqueness/on_conflict/reschedule.rb +20 -0
  57. data/lib/joblin/uniqueness/on_conflict.rb +62 -0
  58. data/lib/joblin/uniqueness/strategy/base.rb +107 -0
  59. data/lib/joblin/uniqueness/strategy/until_and_while_executing.rb +35 -0
  60. data/lib/joblin/uniqueness/strategy/until_executed.rb +20 -0
  61. data/lib/joblin/uniqueness/strategy/until_executing.rb +20 -0
  62. data/lib/joblin/uniqueness/strategy/until_expired.rb +16 -0
  63. data/lib/joblin/uniqueness/strategy/while_executing.rb +26 -0
  64. data/lib/joblin/uniqueness/strategy.rb +27 -0
  65. data/lib/joblin/uniqueness/unique_job_common.rb +79 -0
  66. data/lib/joblin/version.rb +3 -0
  67. data/lib/joblin.rb +37 -0
  68. data/spec/batching/batch_spec.rb +493 -0
  69. data/spec/batching/callback_spec.rb +38 -0
  70. data/spec/batching/compat/active_job_spec.rb +107 -0
  71. data/spec/batching/compat/sidekiq_spec.rb +127 -0
  72. data/spec/batching/context_hash_spec.rb +54 -0
  73. data/spec/batching/flow_spec.rb +82 -0
  74. data/spec/batching/integration/fail_then_succeed.rb +42 -0
  75. data/spec/batching/integration/integration.rb +57 -0
  76. data/spec/batching/integration/nested.rb +88 -0
  77. data/spec/batching/integration/simple.rb +47 -0
  78. data/spec/batching/integration/workflow.rb +134 -0
  79. data/spec/batching/integration_helper.rb +50 -0
  80. data/spec/batching/pool_spec.rb +161 -0
  81. data/spec/batching/status_spec.rb +76 -0
  82. data/spec/batching/support/base_job.rb +19 -0
  83. data/spec/batching/support/sample_callback.rb +2 -0
  84. data/spec/internal/config/database.yml +5 -0
  85. data/spec/internal/config/routes.rb +5 -0
  86. data/spec/internal/config/storage.yml +3 -0
  87. data/spec/internal/db/combustion_test.sqlite +0 -0
  88. data/spec/internal/db/schema.rb +6 -0
  89. data/spec/internal/log/test.log +48200 -0
  90. data/spec/internal/public/favicon.ico +0 -0
  91. data/spec/models/background_task_spec.rb +41 -0
  92. data/spec/spec_helper.rb +29 -0
  93. data/spec/uniqueness/compat/active_job_spec.rb +49 -0
  94. data/spec/uniqueness/compat/sidekiq_spec.rb +68 -0
  95. data/spec/uniqueness/lock_context_spec.rb +106 -0
  96. data/spec/uniqueness/on_conflict/log_spec.rb +11 -0
  97. data/spec/uniqueness/on_conflict/raise_spec.rb +10 -0
  98. data/spec/uniqueness/on_conflict/reschedule_spec.rb +63 -0
  99. data/spec/uniqueness/on_conflict_spec.rb +16 -0
  100. data/spec/uniqueness/spec_helper.rb +19 -0
  101. data/spec/uniqueness/strategy/base_spec.rb +100 -0
  102. data/spec/uniqueness/strategy/until_and_while_executing_spec.rb +48 -0
  103. data/spec/uniqueness/strategy/until_executed_spec.rb +23 -0
  104. data/spec/uniqueness/strategy/until_executing_spec.rb +23 -0
  105. data/spec/uniqueness/strategy/until_expired_spec.rb +23 -0
  106. data/spec/uniqueness/strategy/while_executing_spec.rb +33 -0
  107. data/spec/uniqueness/support/lock_strategy.rb +28 -0
  108. data/spec/uniqueness/support/on_conflict.rb +24 -0
  109. data/spec/uniqueness/support/test_worker.rb +19 -0
  110. data/spec/uniqueness/unique_job_common_spec.rb +45 -0
  111. metadata +308 -0
File without changes
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Joblin::BackgroundTask, type: :model do
4
+ let(:task) { Joblin::BackgroundTask.create!(type: 'Joblin::BackgroundTask') }
5
+
6
+ describe '#options' do
7
+ it 'saves changes' do
8
+ task.options['foo'] = 'bar'
9
+ task.save!
10
+ expect(task.reload.extra_options).to eq({ "foo" => 'bar' })
11
+ end
12
+
13
+ it 'stores references' do
14
+ task.options['ref'] = task
15
+ task.save!
16
+ task.reload
17
+ expect(task.options['ref']).to eq(task)
18
+ expect(task.extra_options.to_h).to eq({ 'ref' => task.to_gid.to_s })
19
+ end
20
+
21
+ it 'is indifferent' do
22
+ task.options[:foo] = 'bar'
23
+ task.save!
24
+ task.reload
25
+ expect(task.options[:foo]).to eq('bar')
26
+ expect(task.options['foo']).to eq('bar')
27
+ expect(task.options[:updated_at]).to eq(task.updated_at)
28
+ expect(task.options['updated_at']).to eq(task.updated_at)
29
+ end
30
+
31
+ it 'reads from columns' do
32
+ expect(task.options[:updated_at]).to eq(task.updated_at)
33
+ end
34
+
35
+ it 'writes to columns' do
36
+ dt = 3.days.ago
37
+ task.options[:updated_at] = dt
38
+ expect(task.updated_at).to eq(dt)
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,29 @@
1
+ require 'bundler'
2
+
3
+ Bundler.require :default, :development
4
+
5
+ Combustion.initialize! :active_record, :active_job
6
+
7
+ require 'redis'
8
+
9
+ require 'rspec/rails'
10
+ # If you're using Capybara:
11
+ # require 'capybara/rails'
12
+
13
+ require 'sidekiq'
14
+ require 'sidekiq/rails'
15
+ require 'sidekiq/testing'
16
+ Sidekiq::Testing.fake!
17
+ Sidekiq::Testing.server_middleware do |chain|
18
+ chain.add Joblin::Batching::Compat::Sidekiq::ServerMiddleware
19
+ end
20
+
21
+ Dir[File.dirname(__FILE__) + "/batching/support/**/*.rb"].each {|f| require f }
22
+
23
+ RSpec.configure do |config|
24
+ config.use_transactional_fixtures = true
25
+ end
26
+
27
+ def set_batch_context(ctx)
28
+ allow_any_instance_of(ActiveJob::Base).to receive(:batch_context).and_return(ctx.with_indifferent_access)
29
+ end
@@ -0,0 +1,49 @@
1
+ require 'uniqueness/spec_helper'
2
+
3
+ RSpec.describe Joblin::Uniqueness::Compat::ActiveJob do
4
+ context 'Job Extension' do
5
+ it 'includes UniqueJobExtension' do
6
+ expect(ActiveJob::Base < Joblin::Uniqueness::Compat::ActiveJob::JobExtension).to be true
7
+ end
8
+
9
+ it 'has the ensure_uniqueness method' do
10
+ expect(ActiveJob::Base.method(:ensure_uniqueness)).to be_present
11
+ end
12
+ end
13
+
14
+ context 'Job' do
15
+ let(:test_job) do
16
+ Class.new(ActiveJob::Base) do
17
+ ensure_uniqueness(
18
+ strategy: :until_executed,
19
+ )
20
+ def perform; end
21
+ end
22
+ end
23
+
24
+ before(:each) do
25
+ stub_const('TestJob', test_job)
26
+ ActiveJob::Base.queue_adapter = :sidekiq
27
+ end
28
+
29
+ it 'runs as expected' do
30
+ strategy = Joblin::Uniqueness::Strategy::UntilExecuted.new(nil)
31
+ allow_any_instance_of(Joblin::Uniqueness::LockContext).to receive(:lock_strategy) do |lock_context|
32
+ strategy.instance_variable_set(:@lock_context, lock_context)
33
+ strategy
34
+ end
35
+ allow(Joblin::Uniqueness::Strategy::UntilExecuted).to receive(:new).and_return(strategy)
36
+
37
+ expect(strategy).to receive(:on_enqueue).and_call_original
38
+ expect(strategy).to receive(:on_perform).and_call_original
39
+ expect(strategy).to receive(:batch_callback).with(:complete, anything).and_call_original
40
+ expect(strategy).to receive(:batch_callback).with(:success, anything).and_call_original
41
+
42
+ expect_any_instance_of(test_job).to receive(:perform)
43
+
44
+ Sidekiq::Testing.inline! do
45
+ test_job.perform_later
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,68 @@
1
+ require 'uniqueness/spec_helper'
2
+
3
+ RSpec.describe Joblin::Uniqueness::Compat::Sidekiq do
4
+ let(:config) { defined?(Sidekiq::Config) ? double(Sidekiq::Config) : class_double(Sidekiq) }
5
+ let(:client_middleware) { double(Sidekiq::Middleware::Chain) }
6
+
7
+ context 'client' do
8
+ it 'adds client middleware' do
9
+ allow(Sidekiq).to receive(:configure_client).and_yield(config)
10
+ expect(config).to receive(:client_middleware).and_yield(client_middleware)
11
+ expect(client_middleware).to receive(:insert_before).with(anything, Joblin::Uniqueness::Compat::Sidekiq::ClientMiddleware)
12
+ Joblin::Uniqueness::Compat::Sidekiq.configure
13
+ end
14
+ end
15
+
16
+ context 'server' do
17
+ let(:server_middleware) { double(Sidekiq::Middleware::Chain) }
18
+
19
+ it 'adds client and server middleware' do
20
+ allow(Sidekiq).to receive(:configure_server).and_yield(config)
21
+ expect(config).to receive(:client_middleware).and_yield(client_middleware)
22
+ expect(config).to receive(:server_middleware).and_yield(server_middleware)
23
+ expect(client_middleware).to receive(:insert_before).with(anything, Joblin::Uniqueness::Compat::Sidekiq::ClientMiddleware)
24
+ expect(server_middleware).to receive(:insert_after).with(anything, Joblin::Uniqueness::Compat::Sidekiq::ServerMiddleware)
25
+ Joblin::Uniqueness::Compat::Sidekiq.configure
26
+ end
27
+ end
28
+
29
+ context 'Job Extension' do
30
+ let!(:dummy_worker) do
31
+ Class.new do
32
+ include Sidekiq::Worker
33
+ end
34
+ end
35
+
36
+ it 'includes UniqueJobExtension' do
37
+ expect(dummy_worker < Joblin::Uniqueness::Compat::Sidekiq::WorkerExtension).to be true
38
+ end
39
+
40
+ it 'has the ensure_uniqueness method' do
41
+ expect(dummy_worker.method(:ensure_uniqueness)).to be_present
42
+ end
43
+ end
44
+
45
+ context 'Job' do
46
+ include_context 'with TestWorker'
47
+
48
+ it 'runs as expected' do
49
+ strategy = Joblin::Uniqueness::Strategy::UntilExecuted.new(nil)
50
+ allow_any_instance_of(Joblin::Uniqueness::LockContext).to receive(:lock_strategy) do |lock_context|
51
+ strategy.instance_variable_set(:@lock_context, lock_context)
52
+ strategy
53
+ end
54
+ allow(Joblin::Uniqueness::Strategy::UntilExecuted).to receive(:new).and_return(strategy)
55
+
56
+ expect(strategy).to receive(:on_enqueue).and_call_original
57
+ expect(strategy).to receive(:on_perform).and_call_original
58
+ expect(strategy).to receive(:batch_callback).with(:complete, anything).and_call_original
59
+ expect(strategy).to receive(:batch_callback).with(:success, anything).and_call_original
60
+
61
+ expect_any_instance_of(TestWorker).to receive(:perform)
62
+
63
+ Sidekiq::Testing.inline! do
64
+ worker.perform_async
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,106 @@
1
+ require 'uniqueness/spec_helper'
2
+
3
+ RSpec.describe Joblin::Uniqueness::LockContext do
4
+ let!(:worker) do
5
+ Class.new do
6
+ include Sidekiq::Worker
7
+
8
+ ensure_uniqueness(
9
+ strategy: :until_executed,
10
+ )
11
+
12
+ def perform; end
13
+ end
14
+ end
15
+
16
+ before(:each) { stub_const('TestWorker', worker) }
17
+
18
+ let(:lock_context) { described_class.new(context_data) }
19
+ let(:context_data) { { queue: 'default', job_clazz: 'TestWorker', args: [1,2,3], kwargs: { foo: 'bar' } } }
20
+
21
+ describe '#base_key' do
22
+ let(:scope) { :per_queue }
23
+ before(:each) do
24
+ TestWorker.unique_job_options[:scope] = scope
25
+ end
26
+
27
+ it 'returns matching keys for equal hashes' do
28
+ context_data[:args] = [{ foo: 'bar', bar: 'foo' }]
29
+ key1 = lock_context.base_key
30
+ lock_context.instance_variable_set(:@base_key, nil)
31
+
32
+ context_data[:args] = [{ bar: 'foo', foo: 'bar' }]
33
+ key2 = lock_context.base_key
34
+
35
+ expect(key1).to eq key2
36
+ end
37
+
38
+ context 'when scope is a Proc' do
39
+ let(:scope) { ->(queue:) { "blob" } }
40
+
41
+ it 'returns the base key' do
42
+ expect(lock_context.base_key).to match /^uniquejob:blob:.*/
43
+ end
44
+ end
45
+
46
+ context 'when scope is :global' do
47
+ let(:scope) { :global }
48
+
49
+ it 'returns the base key' do
50
+ expect(lock_context.base_key).to match /^uniquejob:TestWorker:.*/
51
+ end
52
+ end
53
+
54
+ context 'when scope is :per_queue' do
55
+ let(:scope) { :per_queue }
56
+
57
+ it 'returns the base key' do
58
+ expect(lock_context.base_key).to match /^uniquejob:TestWorker:default:.*/
59
+ end
60
+ end
61
+
62
+ context 'when scope is not a Proc, :global, or :per_queue' do
63
+ let(:scope) { 'foo' }
64
+
65
+ it 'returns the base key' do
66
+ expect(lock_context.base_key).to match /^uniquejob:foo:.*/
67
+ end
68
+ end
69
+ end
70
+
71
+ describe '#call_conflict_strategy' do
72
+ before(:each) do
73
+ worker.unique_job_options[:on_conflict] = :raise
74
+ end
75
+
76
+ it 'invokes the conflict strategy' do
77
+ expect_any_instance_of(Joblin::Uniqueness::OnConflict::Raise).to receive(:call)
78
+ lock_context.send(:call_conflict_strategy, :perform)
79
+ end
80
+ end
81
+
82
+ describe '#conflict_strategy_for' do
83
+ before(:each) do
84
+ worker.unique_job_options[:on_conflict] = :raise
85
+ end
86
+
87
+ it 'resolves a united conflict strategy' do
88
+ expect(lock_context.send(:conflict_strategy_for, :perform)).to be_a Joblin::Uniqueness::OnConflict::Raise
89
+ end
90
+
91
+ it 'resolves enqueue strategy' do
92
+ worker.unique_job_options[:on_conflict] = { enqueue: :log, perform: :raise }
93
+ expect(lock_context.send(:conflict_strategy_for, :enqueue)).to be_a Joblin::Uniqueness::OnConflict::Log
94
+ end
95
+
96
+ it 'resolves perform strategy' do
97
+ worker.unique_job_options[:on_conflict] = { enqueue: :log, perform: :reject }
98
+ expect(lock_context.send(:conflict_strategy_for, :perform)).to be_a Joblin::Uniqueness::OnConflict::Reject
99
+ end
100
+
101
+ it 'resolves null strategy' do
102
+ worker.unique_job_options[:on_conflict] = { enqueue: :log }
103
+ expect(lock_context.send(:conflict_strategy_for, :perform)).to be_a Joblin::Uniqueness::OnConflict::NullStrategy
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,11 @@
1
+ require 'uniqueness/spec_helper'
2
+
3
+ RSpec.describe Joblin::Uniqueness::OnConflict::Log do
4
+ include_context "on_conflict specs"
5
+ include_examples "OnConflict is compatible with", %i[until_executing while_executing until_executed until_and_while_executing until_expired]
6
+
7
+ it "logs" do
8
+ expect(Joblin::Uniqueness.logger).to receive(:info)
9
+ on_conflict.call
10
+ end
11
+ end
@@ -0,0 +1,10 @@
1
+ require 'uniqueness/spec_helper'
2
+
3
+ RSpec.describe Joblin::Uniqueness::OnConflict::Raise do
4
+ include_context "on_conflict specs"
5
+ include_examples "OnConflict is compatible with", %i[until_executing while_executing until_executed until_and_while_executing until_expired]
6
+
7
+ it "raises an error" do
8
+ expect { on_conflict.call }.to raise_error(Joblin::Uniqueness::Conflict)
9
+ end
10
+ end
@@ -0,0 +1,63 @@
1
+ require 'uniqueness/spec_helper'
2
+
3
+ RSpec.describe Joblin::Uniqueness::OnConflict::Reschedule do
4
+ include_context "on_conflict specs"
5
+ include_examples "OnConflict is compatible with", %i[while_executing]
6
+
7
+ it "calls reenqueue" do
8
+ expect(lock_context).to receive(:reenqueue) do |*args, **kwargs|
9
+ expect(Thread.current[:unique_jobs_previous_context]).to be_present
10
+ end
11
+ on_conflict.call
12
+ end
13
+
14
+ it "reschedules successfully" do
15
+ TestWorker.unique_job_options.merge!({ strategy: :until_and_while_executing, on_conflict: { enqueue: :raise, perform: :reschedule }})
16
+
17
+ lock_context.handle_lifecycle!(:enqueue) {}
18
+ lock_context.handle_lifecycle!(:perform) {}
19
+
20
+ lock_context2 = Joblin::Uniqueness::Compat::Sidekiq::SidekiqLockContext.new({ jid: 'j2', queue: 'default', job_clazz: 'TestWorker'}, job_instance: {})
21
+ lock_context2.handle_lifecycle!(:enqueue) {}
22
+ lock_context2.handle_lifecycle!(:perform) {}
23
+ end
24
+
25
+ it "maintains a consistent lock_id" do
26
+ TestWorker.clear
27
+ TestWorker.unique_job_options.merge!({ strategy: :while_executing, on_conflict: :reschedule })
28
+
29
+ lock_context.handle_lifecycle!(:enqueue) {}
30
+ lock_context.handle_lifecycle!(:perform) {}
31
+
32
+ TestWorker.perform_async
33
+ j1 = TestWorker.jobs[0]
34
+ TestWorker.perform_one
35
+ j2 = TestWorker.jobs[0]
36
+
37
+ expect(j1).not_to eq(j2)
38
+ expect(j1['uniqueness_cache_data']['lid']).to be_present
39
+ expect(j1['uniqueness_cache_data']['lid']).to eq(j2['uniqueness_cache_data']['lid'])
40
+ end
41
+
42
+ it "unlocks after rescheduling" do
43
+ TestWorker.clear
44
+ TestWorker.unique_job_options.merge!({ strategy: :while_executing, on_conflict: :reschedule })
45
+
46
+ lock_context.handle_lifecycle!(:enqueue) {}
47
+ lock_context.handle_lifecycle!(:perform) {}
48
+
49
+ TestWorker.perform_async
50
+ expect(TestWorker.jobs.size).to eq(1)
51
+ TestWorker.perform_one
52
+ expect(TestWorker.jobs.size).to eq(1)
53
+
54
+ lock_context.lock_strategy.send(:unlock)
55
+ Sidekiq::Worker.drain_all
56
+ expect(TestWorker.jobs.size).to eq(0)
57
+
58
+ # Run another worker to validate that the lock was released
59
+ TestWorker.perform_async
60
+ TestWorker.perform_one
61
+ expect(TestWorker.jobs.size).to eq(0)
62
+ end
63
+ end
@@ -0,0 +1,16 @@
1
+ require 'uniqueness/spec_helper'
2
+
3
+ RSpec.describe Joblin::Uniqueness::OnConflict do
4
+
5
+ describe ".validate!" do
6
+ it "passes a valid pair" do
7
+ described_class.validate!(:reject, :while_executing)
8
+ end
9
+
10
+ it "raises an error if the strategy is not valid for the given action" do
11
+ expect do
12
+ described_class.validate!(:reject, :until_executed)
13
+ end.to raise_error(ArgumentError)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,19 @@
1
+ require_relative "../spec_helper"
2
+ require "joblin/uniqueness/job_uniqueness"
3
+
4
+ require 'sidekiq/testing'
5
+
6
+ require 'redis'
7
+ Redis.silence_deprecations = true
8
+
9
+ Dir[File.join(File.dirname(__FILE__), "./support/**/*.rb")].sort.each { |f| require f }
10
+
11
+ Sidekiq::Testing.server_middleware do |chain|
12
+ chain.insert_after Joblin::Batching::Compat::Sidekiq::ServerMiddleware, Joblin::Uniqueness::Compat::Sidekiq::ServerMiddleware
13
+ end
14
+
15
+ RSpec.configure do |config|
16
+ config.before(:each) do
17
+ Joblin.redis.flushdb
18
+ end
19
+ end
@@ -0,0 +1,100 @@
1
+ require 'uniqueness/spec_helper'
2
+
3
+ RSpec.describe Joblin::Uniqueness::Strategy::Base do
4
+ include_context 'with TestWorker'
5
+
6
+ let!(:jid) { worker.perform_async }
7
+ let(:context) { Joblin::Uniqueness::LockContext.new({ jid: jid, queue: 'default', job_clazz: 'TestWorker' }) }
8
+ let(:strategy) { Joblin::Uniqueness::Strategy::UntilExecuted.new(context) }
9
+
10
+ describe '#wrap_in_batch' do
11
+ it 'does not wrap if re-enqueuing' do
12
+ Thread.current[:unique_jobs_previous_context] = context
13
+ expect(Joblin::Batching::Batch).not_to receive(:new)
14
+ strategy.send(:wrap_in_batch, &->{ })
15
+ ensure
16
+ Thread.current[:unique_jobs_previous_context] = nil
17
+ end
18
+
19
+ context 'when the job fails' do
20
+ before(:each) do
21
+ TestWorker.unique_job_options[:strategy] = :until_executed
22
+ allow_any_instance_of(worker).to receive(:perform) do |w|
23
+ @bid = w.bid
24
+ raise 'error'
25
+ end
26
+ end
27
+
28
+ context 'unlock_on_failure: :any' do
29
+ before(:each) { TestWorker.unique_job_options[:unlock_on_failure] = :any }
30
+
31
+ it 'unlocks on any failure' do
32
+ expect(strategy).to be_locked
33
+ expect{ Sidekiq::Worker.drain_all }.to raise_error('error')
34
+ Sidekiq::Worker.drain_all
35
+ expect(strategy).not_to be_locked
36
+ end
37
+ end
38
+
39
+ context 'unlock_on_failure: :death' do
40
+ before(:each) { TestWorker.unique_job_options[:unlock_on_failure] = :death }
41
+
42
+ it 'does not unlock on any failure' do
43
+ expect(strategy).to be_locked
44
+ expect{ Sidekiq::Worker.drain_all }.to raise_error('error')
45
+ Sidekiq::Worker.drain_all
46
+ expect(strategy).to be_locked
47
+ end
48
+
49
+ it 'it unlocks on death' do
50
+ expect{ Sidekiq::Worker.drain_all }.to raise_error('error')
51
+ Sidekiq::Worker.drain_all
52
+ expect(strategy).to be_locked
53
+ Joblin::Batching::Batch.process_dead_job(@bid, jid)
54
+ Sidekiq::Worker.drain_all
55
+ expect(strategy).not_to be_locked
56
+ end
57
+ end
58
+
59
+ context 'unlock_on_failure: :stagnant' do
60
+ before(:each) { TestWorker.unique_job_options[:unlock_on_failure] = :stagnant }
61
+
62
+ it 'does not unlock on any failure' do
63
+ expect(strategy).to be_locked
64
+ expect{ Sidekiq::Worker.drain_all }.to raise_error('error')
65
+ Sidekiq::Worker.drain_all
66
+ expect(strategy).to be_locked
67
+ end
68
+
69
+ it 'it unlocks on stagnation' do
70
+ expect{ Sidekiq::Worker.drain_all }.to raise_error('error')
71
+ Sidekiq::Worker.drain_all
72
+ expect(strategy).to be_locked
73
+ Joblin::Batching::Batch.process_dead_job(@bid, jid)
74
+ Sidekiq::Worker.drain_all
75
+ expect(strategy).not_to be_locked
76
+ end
77
+ end
78
+
79
+ context 'unlock_on_failure: :expire' do
80
+ before(:each) { TestWorker.unique_job_options[:unlock_on_failure] = :expire }
81
+
82
+ it 'does not unlock on any failure' do
83
+ expect(strategy).to be_locked
84
+ expect{ Sidekiq::Worker.drain_all }.to raise_error('error')
85
+ Sidekiq::Worker.drain_all
86
+ expect(strategy).to be_locked
87
+ end
88
+
89
+ it 'does not unlock on death' do
90
+ expect{ Sidekiq::Worker.drain_all }.to raise_error('error')
91
+ Sidekiq::Worker.drain_all
92
+ expect(strategy).to be_locked
93
+ Joblin::Batching::Batch.process_dead_job(@bid, jid)
94
+ Sidekiq::Worker.drain_all
95
+ expect(strategy).to be_locked
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,48 @@
1
+ require 'uniqueness/spec_helper'
2
+
3
+ RSpec.describe Joblin::Uniqueness::Strategy::UntilAndWhileExecuting do
4
+ include_context 'lock strategy specs', strategy: :until_and_while_executing
5
+
6
+ include_examples "a lock implementation"
7
+
8
+ describe "#on_enqueue" do
9
+ it "locks the queue" do
10
+ process_one.on_enqueue {}
11
+ expect(process_one).to be_locked
12
+ end
13
+
14
+ it "does not lock the runtime lock" do
15
+ process_one.on_enqueue {}
16
+ expect(process_one.send(:runtime_lock)).to_not be_locked
17
+ end
18
+ end
19
+
20
+ describe "#on_perform" do
21
+ it "unlocks the queue" do
22
+ process_one.on_perform do
23
+ expect(process_one).to_not be_locked
24
+ end
25
+ end
26
+
27
+ it "locks the runtime lock" do
28
+ process_one.on_perform do
29
+ expect(process_one.send(:runtime_lock)).to be_locked
30
+ end
31
+ end
32
+
33
+ it "unlocks the runtime lock after processing" do
34
+ process_one.on_perform do
35
+ process_one.send(:runtime_lock).batch_callback(:success, nil)
36
+ expect(process_one.send(:runtime_lock)).to_not be_locked
37
+ end
38
+ end
39
+
40
+ it "prevents other processes from executing" do
41
+ process_one.on_perform do
42
+ expect do
43
+ process_two.on_perform { }
44
+ end.to raise_error(Joblin::Uniqueness::CouldNotLockError)
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,23 @@
1
+ require 'uniqueness/spec_helper'
2
+
3
+ RSpec.describe Joblin::Uniqueness::Strategy::UntilExecuted do
4
+ include_context 'lock strategy specs', strategy: :until_executed
5
+
6
+ include_examples "a lock implementation"
7
+
8
+ describe "#on_enqueue" do
9
+ it "locks the queue" do
10
+ process_one.on_enqueue {}
11
+ expect(process_one).to be_locked
12
+ end
13
+ end
14
+
15
+ describe "#on_perform" do
16
+ it "unlocks after processing" do
17
+ process_one.on_enqueue {}
18
+ process_one.on_perform {}
19
+ process_one.batch_callback(:success, nil)
20
+ expect(process_one).to_not be_locked
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,23 @@
1
+ require 'uniqueness/spec_helper'
2
+
3
+ RSpec.describe Joblin::Uniqueness::Strategy::UntilExecuting do
4
+ include_context 'lock strategy specs', strategy: :until_executing
5
+
6
+ include_examples "a lock implementation"
7
+
8
+ describe "#on_enqueue" do
9
+ it "locks the queue" do
10
+ process_one.on_enqueue {}
11
+ expect(process_one).to be_locked
12
+ end
13
+ end
14
+
15
+ describe "#on_perform" do
16
+ it "unlocks before processing" do
17
+ process_one.on_enqueue {}
18
+ process_one.on_perform do
19
+ expect(process_one).to_not be_locked
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,23 @@
1
+ require 'uniqueness/spec_helper'
2
+
3
+ RSpec.describe Joblin::Uniqueness::Strategy::UntilExpired do
4
+ include_context 'lock strategy specs', strategy: :until_expired
5
+
6
+ # include_examples "a lock implementation"
7
+
8
+ describe "#on_enqueue" do
9
+ it "locks the queue" do
10
+ process_one.on_enqueue {}
11
+ expect(process_one).to be_locked
12
+ end
13
+ end
14
+
15
+ describe "#on_perform" do
16
+ it "does not unlock after processing" do
17
+ process_one.on_enqueue {}
18
+ process_one.on_perform {}
19
+ process_one.batch_callback(:success, nil)
20
+ expect(process_one).to be_locked
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,33 @@
1
+ require 'uniqueness/spec_helper'
2
+
3
+ RSpec.describe Joblin::Uniqueness::Strategy::WhileExecuting do
4
+ include_context 'lock strategy specs', strategy: :while_executing
5
+
6
+ include_examples "a lock implementation"
7
+
8
+ describe "#on_enqueue" do
9
+ it "does not lock jobs" do
10
+ expect(process_one.on_enqueue {})
11
+ expect(process_one).not_to be_locked
12
+
13
+ expect(process_two.on_enqueue {})
14
+ expect(process_two).not_to be_locked
15
+ end
16
+ end
17
+
18
+ describe "#on_perform" do
19
+ it "locks the process" do
20
+ process_one.on_perform do
21
+ expect(process_one).to be_locked
22
+ end
23
+ end
24
+
25
+ it "prevents other processes from executing" do
26
+ process_one.on_perform do
27
+ expect do
28
+ process_two.on_perform { }
29
+ end.to raise_error(Joblin::Uniqueness::CouldNotLockError)
30
+ end
31
+ end
32
+ end
33
+ end