taskinator 0.0.18 → 0.2.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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +25 -0
  3. data/Gemfile.lock +28 -28
  4. data/README.md +29 -0
  5. data/Rakefile +5 -0
  6. data/bin/console +5 -0
  7. data/lib/taskinator/create_process_worker.rb +5 -2
  8. data/lib/taskinator/definition/builder.rb +12 -7
  9. data/lib/taskinator/definition.rb +36 -28
  10. data/lib/taskinator/executor.rb +4 -4
  11. data/lib/taskinator/job_worker.rb +5 -10
  12. data/lib/taskinator/logger.rb +1 -0
  13. data/lib/taskinator/persistence.rb +74 -36
  14. data/lib/taskinator/process.rb +75 -49
  15. data/lib/taskinator/queues/delayed_job.rb +1 -11
  16. data/lib/taskinator/queues/resque.rb +0 -15
  17. data/lib/taskinator/queues/sidekiq.rb +1 -14
  18. data/lib/taskinator/queues.rb +0 -5
  19. data/lib/taskinator/redis_connection.rb +1 -0
  20. data/lib/taskinator/task.rb +57 -57
  21. data/lib/taskinator/task_worker.rb +1 -8
  22. data/lib/taskinator/version.rb +1 -1
  23. data/lib/taskinator.rb +7 -6
  24. data/spec/examples/queue_adapter_examples.rb +0 -10
  25. data/spec/support/test_definition.rb +4 -0
  26. data/spec/support/test_flow.rb +2 -0
  27. data/spec/support/test_flows.rb +54 -3
  28. data/spec/support/test_queue.rb +41 -6
  29. data/spec/taskinator/create_process_worker_spec.rb +12 -3
  30. data/spec/taskinator/definition/builder_spec.rb +39 -9
  31. data/spec/taskinator/definition_spec.rb +19 -27
  32. data/spec/taskinator/executor_spec.rb +19 -1
  33. data/spec/taskinator/job_worker_spec.rb +0 -11
  34. data/spec/taskinator/persistence_spec.rb +122 -7
  35. data/spec/taskinator/process_spec.rb +39 -23
  36. data/spec/taskinator/queues/delayed_job_spec.rb +1 -19
  37. data/spec/taskinator/queues/resque_spec.rb +1 -22
  38. data/spec/taskinator/queues/sidekiq_spec.rb +1 -20
  39. data/spec/taskinator/task_spec.rb +160 -52
  40. data/spec/taskinator/task_worker_spec.rb +0 -17
  41. data/spec/taskinator/test_flows_spec.rb +43 -0
  42. metadata +2 -5
  43. data/lib/taskinator/process_worker.rb +0 -21
  44. data/spec/taskinator/process_worker_spec.rb +0 -51
@@ -10,13 +10,22 @@ module TestFlows
10
10
 
11
11
  module Support
12
12
 
13
- def iterator(task_count)
13
+ def iterator(task_count, *args)
14
14
  task_count.times do |i|
15
- yield i
15
+ yield i, *args
16
16
  end
17
17
  end
18
18
 
19
19
  def do_task(*args)
20
+ Taskinator.logger.info(">>> Executing task do_task [#{uuid}]...")
21
+ end
22
+
23
+ # just create lots of these, so it's easy to see which task
24
+ # corresponds with each method when debugging specs
25
+ 20.times do |i|
26
+ define_method "task_#{i}" do |*args|
27
+ Taskinator.logger.info(">>> Executing task #{__method__} [#{uuid}]...")
28
+ end
20
29
  end
21
30
 
22
31
  end
@@ -27,7 +36,7 @@ module TestFlows
27
36
 
28
37
  define_process :task_count do
29
38
  for_each :iterator do
30
- task :do_task
39
+ task :do_task, :queue => :foo
31
40
  end
32
41
  end
33
42
 
@@ -83,4 +92,46 @@ module TestFlows
83
92
 
84
93
  end
85
94
 
