gush 0.4.1 → 1.0.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.
data/lib/gush/workflow.rb CHANGED
@@ -102,10 +102,9 @@ module Gush
102
102
  end
103
103
 
104
104
  def run(klass, opts = {})
105
- options =
106
-
107
- node = klass.new(self, {
108
- name: client.next_free_job_id(id,klass.to_s),
105
+ node = klass.new({
106
+ workflow_id: id,
107
+ name: client.next_free_job_id(id, klass.to_s),
109
108
  params: opts.fetch(:params, {})
110
109
  })
111
110
 
@@ -125,7 +124,12 @@ module Gush
125
124
  end
126
125
 
127
126
  def reload
128
- self.class.find(id)
127
+ flow = self.class.find(id)
128
+
129
+ self.jobs = flow.jobs
130
+ self.stopped = flow.stopped
131
+
132
+ self
129
133
  end
130
134
 
131
135
  def initial_jobs
File without changes
@@ -1,12 +1,13 @@
1
1
  require 'spec_helper'
2
+ require 'pry'
2
3
 
3
4
  describe "Workflows" do
4
5
  context "when all jobs finish successfuly" do
5
6
  it "marks workflow as completed" do
6
7
  flow = TestWorkflow.create
7
- flow.start!
8
-
9
- Gush::Worker.drain
8
+ perform_enqueued_jobs do
9
+ flow.start!
10
+ end
10
11
 
11
12
  flow = flow.reload
12
13
  expect(flow).to be_finished
@@ -20,39 +21,39 @@ describe "Workflows" do
20
21
 
21
22
  expect(Gush::Worker).to have_jobs(flow.id, jobs_with_id(['Prepare']))
22
23
 
23
- Gush::Worker.perform_one
24
+ perform_one
24
25
  expect(Gush::Worker).to have_jobs(flow.id, jobs_with_id(["FetchFirstJob", "FetchSecondJob"]))
25
26
 
26
- Gush::Worker.perform_one
27
+ perform_one
27
28
  expect(Gush::Worker).to have_jobs(flow.id, jobs_with_id(["FetchSecondJob", "PersistFirstJob"]))
28
29
 
29
- Gush::Worker.perform_one
30
+ perform_one
30
31
  expect(Gush::Worker).to have_jobs(flow.id, jobs_with_id(["PersistFirstJob"]))
31
32
 
32
- Gush::Worker.perform_one
33
+ perform_one
33
34
  expect(Gush::Worker).to have_jobs(flow.id, jobs_with_id(["NormalizeJob"]))
34
35
 
35
- Gush::Worker.perform_one
36
+ perform_one
36
37
 
37
- expect(Gush::Worker.jobs).to be_empty
38
+ expect(ActiveJob::Base.queue_adapter.enqueued_jobs).to be_empty
38
39
  end
39
40
 
40
41
  it "passes payloads down the workflow" do
41
42
  class UpcaseJob < Gush::Job
42
- def work
43
+ def perform
43
44
  output params[:input].upcase
44
45
  end
45
46
  end
46
47
 
47
48
  class PrefixJob < Gush::Job
48
- def work
49
+ def perform
49
50
  output params[:prefix].capitalize
50
51
  end
51
52
  end
52
53
 
53
54
  class PrependJob < Gush::Job
54
- def work
55
- string = "#{payloads['PrefixJob'].first}: #{payloads['UpcaseJob'].first}"
55
+ def perform
56
+ string = "#{payloads.find { |j| j[:class] == 'PrefixJob'}[:output]}: #{payloads.find { |j| j[:class] == 'UpcaseJob'}[:output]}"
56
57
  output string
57
58
  end
58
59
  end
@@ -68,13 +69,13 @@ describe "Workflows" do
68
69
  flow = PayloadWorkflow.create
69
70
  flow.start!
70
71
 
71
- Gush::Worker.perform_one
72
+ perform_one
72
73
  expect(flow.reload.find_job("UpcaseJob").output_payload).to eq("SOME TEXT")
73
74
 
74
- Gush::Worker.perform_one
75
+ perform_one
75
76
  expect(flow.reload.find_job("PrefixJob").output_payload).to eq("A prefix")
76
77
 
77
- Gush::Worker.perform_one
78
+ perform_one
78
79
  expect(flow.reload.find_job("PrependJob").output_payload).to eq("A prefix: SOME TEXT")
79
80
 
80
81
 
@@ -82,14 +83,14 @@ describe "Workflows" do
82
83
 
83
84
  it "passes payloads from workflow that runs multiple same class jobs with nameized payloads" do
84
85
  class RepetitiveJob < Gush::Job
85
- def work
86
+ def perform
86
87
  output params[:input]
87
88
  end
88
89
  end
89
90
 
90
91
  class SummaryJob < Gush::Job
91
- def work
92
- output payloads['RepetitiveJob']
92
+ def perform
93
+ output payloads.map { |payload| payload[:output] }
93
94
  end
94
95
  end
95
96
 
@@ -106,17 +107,55 @@ describe "Workflows" do
106
107
  flow = PayloadWorkflow.create
107
108
  flow.start!
108
109
 
109
- Gush::Worker.perform_one
110
+ perform_one
110
111
  expect(flow.reload.find_job(flow.jobs[0].name).output_payload).to eq('first')
111
112
 
112
- Gush::Worker.perform_one
113
+ perform_one
113
114
  expect(flow.reload.find_job(flow.jobs[1].name).output_payload).to eq('second')
114
115
 
115
- Gush::Worker.perform_one
116
+ perform_one
116
117
  expect(flow.reload.find_job(flow.jobs[2].name).output_payload).to eq('third')
117
118
 
118
- Gush::Worker.perform_one
119
+ perform_one
119
120
  expect(flow.reload.find_job(flow.jobs[3].name).output_payload).to eq(%w(first second third))
121
+ end
122
+
123
+ it "does not execute `configure` on each job for huge workflows" do
124
+ INTERNAL_SPY = double('spy')
125
+ INTERNAL_CONFIGURE_SPY = double('configure spy')
126
+ expect(INTERNAL_SPY).to receive(:some_method).exactly(110).times
127
+
128
+ # One time when persisting, second time when reloading in the spec
129
+ expect(INTERNAL_CONFIGURE_SPY).to receive(:some_method).exactly(2).times
130
+
131
+ class SimpleJob < Gush::Job
132
+ def perform
133
+ INTERNAL_SPY.some_method
134
+ end
135
+ end
136
+
137
+ class GiganticWorkflow < Gush::Workflow
138
+ def configure
139
+ INTERNAL_CONFIGURE_SPY.some_method
140
+
141
+ 10.times do
142
+ main = run(SimpleJob)
143
+ 10.times do
144
+ run(SimpleJob, after: main)
145
+ end
146
+ end
147
+ end
148
+ end
149
+
150
+ flow = GiganticWorkflow.create
151
+ flow.start!
152
+
153
+ 110.times do
154
+ perform_one
155
+ end
120
156
 
157
+ flow = flow.reload
158
+ expect(flow).to be_finished
159
+ expect(flow).to_not be_failed
121
160
  end
122
161
  end
@@ -41,7 +41,7 @@ describe Gush::Client do
41
41
  workflow = TestWorkflow.create
42
42
  expect {
43
43
  client.start_workflow(workflow)
44
- }.to change{Gush::Worker.jobs.count}.from(0).to(1)
44
+ }.to change{ActiveJob::Base.queue_adapter.enqueued_jobs.size}.from(0).to(1)
45
45
  end
46
46
 
47
47
  it "removes stopped flag when the workflow is started" do
@@ -8,7 +8,6 @@ describe Gush::Configuration do
8
8
  expect(subject.concurrency).to eq(5)
9
9
  expect(subject.namespace).to eq('gush')
10
10
  expect(subject.gushfile).to eq(GUSHFILE.realpath)
11
- expect(subject.environment).to eq('development')
12
11
  end
13
12
 
14
13
  describe "#configure" do
@@ -16,12 +15,10 @@ describe Gush::Configuration do
16
15
  Gush.configure do |config|
17
16
  config.redis_url = "redis://localhost"
18
17
  config.concurrency = 25
19
- config.environment = 'production'
20
18
  end
21
19
 
22
20
  expect(Gush.configuration.redis_url).to eq("redis://localhost")
23
21
  expect(Gush.configuration.concurrency).to eq(25)
24
- expect(Gush.configuration.environment).to eq('production')
25
22
  end
26
23
  end
27
24
  end
@@ -62,7 +62,7 @@ describe Gush::Job do
62
62
  describe "#as_json" do
63
63
  context "finished and enqueued set to true" do
64
64
  it "returns correct hash" do
65
- job = described_class.new(double('flow'), name: "a-job", finished_at: 123, enqueued_at: 120)
65
+ job = described_class.new(workflow_id: 123, name: "a-job", finished_at: 123, enqueued_at: 120)
66
66
  expected = {
67
67
  name: "a-job",
68
68
  klass: "Gush::Job",
@@ -73,7 +73,8 @@ describe Gush::Job do
73
73
  finished_at: 123,
74
74
  enqueued_at: 120,
75
75
  params: {},
76
- output_payload: nil
76
+ output_payload: nil,
77
+ workflow_id: 123
77
78
  }
78
79
  expect(job.as_json).to eq(expected)
79
80
  end
@@ -83,7 +84,6 @@ describe Gush::Job do
83
84
  describe ".from_hash" do
84
85
  it "properly restores state of the job from hash" do
85
86
  job = described_class.from_hash(
86
- double('flow'),
87
87
  {
88
88
  klass: 'Gush::Job',
89
89
  name: 'gob',
@@ -4,68 +4,60 @@ describe Gush::Worker do
4
4
  subject { described_class.new }
5
5
 
6
6
  let!(:workflow) { TestWorkflow.create }
7
- let(:job) { workflow.find_job("Prepare") }
7
+ let!(:job) { client.find_job(workflow.id, "Prepare") }
8
8
  let(:config) { Gush.configuration.to_json }
9
- let!(:client) { double("client") }
10
-
11
- before :each do
12
- allow(subject).to receive(:client).and_return(client)
13
- allow(subject).to receive(:enqueue_outgoing_jobs)
14
-
15
- allow(client).to receive(:find_workflow).with(workflow.id).and_return(workflow)
16
- expect(client).to receive(:persist_job).at_least(1).times
17
- expect(client).to receive(:worker_report).with(hash_including(status: :started)).ordered
18
- end
9
+ let!(:client) { Gush::Client.new }
19
10
 
20
11
  describe "#perform" do
21
12
  context "when job fails" do
22
13
  it "should mark it as failed" do
23
- allow(job).to receive(:work).and_raise(StandardError)
24
- expect(client).to receive(:worker_report).with(hash_including(status: :failed)).ordered
25
-
26
- expect do
27
- subject.perform(workflow.id, "Prepare")
28
- end.to raise_error(StandardError)
29
- expect(workflow.find_job("Prepare")).to be_failed
30
- end
31
-
32
- it "reports that job failed" do
33
- allow(job).to receive(:work).and_raise(StandardError)
34
- expect(client).to receive(:worker_report).with(hash_including(status: :failed)).ordered
35
-
14
+ class FailingJob < Gush::Job
15
+ def perform
16
+ invalid.code_to_raise.error
17
+ end
18
+ end
19
+
20
+ class FailingWorkflow < Gush::Workflow
21
+ def configure
22
+ run FailingJob
23
+ end
24
+ end
25
+
26
+ workflow = FailingWorkflow.create
36
27
  expect do
37
- subject.perform(workflow.id, "Prepare")
38
- end.to raise_error(StandardError)
28
+ subject.perform(workflow.id, "FailingJob")
29
+ end.to raise_error(NameError)
30
+ expect(client.find_job(workflow.id, "FailingJob")).to be_failed
39
31
  end
40
32
  end
41
33
 
42
34
  context "when job completes successfully" do
43
35
  it "should mark it as succedeed" do
44
36
  expect(subject).to receive(:mark_as_finished)
45
- expect(client).to receive(:worker_report).with(hash_including(status: :finished)).ordered
46
37
 
47
38
  subject.perform(workflow.id, "Prepare")
48
39
  end
40
+ end
49
41
 
50
- it "reports that job succedeed" do
51
- expect(client).to receive(:worker_report).with(hash_including(status: :finished)).ordered
42
+ it "calls job.perform method" do
43
+ SPY = double()
44
+ expect(SPY).to receive(:some_method)
52
45
 
53
- subject.perform(workflow.id, "Prepare")
46
+ class OkayJob < Gush::Job
47
+ def perform
48
+ SPY.some_method
49
+ end
54
50
  end
55
- end
56
51
 
57
- it "calls job.work method" do
58
- expect(job).to receive(:work)
59
- expect(client).to receive(:worker_report).with(hash_including(status: :finished)).ordered
60
-
61
- subject.perform(workflow.id, "Prepare")
62
- end
52
+ class OkayWorkflow < Gush::Workflow
53
+ def configure
54
+ run OkayJob
55
+ end
56
+ end
63
57
 
64
- it "reports when the job is started" do
65
- allow(client).to receive(:worker_report)
66
- expect(client).to receive(:worker_report).with(hash_including(status: :finished)).ordered
58
+ workflow = OkayWorkflow.create
67
59
 
68
- subject.perform(workflow.id, "Prepare")
60
+ subject.perform(workflow.id, 'OkayJob')
69
61
  end
70
62
  end
71
63
  end
@@ -114,7 +114,8 @@ describe Gush::Workflow do
114
114
  "enqueued_at"=>nil,
115
115
  "failed_at"=>nil,
116
116
  "params" => {},
117
- "output_payload" => nil
117
+ "output_payload" => nil,
118
+ "workflow_id" => an_instance_of(String)
118
119
  },
119
120
  {
120
121
  "name"=>a_string_starting_with('PersistFirstJob'),
@@ -126,7 +127,8 @@ describe Gush::Workflow do
126
127
  "enqueued_at"=>nil,
127
128
  "failed_at"=>nil,
128
129
  "params" => {},
129
- "output_payload" => nil
130
+ "output_payload" => nil,
131
+ "workflow_id" => an_instance_of(String)
130
132
  }
131
133
  ]
132
134
  }
