appmap 0.102.2 → 0.103.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/.rubocop.yml +6 -0
- data/.standard.yml +1 -0
- data/.travis.yml +2 -2
- data/CHANGELOG.md +17 -0
- data/appmap.gemspec +35 -36
- data/lib/appmap/agent.rb +28 -24
- data/lib/appmap/class_map.rb +36 -31
- data/lib/appmap/config.rb +132 -110
- data/lib/appmap/cucumber.rb +25 -25
- data/lib/appmap/detect_enabled.rb +30 -28
- data/lib/appmap/gem_hooks/activejob.yml +0 -1
- data/lib/appmap/hook/method/ruby2.rb +19 -8
- data/lib/appmap/hook/method/ruby3.rb +20 -13
- data/lib/appmap/hook/method.rb +27 -27
- data/lib/appmap/hook/record_around.rb +77 -0
- data/lib/appmap/hook.rb +74 -44
- data/lib/appmap/metadata.rb +6 -18
- data/lib/appmap/middleware/remote_recording.rb +26 -27
- data/lib/appmap/minitest.rb +27 -27
- data/lib/appmap/rspec.rb +37 -37
- data/lib/appmap/trace.rb +14 -12
- data/lib/appmap/util.rb +71 -61
- data/lib/appmap/version.rb +1 -1
- metadata +5 -16
data/lib/appmap/hook.rb
CHANGED
@@ -1,14 +1,20 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require_relative
|
5
|
-
|
3
|
+
require "English"
|
4
|
+
require_relative "hook_log"
|
5
|
+
|
6
|
+
# rubocop:disable Metrics/ClassLength
|
7
|
+
# rubocop:disable Metrics/MethodLength
|
8
|
+
# rubocop:disable Metrics/BlockLength
|
9
|
+
# rubocop:disable Metrics/AbcSize
|
10
|
+
# rubocop:disable Complexity/CyclomaticComplexity
|
11
|
+
# rubocop:disable Complexity/PerceivedComplexity
|
6
12
|
module AppMap
|
7
13
|
class Hook
|
8
14
|
OBJECT_INSTANCE_METHODS = %i[! != !~ <=> == === =~ __id__ __send__ class clone define_singleton_method display dup
|
9
|
-
|
15
|
+
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
16
|
OBJECT_STATIC_METHODS = %i[! != !~ < <= <=> == === =~ > >= __id__ __send__ alias_method allocate ancestors attr
|
11
|
-
|
17
|
+
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
|
12
18
|
SLOW_PACKAGE_THRESHOLD = 0.001
|
13
19
|
|
14
20
|
@unbound_method_arity = ::UnboundMethod.instance_method(:arity)
|
@@ -39,9 +45,9 @@ module AppMap
|
|
39
45
|
def qualify_method_name(method)
|
40
46
|
if method.owner.singleton_class?
|
41
47
|
class_name = singleton_method_owner_name(method)
|
42
|
-
[class_name,
|
48
|
+
[class_name, ".", method.name]
|
43
49
|
else
|
44
|
-
[method.owner.name,
|
50
|
+
[method.owner.name, "#", method.name]
|
45
51
|
end
|
46
52
|
end
|
47
53
|
end
|
@@ -55,7 +61,7 @@ module AppMap
|
|
55
61
|
|
56
62
|
# Observe class loading and hook all methods which match the config.
|
57
63
|
def enable(&block)
|
58
|
-
require
|
64
|
+
require "appmap/hook/method"
|
59
65
|
|
60
66
|
hook_builtins
|
61
67
|
|
@@ -66,7 +72,7 @@ module AppMap
|
|
66
72
|
@module_load_times = Hash.new { |memo, k| memo[k] = 0 }
|
67
73
|
@slow_packages = Set.new
|
68
74
|
|
69
|
-
if ENV[
|
75
|
+
if ENV["APPMAP_PROFILE_HOOK"] == "true"
|
70
76
|
dump_times = lambda do
|
71
77
|
@module_load_times
|
72
78
|
.keys
|
@@ -80,7 +86,7 @@ module AppMap
|
|
80
86
|
end
|
81
87
|
end
|
82
88
|
|
83
|
-
at_exit
|
89
|
+
at_exit(&dump_times)
|
84
90
|
Thread.new do
|
85
91
|
while true
|
86
92
|
dump_times.call
|
@@ -102,7 +108,7 @@ module AppMap
|
|
102
108
|
hooks_by_class.each do |class_name, hooks|
|
103
109
|
Array(hooks).each do |hook|
|
104
110
|
HookLog.builtin class_name do
|
105
|
-
if builtin && hook.package.require_name && hook.package.require_name !=
|
111
|
+
if builtin && hook.package.require_name && hook.package.require_name != "ruby"
|
106
112
|
begin
|
107
113
|
require hook.package.require_name
|
108
114
|
rescue
|
@@ -110,39 +116,59 @@ module AppMap
|
|
110
116
|
next
|
111
117
|
end
|
112
118
|
end
|
113
|
-
|
119
|
+
|
114
120
|
begin
|
115
121
|
base_cls = Object.const_get class_name
|
116
122
|
rescue NameError
|
117
123
|
HookLog.load_error class_name, "Class #{class_name} not found in global scope" if HookLog.enabled?
|
118
124
|
next
|
119
125
|
end
|
120
|
-
|
126
|
+
|
121
127
|
Array(hook.method_names).each do |method_name|
|
122
128
|
method_name = method_name.to_sym
|
123
|
-
|
129
|
+
|
124
130
|
hook_method = lambda do |entry|
|
125
131
|
cls, method = entry
|
126
132
|
next if config.never_hook?(cls, method)
|
127
|
-
|
133
|
+
|
128
134
|
hook.package.handler_class.new(hook.package, cls, method).activate
|
129
135
|
end
|
130
|
-
|
136
|
+
|
131
137
|
methods = []
|
132
138
|
# irb(main):001:0> Kernel.public_instance_method(:system)
|
133
139
|
# (irb):1:in `public_instance_method': method `system' for module `Kernel' is private (NameError)
|
134
140
|
if base_cls == Kernel
|
135
|
-
|
141
|
+
begin
|
142
|
+
methods << [base_cls, base_cls.instance_method(method_name)]
|
143
|
+
rescue
|
144
|
+
nil
|
145
|
+
end
|
146
|
+
end
|
147
|
+
begin
|
148
|
+
methods << [base_cls, base_cls.public_instance_method(method_name)]
|
149
|
+
rescue
|
150
|
+
nil
|
151
|
+
end
|
152
|
+
begin
|
153
|
+
methods << [base_cls, base_cls.protected_instance_method(method_name)]
|
154
|
+
rescue
|
155
|
+
nil
|
136
156
|
end
|
137
|
-
methods << [base_cls, base_cls.public_instance_method(method_name)] rescue nil
|
138
|
-
methods << [base_cls, base_cls.protected_instance_method(method_name)] rescue nil
|
139
157
|
if base_cls.respond_to?(:singleton_class)
|
140
|
-
|
141
|
-
|
158
|
+
begin
|
159
|
+
methods << [base_cls.singleton_class, base_cls.singleton_class.public_instance_method(method_name)]
|
160
|
+
rescue
|
161
|
+
nil
|
162
|
+
end
|
163
|
+
begin
|
164
|
+
methods << [base_cls.singleton_class, base_cls.singleton_class.protected_instance_method(method_name)]
|
165
|
+
rescue
|
166
|
+
nil
|
167
|
+
end
|
142
168
|
end
|
143
169
|
methods.compact!
|
144
170
|
if methods.empty?
|
145
|
-
HookLog.load_error [
|
171
|
+
HookLog.load_error [base_cls.name, method_name].join("[#.]"), "Method #{method_name} not found on #{base_cls.name}" if HookLog.enabled?
|
146
172
|
else
|
147
173
|
methods.each(&hook_method)
|
148
174
|
end
|
@@ -152,13 +178,13 @@ module AppMap
|
|
152
178
|
end
|
153
179
|
end
|
154
180
|
|
155
|
-
hook_loaded_code.(config.builtin_hooks, true)
|
181
|
+
hook_loaded_code.call(config.builtin_hooks, true)
|
156
182
|
end
|
157
183
|
|
158
184
|
protected
|
159
185
|
|
160
186
|
def trace_location(trace_point)
|
161
|
-
[trace_point.path, trace_point.lineno].join(
|
187
|
+
[trace_point.path, trace_point.lineno].join(":")
|
162
188
|
end
|
163
189
|
|
164
190
|
def trace_end(trace_point)
|
@@ -168,7 +194,7 @@ module AppMap
|
|
168
194
|
path = trace_point.path
|
169
195
|
enabled = !@notrace_paths.member?(path) && config.path_enabled?(path)
|
170
196
|
unless enabled
|
171
|
-
HookLog.log
|
197
|
+
HookLog.log "Not hooking - path is not enabled" if HookLog.enabled?
|
172
198
|
@notrace_paths << path
|
173
199
|
next
|
174
200
|
end
|
@@ -178,31 +204,31 @@ module AppMap
|
|
178
204
|
instance_methods = cls.public_instance_methods(false) + cls.protected_instance_methods(false) - OBJECT_INSTANCE_METHODS
|
179
205
|
# NoMethodError: private method `singleton_class' called for Rack::MiniProfiler:Class
|
180
206
|
class_methods = begin
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
[]
|
185
|
-
end
|
186
|
-
rescue NameError
|
207
|
+
if cls.respond_to?(:singleton_class)
|
208
|
+
cls.singleton_class.public_instance_methods(false) + cls.singleton_class.protected_instance_methods(false) - instance_methods - OBJECT_STATIC_METHODS
|
209
|
+
else
|
187
210
|
[]
|
188
211
|
end
|
212
|
+
rescue NameError
|
213
|
+
[]
|
214
|
+
end
|
189
215
|
|
190
216
|
hook = lambda do |hook_cls|
|
191
217
|
lambda do |method_id|
|
192
218
|
method = begin
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
219
|
+
hook_cls.instance_method(method_id)
|
220
|
+
rescue NameError
|
221
|
+
HookLog.load_error [hook_cls, method_id].join("[#.]"), "Method #{hook_cls} #{method_id} is not accessible: #{$!}" if HookLog.enabled?
|
222
|
+
next
|
223
|
+
end
|
198
224
|
|
199
|
-
|
225
|
+
hook_config = config.lookup_hook_config(hook_cls, method)
|
200
226
|
# doing this check first returned early in 98.7% of cases in sample_app_6th_ed
|
201
|
-
next unless
|
227
|
+
next unless hook_config
|
202
228
|
|
203
229
|
# Don't try and trace the AppMap methods or there will be
|
204
230
|
# a stack overflow in the defined hook method.
|
205
|
-
next if %w[Marshal AppMap ActiveSupport].member?((hook_cls&.name ||
|
231
|
+
next if %w[Marshal AppMap ActiveSupport].member?((hook_cls&.name || "").split("::")[0])
|
206
232
|
|
207
233
|
next if method_id == :call
|
208
234
|
|
@@ -215,15 +241,19 @@ module AppMap
|
|
215
241
|
# TODO: Figure out how to tell the difference?
|
216
242
|
next unless disasm
|
217
243
|
|
218
|
-
|
244
|
+
record_around = Config::RECORD_AROUND_LABELS.find { |label| hook_config.labels.include?(label) }
|
245
|
+
|
246
|
+
HookLog.log "Detected labels: #{hook_config.labels.join(", ")} (record_around: #{record_around})" if !hook_config.labels.empty? && HookLog.enabled?
|
247
|
+
|
248
|
+
hook_config.package.handler_class.new(hook_config.package, hook_cls, method, record_around: record_around).activate
|
219
249
|
end
|
220
250
|
end
|
221
251
|
|
222
252
|
start = Time.now
|
223
|
-
instance_methods.each(&hook.(cls))
|
253
|
+
instance_methods.each(&hook.call(cls))
|
224
254
|
begin
|
225
255
|
# NoMethodError: private method `singleton_class' called for Rack::MiniProfiler:Class
|
226
|
-
class_methods.each(&hook.(cls.singleton_class)) if cls.respond_to?(:singleton_class)
|
256
|
+
class_methods.each(&hook.call(cls.singleton_class)) if cls.respond_to?(:singleton_class)
|
227
257
|
rescue NameError
|
228
258
|
# NameError:
|
229
259
|
# uninitialized constant Faraday::Connection
|
@@ -231,12 +261,12 @@ module AppMap
|
|
231
261
|
end
|
232
262
|
elapsed = Time.now - start
|
233
263
|
if location.index(Bundler.bundle_path.to_s) == 0
|
234
|
-
package_tokens = location[Bundler.bundle_path.to_s.length + 1..-1].split(
|
264
|
+
package_tokens = location[Bundler.bundle_path.to_s.length + 1..-1].split("/")
|
235
265
|
@module_load_times[package_tokens[1]] += elapsed
|
236
266
|
else
|
237
267
|
file_path = location[Dir.pwd.length + 1..-1]
|
238
268
|
if file_path
|
239
|
-
location = file_path.split(
|
269
|
+
location = file_path.split("/", 3)[0..1].join("/")
|
240
270
|
@module_load_times[location] += elapsed
|
241
271
|
end
|
242
272
|
end
|
data/lib/appmap/metadata.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "appmap/util"
|
4
4
|
|
5
5
|
module AppMap
|
6
6
|
module Metadata
|
@@ -9,12 +9,12 @@ module AppMap
|
|
9
9
|
{
|
10
10
|
app: AppMap.configuration.name,
|
11
11
|
language: {
|
12
|
-
name:
|
12
|
+
name: "ruby",
|
13
13
|
engine: RUBY_ENGINE,
|
14
14
|
version: RUBY_VERSION
|
15
15
|
},
|
16
16
|
client: {
|
17
|
-
name:
|
17
|
+
name: "appmap",
|
18
18
|
url: AppMap::URL,
|
19
19
|
version: AppMap::VERSION
|
20
20
|
}
|
@@ -22,7 +22,7 @@ module AppMap
|
|
22
22
|
if defined?(::Rails) && defined?(::Rails.version)
|
23
23
|
m[:frameworks] ||= []
|
24
24
|
m[:frameworks] << {
|
25
|
-
name:
|
25
|
+
name: "rails",
|
26
26
|
version: ::Rails.version
|
27
27
|
}
|
28
28
|
end
|
@@ -33,30 +33,18 @@ module AppMap
|
|
33
33
|
protected
|
34
34
|
|
35
35
|
def git_available
|
36
|
-
@git_available = system(
|
36
|
+
@git_available = system("git status 2>&1 > /dev/null") if @git_available.nil?
|
37
37
|
end
|
38
38
|
|
39
39
|
def git_metadata
|
40
40
|
git_repo = `git config --get remote.origin.url`.strip
|
41
41
|
git_branch = `git rev-parse --abbrev-ref HEAD`.strip
|
42
42
|
git_sha = `git rev-parse HEAD`.strip
|
43
|
-
git_status = `git status -s`.split("\n").map(&:strip)
|
44
|
-
git_last_annotated_tag = `git describe --abbrev=0 2>/dev/null`.strip
|
45
|
-
git_last_annotated_tag = nil if Util.blank?(git_last_annotated_tag)
|
46
|
-
git_last_tag = `git describe --abbrev=0 --tags 2>/dev/null`.strip
|
47
|
-
git_last_tag = nil if Util.blank?(git_last_tag)
|
48
|
-
git_commits_since_last_annotated_tag = `git describe`.strip =~ /-(\d+)-(\w+)$/[1] rescue 0 if git_last_annotated_tag
|
49
|
-
git_commits_since_last_tag = `git describe --tags`.strip =~ /-(\d+)-(\w+)$/[1] rescue 0 if git_last_tag
|
50
43
|
|
51
44
|
{
|
52
45
|
repository: git_repo,
|
53
46
|
branch: git_branch,
|
54
|
-
commit: git_sha
|
55
|
-
status: git_status,
|
56
|
-
git_last_annotated_tag: git_last_annotated_tag,
|
57
|
-
git_last_tag: git_last_tag,
|
58
|
-
git_commits_since_last_annotated_tag: git_commits_since_last_annotated_tag,
|
59
|
-
git_commits_since_last_tag: git_commits_since_last_tag
|
47
|
+
commit: git_sha
|
60
48
|
}
|
61
49
|
end
|
62
50
|
end
|
@@ -6,7 +6,7 @@ module AppMap
|
|
6
6
|
# It can also be enabled to emit an AppMap for each request.
|
7
7
|
class RemoteRecording
|
8
8
|
def initialize(app)
|
9
|
-
require
|
9
|
+
require "json"
|
10
10
|
|
11
11
|
@app = app
|
12
12
|
end
|
@@ -23,18 +23,18 @@ module AppMap
|
|
23
23
|
end
|
24
24
|
|
25
25
|
def ws_start_recording
|
26
|
-
return [
|
26
|
+
return [409, "Recording is already in progress"] if @tracer
|
27
27
|
|
28
28
|
@events = []
|
29
29
|
@tracer = AppMap.tracing.trace
|
30
30
|
@event_thread = Thread.new { event_loop }
|
31
31
|
@event_thread.abort_on_exception = true
|
32
32
|
|
33
|
-
[
|
33
|
+
[200]
|
34
34
|
end
|
35
35
|
|
36
36
|
def ws_stop_recording(req)
|
37
|
-
return [
|
37
|
+
return [404, "No recording is in progress"] unless @tracer
|
38
38
|
|
39
39
|
tracer = @tracer
|
40
40
|
@tracer = nil
|
@@ -50,7 +50,7 @@ module AppMap
|
|
50
50
|
is_control_command_event = lambda do |event|
|
51
51
|
event[:event] == :call &&
|
52
52
|
event[:http_server_request] &&
|
53
|
-
event[:http_server_request][:path_info] ==
|
53
|
+
event[:http_server_request][:path_info] == "/_appmap/record"
|
54
54
|
end
|
55
55
|
control_command_events = @events.select(&is_control_command_event)
|
56
56
|
|
@@ -63,8 +63,8 @@ module AppMap
|
|
63
63
|
|
64
64
|
metadata = AppMap.detect_metadata
|
65
65
|
metadata[:recorder] = {
|
66
|
-
name:
|
67
|
-
type:
|
66
|
+
name: "remote_recording",
|
67
|
+
type: "remote"
|
68
68
|
}
|
69
69
|
|
70
70
|
response = JSON.generate \
|
@@ -73,7 +73,7 @@ module AppMap
|
|
73
73
|
metadata: metadata,
|
74
74
|
events: @events
|
75
75
|
|
76
|
-
[
|
76
|
+
[200, response]
|
77
77
|
end
|
78
78
|
|
79
79
|
def call(env)
|
@@ -82,7 +82,7 @@ module AppMap
|
|
82
82
|
# 0
|
83
83
|
|
84
84
|
req = Rack::Request.new(env)
|
85
|
-
return handle_record_request(req) if AppMap.recording_enabled?(:remote) && req.path ==
|
85
|
+
return handle_record_request(req) if AppMap.recording_enabled?(:remote) && req.path == "/_appmap/record"
|
86
86
|
|
87
87
|
start_time = Time.now
|
88
88
|
# Support multi-threaded web server such as Puma by recording each thread
|
@@ -100,20 +100,19 @@ module AppMap
|
|
100
100
|
event_fields = events.map(&:keys).flatten.map(&:to_sym).uniq.sort
|
101
101
|
return unless %i[http_server_request http_server_response].all? { |field| event_fields.include?(field) }
|
102
102
|
|
103
|
-
path = req.path.gsub(/\/{2,}/,
|
104
|
-
appmap_name = "#{req.request_method} #{path} (#{status}) - #{start_time.strftime(
|
105
|
-
appmap_file_name = AppMap::Util.scenario_filename([
|
106
|
-
output_dir = File.join(AppMap
|
103
|
+
path = req.path.gsub(/\/{2,}/, "/") # Double slashes have been observed
|
104
|
+
appmap_name = "#{req.request_method} #{path} (#{status}) - #{start_time.strftime("%T.%L")}"
|
105
|
+
appmap_file_name = AppMap::Util.scenario_filename([start_time.to_f, req.url].join("_"))
|
106
|
+
output_dir = File.join(AppMap.output_dir, "requests")
|
107
107
|
appmap_file_path = File.join(output_dir, appmap_file_name)
|
108
108
|
|
109
109
|
metadata = AppMap.detect_metadata
|
110
110
|
metadata[:name] = appmap_name
|
111
|
-
metadata[:timestamp] = start_time.to_f
|
112
111
|
metadata[:recorder] = {
|
113
|
-
name:
|
114
|
-
type:
|
112
|
+
name: "rack",
|
113
|
+
type: "requests"
|
115
114
|
}
|
116
|
-
|
115
|
+
|
117
116
|
appmap = {
|
118
117
|
version: AppMap::APPMAP_FORMAT_VERSION,
|
119
118
|
classMap: AppMap.class_map(tracer.event_methods),
|
@@ -124,36 +123,36 @@ module AppMap
|
|
124
123
|
FileUtils.mkdir_p(output_dir)
|
125
124
|
File.write(appmap_file_path, JSON.generate(appmap))
|
126
125
|
|
127
|
-
headers[
|
128
|
-
headers[
|
126
|
+
headers["AppMap-Name"] = File.expand_path(appmap_name)
|
127
|
+
headers["AppMap-File-Name"] = File.expand_path(appmap_file_path)
|
129
128
|
end
|
130
129
|
|
131
130
|
@app.call(env).tap(&record_request)
|
132
131
|
end
|
133
132
|
|
134
133
|
def recording_state
|
135
|
-
[
|
134
|
+
[200, JSON.generate({enabled: recording?})]
|
136
135
|
end
|
137
136
|
|
138
137
|
def handle_record_request(req)
|
139
|
-
method = req.env[
|
138
|
+
method = req.env["REQUEST_METHOD"]
|
140
139
|
|
141
140
|
status, body = \
|
142
|
-
if method.eql?(
|
141
|
+
if method.eql?("GET")
|
143
142
|
recording_state
|
144
|
-
elsif method.eql?(
|
143
|
+
elsif method.eql?("POST")
|
145
144
|
ws_start_recording
|
146
|
-
elsif method.eql?(
|
145
|
+
elsif method.eql?("DELETE")
|
147
146
|
ws_stop_recording(req)
|
148
147
|
else
|
149
|
-
[
|
148
|
+
[404, ""]
|
150
149
|
end
|
151
150
|
|
152
|
-
[status, {
|
151
|
+
[status, {"Content-Type" => "application/json"}, [body || ""]]
|
153
152
|
end
|
154
153
|
|
155
154
|
def html_response?(headers)
|
156
|
-
headers[
|
155
|
+
headers["Content-Type"] && headers["Content-Type"] =~ /html/
|
157
156
|
end
|
158
157
|
|
159
158
|
def record_all_requests?
|
data/lib/appmap/minitest.rb
CHANGED
@@ -1,18 +1,18 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative
|
4
|
-
require_relative
|
5
|
-
require_relative
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
3
|
+
require_relative "../appmap"
|
4
|
+
require_relative "util"
|
5
|
+
require_relative "detect_enabled"
|
6
|
+
require "fileutils"
|
7
|
+
require "active_support"
|
8
|
+
require "active_support/core_ext"
|
9
9
|
|
10
10
|
module AppMap
|
11
11
|
# Integration of AppMap with Minitest. When enabled with APPMAP=true, the AppMap tracer will
|
12
12
|
# be activated around each test.
|
13
13
|
module Minitest
|
14
|
-
APPMAP_OUTPUT_DIR =
|
15
|
-
LOG = (ENV[
|
14
|
+
APPMAP_OUTPUT_DIR = File.join(AppMap.output_dir, "minitest")
|
15
|
+
LOG = (ENV["APPMAP_DEBUG"] == "true" || ENV["DEBUG"] == "true")
|
16
16
|
|
17
17
|
def self.metadata
|
18
18
|
AppMap.detect_metadata
|
@@ -28,13 +28,13 @@ module AppMap
|
|
28
28
|
|
29
29
|
def source_location
|
30
30
|
location = test.method(test_name).source_location
|
31
|
-
[
|
31
|
+
[Util.normalize_path(location.first), location.last].join(":")
|
32
32
|
end
|
33
33
|
|
34
34
|
def finish(failures, exception)
|
35
35
|
failed = failures.any? || exception
|
36
36
|
if AppMap::Minitest::LOG
|
37
|
-
warn "Finishing recording of #{failed ?
|
37
|
+
warn "Finishing recording of #{failed ? "failed " : ""} test #{test.class}.#{test.name}"
|
38
38
|
end
|
39
39
|
warn "Exception: #{exception}" if exception && AppMap::Minitest::LOG
|
40
40
|
|
@@ -53,17 +53,17 @@ module AppMap
|
|
53
53
|
|
54
54
|
class_map = AppMap.class_map(@trace.event_methods)
|
55
55
|
|
56
|
-
feature_group = test.class.name.underscore.split(
|
57
|
-
feature_name = test.name.split(
|
58
|
-
scenario_name = [feature_group, feature_name].join(
|
56
|
+
feature_group = test.class.name.underscore.split("_")[0...-1].join("_").capitalize
|
57
|
+
feature_name = test.name.split("_")[1..-1].join(" ")
|
58
|
+
scenario_name = [feature_group, feature_name].join(" ")
|
59
59
|
|
60
60
|
AppMap::Minitest.save name: scenario_name,
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
61
|
+
class_map: class_map,
|
62
|
+
source_location: source_location,
|
63
|
+
test_status: failed ? "failed" : "succeeded",
|
64
|
+
test_failure: test_failure,
|
65
|
+
exception: exception,
|
66
|
+
events: events
|
67
67
|
end
|
68
68
|
end
|
69
69
|
|
@@ -96,7 +96,7 @@ module AppMap
|
|
96
96
|
end
|
97
97
|
|
98
98
|
def config
|
99
|
-
@config or raise
|
99
|
+
@config or raise "AppMap is not configured"
|
100
100
|
end
|
101
101
|
|
102
102
|
def add_event_methods(event_methods)
|
@@ -110,12 +110,12 @@ module AppMap
|
|
110
110
|
m[:app] = AppMap.configuration.name
|
111
111
|
m[:frameworks] ||= []
|
112
112
|
m[:frameworks] << {
|
113
|
-
name:
|
114
|
-
version: Gem.loaded_specs[
|
113
|
+
name: "minitest",
|
114
|
+
version: Gem.loaded_specs["minitest"]&.version&.to_s
|
115
115
|
}
|
116
116
|
m[:recorder] = {
|
117
|
-
name:
|
118
|
-
type:
|
117
|
+
name: "minitest",
|
118
|
+
type: "tests"
|
119
119
|
}
|
120
120
|
m[:test_status] = test_status
|
121
121
|
m[:test_failure] = test_failure if test_failure
|
@@ -145,11 +145,11 @@ module AppMap
|
|
145
145
|
end
|
146
146
|
|
147
147
|
if AppMap::Minitest.enabled?
|
148
|
-
require
|
149
|
-
require
|
148
|
+
require "appmap"
|
149
|
+
require "minitest/test"
|
150
150
|
|
151
151
|
class ::Minitest::Test
|
152
|
-
|
152
|
+
alias_method :run_without_hook, :run
|
153
153
|
|
154
154
|
def run
|
155
155
|
AppMap::Minitest.begin_test self, name
|