95
+ module EmptySequentialProcessTest
96
+ extend Taskinator::Definition
97
+ include Support
98
+
99
+ define_process do
100
+
101
+ task :task_0
102
+
103
+ sequential do
104
+ # NB: empty!
105
+ end
106
+
107
+ sequential do
108
+ task :task_1
109
+ end
110
+
111
+ task :task_2
112
+
113
+ end
114
+ end
115
+
116
+ module EmptyConcurrentProcessTest
117
+ extend Taskinator::Definition
118
+ include Support
119
+
120
+ define_process do
121
+
122
+ task :task_0
123
+
124
+ concurrent do
125
+ # NB: empty!
126
+ end
127
+
128
+ concurrent do
129
+ task :task_1
130
+ end
131
+
132
+ task :task_2
133
+
134
+ end
135
+ end
136
+
86
137
  end
@@ -5,10 +5,13 @@ module Taskinator
5
5
  TestQueueAdapter.new
6
6
  end
7
7
 
8
+ def self.create_test_queue_worker_adapter(config={})
9
+ QueueWorkerAdapter.new
10
+ end
11
+
8
12
  class TestQueueAdapter
9
13
 
10
14
  attr_reader :creates
11
- attr_reader :processes
12
15
  attr_reader :tasks
13
16
  attr_reader :jobs
14
17
 
@@ -18,7 +21,6 @@ module Taskinator
18
21
 
19
22
  def clear
20
23
  @creates = []
21
- @processes = []
22
24
  @tasks = []
23
25
  @jobs = []
24
26
  end
@@ -27,10 +29,6 @@ module Taskinator
27
29
  @creates << [definition, uuid, args]
28
30
  end
29
31
 
30
- def enqueue_process(process)
31
- @processes << process
32
- end
33
-
34
32
  def enqueue_task(task)
35
33
  @tasks << task
36
34
  end
@@ -39,6 +37,43 @@ module Taskinator
39
37
  @jobs << job
40
38
  end
41
39
 
40
+ def empty?
41
+ @creates.empty? && @tasks.empty? && @jobs.empty?
42
+ end
43
+
42
44
  end
45
+
46
+ #
47
+ # this is a "synchronous" implementation for use in testing
48
+ #
49
+ class QueueWorkerAdapter < TestQueueAdapter
50
+
51
+ def enqueue_create_process(definition, uuid, args)
52
+ super
53
+ invoke do
54
+ Taskinator::CreateProcessWorker.new(definition.name, uuid, args).perform
55
+ end
56
+ end
57
+
58
+ def enqueue_task(task)
59
+ super
60
+ invoke do
61
+ Taskinator::TaskWorker.new(task.uuid).perform
62
+ end
63
+ end
64
+
65
+ def enqueue_job(job)
66
+ super
67
+ invoke do
68
+ Taskinator::JobWorker.new(job.uuid).perform
69
+ end
70
+ end
71
+
72
+ def invoke(&block)
73
+ block.call
74
+ end
75
+
76
+ end
77
+
43
78
  end
44
79
  end
@@ -13,12 +13,23 @@ describe Taskinator::CreateProcessWorker do
13
13
  expect(subject.definition).to eq(definition)
14
14
  }
15
15
 
16
+ it {
17
+ Taskinator::CreateProcessWorker.new(definition.name, uuid, Taskinator::Persistence.serialize(:foo => :bar))
18
+ expect(subject.definition).to eq(definition)
19
+ }
20
+
16
21
  it {
17
22
  MockDefinition.const_set(definition.name, definition)
18
23
  Taskinator::CreateProcessWorker.new("MockDefinition::#{definition.name}", uuid, Taskinator::Persistence.serialize(:foo => :bar))
19
24
  expect(subject.definition).to eq(definition)
20
25
  }
21
26
 
27
+ it {
28
+ expect {
29
+ Taskinator::CreateProcessWorker.new("NonExistent", uuid, Taskinator::Persistence.serialize(:foo => :bar))
30
+ }.to raise_error(NameError)
31
+ }
32
+
22
33
  it {
23
34
  expect(subject.uuid).to eq(uuid)
24
35
  }
@@ -30,7 +41,7 @@ describe Taskinator::CreateProcessWorker do
30
41
 
31
42
  describe "#perform" do
32
43
  it "should create the process" do
