appmap 0.42.1 → 0.46.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.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/.releaserc.yml +11 -0
  3. data/.travis.yml +33 -2
  4. data/CHANGELOG.md +44 -0
  5. data/README.md +74 -16
  6. data/README_CI.md +29 -0
  7. data/Rakefile +4 -2
  8. data/appmap.gemspec +5 -3
  9. data/lib/appmap.rb +3 -7
  10. data/lib/appmap/class_map.rb +11 -22
  11. data/lib/appmap/command/record.rb +1 -1
  12. data/lib/appmap/config.rb +180 -67
  13. data/lib/appmap/cucumber.rb +1 -1
  14. data/lib/appmap/event.rb +46 -27
  15. data/lib/appmap/handler/function.rb +19 -0
  16. data/lib/appmap/handler/net_http.rb +107 -0
  17. data/lib/appmap/handler/rails/request_handler.rb +124 -0
  18. data/lib/appmap/handler/rails/sql_handler.rb +152 -0
  19. data/lib/appmap/handler/rails/template.rb +149 -0
  20. data/lib/appmap/hook.rb +111 -70
  21. data/lib/appmap/hook/method.rb +6 -8
  22. data/lib/appmap/middleware/remote_recording.rb +1 -1
  23. data/lib/appmap/minitest.rb +22 -20
  24. data/lib/appmap/railtie.rb +5 -5
  25. data/lib/appmap/record.rb +1 -1
  26. data/lib/appmap/rspec.rb +22 -21
  27. data/lib/appmap/trace.rb +47 -6
  28. data/lib/appmap/util.rb +47 -2
  29. data/lib/appmap/version.rb +2 -2
  30. data/package-lock.json +3 -3
  31. data/release.sh +17 -0
  32. data/spec/abstract_controller_base_spec.rb +140 -34
  33. data/spec/class_map_spec.rb +5 -13
  34. data/spec/config_spec.rb +33 -1
  35. data/spec/fixtures/hook/custom_instance_method.rb +11 -0
  36. data/spec/fixtures/hook/method_named_call.rb +11 -0
  37. data/spec/fixtures/rails5_users_app/Gemfile +7 -3
  38. data/spec/fixtures/rails5_users_app/app/controllers/api/users_controller.rb +2 -0
  39. data/spec/fixtures/rails5_users_app/app/controllers/users_controller.rb +9 -1
  40. data/spec/fixtures/rails5_users_app/config/application.rb +2 -0
  41. data/spec/fixtures/rails5_users_app/create_app +8 -2
  42. data/spec/fixtures/rails5_users_app/spec/controllers/users_controller_api_spec.rb +13 -0
  43. data/spec/fixtures/rails5_users_app/spec/controllers/users_controller_spec.rb +2 -2
  44. data/spec/fixtures/rails5_users_app/spec/rails_helper.rb +3 -9
  45. data/spec/fixtures/rails6_users_app/Gemfile +5 -4
  46. data/spec/fixtures/rails6_users_app/app/controllers/api/users_controller.rb +1 -0
  47. data/spec/fixtures/rails6_users_app/app/controllers/users_controller.rb +9 -1
  48. data/spec/fixtures/rails6_users_app/config/application.rb +2 -0
  49. data/spec/fixtures/rails6_users_app/create_app +8 -2
  50. data/spec/fixtures/rails6_users_app/spec/controllers/users_controller_api_spec.rb +13 -0
  51. data/spec/fixtures/rails6_users_app/spec/controllers/users_controller_spec.rb +2 -2
  52. data/spec/fixtures/rails6_users_app/spec/rails_helper.rb +3 -9
  53. data/spec/hook_spec.rb +143 -22
  54. data/spec/record_net_http_spec.rb +160 -0
  55. data/spec/record_sql_rails_pg_spec.rb +1 -1
  56. data/spec/spec_helper.rb +16 -0
  57. data/test/expectations/openssl_test_key_sign1.json +2 -4
  58. data/test/gem_test.rb +1 -1
  59. data/test/rspec_test.rb +0 -13
  60. metadata +20 -14
  61. data/exe/appmap +0 -154
  62. data/lib/appmap/rails/request_handler.rb +0 -109
  63. data/lib/appmap/rails/sql_handler.rb +0 -150
  64. data/test/cli_test.rb +0 -116
