canvas_sync 0.16.5 → 0.17.0.beta1

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 (80) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +49 -137
  3. data/app/models/canvas_sync/sync_batch.rb +5 -0
  4. data/db/migrate/20201018210836_create_canvas_sync_sync_batches.rb +11 -0
  5. data/lib/canvas_sync/importers/bulk_importer.rb +4 -7
  6. data/lib/canvas_sync/job.rb +4 -10
  7. data/lib/canvas_sync/job_batches/batch.rb +399 -0
  8. data/lib/canvas_sync/job_batches/batch_aware_job.rb +62 -0
  9. data/lib/canvas_sync/job_batches/callback.rb +153 -0
  10. data/lib/canvas_sync/job_batches/chain_builder.rb +203 -0
  11. data/lib/canvas_sync/job_batches/context_hash.rb +147 -0
  12. data/lib/canvas_sync/job_batches/jobs/base_job.rb +7 -0
  13. data/lib/canvas_sync/job_batches/jobs/concurrent_batch_job.rb +18 -0
  14. data/lib/canvas_sync/job_batches/jobs/serial_batch_job.rb +73 -0
  15. data/lib/canvas_sync/job_batches/sidekiq.rb +91 -0
  16. data/lib/canvas_sync/job_batches/status.rb +63 -0
  17. data/lib/canvas_sync/jobs/begin_sync_chain_job.rb +34 -0
  18. data/lib/canvas_sync/jobs/report_checker.rb +3 -6
  19. data/lib/canvas_sync/jobs/report_processor_job.rb +2 -5
  20. data/lib/canvas_sync/jobs/report_starter.rb +28 -20
  21. data/lib/canvas_sync/jobs/sync_accounts_job.rb +3 -5
  22. data/lib/canvas_sync/jobs/sync_admins_job.rb +2 -4
  23. data/lib/canvas_sync/jobs/sync_assignment_groups_job.rb +2 -4
  24. data/lib/canvas_sync/jobs/sync_assignments_job.rb +2 -4
  25. data/lib/canvas_sync/jobs/sync_context_module_items_job.rb +2 -4
  26. data/lib/canvas_sync/jobs/sync_context_modules_job.rb +2 -4
  27. data/lib/canvas_sync/jobs/sync_provisioning_report_job.rb +4 -31
  28. data/lib/canvas_sync/jobs/sync_roles_job.rb +2 -5
  29. data/lib/canvas_sync/jobs/sync_simple_table_job.rb +11 -32
  30. data/lib/canvas_sync/jobs/sync_submissions_job.rb +2 -4
  31. data/lib/canvas_sync/jobs/sync_terms_job.rb +22 -7
  32. data/lib/canvas_sync/processors/assignment_groups_processor.rb +2 -3
  33. data/lib/canvas_sync/processors/assignments_processor.rb +2 -3
  34. data/lib/canvas_sync/processors/context_module_items_processor.rb +2 -3
  35. data/lib/canvas_sync/processors/context_modules_processor.rb +2 -3
  36. data/lib/canvas_sync/processors/normal_processor.rb +1 -2
  37. data/lib/canvas_sync/processors/provisioning_report_processor.rb +2 -10
  38. data/lib/canvas_sync/processors/submissions_processor.rb +2 -3
  39. data/lib/canvas_sync/version.rb +1 -1
  40. data/lib/canvas_sync.rb +34 -97
  41. data/spec/canvas_sync/canvas_sync_spec.rb +126 -153
  42. data/spec/canvas_sync/jobs/job_spec.rb +9 -17
  43. data/spec/canvas_sync/jobs/report_checker_spec.rb +1 -3
  44. data/spec/canvas_sync/jobs/report_processor_job_spec.rb +0 -3
  45. data/spec/canvas_sync/jobs/report_starter_spec.rb +19 -28
  46. data/spec/canvas_sync/jobs/sync_admins_job_spec.rb +1 -4
  47. data/spec/canvas_sync/jobs/sync_assignment_groups_job_spec.rb +2 -1
  48. data/spec/canvas_sync/jobs/sync_assignments_job_spec.rb +3 -2
  49. data/spec/canvas_sync/jobs/sync_context_module_items_job_spec.rb +3 -2
  50. data/spec/canvas_sync/jobs/sync_context_modules_job_spec.rb +3 -2
  51. data/spec/canvas_sync/jobs/sync_provisioning_report_job_spec.rb +3 -35
  52. data/spec/canvas_sync/jobs/sync_roles_job_spec.rb +1 -4
  53. data/spec/canvas_sync/jobs/sync_simple_table_job_spec.rb +5 -12
  54. data/spec/canvas_sync/jobs/sync_submissions_job_spec.rb +2 -1
  55. data/spec/canvas_sync/jobs/sync_terms_job_spec.rb +1 -4
  56. data/spec/dummy/config/environments/test.rb +2 -0
  57. data/spec/dummy/db/schema.rb +9 -1
  58. data/spec/job_batching/batch_aware_job_spec.rb +100 -0
  59. data/spec/job_batching/batch_spec.rb +363 -0
  60. data/spec/job_batching/callback_spec.rb +38 -0
  61. data/spec/job_batching/flow_spec.rb +91 -0
  62. data/spec/job_batching/integration/integration.rb +57 -0
  63. data/spec/job_batching/integration/nested.rb +88 -0
  64. data/spec/job_batching/integration/simple.rb +47 -0
  65. data/spec/job_batching/integration/workflow.rb +134 -0
  66. data/spec/job_batching/integration_helper.rb +48 -0
  67. data/spec/job_batching/sidekiq_spec.rb +124 -0
  68. data/spec/job_batching/status_spec.rb +92 -0
  69. data/spec/job_batching/support/base_job.rb +14 -0
  70. data/spec/job_batching/support/sample_callback.rb +2 -0
  71. data/spec/spec_helper.rb +10 -0
  72. metadata +91 -23
  73. data/lib/canvas_sync/job_chain.rb +0 -102
  74. data/lib/canvas_sync/jobs/fork_gather.rb +0 -74
  75. data/spec/canvas_sync/jobs/fork_gather_spec.rb +0 -73
  76. data/spec/dummy/db/test.sqlite3 +0 -0
  77. data/spec/dummy/log/development.log +0 -1248
  78. data/spec/dummy/log/test.log +0 -43258
  79. data/spec/support/fixtures/reports/provisioning_csv_unzipped/courses.csv +0 -3
  80. data/spec/support/fixtures/reports/provisioning_csv_unzipped/users.csv +0 -4
