taskinator 0.0.15 → 0.0.16

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +77 -0
  3. data/Gemfile.lock +2 -1
  4. data/README.md +1 -1
  5. data/lib/taskinator/api.rb +3 -1
  6. data/lib/taskinator/create_process_worker.rb +60 -0
  7. data/lib/taskinator/definition.rb +47 -8
  8. data/lib/taskinator/job_worker.rb +2 -2
  9. data/lib/taskinator/logger.rb +13 -75
  10. data/lib/taskinator/persistence.rb +48 -40
  11. data/lib/taskinator/process.rb +13 -6
  12. data/lib/taskinator/process_worker.rb +3 -1
  13. data/lib/taskinator/queues/delayed_job.rb +12 -5
  14. data/lib/taskinator/queues/resque.rb +16 -5
  15. data/lib/taskinator/queues/sidekiq.rb +14 -5
  16. data/lib/taskinator/queues.rb +12 -0
  17. data/lib/taskinator/task.rb +21 -8
  18. data/lib/taskinator/task_worker.rb +3 -1
  19. data/lib/taskinator/tasks.rb +1 -1
  20. data/lib/taskinator/version.rb +1 -1
  21. data/lib/taskinator.rb +20 -1
  22. data/spec/examples/queue_adapter_examples.rb +10 -0
  23. data/spec/spec_helper.rb +11 -0
  24. data/spec/support/mock_definition.rb +22 -0
  25. data/spec/support/spec_support.rb +3 -1
  26. data/spec/support/test_definition.rb +3 -0
  27. data/spec/support/test_process.rb +2 -0
  28. data/spec/support/test_queue.rb +7 -1
  29. data/spec/support/test_task.rb +2 -0
  30. data/spec/taskinator/api_spec.rb +2 -2
  31. data/spec/taskinator/create_process_worker_spec.rb +44 -0
  32. data/spec/taskinator/definition/builder_spec.rb +5 -5
  33. data/spec/taskinator/definition_spec.rb +103 -6
  34. data/spec/taskinator/executor_spec.rb +1 -1
  35. data/spec/taskinator/job_worker_spec.rb +3 -3
  36. data/spec/taskinator/persistence_spec.rb +12 -19
  37. data/spec/taskinator/process_spec.rb +29 -5
  38. data/spec/taskinator/process_worker_spec.rb +3 -3
  39. data/spec/taskinator/queues/delayed_job_spec.rb +23 -6
  40. data/spec/taskinator/queues/resque_spec.rb +27 -6
  41. data/spec/taskinator/queues/sidekiq_spec.rb +28 -10
  42. data/spec/taskinator/task_spec.rb +80 -7
  43. data/spec/taskinator/task_worker_spec.rb +3 -3
  44. data/spec/taskinator/{intermodal_spec.rb → taskinator_spec.rb} +35 -1
  45. data/spec/taskinator/tasks_spec.rb +1 -2
  46. data/taskinator.gemspec +17 -15
  47. metadata +30 -4
@@ -9,11 +9,12 @@ module Taskinator
9
9
 
10
10
  class SidekiqAdapter
11
11
  def initialize(config={})
12
- @config = {
13
- :process_queue => :default,
14
- :task_queue => :default,
15
- :job_queue => :default
16
- }.merge(config)
12
+ @config = Taskinator::Queues::DefaultConfig.merge(config)
13
+ end
14
+
15
+ def enqueue_create_process(definition, uuid, args)
16
+ queue = definition.queue || @config[:definition_queue]
17
+ ProcessWorker.client_push('class' => CreateProcessWorker, 'args' => [definition.name, uuid, Taskinator::Persistence.serialize(args)], 'queue' => queue)
17
18
  end
18
19
 
19
20
  def enqueue_process(process)
@@ -31,6 +32,14 @@ module Taskinator
31
32
  JobWorker.client_push('class' => JobWorker, 'args' => [job.uuid], 'queue' => queue)
32
33
  end
33
34
 
35
+ class CreateProcessWorker
36
+ include ::Sidekiq::Worker
37
+
38
+ def perform(definition_name, uuid, args)
39
+ Taskinator::CreateProcessWorker.new(definition_name, uuid, args).perform
40
+ end
41
+ end
42
+
34
43
  class ProcessWorker