data/spec/gush_spec.rb CHANGED
@@ -4,16 +4,16 @@ describe Gush do
4
4
  describe ".gushfile" do
5
5
  let(:path) { Pathname("/tmp/Gushfile.rb") }
6
6
 
7
- context "Gushfile.rb is missing from pwd" do
8
- it "raises an exception" do
7
+ context "Gushfile is missing from pwd" do
8
+ it "returns nil" do
9
9
  path.delete if path.exist?
10
10
  Gush.configuration.gushfile = path
11
11
 
12
- expect { Gush.gushfile }.to raise_error(Errno::ENOENT)
12
+ expect(Gush.gushfile).to eq(nil)
13
13
  end
14
14
  end
15
15
 
16
- context "Gushfile.rb exists" do
16
+ context "Gushfile exists" do
17
17
  it "returns Pathname to it" do
18
18
  FileUtils.touch(path)
19
19
  Gush.configuration.gushfile = path
data/spec/spec_helper.rb CHANGED
@@ -1,9 +1,10 @@
1
1
  require 'gush'
2
2
  require 'fakeredis'
3
- require 'sidekiq/testing'
3
+ require 'json'
4
+ require 'pry'
4
5
 
5
- Sidekiq::Testing.fake!
6
- Sidekiq::Logging.logger = nil
6
+ ActiveJob::Base.queue_adapter = :test
7
+ ActiveJob::Base.logger = nil
7
8
 