data/lib/appmap/hook.rb CHANGED
@@ -5,6 +5,7 @@ require 'English'
5
5
  module AppMap
6
6
  class Hook
7
7
  LOG = (ENV['APPMAP_DEBUG'] == 'true' || ENV['DEBUG'] == 'true')
8
+ LOG_HOOK = (ENV['DEBUG_HOOK'] == 'true')
8
9
 
9
10
  OBJECT_INSTANCE_METHODS = %i[! != !~ <=> == === =~ __id__ __send__ class clone define_singleton_method display dup enum_for eql? equal? extend freeze frozen? hash inspect instance_eval instance_exec instance_of? instance_variable_defined? instance_variable_get instance_variable_set instance_variables is_a? itself kind_of? method methods nil? object_id private_methods protected_methods public_method public_methods public_send remove_instance_variable respond_to? send singleton_class singleton_method singleton_methods taint tainted? tap then to_enum to_s to_h to_a trust untaint untrust untrusted? yield_self].freeze
10
11
  OBJECT_STATIC_METHODS = %i[! != !~ < <= <=> == === =~ > >= __id__ __send__ alias_method allocate ancestors attr attr_accessor attr_reader attr_writer autoload autoload? class class_eval class_exec class_variable_defined? class_variable_get class_variable_set class_variables clone const_defined? const_get const_missing const_set constants define_method define_singleton_method deprecate_constant display dup enum_for eql? equal? extend freeze frozen? hash include include? included_modules inspect instance_eval instance_exec instance_method instance_methods instance_of? instance_variable_defined? instance_variable_get instance_variable_set instance_variables is_a? itself kind_of? method method_defined? methods module_eval module_exec name new nil? object_id prepend private_class_method private_constant private_instance_methods private_method_defined? private_methods protected_instance_methods protected_method_defined? protected_methods public_class_method public_constant public_instance_method public_instance_methods public_method public_method_defined? public_methods public_send remove_class_variable remove_instance_variable remove_method respond_to? send singleton_class singleton_class? singleton_method singleton_methods superclass taint tainted? tap then to_enum to_s trust undef_method untaint untrust untrusted? yield_self].freeze
@@ -32,108 +33,148 @@ module AppMap
32
33
  end
33
34
 
34
35
  attr_reader :config
36
+
35
37
  def initialize(config)
36
38
  @config = config
39
+ @trace_locations = []
40
+ # Paths that are known to be non-tracing
41
+ @notrace_paths = Set.new
37
42
  end
38
43
 
39
44
  # Observe class loading and hook all methods which match the config.
40
- def enable &block
45
+ def enable(&block)
41
46
  require 'appmap/hook/method'
42
47
 
43
48
  hook_builtins
44
49
 
45
- tp = TracePoint.new(:end) do |trace_point|
46
- cls = trace_point.self
47
-
48
- instance_methods = cls.public_instance_methods(false) - OBJECT_INSTANCE_METHODS
49
- # NoMethodError: private method `singleton_class' called for Rack::MiniProfiler:Class
50
- class_methods = begin
51
- if cls.respond_to?(:singleton_class)
52
- cls.singleton_class.public_instance_methods(false) - instance_methods - OBJECT_STATIC_METHODS
53
- else
54
- []
55
- end
56
- rescue NameError
57
- []
58
- end
59
-
60
- hook = lambda do |hook_cls|
61
- lambda do |method_id|
62
- method = begin
63
- hook_cls.public_instance_method(method_id)
64
- rescue NameError
65
- warn "AppMap: Method #{hook_cls} #{method.name} is not accessible" if LOG
66
- return
67
- end
50
+ @trace_begin = TracePoint.new(:class, &method(:trace_class))
51
+ @trace_end = TracePoint.new(:end, &method(:trace_end))
68
52
 