35
44
  include ::Sidekiq::Worker
36
45
 
@@ -1,6 +1,13 @@
1
1
  module Taskinator
2
2
  module Queues
3
3
 
4
+ DefaultConfig = {
5
+ :definition_queue => :default,
6
+ :process_queue => :default,
7
+ :task_queue => :default,
8
+ :job_queue => :default,
9
+ }.freeze
10
+
4
11
  def self.create_adapter(adapter, config={})
5
12
  begin
6
13
  LoggedAdapter.new(send("create_#{adapter}_adapter", config))
@@ -22,6 +29,11 @@ module Taskinator
22
29
  adapter
23
30
  end
24
31
 
32
+ def enqueue_create_process(definition, uuid, args)
33
+ Taskinator.logger.info("Enqueuing process creation for #{definition}")
34
+ adapter.enqueue_create_process(definition, uuid, args)
35
+ end
36
+
25
37
  def enqueue_process(process)
26
38
  Taskinator.logger.info("Enqueuing process #{process}")
27
39
  adapter.enqueue_process(process)
@@ -24,6 +24,7 @@ module Taskinator
24
24
  attr_reader :process
25
25
  attr_reader :uuid
26
26
  attr_reader :options
27
+ attr_reader :queue
27
28
 
28
29
  # the next task in the sequence
29
30
  attr_accessor :next
@@ -34,6 +35,7 @@ module Taskinator
34
35
  @uuid = SecureRandom.uuid
35
36
  @process = process
36
37
  @options = options
38
+ @queue = options.delete(:queue)
37
39
  end
38
40
 
39
41
  def accept(visitor)
@@ -41,6 +43,7 @@ module Taskinator
41
43
  visitor.visit_process_reference(:process)
42
44
  visitor.visit_task_reference(:next)
43
45
  visitor.visit_args(:options)
46
+ visitor.visit_attribute(:queue)
44
47
  end
45
48
 
46
49
  def <=>(other)
@@ -51,10 +54,6 @@ module Taskinator
51
54
  "#<#{self.class.name}:#{uuid}>"
52
55
  end
53
56
 
54
- def queue
55
- options[:queue]
56
- end
57
-
58
57
  workflow do
59
58
  state :initial do
60
59
  event :enqueue, :transitions_to => :enqueued
@@ -150,7 +149,9 @@ module Taskinator
150
149
 
151
150
  def start
152
151
  # ASSUMPTION: when the method returns, the task is considered to be complete
153
- executor.send(method, *args)
152
+ Taskinator.instrumenter.instrument(:execute_step_task, :uuid => uuid) do
153
+ executor.send(method, *args)
154
+ end
154
155
  @is_complete = true
155
156
  end
156
157
 
@@ -189,8 +190,10 @@ module Taskinator
189
190
  Taskinator.queue.enqueue_job(self)
190
191
  end
191
192
 
192
- def perform(&block)
193
- yield(job, args)
193
+ def perform
194
+ Taskinator.instrumenter.instrument(:execute_job_task, :uuid => uuid) do
195
+ yield(job, args)
196
+ end
194
197
  @is_complete = true
195
198
  end
196
199
 
@@ -220,7 +223,9 @@ module Taskinator
220
223
  end
221
224
 
222
225
  def start
223
- sub_process.start!
226
+ Taskinator.instrumenter.instrument(:execute_subprocess, :uuid => uuid) do
227
+ sub_process.start!
228
+ end
224
229
  end
225
230
 
226
231
  def can_complete_task?
@@ -233,5 +238,13 @@ module Taskinator
233
238
  visitor.visit_process(:sub_process)
234
239
  end
235
240
  end
241
+
242
+ # reloads the task from storage
243
+ # NB: only implemented by LazyLoader so that
244
+ # the task can be lazy loaded, thereafter
245
+ # it has no effect
246
+ def reload
247
+ false
248
+ end
236
249
  end
237
250
  end
@@ -1,5 +1,7 @@
1
1
  module Taskinator
2
2
  class TaskWorker
