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.
- checksums.yaml +4 -4
- data/.releaserc.yml +11 -0
- data/.travis.yml +33 -2
- data/CHANGELOG.md +44 -0
- data/README.md +74 -16
- data/README_CI.md +29 -0
- data/Rakefile +4 -2
- data/appmap.gemspec +5 -3
- data/lib/appmap.rb +3 -7
- data/lib/appmap/class_map.rb +11 -22
- data/lib/appmap/command/record.rb +1 -1
- data/lib/appmap/config.rb +180 -67
- data/lib/appmap/cucumber.rb +1 -1
- data/lib/appmap/event.rb +46 -27
- data/lib/appmap/handler/function.rb +19 -0
- data/lib/appmap/handler/net_http.rb +107 -0
- data/lib/appmap/handler/rails/request_handler.rb +124 -0
- data/lib/appmap/handler/rails/sql_handler.rb +152 -0
- data/lib/appmap/handler/rails/template.rb +149 -0
- data/lib/appmap/hook.rb +111 -70
- data/lib/appmap/hook/method.rb +6 -8
- data/lib/appmap/middleware/remote_recording.rb +1 -1
- data/lib/appmap/minitest.rb +22 -20
- data/lib/appmap/railtie.rb +5 -5
- data/lib/appmap/record.rb +1 -1
- data/lib/appmap/rspec.rb +22 -21
- data/lib/appmap/trace.rb +47 -6
- data/lib/appmap/util.rb +47 -2
- data/lib/appmap/version.rb +2 -2
- data/package-lock.json +3 -3
- data/release.sh +17 -0
- data/spec/abstract_controller_base_spec.rb +140 -34
- data/spec/class_map_spec.rb +5 -13
- data/spec/config_spec.rb +33 -1
- data/spec/fixtures/hook/custom_instance_method.rb +11 -0
- data/spec/fixtures/hook/method_named_call.rb +11 -0
- data/spec/fixtures/rails5_users_app/Gemfile +7 -3
- data/spec/fixtures/rails5_users_app/app/controllers/api/users_controller.rb +2 -0
- data/spec/fixtures/rails5_users_app/app/controllers/users_controller.rb +9 -1
- data/spec/fixtures/rails5_users_app/config/application.rb +2 -0
- data/spec/fixtures/rails5_users_app/create_app +8 -2
- data/spec/fixtures/rails5_users_app/spec/controllers/users_controller_api_spec.rb +13 -0
- data/spec/fixtures/rails5_users_app/spec/controllers/users_controller_spec.rb +2 -2
- data/spec/fixtures/rails5_users_app/spec/rails_helper.rb +3 -9
- data/spec/fixtures/rails6_users_app/Gemfile +5 -4
- data/spec/fixtures/rails6_users_app/app/controllers/api/users_controller.rb +1 -0
- data/spec/fixtures/rails6_users_app/app/controllers/users_controller.rb +9 -1
- data/spec/fixtures/rails6_users_app/config/application.rb +2 -0
- data/spec/fixtures/rails6_users_app/create_app +8 -2
- data/spec/fixtures/rails6_users_app/spec/controllers/users_controller_api_spec.rb +13 -0
- data/spec/fixtures/rails6_users_app/spec/controllers/users_controller_spec.rb +2 -2
- data/spec/fixtures/rails6_users_app/spec/rails_helper.rb +3 -9
- data/spec/hook_spec.rb +143 -22
- data/spec/record_net_http_spec.rb +160 -0
- data/spec/record_sql_rails_pg_spec.rb +1 -1
- data/spec/spec_helper.rb +16 -0
- data/test/expectations/openssl_test_key_sign1.json +2 -4
- data/test/gem_test.rb +1 -1
- data/test/rspec_test.rb +0 -13
- metadata +20 -14
- data/exe/appmap +0 -154
- data/lib/appmap/rails/request_handler.rb +0 -109
- data/lib/appmap/rails/sql_handler.rb +0 -150
- 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
|
45
|
+
def enable(&block)
|
41
46
|
require 'appmap/hook/method'
|
42
47
|
|
43
48
|
hook_builtins
|
44
49
|
|
45
|
-
|
46
|
-
|
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
|
-
|
53
|
+
@trace_begin.enable(&block)
|
54
|
+
end
|
70
55
|
|
71
|
-
|
72
|
-
|
73
|
-
|
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
|
-
|
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
|
-
|
78
|
-
|
79
|
-
|
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 =
|
74
|
+
hook_method = lambda do |entry|
|
75
|
+
cls, method = entry
|
76
|
+
return false if config.never_hook?(cls, method)
|
82
77
|
|
83
|
-
|
84
|
-
|
85
|
-
next if /\AAppMap[:\.]/.match?(hook_method.method_display_name)
|
78
|
+
Hook::Method.new(hook.package, cls, method).activate
|
79
|
+
end
|
86
80
|
|
87
|
-
|
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
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
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
|
-
|
117
|
+
def trace_location(trace_point)
|
118
|
+
[ trace_point.path, trace_point.lineno ].join(':')
|
102
119
|
end
|
103
120
|
|
104
|
-
|
105
|
-
|
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
|
-
|
110
|
-
|
111
|
-
|
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
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
method =
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
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
|
-
|
149
|
+
warn "AppMap: Examining #{hook_cls} #{method.name}" if LOG
|
129
150
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
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
|
data/lib/appmap/hook/method.rb
CHANGED
@@ -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
|
-
|
91
|
-
|
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
|
-
|
101
|
-
|
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
|
70
|
+
classMap: AppMap.class_map(tracer.event_methods),
|
71
71
|
metadata: metadata,
|
72
72
|
events: @events
|
73
73
|
|
data/lib/appmap/minitest.rb
CHANGED
@@ -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
|
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(
|
84
|
+
def save(name:, class_map:, source_location:, test_status:, exception:, events:)
|
82
85
|
metadata = AppMap::Minitest.metadata.tap do |m|
|
83
|
-
m[: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(
|
112
|
+
fname = AppMap::Util.scenario_filename(name)
|
103
113
|
|
104
|
-
|
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
|
data/lib/appmap/railtie.rb
CHANGED
@@ -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
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
|
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(
|
169
|
+
def save(name:, class_map:, source_location:, test_status:, exception:, events:)
|
167
170
|
metadata = AppMap::RSpec.metadata.tap do |m|
|
168
|
-
m[: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(
|
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
|
-
|
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
|