taskinator 0.5.0 → 0.5.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -48,6 +48,7 @@ module Taskinator
48
48
  @parent = value
49
49
  # update the uuid to be "scoped" within the parent task
50
50
  @uuid = "#{@parent.uuid}:subprocess"
51
+ @key = nil # NB: invalidate memoized key
51
52
  end
52
53
 
53
54
  def tasks
@@ -123,7 +124,7 @@ module Taskinator
123
124
  instrument('taskinator.process.completed', completed_payload) do
124
125
  complete if respond_to?(:complete)
125
126
  # notify the parent task (if there is one) that this process has completed
126
- # note: parent may be a proxy, so explicity check for nil?
127
+ # note: parent may be a proxy, so explicitly check for nil?
127
128
  unless parent.nil?
128
129
  parent.complete!
129
130
  else
@@ -133,6 +134,9 @@ module Taskinator
133
134
  end
134
135
  end
135
136
 
137
+ # TODO: add retry method - to pick up from a failed task
138
+ # e.g. like retrying a failed job in Resque Web
139
+
136
140
  def tasks_completed?
137
141
  # TODO: optimize this
138
142
  tasks.all?(&:completed?)
@@ -151,7 +155,7 @@ module Taskinator
151
155
  instrument('taskinator.process.failed', failed_payload(error)) do
152
156
  fail(error) if respond_to?(:fail)
153
157
  # notify the parent task (if there is one) that this process has failed
154
- # note: parent may be a proxy, so explicity check for nil?
158
+ # note: parent may be a proxy, so explicitly check for nil?
155
159
  parent.fail!(error) unless parent.nil?
156
160
  end
157
161
  end
@@ -166,6 +170,7 @@ module Taskinator
166
170
  # subclasses must implement the following methods
167
171
  #--------------------------------------------------
168
172
 
173
+ # :nocov:
169
174
  def enqueue
170
175
  raise NotImplementedError
171
176
  end
@@ -177,6 +182,7 @@ module Taskinator
177
182
  def task_completed(task)
178
183
  raise NotImplementedError
179
184
  end
185
+ # :nocov:
180
186
 
181
187
  #--------------------------------------------------
182
188
 
@@ -199,10 +205,9 @@ module Taskinator
199
205
  end
200
206
 
201
207
  def task_completed(task)
202
- # deincrement the count of pending sequential tasks
208
+ # decrement the count of pending sequential tasks
203
209
  pending = deincr_pending_tasks
204
210
 
205
- Taskinator.statsd_client.count("taskinator.#{definition.name.underscore.parameterize}.pending", pending)
206
211
  Taskinator.logger.info("Completed task for process '#{uuid}'. Pending is #{pending}.")
207
212
 
208
213
  next_task = task.next
@@ -237,7 +242,6 @@ module Taskinator
237
242
  if tasks.empty?
238
243
  complete! # weren't any tasks to start with
239
244
  else
240
- Taskinator.statsd_client.count("taskinator.#{definition.name.underscore.parameterize}.pending", tasks.count)
241
245
  Taskinator.logger.info("Enqueuing #{tasks.count} tasks for process '#{uuid}'.")
242
246
  tasks.each(&:enqueue!)
243
247
  end
@@ -249,6 +253,7 @@ module Taskinator
249
253
  complete! # weren't any tasks to start with
250
254
  else
251
255
  if concurrency_method == :fork
256
+ # :nocov:
252
257
  warn("[DEPRECATED]: concurrency_method will be removed in a future version.")
253
258
  tasks.each do |task|
254
259
  fork do
@@ -256,6 +261,7 @@ module Taskinator
256
261
  end
257
262
  end
258
263
  Process.waitall
264
+ # :nocov:
259
265
  else
260
266
  threads = tasks.map do |task|
261
267
  Thread.new do
@@ -268,14 +274,13 @@ module Taskinator
268
274
  end
269
275
 
270
276
  def task_completed(task)
271
- # deincrement the count of pending concurrent tasks
277
+ # decrement the count of pending concurrent tasks
272
278
  pending = deincr_pending_tasks
273
279
 