69
- warn "AppMap: Examining #{hook_cls} #{method.name}" if LOG
53
+ @trace_begin.enable(&block)
54
+ end
70
55
 
71
- disasm = RubyVM::InstructionSequence.disasm(method)
72
- # Skip methods that have no instruction sequence, as they are obviously trivial.
73
- next unless disasm
56
+ # hook_builtins builds hooks for code that is built in to the Ruby standard library.
57
+ # No TracePoint events are emitted for builtins, so a separate hooking mechanism is needed.
58
+ def hook_builtins
59
+ return unless self.class.lock_builtins
74
60
 
75
- next if config.never_hook?(method)
61
+ class_from_string = lambda do |fq_class|
62
+ fq_class.split('::').inject(Object) do |mod, class_name|
63
+ mod.const_get(class_name)
64
+ end
65
+ end
76
66
 
77
- next unless \
78
- config.always_hook?(hook_cls, method.name) ||
79
- config.included_by_location?(method)
67
+ config.builtin_methods.each do |class_name, hooks|
68
+ Array(hooks).each do |hook|
69
+ require hook.package.package_name if hook.package.package_name
70
+ Array(hook.method_names).each do |method_name|
71
+ method_name = method_name.to_sym
72
+ base_cls = class_from_string.(class_name)
80
73
 
81
- hook_method = Hook::Method.new(config.package_for_method(method), hook_cls, method)
74
+ hook_method = lambda do |entry|
75
+ cls, method = entry
76
+ return false if config.never_hook?(cls, method)
82
77
 
83
- # Don't try and trace the AppMap methods or there will be
84
- # a stack overflow in the defined hook method.
85
- next if /\AAppMap[:\.]/.match?(hook_method.method_display_name)
78
+ Hook::Method.new(hook.package, cls, method).activate
79
+ end
86
80
 
87
- hook_method.activate
81
+ methods = []
82
+ methods << [ base_cls, base_cls.public_instance_method(method_name) ] rescue nil
83
+ if base_cls.respond_to?(:singleton_class)
84
+ methods << [ base_cls.singleton_class, base_cls.singleton_class.public_instance_method(method_name) ] rescue nil
85
+ end
86
+ methods.compact!
87
+ if methods.empty?
88
+ warn "Method #{method_name} not found on #{base_cls.name}"
89
+ else
90
+ methods.each(&hook_method)
91
+ end
88
92
  end
89
93
  end
94
+ end
95
+ end
90
96
 
91
- instance_methods.each(&hook.(cls))
92
- # NoMethodError: private method `singleton_class' called for Rack::MiniProfiler:Class
93
- begin
94
- class_methods.each(&hook.(cls.singleton_class)) if cls.respond_to?(:singleton_class)
95
- rescue NameError
96
- # NameError:
97
- # uninitialized constant Faraday::Connection
97
+ protected
98
+
99
+ def trace_class(trace_point)
100
+ path = trace_point.path
101
+
102
+ return if @notrace_paths.member?(path)
103
+
104
+ if config.path_enabled?(path)
105
+ location = trace_location(trace_point)
106
+ warn "Entering hook-enabled location #{location}" if Hook::LOG || Hook::LOG_HOOK
107
+ @trace_locations << location
108
+ unless @trace_end.enabled?
109
+ warn "Enabling hooking" if Hook::LOG || Hook::LOG_HOOK
110
+ @trace_end.enable
98
111
  end
112
+ else
113
+ @notrace_paths << path
99
114
  end
115
+ end
100
116
 
101
- tp.enable(&block)
117
+ def trace_location(trace_point)
118
+ [ trace_point.path, trace_point.lineno ].join(':')
102
119
  end
103
120
 