33
- expect(definition).to receive(:_create_process_).with(*args, :uuid => uuid).and_return(double('process', :enqueue! => nil))
44
+ expect(definition).to receive(:_create_process_).with(false, *args, :uuid => uuid).and_return(double('process', :enqueue! => nil))
34
45
  subject.perform
35
46
  end
36
47
 
@@ -39,6 +50,4 @@ describe Taskinator::CreateProcessWorker do
39
50
  subject.perform
40
51
  end
41
52
  end
42
-
43
-
44
53
  end
@@ -15,7 +15,9 @@ describe Taskinator::Definition::Builder do
15
15
  Class.new(Taskinator::Process).new(definition)
16
16
  }
17
17
 
18
- let(:args) { [:arg1, :arg2, {:option => 1, :another => false}] }
18
+ let(:args) { [:arg1, :arg2] }
19
+ let(:builder_options) { {:option1 => 1, :another => false} }
20
+ let(:options) { { :bar => :baz } }
19
21
 
20
22
  let(:block) { SpecSupport::Block.new }
21
23
 
@@ -24,19 +26,19 @@ describe Taskinator::Definition::Builder do
24
26
  Proc.new {|*args| the_block.call }
25
27
  }
26
28
 
27
- subject { Taskinator::Definition::Builder.new(process, definition, *args) }
29
+ subject { Taskinator::Definition::Builder.new(process, definition, *[*args, builder_options]) }
28
30
 
29
31
  it "assign attributes" do
30
32
  expect(subject.process).to eq(process)
31
33
  expect(subject.definition).to eq(definition)
32
34
  expect(subject.args).to eq(args)
33
- expect(subject.options).to eq({:option => 1, :another => false})
35
+ expect(subject.builder_options).to eq(builder_options)
34
36
  end
35
37
 
36
38
  describe "#option?" do
37
- it "invokes supplied block for 'option' option" do
39
+ it "invokes supplied block for 'option1' option" do
38
40
  expect(block).to receive(:call)
39
- subject.option?(:option, &define_block)
41
+ subject.option?(:option1, &define_block)
40
42
  end
41
43
 
42
44
  it "does not invoke supplied block for 'another' option" do
@@ -67,6 +69,12 @@ describe Taskinator::Definition::Builder do
67
69
  subject.sequential
68
70
  }.to raise_error(ArgumentError)
69
71
  end
72
+
73
+ it "includes options" do
74
+ allow(block).to receive(:call)
75
+ expect(Taskinator::Process).to receive(:define_sequential_process_for).with(definition, options).and_call_original
76
+ subject.sequential(options, &define_block)
77
+ end
70
78
  end
71
79
 
72
80
  describe "#concurrent" do
@@ -86,6 +94,12 @@ describe Taskinator::Definition::Builder do
86
94
  subject.concurrent
87
95
  }.to raise_error(ArgumentError)
88
96
  end
97
+
98
+ it "includes options" do
99
+ allow(block).to receive(:call)
100
+ expect(Taskinator::Process).to receive(:define_concurrent_process_for).with(definition, Taskinator::CompleteOn::First, options).and_call_original
101
+ subject.concurrent(Taskinator::CompleteOn::First, options, &define_block)
102
+ end
89
103
  end
90
104
 
91
105
  describe "#for_each" do
@@ -146,7 +160,7 @@ describe Taskinator::Definition::Builder do
146
160
 
147
161
  describe "#task" do
148
162
  it "creates a task" do
149
- expect(Taskinator::Task).to receive(:define_step_task).with(process, :task_method, args, {})
163
+ expect(Taskinator::Task).to receive(:define_step_task).with(process, :task_method, args, builder_options)
150
164
  subject.task(:task_method)
151
165
  end
152
166
 
@@ -161,12 +175,17 @@ describe Taskinator::Definition::Builder do
161
175
  subject.task(:undefined)
162
176
  }.to raise_error(NoMethodError)
163
177
  end
178
+
179
+ it "includes options" do
180
+ expect(Taskinator::Task).to receive(:define_step_task).with(process, :task_method, args, builder_options.merge(options))
181
+ subject.task(:task_method, options)
182
+ end
164
183
  end
165
184
 
166
185
  describe "#job" do
167
186
  it "creates a job" do
