taskinator 0.0.17 → 0.0.18
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -1
- data/Gemfile.lock +1 -1
- data/README.md +18 -0
- data/lib/taskinator/api.rb +1 -1
- data/lib/taskinator/definition.rb +22 -9
- data/lib/taskinator/executor.rb +14 -1
- data/lib/taskinator/persistence.rb +93 -16
- data/lib/taskinator/process.rb +12 -6
- data/lib/taskinator/task.rb +11 -4
- data/lib/taskinator/tasks.rb +1 -1
- data/lib/taskinator/version.rb +1 -1
- data/lib/taskinator/visitor.rb +4 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/support/mock_model.rb +18 -0
- data/spec/support/test_flows.rb +86 -0
- data/spec/taskinator/definition/builder_spec.rb +1 -2
- data/spec/taskinator/definition_spec.rb +54 -1
- data/spec/taskinator/persistence_spec.rb +119 -0
- data/spec/taskinator/process_spec.rb +31 -2
- data/spec/taskinator/task_spec.rb +46 -0
- data/spec/taskinator/test_flows_spec.rb +88 -0
- data/spec/taskinator/visitor_spec.rb +1 -0
- metadata +8 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bd4b644d746cddfe50ed5d3c9d984bcd978d3fee
|
4
|
+
data.tar.gz: 45f8166dbd3f7f1b5e27d52c31c7e0384a433cc7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 34d036f6fedd66b61b73c6530716882a9b019a769431c719273e0b684e216838571cc3c49a7317292662f8dc41b1ff379c0c41480effb3f705ee0e8752a551de
|
7
|
+
data.tar.gz: 9fa6364a483ec5d361881b07f26e266ebebbfba07f1b44cb73470c5658e048448f38eece7d531cbb3785219253f83233b75e5589a05eedf06c1eadaeff74fff3
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,12 @@
|
|
1
|
+
v0.0.18 - 14 Jul 2015
|
2
|
+
---
|
3
|
+
Fixed issue with `Taskinator::Api::Processes#each` method, which was causing a Segmentation fault.
|
4
|
+
Added statistics information.
|
5
|
+
Improved specifications code coverage.
|
6
|
+
|
1
7
|
v0.0.17 - 11 Jul 2015
|
2
8
|
---
|
3
|
-
Fixed issue with `Task#each` method, which was causing a Segmentation fault.
|
9
|
+
Fixed issue with `Taskinator::Task#each` method, which was causing a Segmentation fault.
|
4
10
|
Added `define_sequential_process` and `define_concurrent_process` methods for defining processes.
|
5
11
|
Added `ConsoleInstrumenter` instrumenter implementation.
|
6
12
|
Required `resque` for console and rake tasks, to make debugging easier
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -588,6 +588,24 @@ Taskinator.configure do |config|
|
|
588
588
|
end
|
589
589
|
```
|
590
590
|
|
591
|
+
### Instrumentation
|
592
|
+
|
593
|
+
It is possible to instrument processes, tasks and jobs by providing an instrumeter such as `ActiveSupport::Notifications`.
|
594
|
+
|
595
|
+
```ruby
|
596
|
+
Taskinator.configure do |config|
|
597
|
+
config.instrumenter = ActiveSupport::Notifications
|
598
|
+
end
|
599
|
+
```
|
600
|
+
|
601
|
+
Alternatively, you can use the built-in instrumenter for logging to the console for debugging:
|
602
|
+
|
603
|
+
```ruby
|
604
|
+
Taskinator.configure do |config|
|
605
|
+
config.instrumenter = Taskinator::ConsoleInstrumenter.new
|
606
|
+
end
|
607
|
+
```
|
608
|
+
|
591
609
|
## Notes
|
592
610
|
|
593
611
|
The persistence logic is decoupled from the implementation, so it is possible to implement another backing store if required.
|
data/lib/taskinator/api.rb
CHANGED
@@ -33,7 +33,9 @@ module Taskinator
|
|
33
33
|
Process.define_sequential_process_for(definition, options)
|
34
34
|
}
|
35
35
|
|
36
|
-
define_singleton_method :_create_process_ do
|
36
|
+
define_singleton_method :_create_process_ do |*args|
|
37
|
+
|
38
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
37
39
|
|
38
40
|
# TODO: better validation of arguments
|
39
41
|
|
@@ -48,14 +50,25 @@ module Taskinator
|
|
48
50
|
Builder.new(process, self, *args).instance_eval(&block)
|
49
51
|
end
|
50
52
|
|
51
|
-
#
|
52
|
-
|
53
|
-
|
54
|
-
#
|
55
|
-
|
53
|
+
# only save "root processes"
|
54
|
+
unless options[:subprocess]
|
55
|
+
|
56
|
+
# instrument separately
|
57
|
+
Taskinator.instrumenter.instrument(:save_process, :uuid => process.uuid) do
|
58
|
+
|
59
|
+
# this will visit "sub processes" and persist them too
|
60
|
+
process.save
|
61
|
+
|
62
|
+
# add it to the list of "root processes"
|
63
|
+
Persistence.add_process_to_list(process)
|
64
|
+
|
65
|
+
end
|
66
|
+
|
56
67
|
end
|
57
68
|
|
69
|
+
# this is the "root" process
|
58
70
|
process
|
71
|
+
|
59
72
|
end
|
60
73
|
end
|
61
74
|
|
@@ -67,7 +80,7 @@ module Taskinator
|
|
67
80
|
#
|
68
81
|
def create_process(*args)
|
69
82
|
assert_valid_process_module
|
70
|
-
_create_process_(args)
|
83
|
+
_create_process_(*args, :sub_process => false)
|
71
84
|
end
|
72
85
|
|
73
86
|
#
|
@@ -80,12 +93,12 @@ module Taskinator
|
|
80
93
|
uuid = SecureRandom.uuid
|
81
94
|
Taskinator.queue.enqueue_create_process(self, uuid, args)
|
82
95
|
|
83
|
-
Taskinator::Persistence::LazyLoader.new(Taskinator::Process, uuid)
|
96
|
+
Taskinator::Persistence::LazyLoader.new(Taskinator::Process, uuid, uuid)
|
84
97
|
end
|
85
98
|
|
86
99
|
def create_sub_process(*args)
|
87
100
|
assert_valid_process_module
|
88
|
-
_create_process_(args, :subprocess => true)
|
101
|
+
_create_process_(*args, :subprocess => true)
|
89
102
|
end
|
90
103
|
|
91
104
|
private
|
data/lib/taskinator/executor.rb
CHANGED
@@ -10,7 +10,20 @@ module Taskinator
|
|
10
10
|
|
11
11
|
# include the module into the eigen class, so it is only for this instance
|
12
12
|
eigen = class << self; self; end
|
13
|
-
eigen.send
|
13
|
+
eigen.send(:include, definition)
|
14
14
|
end
|
15
|
+
|
16
|
+
def root_key
|
17
|
+
@root_key ||= task.root_key
|
18
|
+
end
|
19
|
+
|
20
|
+
def uuid
|
21
|
+
task.uuid
|
22
|
+
end
|
23
|
+
|
24
|
+
def options
|
25
|
+
task.options
|
26
|
+
end
|
27
|
+
|
15
28
|
end
|
16
29
|
end
|
@@ -61,20 +61,36 @@ module Taskinator
|
|
61
61
|
end
|
62
62
|
|
63
63
|
module InstanceMethods
|
64
|
-
def key
|
65
|
-
self.class.key_for(self.uuid)
|
66
|
-
end
|
67
64
|
|
68
65
|
def save
|
69
66
|
Taskinator.redis do |conn|
|
70
67
|
conn.multi do
|
71
|
-
RedisSerializationVisitor.new(conn, self).visit
|
72
|
-
conn.
|
68
|
+
visitor = RedisSerializationVisitor.new(conn, self).visit
|
69
|
+
conn.hmset(
|
70
|
+
"taskinator:#{self.key}",
|
71
|
+
:tasks_count, visitor.task_count,
|
72
|
+
:tasks_failed, 0,
|
73
|
+
:tasks_completed, 0,
|
74
|
+
:tasks_cancelled, 0,
|
75
|
+
)
|
73
76
|
true
|
74
77
|
end
|
75
78
|
end
|
76
79
|
end
|
77
80
|
|
81
|
+
# this is the persistence key
|
82
|
+
def key
|
83
|
+
@key ||= self.class.key_for(self.uuid)
|
84
|
+
end
|
85
|
+
|
86
|
+
# retrieves the root key associated
|
87
|
+
# with the process or task
|
88
|
+
def root_key
|
89
|
+
@root_key ||= Taskinator.redis do |conn|
|
90
|
+
conn.hget(self.key, :root_key)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
78
94
|
# retrieves the workflow state
|
79
95
|
# this method is called from the workflow gem
|
80
96
|
def load_workflow_state
|
@@ -116,6 +132,39 @@ module Taskinator
|
|
116
132
|
[error_type, error_message, JSON.parse(error_backtrace)]
|
117
133
|
end
|
118
134
|
end
|
135
|
+
|
136
|
+
def tasks_count
|
137
|
+
@tasks_count ||= begin
|
138
|
+
Taskinator.redis do |conn|
|
139
|
+
conn.hget "taskinator:#{self.root_key}", :tasks_count
|
140
|
+
end.to_i
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
%w(
|
145
|
+
failed
|
146
|
+
cancelled
|
147
|
+
completed
|
148
|
+
).each do |status|
|
149
|
+
|
150
|
+
define_method "count_#{status}" do
|
151
|
+
Taskinator.redis do |conn|
|
152
|
+
conn.hget "taskinator:#{self.root_key}", status
|
153
|
+
end.to_i
|
154
|
+
end
|
155
|
+
|
156
|
+
define_method "incr_#{status}" do
|
157
|
+
Taskinator.redis do |conn|
|
158
|
+
conn.hincrby "taskinator:#{self.root_key}", status, 1
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
define_method "percentage_#{status}" do
|
163
|
+
tasks_count > 0 ? (send("count_#{status}") / tasks_count.to_f) * 100.0 : 0.0
|
164
|
+
end
|
165
|
+
|
166
|
+
end
|
167
|
+
|
119
168
|
end
|
120
169
|
|
121
170
|
class RedisSerializationVisitor < Visitor::Base
|
@@ -126,38 +175,49 @@ module Taskinator
|
|
126
175
|
# one roundtrip to the redis server
|
127
176
|
#
|
128
177
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
@
|
133
|
-
|
178
|
+
attr_reader :instance
|
179
|
+
|
180
|
+
def initialize(conn, instance, base_visitor=self)
|
181
|
+
@conn = conn
|
182
|
+
@instance = instance
|
183
|
+
@key = instance.key
|
184
|
+
@root_key = base_visitor.instance.key
|
185
|
+
@base_visitor = base_visitor
|
186
|
+
@task_count = 0
|
134
187
|
end
|
135
188
|
|
136
189
|
# the starting point for serializing the instance
|
137
190
|
def visit
|
138
191
|
@hmset = []
|
139
192
|
@hmset << @key
|
193
|
+
|
140
194
|
@hmset += [:type, @instance.class.name]
|
141
195
|
|
142
196
|
@instance.accept(self)
|
143
197
|
|
198
|
+
# add the root key, for easy access later!
|
199
|
+
@hmset += [:root_key, @root_key]
|
200
|
+
|
144
201
|
# NB: splat args
|
145
202
|
@conn.hmset(*@hmset)
|
203
|
+
|
204
|
+
self
|
146
205
|
end
|
147
206
|
|
148
207
|
def visit_process(attribute)
|
149
208
|
process = @instance.send(attribute)
|
150
209
|
if process
|
151
210
|
@hmset += [attribute, process.uuid]
|
152
|
-
RedisSerializationVisitor.new(@conn, process, @
|
211
|
+
RedisSerializationVisitor.new(@conn, process, @base_visitor).visit
|
153
212
|
end
|
154
213
|
end
|
155
214
|
|
156
215
|
def visit_tasks(tasks)
|
157
|
-
@hmset += [:task_count, tasks.count]
|
216
|
+
@hmset += [:task_count, tasks.count] # not used currently, but for informational purposes
|
158
217
|
tasks.each do |task|
|
159
|
-
RedisSerializationVisitor.new(@conn, task, @
|
218
|
+
RedisSerializationVisitor.new(@conn, task, @base_visitor).visit
|
160
219
|
@conn.rpush "#{@key}:tasks", task.uuid
|
220
|
+
@base_visitor.incr_task_count unless task.is_a?(Task::SubProcess)
|
161
221
|
end
|
162
222
|
end
|
163
223
|
|
@@ -186,6 +246,14 @@ module Taskinator
|
|
186
246
|
yaml = Taskinator::Persistence.serialize(values)
|
187
247
|
@hmset += [attribute, yaml]
|
188
248
|
end
|
249
|
+
|
250
|
+
def task_count
|
251
|
+
@task_count
|
252
|
+
end
|
253
|
+
|
254
|
+
def incr_task_count
|
255
|
+
@task_count += 1
|
256
|
+
end
|
189
257
|
end
|
190
258
|
|
191
259
|
class RedisDeserializationVisitor < Taskinator::Visitor::Base
|
@@ -293,8 +361,10 @@ module Taskinator
|
|
293
361
|
def lazy_instance_for(base, uuid)
|
294
362
|
Taskinator.redis do |conn|
|
295
363
|
type = conn.hget(base.key_for(uuid), :type)
|
364
|
+
root_key = conn.hget(base.key_for(uuid), :root_key)
|
365
|
+
|
296
366
|
klass = Kernel.const_get(type)
|
297
|
-
LazyLoader.new(klass, uuid, @instance_cache)
|
367
|
+
LazyLoader.new(klass, uuid, root_key, @instance_cache)
|
298
368
|
end
|
299
369
|
end
|
300
370
|
end
|
@@ -310,13 +380,16 @@ module Taskinator
|
|
310
380
|
# E.g. this is useful for tasks which refer to their parent processes
|
311
381
|
#
|
312
382
|
|
313
|
-
def initialize(type, uuid, instance_cache={})
|
383
|
+
def initialize(type, uuid, root_key, instance_cache={})
|
314
384
|
@type = type
|
315
385
|
@uuid = uuid
|
386
|
+
@root_key = root_key
|
316
387
|
@instance_cache = instance_cache
|
317
388
|
end
|
318
389
|
|
319
|
-
|
390
|
+
# shadows the real methods, but will be the same!
|
391
|
+
attr_reader :uuid
|
392
|
+
attr_reader :root_key
|
320
393
|
|
321
394
|
# attempts to reload the actual process
|
322
395
|
def reload
|
@@ -343,6 +416,8 @@ module Taskinator
|
|
343
416
|
values.each {|key, value|
|
344
417
|
values[key] = value.global_id if value.respond_to?(:global_id)
|
345
418
|
}
|
419
|
+
elsif values.respond_to?(:global_id)
|
420
|
+
values = values.global_id
|
346
421
|
end
|
347
422
|
YAML.dump(values)
|
348
423
|
end
|
@@ -357,6 +432,8 @@ module Taskinator
|
|
357
432
|
values.each {|key, value|
|
358
433
|
values[key] = value.find if value.respond_to?(:model_id) && value.respond_to?(:find)
|
359
434
|
}
|
435
|
+
elsif values.respond_to?(:model_id) && values.respond_to?(:find)
|
436
|
+
values = values.find
|
360
437
|
end
|
361
438
|
values
|
362
439
|
end
|
data/lib/taskinator/process.rb
CHANGED
@@ -122,7 +122,13 @@ module Taskinator
|
|
122
122
|
include Persistence
|
123
123
|
|
124
124
|
def enqueue
|
125
|
-
|
125
|
+
# don't bother if there aren't any tasks!
|
126
|
+
if tasks.empty?
|
127
|
+
# simply complete the process...
|
128
|
+
complete!
|
129
|
+
else
|
130
|
+
Taskinator.queue.enqueue_process(self)
|
131
|
+
end
|
126
132
|
end
|
127
133
|
|
128
134
|
# callback for when the process has completed
|
@@ -164,7 +170,7 @@ module Taskinator
|
|
164
170
|
end
|
165
171
|
|
166
172
|
def inspect
|
167
|
-
%(#<#{self.class.name}:0x#{self.__id__.to_s(16)} state=:#{current_state.name}, tasks=[#{tasks.inspect}]>)
|
173
|
+
%(#<#{self.class.name}:0x#{self.__id__.to_s(16)} uuid="#{uuid}", state=:#{current_state.name}, tasks=[#{tasks.inspect}]>)
|
168
174
|
end
|
169
175
|
end
|
170
176
|
|
@@ -177,10 +183,10 @@ module Taskinator
|
|
177
183
|
end
|
178
184
|
|
179
185
|
def start
|
180
|
-
if tasks.
|
181
|
-
tasks.each(&:enqueue!)
|
182
|
-
else
|
186
|
+
if tasks.empty?
|
183
187
|
complete! # weren't any tasks to start with
|
188
|
+
else
|
189
|
+
tasks.each(&:enqueue!)
|
184
190
|
end
|
185
191
|
end
|
186
192
|
|
@@ -205,7 +211,7 @@ module Taskinator
|
|
205
211
|
end
|
206
212
|
|
207
213
|
def inspect
|
208
|
-
%(#<#{self.class.name}:0x#{self.__id__.to_s(16)} state=:#{current_state.name}, complete_on=:#{complete_on}, tasks=[#{tasks.inspect}]>)
|
214
|
+
%(#<#{self.class.name}:0x#{self.__id__.to_s(16)} uuid="#{uuid}", state=:#{current_state.name}, complete_on=:#{complete_on}, tasks=[#{tasks.inspect}]>)
|
209
215
|
end
|
210
216
|
end
|
211
217
|
|
data/lib/taskinator/task.rb
CHANGED
@@ -105,16 +105,23 @@ module Taskinator
|
|
105
105
|
|
106
106
|
# callback for when the task has completed
|
107
107
|
def on_completed_entry(*args)
|
108
|
+
self.incr_completed
|
108
109
|
# notify the process that this task has completed
|
109
110
|
process.task_completed(self)
|
110
111
|
end
|
111
112
|
|
112
113
|
# callback for when the task has failed
|
113
114
|
def on_failed_entry(*args)
|
115
|
+
self.incr_failed
|
114
116
|
# notify the process that this task has failed
|
115
117
|
process.task_failed(self, args.last)
|
116
118
|
end
|
117
119
|
|
120
|
+
# callback for when the task has cancelled
|
121
|
+
def on_cancelled_entry(*args)
|
122
|
+
self.incr_cancelled
|
123
|
+
end
|
124
|
+
|
118
125
|
# helper method, delegating to process
|
119
126
|
def paused?
|
120
127
|
process.paused?
|
@@ -144,7 +151,7 @@ module Taskinator
|
|
144
151
|
end
|
145
152
|
|
146
153
|
def executor
|
147
|
-
@executor ||= Taskinator::Executor.new(@definition)
|
154
|
+
@executor ||= Taskinator::Executor.new(@definition, self)
|
148
155
|
end
|
149
156
|
|
150
157
|
def start
|
@@ -168,7 +175,7 @@ module Taskinator
|
|
168
175
|
end
|
169
176
|
|
170
177
|
def inspect
|
171
|
-
%(#<#{self.class.name}:0x#{self.__id__.to_s(16)} method=:#{method}, state=:#{current_state.name}>)
|
178
|
+
%(#<#{self.class.name}:0x#{self.__id__.to_s(16)} uuid="#{uuid}", method=:#{method}, args=#{args}, state=:#{current_state.name}>)
|
172
179
|
end
|
173
180
|
end
|
174
181
|
|
@@ -214,7 +221,7 @@ module Taskinator
|
|
214
221
|
end
|
215
222
|
|
216
223
|
def inspect
|
217
|
-
%(#<#{self.class.name}:0x#{self.__id__.to_s(16)} job=#{job}, state=:#{current_state.name}>)
|
224
|
+
%(#<#{self.class.name}:0x#{self.__id__.to_s(16)} uuid="#{uuid}", job=#{job}, args=#{args}, state=:#{current_state.name}>)
|
218
225
|
end
|
219
226
|
end
|
220
227
|
|
@@ -247,7 +254,7 @@ module Taskinator
|
|
247
254
|
end
|
248
255
|
|
249
256
|
def inspect
|
250
|
-
%(#<#{self.class.name}:0x#{self.__id__.to_s(16)} sub_process=#{sub_process.inspect}, state=:#{current_state.name}>)
|
257
|
+
%(#<#{self.class.name}:0x#{self.__id__.to_s(16)} uuid="#{uuid}", sub_process=#{sub_process.inspect}, state=:#{current_state.name}>)
|
251
258
|
end
|
252
259
|
end
|
253
260
|
|
data/lib/taskinator/tasks.rb
CHANGED
data/lib/taskinator/version.rb
CHANGED
data/lib/taskinator/visitor.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
@@ -76,3 +76,13 @@ end
|
|
76
76
|
|
77
77
|
# require examples, must happen after configure
|
78
78
|
Dir[File.expand_path("../examples/**/*.rb", __FILE__)].each {|f| require f }
|
79
|
+
|
80
|
+
def recursively_enumerate_tasks(tasks, &block)
|
81
|
+
tasks.each do |task|
|
82
|
+
if task.is_a?(Taskinator::Task::SubProcess)
|
83
|
+
recursively_enumerate_tasks(task.sub_process.tasks, &block)
|
84
|
+
else
|
85
|
+
yield task
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class MockModel
|
2
|
+
|
3
|
+
attr_reader :model_id
|
4
|
+
attr_reader :model_type
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@model_id = 1
|
8
|
+
@model_type = 'TypeX'
|
9
|
+
end
|
10
|
+
|
11
|
+
def global_id
|
12
|
+
{ :model_id => model_id, :model_type => model_type }
|
13
|
+
end
|
14
|
+
|
15
|
+
def find
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module TestFlows
|
2
|
+
|
3
|
+
module Worker
|
4
|
+
def self.perform(*args)
|
5
|
+
end
|
6
|
+
|
7
|
+
def perform(*args)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
module Support
|
12
|
+
|
13
|
+
def iterator(task_count)
|
14
|
+
task_count.times do |i|
|
15
|
+
yield i
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def do_task(*args)
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
module Task
|
25
|
+
extend Taskinator::Definition
|
26
|
+
include Support
|
27
|
+
|
28
|
+
define_process :task_count do
|
29
|
+
for_each :iterator do
|
30
|
+
task :do_task
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
module Job
|
37
|
+
extend Taskinator::Definition
|
38
|
+
include Support
|
39
|
+
|
40
|
+
define_process :task_count do
|
41
|
+
for_each :iterator do
|
42
|
+
job Worker
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
module SubProcess
|
49
|
+
extend Taskinator::Definition
|
50
|
+
include Support
|
51
|
+
|
52
|
+
define_process :task_count do
|
53
|
+
sub_process Task
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
module Sequential
|
59
|
+
extend Taskinator::Definition
|
60
|
+
include Support
|
61
|
+
|
62
|
+
define_process :task_count do
|
63
|
+
sequential do
|
64
|
+
for_each :iterator do
|
65
|
+
task :do_task
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
module Concurrent
|
73
|
+
extend Taskinator::Definition
|
74
|
+
include Support
|
75
|
+
|
76
|
+
define_process :task_count do
|
77
|
+
concurrent do
|
78
|
+
for_each :iterator do
|
79
|
+
task :do_task
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
@@ -91,10 +91,9 @@ describe Taskinator::Definition::Builder do
|
|
91
91
|
describe "#for_each" do
|
92
92
|
it "creates tasks for each returned item" do
|
93
93
|
# the definition is mixed into the eigen class of Executor
|
94
|
-
# HACK: replace the internal executor instance
|
95
94
|
|
95
|
+
# HACK: replace the internal executor instance
|
96
96
|
executor = Taskinator::Executor.new(definition)
|
97
|
-
|
98
97
|
subject.instance_eval do
|
99
98
|
@executor = executor
|
100
99
|
end
|
@@ -18,7 +18,6 @@ describe Taskinator::Definition do
|
|
18
18
|
|
19
19
|
describe "#define_process" do
|
20
20
|
it "should define a #create_process method" do
|
21
|
-
subject.define_process
|
22
21
|
expect(subject).to respond_to(:create_process)
|
23
22
|
end
|
24
23
|
|
@@ -34,6 +33,60 @@ describe Taskinator::Definition do
|
|
34
33
|
subject.define_process
|
35
34
|
}.to raise_error(Taskinator::Definition::ProcessAlreadyDefinedError)
|
36
35
|
end
|
36
|
+
|
37
|
+
it "should create a sequential process" do
|
38
|
+
subject.define_process {}
|
39
|
+
expect(subject.create_process).to be_a(Taskinator::Process::Sequential)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "#define_sequential_process" do
|
44
|
+
it "should define a #define_sequential_process method" do
|
45
|
+
expect(subject).to respond_to(:define_sequential_process)
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should not invoke the given block" do
|
49
|
+
block = SpecSupport::Block.new
|
50
|
+
expect(block).to_not receive(:call)
|
51
|
+
subject.define_sequential_process(&block)
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should raise ProcessAlreadyDefinedError error if already defined" do
|
55
|
+
subject.define_sequential_process
|
56
|
+
expect {
|
57
|
+
subject.define_sequential_process
|
58
|
+
}.to raise_error(Taskinator::Definition::ProcessAlreadyDefinedError)
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should create a sequential process" do
|
62
|
+
subject.define_sequential_process {}
|
63
|
+
expect(subject.create_process).to be_a(Taskinator::Process::Sequential)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe "#define_concurrent_process" do
|
68
|
+
it "should define a #define_concurrent_process method" do
|
69
|
+
subject.define_concurrent_process
|
70
|
+
expect(subject).to respond_to(:define_concurrent_process)
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should not invoke the given block" do
|
74
|
+
block = SpecSupport::Block.new
|
75
|
+
expect(block).to_not receive(:call)
|
76
|
+
subject.define_concurrent_process(&block)
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should raise ProcessAlreadyDefinedError error if already defined" do
|
80
|
+
subject.define_concurrent_process
|
81
|
+
expect {
|
82
|
+
subject.define_concurrent_process
|
83
|
+
}.to raise_error(Taskinator::Definition::ProcessAlreadyDefinedError)
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should create a concurrent process" do
|
87
|
+
subject.define_concurrent_process {}
|
88
|
+
expect(subject.create_process).to be_a(Taskinator::Process::Concurrent)
|
89
|
+
end
|
37
90
|
end
|
38
91
|
|
39
92
|
describe "#create_process" do
|
@@ -87,6 +87,125 @@ describe Taskinator::Persistence, :redis => true do
|
|
87
87
|
end
|
88
88
|
end
|
89
89
|
|
90
|
+
describe "serialization helpers" do
|
91
|
+
subject { Taskinator::Persistence }
|
92
|
+
|
93
|
+
describe "#serialize" do
|
94
|
+
describe "Array" do
|
95
|
+
it {
|
96
|
+
expect(subject.serialize([])).to eq(YAML.dump([]))
|
97
|
+
}
|
98
|
+
|
99
|
+
it {
|
100
|
+
expect(subject.serialize([1])).to eq(YAML.dump([1]))
|
101
|
+
}
|
102
|
+
|
103
|
+
it {
|
104
|
+
expect(subject.serialize(["string"])).to eq(YAML.dump(["string"]))
|
105
|
+
}
|
106
|
+
|
107
|
+
it {
|
108
|
+
expect(subject.serialize([MockModel.new])).to eq(YAML.dump([{:model_id => 1, :model_type => 'TypeX'}]))
|
109
|
+
}
|
110
|
+
end
|
111
|
+
|
112
|
+
describe "Hash" do
|
113
|
+
it {
|
114
|
+
expect(subject.serialize({:foo => :bar})).to eq(YAML.dump({:foo => :bar}))
|
115
|
+
}
|
116
|
+
|
117
|
+
it {
|
118
|
+
expect(subject.serialize({:foo => 1})).to eq(YAML.dump({:foo => 1}))
|
119
|
+
}
|
120
|
+
|
121
|
+
it {
|
122
|
+
expect(subject.serialize({:foo => "string"})).to eq(YAML.dump({:foo => "string"}))
|
123
|
+
}
|
124
|
+
|
125
|
+
it {
|
126
|
+
expect(subject.serialize({:foo => MockModel.new})).to eq(YAML.dump({:foo => {:model_id => 1, :model_type => 'TypeX'}}))
|
127
|
+
}
|
128
|
+
end
|
129
|
+
|
130
|
+
describe "Object" do
|
131
|
+
it {
|
132
|
+
expect(subject.serialize(:foo)).to eq(YAML.dump(:foo))
|
133
|
+
}
|
134
|
+
|
135
|
+
it {
|
136
|
+
expect(subject.serialize(1)).to eq(YAML.dump(1))
|
137
|
+
}
|
138
|
+
|
139
|
+
it {
|
140
|
+
expect(subject.serialize("string")).to eq(YAML.dump("string"))
|
141
|
+
}
|
142
|
+
|
143
|
+
it {
|
144
|
+
expect(subject.serialize(MockModel.new)).to eq(YAML.dump({:model_id => 1, :model_type => 'TypeX'}))
|
145
|
+
}
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
describe "#deserialize" do
|
150
|
+
describe "Array" do
|
151
|
+
it {
|
152
|
+
expect(subject.deserialize(YAML.dump([]))).to eq([])
|
153
|
+
}
|
154
|
+
|
155
|
+
it {
|
156
|
+
expect(subject.deserialize(YAML.dump([1]))).to eq([1])
|
157
|
+
}
|
158
|
+
|
159
|
+
it {
|
160
|
+
expect(subject.deserialize(YAML.dump(["string"]))).to eq(["string"])
|
161
|
+
}
|
162
|
+
|
163
|
+
it {
|
164
|
+
expect_any_instance_of(MockModel).to receive(:find)
|
165
|
+
subject.deserialize(YAML.dump([MockModel.new]))
|
166
|
+
}
|
167
|
+
end
|
168
|
+
|
169
|
+
describe "Hash" do
|
170
|
+
it {
|
171
|
+
expect(subject.deserialize(YAML.dump({:foo => :bar}))).to eq({:foo => :bar})
|
172
|
+
}
|
173
|
+
|
174
|
+
it {
|
175
|
+
expect(subject.deserialize(YAML.dump({:foo => 1}))).to eq({:foo => 1})
|
176
|
+
}
|
177
|
+
|
178
|
+
it {
|
179
|
+
expect(subject.deserialize(YAML.dump({:foo => "string"}))).to eq({:foo => "string"})
|
180
|
+
}
|
181
|
+
|
182
|
+
it {
|
183
|
+
expect_any_instance_of(MockModel).to receive(:find)
|
184
|
+
subject.deserialize(YAML.dump({:foo => MockModel.new}))
|
185
|
+
}
|
186
|
+
end
|
187
|
+
|
188
|
+
describe "Object" do
|
189
|
+
it {
|
190
|
+
expect(subject.deserialize(YAML.dump(:foo))).to eq(:foo)
|
191
|
+
}
|
192
|
+
|
193
|
+
it {
|
194
|
+
expect(subject.deserialize(YAML.dump(1))).to eq(1)
|
195
|
+
}
|
196
|
+
|
197
|
+
it {
|
198
|
+
expect(subject.deserialize(YAML.dump("string"))).to eq("string")
|
199
|
+
}
|
200
|
+
|
201
|
+
it {
|
202
|
+
expect_any_instance_of(MockModel).to receive(:find)
|
203
|
+
subject.deserialize(YAML.dump(MockModel.new))
|
204
|
+
}
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
90
209
|
describe "instance methods" do
|
91
210
|
subject {
|
92
211
|
klass = Class.new do
|
@@ -63,19 +63,31 @@ describe Taskinator::Process do
|
|
63
63
|
describe "workflow" do
|
64
64
|
describe "#enqueue!" do
|
65
65
|
it { expect(subject).to respond_to(:enqueue!) }
|
66
|
+
|
66
67
|
it {
|
67
68
|
expect(subject).to receive(:enqueue)
|
68
69
|
subject.enqueue!
|
69
70
|
}
|
71
|
+
|
70
72
|
it {
|
73
|
+
expect(subject.current_state.name).to eq(:initial)
|
71
74
|
subject.enqueue!
|
72
75
|
expect(subject.current_state.name).to eq(:enqueued)
|
73
76
|
}
|
74
|
-
|
77
|
+
|
78
|
+
it "should not enqueue if there aren't any tasks" do
|
79
|
+
expect {
|
80
|
+
subject.enqueue!
|
81
|
+
}.to change { Taskinator.queue.processes.length }.by(0)
|
82
|
+
end
|
83
|
+
|
84
|
+
it "should enqueue if there are tasks" do
|
85
|
+
allow(subject).to receive(:tasks).and_return([Object.new])
|
86
|
+
|
75
87
|
expect {
|
76
88
|
subject.enqueue!
|
77
89
|
}.to change { Taskinator.queue.processes.length }.by(1)
|
78
|
-
|
90
|
+
end
|
79
91
|
end
|
80
92
|
|
81
93
|
describe "#start!" do
|
@@ -85,6 +97,7 @@ describe Taskinator::Process do
|
|
85
97
|
subject.start!
|
86
98
|
}
|
87
99
|
it {
|
100
|
+
expect(subject.current_state.name).to eq(:initial)
|
88
101
|
subject.start!
|
89
102
|
expect(subject.current_state.name).to eq(:processing)
|
90
103
|
}
|
@@ -97,6 +110,7 @@ describe Taskinator::Process do
|
|
97
110
|
subject.cancel!
|
98
111
|
}
|
99
112
|
it {
|
113
|
+
expect(subject.current_state.name).to eq(:initial)
|
100
114
|
subject.cancel!
|
101
115
|
expect(subject.current_state.name).to eq(:cancelled)
|
102
116
|
}
|
@@ -205,6 +219,13 @@ describe Taskinator::Process do
|
|
205
219
|
describe "#reload" do
|
206
220
|
it { expect(subject.reload).to_not be }
|
207
221
|
end
|
222
|
+
|
223
|
+
describe "#tasks_count" do
|
224
|
+
it {
|
225
|
+
expect(subject.tasks_count).to eq(0)
|
226
|
+
}
|
227
|
+
end
|
228
|
+
|
208
229
|
end
|
209
230
|
|
210
231
|
describe Taskinator::Process::Sequential do
|
@@ -318,6 +339,10 @@ describe Taskinator::Process do
|
|
318
339
|
subject.accept(visitor)
|
319
340
|
}
|
320
341
|
end
|
342
|
+
|
343
|
+
describe "#inspect" do
|
344
|
+
it { expect(subject.inspect).to_not be_nil }
|
345
|
+
end
|
321
346
|
end
|
322
347
|
|
323
348
|
describe Taskinator::Process::Concurrent do
|
@@ -463,6 +488,10 @@ describe Taskinator::Process do
|
|
463
488
|
subject.accept(visitor)
|
464
489
|
}
|
465
490
|
end
|
491
|
+
|
492
|
+
describe "#inspect" do
|
493
|
+
it { expect(subject.inspect).to_not be_nil }
|
494
|
+
end
|
466
495
|
end
|
467
496
|
|
468
497
|
end
|
@@ -161,6 +161,12 @@ describe Taskinator::Task do
|
|
161
161
|
describe "#reload" do
|
162
162
|
it { expect(subject.reload).to_not be }
|
163
163
|
end
|
164
|
+
|
165
|
+
describe "#tasks_count" do
|
166
|
+
it {
|
167
|
+
expect(subject.tasks_count).to eq(0)
|
168
|
+
}
|
169
|
+
end
|
164
170
|
end
|
165
171
|
|
166
172
|
describe Taskinator::Task::Step do
|
@@ -197,6 +203,34 @@ describe Taskinator::Task do
|
|
197
203
|
subject.start!
|
198
204
|
end
|
199
205
|
|
206
|
+
it "provides execution context" do
|
207
|
+
executor = Taskinator::Executor.new(definition, subject)
|
208
|
+
|
209
|
+
method = subject.method
|
210
|
+
|
211
|
+
executor.class_eval do
|
212
|
+
define_method method do |*args|
|
213
|
+
# this method executes in the scope of the executor
|
214
|
+
# store the context in an instance variable
|
215
|
+
@exec_context = self
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
# replace the internal executor instance for the task
|
220
|
+
# with this one, so we can hook into the methods
|
221
|
+
subject.instance_eval { @executor = executor }
|
222
|
+
|
223
|
+
# task start will invoke the method on the executor
|
224
|
+
subject.start!
|
225
|
+
|
226
|
+
# extract the instance variable
|
227
|
+
exec_context = executor.instance_eval { @exec_context }
|
228
|
+
|
229
|
+
expect(exec_context).to eq(executor)
|
230
|
+
expect(exec_context.uuid).to eq(subject.uuid)
|
231
|
+
expect(exec_context.options).to eq(subject.options)
|
232
|
+
end
|
233
|
+
|
200
234
|
it "is instrumented" do
|
201
235
|
allow(subject.executor).to receive(subject.method)
|
202
236
|
|
@@ -239,6 +273,10 @@ describe Taskinator::Task do
|
|
239
273
|
subject.accept(visitor)
|
240
274
|
}
|
241
275
|
end
|
276
|
+
|
277
|
+
describe "#inspect" do
|
278
|
+
it { expect(subject.inspect).to_not be_nil }
|
279
|
+
end
|
242
280
|
end
|
243
281
|
|
244
282
|
describe Taskinator::Task::Job do
|
@@ -313,6 +351,10 @@ describe Taskinator::Task do
|
|
313
351
|
subject.accept(visitor)
|
314
352
|
}
|
315
353
|
end
|
354
|
+
|
355
|
+
describe "#inspect" do
|
356
|
+
it { expect(subject.inspect).to_not be_nil }
|
357
|
+
end
|
316
358
|
end
|
317
359
|
|
318
360
|
describe Taskinator::Task::SubProcess do
|
@@ -384,6 +426,10 @@ describe Taskinator::Task do
|
|
384
426
|
subject.accept(visitor)
|
385
427
|
}
|
386
428
|
end
|
429
|
+
|
430
|
+
describe "#inspect" do
|
431
|
+
it { expect(subject.inspect).to_not be_nil }
|
432
|
+
end
|
387
433
|
end
|
388
434
|
|
389
435
|
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe TestFlows do
|
4
|
+
|
5
|
+
[
|
6
|
+
TestFlows::Task,
|
7
|
+
TestFlows::Job,
|
8
|
+
TestFlows::SubProcess,
|
9
|
+
TestFlows::Sequential,
|
10
|
+
TestFlows::Sequential
|
11
|
+
].each do |definition|
|
12
|
+
|
13
|
+
describe definition.name do
|
14
|
+
|
15
|
+
it "should have one task" do
|
16
|
+
process = definition.create_process(1)
|
17
|
+
expect(process.tasks_count).to eq(1)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should have 3 tasks" do
|
21
|
+
process = definition.create_process(3)
|
22
|
+
expect(process.tasks_count).to eq(3)
|
23
|
+
end
|
24
|
+
|
25
|
+
%w(
|
26
|
+
failed
|
27
|
+
cancelled
|
28
|
+
completed
|
29
|
+
).each do |status|
|
30
|
+
|
31
|
+
describe "count_#{status}" do
|
32
|
+
it {
|
33
|
+
process = definition.create_process(1)
|
34
|
+
expect(process.send(:"count_#{status}")).to eq(0)
|
35
|
+
}
|
36
|
+
|
37
|
+
it {
|
38
|
+
process = definition.create_process(2)
|
39
|
+
expect(process.send(:"count_#{status}")).to eq(0)
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "incr_#{status}" do
|
44
|
+
it {
|
45
|
+
process = definition.create_process(1)
|
46
|
+
process.send(:"incr_#{status}")
|
47
|
+
expect(process.send(:"count_#{status}")).to eq(1)
|
48
|
+
}
|
49
|
+
|
50
|
+
it {
|
51
|
+
process = definition.create_process(4)
|
52
|
+
4.times do |i|
|
53
|
+
process.send(:"incr_#{status}")
|
54
|
+
expect(process.send(:"count_#{status}")).to eq(i + 1)
|
55
|
+
end
|
56
|
+
}
|
57
|
+
|
58
|
+
it "should increment completed count" do
|
59
|
+
process = definition.create_process(10)
|
60
|
+
recursively_enumerate_tasks(process.tasks) do |task|
|
61
|
+
task.send(:"incr_#{status}")
|
62
|
+
end
|
63
|
+
expect(process.send(:"count_#{status}")).to eq(10)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe "percentage_#{status}" do
|
68
|
+
it {
|
69
|
+
process = definition.create_process(1)
|
70
|
+
expect(process.send(:"percentage_#{status}")).to eq(0.0)
|
71
|
+
}
|
72
|
+
|
73
|
+
it {
|
74
|
+
process = definition.create_process(4)
|
75
|
+
expect(process.send(:"percentage_#{status}")).to eq(0.0)
|
76
|
+
|
77
|
+
count = 4
|
78
|
+
count.times do |i|
|
79
|
+
process.send(:"incr_#{status}")
|
80
|
+
expect(process.send(:"percentage_#{status}")).to eq( ((i + 1.0) / count) * 100.0 )
|
81
|
+
end
|
82
|
+
}
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: taskinator
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.18
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chris Stefano
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-07-
|
11
|
+
date: 2015-07-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: redis
|
@@ -287,10 +287,12 @@ files:
|
|
287
287
|
- spec/spec_helper.rb
|
288
288
|
- spec/support/delayed_job.rb
|
289
289
|
- spec/support/mock_definition.rb
|
290
|
+
- spec/support/mock_model.rb
|
290
291
|
- spec/support/sidekiq_matchers.rb
|
291
292
|
- spec/support/spec_support.rb
|
292
293
|
- spec/support/test_definition.rb
|
293
294
|
- spec/support/test_flow.rb
|
295
|
+
- spec/support/test_flows.rb
|
294
296
|
- spec/support/test_process.rb
|
295
297
|
- spec/support/test_queue.rb
|
296
298
|
- spec/support/test_task.rb
|
@@ -312,6 +314,7 @@ files:
|
|
312
314
|
- spec/taskinator/task_worker_spec.rb
|
313
315
|
- spec/taskinator/taskinator_spec.rb
|
314
316
|
- spec/taskinator/tasks_spec.rb
|
317
|
+
- spec/taskinator/test_flows_spec.rb
|
315
318
|
- spec/taskinator/visitor_spec.rb
|
316
319
|
- taskinator.gemspec
|
317
320
|
- tasks_workflow.png
|
@@ -347,10 +350,12 @@ test_files:
|
|
347
350
|
- spec/spec_helper.rb
|
348
351
|
- spec/support/delayed_job.rb
|
349
352
|
- spec/support/mock_definition.rb
|
353
|
+
- spec/support/mock_model.rb
|
350
354
|
- spec/support/sidekiq_matchers.rb
|
351
355
|
- spec/support/spec_support.rb
|
352
356
|
- spec/support/test_definition.rb
|
353
357
|
- spec/support/test_flow.rb
|
358
|
+
- spec/support/test_flows.rb
|
354
359
|
- spec/support/test_process.rb
|
355
360
|
- spec/support/test_queue.rb
|
356
361
|
- spec/support/test_task.rb
|
@@ -372,4 +377,5 @@ test_files:
|
|
372
377
|
- spec/taskinator/task_worker_spec.rb
|
373
378
|
- spec/taskinator/taskinator_spec.rb
|
374
379
|
- spec/taskinator/tasks_spec.rb
|
380
|
+
- spec/taskinator/test_flows_spec.rb
|
375
381
|
- spec/taskinator/visitor_spec.rb
|