@@ -0,0 +1,363 @@
1
+ require 'spec_helper'
2
+
3
+ class TestWorker < BatchTestJobBase
4
+ def perform
5
+ end
6
+ end
7
+
8
+ RSpec.describe CanvasSync::JobBatches::Batch do
9
+ describe '#initialize' do
10
+ subject { described_class }
11
+
12
+ it 'creates bid when called without it' do
13
+ expect(subject.new.bid).not_to be_nil
14
+ end
15
+
16
+ it 'reuses bid when called with it' do
17
+ batch = subject.new('dayPO5KxuRXXxw')
18
+ expect(batch.bid).to eq('dayPO5KxuRXXxw')
19
+ end
20
+ end
21
+
22
+ describe '#description' do
23
+ let(:description) { 'custom description' }
24
+ before do
25
+ subject.description = description
26
+ subject.jobs { }
27
+ end
28
+
29
+ it 'sets descriptions' do
30
+ expect(subject.description).to eq(description)
31
+ end
32
+
33
+ it 'persists description' do
34
+ expect(CanvasSync::JobBatches::Batch.redis { |r| r.hget("BID-#{subject.bid}", 'description') })
35
+ .to eq(description)
36
+ end
37
+ end
38
+
39
+ describe '#callback_queue' do
40
+ let(:callback_queue) { 'custom_queue' }
41
+ before do
42
+ subject.callback_queue = callback_queue
43
+ subject.jobs { }
44
+ end
45
+
46
+ it 'sets callback_queue' do
47
+ expect(subject.callback_queue).to eq(callback_queue)
48
+ end
49
+
50
+ it 'persists callback_queue' do
51
+ expect(CanvasSync::JobBatches::Batch.redis { |r| r.hget("BID-#{subject.bid}", 'callback_queue') })
52
+ .to eq(callback_queue)
53
+ end
54
+ end
55
+
56
+ describe '#jobs' do
57
+ it 'throws error if no block given' do
58
+ expect { subject.jobs }.to raise_error CanvasSync::JobBatches::Batch::NoBlockGivenError
59
+ end
60
+
61
+ it 'increments to_process (when started)'
62
+
63
+ it 'decrements to_process (when finished)'
64
+ # it 'calls process_successful_job to wait for block to finish' do
65
+ # batch = CanvasSync::JobBatches::Batch.new
66
+ # expect(CanvasSync::JobBatches::Batch).to receive(:process_successful_job).with(batch.bid)
67
+ # batch.jobs {}
68
+ # end
69
+
70
+ it 'sets Thread.current bid' do
71
+ batch = CanvasSync::JobBatches::Batch.new
72
+ batch.jobs do
73
+ expect(Thread.current[:batch]).to eq(batch)
74
+ end
75
+ end
76
+ end
77
+
78
+ describe '#invalidate_all' do
79
+ class InvalidatableJob < BatchTestJobBase
80
+ def perform
81
+ return unless valid_within_batch?
82
+ was_performed
83
+ end
84
+
85
+ def was_performed; end
86
+ end
87
+
88
+ it 'marks batch in redis as invalidated' do
89
+ batch = CanvasSync::JobBatches::Batch.new
90
+ job = InvalidatableJob.new
91
+ allow(job).to receive(:was_performed)
92
+
93
+ batch.invalidate_all
94
+ batch.jobs { job.perform }
95
+
96
+ expect(job).not_to have_received(:was_performed)
97
+ end
98
+
99
+ context 'nested batches' do
100
+ let(:batch_parent) { CanvasSync::JobBatches::Batch.new }
101
+ let(:batch_child_1) { CanvasSync::JobBatches::Batch.new }
102
+ let(:batch_child_2) { CanvasSync::JobBatches::Batch.new }
103
+ let(:job_of_parent) { InvalidatableJob.new }
104
+ let(:job_of_child_1) { InvalidatableJob.new }
105
+ let(:job_of_child_2) { InvalidatableJob.new }
106
+
107
+ before do
108
+ allow(job_of_parent).to receive(:was_performed)
109
+ allow(job_of_child_1).to receive(:was_performed)
110
+ allow(job_of_child_2).to receive(:was_performed)
111
+ end
112
+
113
+ it 'invalidates all job if parent batch is marked as invalidated' do
114
+ batch_parent.invalidate_all
115
+ batch_parent.jobs do
116
+ [
117
+ job_of_parent.perform,
118
+ batch_child_1.jobs do
119
+ [
120
+ job_of_child_1.perform,
121
+ batch_child_2.jobs { job_of_child_2.perform }
122
+ ]
123
+ end
124
+ ]
125
+ end
126
+
127
+ expect(job_of_parent).not_to have_received(:was_performed)
128
+ expect(job_of_child_1).not_to have_received(:was_performed)
129
+ expect(job_of_child_2).not_to have_received(:was_performed)
130
+ end
131
+
132
+ it 'invalidates only requested batch' do
133
+ batch_child_2.invalidate_all
134
+ batch_parent.jobs do
135
+ [
136
+ job_of_parent.perform,
137
+ batch_child_1.jobs do
138
+ [
139
+ job_of_child_1.perform,
140
+ batch_child_2.jobs { job_of_child_2.perform }
141
+ ]
142
+ end
143
+ ]
144
+ end
145
+
146
+ expect(job_of_parent).to have_received(:was_performed)
147
+ expect(job_of_child_1).to have_received(:was_performed)
148
+ expect(job_of_child_2).not_to have_received(:was_performed)
149
+ end
150
+ end
151
+ end
152
+
153
+ describe '#process_failed_job' do
154
+ let(:batch) { CanvasSync::JobBatches::Batch.new }
155
+ let(:bid) { batch.bid }
156
+ let(:jid) { 'ABCD' }
157
+ before { CanvasSync::JobBatches::Batch.redis { |r| r.hset("BID-#{bid}", 'pending', 1) } }
158
+
159
+ context 'complete' do
160
+ let(:failed_jid) { 'xxx' }
161
+
162
+ it 'tries to call complete callback' do
163
+ expect(CanvasSync::JobBatches::Batch).to receive(:enqueue_callbacks).with(:complete, bid)
164
+ CanvasSync::JobBatches::Batch.process_failed_job(bid, failed_jid)
165
+ end
166
+
167
+ it 'add job to failed list' do
168
+ CanvasSync::JobBatches::Batch.process_failed_job(bid, 'failed-job-id')
169
+ CanvasSync::JobBatches::Batch.process_failed_job(bid, failed_jid)
170
+ failed = CanvasSync::JobBatches::Batch.redis { |r| r.smembers("BID-#{bid}-failed") }
171
+ expect(failed).to eq(['xxx', 'failed-job-id'])
172
+ end
173
+ end
174
+ end
175
+
176
+ describe '#process_successful_job' do
177
+ let(:batch) { CanvasSync::JobBatches::Batch.new }
178
+ let(:bid) { batch.bid }
179
+ let(:jid) { 'ABCD' }
180
+ before { CanvasSync::JobBatches::Batch.redis { |r| r.hset("BID-#{bid}", 'pending', 1) } }
181
+
182
+ context 'complete' do
183
+ before { batch.on(:complete, Object) }
184
+ # before { batch.increment_job_queue(bid) }
185
+ before { batch.jobs do TestWorker.perform_async end }
186
+ before { CanvasSync::JobBatches::Batch.process_failed_job(bid, 'failed-job-id') }
187
+
188
+ it 'tries to call complete callback' do
189
+ expect(CanvasSync::JobBatches::Batch).to receive(:enqueue_callbacks).with(:complete, bid)
190
+ CanvasSync::JobBatches::Batch.process_successful_job(bid, 'failed-job-id')
191
+ end
192
+ end
193
+
194
+ context 'success' do
195
+ before { batch.on(:complete, Object) }
196
+ it 'tries to call complete callback' do
197
+ expect(CanvasSync::JobBatches::Batch).to receive(:enqueue_callbacks).with(:complete, bid).ordered
198
+ expect(CanvasSync::JobBatches::Batch).to receive(:enqueue_callbacks).with(:success, bid).ordered
199
+ CanvasSync::JobBatches::Batch.process_successful_job(bid, jid)
200
+ end
201
+
202
+ it 'cleanups redis key' do
203
+ CanvasSync::JobBatches::Batch.process_successful_job(bid, jid)
204
+ expect(CanvasSync::JobBatches::Batch.redis { |r| r.get("BID-#{bid}-pending") }.to_i).to eq(0)
205
+ end
206
+ end
207
+ end
208
+
209
+ describe '#increment_job_queue' do
210
+ let(:bid) { 'BID' }
211
+ let(:batch) { CanvasSync::JobBatches::Batch.new }
212
+
213
+ it 'increments pending' do
214
+ batch.jobs do TestWorker.perform_async end
215
+ pending = CanvasSync::JobBatches::Batch.redis { |r| r.hget("BID-#{batch.bid}", 'pending') }
216
+ expect(pending).to eq('1')
217
+ end
218
+
219
+ it 'increments total' do
220
+ batch.jobs do TestWorker.perform_async end
221
+ total = CanvasSync::JobBatches::Batch.redis { |r| r.hget("BID-#{batch.bid}", 'total') }
222
+ expect(total).to eq('1')
223
+ end
224
+ end
225
+
226
+ describe '#enqueue_callbacks' do
227
+ let(:callback) { double('callback') }
228
+ let(:event) { :complete }
229
+
230
+ context 'on :success' do
231
+ let(:event) { :success }
232
+ context 'when no callbacks are defined' do
233
+ it 'clears redis keys' do
234
+ batch = CanvasSync::JobBatches::Batch.new
235
+ expect(CanvasSync::JobBatches::Batch).to receive(:cleanup_redis).with(batch.bid)
236
+ CanvasSync::JobBatches::Batch.enqueue_callbacks(event, batch.bid)
237
+ end
238
+ end
239
+ end
240
+
241
+ context 'when already called' do
242
+ it 'returns and does not enqueue callbacks' do
243
+ batch = CanvasSync::JobBatches::Batch.new
244
+ batch.on(event, SampleCallback)
245
+ CanvasSync::JobBatches::Batch.redis { |r| r.hset("BID-#{batch.bid}", event, true) }
246
+
247
+ expect(batch).not_to receive(:push_callbacks)
248
+ CanvasSync::JobBatches::Batch.enqueue_callbacks(event, batch.bid)
249
+ end
250
+ end
251
+
252
+ context 'With ActiveJob Adapter' do
253
+ around(:all) do |block|
254
+ CanvasSync::JobBatches::Batch::Callback.send(:remove_const, :Worker)
255
+ CanvasSync::JobBatches::Batch::Callback.const_set(:Worker, CanvasSync::JobBatches::Batch::Callback::ActiveJobCallbackWorker)
256
+ block.run
257
+ end
258
+
259
+ context 'when not yet called' do
260
+ context 'when there is no callback' do
261
+ it 'it returns' do
262
+ batch = CanvasSync::JobBatches::Batch.new
263
+
264
+ expect(batch).not_to receive(:push_callbacks)
265
+ CanvasSync::JobBatches::Batch.enqueue_callbacks(event, batch.bid)
266
+ end
267
+ end
268
+
269
+ context 'when callback defined' do
270
+ let(:opts) { { 'a' => 'b' } }
271
+
272
+ it 'calls it passing options' do
273
+ batch = CanvasSync::JobBatches::Batch.new
274
+ batch.on(event, SampleCallback, opts)
275
+
276
+ CanvasSync::JobBatches::Batch.enqueue_callbacks(event, batch.bid)
277
+
278
+ expect(CanvasSync::JobBatches::Batch::Callback::Worker).to have_been_enqueued.with(
279
+ 'SampleCallback', event.to_s, opts, batch.bid, nil
280
+ )
281
+ end
282
+ end
283
+
284
+ context 'when multiple callbacks are defined' do
285
+ let(:opts) { { 'a' => 'b' } }
286
+ let(:opts2) { { 'b' => 'a' } }
287
+
288
+ it 'enqueues each callback passing their options' do
289
+ batch = CanvasSync::JobBatches::Batch.new
290
+ batch.on(event, SampleCallback, opts)
291
+ batch.on(event, SampleCallback2, opts2)
292
+
293
+ CanvasSync::JobBatches::Batch.enqueue_callbacks(event, batch.bid)
294
+ expect(CanvasSync::JobBatches::Batch::Callback::Worker).to have_been_enqueued.with(
295
+ 'SampleCallback2', event.to_s, opts2, batch.bid, nil
296
+ )
297
+ expect(CanvasSync::JobBatches::Batch::Callback::Worker).to have_been_enqueued.with(
298
+ 'SampleCallback', event.to_s, opts, batch.bid, nil
299
+ )
300
+ end
301
+ end
302
+ end
303
+ end
304
+
305
+ context 'With Sidekiq Adapter' do
306
+ around(:all) do |block|
307
+ CanvasSync::JobBatches::Batch::Callback.send(:remove_const, :Worker)
308
+ CanvasSync::JobBatches::Batch::Callback.const_set(:Worker, CanvasSync::JobBatches::Batch::Callback::SidekiqCallbackWorker)
309
+ block.run
310
+ end
311
+
312
+ context 'when not yet called' do
313
+ context 'when there is no callback' do
314
+ it 'it returns' do
315
+ batch = CanvasSync::JobBatches::Batch.new
316
+
317
+ expect(batch).not_to receive(:push_callbacks)
318
+ CanvasSync::JobBatches::Batch.enqueue_callbacks(event, batch.bid)
319
+ end
320
+ end
321
+
322
+ context 'when callback defined' do
323
+ let(:opts) { { 'a' => 'b' } }
324
+
325
+ it 'calls it passing options' do
326
+ batch = CanvasSync::JobBatches::Batch.new
327
+ batch.on(event, SampleCallback, opts)
328
+
329
+ expect(Sidekiq::Client).to receive(:push_bulk).with(
330
+ 'class' => Sidekiq::Batch::Callback::Worker,
331
+ 'args' => [['SampleCallback', event.to_s, opts, batch.bid, nil]],
332
+ 'queue' => 'default'
333
+ )
334
+
335
+ CanvasSync::JobBatches::Batch.enqueue_callbacks(event, batch.bid)
336
+ end
337
+ end
338
+
339
+ context 'when multiple callbacks are defined' do
340
+ let(:opts) { { 'a' => 'b' } }
341
+ let(:opts2) { { 'b' => 'a' } }
342
+
343
+ it 'enqueues each callback passing their options' do
344
+ batch = CanvasSync::JobBatches::Batch.new
345
+ batch.on(event, SampleCallback, opts)
346
+ batch.on(event, SampleCallback2, opts2)
347
+
348
+ expect(Sidekiq::Client).to receive(:push_bulk).with(
349
+ 'class' => Sidekiq::Batch::Callback::Worker,
350
+ 'args' => [
351
+ ['SampleCallback2', event.to_s, opts2, batch.bid, nil],
352
+ ['SampleCallback', event.to_s, opts, batch.bid, nil]
353
+ ],
354
+ 'queue' => 'default'
355
+ )
356
+
357
+ CanvasSync::JobBatches::Batch.enqueue_callbacks(event, batch.bid)
358
+ end
359
+ end
360
+ end
361
+ end
362
+ end
363
+ end
@@ -0,0 +1,38 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe CanvasSync::JobBatches::Batch::Callback::Worker do
4
+ describe '#perform' do
5
+ it 'does not do anything if it cannot find the callback class' do
6
+ subject.perform('SampleCallback', 'complete', {}, 'ABCD', 'EFGH')
7
+ end
8
+
9
+ it 'does not do anything if event is different from complete or success' do
10
+ expect(SampleCallback).not_to receive(:new)
11
+ subject.perform('SampleCallback', 'ups', {}, 'ABCD', 'EFGH')
12
+ end
13
+
14
+ it 'calls on_success if defined' do
15
+ callback_instance = double('SampleCallback', on_success: true)
16
+ expect(SampleCallback).to receive(:new).and_return(callback_instance)
17
+ expect(callback_instance).to receive(:on_success)
18
+ .with(instance_of(CanvasSync::JobBatches::Batch::Status), {})
19
+ subject.perform('SampleCallback', 'success', {}, 'ABCD', 'EFGH')
20
+ end
21
+
22
+ it 'calls on_complete if defined' do
23
+ callback_instance = double('SampleCallback')
24
+ expect(SampleCallback).to receive(:new).and_return(callback_instance)
25
+ expect(callback_instance).to receive(:on_complete)
26
+ .with(instance_of(CanvasSync::JobBatches::Batch::Status), {})
27
+ subject.perform('SampleCallback', 'complete', {}, 'ABCD', 'EFGH')
28
+ end
29
+
30
+ it 'calls specific callback if defined' do
31
+ callback_instance = double('SampleCallback')
32
+ expect(SampleCallback).to receive(:new).and_return(callback_instance)
33
+ expect(callback_instance).to receive(:sample_method)
34
+ .with(instance_of(CanvasSync::JobBatches::Batch::Status), {})
35
+ subject.perform('SampleCallback#sample_method', 'complete', {}, 'ABCD', 'EFGH')
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,91 @@
1
+ require 'spec_helper'
2
+
3
+ class WorkerA < BatchTestJobBase
4
+ def perform
5
+ puts 'A'
6
+ end
7
+ end
8
+
9
+ class WorkerB < BatchTestJobBase
10
+ def perform
11
+ puts 'B'
12
+ end
13
+ end
14
+
15
+ class WorkerC < BatchTestJobBase
16
+ def perform
17
+ puts 'C'
18
+ end
19
+ end
20
+
21
+ RSpec.describe 'Batch flow' do
22
+ context 'when handling a batch' do
23
+ let(:batch) { CanvasSync::JobBatches::Batch.new }
24
+ before { batch.on(:complete, SampleCallback, :id => 42) }
25
+ before { batch.description = 'describing the batch' }
26
+ let(:status) { CanvasSync::JobBatches::Batch::Status.new(batch.bid) }
27
+ let(:jids) { batch.jobs do 3.times do TestWorker.perform_async end end }
28
+ let(:queue) { Sidekiq::Queue.new }
29
+
30
+ it 'correctly initializes' do
31
+ expect(jids.size).to eq(3)
32
+
33
+ expect(batch.bid).not_to be_nil
34
+ expect(batch.description).to eq('describing the batch')
35
+
36
+ batch.jobs {}
37
+
38
+ expect(status.total).to eq(3)
39
+ expect(status.pending).to eq(3)
40
+ expect(status.failures).to eq(0)
41
+ expect(status.complete?).to be false
42
+ expect(status.created_at).not_to be_nil
43
+ expect(status.bid).to eq(batch.bid)
44
+ end
45
+
46
+ it 'handles an empty batch' do
47
+ batch = CanvasSync::JobBatches::Batch.new
48
+ jids = batch.jobs do nil end
49
+ expect(jids.size).to eq(0)
50
+ end
51
+ end
52
+
53
+ context 'when handling a nested batch' do
54
+ let(:batchA) { CanvasSync::JobBatches::Batch.new }
55
+ let(:batchB) { CanvasSync::JobBatches::Batch.new }
56
+ let(:batchC) { CanvasSync::JobBatches::Batch.new(batchA.bid) }
57
+ let(:batchD) { CanvasSync::JobBatches::Batch.new }
58
+ let(:jids) { [] }
59
+ let(:parent) { batchA.bid }
60
+ let(:children) { [] }
61
+
62
+ it 'handles a basic nested batch' do
63
+ batchA.jobs do
64
+ jids << WorkerA.perform_async
65
+ batchB.jobs do
66
+ jids << WorkerB.perform_async
67
+ end
68
+ jids << WorkerA.perform_async
69
+ children << batchB.bid
70
+ end
71
+
72
+ batchC.jobs do
73
+ batchD.jobs do
74
+ jids << WorkerC.perform_async
75
+ end
76
+ children << batchD.bid
77
+ end
78
+
79
+ expect(jids.size).to eq(4)
80
+ expect(CanvasSync::JobBatches::Batch::Status.new(parent).child_count).to eq(2)
81
+ children.each do |kid|
82
+ status = CanvasSync::JobBatches::Batch::Status.new(kid)
83
+ expect(status.child_count).to eq(0)
84
+ expect(status.pending).to eq(1)
85
+ expect(status.parent_bid).to eq(parent)
86
+ end
87
+
88
+ end
89
+
90
+ end
91
+ end
@@ -0,0 +1,57 @@
1
+ require 'integration_helper'
2
+
3
+ # Simple test of adding jobs to the current batch
4
+ # Batches:
5
+ # - Overall (TestWoker) + Another worker
6
+
7
+ class AnotherWorker
8
+ include Sidekiq::Worker
9
+
10
+ def perform
11
+ Sidekiq.logger.info "Another Worker"
12
+ end
13
+ end
14
+
15
+ class TestWorker
16
+ include Sidekiq::Worker
17
+
18
+ def perform
19
+ Sidekiq.logger.info "Test Worker"
20
+ if bid
21
+ batch.jobs do
22
+ AnotherWorker.perform_async
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ class MyCallback
29
+ def on_success(status, options)
30
+ Sidekiq.logger.info "Success #{options} #{status.data}"
31
+ end
32
+ alias_method :multi, :on_success
33
+
34
+ def on_complete(status, options)
35
+ Sidekiq.logger.info "Complete #{options} #{status.data}"
36
+ end
37
+ end
38
+
39
+ batch = CanvasSync::JobBatches::Batch.new
40
+ batch.description = 'Test batch'
41
+ batch.callback_queue = :default
42
+ batch.on(:success, 'MyCallback#on_success', to: 'success@gmail.com')
43
+ batch.on(:success, 'MyCallback#multi', to: 'success@gmail.com')
44
+ batch.on(:complete, MyCallback, to: 'complete@gmail.com')
45
+
46
+ batch.jobs do
47
+ 10.times do
48
+ TestWorker.perform_async
49
+ end
50
+ end
51
+ puts CanvasSync::JobBatches::Batch::Status.new(batch.bid).data
52
+
53
+ dump_redis_keys
54
+
55
+ Sidekiq::Worker.drain_all
56
+
57
+ dump_redis_keys
@@ -0,0 +1,88 @@
1
+ require 'integration_helper'
2
+
3
+ # Tests deep nesting of batches
4
+ # Batches:
5
+ # - Overall (Worker 1)
6
+ # - Worker 2
7
+ # - Worker 3
8
+ # - Worker 4
9
+
10
+ class Worker1
11
+ include Sidekiq::Worker
12
+
13
+ def perform
14
+ Sidekiq.logger.info "Work1"
15
+ batch = CanvasSync::JobBatches::Batch.new
16
+ batch.on(:success, Worker2)
17
+ batch.jobs do
18
+ Worker2.perform_async
19
+ end
20
+ end
21
+ end
22
+
23
+ class Worker2
24
+ include Sidekiq::Worker
25
+
26
+ def perform
27
+ Sidekiq.logger.info "Work2"
28
+ batch = CanvasSync::JobBatches::Batch.new
29
+ batch.on(:success, Worker3)
30
+ batch.jobs do
31
+ Worker3.perform_async
32
+ end
33
+ end
34
+
35
+ def on_success status, opts
36
+ Sidekiq.logger.info "Worker 2 Success"
37
+ end
38
+ end
39
+
40
+ class Worker3
41
+ include Sidekiq::Worker
42
+
43
+ def perform
44
+ Sidekiq.logger.info "Work3"
45
+ batch = CanvasSync::JobBatches::Batch.new
46
+ batch.on(:success, Worker4)
47
+ batch.jobs do
48
+ Worker4.perform_async
49
+ end
50
+ end
51
+
52
+ def on_success status, opts
53
+ Sidekiq.logger.info "Worker 3 Success"
54
+ end
55
+ end
56
+
57
+ class Worker4
58
+ include Sidekiq::Worker
59
+
60
+ def perform
61
+ Sidekiq.logger.info "Work4"
62
+ end
63
+
64
+ def on_success status, opts
65
+ Sidekiq.logger.info "Worker 4 Success"
66
+ end
67
+ end
68
+
69
+
70
+ class SomeClass
71
+ def on_complete(status, options)
72
+ Sidekiq.logger.info "Overall Complete #{options} #{status.data}"
73
+ end
74
+ def on_success(status, options)
75
+ Sidekiq.logger.info "Overall Success #{options} #{status.data}"
76
+ end
77
+ end
78
+ batch = CanvasSync::JobBatches::Batch.new
79
+ batch.on(:success, SomeClass, 'uid' => 3)
80
+ batch.on(:complete, SomeClass, 'uid' => 3)
81
+ batch.jobs do
82
+ Worker1.perform_async
83
+ end
84
+
85
+ puts "Overall bid #{batch.bid}"
86
+
87
+ output, keys = process_tests
88
+ overall_tests output, keys
@@ -0,0 +1,47 @@
1
+ require 'integration_helper'
2
+
3
+ # Simple nested batch without callbacks
4
+ # Batches:
5
+ # - Overall (Worker1)
6
+ # - Worker2
7
+
8
+ class Worker1
9
+ include Sidekiq::Worker
10
+
11
+ def perform
12
+ Sidekiq.logger.info "Work1"
13
+ batch = CanvasSync::JobBatches::Batch.new
14
+ batch.jobs do
15
+ Worker2.perform_async
16
+ end
17
+ end
18
+ end
19
+
20
+ class Worker2
21
+ include Sidekiq::Worker
22
+
23
+ def perform
24
+ Sidekiq.logger.info "Work2"
25
+ end
26
+ end
27
+
28
+ class SomeClass
29
+ def on_complete(status, options)
30
+ Sidekiq.logger.info "Overall Complete #{options} #{status.data}"
31
+ end
32
+ def on_success(status, options)
33
+ Sidekiq.logger.info "Overall Success #{options} #{status.data}"
34
+ end
35
+ end
36
+
37
+ batch = CanvasSync::JobBatches::Batch.new
38
+ batch.on(:success, SomeClass)
39
+ batch.on(:complete, SomeClass)
40
+ batch.jobs do
41
+ Worker1.perform_async
42
+ end
43
+
44
+ puts "Overall bid #{batch.bid}"
45
+
46
+ output, keys = process_tests
47
+ overall_tests output, keys