104
- # hook_builtins builds hooks for code that is built in to the Ruby standard library.
105
- # No TracePoint events are emitted for builtins, so a separate hooking mechanism is needed.
106
- def hook_builtins
107
- return unless self.class.lock_builtins
121
+ def trace_end(trace_point)
122
+ cls = trace_point.self
108
123
 
109
- class_from_string = lambda do |fq_class|
110
- fq_class.split('::').inject(Object) do |mod, class_name|
111
- mod.const_get(class_name)
124
+ instance_methods = cls.public_instance_methods(false) - OBJECT_INSTANCE_METHODS
125
+ # NoMethodError: private method `singleton_class' called for Rack::MiniProfiler:Class
126
+ class_methods = begin
127
+ if cls.respond_to?(:singleton_class)
128
+ cls.singleton_class.public_instance_methods(false) - instance_methods - OBJECT_STATIC_METHODS
129
+ else
130
+ []
112
131
  end
132
+ rescue NameError
133
+ []
113
134
  end
114
135
 
115
- Config::BUILTIN_METHODS.each do |class_name, hook|
116
- require hook.package.package_name if hook.package.package_name
117
- Array(hook.method_names).each do |method_name|
118
- method_name = method_name.to_sym
119
-
120
- cls = class_from_string.(class_name)
121
- method = \
122
- begin
123
- cls.instance_method(method_name)
124
- rescue NameError
125
- cls.method(method_name) rescue nil
126
- end
136
+ hook = lambda do |hook_cls|
137
+ lambda do |method_id|
138
+ # Don't try and trace the AppMap methods or there will be
139
+ # a stack overflow in the defined hook method.
140
+ next if %w[Marshal AppMap ActiveSupport].member?((hook_cls&.name || '').split('::')[0])
141
+
142
+ method = begin
143
+ hook_cls.public_instance_method(method_id)
144
+ rescue NameError
145
+ warn "AppMap: Method #{hook_cls} #{method.name} is not accessible" if LOG
146
+ next
147
+ end
127
148
 
128
- next if config.never_hook?(method)
149
+ warn "AppMap: Examining #{hook_cls} #{method.name}" if LOG
129
150
 
130
- if method
131
- Hook::Method.new(hook.package, cls, method).activate
132
- else
133
- warn "Method #{method_name} not found on #{cls.name}"
134
- end
151
+ disasm = RubyVM::InstructionSequence.disasm(method)
152
+ # Skip methods that have no instruction sequence, as they are obviously trivial.
153
+ next unless disasm
154
+
155
+ package = config.lookup_package(hook_cls, method)
156
+ next unless package
157
+
158
+ Hook::Method.new(package, hook_cls, method).activate
135
159
  end
136
160
  end
161
+
162
+ instance_methods.each(&hook.(cls))
163
+ begin
164
+ # NoMethodError: private method `singleton_class' called for Rack::MiniProfiler:Class
165
+ class_methods.each(&hook.(cls.singleton_class)) if cls.respond_to?(:singleton_class)
166
+ rescue NameError
167
+ # NameError:
168
+ # uninitialized constant Faraday::Connection
169
+ warn "NameError in #{__FILE__}: #{$!.message}"
170
+ end
171
+
172
+ location = @trace_locations.pop
173
+ warn "Leaving hook-enabled location #{location}" if Hook::LOG || Hook::LOG_HOOK
174
+ if @trace_locations.empty?
175
+ warn "Disabling hooking" if Hook::LOG || Hook::LOG_HOOK
176
+ @trace_end.disable
177
+ end
137
178
  end
138
179
  end
139
180
  end
@@ -76,7 +76,7 @@ module AppMap
76
76
  raise
77
77
  ensure
78
78
  with_disabled_hook.call do
79
- after_hook.call(self, call_event, start_time, return_value, exception)
79
+ after_hook.call(self, call_event, start_time, return_value, exception) if call_event
80
80
  end
81
81
  end
82
82
  end
@@ -87,18 +87,16 @@ module AppMap
87
87
  protected
