taskinator 0.0.17 → 0.0.18
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.
- 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
|