burstflow 0.1.0

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.
@@ -0,0 +1,180 @@
1
+ require 'spec_helper'
2
+
3
+ describe Burst::Manager do
4
+ class JobHandler
5
+
6
+ attr_accessor :jobs
7
+
8
+ def initialize
9
+ @jobs = []
10
+ end
11
+
12
+ def add(json)
13
+ @jobs.push json
14
+ end
15
+
16
+ def find_job(klass)
17
+ @jobs.detect do |json|
18
+ json['klass'].to_s == klass.to_s
19
+ end
20
+ end
21
+
22
+ end
23
+
24
+ around :each do |ex|
25
+ begin
26
+ $job_handler = JobHandler.new
27
+ ex.run
28
+ ensure
29
+ $job_handler = nil
30
+ end
31
+ end
32
+
33
+ class TestCaseJob < Burst::Job
34
+
35
+ def perform
36
+ # puts "#{self.class.to_s}"
37
+ set_output(self.class.to_s)
38
+ $job_handler.add as_json.with_indifferent_access.merge(payloads: payloads)
39
+ end
40
+
41
+ end
42
+
43
+ class CaseJob1 < TestCaseJob; end
44
+ class CaseJob2 < TestCaseJob; end
45
+ class CaseJob3 < TestCaseJob; end
46
+ class CaseAsyncJob < TestCaseJob
47
+
48
+ def perform
49
+ suspend
50
+ $job_handler.add as_json.with_indifferent_access.merge(payloads: payloads)
51
+ end
52
+
53
+ end
54
+
55
+ class CaseW1 < Burst::Workflow
56
+
57
+ configure do |*_args|
58
+ id1 = run CaseJob1, id: 'job1', params: { p1: 1 }
59
+ run CaseJob2, id: 'job2', after: id1, params: { p2: 2 }
60
+ run CaseJob3, after: [CaseJob1, 'job2']
61
+ end
62
+
63
+ end
64
+
65
+
66
+ it 'case1 no async job state' do
67
+ w = CaseW1.build
68
+
69
+ perform_enqueued_jobs do
70
+ w.start!
71
+ end
72
+
73
+ w.reload
74
+
75
+ expect(w.started?).to eq true
76
+ expect(w.failed?).to eq false
77
+ expect(w.finished?).to eq true
78
+ expect(w.running?).to eq false
79
+ expect(w.status).to eq Burst::Workflow::FINISHED
80
+
81
+ expect($job_handler.jobs.count).to eq 3
82
+ expect($job_handler.find_job(CaseJob1)).to include(output: 'CaseJob1', params: { p1: 1 })
83
+
84
+ expect($job_handler.find_job(CaseJob2)).to include(output: 'CaseJob2', params: { p2: 2 })
85
+ expect($job_handler.find_job(CaseJob2)[:payloads]).to include(id: 'job1', class: 'CaseJob1', payload: 'CaseJob1')
86
+
87
+ expect($job_handler.find_job(CaseJob3)).to include(output: 'CaseJob3')
88
+ expect($job_handler.find_job(CaseJob3)[:payloads]).to include(id: 'job1', class: 'CaseJob1', payload: 'CaseJob1')
89
+ expect($job_handler.find_job(CaseJob3)[:payloads]).to include(id: 'job2', class: 'CaseJob2', payload: 'CaseJob2')
90
+ end
91
+
92
+ class CaseW2 < Burst::Workflow
93
+
94
+ configure do |*_args|
95
+ id1 = run CaseJob1, id: 'job1', params: { p1: 1 }
96
+ run CaseAsyncJob, id: 'job2', after: id1, params: { p2: 2 }
97
+ run CaseJob3, after: [CaseJob1, 'job2']
98
+ end
99
+
100
+ end
101
+
102
+ it 'case2 with async job state' do
103
+ w = CaseW2.build
104
+
105
+ perform_enqueued_jobs do
106
+ w.start!
107
+ end
108
+
109
+ w.reload
110
+
111
+ expect(w.started?).to eq true
112
+ expect(w.failed?).to eq false
113
+ expect(w.finished?).to eq false
114
+ expect(w.running?).to eq true
115
+ expect(w.suspended?).to eq true
116
+ expect(w.status).to eq Burst::Workflow::SUSPENDED
117
+
118
+ expect($job_handler.jobs.count).to eq 2
119
+ expect($job_handler.find_job(CaseJob1)).to include(output: 'CaseJob1', params: { p1: 1 })
120
+
121
+ expect($job_handler.find_job(CaseAsyncJob)).to include(output: Burst::Job::SUSPEND, params: { p2: 2 })
122
+ expect($job_handler.find_job(CaseAsyncJob)[:payloads]).to include(id: 'job1', class: 'CaseJob1', payload: 'CaseJob1')
123
+
124
+ w = CaseW2.find(w.id)
125
+
126
+ perform_enqueued_jobs do
127
+ w.resume!('job2', 'result')
128
+ end
129
+
130
+ w.reload
131
+
132
+ expect($job_handler.jobs.count).to eq 3
133
+ expect($job_handler.find_job(CaseJob1)).to include(output: 'CaseJob1', params: { p1: 1 })
134
+
135
+ # expect($job_handler.find_job(AsyncJob)).to include(output: 'result', params: {p2: 2})
136
+ expect($job_handler.find_job(CaseAsyncJob)[:payloads]).to include(id: 'job1', class: 'CaseJob1', payload: 'CaseJob1')
137
+
138
+ expect($job_handler.find_job(CaseJob3)).to include(output: 'CaseJob3')
139
+ expect($job_handler.find_job(CaseJob3)[:payloads]).to include(id: 'job1', class: 'CaseJob1', payload: 'CaseJob1')
140
+ expect($job_handler.find_job(CaseJob3)[:payloads]).to include(id: 'job2', class: 'CaseAsyncJob', payload: 'result')
141
+ end
142
+
143
+ describe 'dynamic jobs with payloads' do
144
+ class DynJob1 < TestCaseJob
145
+
146
+ def perform
147
+ configure do
148
+ run DynJob2, params: { a: 1 }
149
+ run DynJob2, params: { a: 2 }
150
+ run DynJob2, params: { a: 3 }
151
+ end
152
+ super
153
+ end
154
+
155
+ end
156
+
157
+ class DynJob2 < TestCaseJob; end
158
+ class DynJob3 < TestCaseJob; end
159
+
160
+ class DynFlow1 < Burst::Workflow
161
+
162
+ configure do |*_args|
163
+ run DynJob1
164
+ run DynJob3, after: DynJob1
165
+ end
166
+
167
+ end
168
+
169
+ it 'create intemediate jobs' do
170
+ w = DynFlow1.build
171
+
172
+ perform_enqueued_jobs do
173
+ w.start!
174
+ end
175
+
176
+ expect($job_handler.jobs.count).to eq 5
177
+ expect($job_handler.jobs.last['payloads'].count).to eq 4
178
+ end
179
+ end
180
+ end
data/spec/job_spec.rb ADDED
@@ -0,0 +1,80 @@
1
+ require 'spec_helper'
2
+
3
+ describe Burst::Job do
4
+ let(:w) { Burst::Workflow.new }
5
+
6
+ context 'initializing' do
7
+ class TestJob1 < Burst::Job
8
+ end
9
+
10
+ subject{ TestJob1.new(w, {}.with_indifferent_access) }
11
+
12
+ it 'empty' do
13
+ expect(subject.workflow_id).to eq w.id
14
+ expect(subject.klass).to eq TestJob1.to_s
15
+
16
+ expect(subject.incoming).to eq []
17
+ expect(subject.outgoing).to eq []
18
+
19
+ expect(subject.initial?).to eq true
20
+
21
+ expect(subject.enqueued?).to eq false
22
+ expect(subject.started?).to eq false
23
+ expect(subject.finished?).to eq false
24
+ expect(subject.failed?).to eq false
25
+ expect(subject.suspended?).to eq false
26
+ expect(subject.resumed?).to eq false
27
+
28
+ expect(subject.ready_to_start?).to eq true
29
+ end
30
+
31
+ it '#enqueue!' do
32
+ subject.enqueue!
33
+
34
+ expect(subject.enqueued?).to eq true
35
+ expect(subject.ready_to_start?).to eq false
36
+ end
37
+
38
+ it '#start!' do
39
+ subject.start!
40
+
41
+ expect(subject.started?).to eq true
42
+ expect(subject.ready_to_start?).to eq false
43
+ end
44
+
45
+ it '#finish!' do
46
+ subject.finish!
47
+
48
+ expect(subject.finished?).to eq true
49
+ expect(subject.succeeded?).to eq true
50
+ expect(subject.failed?).to eq false
51
+ expect(subject.ready_to_start?).to eq false
52
+ end
53
+
54
+ it '#fail!' do
55
+ subject.fail!
56
+
57
+ expect(subject.finished?).to eq true
58
+ expect(subject.succeeded?).to eq false
59
+ expect(subject.failed?).to eq true
60
+ expect(subject.ready_to_start?).to eq false
61
+ end
62
+
63
+ it '#suspend!' do
64
+ subject.suspend!
65
+
66
+ expect(subject.finished?).to eq false
67
+ expect(subject.suspended?).to eq true
68
+ expect(subject.ready_to_start?).to eq true
69
+ end
70
+
71
+ it '#resume!' do
72
+ subject.suspend!
73
+ subject.resume!
74
+
75
+ expect(subject.finished?).to eq false
76
+ expect(subject.resumed?).to eq true
77
+ expect(subject.ready_to_start?).to eq true
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,47 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ require 'bundler/setup'
4
+ Bundler.require(:default)
5
+
6
+ require 'burst'
7
+
8
+
9
+ ActiveJob::Base.queue_adapter = :test
10
+ ActiveJob::Base.logger = nil
11
+
12
+ $root = File.join(File.dirname(__dir__), 'spec')
13
+ Dir[File.join($root, 'support', '**', '*.rb')].each {|f| require f }
14
+
15
+
16
+
17
+
18
+ RSpec::Matchers.define :have_jobs do |flow, jobs|
19
+ match do |_actual|
20
+ expected = jobs.map do |job|
21
+ hash_including(args: include(flow, job))
22
+ end
23
+ expect(ActiveJob::Base.queue_adapter.enqueued_jobs).to match_array(expected)
24
+ end
25
+
26
+ failure_message do |_actual|
27
+ "expected queue to have #{jobs}, but instead has: #{ActiveJob::Base.queue_adapter.enqueued_jobs.map{|j| j[:args][1] }}"
28
+ end
29
+ end
30
+
31
+ RSpec.configure do |config|
32
+ config.include ActiveJob::TestHelper
33
+
34
+ config.mock_with :rspec do |mocks|
35
+ mocks.verify_partial_doubles = true
36
+ end
37
+
38
+ config.before(:each) do
39
+ clear_enqueued_jobs
40
+ clear_performed_jobs
41
+ end
42
+
43
+ config.after(:each) do
44
+ clear_enqueued_jobs
45
+ clear_performed_jobs
46
+ end
47
+ end
@@ -0,0 +1,16 @@
1
+ require 'database_cleaner'
2
+
3
+ RSpec.configure do |config|
4
+ config.before(:all) do
5
+ DatabaseCleaner.clean_with(:truncation)
6
+ end
7
+
8
+ config.before(:each) do
9
+ DatabaseCleaner.strategy = :transaction
10
+ DatabaseCleaner.start
11
+ end
12
+
13
+ config.after(:each) do
14
+ DatabaseCleaner.clean
15
+ end
16
+ end
@@ -0,0 +1,12 @@
1
+ require 'otr-activerecord'
2
+ require 'yaml'
3
+ require 'erb'
4
+
5
+ RSpec.configure do |config|
6
+ # ActiveSupport::Dependencies.autoload_paths += [ 'lib' ].map {|f| File.join($root, '..', f)}
7
+
8
+ config.before(:suite) do
9
+ OTR::ActiveRecord.configure_from_file! 'config/database.yml'
10
+ ActiveRecord::Base.logger = nil
11
+ end
12
+ end
@@ -0,0 +1,185 @@
1
+ require 'spec_helper'
2
+
3
+ describe Burst::Workflow do
4
+ class TestJob < Burst::Job
5
+
6
+ def perform
7
+ # puts "#{self.class} perform"
8
+ end
9
+
10
+ end
11
+
12
+ class WfJob1 < TestJob
13
+ end
14
+
15
+ class WfJob2 < TestJob
16
+ end
17
+
18
+ class WfJob3 < TestJob
19
+ end
20
+
21
+
22
+ class W1 < Burst::Workflow
23
+
24
+ configure do |*_args|
25
+ id1 = run WfJob1, id: 'job1'
26
+ run WfJob2, id: 'job2', after: id1
27
+ run WfJob3, after: [WfJob1, 'job2']
28
+ end
29
+
30
+ end
31
+
32
+ def expect_jobs(*jobs)
33
+ j1, j2, j3 = *jobs
34
+
35
+ expect(j1.finished?).to eq false
36
+ expect(j2.finished?).to eq false
37
+
38
+ expect(j1.klass).to eq 'WfJob1'
39
+ expect(j2.klass).to eq 'WfJob2'
40
+ expect(j3.klass).to eq 'WfJob3'
41
+
42
+ expect(j1.outgoing).to include(j2.id, j3.id)
43
+ expect(j2.outgoing).to include(j3.id)
44
+ expect(j3.outgoing).to include
45
+
46
+ expect(j1.incoming).to include
47
+ expect(j2.incoming).to include(j1.id)
48
+ expect(j3.incoming).to include(j1.id, j2.id)
49
+
50
+ expect(j1.initial?).to eq true
51
+ expect(j2.initial?).to eq false
52
+ expect(j3.initial?).to eq false
53
+ end
54
+
55
+ context 'model' do
56
+ it 'default store' do
57
+ w = Burst::Workflow.new
58
+
59
+ expect(w.attributes).to include(:id, jobs: {}, klass: Burst::Workflow.to_s)
60
+
61
+ expect(w.started?).to eq false
62
+ expect(w.failed?).to eq false
63
+ expect(w.finished?).to eq true
64
+ expect(w.running?).to eq false
65
+ expect(w.status).to eq Burst::Workflow::FINISHED
66
+ end
67
+
68
+ it 'store persistance' do
69
+ w = Burst::Workflow.new
70
+ w.save!
71
+
72
+ w2 = Burst::Workflow.find(w.id)
73
+ expect(w2.attributes).to include(w.attributes)
74
+ end
75
+
76
+ it 'builded store' do
77
+ w = W1.build
78
+
79
+ jobs = w.jobs
80
+ expect_jobs(*jobs.values.map{|json| Burst::Job.new(w, json) })
81
+
82
+ expect(w.attributes).to include(:id, jobs: jobs, klass: W1.to_s)
83
+ end
84
+
85
+ it 'builded persistance' do
86
+ w = W1.build
87
+ w.save!
88
+
89
+ jobs = w.jobs
90
+ expect_jobs(*jobs.values.map{|json| Burst::Job.new(w, json) })
91
+
92
+ w2 = Burst::Workflow.find(w.id)
93
+ expect(w2.attributes).to include(w.attributes)
94
+ end
95
+ end
96
+
97
+ def expect_wf(w)
98
+ j1 = w.find_job('job1')
99
+ j2 = w.find_job('job2')
100
+ j3 = w.find_job(WfJob3)
101
+
102
+ expect(j1.finished?).to eq false
103
+ expect(j2.finished?).to eq false
104
+
105
+ expect(j1.klass).to eq 'WfJob1'
106
+ expect(j2.klass).to eq 'WfJob2'
107
+ expect(j3.klass).to eq 'WfJob3'
108
+
109
+ expect(j1.outgoing).to include(j2.id, j3.id)
110
+ expect(j2.outgoing).to include(j3.id)
111
+ expect(j3.outgoing).to include
112
+
113
+ expect(j1.incoming).to include
114
+ expect(j2.incoming).to include(j1.id)
115
+ expect(j3.incoming).to include(j1.id, j2.id)
116
+
117
+ expect(j1.initial?).to eq true
118
+ expect(j2.initial?).to eq false
119
+ expect(j3.initial?).to eq false
120
+
121
+ expect(w.initial_jobs).to include(j1)
122
+ end
123
+
124
+ it 'check builder' do
125
+ w = W1.build
126
+ expect_wf(w)
127
+
128
+ expect(w.status)
129
+ end
130
+
131
+ it 'check persistance' do
132
+ w = W1.build
133
+ w.save!
134
+
135
+ w2 = W1.find(w.id)
136
+ expect_wf(w2)
137
+ end
138
+
139
+ it 'start without perform' do
140
+ w = W1.build
141
+ w.save!
142
+
143
+ expect(w.started?).to eq false
144
+ expect(w.failed?).to eq false
145
+ expect(w.finished?).to eq false
146
+ expect(w.running?).to eq false
147
+ expect(w.status).to eq Burst::Workflow::INITIAL
148
+
149
+ w.start!
150
+
151
+ expect(Burst::Worker).to have_jobs(w.id, ['job1'])
152
+ expect(Burst::Worker).not_to have_jobs(w.id, ['job2'])
153
+
154
+ w = W1.find(w.id)
155
+
156
+ expect(w.started?).to eq false
157
+ expect(w.failed?).to eq false
158
+ expect(w.finished?).to eq false
159
+ expect(w.running?).to eq false
160
+ expect(w.status).to eq Burst::Workflow::INITIAL
161
+ end
162
+
163
+ it 'start with perform' do
164
+ w = W1.build
165
+ w.save!
166
+
167
+ expect(w.started?).to eq false
168
+ expect(w.failed?).to eq false
169
+ expect(w.finished?).to eq false
170
+ expect(w.running?).to eq false
171
+ expect(w.status).to eq Burst::Workflow::INITIAL
172
+
173
+ perform_enqueued_jobs do
174
+ w.start!
175
+ end
176
+
177
+ w = W1.find(w.id)
178
+
179
+ expect(w.started?).to eq true
180
+ expect(w.failed?).to eq false
181
+ expect(w.finished?).to eq true
182
+ expect(w.running?).to eq false
183
+ expect(w.status).to eq Burst::Workflow::FINISHED
184
+ end
185
+ end
metadata ADDED
@@ -0,0 +1,149 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: burstflow
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Samoilenko Yuri
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-01-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activejob
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: activerecord
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: It has dependency, result pipelining and suspend/resume ability
84
+ email:
85
+ - kinnalru@gmail.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - ".gitignore"
91
+ - ".rspec"
92
+ - ".rubocop.yml"
93
+ - ".travis.yml"
94
+ - Gemfile
95
+ - README.md
96
+ - Rakefile
97
+ - burst.gemspec
98
+ - config/database.yml
99
+ - db/migrate/20180101000001_create_workflow.rb
100
+ - db/schema.rb
101
+ - db/seeds.rb
102
+ - lib/burst.rb
103
+ - lib/burst/builder.rb
104
+ - lib/burst/configuration.rb
105
+ - lib/burst/job.rb
106
+ - lib/burst/manager.rb
107
+ - lib/burst/model.rb
108
+ - lib/burst/worker.rb
109
+ - lib/burst/workflow.rb
110
+ - lib/burst/workflow_helper.rb
111
+ - spec/burst_spec.rb
112
+ - spec/cases_spec.rb
113
+ - spec/job_spec.rb
114
+ - spec/spec_helper.rb
115
+ - spec/support/database_clean.rb
116
+ - spec/support/runner.rb
117
+ - spec/workflow_spec.rb
118
+ homepage: https://github.com/RnD-Soft/burst
119
+ licenses:
120
+ - MIT
121
+ metadata: {}
122
+ post_install_message:
123
+ rdoc_options: []
124
+ require_paths:
125
+ - lib
126
+ required_ruby_version: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ required_rubygems_version: !ruby/object:Gem::Requirement
132
+ requirements:
133
+ - - ">="
134
+ - !ruby/object:Gem::Version
135
+ version: '0'
136
+ requirements: []
137
+ rubyforge_project:
138
+ rubygems_version: 2.6.14
139
+ signing_key:
140
+ specification_version: 4
141
+ summary: Burst is a parallel workflow runner using ActiveRecord and ActiveJob
142
+ test_files:
143
+ - spec/burst_spec.rb
144
+ - spec/cases_spec.rb
145
+ - spec/job_spec.rb
146
+ - spec/spec_helper.rb
147
+ - spec/support/database_clean.rb
148
+ - spec/support/runner.rb
149
+ - spec/workflow_spec.rb