appmap 0.28.0 → 0.34.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/CHANGELOG.md +32 -0
- data/README.md +54 -2
- data/Rakefile +1 -1
- data/appmap.gemspec +1 -0
- data/lib/appmap.rb +25 -14
- data/lib/appmap/algorithm/stats.rb +2 -1
- data/lib/appmap/class_map.rb +26 -28
- data/lib/appmap/config.rb +115 -0
- data/lib/appmap/event.rb +28 -19
- data/lib/appmap/hook.rb +88 -129
- data/lib/appmap/hook/method.rb +78 -0
- data/lib/appmap/metadata.rb +1 -1
- data/lib/appmap/minitest.rb +141 -0
- data/lib/appmap/open.rb +57 -0
- data/lib/appmap/rails/action_handler.rb +7 -7
- data/lib/appmap/rails/sql_handler.rb +10 -8
- data/lib/appmap/record.rb +27 -0
- data/lib/appmap/rspec.rb +2 -2
- data/lib/appmap/trace.rb +17 -9
- data/lib/appmap/util.rb +19 -0
- data/lib/appmap/version.rb +1 -1
- data/package-lock.json +3 -3
- data/spec/abstract_controller4_base_spec.rb +1 -1
- data/spec/abstract_controller_base_spec.rb +9 -2
- data/spec/config_spec.rb +3 -3
- data/spec/fixtures/hook/compare.rb +7 -0
- data/spec/fixtures/hook/singleton_method.rb +54 -0
- data/spec/hook_spec.rb +280 -53
- data/spec/open_spec.rb +19 -0
- data/spec/record_sql_rails_pg_spec.rb +56 -33
- data/spec/util_spec.rb +1 -1
- data/test/cli_test.rb +14 -4
- data/test/fixtures/minitest_recorder/Gemfile +5 -0
- data/test/fixtures/minitest_recorder/appmap.yml +3 -0
- data/test/fixtures/minitest_recorder/lib/hello.rb +5 -0
- data/test/fixtures/minitest_recorder/test/hello_test.rb +12 -0
- data/test/fixtures/openssl_recorder/Gemfile +3 -0
- data/test/fixtures/openssl_recorder/appmap.yml +3 -0
- data/test/fixtures/openssl_recorder/lib/openssl_cert_sign.rb +94 -0
- data/test/fixtures/openssl_recorder/lib/openssl_encrypt.rb +34 -0
- data/test/fixtures/openssl_recorder/lib/openssl_key_sign.rb +28 -0
- data/test/fixtures/process_recorder/appmap.yml +3 -0
- data/test/fixtures/process_recorder/hello.rb +9 -0
- data/test/minitest_test.rb +38 -0
- data/test/openssl_test.rb +203 -0
- data/test/record_process_test.rb +35 -0
- data/test/test_helper.rb +1 -0
- metadata +38 -4
- data/spec/fixtures/hook/class_method.rb +0 -17
data/lib/appmap/event.rb
CHANGED
@@ -15,24 +15,15 @@ module AppMap
|
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
|
-
MethodEventStruct = Struct.new(:id, :event, :
|
18
|
+
MethodEventStruct = Struct.new(:id, :event, :thread_id)
|
19
19
|
|
20
20
|
class MethodEvent < MethodEventStruct
|
21
21
|
LIMIT = 100
|
22
22
|
|
23
23
|
class << self
|
24
|
-
def build_from_invocation(me, event_type
|
25
|
-
singleton = method.owner.singleton_class?
|
26
|
-
|
24
|
+
def build_from_invocation(me, event_type)
|
27
25
|
me.id = AppMap::Event.next_id_counter
|
28
26
|
me.event = event_type
|
29
|
-
me.defined_class = defined_class
|
30
|
-
me.method_id = method.name.to_s
|
31
|
-
path = method.source_location[0]
|
32
|
-
path = path[Dir.pwd.length + 1..-1] if path.index(Dir.pwd) == 0
|
33
|
-
me.path = path
|
34
|
-
me.lineno = method.source_location[1]
|
35
|
-
me.static = singleton
|
36
27
|
me.thread_id = Thread.current.object_id
|
37
28
|
end
|
38
29
|
|
@@ -61,16 +52,25 @@ module AppMap
|
|
61
52
|
(value_string||'')[0...LIMIT].encode('utf-8', invalid: :replace, undef: :replace, replace: '_')
|
62
53
|
end
|
63
54
|
end
|
64
|
-
|
65
|
-
alias static? static
|
66
55
|
end
|
67
56
|
|
68
57
|
class MethodCall < MethodEvent
|
69
|
-
attr_accessor :parameters, :receiver
|
58
|
+
attr_accessor :defined_class, :method_id, :path, :lineno, :parameters, :receiver, :static
|
70
59
|
|
71
60
|
class << self
|
72
61
|
def build_from_invocation(mc = MethodCall.new, defined_class, method, receiver, arguments)
|
73
62
|
mc.tap do
|
63
|
+
static = receiver.is_a?(Module)
|
64
|
+
mc.defined_class = defined_class
|
65
|
+
mc.method_id = method.name.to_s
|
66
|
+
if method.source_location
|
67
|
+
path = method.source_location[0]
|
68
|
+
path = path[Dir.pwd.length + 1..-1] if path.index(Dir.pwd) == 0
|
69
|
+
mc.path = path
|
70
|
+
mc.lineno = method.source_location[1]
|
71
|
+
else
|
72
|
+
mc.path = [ defined_class, static ? '.' : '#', method.name ].join
|
73
|
+
end
|
74
74
|
mc.parameters = method.parameters.map.with_index do |method_param, idx|
|
75
75
|
param_type, param_name = method_param
|
76
76
|
param_name ||= 'arg'
|
@@ -88,28 +88,37 @@ module AppMap
|
|
88
88
|
object_id: receiver.__id__,
|
89
89
|
value: display_string(receiver)
|
90
90
|
}
|
91
|
-
|
91
|
+
mc.static = static
|
92
|
+
MethodEvent.build_from_invocation(mc, :call)
|
92
93
|
end
|
93
94
|
end
|
94
95
|
end
|
95
96
|
|
96
97
|
def to_h
|
97
98
|
super.tap do |h|
|
99
|
+
h[:defined_class] = defined_class
|
100
|
+
h[:method_id] = method_id
|
101
|
+
h[:path] = path
|
102
|
+
h[:lineno] = lineno
|
103
|
+
h[:static] = static
|
98
104
|
h[:parameters] = parameters
|
99
105
|
h[:receiver] = receiver
|
106
|
+
h.delete_if { |_, v| v.nil? }
|
100
107
|
end
|
101
108
|
end
|
109
|
+
|
110
|
+
alias static? static
|
102
111
|
end
|
103
112
|
|
104
113
|
class MethodReturnIgnoreValue < MethodEvent
|
105
114
|
attr_accessor :parent_id, :elapsed
|
106
115
|
|
107
116
|
class << self
|
108
|
-
def build_from_invocation(mr = MethodReturnIgnoreValue.new,
|
117
|
+
def build_from_invocation(mr = MethodReturnIgnoreValue.new, parent_id, elapsed)
|
109
118
|
mr.tap do |_|
|
110
119
|
mr.parent_id = parent_id
|
111
120
|
mr.elapsed = elapsed
|
112
|
-
MethodEvent.build_from_invocation(mr, :return
|
121
|
+
MethodEvent.build_from_invocation(mr, :return)
|
113
122
|
end
|
114
123
|
end
|
115
124
|
end
|
@@ -126,7 +135,7 @@ module AppMap
|
|
126
135
|
attr_accessor :return_value, :exceptions
|
127
136
|
|
128
137
|
class << self
|
129
|
-
def build_from_invocation(mr = MethodReturn.new,
|
138
|
+
def build_from_invocation(mr = MethodReturn.new, parent_id, elapsed, return_value, exception)
|
130
139
|
mr.tap do |_|
|
131
140
|
if return_value
|
132
141
|
mr.return_value = {
|
@@ -152,7 +161,7 @@ module AppMap
|
|
152
161
|
|
153
162
|
mr.exceptions = exceptions
|
154
163
|
end
|
155
|
-
MethodReturnIgnoreValue.build_from_invocation(mr,
|
164
|
+
MethodReturnIgnoreValue.build_from_invocation(mr, parent_id, elapsed)
|
156
165
|
end
|
157
166
|
end
|
158
167
|
end
|
data/lib/appmap/hook.rb
CHANGED
@@ -4,152 +4,111 @@ require 'English'
|
|
4
4
|
|
5
5
|
module AppMap
|
6
6
|
class Hook
|
7
|
-
LOG =
|
8
|
-
|
9
|
-
Package = Struct.new(:path, :exclude) do
|
10
|
-
def to_h
|
11
|
-
{
|
12
|
-
path: path,
|
13
|
-
exclude: exclude.blank? ? nil : exclude
|
14
|
-
}.compact
|
15
|
-
end
|
16
|
-
end
|
7
|
+
LOG = (ENV['DEBUG'] == 'true')
|
17
8
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
def load_from_file(config_file_name)
|
22
|
-
require 'yaml'
|
23
|
-
load YAML.safe_load(::File.read(config_file_name))
|
24
|
-
end
|
9
|
+
class << self
|
10
|
+
def lock_builtins
|
11
|
+
return if @builtins_hooked
|
25
12
|
|
26
|
-
|
27
|
-
def load(config_data)
|
28
|
-
packages = (config_data['packages'] || []).map do |package|
|
29
|
-
Package.new(package['path'], package['exclude'] || [])
|
30
|
-
end
|
31
|
-
Config.new config_data['name'], packages
|
32
|
-
end
|
13
|
+
@builtins_hooked = true
|
33
14
|
end
|
34
15
|
|
35
|
-
|
36
|
-
|
16
|
+
# Return the class, separator ('.' or '#'), and method name for
|
17
|
+
# the given method.
|
18
|
+
def qualify_method_name(method)
|
19
|
+
if method.owner.singleton_class?
|
20
|
+
# Singleton class names can take two forms:
|
21
|
+
# #<Class:Foo> or
|
22
|
+
# #<Class:#<Bar:0x0123ABC>>. Retrieve the name of
|
23
|
+
# the class from the string.
|
24
|
+
#
|
25
|
+
# (There really isn't a better way to do this. The
|
26
|
+
# singleton's reference to the class it was created
|
27
|
+
# from is stored in an instance variable named
|
28
|
+
# '__attached__'. It doesn't have the '@' prefix, so
|
29
|
+
# it's internal only, and not accessible from user
|
30
|
+
# code.)
|
31
|
+
class_name = /#<Class:((#<(?<cls>.*?):)|((?<cls>.*?)>))/.match(method.owner.to_s)['cls']
|
32
|
+
[ class_name, '.', method.name ]
|
33
|
+
else
|
34
|
+
[ method.owner.name, '#', method.name ]
|
35
|
+
end
|
37
36
|
end
|
37
|
+
end
|
38
38
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
packages: packages.map(&:to_h)
|
43
|
-
}
|
44
|
-
end
|
39
|
+
attr_reader :config
|
40
|
+
def initialize(config)
|
41
|
+
@config = config
|
45
42
|
end
|
46
43
|
|
47
|
-
|
44
|
+
# Observe class loading and hook all methods which match the config.
|
45
|
+
def enable &block
|
46
|
+
require 'appmap/hook/method'
|
48
47
|
|
49
|
-
|
50
|
-
# Observe class loading and hook all methods which match the config.
|
51
|
-
def hook(config = AppMap.configure)
|
52
|
-
package_include_paths = config.packages.map(&:path)
|
53
|
-
package_exclude_paths = config.packages.map do |pkg|
|
54
|
-
pkg.exclude.map do |exclude|
|
55
|
-
File.join(pkg.path, exclude)
|
56
|
-
end
|
57
|
-
end.flatten
|
48
|
+
hook_builtins
|
58
49
|
|
59
|
-
|
60
|
-
|
61
|
-
call_event = AppMap::Event::MethodCall.build_from_invocation(defined_class, method, receiver, args)
|
62
|
-
AppMap.tracing.record_event call_event, defined_class: defined_class, method: method
|
63
|
-
[ call_event, Time.now ]
|
64
|
-
end
|
50
|
+
tp = TracePoint.new(:end) do |trace_point|
|
51
|
+
cls = trace_point.self
|
65
52
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
53
|
+
instance_methods = cls.public_instance_methods(false)
|
54
|
+
class_methods = cls.singleton_class.public_instance_methods(false) - instance_methods
|
55
|
+
|
56
|
+
hook = lambda do |hook_cls|
|
57
|
+
lambda do |method_id|
|
58
|
+
method = hook_cls.public_instance_method(method_id)
|
59
|
+
hook_method = Hook::Method.new(hook_cls, method)
|
73
60
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
61
|
+
warn "AppMap: Examining #{hook_method.method_display_name}" if LOG
|
62
|
+
|
63
|
+
disasm = RubyVM::InstructionSequence.disasm(method)
|
64
|
+
# Skip methods that have no instruction sequence, as they are obviously trivial.
|
65
|
+
next unless disasm
|
66
|
+
|
67
|
+
# Don't try and trace the AppMap methods or there will be
|
68
|
+
# a stack overflow in the defined hook method.
|
69
|
+
next if /\AAppMap[:\.]/.match?(hook_method.method_display_name)
|
70
|
+
|
71
|
+
next unless \
|
72
|
+
config.always_hook?(hook_method.defined_class, method.name) ||
|
73
|
+
config.included_by_location?(method)
|
74
|
+
|
75
|
+
hook_method.activate
|
82
76
|
end
|
83
77
|
end
|
84
78
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
# Singleton class name is like: #<Class:<(.*)>>
|
112
|
-
class_name = method.owner.to_s['#<Class:<'.length-1..-2]
|
113
|
-
[ class_name, '.' ]
|
114
|
-
else
|
115
|
-
[ method.owner.name, '#' ]
|
116
|
-
end
|
117
|
-
|
118
|
-
method_display_name = "#{defined_class}#{method_symbol}#{method.name}"
|
119
|
-
# Don't try and trace the tracing method or there will be a stack overflow
|
120
|
-
# in the defined hook method.
|
121
|
-
next if method_display_name == "AppMap.tracing"
|
122
|
-
|
123
|
-
warn "AppMap: Hooking #{method_display_name}" if LOG
|
124
|
-
|
125
|
-
cls.define_method method_id do |*args, &block|
|
126
|
-
base_method = method.bind(self).to_proc
|
127
|
-
|
128
|
-
hook_disabled = Thread.current[HOOK_DISABLE_KEY]
|
129
|
-
enabled = true if !hook_disabled && AppMap.tracing.enabled?
|
130
|
-
return base_method.call(*args, &block) unless enabled
|
131
|
-
|
132
|
-
call_event, start_time = with_disabled_hook.call do
|
133
|
-
before_hook.call(defined_class, method, self, args)
|
134
|
-
end
|
135
|
-
return_value = nil
|
136
|
-
exception = nil
|
137
|
-
begin
|
138
|
-
return_value = base_method.call(*args, &block)
|
139
|
-
rescue
|
140
|
-
exception = $ERROR_INFO
|
141
|
-
raise
|
142
|
-
ensure
|
143
|
-
with_disabled_hook.call do
|
144
|
-
after_hook.call(call_event, defined_class, method, start_time, return_value, exception)
|
145
|
-
end
|
146
|
-
end
|
147
|
-
end
|
79
|
+
instance_methods.each(&hook.(cls))
|
80
|
+
class_methods.each(&hook.(cls.singleton_class))
|
81
|
+
end
|
82
|
+
|
83
|
+
tp.enable(&block)
|
84
|
+
end
|
85
|
+
|
86
|
+
def hook_builtins
|
87
|
+
return unless self.class.lock_builtins
|
88
|
+
|
89
|
+
class_from_string = lambda do |fq_class|
|
90
|
+
fq_class.split('::').inject(Object) do |mod, class_name|
|
91
|
+
mod.const_get(class_name)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
Config::BUILTIN_METHODS.each do |class_name, hook|
|
96
|
+
require hook.package.package_name if hook.package.package_name
|
97
|
+
Array(hook.method_names).each do |method_name|
|
98
|
+
method_name = method_name.to_sym
|
99
|
+
cls = class_from_string.(class_name)
|
100
|
+
method = \
|
101
|
+
begin
|
102
|
+
cls.instance_method(method_name)
|
103
|
+
rescue NameError
|
104
|
+
cls.method(method_name) rescue nil
|
148
105
|
end
|
149
|
-
end
|
150
106
|
|
151
|
-
|
152
|
-
|
107
|
+
if method
|
108
|
+
Hook::Method.new(cls, method).activate
|
109
|
+
else
|
110
|
+
warn "Method #{method_name} not found on #{cls.name}"
|
111
|
+
end
|
153
112
|
end
|
154
113
|
end
|
155
114
|
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module AppMap
|
2
|
+
class Hook
|
3
|
+
class Method
|
4
|
+
attr_reader :hook_class, :hook_method, :defined_class, :method_display_name
|
5
|
+
|
6
|
+
HOOK_DISABLE_KEY = 'AppMap::Hook.disable'
|
7
|
+
private_constant :HOOK_DISABLE_KEY
|
8
|
+
|
9
|
+
def initialize(hook_class, hook_method)
|
10
|
+
@hook_class = hook_class
|
11
|
+
@hook_method = hook_method
|
12
|
+
@defined_class, method_symbol = Hook.qualify_method_name(@hook_method)
|
13
|
+
@method_display_name = [@defined_class, method_symbol, @hook_method.name].join
|
14
|
+
end
|
15
|
+
|
16
|
+
def activate
|
17
|
+
warn "AppMap: Hooking #{method_display_name}" if Hook::LOG
|
18
|
+
|
19
|
+
hook_method = self.hook_method
|
20
|
+
before_hook = self.method(:before_hook)
|
21
|
+
after_hook = self.method(:after_hook)
|
22
|
+
with_disabled_hook = self.method(:with_disabled_hook)
|
23
|
+
|
24
|
+
hook_class.define_method hook_method.name do |*args, &block|
|
25
|
+
instance_method = hook_method.bind(self).to_proc
|
26
|
+
|
27
|
+
hook_disabled = Thread.current[HOOK_DISABLE_KEY]
|
28
|
+
enabled = true if !hook_disabled && AppMap.tracing.enabled?
|
29
|
+
return instance_method.call(*args, &block) unless enabled
|
30
|
+
|
31
|
+
call_event, start_time = with_disabled_hook.() do
|
32
|
+
before_hook.(self, args)
|
33
|
+
end
|
34
|
+
return_value = nil
|
35
|
+
exception = nil
|
36
|
+
begin
|
37
|
+
return_value = instance_method.(*args, &block)
|
38
|
+
rescue
|
39
|
+
exception = $ERROR_INFO
|
40
|
+
raise
|
41
|
+
ensure
|
42
|
+
with_disabled_hook.() do
|
43
|
+
after_hook.(call_event, start_time, return_value, exception)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
protected
|
50
|
+
|
51
|
+
def before_hook(receiver, args)
|
52
|
+
require 'appmap/event'
|
53
|
+
call_event = AppMap::Event::MethodCall.build_from_invocation(defined_class, hook_method, receiver, args)
|
54
|
+
AppMap.tracing.record_event call_event, defined_class: defined_class, method: hook_method
|
55
|
+
[ call_event, Time.now ]
|
56
|
+
end
|
57
|
+
|
58
|
+
def after_hook(call_event, start_time, return_value, exception)
|
59
|
+
require 'appmap/event'
|
60
|
+
elapsed = Time.now - start_time
|
61
|
+
return_event = \
|
62
|
+
AppMap::Event::MethodReturn.build_from_invocation call_event.id, elapsed, return_value, exception
|
63
|
+
AppMap.tracing.record_event return_event
|
64
|
+
end
|
65
|
+
|
66
|
+
def with_disabled_hook(&fn)
|
67
|
+
# Don't record functions, such as to_s and inspect, that might be called
|
68
|
+
# by the fn. Otherwise there can be a stack overflow.
|
69
|
+
Thread.current[HOOK_DISABLE_KEY] = true
|
70
|
+
begin
|
71
|
+
fn.call
|
72
|
+
ensure
|
73
|
+
Thread.current[HOOK_DISABLE_KEY] = false
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
data/lib/appmap/metadata.rb
CHANGED
@@ -0,0 +1,141 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'appmap/util'
|
4
|
+
|
5
|
+
module AppMap
|
6
|
+
# Integration of AppMap with Minitest. When enabled with APPMAP=true, the AppMap tracer will
|
7
|
+
# be activated around each test.
|
8
|
+
module Minitest
|
9
|
+
APPMAP_OUTPUT_DIR = 'tmp/appmap/minitest'
|
10
|
+
LOG = false
|
11
|
+
|
12
|
+
def self.metadata
|
13
|
+
AppMap.detect_metadata
|
14
|
+
end
|
15
|
+
|
16
|
+
Recording = Struct.new(:test) do
|
17
|
+
def initialize(test)
|
18
|
+
super
|
19
|
+
|
20
|
+
warn "Starting recording of test #{test.class}.#{test.name}" if AppMap::Minitest::LOG
|
21
|
+
@trace = AppMap.tracing.trace
|
22
|
+
end
|
23
|
+
|
24
|
+
def finish
|
25
|
+
warn "Finishing recording of test #{test.class}.#{test.name}" if AppMap::Minitest::LOG
|
26
|
+
|
27
|
+
events = []
|
28
|
+
AppMap.tracing.delete @trace
|
29
|
+
|
30
|
+
events << @trace.next_event.to_h while @trace.event?
|
31
|
+
|
32
|
+
AppMap::Minitest.add_event_methods @trace.event_methods
|
33
|
+
|
34
|
+
class_map = AppMap.class_map(@trace.event_methods)
|
35
|
+
|
36
|
+
feature_group = test.class.name.underscore.split('_')[0...-1].join('_').capitalize
|
37
|
+
feature_name = test.name.split('_')[1..-1].join(' ')
|
38
|
+
scenario_name = [ feature_group, feature_name ].join(' ')
|
39
|
+
|
40
|
+
AppMap::Minitest.save scenario_name,
|
41
|
+
class_map,
|
42
|
+
events: events,
|
43
|
+
feature_name: feature_name,
|
44
|
+
feature_group_name: feature_group
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
@recordings_by_test = {}
|
49
|
+
@event_methods = Set.new
|
50
|
+
|
51
|
+
class << self
|
52
|
+
def init
|
53
|
+
warn 'Configuring AppMap recorder for Minitest'
|
54
|
+
|
55
|
+
FileUtils.mkdir_p APPMAP_OUTPUT_DIR
|
56
|
+
end
|
57
|
+
|
58
|
+
def begin_test(test)
|
59
|
+
@recordings_by_test[test.object_id] = Recording.new(test)
|
60
|
+
end
|
61
|
+
|
62
|
+
def end_test(test)
|
63
|
+
recording = @recordings_by_test.delete(test.object_id)
|
64
|
+
return warn "No recording found for #{test}" unless recording
|
65
|
+
|
66
|
+
recording.finish
|
67
|
+
end
|
68
|
+
|
69
|
+
def config
|
70
|
+
@config or raise "AppMap is not configured"
|
71
|
+
end
|
72
|
+
|
73
|
+
def add_event_methods(event_methods)
|
74
|
+
@event_methods += event_methods
|
75
|
+
end
|
76
|
+
|
77
|
+
def save(example_name, class_map, events: nil, feature_name: nil, feature_group_name: nil, labels: nil)
|
78
|
+
metadata = AppMap::Minitest.metadata.tap do |m|
|
79
|
+
m[:name] = example_name
|
80
|
+
m[:app] = AppMap.configuration.name
|
81
|
+
m[:feature] = feature_name if feature_name
|
82
|
+
m[:feature_group] = feature_group_name if feature_group_name
|
83
|
+
m[:frameworks] ||= []
|
84
|
+
m[:frameworks] << {
|
85
|
+
name: 'minitest',
|
86
|
+
version: Gem.loaded_specs['minitest']&.version&.to_s
|
87
|
+
}
|
88
|
+
m[:recorder] = {
|
89
|
+
name: 'minitest'
|
90
|
+
}
|
91
|
+
end
|
92
|
+
|
93
|
+
appmap = {
|
94
|
+
version: AppMap::APPMAP_FORMAT_VERSION,
|
95
|
+
metadata: metadata,
|
96
|
+
classMap: class_map,
|
97
|
+
events: events
|
98
|
+
}.compact
|
99
|
+
fname = AppMap::Util.scenario_filename(example_name)
|
100
|
+
|
101
|
+
File.write(File.join(APPMAP_OUTPUT_DIR, fname), JSON.generate(appmap))
|
102
|
+
end
|
103
|
+
|
104
|
+
def print_inventory
|
105
|
+
class_map = AppMap.class_map(@event_methods)
|
106
|
+
save 'Inventory', class_map, labels: %w[inventory]
|
107
|
+
end
|
108
|
+
|
109
|
+
def enabled?
|
110
|
+
ENV['APPMAP'] == 'true'
|
111
|
+
end
|
112
|
+
|
113
|
+
def run
|
114
|
+
init
|
115
|
+
at_exit do
|
116
|
+
print_inventory
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
if AppMap::Minitest.enabled?
|
124
|
+
require 'appmap'
|
125
|
+
require 'minitest/test'
|
126
|
+
|
127
|
+
class ::Minitest::Test
|
128
|
+
alias run_without_hook run
|
129
|
+
|
130
|
+
def run
|
131
|
+
AppMap::Minitest.begin_test self
|
132
|
+
begin
|
133
|
+
run_without_hook
|
134
|
+
ensure
|
135
|
+
AppMap::Minitest.end_test self
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
AppMap::Minitest.run
|
141
|
+
end
|