88
88
 
89
89
  def before_hook(receiver, defined_class, args)
90
- require 'appmap/event'
91
- call_event = AppMap::Event::MethodCall.build_from_invocation(defined_class, hook_method, receiver, args)
92
- AppMap.tracing.record_event call_event, package: hook_package, defined_class: defined_class, method: hook_method
90
+ call_event = hook_package.handler_class.handle_call(defined_class, hook_method, receiver, args)
91
+ AppMap.tracing.record_event(call_event, package: hook_package, defined_class: defined_class, method: hook_method) if call_event
93
92
  [ call_event, TIME_NOW.call ]
94
93
  end
95
94
 
96
95
  def after_hook(_receiver, call_event, start_time, return_value, exception)
97
- require 'appmap/event'
98
96
  elapsed = TIME_NOW.call - start_time
99
- return_event = \
100
- AppMap::Event::MethodReturn.build_from_invocation call_event.id, elapsed, return_value, exception
101
- AppMap.tracing.record_event return_event
97
+ return_event = hook_package.handler_class.handle_return(call_event.id, elapsed, return_value, exception)
98
+ AppMap.tracing.record_event(return_event) if return_event
99
+ nil
102
100
  end
103
101
 
104
102
  def with_disabled_hook(&function)
@@ -67,7 +67,7 @@ module AppMap
67
67
 
68
68
  response = JSON.generate \
69
69
  version: AppMap::APPMAP_FORMAT_VERSION,
70
- classMap: AppMap.class_map(tracer.event_methods, include_source: AppMap.include_source?),
70
+ classMap: AppMap.class_map(tracer.event_methods),
71
71
  metadata: metadata,
72
72
  events: @events
73
73
 
@@ -26,8 +26,9 @@ module AppMap
26
26
  end
27
27
 
28
28
 
29
- def finish
29
+ def finish(exception)
30
30
  warn "Finishing recording of test #{test.class}.#{test.name}" if AppMap::Minitest::LOG
31
+ warn "Exception: #{exception}" if exception && AppMap::Minitest::LOG
31
32
 
32
33
  events = []
33
34
  AppMap.tracing.delete @trace
@@ -36,15 +37,17 @@ module AppMap
36
37
 
37
38
  AppMap::Minitest.add_event_methods @trace.event_methods
38
39
 
39
- class_map = AppMap.class_map(@trace.event_methods, include_source: AppMap.include_source?)
40
+ class_map = AppMap.class_map(@trace.event_methods)
40
41
 
41
42
  feature_group = test.class.name.underscore.split('_')[0...-1].join('_').capitalize
42
43
  feature_name = test.name.split('_')[1..-1].join(' ')
43
44
  scenario_name = [ feature_group, feature_name ].join(' ')
44
45
 
45
- AppMap::Minitest.save scenario_name,
46
- class_map,
47
- source_location,
46
+ AppMap::Minitest.save name: scenario_name,
47
+ class_map: class_map,
48
+ source_location: source_location,
49
+ test_status: exception ? 'failed' : 'succeeded',
50
+ exception: exception,
48
51
  events: events
49
52
  end
50
53
  end
@@ -63,11 +66,11 @@ module AppMap
63
66
  @recordings_by_test[test.object_id] = Recording.new(test, name)
64
67
  end
65
68
 
66
- def end_test(test)
69
+ def end_test(test, exception:)
67
70
  recording = @recordings_by_test.delete(test.object_id)
68
71
  return warn "No recording found for #{test}" unless recording
69
72
 
70
- recording.finish
73
+ recording.finish exception
71
74
  end
72
75
 
73
76
  def config
@@ -78,9 +81,9 @@ module AppMap
78
81
  @event_methods += event_methods
79
82
  end
80
83
 
81
- def save(example_name, class_map, source_location, events: nil, labels: nil)
84
+ def save(name:, class_map:, source_location:, test_status:, exception:, events:)
82
85
  metadata = AppMap::Minitest.metadata.tap do |m|
