taskinator 0.0.18 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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