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 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