83
- m[:name] = example_name
86
+ m[:name] = name
84
87
  m[:source_location] = source_location
85
88
  m[:app] = AppMap.configuration.name
86
89
  m[:frameworks] ||= []
@@ -91,6 +94,13 @@ module AppMap
91
94
  m[:recorder] = {
92
95
  name: 'minitest'
93
96
  }
97
+ m[:test_status] = test_status
98
+ if exception
99
+ m[:exception] = {
100
+ class: exception.class.name,
101
+ message: exception.to_s
102
+ }
103
+ end
94
104
  end
95
105
 
96
106
  appmap = {
@@ -99,14 +109,9 @@ module AppMap
99
109
  classMap: class_map,
100
110
  events: events
101
111
  }.compact
102
- fname = AppMap::Util.scenario_filename(example_name)
112
+ fname = AppMap::Util.scenario_filename(name)
103
113
 
104
- File.write(File.join(APPMAP_OUTPUT_DIR, fname), JSON.generate(appmap))
105
- end
106
-
107
- def print_inventory
108
- class_map = AppMap.class_map(@event_methods)
109
- save 'Inventory', class_map, labels: %w[inventory]
114
+ AppMap::Util.write_appmap(File.join(APPMAP_OUTPUT_DIR, fname), JSON.generate(appmap))
110
115
  end
111
116
 
112
117
  def enabled?
@@ -115,9 +120,6 @@ module AppMap
115
120
 
116
121
  def run
117
122
  init
118
- at_exit do
119
- print_inventory
120
- end
121
123
  end
122
124
  end
123
125
  end
@@ -135,7 +137,7 @@ if AppMap::Minitest.enabled?
135
137
  begin
136
138
  run_without_hook
137
139
  ensure
138
- AppMap::Minitest.end_test self
140
+ AppMap::Minitest.end_test self, exception: $!
139
141
  end
140
142
  end
141
143
  end
@@ -8,12 +8,12 @@ module AppMap
8
8
  # appmap.subscribe subscribes to ActiveSupport Notifications so that they can be recorded as
9
9
  # AppMap events.
10
10
  initializer 'appmap.subscribe' do |_| # params: app
11
- require 'appmap/rails/sql_handler'
12
- require 'appmap/rails/request_handler'
13
- ActiveSupport::Notifications.subscribe 'sql.sequel', AppMap::Rails::SQLHandler.new
14
- ActiveSupport::Notifications.subscribe 'sql.active_record', AppMap::Rails::SQLHandler.new
11
+ require 'appmap/handler/rails/sql_handler'
12
+ require 'appmap/handler/rails/request_handler'
13
+ ActiveSupport::Notifications.subscribe 'sql.sequel', AppMap::Handler::Rails::SQLHandler.new
14
+ ActiveSupport::Notifications.subscribe 'sql.active_record', AppMap::Handler::Rails::SQLHandler.new
15
15
 
16
- AppMap::Rails::RequestHandler::HookMethod.new.activate
16
+ AppMap::Handler::Rails::RequestHandler::HookMethod.new.activate
17
17
  end
18
18
 
19
19
  # appmap.trace begins recording an AppMap trace and writes it to appmap.json.
data/lib/appmap/record.rb CHANGED
@@ -23,5 +23,5 @@ at_exit do
23
23
  'classMap' => AppMap.class_map(tracer.event_methods),
24
24
  'events' => events
25
25
  }
26
- File.write 'appmap.json', JSON.generate(appmap)
26
+ AppMap::Util.write_appmap('appmap.json', JSON.generate(appmap))
27
27
  end
data/lib/appmap/rspec.rb CHANGED
@@ -94,8 +94,9 @@ module AppMap
94
94
  result
95
95
  end
96
96
 
97
- def finish
97
+ def finish(exception)
98
98
  warn "Finishing recording of example #{example}" if AppMap::RSpec::LOG