8
9
  class Prepare < Gush::Job; end
9
10
  class FetchFirstJob < Gush::Job; end
@@ -13,7 +14,7 @@ class PersistSecondJob < Gush::Job; end
13
14
  class NormalizeJob < Gush::Job; end
14
15
  class BobJob < Gush::Job; end
15
16
 
16
- GUSHFILE = Pathname.new(__FILE__).parent.join("Gushfile.rb")
17
+ GUSHFILE = Pathname.new(__FILE__).parent.join("Gushfile")
17
18
 
18
19
  class TestWorkflow < Gush::Workflow
19
20
  def configure
@@ -47,6 +48,15 @@ module GushHelpers
47
48
  @redis ||= Redis.new(url: REDIS_URL)
48
49
  end
49
50
 
51
+ def perform_one
52
+ job = ActiveJob::Base.queue_adapter.enqueued_jobs.first
53
+ if job
54
+ Gush::Worker.new.perform(*job[:args])
55
+ ActiveJob::Base.queue_adapter.performed_jobs << job
56
+ ActiveJob::Base.queue_adapter.enqueued_jobs.shift
57
+ end
58
+ end
59
+
50
60
  def jobs_with_id(jobs_array)
51
61
  jobs_array.map {|job_name| job_with_id(job_name) }