3
+ attr_reader :uuid
4
+
3
5
  def initialize(uuid)
4
6
  @uuid = uuid
5
7
  end
@@ -10,7 +12,7 @@ module Taskinator
10
12
  begin
11
13
  task.start!
12
14
  task.complete! if task.can_complete?
13
- rescue Exception => e
15
+ rescue => e
14
16
  Taskinator.logger.error(e)
15
17
  task.fail!(e)
16
18
  raise e
@@ -31,7 +31,7 @@ module Taskinator
31
31
  @head.nil?
32
32
  end
33
33
 
34
- def each(&block)
34
+ def each
35
35
  return to_enum(__method__) unless block_given?
36
36
 
37
37
  current = @head
@@ -1,3 +1,3 @@
1
1
  module Taskinator
2
- VERSION = "0.0.15"
2
+ VERSION = "0.0.16"
3
3
  end
data/lib/taskinator.rb CHANGED
@@ -21,6 +21,7 @@ require 'taskinator/process'
21
21
  require 'taskinator/task_worker'
22
22
  require 'taskinator/job_worker'
23
23
  require 'taskinator/process_worker'
24
+ require 'taskinator/create_process_worker'
24
25
 
25
26
  require 'taskinator/executor'
26
27
  require 'taskinator/queues'
@@ -46,7 +47,7 @@ module Taskinator
46
47
  ##
47
48
  # Configuration for Taskinator client, use like:
48
49
  #
49
- # Taskinator.configure_client do |config|
50
+ # Taskinator.configure do |config|
50
51
  # config.redis = { :namespace => 'myapp', :pool_size => 1, :url => 'redis://myhost:8877/0' }
51
52
  # config.queue_config = { :process_queue => 'processes', :task_queue => 'tasks' }
52
53
  # end
@@ -101,5 +102,23 @@ module Taskinator
101
102
  config = queue_config || {}
102
103
  @queue ||= Taskinator::Queues.create_adapter(adapter, config)
103
104
  end
105
+
106
+ # set the instrumenter to use.
107
+ # can be ActiveSupport::Notifications
108
+ def instrumenter
109
+ @instrumenter ||= NoOpInstrumenter.new
110
+ end
111
+ def instrumenter=(value)
112
+ @instrumenter = value
113
+ end
114
+
104
115
  end
116
+
117
+ class NoOpInstrumenter
118
+ # just yield to the given block
119
+ def instrument(event, payload={})
120
+ yield(payload) if block_given?
121
+ end
122
+ end
123
+
105
124
  end
@@ -10,6 +10,16 @@ shared_examples_for "a queue adapter" do |adapter_name, adapter_type|
10
10
  expect(Taskinator.queue.adapter).to be_a(adapter_type)
11
11
  end
12
12
 
13
+ describe "#enqueue_create_process" do
14
+ it { expect(subject).to respond_to(:enqueue_create_process) }
15
+
16
+ it "should enqueue a create process" do
17
+ expect {
18
+ subject.enqueue_create_process(double('definition', :name => 'definition', :queue => nil), 'xx-xx-xx-xx', :foo => :bar)
19
+ }.to_not raise_error
20
+ end
21
+ end
22
+
13
23
  describe "#enqueue_process" do
14
24
  it { expect(subject).to respond_to(:enqueue_process) }
15
25
 
data/spec/spec_helper.rb CHANGED
@@ -4,6 +4,7 @@ Bundler.setup
4
4
  require 'simplecov'
5
5
  require 'coveralls'
6
6
  require 'pry'
7
+ require 'active_support/notifications'
7
8
 
8
9
  SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
9
10
  SimpleCov::Formatter::HTMLFormatter,
@@ -26,6 +27,16 @@ ResqueSpec.disable_ext = false
26
27
 
27
28
  require 'taskinator'
28
29
 
30
+ Taskinator.configure do |config|
31
+
32
+ # use active support for instrumentation
33
+ config.instrumenter = ActiveSupport::Notifications
34
+
35
+ # use a "null stream" for logging
36
+ config.logger = Logger.new(File::NULL)
37
+
38
+ end
39
+
29
40
  # require supporting files with custom matchers and macros, etc