168
187
  job = double('job', :perform => true)
169
- expect(Taskinator::Task).to receive(:define_job_task).with(process, job, args, {})
188
+ expect(Taskinator::Task).to receive(:define_job_task).with(process, job, args, builder_options)
170
189
  subject.job(job)
171
190
  end
172
191
 
@@ -182,6 +201,12 @@ describe Taskinator::Definition::Builder do
182
201
  subject.job(double('job', :methods => [], :instance_methods => []))
183
202
  }.to raise_error(ArgumentError)
184
203
  end
204
+
205
+ it "includes options" do
206
+ job = double('job', :perform => true)
207
+ expect(Taskinator::Task).to receive(:define_job_task).with(process, job, args, builder_options.merge(options))
208
+ subject.job(job, options)
209
+ end
185
210
  end
186
211
 
187
212
  describe "#sub_process" do
@@ -195,16 +220,21 @@ describe Taskinator::Definition::Builder do
195
220
  end
196
221
 
197
222
  it "creates a sub process" do
198
- expect(sub_definition).to receive(:create_sub_process).with(*args).and_call_original
223
+ expect(sub_definition).to receive(:create_sub_process).with(*args, builder_options).and_call_original
199
224
  subject.sub_process(sub_definition)
200
225
  end
201
226
 
202
227
  it "creates a sub process task" do
203
228
  sub_process = sub_definition.create_process(:argX, :argY, :argZ)
204
229
  allow(sub_definition).to receive(:create_sub_process) { sub_process }
205
- expect(Taskinator::Task).to receive(:define_sub_process_task).with(process, sub_process, {})
230
+ expect(Taskinator::Task).to receive(:define_sub_process_task).with(process, sub_process, builder_options)
206
231
  subject.sub_process(sub_definition)
207
232
  end
233
+
234
+ it "includes options" do
235
+ expect(sub_definition).to receive(:create_sub_process).with(*args, builder_options.merge(options)).and_call_original
236
+ subject.sub_process(sub_definition, options)
237
+ end
208
238
  end
209
239
 
210
240
  end
@@ -106,6 +106,17 @@ describe Taskinator::Definition do
106
106
  expect(subject.create_process).to be_a(Taskinator::Process)
107
107
  end
108
108
 
109
+ it "receives options" do
110
+ block = SpecSupport::Block.new
111
+ allow(block).to receive(:to_proc) {
112
+ Proc.new {|*args| }
113
+ }
114
+ subject.define_process(&block)
115
+
116
+ process = subject.create_process(:foo => :bar)
117
+ expect(process.options).to eq(:foo => :bar)
118
+ end
119
+
109
120
  it "invokes the given block in the context of a ProcessBuilder" do
110
121
  block = SpecSupport::Block.new
111
122
  expect(block).to receive(:call)
@@ -133,20 +144,24 @@ describe Taskinator::Definition do
133
144
 
134
145
  it "for create process" do
135
146
  instrumentation_block = SpecSupport::Block.new
136
- expect(instrumentation_block).to receive(:call)
147
+ expect(instrumentation_block).to receive(:call) do |*args|
148
+ expect(args.first).to eq('taskinator.process.created')
149
+ end
137
150
 
138
151
  # temporary subscription
139
- ActiveSupport::Notifications.subscribed(instrumentation_block, /create_process/) do
152
+ ActiveSupport::Notifications.subscribed(instrumentation_block, /taskinator.process.created/) do
140
153
  subject.create_process :foo
141
154
  end
142
155
  end
143
156
 
144
157
  it "for save process" do
145
158
  instrumentation_block = SpecSupport::Block.new
146
- expect(instrumentation_block).to receive(:call)
159
+ expect(instrumentation_block).to receive(:call) do |*args|
160
+ expect(args.first).to eq('taskinator.process.saved')
161
+ end
147
162
 
148
163
  # temporary subscription
149
- ActiveSupport::Notifications.subscribed(instrumentation_block, /save_process/) do
164
+ ActiveSupport::Notifications.subscribed(instrumentation_block, /taskinator.process.saved/) do
150
165
  subject.create_process :foo
151
166
  end
152
167
  end
