trailblazer-activity 0.15.1 → 0.16.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGES.md +58 -0
- data/Gemfile +3 -2
- data/lib/trailblazer/activity/adds.rb +122 -119
- data/lib/trailblazer/activity/circuit/task_adapter.rb +3 -3
- data/lib/trailblazer/activity/circuit.rb +6 -2
- data/lib/trailblazer/activity/deprecate.rb +4 -5
- data/lib/trailblazer/activity/introspect/render.rb +53 -0
- data/lib/trailblazer/activity/introspect.rb +27 -113
- data/lib/trailblazer/activity/schema/compiler.rb +106 -0
- data/lib/trailblazer/activity/schema/implementation.rb +2 -1
- data/lib/trailblazer/activity/schema/intermediate.rb +6 -86
- data/lib/trailblazer/activity/schema.rb +27 -4
- data/lib/trailblazer/activity/structures.rb +9 -7
- data/lib/trailblazer/activity/task_wrap/call_task.rb +4 -1
- data/lib/trailblazer/activity/task_wrap/extension.rb +12 -6
- data/lib/trailblazer/activity/task_wrap/pipeline.rb +1 -1
- data/lib/trailblazer/activity/task_wrap/runner.rb +8 -7
- data/lib/trailblazer/activity/task_wrap.rb +7 -5
- data/lib/trailblazer/activity/testing.rb +47 -126
- data/lib/trailblazer/activity/version.rb +1 -1
- data/lib/trailblazer/activity.rb +6 -11
- data/trailblazer-activity.gemspec +0 -1
- metadata +4 -17
- data/lib/trailblazer/activity/config.rb +0 -35
@@ -0,0 +1,106 @@
|
|
1
|
+
module Trailblazer
|
2
|
+
class Activity
|
3
|
+
class Schema
|
4
|
+
class Intermediate
|
5
|
+
# @private This class might get removed in 0.17.0.
|
6
|
+
module Compiler
|
7
|
+
module_function
|
8
|
+
|
9
|
+
DEFAULT_CONFIG = {wrap_static: Hash.new(Activity::TaskWrap.initial_wrap_static.freeze)} # DISCUSS: this really doesn't have to be here, but works for now and we want it in 99%.
|
10
|
+
|
11
|
+
# Compiles a {Schema} instance from an {intermediate} structure and
|
12
|
+
# the {implementation} object references.
|
13
|
+
#
|
14
|
+
# Intermediate structure, Implementation, calls extensions, passes {config} # TODO
|
15
|
+
def call(intermediate, implementation, config_merge: {})
|
16
|
+
config = DEFAULT_CONFIG
|
17
|
+
.merge(config_merge)
|
18
|
+
.freeze
|
19
|
+
|
20
|
+
circuit, outputs, nodes, config = schema_components(intermediate, implementation, config)
|
21
|
+
|
22
|
+
Schema.new(circuit, outputs, nodes, config)
|
23
|
+
end
|
24
|
+
|
25
|
+
# From the intermediate "template" and the actual implementation, compile a {Circuit} instance.
|
26
|
+
def schema_components(intermediate, implementation, config)
|
27
|
+
wiring = {}
|
28
|
+
nodes_attributes = []
|
29
|
+
|
30
|
+
intermediate.wiring.each do |task_ref, outs|
|
31
|
+
id = task_ref.id
|
32
|
+
impl_task = implementation.fetch(id)
|
33
|
+
task = impl_task.circuit_task
|
34
|
+
outputs = impl_task.outputs
|
35
|
+
|
36
|
+
wiring[task] = connections_for(outs, outputs, implementation)
|
37
|
+
|
38
|
+
nodes_attributes << [
|
39
|
+
id, # id
|
40
|
+
task, # task
|
41
|
+
task_ref[:data], # TODO: allow adding data from implementation.
|
42
|
+
outputs # {Activity::Output}s
|
43
|
+
]
|
44
|
+
|
45
|
+
# Invoke each task's extensions (usually coming from the DSL user or some macro).
|
46
|
+
# We're expecting each {ext} to be a {TaskWrap::Extension::WrapStatic} instance.
|
47
|
+
config = invoke_extensions_for(config, impl_task, id)
|
48
|
+
end
|
49
|
+
|
50
|
+
start_id = intermediate.start_task_id
|
51
|
+
|
52
|
+
terminus_to_output = terminus_to_output(intermediate, implementation)
|
53
|
+
activity_outputs = terminus_to_output.values
|
54
|
+
|
55
|
+
circuit = Circuit.new(
|
56
|
+
wiring,
|
57
|
+
terminus_to_output.keys, # termini
|
58
|
+
start_task: implementation.fetch(start_id).circuit_task
|
59
|
+
)
|
60
|
+
|
61
|
+
return circuit,
|
62
|
+
activity_outputs,
|
63
|
+
Schema::Nodes(nodes_attributes),
|
64
|
+
config
|
65
|
+
end
|
66
|
+
|
67
|
+
# Compute the connections for {circuit_task}.
|
68
|
+
# For a terminus, this produces an empty hash.
|
69
|
+
def connections_for(intermediate_outs, task_outputs, implementation)
|
70
|
+
intermediate_outs.collect { |intermediate_out| # Intermediate::Out, it's abstract.
|
71
|
+
[
|
72
|
+
output_for_semantic(task_outputs, intermediate_out.semantic).signal,
|
73
|
+
implementation.fetch(intermediate_out.target).circuit_task # Find the implementation task, the Ruby code component for runtime.
|
74
|
+
]
|
75
|
+
}.to_h
|
76
|
+
end
|
77
|
+
|
78
|
+
def terminus_to_output(intermediate, implementation)
|
79
|
+
terminus_to_semantic = intermediate.stop_task_ids
|
80
|
+
|
81
|
+
terminus_to_semantic.collect do |id, semantic|
|
82
|
+
terminus_task = implementation.fetch(id).circuit_task
|
83
|
+
|
84
|
+
[
|
85
|
+
terminus_task,
|
86
|
+
Activity::Output(terminus_task, semantic) # The End instance is the signal.
|
87
|
+
]
|
88
|
+
end.to_h
|
89
|
+
end
|
90
|
+
|
91
|
+
# Run all Implementation::Task.extensions and return new {config}
|
92
|
+
def invoke_extensions_for(config, impl_task, id)
|
93
|
+
impl_task
|
94
|
+
.extensions
|
95
|
+
.inject(config) { |cfg, ext| ext.(config: cfg, id: id, task: impl_task) } # {ext} must return new config hash.
|
96
|
+
end
|
97
|
+
|
98
|
+
# In an array of {Activity::Output}, find the output matching {semantic}.
|
99
|
+
def output_for_semantic(outputs, semantic)
|
100
|
+
outputs.find { |out| out.semantic == semantic } or raise "`#{semantic}` not found in implementation"
|
101
|
+
end
|
102
|
+
end # Intermediate
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -1,10 +1,11 @@
|
|
1
1
|
class Trailblazer::Activity
|
2
2
|
class Schema
|
3
|
+
# @private This class might get removed in 0.17.0.
|
3
4
|
module Implementation
|
4
5
|
# Implementation structures
|
5
6
|
Task = Struct.new(:circuit_task, :outputs, :extensions)
|
6
7
|
|
7
|
-
def self.Task(task, outputs, extensions=[])
|
8
|
+
def self.Task(task, outputs, extensions = [])
|
8
9
|
Task.new(task, outputs, extensions)
|
9
10
|
end
|
10
11
|
end
|
@@ -2,97 +2,17 @@ class Trailblazer::Activity
|
|
2
2
|
class Schema
|
3
3
|
# An {Intermediate} structure defines the *structure* of the circuit. It usually
|
4
4
|
# comes from a DSL or a visual editor.
|
5
|
-
class
|
5
|
+
# @private This class might get removed in 0.17.0.
|
6
|
+
class Intermediate < Struct.new(:wiring, :stop_task_ids, :start_task_id)
|
6
7
|
TaskRef = Struct.new(:id, :data) # TODO: rename to NodeRef
|
7
8
|
Out = Struct.new(:semantic, :target)
|
8
9
|
|
9
|
-
def self.TaskRef(id, data={})
|
10
|
-
|
11
|
-
|
12
|
-
# Compiles a {Schema} instance from an {intermediate} structure and
|
13
|
-
# the {implementation} object references.
|
14
|
-
#
|
15
|
-
# Intermediate structure, Implementation, calls extensions, passes {config} # TODO
|
16
|
-
def self.call(intermediate, implementation, config_merge: {})
|
17
|
-
config_default = {wrap_static: Hash.new(TaskWrap.initial_wrap_static)} # DISCUSS: this really doesn't have to be here, but works for now and we want it in 99%.
|
18
|
-
config = config_default.merge(config_merge)
|
19
|
-
config.freeze
|
20
|
-
|
21
|
-
circuit = circuit(intermediate, implementation)
|
22
|
-
nodes = node_attributes(intermediate, implementation)
|
23
|
-
outputs = outputs(intermediate.stop_task_ids, nodes)
|
24
|
-
config = config(implementation, config: config)
|
25
|
-
|
26
|
-
Schema.new(circuit, outputs, nodes, config)
|
27
|
-
end
|
28
|
-
|
29
|
-
# From the intermediate "template" and the actual implementation, compile a {Circuit} instance.
|
30
|
-
def self.circuit(intermediate, implementation)
|
31
|
-
end_events = intermediate.stop_task_ids
|
32
|
-
|
33
|
-
wiring = Hash[
|
34
|
-
intermediate.wiring.collect do |task_ref, outs|
|
35
|
-
task = implementation.fetch(task_ref.id)
|
36
|
-
|
37
|
-
[
|
38
|
-
task.circuit_task,
|
39
|
-
end_events.include?(task_ref.id) ? {} : connections_for(outs, task.outputs, implementation)
|
40
|
-
]
|
41
|
-
end
|
42
|
-
]
|
43
|
-
|
44
|
-
Circuit.new(
|
45
|
-
wiring,
|
46
|
-
intermediate.stop_task_ids.collect { |id| implementation.fetch(id).circuit_task },
|
47
|
-
start_task: intermediate.start_task_ids.collect { |id| implementation.fetch(id).circuit_task }[0]
|
48
|
-
)
|
49
|
-
end
|
50
|
-
|
51
|
-
# Compute the connections for {circuit_task}.
|
52
|
-
def self.connections_for(outs, task_outputs, implementation)
|
53
|
-
Hash[
|
54
|
-
outs.collect { |required_out| # Intermediate::Out, it's abstract.
|
55
|
-
[
|
56
|
-
for_semantic(task_outputs, required_out.semantic).signal,
|
57
|
-
implementation.fetch(required_out.target).circuit_task
|
58
|
-
]
|
59
|
-
}.compact
|
60
|
-
]
|
10
|
+
def self.TaskRef(id, data = {})
|
11
|
+
TaskRef.new(id, data)
|
61
12
|
end
|
62
13
|
|
63
|
-
def self.
|
64
|
-
|
65
|
-
id = task_ref.id
|
66
|
-
implementation_task = implementation.fetch(id)
|
67
|
-
data = task_ref[:data] # TODO: allow adding data from implementation.
|
68
|
-
|
69
|
-
NodeAttributes.new(id, implementation_task.outputs, implementation_task.circuit_task, data)
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
# intermediate/implementation independent.
|
74
|
-
def self.outputs(stop_task_ids, nodes_attributes)
|
75
|
-
stop_task_ids.collect do |id|
|
76
|
-
# Grab the {outputs} of the stop nodes.
|
77
|
-
nodes_attributes.find { |node_attrs| id == node_attrs.id }.outputs
|
78
|
-
end.flatten(1)
|
79
|
-
end
|
80
|
-
|
81
|
-
# Invoke each task's extensions (usually coming from the DSL user or some macro).
|
82
|
-
# We're expecting each {ext} to be a {TaskWrap::Extension::WrapStatic} instance.
|
83
|
-
def self.config(implementation, config:)
|
84
|
-
implementation.each do |id, task|
|
85
|
-
task.extensions.each { |ext| config = ext.(config: config, id: id, task: task) } # DISCUSS: ext must return new {Config}.
|
86
|
-
end
|
87
|
-
|
88
|
-
config
|
89
|
-
end
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
# Apply to any array.
|
94
|
-
private_class_method def self.for_semantic(outputs, semantic)
|
95
|
-
outputs.find { |out| out.semantic == semantic } or raise "`#{semantic}` not found"
|
14
|
+
def self.Out(*args)
|
15
|
+
Out.new(*args)
|
96
16
|
end
|
97
17
|
end # Intermediate
|
98
18
|
end
|
@@ -1,15 +1,38 @@
|
|
1
1
|
module Trailblazer
|
2
2
|
class Activity
|
3
|
-
#
|
4
|
-
#
|
5
|
-
NodeAttributes = Struct.new(:id, :outputs, :task, :data) # TODO: rename to Schema::Task::Attributes.
|
6
|
-
|
3
|
+
# The idea with {:config} is to have a generic runtime store for feature fields
|
4
|
+
# like {:wrap_static} but also for flags, e.g. `each: true` from the Each() macro.
|
7
5
|
class Schema < Struct.new(:circuit, :outputs, :nodes, :config)
|
8
6
|
# {:nodes} is passed directly from {compile_activity}. We need to store this data here.
|
9
7
|
|
10
8
|
# @!method to_h()
|
11
9
|
# Returns a hash containing the schema's components.
|
12
10
|
|
11
|
+
class Nodes < Hash
|
12
|
+
# In Attributes we store data from Intermediate and Implementing compile-time.
|
13
|
+
# This would be lost otherwise.
|
14
|
+
Attributes = Struct.new(:id, :task, :data, :outputs)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Builder for {Schema::Nodes} datastructure.
|
18
|
+
#
|
19
|
+
# A {Nodes} instance is a hash of Attributes, keyed by task. It turned out that
|
20
|
+
# 90% of introspect lookups, we search for attributes for a particular *task*, not ID.
|
21
|
+
# That's why in 0.16.0 we changed this structure.5
|
22
|
+
#
|
23
|
+
# Nodes{#<task> => #<Nodes::Attributes id= task= data= outputs=>}
|
24
|
+
#
|
25
|
+
# @private Please use {Introspect.Nodes} for querying nodes.
|
26
|
+
def self.Nodes(nodes)
|
27
|
+
Nodes[
|
28
|
+
nodes.collect do |attrs|
|
29
|
+
[
|
30
|
+
attrs[1], # task
|
31
|
+
Nodes::Attributes.new(*attrs).freeze
|
32
|
+
]
|
33
|
+
end
|
34
|
+
].freeze
|
35
|
+
end
|
13
36
|
end # Schema
|
14
37
|
end
|
15
38
|
end
|
@@ -12,8 +12,8 @@ module Trailblazer
|
|
12
12
|
@options = options.merge(semantic: semantic)
|
13
13
|
end
|
14
14
|
|
15
|
-
def call(args, **
|
16
|
-
return self, args
|
15
|
+
def call(args, **)
|
16
|
+
return self, args
|
17
17
|
end
|
18
18
|
|
19
19
|
def to_h
|
@@ -21,21 +21,23 @@ module Trailblazer
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def to_s
|
24
|
-
%
|
24
|
+
%(#<#{self.class.name} #{@options.collect { |k, v| "#{k}=#{v.inspect}" }.join(" ")}>)
|
25
25
|
end
|
26
26
|
|
27
27
|
alias inspect to_s
|
28
28
|
end
|
29
29
|
|
30
30
|
class Start < End
|
31
|
-
def call(args, **
|
32
|
-
return Activity::Right, args
|
31
|
+
def call(args, **)
|
32
|
+
return Activity::Right, args
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
36
|
-
class Signal;
|
36
|
+
class Signal; end
|
37
|
+
|
37
38
|
class Right < Signal; end
|
38
|
-
|
39
|
+
|
40
|
+
class Left < Signal; end
|
39
41
|
|
40
42
|
# signal: actual signal emitted by the task
|
41
43
|
# color: the mapping, where this signal will travel to. This can be e.g. Left=>:success. The polarization when building the graph.
|
@@ -13,7 +13,10 @@ class Trailblazer::Activity
|
|
13
13
|
return_signal, return_args = task.(original_arguments, **original_circuit_options)
|
14
14
|
|
15
15
|
# DISCUSS: do we want original_args here to be passed on, or the "effective" return_args which are different to original_args now?
|
16
|
-
wrap_ctx = wrap_ctx.merge(
|
16
|
+
wrap_ctx = wrap_ctx.merge(
|
17
|
+
return_signal: return_signal,
|
18
|
+
return_args: return_args
|
19
|
+
)
|
17
20
|
|
18
21
|
return wrap_ctx, original_args
|
19
22
|
end
|
@@ -9,7 +9,7 @@ module Trailblazer
|
|
9
9
|
# |-- Call (call actual task) id: "task_wrap.call_task"
|
10
10
|
# |-- Trace.capture_return [optional]
|
11
11
|
# |-- Wrap::End
|
12
|
-
module TaskWrap
|
12
|
+
module TaskWrap
|
13
13
|
# inserts must be
|
14
14
|
# An {Extension} can be used for {:wrap_runtime}. It expects a collection of
|
15
15
|
# "friendly interface" arrays.
|
@@ -86,14 +86,20 @@ Please update to the new TaskWrap.Extension() API."
|
|
86
86
|
end
|
87
87
|
|
88
88
|
def call(config:, task:, **)
|
89
|
-
#
|
90
|
-
|
91
|
-
before_pipe = Config.get(config, :wrap_static, task.circuit_task)
|
89
|
+
# DISCUSS: does it make sense to test the immutable behavior here? What would be the worst outcome?
|
90
|
+
task = task.circuit_task # Receives {Schema::Implementation::Task}. DISCUSS: why?
|
92
91
|
|
93
|
-
|
92
|
+
# Add the extension's task(s) to the activity's {:wrap_static} taskWrap
|
93
|
+
# which is stored in the {:config} field.
|
94
|
+
wrap_static = config[:wrap_static] # the activity's {wrap_static}.
|
95
|
+
task_wrap = wrap_static[task] # the "original" taskWrap for {task}.
|
96
|
+
|
97
|
+
# Overwrite the original task_wrap:
|
98
|
+
wrap_static = wrap_static.merge(task => @extension.(task_wrap))
|
99
|
+
|
100
|
+
config.merge(wrap_static: wrap_static) # Return new config hash. This needs to be immutable code!
|
94
101
|
end
|
95
102
|
end # WrapStatic
|
96
|
-
|
97
103
|
end # Extension
|
98
104
|
end # TaskWrap
|
99
105
|
end
|
@@ -29,7 +29,7 @@ class Trailblazer::Activity
|
|
29
29
|
insert_before: :Prepend,
|
30
30
|
insert_after: :Append,
|
31
31
|
append: :Append,
|
32
|
-
prepend: :Prepend
|
32
|
+
prepend: :Prepend
|
33
33
|
}.fetch(name)
|
34
34
|
|
35
35
|
warn "[Trailblazer] Using `Trailblazer::Activity::TaskWrap::Pipeline.method(:#{name})` is deprecated.
|
@@ -10,7 +10,7 @@ class Trailblazer::Activity
|
|
10
10
|
# @api private
|
11
11
|
# @interface Runner
|
12
12
|
def self.call(task, args, **circuit_options)
|
13
|
-
wrap_ctx = {
|
13
|
+
wrap_ctx = {task: task}
|
14
14
|
|
15
15
|
# this pipeline is "wrapped around" the actual `task`.
|
16
16
|
task_wrap_pipeline = merge_static_with_runtime(task, **circuit_options) || raise
|
@@ -28,14 +28,13 @@ class Trailblazer::Activity
|
|
28
28
|
return wrap_ctx[:return_signal], wrap_ctx[:return_args]
|
29
29
|
end
|
30
30
|
|
31
|
-
|
32
31
|
# Compute the task's wrap by applying alterations both static and from runtime.
|
33
32
|
#
|
34
33
|
# NOTE: this is for performance reasons: we could have only one hash containing everything but that'd mean
|
35
34
|
# unnecessary computations at `call`-time since steps might not even be executed.
|
36
35
|
# TODO: make this faster.
|
37
|
-
private_class_method def self.merge_static_with_runtime(task, wrap_runtime:, **circuit_options)
|
38
|
-
static_wrap = TaskWrap.wrap_static_for(task,
|
36
|
+
private_class_method def self.merge_static_with_runtime(task, wrap_runtime:, activity:, **circuit_options)
|
37
|
+
static_wrap = TaskWrap.wrap_static_for(task, activity) # find static wrap for this specific task [, or default wrap activity].
|
39
38
|
|
40
39
|
# Apply runtime alterations.
|
41
40
|
# Grab the additional wirings for the particular `task` from `wrap_runtime`.
|
@@ -44,9 +43,11 @@ class Trailblazer::Activity
|
|
44
43
|
end # Runner
|
45
44
|
|
46
45
|
# Retrieve the static wrap config from {activity}.
|
47
|
-
|
48
|
-
|
49
|
-
|
46
|
+
# @private
|
47
|
+
def self.wrap_static_for(task, activity)
|
48
|
+
activity.to_h
|
49
|
+
.fetch(:config)
|
50
|
+
.fetch(:wrap_static)[task] # the {wrap_static} for {task}.
|
50
51
|
end
|
51
52
|
end
|
52
53
|
end
|
@@ -21,7 +21,7 @@ module Trailblazer
|
|
21
21
|
circuit_options = circuit_options.merge(
|
22
22
|
runner: TaskWrap::Runner,
|
23
23
|
wrap_runtime: wrap_runtime,
|
24
|
-
activity: container_activity
|
24
|
+
activity: container_activity # for Runner. Ideally we'd have a list of all static_wraps here (even nested).
|
25
25
|
)
|
26
26
|
|
27
27
|
# signal, (ctx, flow), circuit_options =
|
@@ -43,10 +43,13 @@ module Trailblazer
|
|
43
43
|
#
|
44
44
|
# DISCUSS: we could cache that on Strategy/Operation level.
|
45
45
|
# merging the **config hash is 1/4 slower than before.
|
46
|
-
def container_activity_for(activity, wrap_static: initial_wrap_static, **config)
|
46
|
+
def container_activity_for(activity, wrap_static: initial_wrap_static, id: nil, **config)
|
47
47
|
{
|
48
|
-
|
49
|
-
|
48
|
+
config: {
|
49
|
+
wrap_static: {activity => wrap_static},
|
50
|
+
**config
|
51
|
+
},
|
52
|
+
nodes: Schema.Nodes([[id, activity]])
|
50
53
|
}
|
51
54
|
end
|
52
55
|
|
@@ -54,4 +57,3 @@ module Trailblazer
|
|
54
57
|
end # TaskWrap
|
55
58
|
end
|
56
59
|
end
|
57
|
-
|
@@ -10,8 +10,9 @@ module Trailblazer
|
|
10
10
|
def self.def_steps(*names)
|
11
11
|
Module.new do
|
12
12
|
module_function
|
13
|
+
|
13
14
|
names.each do |name|
|
14
|
-
define_method(name) do |
|
15
|
+
define_method(name) do |ctx, **|
|
15
16
|
ctx[:seq] << name
|
16
17
|
ctx.key?(name) ? ctx[name] : true
|
17
18
|
end
|
@@ -24,21 +25,15 @@ module Trailblazer
|
|
24
25
|
# @example
|
25
26
|
# task task: T.def_task(:create)
|
26
27
|
def self.def_task(name)
|
27
|
-
|
28
|
-
define_singleton_method(name) do | (ctx, flow_options), ** |
|
29
|
-
ctx[:seq] << name
|
30
|
-
signal = ctx.key?(name) ? ctx[name] : Activity::Right
|
31
|
-
|
32
|
-
return signal, [ctx, flow_options]
|
33
|
-
end
|
34
|
-
end.method(name)
|
28
|
+
def_tasks(name).method(name)
|
35
29
|
end
|
36
30
|
|
37
31
|
def self.def_tasks(*names)
|
38
32
|
Module.new do
|
39
33
|
module_function
|
34
|
+
|
40
35
|
names.each do |name|
|
41
|
-
define_method(name) do |
|
36
|
+
define_method(name) do |(ctx, flow_options), **|
|
42
37
|
ctx[:seq] << name
|
43
38
|
signal = ctx.key?(name) ? ctx[name] : Activity::Right
|
44
39
|
|
@@ -49,136 +44,62 @@ module Trailblazer
|
|
49
44
|
end
|
50
45
|
|
51
46
|
module Assertions
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
# Call without taskWrap!
|
62
|
-
signal, (ctx, _) = activity.([{seq: [], **ctx_variables}, _flow_options = {}]) # simply call the activity with the input you want to assert.
|
63
|
-
|
64
|
-
assert_call_for(signal, ctx, terminus: terminus, seq: seq, **expected_ctx_variables, **ctx_variables)
|
65
|
-
end
|
66
|
-
|
67
|
-
# Use {TaskWrap.invoke} to call the activity.
|
68
|
-
def assert_invoke(activity, terminus: :success, seq: "[]", circuit_options: {}, expected_ctx_variables: {}, **ctx_variables)
|
69
|
-
signal, (ctx, _flow_options) = TaskWrap.invoke(
|
70
|
-
activity,
|
71
|
-
[
|
72
|
-
{seq: [], **ctx_variables},
|
73
|
-
{} # flow_options
|
74
|
-
],
|
75
|
-
**circuit_options
|
76
|
-
)
|
77
|
-
|
78
|
-
assert_call_for(signal, ctx, terminus: terminus, seq: seq, **ctx_variables, **expected_ctx_variables) # DISCUSS: ordering of variables?
|
79
|
-
end
|
80
|
-
|
81
|
-
def assert_call_for(signal, ctx, terminus: :success, seq: "[]", **ctx_variables)
|
82
|
-
assert_equal signal.to_h[:semantic], terminus, "assert_call expected #{terminus} terminus, not #{signal}. Use assert_call(activity, terminus: #{signal.to_h[:semantic].inspect})"
|
83
|
-
|
84
|
-
assert_equal ctx.inspect, {seq: "%%%"}.merge(ctx_variables).inspect.sub('"%%%"', seq)
|
85
|
-
|
86
|
-
return ctx
|
87
|
-
end
|
88
|
-
|
89
|
-
module Implementing
|
90
|
-
extend Activity::Testing.def_tasks(:a, :b, :c, :d, :f, :g)
|
47
|
+
# `:seq` is always passed into ctx.
|
48
|
+
# @param :seq String What the {:seq} variable in the result ctx looks like. (expected seq)
|
49
|
+
# @param :expected_ctx_variables Variables that are added during the call by the asserted activity.
|
50
|
+
def assert_call(activity, terminus: :success, seq: "[]", expected_ctx_variables: {}, **ctx_variables)
|
51
|
+
# Call without taskWrap!
|
52
|
+
signal, (ctx, _) = activity.([{seq: [], **ctx_variables}, _flow_options = {}]) # simply call the activity with the input you want to assert.
|
53
|
+
|
54
|
+
assert_call_for(signal, ctx, terminus: terminus, seq: seq, **expected_ctx_variables, **ctx_variables)
|
55
|
+
end
|
91
56
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
57
|
+
# Use {TaskWrap.invoke} to call the activity.
|
58
|
+
def assert_invoke(activity, terminus: :success, seq: "[]", circuit_options: {}, expected_ctx_variables: {}, **ctx_variables)
|
59
|
+
signal, (ctx, _flow_options) = Activity::TaskWrap.invoke(
|
60
|
+
activity,
|
61
|
+
[
|
62
|
+
{seq: [], **ctx_variables},
|
63
|
+
{} # flow_options
|
64
|
+
],
|
65
|
+
**circuit_options
|
66
|
+
)
|
67
|
+
|
68
|
+
assert_call_for(signal, ctx, terminus: terminus, seq: seq, **ctx_variables, **expected_ctx_variables) # DISCUSS: ordering of variables?
|
69
|
+
end
|
96
70
|
|
97
|
-
|
98
|
-
|
99
|
-
Implementing
|
100
|
-
end
|
71
|
+
def assert_call_for(signal, ctx, terminus: :success, seq: "[]", **ctx_variables)
|
72
|
+
assert_equal signal.to_h[:semantic], terminus, "assert_call expected #{terminus} terminus, not #{signal}. Use assert_call(activity, terminus: #{signal.to_h[:semantic].inspect})"
|
101
73
|
|
102
|
-
|
103
|
-
return @_flat_activity if defined?(@_flat_activity)
|
104
|
-
|
105
|
-
intermediate = Inter.new(
|
106
|
-
{
|
107
|
-
Inter::TaskRef("Start.default") => [Inter::Out(:success, :B)],
|
108
|
-
Inter::TaskRef(:B, additional: true) => [Inter::Out(:success, :C), Inter::Out(:failure, "End.failure")],
|
109
|
-
Inter::TaskRef(:C) => [Inter::Out(:success, "End.success")],
|
110
|
-
Inter::TaskRef("End.success", stop_event: true) => [Inter::Out(:success, nil)],
|
111
|
-
Inter::TaskRef("End.failure", stop_event: true) => [Inter::Out(:failure, nil)],
|
112
|
-
},
|
113
|
-
["End.success", "End.failure"],
|
114
|
-
["Start.default"], # start
|
115
|
-
)
|
116
|
-
|
117
|
-
implementation = {
|
118
|
-
"Start.default" => Schema::Implementation::Task(st = Implementing::Start, [Activity::Output(Activity::Right, :success)], []),
|
119
|
-
:B => Schema::Implementation::Task(b = implementing.method(:b), [Activity::Output(Activity::Right, :success), Activity::Output(Activity::Left, :failure)], []),
|
120
|
-
:C => Schema::Implementation::Task(c = implementing.method(:c), [Activity::Output(Activity::Right, :success)], []),
|
121
|
-
"End.success" => Schema::Implementation::Task(_es = Implementing::Success, [Activity::Output(Implementing::Success, :success)], []), # DISCUSS: End has one Output, signal is itself?
|
122
|
-
"End.failure" => Schema::Implementation::Task(Implementing::Failure, [Activity::Output(Implementing::Failure, :failure)], []), # DISCUSS: End has one Output, signal is itself?
|
123
|
-
}
|
124
|
-
|
125
|
-
schema = Inter.(intermediate, implementation)
|
126
|
-
|
127
|
-
@_flat_activity = Activity.new(schema)
|
128
|
-
end
|
74
|
+
assert_equal ctx.inspect, {seq: "%%%"}.merge(ctx_variables).inspect.sub('"%%%"', seq)
|
129
75
|
|
130
|
-
|
131
|
-
|
132
|
-
intermediate = Inter.new(
|
133
|
-
{
|
134
|
-
Inter::TaskRef("Start.default") => [Inter::Out(:success, :B)],
|
135
|
-
Inter::TaskRef(:B, more: true) => [Inter::Out(:success, d_id)],
|
136
|
-
Inter::TaskRef(d_id) => [Inter::Out(:success, :E)],
|
137
|
-
Inter::TaskRef(:E) => [Inter::Out(:success, "End.success")],
|
138
|
-
Inter::TaskRef("End.success", stop_event: true) => [Inter::Out(:success, nil)]
|
139
|
-
},
|
140
|
-
["End.success"],
|
141
|
-
["Start.default"] # start
|
142
|
-
)
|
143
|
-
|
144
|
-
implementation = {
|
145
|
-
"Start.default" => Schema::Implementation::Task(st = Implementing::Start, [Activity::Output(Activity::Right, :success)], []),
|
146
|
-
:B => Schema::Implementation::Task(b = Implementing.method(:b), [Activity::Output(Activity::Right, :success)], []),
|
147
|
-
d_id => Schema::Implementation::Task(flat_activity, [Activity::Output(Implementing::Success, :success)], []),
|
148
|
-
:E => Schema::Implementation::Task(e = Implementing.method(:f), [Activity::Output(Activity::Right, :success)], []),
|
149
|
-
"End.success" => Schema::Implementation::Task(_es = Implementing::Success, [Activity::Output(Implementing::Success, :success)], []), # DISCUSS: End has one Output, signal is itself?
|
150
|
-
}
|
151
|
-
|
152
|
-
schema = Inter.(intermediate, implementation)
|
153
|
-
|
154
|
-
Activity.new(schema)
|
155
|
-
end
|
76
|
+
return ctx
|
77
|
+
end
|
156
78
|
|
157
|
-
|
158
|
-
|
79
|
+
# Tests {:circuit} and {:outputs} fields so far.
|
80
|
+
def assert_process_for(process, *args)
|
81
|
+
semantics, circuit = args[0..-2], args[-1]
|
159
82
|
|
160
|
-
|
161
|
-
def assert_process_for(process, *args)
|
162
|
-
semantics, circuit = args[0..-2], args[-1]
|
83
|
+
inspects = semantics.collect { |semantic| %(#<struct Trailblazer::Activity::Output signal=#<Trailblazer::Activity::End semantic=#{semantic.inspect}>, semantic=#{semantic.inspect}>) }
|
163
84
|
|
164
|
-
|
85
|
+
assert_equal %([#{inspects.join(", ")}]), process.to_h[:outputs].inspect
|
165
86
|
|
166
|
-
|
87
|
+
assert_circuit(process, circuit)
|
167
88
|
|
168
|
-
|
89
|
+
process
|
90
|
+
end
|
169
91
|
|
170
|
-
|
171
|
-
end
|
92
|
+
alias_method :assert_process, :assert_process_for
|
172
93
|
|
173
|
-
|
174
|
-
|
94
|
+
def assert_circuit(schema, circuit)
|
95
|
+
cct = Cct(schema)
|
175
96
|
|
176
|
-
|
177
|
-
|
178
|
-
|
97
|
+
cct = cct.gsub("#<Trailblazer::Activity::TaskBuilder::Task user_proc=", "<*")
|
98
|
+
assert_equal circuit.to_s, cct
|
99
|
+
end
|
179
100
|
|
180
101
|
def Cct(activity)
|
181
|
-
|
102
|
+
Activity::Introspect::Render.(activity, inspect_task: Trailblazer::Activity::Testing.method(:render_task))
|
182
103
|
end
|
183
104
|
end
|
184
105
|
|