274
- Taskinator.statsd_client.count("taskinator.#{definition.name.underscore.parameterize}.pending", pending)
275
280
  Taskinator.logger.info("Completed task for process '#{uuid}'. Pending is #{pending}.")
276
281
 
277
282
  # when complete on first, then don't bother with subsequent tasks completing
278
- if (complete_on == CompleteOn::First)
283
+ if complete_on == CompleteOn::First
279
284
  complete! unless completed?
280
285
  else
281
286
  complete! if pending < 1
@@ -283,7 +288,7 @@ module Taskinator
283
288
  end
284
289
 
285
290
  def tasks_completed?
286
- if (complete_on == CompleteOn::First)
291
+ if complete_on == CompleteOn::First
287
292
  tasks.any?(&:completed?)
288
293
  else
289
294
  super # all
@@ -21,6 +21,7 @@ module Taskinator
21
21
  end
22
22
 
23
23
  attr_reader :process
24
+ attr_reader :definition
24
25
  attr_reader :uuid
25
26
  attr_reader :options
26
27
  attr_reader :queue
@@ -35,6 +36,7 @@ module Taskinator
35
36
 
36
37
  @uuid = "#{process.uuid}:task:#{Taskinator.generate_uuid}"
37
38
  @process = process
39
+ @definition = process.definition
38
40
  @options = options
39
41
  @queue = options.delete(:queue)
40
42
  @created_at = Time.now.utc
@@ -45,6 +47,7 @@ module Taskinator
45
47
  def accept(visitor)
46
48
  visitor.visit_attribute(:uuid)
47
49
  visitor.visit_process_reference(:process)
50
+ visitor.visit_type(:definition)
48
51
  visitor.visit_task_reference(:next)
49
52
  visitor.visit_args(:options)
50
53
  visitor.visit_attribute(:queue)
@@ -134,6 +137,7 @@ module Taskinator
134
137
  # subclasses must implement the following methods
135
138
  #--------------------------------------------------
136
139
 
140
+ # :nocov:
137
141
  def enqueue
138
142
  raise NotImplementedError
139
143
  end
@@ -141,6 +145,7 @@ module Taskinator
141
145
  def start
142
146
  raise NotImplementedError
143
147
  end
148
+ # :nocov:
144
149
 
145
150
  #--------------------------------------------------
146
151
  # and optionally, provide methods:
@@ -155,13 +160,11 @@ module Taskinator
155
160
  # a task which invokes the specified method on the definition
156
161
  # the args must be intrinsic types, since they are serialized to YAML
157
162
  class Step < Task
158
- attr_reader :definition
159
163
  attr_reader :method
160
164
  attr_reader :args
161
165
 
162
166
  def initialize(process, method, args, options={})
163
167
  super(process, options)
164
- @definition = process.definition # for convenience
165
168
 
166
169
  raise ArgumentError, 'method' if method.nil?
167
170
  raise NoMethodError, method unless executor.respond_to?(method)
@@ -188,17 +191,16 @@ module Taskinator
188
191
 
189
192
  def accept(visitor)
190
193
  super
191
- visitor.visit_type(:definition)
192
194
  visitor.visit_attribute(:method)
193
195
  visitor.visit_args(:args)
194
196
  end
195
197
 
196
198
  def executor
197
- @executor ||= Taskinator::Executor.new(@definition, self)
199
+ @executor ||= Taskinator::Executor.new(definition, self)
198
200
  end
199
201
 
200
202
  def inspect