@@ -170,7 +185,6 @@ describe Taskinator::Definition do
170
185
  process = subject.create_process_remotely
171
186
 
172
187
  expect(process).to_not be_nil
173
- expect(process.uuid).to_not be_nil
174
188
  end
175
189
 
176
190
  it "enqueues" do
@@ -184,28 +198,6 @@ describe Taskinator::Definition do
184
198
 
185
199
  subject.create_process_remotely
186
200
  end
187
-
188
- describe "reloading" do
189
- it "returns false if not persisted yet" do
190
- block = SpecSupport::Block.new
191
- allow(block).to receive(:to_proc) {
192
- Proc.new {|*args| }
193
- }
194
- subject.define_process(&block)
195
- process = subject.create_process_remotely
196
-
197
- expect(process.reload).to eq(false)
198
- end
199
-
200
- it "returns true if persisted" do
201
- definition = MockDefinition.create
202
- process = definition.create_process_remotely(:foo)
203
- definition._create_process_(:foo, :uuid => process.uuid).save
204
-
205
- expect(process.reload).to eq(true)
206
- end
207
- end
208
-
209
201
  end
210
202
 
211
203
  describe "#queue" do
@@ -8,7 +8,25 @@ describe Taskinator::Executor do
8
8
  end
9
9
  end
10
10
 
11
- subject { Taskinator::Executor.new(definition) }
11
+ let(:task) { double('task') }
12
+ subject { Taskinator::Executor.new(definition, task) }
13
+
14
+ describe "helpers" do
15
+ it "#process_uuid" do
16
+ expect(task).to receive(:process_uuid)
17
+ subject.process_uuid
18
+ end
19
+
20
+ it "#uuid" do
21
+ expect(task).to receive(:uuid)
22
+ subject.uuid
23
+ end
24
+
25
+ it "#options" do
26
+ expect(task).to receive(:options)
27
+ subject.options
28
+ end
29
+ end
12
30
 
13
31
  it "should mixin definition" do
14
32
  expect(subject).to be_a(definition)
@@ -33,16 +33,6 @@ describe Taskinator::JobWorker do
33
33
  allow(Taskinator::Task).to receive(:fetch).with(uuid) { job }
34
34
  allow(job).to receive(:start!)
35
35
  expect(job).to receive(:perform)
36
- allow(job).to receive(:complete!)
37
- subject.perform
38
- end
39
-
40
- it "should complete the job" do
41
- job = mock_job
42
- allow(Taskinator::Task).to receive(:fetch).with(uuid) { job }
43
- allow(job).to receive(:start!)
44
- allow(job).to receive(:perform)
45
- expect(job).to receive(:complete!)
46
36
  subject.perform
47
37
  end
48
38
 
@@ -64,7 +54,6 @@ describe Taskinator::JobWorker do
64
54
  job = mock_job
65
55
  allow(Taskinator::Task).to receive(:fetch).with(uuid) { job }
66
56
  allow(job).to receive(:start!) { raise StandardError }
67
- expect(job).to receive(:fail!).with(StandardError)
68
57
  expect {
69
58
  subject.perform
70
59
  }.to raise_error(StandardError)
@@ -224,14 +224,34 @@ describe Taskinator::Persistence, :redis => true do
224
224
  klass.new
225
225
  }
226
226
 
227
+ describe "#save" do
228
+ pending __FILE__
229
+ end
230
+
227
231
  describe "#key" do
