trailblazer-developer 0.0.27 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,7 @@ require "hirb"
2
2
 
3
3
  module Trailblazer::Developer
4
4
  module Trace
5
- module Present
5
+ module Present # DISCUSS: rename to Debugger?
6
6
  module_function
7
7
 
8
8
  # @private
@@ -12,33 +12,65 @@ module Trailblazer::Developer
12
12
 
13
13
  # Returns the console output string.
14
14
  # @private
15
- def render(debugger_nodes, renderer: method(:default_renderer), **options_for_renderer)
16
- nodes = debugger_nodes.collect do |debugger_node|
17
- renderer.(debugger_node: debugger_node, tree: debugger_nodes, **options_for_renderer)
15
+ def render(debugger_trace:, renderer: method(:default_renderer), **options_for_renderer)
16
+ nodes = debugger_trace.to_a.collect do |debugger_node|
17
+ renderer.(debugger_node: debugger_node, debugger_trace: debugger_trace, **options_for_renderer)
18
18
  end
19
19
 
20
20
  Hirb::Console.format_output(nodes, class: :tree, type: :directory, multi_line_nodes: true)
21
21
  end
22
22
 
23
- # Entry point for rendering a Stack as a "tree branch" the way we do it in {#wtf?}.
24
- def call(stack, render_method: method(:render), node_options: {}, **options)
23
+ # Entry point for rendering a {Trace::Stack}.
24
+ # Used in `#wtf?`.
25
+ def call(stack, render_method: method(:render), **options, &block)
26
+ deprecate_node_options!(**options) # TODO: remove in 0.2.0.
27
+
28
+ # Build a generic array of {Trace::Node}s.
29
+ trace_nodes = Trace.build_nodes(stack.to_a)
30
+
25
31
  # The top activity doesn't have an ID, hence we need to compute a default label.
