dynflow 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -0
- data/.travis.yml +9 -0
- data/Gemfile +0 -10
- data/MIT-LICENSE +1 -1
- data/README.md +99 -37
- data/Rakefile +2 -6
- data/doc/images/logo.png +0 -0
- data/dynflow.gemspec +10 -1
- data/examples/generate_work_for_daemon.rb +24 -0
- data/examples/orchestrate.rb +121 -0
- data/examples/run_daemon.rb +17 -0
- data/examples/web_console.rb +29 -0
- data/lib/dynflow.rb +27 -6
- data/lib/dynflow/action.rb +185 -77
- data/lib/dynflow/action/cancellable_polling.rb +18 -0
- data/lib/dynflow/action/finalize_phase.rb +18 -0
- data/lib/dynflow/action/flow_phase.rb +44 -0
- data/lib/dynflow/action/format.rb +46 -0
- data/lib/dynflow/action/missing.rb +26 -0
- data/lib/dynflow/action/plan_phase.rb +85 -0
- data/lib/dynflow/action/polling.rb +49 -0
- data/lib/dynflow/action/presenter.rb +51 -0
- data/lib/dynflow/action/progress.rb +62 -0
- data/lib/dynflow/action/run_phase.rb +43 -0
- data/lib/dynflow/action/suspended.rb +21 -0
- data/lib/dynflow/clock.rb +133 -0
- data/lib/dynflow/daemon.rb +29 -0
- data/lib/dynflow/execution_plan.rb +285 -33
- data/lib/dynflow/execution_plan/dependency_graph.rb +29 -0
- data/lib/dynflow/execution_plan/output_reference.rb +52 -0
- data/lib/dynflow/execution_plan/steps.rb +12 -0
- data/lib/dynflow/execution_plan/steps/abstract.rb +121 -0
- data/lib/dynflow/execution_plan/steps/abstract_flow_step.rb +52 -0
- data/lib/dynflow/execution_plan/steps/error.rb +33 -0
- data/lib/dynflow/execution_plan/steps/finalize_step.rb +23 -0
- data/lib/dynflow/execution_plan/steps/plan_step.rb +81 -0
- data/lib/dynflow/execution_plan/steps/run_step.rb +21 -0
- data/lib/dynflow/executors.rb +9 -0
- data/lib/dynflow/executors/abstract.rb +32 -0
- data/lib/dynflow/executors/parallel.rb +88 -0
- data/lib/dynflow/executors/parallel/core.rb +119 -0
- data/lib/dynflow/executors/parallel/execution_plan_manager.rb +120 -0
- data/lib/dynflow/executors/parallel/flow_manager.rb +48 -0
- data/lib/dynflow/executors/parallel/pool.rb +102 -0
- data/lib/dynflow/executors/parallel/running_steps_manager.rb +63 -0
- data/lib/dynflow/executors/parallel/sequence_cursor.rb +97 -0
- data/lib/dynflow/executors/parallel/sequential_manager.rb +81 -0
- data/lib/dynflow/executors/parallel/work_queue.rb +44 -0
- data/lib/dynflow/executors/parallel/worker.rb +30 -0
- data/lib/dynflow/executors/remote_via_socket.rb +38 -0
- data/lib/dynflow/executors/remote_via_socket/core.rb +150 -0
- data/lib/dynflow/flows.rb +13 -0
- data/lib/dynflow/flows/abstract.rb +36 -0
- data/lib/dynflow/flows/abstract_composed.rb +104 -0
- data/lib/dynflow/flows/atom.rb +36 -0
- data/lib/dynflow/flows/concurrence.rb +28 -0
- data/lib/dynflow/flows/sequence.rb +13 -0
- data/lib/dynflow/future.rb +173 -0
- data/lib/dynflow/listeners.rb +7 -0
- data/lib/dynflow/listeners/abstract.rb +13 -0
- data/lib/dynflow/listeners/serialization.rb +41 -0
- data/lib/dynflow/listeners/socket.rb +88 -0
- data/lib/dynflow/logger_adapters.rb +8 -0
- data/lib/dynflow/logger_adapters/abstract.rb +30 -0
- data/lib/dynflow/logger_adapters/delegator.rb +13 -0
- data/lib/dynflow/logger_adapters/formatters.rb +8 -0
- data/lib/dynflow/logger_adapters/formatters/abstract.rb +33 -0
- data/lib/dynflow/logger_adapters/formatters/exception.rb +15 -0
- data/lib/dynflow/logger_adapters/simple.rb +59 -0
- data/lib/dynflow/micro_actor.rb +102 -0
- data/lib/dynflow/persistence.rb +53 -0
- data/lib/dynflow/persistence_adapters.rb +6 -0
- data/lib/dynflow/persistence_adapters/abstract.rb +56 -0
- data/lib/dynflow/persistence_adapters/sequel.rb +160 -0
- data/lib/dynflow/persistence_adapters/sequel_migrations/001_initial.rb +52 -0
- data/lib/dynflow/serializable.rb +66 -0
- data/lib/dynflow/simple_world.rb +18 -0
- data/lib/dynflow/stateful.rb +40 -0
- data/lib/dynflow/testing.rb +32 -0
- data/lib/dynflow/testing/assertions.rb +64 -0
- data/lib/dynflow/testing/dummy_execution_plan.rb +40 -0
- data/lib/dynflow/testing/dummy_executor.rb +29 -0
- data/lib/dynflow/testing/dummy_planned_action.rb +18 -0
- data/lib/dynflow/testing/dummy_step.rb +19 -0
- data/lib/dynflow/testing/dummy_world.rb +33 -0
- data/lib/dynflow/testing/factories.rb +83 -0
- data/lib/dynflow/testing/managed_clock.rb +23 -0
- data/lib/dynflow/testing/mimic.rb +38 -0
- data/lib/dynflow/transaction_adapters.rb +9 -0
- data/lib/dynflow/transaction_adapters/abstract.rb +26 -0
- data/lib/dynflow/transaction_adapters/active_record.rb +27 -0
- data/lib/dynflow/transaction_adapters/none.rb +12 -0
- data/lib/dynflow/version.rb +1 -1
- data/lib/dynflow/web_console.rb +277 -0
- data/lib/dynflow/world.rb +168 -0
- data/test/action_test.rb +89 -11
- data/test/clock_test.rb +59 -0
- data/test/code_workflow_example.rb +382 -0
- data/test/execution_plan_test.rb +195 -64
- data/test/executor_test.rb +692 -0
- data/test/persistance_adapters_test.rb +173 -0
- data/test/test_helper.rb +316 -1
- data/test/testing_test.rb +148 -0
- data/test/web_console_test.rb +38 -0
- data/web/assets/javascripts/application.js +25 -0
- data/web/assets/stylesheets/application.css +101 -0
- data/web/assets/vendor/bootstrap/css/bootstrap-responsive.css +1109 -0
- data/web/assets/vendor/bootstrap/css/bootstrap-responsive.min.css +9 -0
- data/web/assets/vendor/bootstrap/css/bootstrap.css +6167 -0
- data/web/assets/vendor/bootstrap/css/bootstrap.min.css +9 -0
- data/web/assets/vendor/bootstrap/img/glyphicons-halflings-white.png +0 -0
- data/web/assets/vendor/bootstrap/img/glyphicons-halflings.png +0 -0
- data/web/assets/vendor/bootstrap/js/bootstrap.js +2280 -0
- data/web/assets/vendor/bootstrap/js/bootstrap.min.js +6 -0
- data/web/assets/vendor/google-code-prettify/lang-basic.js +3 -0
- data/web/assets/vendor/google-code-prettify/prettify.css +1 -0
- data/web/assets/vendor/google-code-prettify/prettify.js +30 -0
- data/web/assets/vendor/google-code-prettify/run_prettify.js +34 -0
- data/web/assets/vendor/jquery/jquery.js +9807 -0
- data/web/views/flow.erb +19 -0
- data/web/views/flow_step.erb +31 -0
- data/web/views/index.erb +39 -0
- data/web/views/layout.erb +20 -0
- data/web/views/plan_step.erb +11 -0
- data/web/views/show.erb +54 -0
- metadata +250 -11
- data/examples/events.rb +0 -71
- data/examples/workflow.rb +0 -140
- data/lib/dynflow/bus.rb +0 -168
- data/lib/dynflow/dispatcher.rb +0 -36
- data/lib/dynflow/logger.rb +0 -34
- data/lib/dynflow/step.rb +0 -234
- data/test/bus_test.rb +0 -150
@@ -0,0 +1,160 @@
|
|
1
|
+
require 'sequel/no_core_ext' # to avoid sequel ~> 3.0 coliding with ActiveRecord
|
2
|
+
require 'multi_json'
|
3
|
+
|
4
|
+
module Dynflow
|
5
|
+
module PersistenceAdapters
|
6
|
+
|
7
|
+
Sequel.extension :migration
|
8
|
+
|
9
|
+
class Sequel < Abstract
|
10
|
+
include Algebrick::TypeCheck
|
11
|
+
|
12
|
+
attr_reader :db
|
13
|
+
|
14
|
+
def pagination?
|
15
|
+
true
|
16
|
+
end
|
17
|
+
|
18
|
+
def filtering_by
|
19
|
+
META_DATA.fetch :execution_plan
|
20
|
+
end
|
21
|
+
|
22
|
+
def ordering_by
|
23
|
+
META_DATA.fetch :execution_plan
|
24
|
+
end
|
25
|
+
|
26
|
+
META_DATA = { execution_plan: %w(state result started_at ended_at real_time execution_time),
|
27
|
+
action: [],
|
28
|
+
step: %w(state started_at ended_at real_time execution_time action_id) }
|
29
|
+
|
30
|
+
def initialize(db_path)
|
31
|
+
@db = initialize_db db_path
|
32
|
+
migrate_db
|
33
|
+
end
|
34
|
+
|
35
|
+
def find_execution_plans(options = {})
|
36
|
+
data_set = filter(order(paginate(table(:execution_plan), options), options), options)
|
37
|
+
|
38
|
+
data_set.map do |record|
|
39
|
+
HashWithIndifferentAccess.new(MultiJson.load(record[:data]))
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def load_execution_plan(execution_plan_id)
|
44
|
+
load :execution_plan, uuid: execution_plan_id
|
45
|
+
end
|
46
|
+
|
47
|
+
def save_execution_plan(execution_plan_id, value)
|
48
|
+
save :execution_plan, { uuid: execution_plan_id }, value
|
49
|
+
end
|
50
|
+
|
51
|
+
def load_step(execution_plan_id, step_id)
|
52
|
+
load :step, execution_plan_uuid: execution_plan_id, id: step_id
|
53
|
+
end
|
54
|
+
|
55
|
+
def save_step(execution_plan_id, step_id, value)
|
56
|
+
save :step, { execution_plan_uuid: execution_plan_id, id: step_id }, value
|
57
|
+
end
|
58
|
+
|
59
|
+
def load_action(execution_plan_id, action_id)
|
60
|
+
load :action, execution_plan_uuid: execution_plan_id, id: action_id
|
61
|
+
end
|
62
|
+
|
63
|
+
def save_action(execution_plan_id, action_id, value)
|
64
|
+
save :action, { execution_plan_uuid: execution_plan_id, id: action_id }, value
|
65
|
+
end
|
66
|
+
|
67
|
+
def to_hash
|
68
|
+
{ execution_plans: table(:execution_plan).all,
|
69
|
+
steps: table(:step).all,
|
70
|
+
actions: table(:action).all }
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
TABLES = { execution_plan: :dynflow_execution_plans,
|
76
|
+
action: :dynflow_actions,
|
77
|
+
step: :dynflow_steps }
|
78
|
+
|
79
|
+
def table(which)
|
80
|
+
db[TABLES.fetch(which)]
|
81
|
+
end
|
82
|
+
|
83
|
+
def initialize_db(db_path)
|
84
|
+
::Sequel.connect db_path
|
85
|
+
end
|
86
|
+
|
87
|
+
def self.migrations_path
|
88
|
+
File.expand_path('../sequel_migrations', __FILE__)
|
89
|
+
end
|
90
|
+
|
91
|
+
def migrate_db
|
92
|
+
::Sequel::Migrator.run(db, self.class.migrations_path, table: 'dynflow_schema_info')
|
93
|
+
end
|
94
|
+
|
95
|
+
def save(what, condition, value)
|
96
|
+
table = table(what)
|
97
|
+
existing_record = table.first condition
|
98
|
+
|
99
|
+
if value
|
100
|
+
value = value.with_indifferent_access
|
101
|
+
record = existing_record || condition
|
102
|
+
record[:data] = MultiJson.dump Type!(value, Hash)
|
103
|
+
meta_data = META_DATA.fetch(what).inject({}) { |h, k| h.update k.to_sym => value.fetch(k) }
|
104
|
+
record.merge! meta_data
|
105
|
+
record.each { |k, v| record[k] = v.to_s if v.is_a? Symbol }
|
106
|
+
|
107
|
+
if existing_record
|
108
|
+
table.where(condition).update(record)
|
109
|
+
else
|
110
|
+
table.insert record
|
111
|
+
end
|
112
|
+
|
113
|
+
else
|
114
|
+
existing_record and table.where(condition).delete
|
115
|
+
end
|
116
|
+
value
|
117
|
+
end
|
118
|
+
|
119
|
+
def load(what, condition)
|
120
|
+
table = table(what)
|
121
|
+
if (record = table.first(condition.symbolize_keys))
|
122
|
+
HashWithIndifferentAccess.new MultiJson.load(record[:data])
|
123
|
+
else
|
124
|
+
raise KeyError, "searching: #{what} by: #{condition.inspect}"
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def paginate(data_set, options)
|
129
|
+
page = Integer(options[:page] || 0)
|
130
|
+
per_page = Integer(options[:per_page] || 20)
|
131
|
+
|
132
|
+
if page
|
133
|
+
data_set.limit per_page, per_page * page
|
134
|
+
else
|
135
|
+
data_set
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def order(data_set, options)
|
140
|
+
order_by = (options[:order_by] || :started_at).to_s
|
141
|
+
unless META_DATA.fetch(:execution_plan).include? order_by
|
142
|
+
raise ArgumentError, "unknown column #{order_by.inspect}"
|
143
|
+
end
|
144
|
+
order_by = order_by.to_sym
|
145
|
+
data_set.order_by options[:desc] ? ::Sequel.desc(order_by) : order_by
|
146
|
+
end
|
147
|
+
|
148
|
+
def filter(data_set, options)
|
149
|
+
filters = Type! options[:filters], NilClass, Hash
|
150
|
+
return data_set if filters.nil?
|
151
|
+
|
152
|
+
unless (unknown = filters.keys - META_DATA.fetch(:execution_plan)).empty?
|
153
|
+
raise ArgumentError, "unkown columns: #{unknown.inspect}"
|
154
|
+
end
|
155
|
+
|
156
|
+
data_set.where filters.symbolize_keys
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
Sequel.migration do
|
2
|
+
up do
|
3
|
+
create_table(:dynflow_execution_plans) do
|
4
|
+
column :uuid, String, primary_key: true, size: 36, fixed: true
|
5
|
+
index :uuid, :unique => true
|
6
|
+
|
7
|
+
column :data, String, text: true
|
8
|
+
|
9
|
+
column :state, String
|
10
|
+
column :result, String
|
11
|
+
column :started_at, Time
|
12
|
+
column :ended_at, Time
|
13
|
+
column :real_time, Float
|
14
|
+
column :execution_time, Float
|
15
|
+
end
|
16
|
+
|
17
|
+
create_table(:dynflow_actions) do
|
18
|
+
foreign_key :execution_plan_uuid, :dynflow_execution_plans, type: String, size: 36, fixed: true
|
19
|
+
index :execution_plan_uuid
|
20
|
+
column :id, Fixnum
|
21
|
+
primary_key [:execution_plan_uuid, :id]
|
22
|
+
index [:execution_plan_uuid, :id], :unique => true
|
23
|
+
|
24
|
+
column :data, String, text: true
|
25
|
+
end
|
26
|
+
|
27
|
+
create_table(:dynflow_steps) do
|
28
|
+
foreign_key :execution_plan_uuid, :dynflow_execution_plans, type: String, size: 36, fixed: true
|
29
|
+
index :execution_plan_uuid
|
30
|
+
column :id, Fixnum
|
31
|
+
primary_key [:execution_plan_uuid, :id]
|
32
|
+
index [:execution_plan_uuid, :id], :unique => true
|
33
|
+
column :action_id, Fixnum
|
34
|
+
foreign_key [:execution_plan_uuid, :action_id], :dynflow_actions
|
35
|
+
index [:execution_plan_uuid, :action_id]
|
36
|
+
|
37
|
+
column :data, String, text: true
|
38
|
+
|
39
|
+
column :state, String
|
40
|
+
column :started_at, Time
|
41
|
+
column :ended_at, Time
|
42
|
+
column :real_time, Float
|
43
|
+
column :execution_time, Float
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
down do
|
48
|
+
drop_table(:dynflow_steps)
|
49
|
+
drop_table(:dynflow_actions)
|
50
|
+
drop_table(:dynflow_execution_plans)
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module Dynflow
|
2
|
+
class Serializable
|
3
|
+
def self.from_hash(hash, *args)
|
4
|
+
check_class_key_present hash
|
5
|
+
hash[:class].constantize.new_from_hash(hash, *args)
|
6
|
+
end
|
7
|
+
|
8
|
+
def to_hash
|
9
|
+
raise NotImplementedError
|
10
|
+
end
|
11
|
+
|
12
|
+
# @api private
|
13
|
+
def self.new_from_hash(hash, *args)
|
14
|
+
raise NotImplementedError
|
15
|
+
# new ...
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.check_class_matching(hash)
|
19
|
+
check_class_key_present hash
|
20
|
+
unless self.to_s == hash[:class]
|
21
|
+
raise ArgumentError, "class mismatch #{hash[:class]} != #{self}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.check_class_key_present(hash)
|
26
|
+
raise ArgumentError, 'missing :class' unless hash[:class]
|
27
|
+
end
|
28
|
+
|
29
|
+
private_class_method :check_class_matching, :check_class_key_present
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def recursive_to_hash(value)
|
34
|
+
case value
|
35
|
+
when Numeric, String, Symbol, TrueClass, FalseClass, NilClass
|
36
|
+
value
|
37
|
+
when Array
|
38
|
+
value.map { |v| recursive_to_hash v }
|
39
|
+
when Hash
|
40
|
+
value.inject({}) { |h, (k, v)| h.update k => recursive_to_hash(v) }
|
41
|
+
else
|
42
|
+
value.to_hash
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.string_to_time(string)
|
47
|
+
return if string.nil?
|
48
|
+
_, year, month, day, hour, min, sec = */(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/.match(string)
|
49
|
+
Time.new(year.to_i, month.to_i, day.to_i, hour.to_i, min.to_i, sec.to_i)
|
50
|
+
end
|
51
|
+
|
52
|
+
def time_to_str(time)
|
53
|
+
return if time.nil?
|
54
|
+
Type! time, Time
|
55
|
+
time.strftime '%Y-%m-%d %H:%M:%S'
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.hash_to_error(hash)
|
59
|
+
return nil if hash.nil?
|
60
|
+
ExecutionPlan::Steps::Error.from_hash(hash)
|
61
|
+
end
|
62
|
+
|
63
|
+
private_class_method :string_to_time, :hash_to_error
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Dynflow
|
2
|
+
class SimpleWorld < World
|
3
|
+
def initialize(options_hash = {})
|
4
|
+
super options_hash
|
5
|
+
at_exit { self.terminate.wait } if options[:auto_terminate]
|
6
|
+
# we can check consistency here because SimpleWorld doesn't expect
|
7
|
+
# remote executor being in place.
|
8
|
+
self.consistency_check
|
9
|
+
end
|
10
|
+
|
11
|
+
def default_options
|
12
|
+
super.merge(pool_size: 5,
|
13
|
+
persistence_adapter: PersistenceAdapters::Sequel.new('sqlite:/'),
|
14
|
+
transaction_adapter: TransactionAdapters::None.new,
|
15
|
+
auto_terminate: true)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Dynflow
|
2
|
+
module Stateful
|
3
|
+
def self.included(base)
|
4
|
+
base.extend ClassMethods
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
def states
|
9
|
+
raise NotImplementedError
|
10
|
+
end
|
11
|
+
|
12
|
+
def state_transitions
|
13
|
+
raise NotImplementedError
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def states
|
18
|
+
self.class.states
|
19
|
+
end
|
20
|
+
|
21
|
+
def state_transitions
|
22
|
+
self.class.state_transitions
|
23
|
+
end
|
24
|
+
|
25
|
+
attr_reader :state
|
26
|
+
|
27
|
+
def state=(state)
|
28
|
+
set_state state, false
|
29
|
+
end
|
30
|
+
|
31
|
+
def set_state(state, skip_transition_check)
|
32
|
+
state = state.to_sym if state.is_a?(String) && states.map(&:to_s).include?(state)
|
33
|
+
raise "unknown state #{state}" unless states.include? state
|
34
|
+
unless self.state.nil? || skip_transition_check || state_transitions.fetch(self.state).include?(state)
|
35
|
+
raise "invalid state transition #{self.state} >> #{state} in #{self}"
|
36
|
+
end
|
37
|
+
@state = state
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Dynflow
|
2
|
+
module Testing
|
3
|
+
extend Algebrick::TypeCheck
|
4
|
+
|
5
|
+
def self.logger_adapter
|
6
|
+
@logger_adapter ||= LoggerAdapters::Simple.new $stdout, 0
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.logger_adapter=(adapter)
|
10
|
+
Type! adapter, LoggerAdapters::Abstract
|
11
|
+
@logger_adapter = adapter
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.get_id
|
15
|
+
@last_id ||= 0
|
16
|
+
@last_id += 1
|
17
|
+
end
|
18
|
+
|
19
|
+
require 'dynflow/testing/mimic'
|
20
|
+
require 'dynflow/testing/managed_clock'
|
21
|
+
require 'dynflow/testing/dummy_world'
|
22
|
+
require 'dynflow/testing/dummy_executor'
|
23
|
+
require 'dynflow/testing/dummy_execution_plan'
|
24
|
+
require 'dynflow/testing/dummy_step'
|
25
|
+
require 'dynflow/testing/dummy_planned_action'
|
26
|
+
require 'dynflow/testing/assertions'
|
27
|
+
require 'dynflow/testing/factories'
|
28
|
+
|
29
|
+
include Assertions
|
30
|
+
include Factories
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Dynflow
|
2
|
+
module Testing
|
3
|
+
module Assertions
|
4
|
+
# assert that +assert_actioned_plan+ was planned by +action+ with arguments +plan_input+
|
5
|
+
# alternatively plan-input can be asserted with +block+
|
6
|
+
def assert_action_planed_with(action, planned_action_class, *plan_input, &block)
|
7
|
+
found_classes = assert_action_planed(action, planned_action_class)
|
8
|
+
found = found_classes.select do |a|
|
9
|
+
if plan_input.empty?
|
10
|
+
block.call a.plan_input
|
11
|
+
else
|
12
|
+
a.plan_input == plan_input
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
assert(!found.empty?,
|
17
|
+
"Action #{planned_action_class} with plan_input #{plan_input} was not planned, there were only #{found_classes.map(&:plan_input)}")
|
18
|
+
found
|
19
|
+
end
|
20
|
+
|
21
|
+
# assert that +assert_actioned_plan+ was planned by +action+
|
22
|
+
def assert_action_planed(action, planned_action_class)
|
23
|
+
Type! action, Dynflow::Action::PlanPhase
|
24
|
+
Match! action.state, :success
|
25
|
+
found = action.execution_plan.planned_plan_steps.
|
26
|
+
select { |a| a.is_a?(planned_action_class) }
|
27
|
+
|
28
|
+
assert(!found.empty?, "Action #{planned_action_class} was not planned")
|
29
|
+
found
|
30
|
+
end
|
31
|
+
|
32
|
+
# assert that +action+ has run-phase planned
|
33
|
+
def assert_run_phase(action, input = nil, &block)
|
34
|
+
Type! action, Dynflow::Action::PlanPhase
|
35
|
+
Match! action.state, :success
|
36
|
+
action.execution_plan.planned_run_steps.must_include action
|
37
|
+
action.input.must_equal input.stringify_keys if input
|
38
|
+
block.call input if block
|
39
|
+
end
|
40
|
+
|
41
|
+
# refute that +action+ has run-phase planned
|
42
|
+
def refute_run_phase(action)
|
43
|
+
Type! action, Dynflow::Action::PlanPhase
|
44
|
+
Match! action.state, :success
|
45
|
+
action.execution_plan.planned_run_steps.wont_include action
|
46
|
+
end
|
47
|
+
|
48
|
+
# assert that +action+ has finalize-phase planned
|
49
|
+
def assert_finalize_phase(action)
|
50
|
+
Type! action, Dynflow::Action::PlanPhase
|
51
|
+
Match! action.state, :success
|
52
|
+
action.execution_plan.planned_finalize_steps.must_include action
|
53
|
+
end
|
54
|
+
|
55
|
+
# refute that +action+ has finalize-phase planned
|
56
|
+
def refute_finalize_phase(action)
|
57
|
+
Type! action, Dynflow::Action::PlanPhase
|
58
|
+
Match! action.state, :success
|
59
|
+
action.execution_plan.planned_finalize_steps.wont_include action
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Dynflow
|
2
|
+
module Testing
|
3
|
+
class DummyExecutionPlan
|
4
|
+
extend Mimic
|
5
|
+
mimic! ExecutionPlan
|
6
|
+
|
7
|
+
attr_reader :id, :planned_plan_steps, :planned_run_steps, :planned_finalize_steps
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@id = Testing.get_id
|
11
|
+
@planned_plan_steps = []
|
12
|
+
@planned_run_steps = []
|
13
|
+
@planned_finalize_steps = []
|
14
|
+
end
|
15
|
+
|
16
|
+
def world
|
17
|
+
@world ||= DummyWorld.new
|
18
|
+
end
|
19
|
+
|
20
|
+
def add_run_step(action)
|
21
|
+
@planned_run_steps << action
|
22
|
+
action
|
23
|
+
end
|
24
|
+
|
25
|
+
def add_finalize_step(action)
|
26
|
+
@planned_finalize_steps << action
|
27
|
+
action
|
28
|
+
end
|
29
|
+
|
30
|
+
def add_plan_step(klass, action)
|
31
|
+
@planned_plan_steps << action = DummyPlannedAction.new(klass)
|
32
|
+
action
|
33
|
+
end
|
34
|
+
|
35
|
+
def switch_flow(*args, &block)
|
36
|
+
block.call
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|