201
- %(#<#{self.class.name}:0x#{self.__id__.to_s(16)} uuid="#{uuid}", method=:#{method}, args=#{args}, current_state=:#{current_state}>)
203
+ %(#<#{self.class.name}:0x#{self.__id__.to_s(16)} uuid="#{uuid}", definition=:#{definition}, method=:#{method}, args=#{args}, current_state=:#{current_state}>)
202
204
  end
203
205
  end
204
206
 
@@ -207,13 +209,11 @@ module Taskinator
207
209
  # a task which invokes the specified background job
208
210
  # the args must be intrinsic types, since they are serialized to YAML
209
211
  class Job < Task
210
- attr_reader :definition
211
212
  attr_reader :job
212
213
  attr_reader :args
213
214
 
214
215
  def initialize(process, job, args, options={})
215
216
  super(process, options)
216
- @definition = process.definition # for convenience
217
217
 
218
218
  raise ArgumentError, 'job' if job.nil?
219
219
  raise ArgumentError, 'job' unless job.methods.include?(:perform) || job.instance_methods.include?(:perform)
@@ -228,7 +228,6 @@ module Taskinator
228
228
 
229
229
  def start
230
230
  # NNB: if other job types are required, may need to implement how they get invoked here!
231
- # FIXME: possible implement using ActiveJob instead, so it doesn't matter how the worker is implemented
232
231
 
233
232
  if job.respond_to?(:perform)
234
233
  # resque
@@ -250,13 +249,12 @@ module Taskinator
250
249
 
251
250
  def accept(visitor)
252
251
  super
253
- visitor.visit_type(:definition)
254
252
  visitor.visit_type(:job)
255
253
  visitor.visit_args(:args)
256
254
  end
257
255
 
258
256
  def inspect
259
- %(#<#{self.class.name}:0x#{self.__id__.to_s(16)} uuid="#{uuid}", job=#{job}, args=#{args}, current_state=:#{current_state}>)
257
+ %(#<#{self.class.name}:0x#{self.__id__.to_s(16)} uuid="#{uuid}", definition=:#{definition}, job=#{job}, args=#{args}, current_state=:#{current_state}>)
260
258
  end
261
259
  end
262
260
 
@@ -302,7 +300,7 @@ module Taskinator
302
300
  end
303
301
 
304
302
  def inspect
305
- %(#<#{self.class.name}:0x#{self.__id__.to_s(16)} uuid="#{uuid}", sub_process=#{sub_process.inspect}, current_state=:#{current_state}>)
303
+ %(#<#{self.class.name}:0x#{self.__id__.to_s(16)} uuid="#{uuid}", definition=:#{definition}, sub_process=#{sub_process.inspect}, current_state=:#{current_state}>)
306
304
  end
307
305
  end
308
306
  end
@@ -1,3 +1,3 @@
1
1
  module Taskinator
2
- VERSION = "0.5.0"
2
+ VERSION = "0.5.2"
3
3
  end
data/lib/taskinator.rb CHANGED
@@ -6,8 +6,6 @@ require 'delegate'
6
6
 
7
7
  require 'taskinator/version'
8
8
 
9
- require 'taskinator/log_stats'
10
-
11
9
  require 'taskinator/complete_on'
12
10
  require 'taskinator/redis_connection'
13
11
  require 'taskinator/logger'
@@ -19,7 +17,6 @@ require 'taskinator/workflow'
19
17
  require 'taskinator/visitor'
20
18
  require 'taskinator/persistence'
21
19
  require 'taskinator/instrumentation'
22
- require 'taskinator/xml_visitor'
23
20
 
24
21
  require 'taskinator/task'
25
22
  require 'taskinator/tasks'
@@ -87,14 +84,6 @@ module Taskinator
87
84
  Taskinator::Logging.logger = log
88
85
  end
89
86
 
90
- def statsd_client
91
- Taskinator::LogStats.client
92
- end
93
-
94
- def statsd_client=(client)
95
- Taskinator::LogStats.client = client
96
- end
97
-
98
87
  # the queue adapter to use
99
88
  # supported adapters include
100
89
  # :active_job, :delayed_job, :redis and :sidekiq
@@ -135,14 +124,12 @@ module Taskinator
135
124
  end
136
125
 
137
126
  class NoOpInstrumenter
138
- # :nocov:
139
127
  def instrument(event, payload={})
140
128
  yield(payload) if block_given?
141
129
  end
142
130
  end
143
131
 
144
132
  class ConsoleInstrumenter
145
- # :nocov:
146
133
  def instrument(event, payload={})
147
134
  puts [event.inspect, payload.to_yaml]
148
135
  yield(payload) if block_given?
@@ -0,0 +1,30 @@
1
+ module TestJob
2
+ def self.perform(*args)
3
+ end
4
+ end
5
+
6
+ class TestJobClass
7
+ def perform(*args)
8
+ end
9
+ end
10
+
11
+ module TestJobModule
12
+ def self.perform(*args)
13
+ end
14
+ end
15
+
16
+ class TestJobClassNoArgs
17
+ def perform
18
+ end
19
+ end
20
+
21
+ module TestJobModuleNoArgs
22
+ def self.perform
23
+ end
24
+ end
25
+
26
+ module TestJobError
27
+ def self.perform
28
+ raise ArgumentError
29
+ end
30
+ end
@@ -0,0 +1,2 @@
1
+ class TestJobTask < Taskinator::Task::Job
2
+ end
@@ -0,0 +1,2 @@
1
+ class TestStepTask < Taskinator::Task::Step
2
+ end
@@ -0,0 +1,2 @@
1
+ class TestSubProcessTask < Taskinator::Task::SubProcess
2
+ end
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Taskinator::Definition::Builder do
3
+ describe Taskinator::Builder do
4
4
 
5
5
  let(:definition) do
6
6
  Module.new do
@@ -26,7 +26,7 @@ describe Taskinator::Definition::Builder do
26
26
  Proc.new {|*args| the_block.call }
27
27
  }
28
28
 
29
- subject { Taskinator::Definition::Builder.new(process, definition, *[*args, builder_options]) }
29
+ subject { Taskinator::Builder.new(process, definition, *[*args, builder_options]) }
30
30
 
31
31
  it "assign attributes" do
32
32
  expect(subject.process).to eq(process)
@@ -106,6 +106,37 @@ describe Taskinator::Definition do
106
106
  expect(subject.create_process).to be_a(Taskinator::Process)
107
107
  end
108
108
 
109
+ it "handles error" do
110
+ subject.define_process do
111
+ raise ArgumentError
112
+ end
113
+
114
+ expect {
115
+ subject.create_process
116
+ }.to raise_error(ArgumentError)
117
+ end
118
+
119
+ it "checks defined arguments provided" do
120
+ subject.define_process :arg1, :arg2 do
121
+ end
122
+
123
+ expect {
124
+ subject.create_process
125
+ }.to raise_error(ArgumentError)
126
+
127
+ expect {
128
+ subject.create_process :foo
129
+ }.to raise_error(ArgumentError)
130
+
131
+ expect {
132
+ subject.create_process :foo, :bar
133
+ }.not_to raise_error
134
+
135
+ expect {
136
+ subject.create_process :foo, :bar, :baz
137
+ }.not_to raise_error
138
+ end
139
+
109
140
  it "defaults the scope to :shared" do
110
141
  block = SpecSupport::Block.new
111
142
  allow(block).to receive(:to_proc) {
@@ -2,8 +2,6 @@ require 'spec_helper'
2
2
 
3
3
  describe Taskinator::Instrumentation, :redis => true do
4
4
 
5
- let(:definition) { TestDefinition }
6
-
7
5
  subject do
8
6
  klass = Class.new do
9
7
  include Taskinator::Persistence
@@ -15,10 +13,12 @@ describe Taskinator::Instrumentation, :redis => true do
15
13
 
16
14
  attr_reader :uuid
17
15
  attr_reader :options
16
+ attr_reader :definition
18
17
 
19
18
  def initialize
20
19
  @uuid = Taskinator.generate_uuid
21
20
  @options = { :bar => :baz }
21
+ @definition = TestDefinition
22
22
  end
23
23
  end
24
24
 
@@ -75,6 +75,7 @@ describe Taskinator::Instrumentation, :redis => true do
75
75
  expect(subject.completed_payload(:baz => :qux)).to eq(
76
76
  OpenStruct.new({
77
77
  :type => subject.class.name,
78
+ :definition => subject.definition.name,
78
79
  :process_uuid => subject.uuid,
79
80
  :process_options => {:foo => :bar},
80
81
  :uuid => subject.uuid,
@@ -46,6 +46,15 @@ describe Taskinator::Persistence, :redis => true do
46
46
  expect(subject.fetch('uuid', cache)).to eq(item)
47
47
  end
48
48
 
49
+ it "yields UnknownType" do
50
+ Taskinator.redis do |conn|
51
+ conn.hmset(*[subject.key_for("foo"), [:type, 'UnknownFoo']])
52
+ end
53
+ instance = subject.fetch("foo")
54
+ expect(instance).to be_a(Taskinator::Persistence::UnknownType)
55
+ expect(instance.type).to eq("UnknownFoo")
56
+ end
57
+
49
58
  describe "for processes" do
50
59
  let(:process) { TestProcess.new(definition) }
51
60
 
@@ -53,6 +62,19 @@ describe Taskinator::Persistence, :redis => true do
53
62
  process.save
54
63
  expect(TestProcess.fetch(process.uuid)).to eq(process)
55
64
  }
65
+
66
+ describe "unknown definition" do
67
+ it "yields UnknownType" do
68
+ Taskinator.redis do |conn|
69
+ conn.hmset(*[process.key, [:type, TestProcess.name], [:uuid, process.uuid], [:definition, 'UnknownFoo']])
70
+ end
71
+
72
+ instance = TestProcess.fetch(process.uuid)
73
+ expect(instance.uuid).to eq(process.uuid)
74
+ expect(instance.definition).to be_a(Taskinator::Persistence::UnknownType)
75
+ expect(instance.definition.type).to eq("UnknownFoo")
76
+ end
77
+ end
56
78
  end
57
79
 
58
80
  describe "for tasks" do
@@ -62,9 +84,45 @@ describe Taskinator::Persistence, :redis => true do
62
84
  it {
63
85
  process.tasks << task
64
86
  process.save
65
- expect(TestTask.fetch(task.uuid)).to eq(task)
66
- expect(TestTask.fetch(task.uuid).process).to eq(process)
87
+
88
+ instance = TestTask.fetch(task.uuid)
89
+ expect(instance).to eq(task)
90
+ expect(instance.process).to eq(process)
67
91
  }
92
+
93
+ describe "unknown job" do
94
+ let(:task) { TestJobTask.new(process, TestJob, []) }
95
+
96
+ it "yields UnknownType" do
97
+ Taskinator.redis do |conn|
98
+ conn.hmset(*[task.key, [:type, task.class.name], [:uuid, task.uuid], [:job, 'UnknownBar']])
99
+ end
100
+
101
+ instance = TestJobTask.fetch(task.uuid)
102
+ expect(instance.uuid).to eq(task.uuid)
103
+ expect(instance.job).to be_a(Taskinator::Persistence::UnknownType)
104
+ expect(instance.job.type).to eq("UnknownBar")
105
+ end
106
+ end
107
+
108
+ describe "unknown subprocess" do
109
+ let(:sub_process) { TestProcess.new(definition) }
110
+ let(:task) { TestSubProcessTask.new(process, sub_process) }
111
+
112
+ it "yields UnknownType" do
113
+ Taskinator.redis do |conn|
114
+ conn.multi do |transaction|
115
+ transaction.hmset(*[task.key, [:type, task.class.name], [:uuid, task.uuid], [:sub_process, sub_process.uuid]])
116
+ transaction.hmset(*[sub_process.key, [:type, sub_process.class.name], [:uuid, sub_process.uuid], [:definition, 'UnknownBaz']])
117
+ end
118
+ end
119
+
120
+ instance = TestSubProcessTask.fetch(task.uuid)
121
+ expect(instance.uuid).to eq(task.uuid)
122
+ expect(instance.sub_process.definition).to be_a(Taskinator::Persistence::UnknownType)
123
+ expect(instance.sub_process.definition.type).to eq("UnknownBaz")
124
+ end
125
+ end
68
126
  end
69
127
  end
70
128
  end
@@ -188,6 +246,72 @@ describe Taskinator::Persistence, :redis => true do
188
246
  end
189
247
  end
190
248
 
249
+ describe "unknown type helpers" do
250
+ subject { Taskinator::Persistence::UnknownType }
251
+
252
+ describe "#new" do
253
+ it "instantiates new module instance" do
254
+ instance = subject.new("foo")
255
+ expect(instance).to_not be_nil
256
+ expect(instance).to be_a(::Module)
257
+ end
258
+
259
+ it "yields same instance for same type" do
260
+ instance1 = subject.new("foo")
261
+ instance2 = subject.new("foo")
262
+ expect(instance1).to eq(instance2)
263
+ end
264
+ end
265
+
266
+ describe ".type" do
267
+ it {
268
+ instance = subject.new("foo")
269
+ expect(instance.type).to eq("foo")
270
+ }
271
+ end
272
+
273
+ describe ".to_s" do
274
+ it {
275
+ instance = subject.new("foo")
276
+ expect(instance.to_s).to eq("Unknown type 'foo'.")
277
+ }
278
+ end
279
+
280
+ describe ".allocate" do
281
+ it "emulates Object#allocate" do
282
+ instance = subject.new("foo")
283
+ expect(instance.allocate).to eq(instance)
284
+ end
285
+ end
286
+
287
+ describe ".accept" do
288
+ it {
289
+ instance = subject.new("foo")
290
+ expect(instance).to respond_to(:accept)
291
+ }
292
+ end
293
+
294
+ describe ".perform" do
295
+ it "raises UnknownTypeError" do
296
+ instance = subject.new("foo")
297
+ expect {
298
+ instance.perform(:foo, 1, false)
299
+ }.to raise_error(Taskinator::Persistence::UnknownTypeError)
300
+ end
301
+ end
302
+
303
+ describe "via executor" do
304
+ it "raises UnknownTypeError" do
305
+ instance = subject.new("foo")
306
+ executor = Taskinator::Executor.new(instance)
307
+
308
+ expect {
309
+ executor.foo
310
+ }.to raise_error(Taskinator::Persistence::UnknownTypeError)
311
+ end
312
+ end
313
+ end
314
+
191
315
  describe "instance methods" do
192
316
  subject {
193
317
  klass = Class.new do
@@ -207,11 +331,14 @@ describe Taskinator::Persistence, :redis => true do
207
331
  }
208
332
 
209
333
  describe "#save" do
210
- pending __FILE__
334
+ pending
211
335
  end
212
336
 
213
337
  describe "#to_xml" do
214
- pending __FILE__
338
+ it {
339
+ process = TestFlows::Task.create_process(1)
340
+ expect(process.to_xml).to match(/xml/)
341
+ }
215
342
  end
216
343
 
217
344
  describe "#key" do
@@ -426,6 +553,5 @@ describe Taskinator::Persistence, :redis => true do
426
553
  end
427
554
 
428
555
  end
429
-
430
556
  end
431
557
  end
@@ -35,6 +35,14 @@ describe Taskinator::Process do
35
35
  it { expect(subject.tasks).to be_a(Taskinator::Tasks) }
36
36
  end
37
37
 
38
+ describe "#no_tasks_defined?" do
39
+ it { expect(subject.no_tasks_defined?).to be }
40
+ it {
41
+ subject.tasks << :mock
42
+ expect(subject.no_tasks_defined?).to_not be
43
+ }
44
+ end
45
+
38
46
  describe "#to_s" do
39
47
  it { expect(subject.to_s).to match(/#{subject.uuid}/) }
40
48
  end
@@ -288,7 +296,7 @@ describe Taskinator::Process do
288
296
  subject.task_completed(task1)
289
297
  end
290
298
 
291
- it "deincrements the pending task count" do
299
+ it "decrements the pending task count" do
292
300
  tasks.each {|t| subject.tasks << t }
293
301
  task1 = tasks[0]
294
302
  task2 = tasks[1]
@@ -367,7 +375,7 @@ describe Taskinator::Process do
367
375
  subject.task_failed(tasks.first, error)
368
376
  end
369
377
 
370
- it "doesn't deincement pending task count" do
378
+ it "doesn't decrement pending task count" do
371
379
  tasks.each {|t| subject.tasks << t }
372
380
 
373
381
  expect(subject).to_not receive(:deincr_pending_tasks)
@@ -515,7 +523,7 @@ describe Taskinator::Process do
515
523
  end
516
524
  end
517
525
 
518
- it "deincrements the pending task count" do
526
+ it "decrements the pending task count" do
519
527
  tasks.each {|t| subject.tasks << t }
520
528
  task1 = tasks[0]
521
529
  task2 = tasks[1]