26
- # TODO: maybe we should deep-merge here.
27
- captured_input_for_top_activity = stack.to_a[0]
28
-
29
- top_activity_options = {
30
- # we can pass particular label "hints".
31
- captured_input_for_top_activity => {
32
- # label: %{#{captured_input_for_top_activity.task.superclass} (anonymous)},
33
- label: captured_input_for_top_activity.task.inspect,
34
- },
32
+ top_activity_trace_node = trace_nodes[0]
33
+
34
+ build_options = {
35
+ node_options: {
36
+ # we can pass particular label "hints".
37
+ top_activity_trace_node => {
38
+ # label: %{#{top_activity_trace_node.task.superclass} (anonymous)},
39
+ label: top_activity_trace_node.task.inspect,
40
+ },
41
+ }
35
42
  }
36
43
 
37
- node_options = top_activity_options.merge(node_options)
44
+ build_options = build_options.merge(options) # since we only have {:node_options} in {build_options}, we can safely merge here.
45
+
46
+ # specific rendering.
47
+ options_from_block = block_given? ? block.call(trace_nodes: trace_nodes, stack: stack, **build_options) : {}
48
+
49
+ build_options = merge_local_options(options_from_block, build_options)
50
+
51
+ # currently, we agree on using a Debugger::Node list as the presentation data structure.
52
+ debugger_trace = Debugger::Trace.build(stack, trace_nodes, **build_options)
38
53
 
39
- debugger_nodes = Debugger::Node.build_for_stack(stack, node_options: node_options, **options) # currently, we agree on using a Debugger::Node list as the presentation data structure.
54
+ return render_method.(debugger_trace: debugger_trace, **build_options)
55
+ end
56
+
57
+ def deprecate_node_options!(node_options: nil, **) # TODO: remove in 0.2.0.
58
+ return unless node_options
59
+
60
+ raise "[Trailblazer] The `:node_options` option for `Trace::Present` is deprecated.
61
+ Please use the block style as described here: https://trailblazer.to/2.1/docs/internals.html#internals-developer-trace-present"
62
+ end
63
+
64
+ # @private
65
+ def merge_local_options(options, local_options)
66
+ merged_hash = options.collect do |key, value|
67
+ [
68
+ key,
69
+ value.is_a?(Hash) ? local_options.fetch(key, {}).merge(value) : value # options are winning over local_options[key]
70
+ ]
71
+ end.to_h
40
72
 
41
- return render_method.(debugger_nodes, **options)
73
+ local_options.merge(merged_hash)
42
74
  end
43
75
  end # Present
44
76
  end
@@ -0,0 +1,39 @@
1
+ module Trailblazer::Developer
2
+ module Trace
3
+ class Snapshot
4
+ # {Value} serializes the variable value using with custom logic, e.g. {value.inspect}.
5
+ # A series of matchers decide which snapshooter is used.
6
+ class Value
7
+ def initialize(matchers)
8
+ @matchers = matchers
9
+ end
10
+
11
+ # DISCUSS: this could be a compiled pattern matching `case/in` block here.
12
+ def call(name, value, **options)
13
+ @matchers.each do |matcher, inspect_method|
14
+ if matcher.(name, value, **options)
15
+ return inspect_method.(name, value, **options)
16
+ end
17
+ end
18
+
19
+ raise "no matcher found for #{name.inspect}" # TODO: this should never happen.
20
+ end
21
+
22
+ def self.default_variable_inspect(name, value, ctx:)
23
+ value.inspect
24
+ end
25
+
26
+ def self.build
27
+ new(
28
+ [
29
+ [
30
+ ->(*) { true }, # matches everything
31
+ method(:default_variable_inspect)
32
+ ]
33
+ ]
34
+ )
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,105 @@
1
+ module Trailblazer::Developer
2
+ module Trace
3
+ class Snapshot
4
+ # Snapshot::Ctx keeps an inspected version of each ctx variable.
5
+ # We figure out if a variable has changed by using `variable.hash` (works
6
+ # even with deeply nested structures).
7
+ #
8
+ # Key idea here is to have minimum work at operation-runtime. Specifics like
9
+ # figuring out what has changed can be done when using the debugger.
10
+ #
11
+ # By keeping "old" versions, we get three benefits.
12
+ # 1. We only need to call {inspect} once on a traced variable. Especially
13
+ # when variables are complex structures or strings, this dramatically speeds
14
+ # up tracing, from same-ish to factor 5!
15
+ # 2. The content sent to our debugger is much smaller which reduces network load
16
+ # and storage space.
17
+ # 3. Presentation becomes simpler as we "know" what variable has changed.
18
+ #
19
+ # Possible problems: when {variable.hash} returns the same key even though the
20
+ # data has changed.
21
+ #
22
+ # DISCUSS: speed up by checking mutable, only?
23
+ # DISCUSS: we currently only use this for testing.
24
+ # DISCUSS: this has knowledge about {Stack} internals.
25
+ #
26
+ # This is for the "rendering" layer.
27
+ # @private
28
+ def self.snapshot_ctx_for(snapshot, variable_versions)
29
+ variable_versions = variable_versions.instance_variable_get(:@variables)
30
+
31
+ snapshot.data[:ctx_variable_changeset].collect do |name, hash, has_changed|
32
+ [
33
+ name,
34
+ {
35
+ value: variable_versions[name][hash],
36
+ has_changed: !!has_changed,
37
+ }
38
+ ]
39
+ end.to_h
40
+ end
41
+
42
+ # A table of all ctx variables, their hashes and serialized values.
43
+ #
44
+ # {:current_user=>
45
+ # {3298051090906520533=>"#<TraceTest::User:0x000055b2e3424460 @id=1>",
46
+ # 3764938782671692590=>"#<TraceTest::User:0x000055b2e33e45b8 @id=2>"},
47
+ # :params=>
48
+ # {2911818769466875657=>"{:name=>\"Q & I\"}",
49
+ # 2238394858183550663=>"{:name=>\"Q & I\", :song=>{...}}"},
50
+ # :seq=>
51
+ # {-105020188158523405=>"[]",
52
+ # -2281497291400788995=>"[:authenticate]",
53
+ # 150926802063554866=>"[:authenticate, :authorize]",
54
+ # 3339595138798116233=>"[:authenticate, :authorize, :model]",
55
+ # -3395325862879242711=>
56
+ # "[:authenticate, :authorize, :model, :screw_params!]"},
57
+ # :model=>{348183403054247453=>"Object"}}
58
+ class Versions
59
+ def initialize
60
+ @variables = {}
61
+ end
62
+
63
+ # DISCUSS: problem with changeset is, we have to go through variables twice.
64
+ def changeset_for(ctx, value_snapshooter:)
65
+ new_versions = []
66
+
67
+ changeset_for_snapshot = ctx.collect do |name, value|
68
+ # DISCUSS: do we have to call that explicitly or does Hash#[] do that for us, anyway?
69
+ value_hash = value.hash # DISCUSS: does this really always change when a deeply nested object changes?
70
+
71
+ if (variable_versions = @variables[name]) && variable_versions.key?(value_hash) # TODO: test {variable: nil} value
72
+ [name, value_hash, nil] # nil means it's an existing reference.
73
+ else
74
+ value_snapshot = value_snapshooter.(name, value, ctx: ctx)
75
+
76
+ version = [name, value_hash, value_snapshot]
77
+
78
+ new_versions << version
79
+ version
80
+ end
81
+ end
82
+
83
+ return changeset_for_snapshot, new_versions
84
+ end
85
+
86
+ def add_changes!(new_versions)
87
+ new_versions.each do |args|
88
+ add_variable_version!(*args)
89
+ end
90
+ end
91
+
92
+ # @private
93
+ def add_variable_version!(name, hash, value)
94
+ @variables[name] ||= {}
95
+
96
+ @variables[name][hash] = value # i hate mutations.
97
+ end
98
+
99
+ def to_h
100
+ @variables
101
+ end
102
+ end
103
+ end # Snapshot
104
+ end
105
+ end
@@ -0,0 +1,71 @@
1
+ module Trailblazer::Developer
2
+ module Trace
3
+ # WARNING: the interfaces here are subject to change, we're still experimenting
4
+ # with the architecture of tracing, and a healthy balance of performance/memory
5
+ # and clean design.
6
+ # A Snapshot comprises of data captured before of after a "step". This usually
7
+ # includes a ctx snapshot, variable versions and a returned signal for after-step
8
+ # snapshots.
9
+ #
10
+ # Note that {Before} and {After} are generic concepts know to Trace::Present and Debugger.
11
+ #
12
+ # Snapshot::After{
13
+ # signal: <End.Success>
14
+ # ctx_snapshot: Snapshot::Ctx{
15
+ # variable_versions: [:current_user, 0], [:model, 0]
16
+ # }
17
+ # }
18
+ class Snapshot < Struct.new(:task, :activity, :data)
19
+ Before = Class.new(Snapshot)
20
+ After = Class.new(Snapshot)
21
+
22
+ # This is called from {Trace.capture_args} and {Trace.capture_return} in the taskWrap.
23
+ def self.call(ctx_snapshooter, wrap_config, ((ctx, flow_options), circuit_options))
24
+ # DISCUSS: grab the {snapshooter} here from flow_options, instead of in Trace.capture_args?
25
+ changeset, new_versions = ctx_snapshooter.call(wrap_config, [[ctx, flow_options], circuit_options])
26
+
27
+ snapshot = new( # either Before or After.
28
+ wrap_config[:task],
29
+ circuit_options[:activity],
30
+ changeset
31
+ ).freeze
32
+
33
+ return snapshot, new_versions
34
+ end
35
+
36
+ # Serialize all ctx variables before {call_task}.
37
+ # This is run just before {call_task}, after In().
38
+ def self.before_snapshooter(wrap_ctx, ((ctx, flow_options), _))
39
+ changeset, new_versions = snapshot_for(ctx, **flow_options)
40
+
41
+ data = {
42
+ ctx_variable_changeset: changeset,
43
+ }
44
+
45
+ return data, new_versions
46
+ end
47
+
48
+ # Serialize all ctx variables at the very end of taskWrap, after Out().
49
+ def self.after_snapshooter(wrap_ctx, _)
50
+ snapshot_before = wrap_ctx[:snapshot_before]
51
+ returned_ctx, flow_options = wrap_ctx[:return_args]
52
+
53
+ changeset, new_versions = snapshot_for(returned_ctx, **flow_options)
54
+
55
+ data = {
56
+ ctx_variable_changeset: changeset,
57
+ signal: wrap_ctx[:return_signal],
58
+ snapshot_before: snapshot_before, # add this so we know who belongs together.
59
+ }
60
+
61
+ return data, new_versions
62
+ end
63
+
64
+ def self.snapshot_for(ctx, value_snapshooter:, stack:, **)
65
+ variable_versions = stack.variable_versions
66
+
67
+ variable_versions.changeset_for(ctx, value_snapshooter: value_snapshooter) # return {changeset, new_versions}
68
+ end
69
+ end
70
+ end
71
+ end
@@ -3,17 +3,30 @@ module Trailblazer
3
3
  module Trace
4
4
  # The stack is a linear one-dimensional array. Per traced task two {Trace::Captured}
5
5
  # elements get pushed onto it (unless there's an Exception).
6
+ #
7
+ # The Stack object maintains the snapshots and the variable versions. It should probably
8
+ # be named "Trace" :D
9
+ # It is by design coupled to both Snapshot and Ctx::Versions.
6
10
  class Stack
7
- def initialize(captureds=[])
8
- @stack = captureds
11
+ def initialize(snapshots = [], variable_versions = Snapshot::Versions.new)
12
+ @snapshots = snapshots
13
+ @variable_versions = variable_versions # DISCUSS: I dislike the coupling here to Stack, but introducting another object comprised of Stack and VariableVersions seems overkill.
9
14
  end
10
15
 
11
- def <<(captured)
12
- @stack << captured
16
+ attr_reader :variable_versions # TODO: the accessor sucks. But I guess to_h[:variable_versions] is slower.
17
+
18
+ def add!(snapshot, new_variable_versions)
19
+ # variable_versions is mutated in the snapshooter, that's
20
+ # why we don't have to re-set it here. I'm not a huge fan of mutating it
21
+ # in a deeply nested scenario but everything else we played with added huge amounts
22
+ # or runtime code.
23
+ @variable_versions.add_changes!(new_variable_versions)
24
+
25
+ @snapshots << snapshot
13
26
  end
14
27
 
15
28
  def to_a
16
- @stack
29
+ @snapshots
17
30
  end
18
31
  end # Stack
19
32
  end
@@ -1,10 +1,11 @@
1
1
  module Trailblazer::Developer
2
2
  module Trace
3
-
4
3
  class << self
5
- # Public entry point to activate tracing when running {activity}.
4
+ # Public entry point to run an activity with tracing.
5
+ # It returns the accumulated stack of Snapshots, along with the original return values.
6
+ # Note that {Trace.invoke} does not do any rendering.
6
7
  def call(activity, (ctx, flow_options), **circuit_options)
7
- activity, (ctx, flow_options), circuit_options = Trace.arguments_for_call( activity, [ctx, flow_options], **circuit_options ) # only run once for the entire circuit!
8
+ activity, (ctx, flow_options), circuit_options = Trace.arguments_for_call(activity, [ctx, flow_options], **circuit_options) # only run once for the entire circuit!
8
9
 
9
10
  signal, (ctx, flow_options) = Trailblazer::Activity::TaskWrap.invoke(activity, [ctx, flow_options], **circuit_options)
10
11
 
@@ -15,9 +16,10 @@ module Trailblazer::Developer
15
16
 
16
17
  def arguments_for_call(activity, (options, original_flow_options), **original_circuit_options)
17
18
  default_flow_options = {
18
- stack: Trace::Stack.new,
19
- input_data_collector: Trace.method(:default_input_data_collector),
20
- output_data_collector: Trace.method(:default_output_data_collector),
19
+ stack: Trace::Stack.new,
20
+ before_snapshooter: Snapshot.method(:before_snapshooter),
21
+ after_snapshooter: Snapshot.method(:after_snapshooter),
22
+ value_snapshooter: Trace.value_snapshooter
21
23
  }
22
24
 
23
25
  flow_options = {**default_flow_options, **Hash(original_flow_options)}
@@ -32,79 +34,52 @@ module Trailblazer::Developer
32
34
  end
33
35
  end
34
36
 
37
+ @value_snapshooter = Trace::Snapshot::Value.build()
38
+ singleton_class.attr_reader :value_snapshooter # NOTE: this is semi-private.
39
+
35
40
  module_function
36
- # Insertions for the trace tasks that capture the arguments just before calling the task,
37
- # and before the TaskWrap is finished.
38
- #
41
+
39
42
  # @private
40
43
  def task_wrap_extensions
41
- Trailblazer::Activity::TaskWrap.Extension(
42
- [Trace.method(:capture_args), id: "task_wrap.capture_args", prepend: "task_wrap.call_task"],
43
- [Trace.method(:capture_return), id: "task_wrap.capture_return", append: nil], # append to the very end of tW.
44
- )
44
+ TASK_WRAP_EXTENSION
45
45
  end
46
46
 
47
+ # Snapshot::Before and After are a generic concept of Trace, as
48
+ # they're the interface to Trace::Present, WTF, and Debugger.
49
+
47
50
  # It's important to understand that {flow[:stack]} is mutated by design. This is needed so
48
51
  # in case of exceptions we still have a "global" trace - unfortunately Ruby doesn't allow
49
52
  # us a better way.
50
53
  # taskWrap step to capture incoming arguments of a step.
51
- def capture_args(wrap_config, ((ctx, flow), circuit_options))
52
- original_args = [[ctx, flow], circuit_options]
54
+ #
55
+ # Note that we save the created {Snapshot::Before} in the wrap_ctx.
56
+ def capture_args(wrap_config, original_args)
57
+ flow_options = original_args[0][1]
53
58
 
54
- captured_input = Captured(Captured::Input, flow[:input_data_collector], wrap_config, original_args)
59
+ snapshot, new_versions = Snapshot::Before.(flow_options[:before_snapshooter], wrap_config, original_args)
55
60
 
56
- flow[:stack] << captured_input
61
+ # We try to be generic here in the taskWrap snapshooting code, where details happen in Snapshot::Before/After and Stack#add!.
62
+ flow_options[:stack].add!(snapshot, new_versions)
57
63
 
58
- return wrap_config, original_args
64
+ return wrap_config.merge(snapshot_before: snapshot), original_args
59
65
  end
60
66
 
61
67
  # taskWrap step to capture outgoing arguments from a step.
62
- def capture_return(wrap_config, ((ctx, flow), circuit_options))
63
- original_args = [[ctx, flow], circuit_options]
68
+ def capture_return(wrap_config, ((ctx, flow_options), circuit_options))
69
+ original_args = [[ctx, flow_options], circuit_options]
64
70
 
65
- captured_output = Captured(Captured::Output, flow[:output_data_collector], wrap_config, original_args)
71
+ snapshot, new_versions = Snapshot::After.(flow_options[:after_snapshooter], wrap_config, original_args)
66
72
 
67
- flow[:stack] << captured_output
73
+ flow_options[:stack].add!(snapshot, new_versions)
68
74
 
69
75
  return wrap_config, original_args
70
76
  end
71
77
 
72
- def Captured(captured_class, data_collector, wrap_config, ((ctx, flow), circuit_options))
73
- collected_data = data_collector.call(wrap_config, [[ctx, flow], circuit_options])
74
-
75
- captured_class.new( # either Input or Output
76
- wrap_config[:task],
77
- circuit_options[:activity],
78
- collected_data
79
- ).freeze
80
- end
81
-
82
- # Called in {#Captured}.
83
- # DISCUSS: this is where to start for a new {Inspector} implementation.
84
- def default_input_data_collector(wrap_config, ((ctx, _), _)) # DISCUSS: would it be faster to access ctx via {original_args[0][0]}?
85
- # mutable, old_ctx = ctx.decompose
86
- # mutable, old_ctx = ctx, nil
87
-
88
- {
89
- # ctx: ctx.to_h.freeze,
90
- ctx_snapshot: ctx.to_h.collect { |k,v| [k, v.inspect] }.to_h,
91
- } # TODO: proper snapshot!
92
- end
93
-
94
- # Called in {#Captured}.
95
- def default_output_data_collector(wrap_config, ((ctx, _), _))
96
- returned_ctx, _ = wrap_config[:return_args]
97
-
98
- # FIXME: snapshot!
99
- {
100
- # ctx: ctx.to_h.freeze,
101
- ctx_snapshot: returned_ctx.to_h.collect { |k,v| [k, v.inspect] }.to_h,
102
- signal: wrap_config[:return_signal]
103
- }
104
- end
105
-
106
- Captured = Struct.new(:task, :activity, :data)
107
- Captured::Input = Class.new(Captured)
108
- Captured::Output = Class.new(Captured)
78
+ # Insertions for the trace tasks that capture the arguments just before calling the task,
79
+ # and before the TaskWrap is finished.
80
+ TASK_WRAP_EXTENSION = Trailblazer::Activity::TaskWrap.Extension(
81
+ [Trace.method(:capture_args), id: "task_wrap.capture_args", prepend: "task_wrap.call_task"],
82
+ [Trace.method(:capture_return), id: "task_wrap.capture_return", append: nil], # append to the very end of tW.
83
+ )
109
84
  end
110
85
  end
@@ -1,7 +1,7 @@
1
1
  module Trailblazer
2
2
  module Version
3
3
  module Developer
4
- VERSION = "0.0.27"
4
+ VERSION = "0.1.0"
5
5
  end
6
6
  end
7
7
  end
@@ -14,21 +14,21 @@ module Trailblazer::Developer
14
14
 
15
15
  module_function
16
16
 
17
- # {options} can be {style: {#<Captured::Input> => [:red, :bold]}}
18
- def call(tree:, debugger_node:, style: {}, **options)
19
- label = styled_label(tree, debugger_node, style: style, **options)
17
+ # {options} can be {style: {#<Debugger::Node> => [:red, :bold]}}
18
+ def call(debugger_trace:, debugger_node:, style: {}, **options)
19
+ label = styled_label(debugger_trace, debugger_node, style: style, **options)
20
20
 
21
21
  [debugger_node.level, label]
22
22
  end
23
23
 
24
- def styled_label(tree, debugger_node, color_map:, **options)
24
+ def styled_label(debugger_trace, debugger_node, color_map:, **options)
25
25
  label = apply_style(debugger_node.label, debugger_node, **options)
26
26
 
27
- %{#{fmt(label, color_map[ signal_of(debugger_node) ])}}
27
+ %{#{fmt(label, color_map[signal_of(debugger_node)])}}
28
28
  end
29
29
 
30
30
  def apply_style(label, debugger_node, style:, **)
31
- return label unless styles = style[debugger_node.captured_input]
31
+ return label unless styles = style[debugger_node.trace_node]
32
32
 
33
33
  styles.each { |s| label = fmt(label, s) }
34
34
  label
@@ -42,8 +42,8 @@ module Trailblazer::Developer
42
42
  String.send(style, line)
43
43
  end
44
44
 
45
- def signal_of(task_node)
46
- entity_signal = task_node.captured_output.data[:signal]
45
+ def signal_of(debugger_node)
46
+ entity_signal = debugger_node.incomplete? ? nil : debugger_node.snapshot_after.data[:signal]
47
47
  entity_klass = entity_signal.is_a?(Class) ? entity_signal : entity_signal.class
48
48
 
49
49
  SIGNALS_MAP[entity_klass.name.to_sym]
@@ -16,62 +16,62 @@ module Trailblazer::Developer
16
16
  # This allows to display the trace even when an exception happened
17
17
  def invoke(activity, (ctx, flow_options), present_options: {}, **circuit_options)
18
18
  flow_options ||= {}
19
+ local_present_options_block = ->(*) { {} }
19
20
 
20
21
  stack = Trace::Stack.new # unfortunately, we need this mutable object before things break.
22
+ raise_exception = false
23
+
24
+ begin
25
+ complete_stack, signal, (ctx, flow_options) = Trace.invoke(
26
+ activity,
27
+ [ctx, flow_options.merge(stack: stack)],
28
+ **circuit_options
29
+ )
30
+ rescue
31
+ raise_exception = $! # TODO: will this show the very same stacktrace?
32
+
33
+ exception_source = Exception.find_exception_source(stack, $!)
34
+ complete_stack = stack
35
+
36
+ local_present_options_block = ->(trace_nodes:, **) {
37
+ exception_source_node = trace_nodes.reverse.find do |trace_node|
38
+ trace_node.snapshot_after == exception_source || trace_node.snapshot_before == exception_source
39
+ end
21
40
 
22
- complete_stack, signal, (ctx, flow_options) = Trace.invoke(
23
- activity,
24
- [ctx, flow_options.merge(stack: stack)],
25
- **circuit_options
26
- )
27
-
28
- return signal, [ctx, flow_options], circuit_options
29
-
30
- ensure
31
- # incomplete_stack = flow_options[:stack]
32
- incomplete_stack = stack
33
-
34
- # in 99%, exception_source is a {Captured::Input}.
35
- exception_source = incomplete_stack.to_a.last # DISCUSS: in most cases, this is where the problem has happened.
36
- # However, what if an error happens in, say, an input filter? TODO: test this
37
-
38
- complete_stack = Exception::Stack.complete(incomplete_stack) # TODO: only in case of exception!
39
-
40
- puts Trace::Present.(
41
+ {
42
+ # we can hand in options per node, identified by their captured_input part.
43
+ node_options: {
44
+ exception_source_node => {data: {exception_source: true}}, # goes to {Debugger::Node.build}
45
+ },
46
+ style: {
47
+ exception_source_node => [:red, :bold]
48
+ },
49
+ }
50
+ }
51
+ end
52
+
53
+ # always render the trace.
54
+ output, returned_args = Trace::Present.(
41
55
  complete_stack,
42
- # we can hand in options per node, identified by their captured_input part.
43
- node_options: {
44
- exception_source => {data: {exception_source: true}}, # goes to {Debugger::Node.build}
45
- },
46
-
47
56
  renderer: Wtf::Renderer,
48
- color_map: Wtf::Renderer::DEFAULT_COLOR_MAP.merge( flow_options[:color_map] || {} ),
49
- style: {exception_source => [:red, :bold]},
50
- **present_options, # TODO: test.
57
+ color_map: Wtf::Renderer::DEFAULT_COLOR_MAP.merge(flow_options[:color_map] || {}),
58
+ activity: activity,
59
+ **present_options,
60
+ &local_present_options_block
51
61
  )
52
- end
53
-
54
- module Exception
55
- # When an exception occurs the Stack instance is incomplete - it is missing Captured::Output instances
56
- # for Inputs still open. This method adds the missing elements so the Trace::Tree algorithm doesn't crash.
57
- module Stack
58
- def self.complete(incomplete_stack)
59
- processed = []
60
62
 
61
- incomplete_stack.to_a.each do |captured|
62
- if captured.is_a?(Trace::Captured::Input)
63
- processed << captured
64
- else
65
- processed.pop
66
- end
67
- end
63
+ puts output
68
64
 
69
- missing_captured = processed.reverse.collect { |captured| Trace::Captured::Output.new(captured.task, captured.activity, {}) }
70
-
71
- Trace::Stack.new(incomplete_stack.to_a + missing_captured)
72
- end
73
- end # Stack
65
+ raise raise_exception if raise_exception
66
+ return signal, [ctx, flow_options], circuit_options, output, returned_args
67
+ end
74
68
 
69
+ module Exception
70
+ def self.find_exception_source(stack, exception)
71
+ # in 99%, exception_source is a {Snapshot::Before}.
72
+ exception_source = stack.to_a.last # DISCUSS: in most cases, this is where the problem has happened.
73
+ # However, what if an error happens in, say, an input filter? TODO: test this
74
+ end
75
75
  end
76
76
  end
77
77
  end
@@ -9,13 +9,19 @@ end
9
9
  require "trailblazer/activity"
10
10
  require "trailblazer/developer/wtf"
11
11
  require "trailblazer/developer/wtf/renderer"
12
+ require "trailblazer/developer/trace/snapshot"
13
+ require "trailblazer/developer/trace/snapshot/value"
14
+ require "trailblazer/developer/trace/snapshot/versions"
12
15
  require "trailblazer/developer/trace"
13
16
  require "trailblazer/developer/trace/stack"
14
- require "trailblazer/developer/trace/tree"
17
+ require "trailblazer/developer/trace/node"
18
+ require "trailblazer/developer/trace/parent_map"
15
19
  require "trailblazer/developer/trace/present"
16
- require "trailblazer/developer/trace/debugger"
20
+ require "trailblazer/developer/debugger"
17
21
  require "trailblazer/developer/render/circuit"
18
22
  require "trailblazer/developer/render/linear"
19
23
  require "trailblazer/developer/render/task_wrap"
20
24
  require "trailblazer/developer/introspect" # TODO: might get removed, again.
21
- require "trailblazer/developer/trace/debugger/normalizer"
25
+ require "trailblazer/developer/debugger/normalizer"
26
+ require "trailblazer/developer/introspect/graph"
27
+ Trailblazer::Developer::Trace::Debugger = Trailblazer::Developer::Debugger # FIXME: deprecate constant!