228
232
  it {
229
- expect(subject.key).to match(/#{subject.uuid}/)
233
+ expect(subject.key).to match(/taskinator:base_key:#{subject.uuid}/)
230
234
  }
231
235
  end
232
236
 
233
- describe "#save" do
234
- pending __FILE__
237
+ describe "#process_uuid" do
238
+ it {
239
+ Taskinator.redis do |conn|
240
+ conn.hset(subject.key, :process_uuid, subject.uuid)
241
+ end
242
+
243
+ expect(subject.process_uuid).to match(/#{subject.uuid}/)
244
+ }
245
+ end
246
+
247
+ describe "#process_key" do
248
+ it {
249
+ Taskinator.redis do |conn|
250
+ conn.hset(subject.key, :process_uuid, subject.uuid)
251
+ end
252
+
253
+ expect(subject.process_key).to match(/taskinator:process:#{subject.uuid}/)
254
+ }
235
255
  end
236
256
 
237
257
  describe "#load_workflow_state" do
@@ -256,11 +276,13 @@ describe Taskinator::Persistence, :redis => true do
256
276
  subject.fail(e)
257
277
  end
258
278
 
259
- Taskinator.redis do |conn|
260
- expect(conn.hget(subject.key, :error_type)).to eq('StandardError')
261
- expect(conn.hget(subject.key, :error_message)).to eq('a error')
262
- expect(conn.hget(subject.key, :error_backtrace)).to_not be_empty
279
+ type, message, backtrace = Taskinator.redis do |conn|
280
+ conn.hmget(subject.key, :error_type, :error_message, :error_backtrace)
263
281
  end
282
+
283
+ expect(type).to eq('StandardError')
284
+ expect(message).to eq('a error')
285
+ expect(backtrace).to_not be_empty
264
286
  end
265
287
  end
266
288
 
@@ -278,5 +300,98 @@ describe Taskinator::Persistence, :redis => true do
278
300
  expect(subject.error).to eq([error.class.name, error.message, error.backtrace])
279
301
  end
280
302
  end
303
+
304
+ describe "#tasks_count" do
305
+ it {
306
+ Taskinator.redis do |conn|
307
+ conn.hset(subject.process_key, :tasks_count, 99)
308
+ end
309
+
310
+ expect(subject.tasks_count).to eq(99)
311
+ }
312
+ end
313
+
314
+ %w(
315
+ failed
316
+ cancelled
317
+ completed
318
+ ).each do |status|
319
+
320
+ describe "#count_#{status}" do
321
+ it {
322
+ Taskinator.redis do |conn|
323
+ conn.hset(subject.process_key, status, 99)
324
+ end
325
+
326
+ expect(subject.send(:"count_#{status}")).to eq(99)
327
+ }
328
+ end
329
+
330
+ describe "#incr_#{status}" do
331
+ it {
332
+ Taskinator.redis do |conn|
333
+ conn.hset(subject.process_key, status, 99)
334
+ end
335
+
336
+ subject.send(:"incr_#{status}")
337
+
338
+ expect(subject.send(:"count_#{status}")).to eq(100)
339
+ }
340
+ end
341
+
342
+ describe "#percentage_#{status}" do
343
+ it {
344
+ Taskinator.redis do |conn|
345
+ conn.hmset(
346
+ subject.process_key,
347
+ [:tasks_count, 100],
348
+ [status, 1]
349
+ )
350
+ end
351
+
352
+ expect(subject.send(:"percentage_#{status}")).to eq(1.0)
353
+ }
354
+ end
355
+
356
+ end
357
+
358
+ describe "#process_options" do
359
+ it {
360
+ Taskinator.redis do |conn|
361
+ conn.hset(subject.process_key, :options, YAML.dump({:foo => :bar}))
362
+ end
363
+
364
+ expect(subject.process_options).to eq(:foo => :bar)
365
+ }
366
+ end
367
+
368
+ describe "#instrumentation_payload" do
369
+ it {
370
+ Taskinator.redis do |conn|
371
+ conn.hset(subject.key, :process_uuid, subject.uuid)
372
+ conn.hmset(
373
+ subject.process_key,
374
+ [:options, YAML.dump({:foo => :bar})],
375
+ [:tasks_count, 100],
376
+ [:completed, 3],
377
+ [:cancelled, 2],
378
+ [:failed, 1]
379
+ )
380
+ end
381
+
382
+ expect(subject.instrumentation_payload(:baz => :qux)).to eq({
383
+ :type => subject.class.name,
384
+ :process_uuid => subject.uuid,
385
+ :process_options => {:foo => :bar},
386
+ :uuid => subject.uuid,
387
+ :percentage_failed => 1.0,
388
+ :percentage_cancelled => 2.0,
389
+ :percentage_completed => 3.0,
390
+ :tasks_count => 100,
391
+ :baz => :qux
392
+ })
393
+ }
394
+
395
+ end
281
396
  end
282
397
  end