30
41
  Dir[File.expand_path("../support/**/*.rb", __FILE__)].each {|f| require f }
31
42
 
@@ -0,0 +1,22 @@
1
+ module MockDefinition
2
+
3
+ class << self
4
+ def create(queue=nil)
5
+
6
+ definition = Module.new do
7
+ extend Taskinator::Definition
8
+
9
+ define_process :foo_hash do
10
+ # empty on purpose
11
+ end
12
+ end
13
+
14
+ definition.queue = queue
15
+
16
+ # create a constant, so that the mock definition isn't anonymous
17
+ Object.const_set("Mock#{SecureRandom.hex}Definition", definition)
18
+
19
+ end
20
+ end
21
+
22
+ end
@@ -1,7 +1,9 @@
1
1
  module SpecSupport
2
2
  class Block
3
3
  def to_proc
4
- lambda { |*args| call(*args) }
4
+ lambda { |*args|
5
+ call(*args)
6
+ }
5
7
  end
6
8
 
7
9
  # the call method must be provided by in specs
@@ -0,0 +1,3 @@
1
+ module TestDefinition
2
+ extend Taskinator::Definition
3
+ end
@@ -0,0 +1,2 @@
1
+ class TestProcess < Taskinator::Process
2
+ end
@@ -2,11 +2,12 @@ module Taskinator
2
2
  module Queues
3
3
 
4
4
  def self.create_test_queue_adapter(config={})
5
- TestQueueAdapter::new()
5
+ TestQueueAdapter.new
6
6
  end
7
7
 
8
8
  class TestQueueAdapter
9
9
 
10
+ attr_reader :creates
10
11
  attr_reader :processes
11
12
  attr_reader :tasks
12
13
  attr_reader :jobs
@@ -16,11 +17,16 @@ module Taskinator
16
17
  end
17
18
 
18
19
  def clear
20
+ @creates = []
19
21
  @processes = []
20
22
  @tasks = []
21
23
  @jobs = []
22
24
  end
23
25
 
26
+ def enqueue_create_process(definition, uuid, args)
27
+ @creates << [definition, uuid, args]
28
+ end
29
+
24
30
  def enqueue_process(process)
25
31
  @processes << process
26
32
  end
@@ -0,0 +1,2 @@
1
+ class TestTask < Taskinator::Task
2
+ end
@@ -8,7 +8,7 @@ describe Taskinator::Api, :redis => true do
8
8
 
9
9
  describe "#each" do
10
10
  it "does not enumerate when there aren't any processes" do
11
- block = SpecSupport::Block.new()
11
+ block = SpecSupport::Block.new
12
12
  expect(block).to_not receive(:call)
13
13
  subject.each(&block)
14
14
  end
@@ -22,7 +22,7 @@ describe Taskinator::Api, :redis => true do
22
22
  end
23
23
  end
24
24
 
25
- block = SpecSupport::Block.new()
25
+ block = SpecSupport::Block.new
26
26
  expect(block).to receive(:call).exactly(3).times
27
27
 
28
28
  subject.each(&block)
@@ -0,0 +1,44 @@
1
+ require 'spec_helper'
2
+
3
+ describe Taskinator::CreateProcessWorker do
4
+
5
+ let(:definition) { MockDefinition.create }
6
+ let(:uuid) { SecureRandom.uuid }
7
+ let(:args) { {:foo => :bar} }
8
+
9
+ subject { Taskinator::CreateProcessWorker.new(definition.name, uuid, Taskinator::Persistence.serialize(:foo => :bar)) }
10
+
11
+ describe "#initialize" do
12
+ it {
13
+ expect(subject.definition).to eq(definition)
14
+ }
15
+
16
+ it {
17
+ MockDefinition.const_set(definition.name, definition)
18
+ Taskinator::CreateProcessWorker.new("MockDefinition::#{definition.name}", uuid, Taskinator::Persistence.serialize(:foo => :bar))
19
+ expect(subject.definition).to eq(definition)
20
+ }
21
+
22
+ it {
23
+ expect(subject.uuid).to eq(uuid)
24
+ }
25
+
26
+ it {
27
+ expect(subject.args).to eq(args)
28
+ }
29
+ end
30
+
31
+ describe "#perform" do
32
+ it "should create the process" do
33
+ expect(definition).to receive(:_create_process_).with(*args, :uuid => uuid).and_return(double('process', :enqueue! => nil))
34
+ subject.perform
35
+ end
36
+
37
+ it "should enqueue the process" do
38
+ expect_any_instance_of(Taskinator::Process).to receive(:enqueue!)
39
+ subject.perform
40
+ end
41
+ end
42
+
43
+
44
+ end
@@ -3,7 +3,7 @@ require 'spec_helper'
3
3
  describe Taskinator::Definition::Builder do
