rworkflow 0.6.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/MIT-LICENSE +20 -0
- data/Rakefile +117 -0
- data/lib/rworkflow/flow.rb +450 -0
- data/lib/rworkflow/flow_registry.rb +69 -0
- data/lib/rworkflow/lifecycle.rb +102 -0
- data/lib/rworkflow/minitest/test.rb +53 -0
- data/lib/rworkflow/minitest/worker.rb +17 -0
- data/lib/rworkflow/minitest.rb +8 -0
- data/lib/rworkflow/sidekiq_flow.rb +186 -0
- data/lib/rworkflow/sidekiq_helper.rb +84 -0
- data/lib/rworkflow/sidekiq_lifecycle.rb +8 -0
- data/lib/rworkflow/sidekiq_state.rb +42 -0
- data/lib/rworkflow/state.rb +104 -0
- data/lib/rworkflow/state_error.rb +13 -0
- data/lib/rworkflow/transition_error.rb +9 -0
- data/lib/rworkflow/version.rb +3 -0
- data/lib/rworkflow/worker.rb +62 -0
- data/lib/rworkflow.rb +15 -0
- data/lib/tasks/rworkflow_tasks.rake +4 -0
- data/test/dummy/README.rdoc +28 -0
- data/test/dummy/Rakefile +6 -0
- data/test/dummy/app/assets/javascripts/application.js +13 -0
- data/test/dummy/app/assets/stylesheets/application.css +15 -0
- data/test/dummy/app/controllers/application_controller.rb +5 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/bin/bundle +3 -0
- data/test/dummy/bin/rails +4 -0
- data/test/dummy/bin/rake +4 -0
- data/test/dummy/config/application.rb +15 -0
- data/test/dummy/config/boot.rb +5 -0
- data/test/dummy/config/database.yml +25 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +37 -0
- data/test/dummy/config/environments/production.rb +83 -0
- data/test/dummy/config/environments/test.rb +41 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/test/dummy/config/initializers/inflections.rb +16 -0
- data/test/dummy/config/initializers/mime_types.rb +4 -0
- data/test/dummy/config/initializers/session_store.rb +3 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +23 -0
- data/test/dummy/config/routes.rb +56 -0
- data/test/dummy/config/secrets.yml +22 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/db/test.sqlite3 +0 -0
- data/test/dummy/log/test.log +516 -0
- data/test/dummy/public/404.html +67 -0
- data/test/dummy/public/422.html +67 -0
- data/test/dummy/public/500.html +66 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/flow_test.rb +112 -0
- data/test/lifecycle_test.rb +81 -0
- data/test/rworkflow_test.rb +7 -0
- data/test/sidekiq_flow_test.rb +173 -0
- data/test/state_test.rb +99 -0
- data/test/test_helper.rb +32 -0
- metadata +199 -0
@@ -0,0 +1,66 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>We're sorry, but something went wrong (500)</title>
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
6
|
+
<style>
|
7
|
+
body {
|
8
|
+
background-color: #EFEFEF;
|
9
|
+
color: #2E2F30;
|
10
|
+
text-align: center;
|
11
|
+
font-family: arial, sans-serif;
|
12
|
+
margin: 0;
|
13
|
+
}
|
14
|
+
|
15
|
+
div.dialog {
|
16
|
+
width: 95%;
|
17
|
+
max-width: 33em;
|
18
|
+
margin: 4em auto 0;
|
19
|
+
}
|
20
|
+
|
21
|
+
div.dialog > div {
|
22
|
+
border: 1px solid #CCC;
|
23
|
+
border-right-color: #999;
|
24
|
+
border-left-color: #999;
|
25
|
+
border-bottom-color: #BBB;
|
26
|
+
border-top: #B00100 solid 4px;
|
27
|
+
border-top-left-radius: 9px;
|
28
|
+
border-top-right-radius: 9px;
|
29
|
+
background-color: white;
|
30
|
+
padding: 7px 12% 0;
|
31
|
+
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
|
32
|
+
}
|
33
|
+
|
34
|
+
h1 {
|
35
|
+
font-size: 100%;
|
36
|
+
color: #730E15;
|
37
|
+
line-height: 1.5em;
|
38
|
+
}
|
39
|
+
|
40
|
+
div.dialog > p {
|
41
|
+
margin: 0 0 1em;
|
42
|
+
padding: 1em;
|
43
|
+
background-color: #F7F7F7;
|
44
|
+
border: 1px solid #CCC;
|
45
|
+
border-right-color: #999;
|
46
|
+
border-left-color: #999;
|
47
|
+
border-bottom-color: #999;
|
48
|
+
border-bottom-left-radius: 4px;
|
49
|
+
border-bottom-right-radius: 4px;
|
50
|
+
border-top-color: #DADADA;
|
51
|
+
color: #666;
|
52
|
+
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
|
53
|
+
}
|
54
|
+
</style>
|
55
|
+
</head>
|
56
|
+
|
57
|
+
<body>
|
58
|
+
<!-- This file lives in public/500.html -->
|
59
|
+
<div class="dialog">
|
60
|
+
<div>
|
61
|
+
<h1>We're sorry, but something went wrong.</h1>
|
62
|
+
</div>
|
63
|
+
<p>If you are the application owner check the logs for more information.</p>
|
64
|
+
</div>
|
65
|
+
</body>
|
66
|
+
</html>
|
File without changes
|
data/test/flow_test.rb
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module Rworkflow
|
4
|
+
class FlowTest < ActiveSupport::TestCase
|
5
|
+
def setup
|
6
|
+
super
|
7
|
+
RedisRds::Object.flushdb
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_workflow
|
11
|
+
lifecycle = Lifecycle.new do |lc|
|
12
|
+
lc.state("State1", {cardinality: 2}) do |state|
|
13
|
+
state.transition :pushed, Flow::STATE_SUCCESSFUL
|
14
|
+
state.transition :failed, Flow::STATE_FAILED
|
15
|
+
end
|
16
|
+
|
17
|
+
lc.initial = "State1"
|
18
|
+
end
|
19
|
+
initial_objects = [1,2,3]
|
20
|
+
workflow = Flow.create(lifecycle, 'myWorkflow')
|
21
|
+
workflow_id = workflow.id
|
22
|
+
|
23
|
+
assert Flow.registered?(workflow)
|
24
|
+
|
25
|
+
workflow.start(initial_objects)
|
26
|
+
workflow = Flow.new(workflow_id)
|
27
|
+
assert !workflow.finished?
|
28
|
+
|
29
|
+
workflow.fetch(1, 'State1') do |objects|
|
30
|
+
assert_equal 2, objects.size
|
31
|
+
assert_equal (objects & initial_objects), objects
|
32
|
+
assert !workflow.finished?
|
33
|
+
workflow.transition('State1', :pushed, objects.first)
|
34
|
+
workflow.transition('State1', :failed, objects.second)
|
35
|
+
end
|
36
|
+
|
37
|
+
assert !workflow.finished?
|
38
|
+
|
39
|
+
workflow.fetch(2, 'State1') do |last|
|
40
|
+
assert_equal 1, last.size
|
41
|
+
assert_equal (last & initial_objects), last
|
42
|
+
assert !workflow.finished?
|
43
|
+
workflow.transition('State1', :pushed, last.first)
|
44
|
+
end
|
45
|
+
|
46
|
+
assert workflow.finished?
|
47
|
+
counters = workflow.get_counters
|
48
|
+
assert_equal 2, counters[Flow::STATE_SUCCESSFUL]
|
49
|
+
assert_equal 1, counters[Flow::STATE_FAILED]
|
50
|
+
|
51
|
+
assert_equal [1,3], workflow.list_objects(Flow::STATE_SUCCESSFUL)
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_flow_cardinality_all_started
|
55
|
+
lifecycle = Lifecycle.new do |lc|
|
56
|
+
lc.state("State1", {cardinality: Lifecycle::CARDINALITY_ALL_STARTED}) do |state|
|
57
|
+
state.transition :pushed, Flow::STATE_SUCCESSFUL
|
58
|
+
state.transition :failed, Flow::STATE_FAILED
|
59
|
+
end
|
60
|
+
|
61
|
+
lc.initial = "State1"
|
62
|
+
end
|
63
|
+
|
64
|
+
initial_objects = (1..6).to_a
|
65
|
+
workflow = Flow.create(lifecycle, 'myWorkflow')
|
66
|
+
workflow.start(initial_objects)
|
67
|
+
workflow.fetch(1, 'State1') do |objects|
|
68
|
+
assert_equal initial_objects.size, objects.size, 'The flow should fetch the number of objects given at the start'
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_flow_state_policy_wait
|
73
|
+
initial_objects = [1,2,3,4]
|
74
|
+
lifecycle = Lifecycle.new do |lc|
|
75
|
+
lc.state("InitState", {cardinality: 1}) do |state|
|
76
|
+
state.transition :pushed, "WaitState"
|
77
|
+
end
|
78
|
+
|
79
|
+
lc.state("WaitState", {cardinality: initial_objects.size, priority: State::DEFAULT_PRIORITY, policy: State::STATE_POLICY_WAIT}) do |state|
|
80
|
+
state.transition :collected, Flow::STATE_SUCCESSFUL
|
81
|
+
end
|
82
|
+
|
83
|
+
lc.initial = "InitState"
|
84
|
+
end
|
85
|
+
|
86
|
+
workflow = Flow.create(lifecycle, 'myWorkflow')
|
87
|
+
workflow.start(initial_objects)
|
88
|
+
|
89
|
+
(initial_objects.size - 1).times do
|
90
|
+
workflow.fetch(1, 'InitState') do |objects|
|
91
|
+
assert_equal 1, objects.size, 'The flow should fetch the number of objects corresponding to the state cardinality'
|
92
|
+
workflow.transition('InitState', :pushed, objects)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
workflow.fetch(1, 'WaitState') do |objects|
|
97
|
+
# This block should not be executed
|
98
|
+
assert false, 'The collector state should not be executed until there is enough waiting objects (>= cardinality)'
|
99
|
+
end
|
100
|
+
|
101
|
+
# Lat object push in the initial state
|
102
|
+
workflow.fetch(1, 'InitState') do |objects|
|
103
|
+
assert_equal 1, objects.size, 'The flow should fetch the number of objects corresponding to the state cardinality'
|
104
|
+
workflow.transition('InitState', :pushed, objects)
|
105
|
+
end
|
106
|
+
|
107
|
+
workflow.fetch(1, 'WaitState') do |objects|
|
108
|
+
assert_equal initial_objects.size, objects.size, 'The flow should fetch the number of objects given at the start'
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module Rworkflow
|
4
|
+
class LifecycleTest < ActiveSupport::TestCase
|
5
|
+
def setup
|
6
|
+
super
|
7
|
+
RedisRds::Object.flushdb
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_definition
|
11
|
+
lifecycle = Lifecycle.new do |lc|
|
12
|
+
lc.state("State1") do |state|
|
13
|
+
state.transition :pushed, :successful
|
14
|
+
end
|
15
|
+
|
16
|
+
lc.initial = 'State1'
|
17
|
+
end
|
18
|
+
|
19
|
+
assert_equal 'State1', lifecycle.initial
|
20
|
+
assert_equal :successful, lifecycle.transition("State1", :pushed)
|
21
|
+
assert_raises(Rworkflow::StateError) { lifecycle.transition("UnexistingState", :pushed) }
|
22
|
+
assert_raises(Rworkflow::TransitionError) { lifecycle.transition("State1", :non_existing_transition) }
|
23
|
+
|
24
|
+
lifecycle.default = Rworkflow::Flow::STATE_FAILED
|
25
|
+
assert_equal Rworkflow::Flow::STATE_FAILED, lifecycle.default
|
26
|
+
assert_equal Rworkflow::Flow::STATE_FAILED, lifecycle.transition("State1", :non_existing_transition)
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_serialization
|
30
|
+
lifecycle = Lifecycle.new do |lc|
|
31
|
+
lc.state("State1") do |state|
|
32
|
+
state.transition :pushed, :successful
|
33
|
+
end
|
34
|
+
|
35
|
+
lc.initial = "State1"
|
36
|
+
end
|
37
|
+
|
38
|
+
serialized = lifecycle.serialize
|
39
|
+
|
40
|
+
unserialized = Lifecycle.unserialize(serialized)
|
41
|
+
|
42
|
+
assert_equal lifecycle.initial, unserialized.initial
|
43
|
+
assert_equal Set.new(lifecycle.states.keys), Set.new(unserialized.states.keys)
|
44
|
+
assert lifecycle.states.all? {|name, state| unserialized.states[name].instance_eval{@transitions} == state.instance_eval{@transitions} }
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_concat
|
48
|
+
lifecycle_one = LCFactory.simple_lifecycle("1", :next)
|
49
|
+
lifecycle_two = LCFactory.simple_lifecycle("2", :finish)
|
50
|
+
|
51
|
+
lifecycle_one.concat!("1", :next, lifecycle_two)
|
52
|
+
|
53
|
+
assert_equal '1', lifecycle_one.initial
|
54
|
+
assert_equal '2', lifecycle_one.transition("1", :next)
|
55
|
+
assert_equal SidekiqFlow::STATE_SUCCESSFUL, lifecycle_one.transition("2", :finish)
|
56
|
+
|
57
|
+
lifecycle_three = LCFactory.simple_lifecycle("3", :finish)
|
58
|
+
lifecycle_three.state('1') do |s|
|
59
|
+
s.transition(:next, '3')
|
60
|
+
s.transition(:prev, '2')
|
61
|
+
end
|
62
|
+
|
63
|
+
lifecycle_one.concat!('2', :finish, lifecycle_three)
|
64
|
+
assert_equal '3', lifecycle_one.transition('1', :next)
|
65
|
+
assert_equal '2', lifecycle_one.transition('1', :prev)
|
66
|
+
assert_equal '3', lifecycle_one.transition('2', :finish)
|
67
|
+
end
|
68
|
+
|
69
|
+
class LCFactory
|
70
|
+
def self.simple_lifecycle(state_name, transition, cardinality = 1, priority = nil)
|
71
|
+
return Rworkflow::Lifecycle.new do |cycle|
|
72
|
+
cycle.state(state_name, cardinality: cardinality, priority: priority) do |state|
|
73
|
+
state.transition transition, Rworkflow::SidekiqFlow::STATE_SUCCESSFUL
|
74
|
+
state.transition :failed, Rworkflow::SidekiqFlow::STATE_FAILED
|
75
|
+
end
|
76
|
+
cycle.initial = state_name
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,173 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module Rworkflow
|
4
|
+
class SidekiqFlowTest < ActiveSupport::TestCase
|
5
|
+
def setup
|
6
|
+
super
|
7
|
+
RedisRds::Object.flushdb
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_lethal_workflow
|
11
|
+
lifecycle = Lifecycle.new do |lc|
|
12
|
+
lc.state("Rworkflow::SidekiqFlowTest::Floating", {CARDINALITY_ALL_STARTEDality: 10}) do |state|
|
13
|
+
state.transition :rescued, 'Rworkflow::SidekiqFlowTest::Lifeboat'
|
14
|
+
state.transition :drowned, Rworkflow::Flow::STATE_FAILED
|
15
|
+
end
|
16
|
+
lc.state("Rworkflow::SidekiqFlowTest::Lifeboat", {cardinality: 2}) do |state|
|
17
|
+
state.transition :landed, 'Rworkflow::SidekiqFlowTest::Land'
|
18
|
+
state.transition :starved, Rworkflow::Flow::STATE_FAILED
|
19
|
+
end
|
20
|
+
lc.state("Rworkflow::SidekiqFlowTest::Land") do |state|
|
21
|
+
state.transition :rescued, Rworkflow::Flow::STATE_SUCCESSFUL
|
22
|
+
state.transition :died, Rworkflow::Flow::STATE_FAILED
|
23
|
+
end
|
24
|
+
|
25
|
+
lc.initial = "Rworkflow::SidekiqFlowTest::Floating"
|
26
|
+
end
|
27
|
+
|
28
|
+
initial_objects = (0...20).to_a
|
29
|
+
workflow = SidekiqFlow.create(lifecycle, 'Lethal Rworkflow 2: Lethaler', {})
|
30
|
+
workflow.start(initial_objects)
|
31
|
+
|
32
|
+
assert workflow.finished?
|
33
|
+
counters = workflow.get_counters
|
34
|
+
assert_equal 19, counters[Rworkflow::Flow::STATE_FAILED]
|
35
|
+
assert_equal 1, counters[Rworkflow::Flow::STATE_SUCCESSFUL]
|
36
|
+
|
37
|
+
assert 2, RedisRds::String.new("Rworkflow::SidekiqFlowTest::Floating").get.to_i
|
38
|
+
assert 6, RedisRds::String.new("Rworkflow::SidekiqFlowTest::Lifeboat").get.to_i
|
39
|
+
assert 2, RedisRds::String.new("Rworkflow::SidekiqFlowTest::Land").get.to_i
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_pause_continue
|
43
|
+
lifecycle = Lifecycle.new do |lc|
|
44
|
+
lc.state("Rworkflow::SidekiqFlowTest::Floating", {cardinality: 10}) do |state|
|
45
|
+
state.transition :rescued, 'Rworkflow::SidekiqFlowTest::Lifeboat'
|
46
|
+
state.transition :drowned, Rworkflow::Flow::STATE_FAILED
|
47
|
+
end
|
48
|
+
lc.state("Rworkflow::SidekiqFlowTest::Lifeboat", {cardinality: 2}) do |state|
|
49
|
+
state.transition :landed, Rworkflow::Flow::STATE_SUCCESSFUL
|
50
|
+
state.transition :starved, Rworkflow::Flow::STATE_FAILED
|
51
|
+
end
|
52
|
+
|
53
|
+
lc.initial = "Rworkflow::SidekiqFlowTest::Floating"
|
54
|
+
end
|
55
|
+
|
56
|
+
initial_objects = (0...20).to_a
|
57
|
+
workflow = SidekiqFlow.create(lifecycle, 'Lethal Workflow 4: Lethalerest', {})
|
58
|
+
|
59
|
+
workflow.pause
|
60
|
+
workflow.start(initial_objects)
|
61
|
+
assert !workflow.finished?
|
62
|
+
workflow.pause
|
63
|
+
workflow.continue
|
64
|
+
assert !workflow.finished?
|
65
|
+
workflow.continue
|
66
|
+
assert workflow.finished?
|
67
|
+
|
68
|
+
counters = workflow.get_counters
|
69
|
+
assert_equal 18, counters[Rworkflow::Flow::STATE_FAILED]
|
70
|
+
assert_equal 2, counters[Rworkflow::Flow::STATE_SUCCESSFUL]
|
71
|
+
|
72
|
+
assert 2, RedisRds::String.new("Rworkflow::SidekiqFlowTest::Floating").get.to_i
|
73
|
+
assert 6, RedisRds::String.new("Rworkflow::SidekiqFlowTest::Lifeboat").get.to_i
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_collector_state_workflow
|
77
|
+
lifecycle = Lifecycle.new do |lc|
|
78
|
+
lc.state("Rworkflow::SidekiqFlowTest::PostcardSend", {cardinality: 1}) do |state|
|
79
|
+
state.transition :sent, 'Rworkflow::SidekiqFlowTest::PostcardCollector'
|
80
|
+
end
|
81
|
+
|
82
|
+
lc.state("Rworkflow::SidekiqFlowTest::PostcardCollector", {cardinality: Lifecycle::CARDINALITY_ALL_STARTED, priority: State::DEFAULT_PRIORITY, policy: State::STATE_POLICY_WAIT}) do |state|
|
83
|
+
state.transition :received, Rworkflow::Flow::STATE_SUCCESSFUL
|
84
|
+
end
|
85
|
+
|
86
|
+
lc.initial = "Rworkflow::SidekiqFlowTest::PostcardSend"
|
87
|
+
end
|
88
|
+
|
89
|
+
initial_objects = (0...20).to_a
|
90
|
+
workflow = SidekiqFlow.create(lifecycle, 'CollectorWorkflow', {})
|
91
|
+
workflow.start(initial_objects)
|
92
|
+
|
93
|
+
assert workflow.finished?, 'Rworkflow finish successfully'
|
94
|
+
assert_equal 20, RedisRds::String.new("Rworkflow::SidekiqFlowTest::PostcardSend").get.to_i, 'All initial objects should be processed by the first state one by one'
|
95
|
+
assert_equal 1, RedisRds::String.new("Rworkflow::SidekiqFlowTest::PostcardSend_card").get.to_i, 'All initial objects should be processed by the first state one by one'
|
96
|
+
assert_equal 1, RedisRds::String.new("Rworkflow::SidekiqFlowTest::PostcardCollector").get.to_i, 'All initial objects should be processed by the collector state all at once'
|
97
|
+
assert_equal initial_objects.size, RedisRds::String.new("Rworkflow::SidekiqFlowTest::PostcardCollector_card").get.to_i, 'All initial objects should be processed by the collector state all at once'
|
98
|
+
end
|
99
|
+
|
100
|
+
def test_gated
|
101
|
+
lifecycle = Lifecycle.new do |lc|
|
102
|
+
lc.state("Rworkflow::SidekiqFlowTest::Floating", {cardinality: 10}) do |state|
|
103
|
+
state.transition :rescued, 'Rworkflow::SidekiqFlowTest::Lifeboat'
|
104
|
+
state.transition :drowned, Rworkflow::Flow::STATE_FAILED
|
105
|
+
end
|
106
|
+
lc.state("Rworkflow::SidekiqFlowTest::Lifeboat", {cardinality: 2, policy: SidekiqFlow::STATE_POLICY_GATED}) do |state|
|
107
|
+
state.transition :landed, Rworkflow::Flow::STATE_SUCCESSFUL
|
108
|
+
state.transition :starved, Rworkflow::Flow::STATE_FAILED
|
109
|
+
end
|
110
|
+
|
111
|
+
lc.initial = "Rworkflow::SidekiqFlowTest::Floating"
|
112
|
+
end
|
113
|
+
|
114
|
+
initial_objects = (0...20).to_a
|
115
|
+
workflow = SidekiqFlow.create(lifecycle, 'Lethal Workflow 4: Lethalerest', {})
|
116
|
+
|
117
|
+
workflow.start(initial_objects)
|
118
|
+
assert !workflow.finished?
|
119
|
+
workflow.open_gate('Rworkflow::SidekiqFlowTest::Lifeboat')
|
120
|
+
assert workflow.finished?
|
121
|
+
|
122
|
+
counters = workflow.get_counters
|
123
|
+
assert_equal 18, counters[Rworkflow::Flow::STATE_FAILED]
|
124
|
+
assert_equal 2, counters[Rworkflow::Flow::STATE_SUCCESSFUL]
|
125
|
+
|
126
|
+
assert 2, RedisRds::String.new("Rworkflow::SidekiqFlowTest::Floating").get.to_i
|
127
|
+
assert 6, RedisRds::String.new("Rworkflow::SidekiqFlowTest::Lifeboat").get.to_i
|
128
|
+
end
|
129
|
+
|
130
|
+
class Floating < Worker
|
131
|
+
def process(objects)
|
132
|
+
rescued, drowned = objects.partition { |object| object.even? }
|
133
|
+
transition(:rescued, rescued)
|
134
|
+
transition(:drowned, drowned)
|
135
|
+
RedisRds::String.new(self.class.name).incr
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
class Lifeboat < Worker
|
140
|
+
def process(objects)
|
141
|
+
landed, starved = objects.partition { |object| object < 4 }
|
142
|
+
transition(:landed, landed)
|
143
|
+
transition(:starved, starved)
|
144
|
+
RedisRds::String.new(self.class.name).incr
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
class Land < Worker
|
149
|
+
def process(objects)
|
150
|
+
rescued, died = objects.partition { |object| object == 0 }
|
151
|
+
transition(:rescued, rescued)
|
152
|
+
transition(:died, died)
|
153
|
+
RedisRds::String.new(self.class.name).incr
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
class PostcardSend < Worker
|
158
|
+
def process(objects)
|
159
|
+
transition(:sent, objects)
|
160
|
+
RedisRds::String.new(self.class.name).incr
|
161
|
+
RedisRds::String.new("#{self.class.name}_card").set(objects.size)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
class PostcardCollector < Worker
|
166
|
+
def process(objects)
|
167
|
+
transition(:received, objects)
|
168
|
+
RedisRds::String.new(self.class.name).incr
|
169
|
+
RedisRds::String.new("#{self.class.name}_card").set(objects.size)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
data/test/state_test.rb
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module Rworkflow
|
4
|
+
class StateTest < ActiveSupport::TestCase
|
5
|
+
def setup
|
6
|
+
super
|
7
|
+
@state = State.new
|
8
|
+
RedisRds::Object.flushdb
|
9
|
+
end
|
10
|
+
|
11
|
+
def teardown
|
12
|
+
super
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_transition
|
16
|
+
@state.transition('a', 'b')
|
17
|
+
@state.transition('b', 'c')
|
18
|
+
|
19
|
+
assert_equal 'b', @state.transitions['a'], 'Transition A->B missing.'
|
20
|
+
assert_equal 'c', @state.transitions['b'], 'Transition B->C missing.'
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_perform
|
24
|
+
@state.transition('a', 'b')
|
25
|
+
@state.transition('b', 'c')
|
26
|
+
|
27
|
+
assert_equal 'b', @state.perform('a'), 'Transition A->B missing.'
|
28
|
+
assert_equal 'c', @state.perform('b'), 'Transition B->C missing.'
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_equality
|
32
|
+
other_state = State.new
|
33
|
+
|
34
|
+
assert_equal @state, other_state, 'State A and B should be equal.'
|
35
|
+
|
36
|
+
other_state.policy = State::STATE_POLICY_WAIT
|
37
|
+
assert_not_equal @state, other_state, 'State A != B: different policies!'
|
38
|
+
|
39
|
+
other_state = State.new
|
40
|
+
other_state.priority = :high
|
41
|
+
assert_not_equal @state, other_state, 'State A != B: different priorities!'
|
42
|
+
|
43
|
+
other_state = State.new
|
44
|
+
other_state.cardinality = 32
|
45
|
+
assert_not_equal @state, other_state, 'State A != B: different cardinalities!'
|
46
|
+
|
47
|
+
other_state = State.new
|
48
|
+
other_state.transition('a', 'b')
|
49
|
+
other_state.transition('b', 'c')
|
50
|
+
@state.transition('a', 'b')
|
51
|
+
@state.transition('b', 'c')
|
52
|
+
assert_equal @state, other_state, 'State A === B: same transitions!'
|
53
|
+
|
54
|
+
other_state.transition('c', 'd')
|
55
|
+
assert_not_equal @state, other_state, 'State A != B: different transitions!'
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_serialization
|
59
|
+
other_state = State.new
|
60
|
+
other_state.transition('a', 'b')
|
61
|
+
other_state.transition('b', 'c')
|
62
|
+
assert_not_equal @state.serialize, other_state.serialize, 'State A should not equal B: serialization failed'
|
63
|
+
|
64
|
+
@state.transition('a', 'b')
|
65
|
+
@state.transition('b', 'c')
|
66
|
+
assert_equal @state.serialize, other_state.serialize, 'State A should equal B: serialization failed'
|
67
|
+
end
|
68
|
+
|
69
|
+
def test_clone
|
70
|
+
cloned = @state.clone
|
71
|
+
assert_equal @state, cloned, 'Original and cloned states should be equal'
|
72
|
+
assert !@state.equal?(cloned), 'Original and cloned states should not be the same object'
|
73
|
+
|
74
|
+
@state.transition('a', 'b')
|
75
|
+
@state.policy = State::STATE_POLICY_WAIT
|
76
|
+
@state.cardinality = 2
|
77
|
+
@state.priority = :high
|
78
|
+
cloned = @state.clone
|
79
|
+
assert_equal @state, cloned, 'Original and cloned states should be equal'
|
80
|
+
assert !@state.equal?(cloned), 'Original and cloned states should not be the same object'
|
81
|
+
end
|
82
|
+
|
83
|
+
def test_merge
|
84
|
+
other_state = State.new(cardinality: 2, priority: :high, policy: State::STATE_POLICY_WAIT)
|
85
|
+
merged = @state.merge(other_state)
|
86
|
+
assert_equal merged, other_state, 'Merged state should be equal to state B'
|
87
|
+
|
88
|
+
other_state.transition('a', 'b')
|
89
|
+
other_state.transition('b', 'c')
|
90
|
+
@state.transition('a', 'c')
|
91
|
+
@state.transition('c', 'd')
|
92
|
+
|
93
|
+
expected_state = other_state.clone
|
94
|
+
expected_state.transition('c', 'd')
|
95
|
+
merged = @state.merge(other_state)
|
96
|
+
assert_equal merged, expected_state, 'Merged state should have same properties as B plus additional transition C->D'
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# Configure Rails Environment
|
2
|
+
ENV['RAILS_ENV'] = 'test'
|
3
|
+
|
4
|
+
require 'simplecov'
|
5
|
+
require 'coveralls'
|
6
|
+
|
7
|
+
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
|
8
|
+
SimpleCov::Formatter::HTMLFormatter,
|
9
|
+
Coveralls::SimpleCov::Formatter
|
10
|
+
]
|
11
|
+
SimpleCov.start
|
12
|
+
|
13
|
+
require File.expand_path('../dummy/config/environment.rb', __FILE__)
|
14
|
+
require 'rails/test_help'
|
15
|
+
|
16
|
+
Rails.backtrace_cleaner.remove_silencers!
|
17
|
+
|
18
|
+
connection = Redis.new(
|
19
|
+
host: 'localhost',
|
20
|
+
db: 1,
|
21
|
+
port: 6379,
|
22
|
+
timeout: 30,
|
23
|
+
thread_safe: true
|
24
|
+
)
|
25
|
+
|
26
|
+
RedisRds.configure(
|
27
|
+
connection: connection,
|
28
|
+
namespace: 'testns'
|
29
|
+
)
|
30
|
+
|
31
|
+
# Load support files
|
32
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
|