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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3936550148208b98dfaa2a841e97d322d14413b9
4
- data.tar.gz: 92c8caa24c938962f8c5692dc5e45c9cfef012e2
3
+ metadata.gz: bd4b644d746cddfe50ed5d3c9d984bcd978d3fee
4
+ data.tar.gz: 45f8166dbd3f7f1b5e27d52c31c7e0384a433cc7
5
5
  SHA512:
6
- metadata.gz: 21b0b016cbdec925215d9a621c60859b2c8ff69cee341e350e0962b557b6b1b27338df3cf5b84a24c7858221461571f5710e7a44af14531f8e7d1ac6e5ce676f
7
- data.tar.gz: ca89c615cc65798746ccbdcfdb14eca8ac0d5617f24359f1402bd7001e802a4ee6ddbc21cd43fece92971e76b5d22e421dd46c12a43e6090a0f8ea21267284d1
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
@@ -8,7 +8,7 @@ GIT
8
8
  PATH
9
9
  remote: .
10
10
  specs:
11
- taskinator (0.0.17)
11
+ taskinator (0.0.18)
12
12
  connection_pool (>= 2.2.0)
13
13
  json (>= 1.8.2)
14
14
  redis (>= 3.2.1)
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.
@@ -3,7 +3,7 @@ module Taskinator
3
3
  class Processes
4
4
  include Enumerable
5
5
 
6
- def each
6
+ def each(&block)
7
7
  return to_enum(__method__) unless block_given?
8
8
 
9
9
  instance_cache = {}
@@ -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 |args, options={}|
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
- # instrument separately
52
- Taskinator.instrumenter.instrument(:save_process, :uuid => process.uuid) do
53
- process.save
54
- # if this is a root process, then add it to the list
55
- Persistence.add_process_to_list(process) unless options[:subprocess]
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
@@ -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 :include, definition
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.sadd "taskinator:#{self.class.base_key}", self.uuid
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
- def initialize(conn, instance, parent=nil)
130
- @conn = conn
131
- @instance = instance
132
- @key = instance.key
133
- # @parent = parent # not using this yet
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, @instance).visit
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, @instance).visit
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
- attr_reader :uuid # shadows the real method, but will be the same!
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
@@ -122,7 +122,13 @@ module Taskinator
122
122
  include Persistence
123
123
 
124
124
  def enqueue
125
- Taskinator.queue.enqueue_process(self)
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.any?
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
 
@@ -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
 
@@ -42,7 +42,7 @@ module Taskinator
42
42
  end
43
43
 
44
44
  def inspect
45
- %(#<#{self.class.name}:0x#{self.__id__.to_s(16)} tasks=[#{collect(&:inspect).join(', ')}]>)
45
+ %([#{collect(&:inspect).join(', ')}])
46
46
  end
47
47
 
48
48
  end
@@ -1,3 +1,3 @@
1
1
  module Taskinator
2
- VERSION = "0.0.17"
2
+ VERSION = "0.0.18"
3
3
  end
@@ -21,6 +21,10 @@ module Taskinator
21
21
 
22
22
  def visit_args(attribute)
23
23
  end
24
+
25
+ def task_count
26
+ # return the total count of all tasks
27
+ end
24
28
  end
25
29
  end
26
30
  end
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
- it {
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
@@ -9,5 +9,6 @@ describe Taskinator::Visitor::Base do
9
9
  it { respond_to(:visit_task_reference) }
10
10
  it { respond_to(:visit_type) }
11
11
  it { respond_to(:visit_args) }
12
+ it { respond_to(:task_count) }
12
13
 
13
14
  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.17
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 00:00:00.000000000 Z
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