4
4
 
5
5
  let(:definition) do
6
- Module.new() do
6
+ Module.new do
7
7
  extend Taskinator::Definition
8
8
 
9
9
  def iterator_method(*); end
@@ -17,7 +17,7 @@ describe Taskinator::Definition::Builder do
17
17
 
18
18
  let(:args) { [:arg1, :arg2, {:option => 1, :another => false}] }
19
19
 
20
- let(:block) { SpecSupport::Block.new() }
20
+ let(:block) { SpecSupport::Block.new }
21
21
 
22
22
  let(:define_block) {
23
23
  the_block = block
@@ -64,7 +64,7 @@ describe Taskinator::Definition::Builder do
64
64
 
65
65
  it "fails if block isn't given" do
66
66
  expect {
67
- subject.sequential()
67
+ subject.sequential
68
68
  }.to raise_error(ArgumentError)
69
69
  end
70
70
  end
@@ -83,7 +83,7 @@ describe Taskinator::Definition::Builder do
83
83
 
84
84
  it "fails if block isn't given" do
85
85
  expect {
86
- subject.concurrent()
86
+ subject.concurrent
87
87
  }.to raise_error(ArgumentError)
88
88
  end
89
89
  end
@@ -187,7 +187,7 @@ describe Taskinator::Definition::Builder do
187
187
 
188
188
  describe "#sub_process" do
189
189
  let(:sub_definition) do
190
- Module.new() do
190
+ Module.new do
191
191
  extend Taskinator::Definition
192
192
 
193
193
  define_process :some_arg1, :some_arg2, :some_arg3 do
@@ -3,7 +3,7 @@ require 'spec_helper'
3
3
  describe Taskinator::Definition do
4
4
 
5
5
  subject do
6
- Module.new() do
6
+ Module.new do
7
7
  extend Taskinator::Definition
8
8
  end
9
9
  end
@@ -23,21 +23,28 @@ describe Taskinator::Definition do
23
23
  end
24
24
 
25
25
  it "should not invoke the given block" do
26
- block = SpecSupport::Block.new()
26
+ block = SpecSupport::Block.new
27
27
  expect(block).to_not receive(:call)
28
28
  subject.define_process(&block)
29
29
  end
30
+
31
+ it "should raise ProcessAlreadyDefinedError error if already defined" do
32
+ subject.define_process
33
+ expect {
34
+ subject.define_process
35
+ }.to raise_error(Taskinator::Definition::ProcessAlreadyDefinedError)
36
+ end
30
37
  end
31
38
 
32
39
  describe "#create_process" do
33
- it "raises UndefinedProcessError" do
40
+ it "raises ProcessUndefinedError" do
34
41
  expect {
35
42
  subject.create_process
36
- }.to raise_error(Taskinator::Definition::UndefinedProcessError)
43
+ }.to raise_error(Taskinator::Definition::ProcessUndefinedError)
37
44
  end
38
45
 
39
46
  it "returns a process" do
40
- block = SpecSupport::Block.new()
47
+ block = SpecSupport::Block.new
41
48
  allow(block).to receive(:to_proc) {
42
49
  Proc.new {|*args| }
43
50
  }
@@ -47,7 +54,7 @@ describe Taskinator::Definition do
47
54
  end
48
55
 
49
56
  it "invokes the given block in the context of a ProcessBuilder" do
50
- block = SpecSupport::Block.new()
57
+ block = SpecSupport::Block.new
51
58
  expect(block).to receive(:call)
