postjob 0.1.1
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 +7 -0
- data/README.md +23 -0
- data/bin/postjob +11 -0
- data/lib/postjob/cli/db.rb +39 -0
- data/lib/postjob/cli/job.rb +67 -0
- data/lib/postjob/cli/ps.rb +110 -0
- data/lib/postjob/cli/run.rb +19 -0
- data/lib/postjob/cli.rb +31 -0
- data/lib/postjob/error.rb +16 -0
- data/lib/postjob/job.rb +66 -0
- data/lib/postjob/migrations.rb +97 -0
- data/lib/postjob/queue/encoder.rb +40 -0
- data/lib/postjob/queue/notifications.rb +72 -0
- data/lib/postjob/queue/search.rb +82 -0
- data/lib/postjob/queue.rb +331 -0
- data/lib/postjob/registry.rb +52 -0
- data/lib/postjob/runner.rb +153 -0
- data/lib/postjob/workflow.rb +60 -0
- data/lib/postjob.rb +170 -0
- data/spec/postjob/enqueue_spec.rb +86 -0
- data/spec/postjob/full_workflow_spec.rb +86 -0
- data/spec/postjob/job_control/manual_spec.rb +45 -0
- data/spec/postjob/job_control/max_attempts_spec.rb +70 -0
- data/spec/postjob/job_control/timeout_spec.rb +31 -0
- data/spec/postjob/job_control/workflow_status_spec.rb +52 -0
- data/spec/postjob/process_job_spec.rb +25 -0
- data/spec/postjob/queue/encoder_spec.rb +46 -0
- data/spec/postjob/queue/search_spec.rb +141 -0
- data/spec/postjob/run_spec.rb +69 -0
- data/spec/postjob/step_spec.rb +26 -0
- data/spec/postjob/sub_workflow_spec.rb +27 -0
- data/spec/spec_helper.rb +35 -0
- data/spec/support/configure_active_record.rb +18 -0
- data/spec/support/configure_database.rb +19 -0
- data/spec/support/configure_simple_sql.rb +17 -0
- data/spec/support/connect_active_record.rb +6 -0
- data/spec/support/test_helper.rb +53 -0
- metadata +269 -0
data/lib/postjob.rb
ADDED
@@ -0,0 +1,170 @@
|
|
1
|
+
require "expectation"
|
2
|
+
require "simple/sql"
|
3
|
+
|
4
|
+
require_relative "postjob/version"
|
5
|
+
require_relative "postjob/workflow"
|
6
|
+
require_relative "postjob/registry"
|
7
|
+
require_relative "postjob/job"
|
8
|
+
require_relative "postjob/error"
|
9
|
+
require_relative "postjob/queue"
|
10
|
+
require_relative "postjob/runner"
|
11
|
+
|
12
|
+
module Postjob
|
13
|
+
attr_accessor :logger
|
14
|
+
extend self
|
15
|
+
|
16
|
+
# In fast mode <tt>Postjob.run</tt> doesn't wait that long between retrying
|
17
|
+
# failed jobs. This mode is enabled by default during tests; and it can be
|
18
|
+
# enabled via "postjob run --fast"
|
19
|
+
#
|
20
|
+
# Note that fast mode should only be used during development and tests.
|
21
|
+
attr_accessor :fast_mode
|
22
|
+
self.fast_mode = false
|
23
|
+
|
24
|
+
def enqueue!(workflow, *args, queue: nil,
|
25
|
+
parent_id: nil,
|
26
|
+
max_attempts: nil,
|
27
|
+
timeout: nil,
|
28
|
+
version: nil,
|
29
|
+
tags: nil)
|
30
|
+
expect! queue => [nil, String]
|
31
|
+
expect! workflow => String
|
32
|
+
expect! parent_id => [nil, Integer]
|
33
|
+
expect! max_attempts => [nil, Integer]
|
34
|
+
expect! timeout => [nil, Numeric]
|
35
|
+
expect! tags => [nil, Hash]
|
36
|
+
|
37
|
+
tags = stringify_hash(tags) if tags
|
38
|
+
job = Queue.enqueue_job workflow, *args, queue: queue,
|
39
|
+
parent_id: parent_id,
|
40
|
+
max_attempts: max_attempts,
|
41
|
+
timeout: timeout,
|
42
|
+
tags: tags,
|
43
|
+
version: version
|
44
|
+
logger.info "Generated process #{job}"
|
45
|
+
job.id
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def stringify_hash(hsh)
|
51
|
+
hsh.inject({}) do |r, (k, v)|
|
52
|
+
k = k.to_s if k.is_a?(Symbol)
|
53
|
+
r.update k => v
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
public
|
58
|
+
|
59
|
+
# process all waiting jobs.
|
60
|
+
#
|
61
|
+
# This method starts processing jobs, as long as there are some. It returns
|
62
|
+
# once no runnable jobs can be found anymore.
|
63
|
+
#
|
64
|
+
# Note that this method is not limited to the set of runnable jobs present
|
65
|
+
# when calling it; if running a job results in newly created runnable jobs
|
66
|
+
# these jobs will be processed as well.
|
67
|
+
#
|
68
|
+
# This method returns the number of processed jobs.
|
69
|
+
def process_all
|
70
|
+
run do |job|
|
71
|
+
!job.nil?
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# processes many jobs.
|
76
|
+
#
|
77
|
+
# This method starts processing jobs, as long as there are some. If no
|
78
|
+
# jobs can be found this method waits until a job becomes available.
|
79
|
+
#
|
80
|
+
# After processing each job is yielded into the passed in block.
|
81
|
+
#
|
82
|
+
# This method continues until:
|
83
|
+
# a) the requested number of jobs (via the count: argument) was processed (note:
|
84
|
+
# repeated job executions due to rerunning jobs that slept or errored count
|
85
|
+
# multiple times), or
|
86
|
+
# b) the block yielded into returns false.
|
87
|
+
#
|
88
|
+
# This method returns the number of processed jobs.
|
89
|
+
def run(count: nil, &block)
|
90
|
+
# to run 10^12 jobs that would take 1 msecs each we would need, at least,
|
91
|
+
# 760 years - so this default should be fine. Also, someone should update
|
92
|
+
# the machine in the meantime :)
|
93
|
+
count ||= 1_000_000_000_000
|
94
|
+
|
95
|
+
processed_jobs_count = 0
|
96
|
+
|
97
|
+
loop do
|
98
|
+
processed_job = Postjob.step
|
99
|
+
processed_jobs_count += 1 if processed_job
|
100
|
+
|
101
|
+
break if processed_jobs_count >= count
|
102
|
+
break if block && (yield(processed_job) == false)
|
103
|
+
|
104
|
+
next if processed_job
|
105
|
+
|
106
|
+
Queue::Notifications.wait_for_new_job
|
107
|
+
end
|
108
|
+
|
109
|
+
processed_jobs_count
|
110
|
+
end
|
111
|
+
|
112
|
+
# Runs a single job
|
113
|
+
#
|
114
|
+
# This method tries to check out a runnable job. If it finds one the
|
115
|
+
# job is processed (via Postjob.process_job) and returned. If not,
|
116
|
+
# this method just returns nil.
|
117
|
+
def step
|
118
|
+
Queue.checkout_runnable do |job|
|
119
|
+
process_job job
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
private
|
124
|
+
|
125
|
+
# This method is called from tests. Otherwise it is supposed to be private.
|
126
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
127
|
+
def process_job(job) # :nodoc:
|
128
|
+
expect! job => Job
|
129
|
+
|
130
|
+
version, status, value = if job.timed_out
|
131
|
+
[job.workflow_version, :timeout, nil]
|
132
|
+
else
|
133
|
+
Runner.process_job(job)
|
134
|
+
end
|
135
|
+
|
136
|
+
if job.workflow_version != "" && version != job.workflow_version
|
137
|
+
raise "Integrity check failed: job's workflow version changed (from #{job.workflow_version} to #{version})"
|
138
|
+
end
|
139
|
+
|
140
|
+
expect! version => String
|
141
|
+
|
142
|
+
case status
|
143
|
+
when :failed then Queue.set_job_error job, *value, status: :failed, version: version
|
144
|
+
when :err then Queue.set_job_error job, *value, status: :err, version: version
|
145
|
+
when :timeout then Queue.set_job_error job, "Timeout", "Timeout", status: :timeout, version: version
|
146
|
+
when :pending then Queue.set_job_pending job, version: version
|
147
|
+
when :ok then Queue.set_job_result job, value, version: version
|
148
|
+
else raise ArgumentError, "Invalid status #{status.inspect}"
|
149
|
+
end
|
150
|
+
|
151
|
+
[status, value]
|
152
|
+
end
|
153
|
+
|
154
|
+
public
|
155
|
+
|
156
|
+
def resolve(token:, result:)
|
157
|
+
job = Queue.find_job_by_token(token)
|
158
|
+
Queue.set_job_result job, result, version: nil
|
159
|
+
end
|
160
|
+
|
161
|
+
def register_workflow(workflow, options = {})
|
162
|
+
expect! options => {
|
163
|
+
version: [nil, /^\d+(\.\d+)*/]
|
164
|
+
}
|
165
|
+
|
166
|
+
workflow.include Postjob::Workflow
|
167
|
+
workflow.send(:set_workflow_version, options[:version] || "0.0")
|
168
|
+
Registry.register workflow, options
|
169
|
+
end
|
170
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# rubocop:disable Metrics/BlockLength
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
|
5
|
+
describe "Postjob.enqueue!" do
|
6
|
+
include TestHelper
|
7
|
+
|
8
|
+
context "with valid arguments" do
|
9
|
+
it "creates a job" do
|
10
|
+
Postjob.enqueue! "FooishWorkflow"
|
11
|
+
expect(TestHelper.jobs_count).to eq(1)
|
12
|
+
end
|
13
|
+
|
14
|
+
it "returns the created job" do
|
15
|
+
id = Postjob.enqueue! "FooishWorkflow"
|
16
|
+
newest_job = TestHelper.newest_job
|
17
|
+
expect(newest_job.id).to eq(id)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "uses the parent id and sets a full id" do
|
21
|
+
id1 = Postjob.enqueue! "FooishWorkflow"
|
22
|
+
id2 = Postjob.enqueue! "FooishWorkflow", parent_id: id1
|
23
|
+
id3 = Postjob.enqueue! "FooishWorkflow", parent_id: id2
|
24
|
+
|
25
|
+
job1 = load_job id1
|
26
|
+
job2 = load_job id2
|
27
|
+
job3 = load_job id3
|
28
|
+
|
29
|
+
expect(job3.full_id).to eq("#{job1.id}.#{job2.id}.#{job3.id}")
|
30
|
+
expect(job3.full_id).to match(/\d+\.\d+\.\d+/)
|
31
|
+
end
|
32
|
+
|
33
|
+
it "sets arguments" do
|
34
|
+
id1 = Postjob.enqueue! "FooishWorkflow", 1, "two", "three"
|
35
|
+
job1 = load_job id1
|
36
|
+
expect(job1.args).to eq([1, "two", "three"])
|
37
|
+
|
38
|
+
id2 = Postjob.enqueue! "FooishWorkflow"
|
39
|
+
job2 = load_job id2
|
40
|
+
expect(job2.args).to eq([])
|
41
|
+
end
|
42
|
+
|
43
|
+
it "sets queue" do
|
44
|
+
id1 = Postjob.enqueue! "FooishWorkflow"
|
45
|
+
job1 = load_job id1
|
46
|
+
expect(job1.queue).to eq("q")
|
47
|
+
|
48
|
+
id1 = Postjob.enqueue! "FooishWorkflow", queue: "bla"
|
49
|
+
job1 = load_job id1
|
50
|
+
expect(job1.queue).to eq("bla")
|
51
|
+
end
|
52
|
+
|
53
|
+
it "sets max_attempts" do
|
54
|
+
id1 = Postjob.enqueue! "FooishWorkflow"
|
55
|
+
job1 = load_job id1
|
56
|
+
expect(job1.max_attempts).to eq(5)
|
57
|
+
|
58
|
+
id1 = Postjob.enqueue! "FooishWorkflow", max_attempts: 2
|
59
|
+
job1 = load_job id1
|
60
|
+
expect(job1.max_attempts).to eq(2)
|
61
|
+
end
|
62
|
+
|
63
|
+
it "sets the status to ready" do
|
64
|
+
id1 = Postjob.enqueue! "FooishWorkflow", 1, "two", "three"
|
65
|
+
job1 = load_job id1
|
66
|
+
expect(job1.status).to eq("ready")
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
context "with invalid arguments" do
|
71
|
+
it "raises an error with missing args" do
|
72
|
+
expect { Postjob.enqueue! queue: "q" }.to raise_error(ArgumentError)
|
73
|
+
expect { Postjob.enqueue! workflow: "q" }.to raise_error(ArgumentError)
|
74
|
+
end
|
75
|
+
|
76
|
+
it "raises an error with invalid args" do
|
77
|
+
expect { Postjob.enqueue! queue: 1, workflow: 13 }.to raise_error(ArgumentError)
|
78
|
+
end
|
79
|
+
|
80
|
+
it "raises an error with invalid parent_id" do
|
81
|
+
id1 = Postjob.enqueue! "FooishWorkflow"
|
82
|
+
|
83
|
+
expect { Postjob.enqueue! "FooishWorkflow", parent_id: id1 - 1 }.to raise_error(PG::ForeignKeyViolation)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
#
|
4
|
+
# This module simulates the MPX services interfaces, with a certain
|
5
|
+
# change not to be able to run (see operational_error?)
|
6
|
+
module MPX
|
7
|
+
module Impl
|
8
|
+
extend self
|
9
|
+
|
10
|
+
def self.load_users(user_ids)
|
11
|
+
user_ids.map do |user_id|
|
12
|
+
{
|
13
|
+
"type" => "User",
|
14
|
+
"id" => user_id
|
15
|
+
}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.clone_group(group_id)
|
20
|
+
group_id + 1000
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.send_email(_user, _cloned_group_id)
|
24
|
+
"sent"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.method_missing(sym, *args)
|
29
|
+
operational_error?
|
30
|
+
Impl.send sym, *args
|
31
|
+
end
|
32
|
+
|
33
|
+
# The operational_error method introduces a certain chance of not being able
|
34
|
+
# to run one of the MPX.* methods, simulating a network timeout or other such
|
35
|
+
# error condition.
|
36
|
+
def self.operational_error?
|
37
|
+
raise "please rerun" if rand < 0.1
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
module RecommendGroupWorkflow
|
42
|
+
Postjob.register_workflow self
|
43
|
+
|
44
|
+
def self.clone_group(group_id)
|
45
|
+
MPX.clone_group(group_id)
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.load_users(user_ids)
|
49
|
+
MPX.load_users(user_ids)
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.send_email(user, cloned_group_id)
|
53
|
+
MPX.send_email user, cloned_group_id
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.run(group_id, user_ids)
|
57
|
+
users = await :load_users, user_ids
|
58
|
+
|
59
|
+
cloned_group_id = await :clone_group, group_id
|
60
|
+
users.each do |user|
|
61
|
+
async :send_email, user, cloned_group_id
|
62
|
+
end
|
63
|
+
await :all
|
64
|
+
users.count
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe "Sub Workflows" do
|
69
|
+
include TestHelper
|
70
|
+
|
71
|
+
let!(:users_count) { 100 }
|
72
|
+
let!(:user_ids) { 100.upto(100 + users_count - 1).to_a }
|
73
|
+
let!(:id) { Postjob.enqueue! "RecommendGroupWorkflow", 1, user_ids }
|
74
|
+
|
75
|
+
it "runs the job returning the result" do
|
76
|
+
expect(MPX::Impl).to receive(:clone_group).exactly(1).times.and_call_original
|
77
|
+
expect(MPX::Impl).to receive(:load_users).exactly(1).times.and_call_original
|
78
|
+
expect(MPX::Impl).to receive(:send_email).exactly(users_count).times.and_call_original
|
79
|
+
|
80
|
+
Postjob.process_all
|
81
|
+
|
82
|
+
print_jobs
|
83
|
+
processed_job = load_job(id)
|
84
|
+
expect(processed_job.status).to eq("ok")
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
module ManualWorkflow
|
4
|
+
def self.run
|
5
|
+
job = async :manual, timeout: 10
|
6
|
+
_token = workflow_token job
|
7
|
+
manual_result = await job
|
8
|
+
"manual-result:#{manual_result}"
|
9
|
+
end
|
10
|
+
|
11
|
+
Postjob.register_workflow self
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "manual processing" do
|
15
|
+
let!(:id) { Postjob.enqueue! "ManualWorkflow" }
|
16
|
+
|
17
|
+
include TestHelper
|
18
|
+
|
19
|
+
before do
|
20
|
+
Postjob.process_all
|
21
|
+
end
|
22
|
+
|
23
|
+
def load_child_job
|
24
|
+
TestHelper.load_job "SELECT * FROM postjobs WHERE parent_id=$1", id
|
25
|
+
end
|
26
|
+
|
27
|
+
def token
|
28
|
+
load_token(load_child_job)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "creates a token" do
|
32
|
+
expect! token => /[A-F0-9]{8}-[A-F0-9]{4}-[A-F0-9]{4}-[a-fA-F0-9]{4}-[A-F0-9]{12}/i
|
33
|
+
end
|
34
|
+
|
35
|
+
it "the token can be used to resolve the load_child_job" do
|
36
|
+
Postjob.resolve token: token, result: "foobar"
|
37
|
+
expect(load_child_job.status).to eq("ok")
|
38
|
+
expect(load_child_job.result).to eq("foobar")
|
39
|
+
|
40
|
+
Postjob.process_all
|
41
|
+
job = load_job id
|
42
|
+
expect(job.status).to eq("ok")
|
43
|
+
expect(job.result).to eq("manual-result:foobar")
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# rubocop:disable Metrics/BlockLength
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
|
5
|
+
module MaxAttemptWorkflow
|
6
|
+
module ChildWorker
|
7
|
+
def self.run
|
8
|
+
raise "Nono nono!"
|
9
|
+
end
|
10
|
+
|
11
|
+
Postjob.register_workflow self
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.run(max_attempts)
|
15
|
+
set_workflow_status "starting up"
|
16
|
+
await async ChildWorker, max_attempts: max_attempts
|
17
|
+
set_workflow_status "starting finished"
|
18
|
+
end
|
19
|
+
|
20
|
+
Postjob.register_workflow self
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "max_attempts" do
|
24
|
+
before do
|
25
|
+
Simple::SQL.all("DELETE FROM postjob.postjobs")
|
26
|
+
end
|
27
|
+
|
28
|
+
def load_child_job
|
29
|
+
TestHelper.load_job "SELECT * FROM postjobs WHERE parent_id=$1", id
|
30
|
+
end
|
31
|
+
|
32
|
+
def load_job
|
33
|
+
TestHelper.load_job(id)
|
34
|
+
end
|
35
|
+
|
36
|
+
def load_child_job
|
37
|
+
TestHelper.load_job "SELECT * FROM postjobs WHERE parent_id=$1", id
|
38
|
+
end
|
39
|
+
|
40
|
+
context "when running with max_attempts 1" do
|
41
|
+
let!(:id) { Postjob.enqueue!("MaxAttemptWorkflow", 1, max_attempts: 1) }
|
42
|
+
|
43
|
+
it "fails the childjob with a timeout" do
|
44
|
+
while Postjob.process_all > 0
|
45
|
+
sleep 0.03
|
46
|
+
end
|
47
|
+
|
48
|
+
expect(load_child_job.failed_attempts).to eq(1)
|
49
|
+
expect(load_child_job.status).to eq("failed")
|
50
|
+
|
51
|
+
expect(load_job.status).to eq("failed")
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context "when running with max_attempts 5" do
|
56
|
+
let!(:id) { Postjob.enqueue!("MaxAttemptWorkflow", 5, max_attempts: 1) }
|
57
|
+
|
58
|
+
it "fails the childjob with a timeout" do
|
59
|
+
while Postjob.process_all > 0
|
60
|
+
sleep 0.03
|
61
|
+
end
|
62
|
+
|
63
|
+
expect(load_child_job.failed_attempts).to eq(5)
|
64
|
+
expect(load_child_job.status).to eq("failed")
|
65
|
+
|
66
|
+
# The main job now failed. It only ran with max_attempts: 1
|
67
|
+
expect(load_job.status).to eq("failed")
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
module FastFailWorkflow
|
4
|
+
def self.run
|
5
|
+
await async :manual, timeout: 0.000001
|
6
|
+
end
|
7
|
+
|
8
|
+
Postjob.register_workflow self
|
9
|
+
end
|
10
|
+
|
11
|
+
describe "timeout" do
|
12
|
+
context "with an instant timeout" do
|
13
|
+
let!(:id) { Postjob.enqueue! "FastFailWorkflow" }
|
14
|
+
|
15
|
+
before do
|
16
|
+
Postjob.process_all
|
17
|
+
end
|
18
|
+
|
19
|
+
it "fails the childjob with a timeout" do
|
20
|
+
manual_job = TestHelper.load_job "SELECT * FROM postjobs WHERE parent_id=$1", id
|
21
|
+
expect(manual_job.status).to eq("timeout")
|
22
|
+
expect { manual_job.resolve }.to raise_error(Timeout::Error)
|
23
|
+
end
|
24
|
+
|
25
|
+
xit "fails the mainjob" do
|
26
|
+
job = TestHelper.load_job(id)
|
27
|
+
expect(job.status).to eq("err")
|
28
|
+
expect { job.resolve }.to raise_error(Postjob::Error)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
module WorkflowStatusWorkflow
|
4
|
+
def self.run
|
5
|
+
set_workflow_status "starting-up"
|
6
|
+
|
7
|
+
job = async :manual, timeout: 10
|
8
|
+
set_workflow_status "created-manual"
|
9
|
+
|
10
|
+
_token = workflow_token job
|
11
|
+
set_workflow_status "created-manual-token"
|
12
|
+
|
13
|
+
manual_result = await job
|
14
|
+
set_workflow_status "got-manual-token"
|
15
|
+
|
16
|
+
"manual-result:#{manual_result}"
|
17
|
+
end
|
18
|
+
|
19
|
+
Postjob.register_workflow self
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "workflow_status" do
|
23
|
+
let!(:id) { Postjob.enqueue! "WorkflowStatusWorkflow" }
|
24
|
+
|
25
|
+
include TestHelper
|
26
|
+
|
27
|
+
before do
|
28
|
+
Postjob.process_all
|
29
|
+
end
|
30
|
+
|
31
|
+
def load_child_job
|
32
|
+
TestHelper.load_job "SELECT * FROM postjobs WHERE parent_id=$1", id
|
33
|
+
end
|
34
|
+
|
35
|
+
def token
|
36
|
+
load_token(load_child_job)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "creates a token" do
|
40
|
+
expect! token => /[A-F0-9]{8}-[A-F0-9]{4}-[A-F0-9]{4}-[a-fA-F0-9]{4}-[A-F0-9]{12}/i
|
41
|
+
end
|
42
|
+
|
43
|
+
it "the token can be used to resolve the load_child_job" do
|
44
|
+
Postjob.resolve token: token, result: "foobar"
|
45
|
+
expect(load_child_job.status).to eq("ok")
|
46
|
+
expect(load_job(id).workflow_status).to eq("created-manual-token")
|
47
|
+
|
48
|
+
Postjob.process_all
|
49
|
+
expect(load_job(id).status).to eq("ok")
|
50
|
+
expect(load_job(id).workflow_status).to eq("got-manual-token")
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
module BarWorkflow
|
4
|
+
def self.run
|
5
|
+
"Foo"
|
6
|
+
end
|
7
|
+
|
8
|
+
Postjob.register_workflow self, version: "1.2"
|
9
|
+
end
|
10
|
+
|
11
|
+
describe "Postjob.process_job" do
|
12
|
+
include TestHelper
|
13
|
+
|
14
|
+
let!(:job) { Postjob.enqueue! "BarWorkflow" }
|
15
|
+
|
16
|
+
it "runs the job returning the result" do
|
17
|
+
expect(Postjob.process_job(newest_job)).to eq([:ok, "Foo"])
|
18
|
+
end
|
19
|
+
|
20
|
+
it "updates the job version" do
|
21
|
+
expect(newest_job.workflow_version).to eq("")
|
22
|
+
Postjob.process_job(newest_job)
|
23
|
+
expect(newest_job.workflow_version).to eq("1.2")
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
module EncodeSpecWorkflow
|
4
|
+
def self.run(_inp, out)
|
5
|
+
out
|
6
|
+
end
|
7
|
+
|
8
|
+
Postjob.register_workflow self
|
9
|
+
end
|
10
|
+
|
11
|
+
describe "Postjob::Queue::Encoder" do
|
12
|
+
def run_workflow(input: "dummy input", output: "dummy output")
|
13
|
+
id = Postjob.enqueue! "EncodeSpecWorkflow", input, output
|
14
|
+
Postjob.process_all
|
15
|
+
job = TestHelper.load_job(id)
|
16
|
+
job.result
|
17
|
+
end
|
18
|
+
|
19
|
+
it "allows encoding of numbers" do
|
20
|
+
expect(run_workflow(input: 10, output: 12)).to eq(12)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "allows encoding of booleans" do
|
24
|
+
expect(run_workflow(input: true, output: false)).to eq(false)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "allows encoding of strings" do
|
28
|
+
expect(run_workflow(input: "foo", output: "bar")).to eq("bar")
|
29
|
+
end
|
30
|
+
|
31
|
+
it "allows encoding of arrays" do
|
32
|
+
expect(run_workflow(input: ["foo", "foo"], output: ["bar", "baz"])).to eq(["bar", "baz"])
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "invalid input" do
|
36
|
+
it "cannot encode Symbols on input" do
|
37
|
+
expect do
|
38
|
+
run_workflow input: :foo
|
39
|
+
end.to raise_error(ArgumentError)
|
40
|
+
|
41
|
+
expect do
|
42
|
+
run_workflow output: :foo
|
43
|
+
end.to raise_error(ArgumentError)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|