99
+ warn "Exception: #{exception}" if exception && AppMap::RSpec::LOG
99
100
 
100
101
  events = []
101
102
  AppMap.tracing.delete @trace
@@ -104,7 +105,7 @@ module AppMap
104
105
 
105
106
  AppMap::RSpec.add_event_methods @trace.event_methods
106
107
 
107
- class_map = AppMap.class_map(@trace.event_methods, include_source: AppMap.include_source?)
108
+ class_map = AppMap.class_map(@trace.event_methods)
108
109
 
109
110
  description = []
110
111
  scope = ScopeExample.new(example)
@@ -127,9 +128,11 @@ module AppMap
127
128
 
128
129
  full_description = normalize.call(description.join(' '))
129
130
 
130
- AppMap::RSpec.save full_description,
131
- class_map,
132
- source_location,
131
+ AppMap::RSpec.save name: full_description,
132
+ class_map: class_map,
133
+ source_location: source_location,
134
+ test_status: exception ? 'failed' : 'succeeded',
135
+ exception: exception,
133
136
  events: events
134
137
  end
135
138
  end
@@ -148,11 +151,11 @@ module AppMap
148
151
  @recordings_by_example[example.object_id] = Recording.new(example)
149
152
  end
150
153
 
151
- def end_spec(example)
154
+ def end_spec(example, exception:)
152
155
  recording = @recordings_by_example.delete(example.object_id)
153
156
  return warn "No recording found for #{example}" unless recording
154
157
 
155
- recording.finish
158
+ recording.finish exception
156
159
  end
157
160
 
158
161
  def config
@@ -163,12 +166,11 @@ module AppMap
163
166
  @event_methods += event_methods
164
167
  end
165
168
 
166
- def save(example_name, class_map, source_location, events: nil, labels: nil)
169
+ def save(name:, class_map:, source_location:, test_status:, exception:, events:)
167
170
  metadata = AppMap::RSpec.metadata.tap do |m|
168
- m[:name] = example_name
171
+ m[:name] = name
169
172
  m[:source_location] = source_location
170
173
  m[:app] = AppMap.configuration.name
171
- m[:labels] = labels if labels
172
174
  m[:frameworks] ||= []
173
175
  m[:frameworks] << {
174
176
  name: 'rspec',
@@ -177,6 +179,13 @@ module AppMap
177
179
  m[:recorder] = {
178
180
  name: 'rspec'
179
181
  }
182
+ m[:test_status] = test_status
183
+ if exception
184
+ m[:exception] = {
185
+ class: exception.class.name,
186
+ message: exception.to_s
187
+ }
188
+ end
180
189
  end
181
190
 
182
191
  appmap = {
@@ -185,14 +194,9 @@ module AppMap
185
194
  classMap: class_map,
186
195
  events: events
187
196
  }.compact
188
- fname = AppMap::Util.scenario_filename(example_name)
189
-
190
- File.write(File.join(APPMAP_OUTPUT_DIR, fname), JSON.generate(appmap))
191
- end
197
+ fname = AppMap::Util.scenario_filename(name)
192
198
 
193
- def print_inventory
194
- class_map = AppMap.class_map(@event_methods)
195
- save 'Inventory', class_map, labels: %w[inventory]
199
+ AppMap::Util.write_appmap(File.join(APPMAP_OUTPUT_DIR, fname), JSON.generate(appmap))
196
200
  end
197
201
 
198
202
  def enabled?
@@ -201,9 +205,6 @@ module AppMap
201
205
 
202
206
  def run
203
207
  init
204
- at_exit do
205
- print_inventory
206
- end
207
208
  end
208
209
  end
209
210
  end
@@ -225,7 +226,7 @@ if AppMap::RSpec.enabled?
225
226
  begin
226
227
  instance_exec(&fn)
227
228
  ensure
228
- AppMap::RSpec.end_spec example
229
+ AppMap::RSpec.end_spec example, exception: $!
229
230
  end
230
231
  end
231
232
  end