52
59
 
53
60
  subject.define_process do
@@ -67,6 +74,96 @@ describe Taskinator::Definition do
67
74
  subject.create_process
68
75
  }.to_not raise_error
69
76
  end
77
+
78
+ context "is instrumented" do
79
+ subject { MockDefinition.create }
80
+
81
+ it "for create process" do
82
+ instrumentation_block = SpecSupport::Block.new
83
+ expect(instrumentation_block).to receive(:call)
84
+
85
+ # temporary subscription
86
+ ActiveSupport::Notifications.subscribed(instrumentation_block, /create_process/) do
87
+ subject.create_process :foo
88
+ end
89
+ end
90
+
91
+ it "for save process" do
92
+ instrumentation_block = SpecSupport::Block.new
93
+ expect(instrumentation_block).to receive(:call)
94
+
95
+ # temporary subscription
96
+ ActiveSupport::Notifications.subscribed(instrumentation_block, /save_process/) do
97
+ subject.create_process :foo
98
+ end
99
+ end
100
+ end
101
+ end
102
+
103
+ describe "#create_process_remotely" do
104
+ it "raises ProcessUndefinedError" do
105
+ expect {
106
+ subject.create_process_remotely
107
+ }.to raise_error(Taskinator::Definition::ProcessUndefinedError)
108
+ end
109
+
110
+ it "returns the process uuid" do
111
+ block = SpecSupport::Block.new
112
+ allow(block).to receive(:to_proc) {
113
+ Proc.new {|*args| }
114
+ }
115
+ subject.define_process(&block)
116
+
117
+ process = subject.create_process_remotely
118
+
119
+ expect(process).to_not be_nil
120
+ expect(process.uuid).to_not be_nil
121
+ end
122
+
123
+ it "enqueues" do
124
+ block = SpecSupport::Block.new
125
+ allow(block).to receive(:to_proc) {
126
+ Proc.new {|*args| }
127
+ }
128
+ subject.define_process(&block)
129
+
130
+ expect(Taskinator.queue).to receive(:enqueue_create_process)
131
+
132
+ subject.create_process_remotely
133
+ end
134
+
135
+ describe "reloading" do
136
+ it "returns false if not persisted yet" do
137
+ block = SpecSupport::Block.new
138
+ allow(block).to receive(:to_proc) {
139
+ Proc.new {|*args| }
140
+ }
141
+ subject.define_process(&block)
142
+ process = subject.create_process_remotely
143
+
144
+ expect(process.reload).to eq(false)
145
+ end
146
+
147
+ it "returns true if persisted" do
148
+ definition = MockDefinition.create
149
+ process = definition.create_process_remotely(:foo)
150
+ definition._create_process_(:foo, :uuid => process.uuid).save
151
+
152
+ expect(process.reload).to eq(true)
153
+ end
154
+ end
155
+
156
+ end
157
+
158
+ describe "#queue" do
159
+ it {
160
+ expect(subject.queue).to be_nil
161
+ }
162
+
163
+ it {
164
+ subject.queue = :foo
165
+ expect(subject.queue).to eq(:foo)
166
+ }
70
167
  end
71
168
 
72
169
  end
@@ -3,7 +3,7 @@ require 'spec_helper'
3
3
  describe Taskinator::Executor do
4
4
 
5
5
  let(:definition) do
6
- Module.new() do
6
+ Module.new do
7
7
  def method; end
8
8
  end
9
9
  end
@@ -63,11 +63,11 @@ describe Taskinator::JobWorker do
63
63
  it "should fail if job raises an error" do
64
64
  job = mock_job
65
65
  allow(Taskinator::Task).to receive(:fetch).with(uuid) { job }
66
- allow(job).to receive(:start!) { raise NotImplementedError }
67
- expect(job).to receive(:fail!).with(NotImplementedError)
66
+ allow(job).to receive(:start!) { raise StandardError }
67
+ expect(job).to receive(:fail!).with(StandardError)
68
68
  expect {
69
69
  subject.perform
70
- }.to raise_error(NotImplementedError)
70
+ }.to raise_error(StandardError)
71
71
  end
72
72
 
73
73
  end