52
62
  end
@@ -59,17 +69,18 @@ end
59
69
  RSpec::Matchers.define :have_jobs do |flow, jobs|
60
70
  match do |actual|
61
71
  expected = jobs.map do |job|
62
- hash_including("args" => include(flow, job))
72
+ hash_including(args: include(flow, job))
63
73
  end
64
- expect(Gush::Worker.jobs).to match_array(expected)
74
+ expect(ActiveJob::Base.queue_adapter.enqueued_jobs).to match_array(expected)
65
75
  end
66
76
 
67
77
  failure_message do |actual|
68
- "expected queue to have #{jobs}, but instead has: #{actual.jobs.map{ |j| j["args"][1]}}"
78
+ "expected queue to have #{jobs}, but instead has: #{ActiveJob::Base.queue_adapter.enqueued_jobs.map{ |j| j[:args][1]}}"
69
79
  end
70
80
  end
71
81
 
72
82
  RSpec.configure do |config|
83
+ config.include ActiveJob::TestHelper
73
84
  config.include GushHelpers
74
85
 
75
86
  config.mock_with :rspec do |mocks|
@@ -77,16 +88,19 @@ RSpec.configure do |config|
77
88
  end
78
89
 
79
90
  config.before(:each) do
91
+ clear_enqueued_jobs
92
+ clear_performed_jobs
93
+
80
94
  Gush.configure do |config|
81
95
  config.redis_url = REDIS_URL
82
- config.environment = 'test'
83
96
  config.gushfile = GUSHFILE
84
97
  end
85
98
  end
86
99
 
87
100
 
88
101
  config.after(:each) do
89
- Sidekiq::Worker.clear_all
102
+ clear_enqueued_jobs
103
+ clear_performed_jobs
90
104
  redis.flushdb
91
105
  end
92
106
  end