appmap 0.27.0 → 0.33.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/.gitignore +1 -3
- data/CHANGELOG.md +37 -0
- data/README.md +170 -29
- data/Rakefile +1 -1
- data/appmap.gemspec +1 -0
- data/exe/appmap +3 -1
- data/lib/appmap.rb +54 -38
- data/lib/appmap/algorithm/stats.rb +2 -1
- data/lib/appmap/class_map.rb +21 -28
- data/lib/appmap/command/record.rb +2 -61
- data/lib/appmap/config.rb +89 -0
- data/lib/appmap/cucumber.rb +89 -0
- data/lib/appmap/event.rb +28 -19
- data/lib/appmap/hook.rb +56 -128
- data/lib/appmap/hook/method.rb +78 -0
- data/lib/appmap/metadata.rb +62 -0
- data/lib/appmap/middleware/remote_recording.rb +2 -6
- 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/railtie.rb +2 -2
- data/lib/appmap/record.rb +27 -0
- data/lib/appmap/rspec.rb +9 -37
- data/lib/appmap/trace.rb +18 -10
- data/lib/appmap/util.rb +59 -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/fixtures/rails_users_app/Gemfile +1 -0
- data/spec/fixtures/rails_users_app/features/api_users.feature +13 -0
- data/spec/fixtures/rails_users_app/features/support/env.rb +4 -0
- data/spec/fixtures/rails_users_app/features/support/hooks.rb +11 -0
- data/spec/fixtures/rails_users_app/features/support/steps.rb +18 -0
- data/spec/hook_spec.rb +228 -53
- data/spec/open_spec.rb +19 -0
- data/spec/rails_spec_helper.rb +2 -0
- data/spec/record_sql_rails_pg_spec.rb +56 -33
- data/spec/rspec_feature_metadata_spec.rb +2 -0
- data/spec/spec_helper.rb +4 -0
- data/spec/util_spec.rb +21 -0
- data/test/cli_test.rb +4 -4
- data/test/cucumber_test.rb +72 -0
- data/test/fixtures/cucumber4_recorder/Gemfile +5 -0
- data/test/fixtures/cucumber4_recorder/appmap.yml +3 -0
- data/test/fixtures/cucumber4_recorder/features/say_hello.feature +5 -0
- data/test/fixtures/cucumber4_recorder/features/support/env.rb +5 -0
- data/test/fixtures/cucumber4_recorder/features/support/hooks.rb +11 -0
- data/test/fixtures/cucumber4_recorder/features/support/steps.rb +9 -0
- data/test/fixtures/cucumber4_recorder/lib/hello.rb +7 -0
- data/test/fixtures/cucumber_recorder/Gemfile +5 -0
- data/test/fixtures/cucumber_recorder/appmap.yml +3 -0
- data/test/fixtures/cucumber_recorder/features/say_hello.feature +5 -0
- data/test/fixtures/cucumber_recorder/features/support/env.rb +5 -0
- data/test/fixtures/cucumber_recorder/features/support/hooks.rb +11 -0
- data/test/fixtures/cucumber_recorder/features/support/steps.rb +9 -0
- data/test/fixtures/cucumber_recorder/lib/hello.rb +7 -0
- 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/process_recorder/appmap.yml +3 -0
- data/test/fixtures/process_recorder/hello.rb +9 -0
- data/test/minitest_test.rb +38 -0
- data/test/record_process_test.rb +35 -0
- data/test/test_helper.rb +1 -0
- metadata +55 -3
- data/spec/fixtures/hook/class_method.rb +0 -17
data/lib/appmap/hook.rb
CHANGED
@@ -4,149 +4,77 @@ require 'English'
|
|
4
4
|
|
5
5
|
module AppMap
|
6
6
|
class Hook
|
7
|
-
LOG =
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
7
|
+
LOG = (ENV['DEBUG'] == 'true')
|
8
|
+
|
9
|
+
class << self
|
10
|
+
# Return the class, separator ('.' or '#'), and method name for
|
11
|
+
# the given method.
|
12
|
+
def qualify_method_name(method)
|
13
|
+
if method.owner.singleton_class?
|
14
|
+
# Singleton class names can take two forms:
|
15
|
+
# #<Class:Foo> or
|
16
|
+
# #<Class:#<Bar:0x0123ABC>>. Retrieve the name of
|
17
|
+
# the class from the string.
|
18
|
+
#
|
19
|
+
# (There really isn't a better way to do this. The
|
20
|
+
# singleton's reference to the class it was created
|
21
|
+
# from is stored in an instance variable named
|
22
|
+
# '__attached__'. It doesn't have the '@' prefix, so
|
23
|
+
# it's internal only, and not accessible from user
|
24
|
+
# code.)
|
25
|
+
class_name = /#<Class:((#<(?<cls>.*?):)|((?<cls>.*?)>))/.match(method.owner.to_s)['cls']
|
26
|
+
[ class_name, '.', method.name ]
|
27
|
+
else
|
28
|
+
[ method.owner.name, '#', method.name ]
|
29
|
+
end
|
15
30
|
end
|
16
31
|
end
|
17
32
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
require 'yaml'
|
23
|
-
load YAML.safe_load(::File.read(config_file_name))
|
24
|
-
end
|
33
|
+
attr_reader :config
|
34
|
+
def initialize(config)
|
35
|
+
@config = config
|
36
|
+
end
|
25
37
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
Package.new(package['path'], package['exclude'] || [])
|
30
|
-
end
|
31
|
-
Config.new config_data['name'], packages
|
32
|
-
end
|
33
|
-
end
|
38
|
+
# Observe class loading and hook all methods which match the config.
|
39
|
+
def enable &block
|
40
|
+
require 'appmap/hook/method'
|
34
41
|
|
35
|
-
|
36
|
-
|
37
|
-
end
|
42
|
+
tp = TracePoint.new(:end) do |trace_point|
|
43
|
+
cls = trace_point.self
|
38
44
|
|
39
|
-
|
40
|
-
|
41
|
-
name: name,
|
42
|
-
packages: packages.map(&:to_h)
|
43
|
-
}
|
44
|
-
end
|
45
|
-
end
|
45
|
+
instance_methods = cls.public_instance_methods(false)
|
46
|
+
class_methods = cls.singleton_class.public_instance_methods(false) - instance_methods
|
46
47
|
|
47
|
-
|
48
|
+
hook = lambda do |hook_cls|
|
49
|
+
lambda do |method_id|
|
50
|
+
next if method_id.to_s =~ /_hooked_by_appmap$/
|
48
51
|
|
49
|
-
|
50
|
-
|
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
|
52
|
+
method = hook_cls.public_instance_method(method_id)
|
53
|
+
hook_method = Hook::Method.new(hook_cls, method)
|
58
54
|
|
59
|
-
|
60
|
-
require 'appmap/event'
|
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
|
55
|
+
warn "AppMap: Examining #{hook_method.method_display_name}" if LOG
|
65
56
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
return_event = AppMap::Event::MethodReturn.build_from_invocation \
|
70
|
-
defined_class, method, call_event.id, elapsed, return_value, exception
|
71
|
-
AppMap.tracing.record_event return_event
|
72
|
-
end
|
57
|
+
disasm = RubyVM::InstructionSequence.disasm(method)
|
58
|
+
# Skip methods that have no instruction sequence, as they are obviously trivial.
|
59
|
+
next unless disasm
|
73
60
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
Thread.current[HOOK_DISABLE_KEY] = true
|
78
|
-
begin
|
79
|
-
fn.call
|
80
|
-
ensure
|
81
|
-
Thread.current[HOOK_DISABLE_KEY] = false
|
82
|
-
end
|
83
|
-
end
|
61
|
+
# Don't try and trace the AppMap methods or there will be
|
62
|
+
# a stack overflow in the defined hook method.
|
63
|
+
next if /\AAppMap[:\.]/.match?(hook_method.method_display_name)
|
84
64
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
instance_methods = cls.public_instance_methods(false)
|
89
|
-
class_methods = cls.singleton_class.public_instance_methods(false) - instance_methods
|
90
|
-
|
91
|
-
hook_method = lambda do |cls|
|
92
|
-
lambda do |method_id|
|
93
|
-
next if method_id.to_s =~ /_hooked_by_appmap$/
|
94
|
-
|
95
|
-
method = cls.public_instance_method(method_id)
|
96
|
-
location = method.source_location
|
97
|
-
location_file, = location
|
98
|
-
next unless location_file
|
99
|
-
|
100
|
-
location_file = location_file[Dir.pwd.length + 1..-1] if location_file.index(Dir.pwd) == 0
|
101
|
-
match = package_include_paths.find { |p| location_file.index(p) == 0 }
|
102
|
-
match &&= !package_exclude_paths.find { |p| location_file.index(p) }
|
103
|
-
next unless match
|
104
|
-
|
105
|
-
disasm = RubyVM::InstructionSequence.disasm(method)
|
106
|
-
# Skip methods that have no instruction sequence, as they are obviously trivial.
|
107
|
-
next unless disasm
|
108
|
-
|
109
|
-
defined_class, method_symbol = \
|
110
|
-
if method.owner.singleton_class?
|
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
|
-
warn "AppMap: Hooking #{defined_class}#{method_symbol}#{method.name}" if LOG
|
119
|
-
|
120
|
-
cls.define_method method_id do |*args, &block|
|
121
|
-
base_method = method.bind(self).to_proc
|
122
|
-
|
123
|
-
hook_disabled = Thread.current[HOOK_DISABLE_KEY]
|
124
|
-
enabled = true if !hook_disabled && AppMap.tracing.enabled?
|
125
|
-
return base_method.call(*args, &block) unless enabled
|
126
|
-
|
127
|
-
call_event, start_time = with_disabled_hook.call do
|
128
|
-
before_hook.call(defined_class, method, self, args)
|
129
|
-
end
|
130
|
-
return_value = nil
|
131
|
-
exception = nil
|
132
|
-
begin
|
133
|
-
return_value = base_method.call(*args, &block)
|
134
|
-
rescue
|
135
|
-
exception = $ERROR_INFO
|
136
|
-
raise
|
137
|
-
ensure
|
138
|
-
with_disabled_hook.call do
|
139
|
-
after_hook.call(call_event, defined_class, method, start_time, return_value, exception)
|
140
|
-
end
|
141
|
-
end
|
142
|
-
end
|
143
|
-
end
|
144
|
-
end
|
65
|
+
next unless \
|
66
|
+
config.always_hook?(hook_method.defined_class, method.name) ||
|
67
|
+
config.included_by_location?(method)
|
145
68
|
|
146
|
-
|
147
|
-
|
69
|
+
hook_method.activate
|
70
|
+
end
|
148
71
|
end
|
72
|
+
|
73
|
+
instance_methods.each(&hook.(cls))
|
74
|
+
class_methods.each(&hook.(cls.singleton_class))
|
149
75
|
end
|
76
|
+
|
77
|
+
tp.enable(&block)
|
150
78
|
end
|
151
79
|
end
|
152
80
|
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
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AppMap
|
4
|
+
module Metadata
|
5
|
+
class << self
|
6
|
+
def detect
|
7
|
+
{
|
8
|
+
app: AppMap.configuration.name,
|
9
|
+
language: {
|
10
|
+
name: 'ruby',
|
11
|
+
engine: RUBY_ENGINE,
|
12
|
+
version: RUBY_VERSION
|
13
|
+
},
|
14
|
+
client: {
|
15
|
+
name: 'appmap',
|
16
|
+
url: AppMap::URL,
|
17
|
+
version: AppMap::VERSION
|
18
|
+
}
|
19
|
+
}.tap do |m|
|
20
|
+
if defined?(::Rails) && defined?(::Rails.version)
|
21
|
+
m[:frameworks] ||= []
|
22
|
+
m[:frameworks] << {
|
23
|
+
name: 'rails',
|
24
|
+
version: ::Rails.version
|
25
|
+
}
|
26
|
+
end
|
27
|
+
m[:git] = git_metadata if git_available
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
protected
|
32
|
+
|
33
|
+
def git_available
|
34
|
+
@git_available = system('git status 2>&1 > /dev/null') if @git_available.nil?
|
35
|
+
end
|
36
|
+
|
37
|
+
def git_metadata
|
38
|
+
git_repo = `git config --get remote.origin.url`.strip
|
39
|
+
git_branch = `git rev-parse --abbrev-ref HEAD`.strip
|
40
|
+
git_sha = `git rev-parse HEAD`.strip
|
41
|
+
git_status = `git status -s`.split("\n").map(&:strip)
|
42
|
+
git_last_annotated_tag = `git describe --abbrev=0 2>/dev/null`.strip
|
43
|
+
git_last_annotated_tag = nil if git_last_annotated_tag.blank?
|
44
|
+
git_last_tag = `git describe --abbrev=0 --tags 2>/dev/null`.strip
|
45
|
+
git_last_tag = nil if git_last_tag.blank?
|
46
|
+
git_commits_since_last_annotated_tag = `git describe`.strip =~ /-(\d+)-(\w+)$/[1] rescue 0 if git_last_annotated_tag
|
47
|
+
git_commits_since_last_tag = `git describe --tags`.strip =~ /-(\d+)-(\w+)$/[1] rescue 0 if git_last_tag
|
48
|
+
|
49
|
+
{
|
50
|
+
repository: git_repo,
|
51
|
+
branch: git_branch,
|
52
|
+
commit: git_sha,
|
53
|
+
status: git_status,
|
54
|
+
git_last_annotated_tag: git_last_annotated_tag,
|
55
|
+
git_last_tag: git_last_tag,
|
56
|
+
git_commits_since_last_annotated_tag: git_commits_since_last_annotated_tag,
|
57
|
+
git_commits_since_last_tag: git_commits_since_last_tag
|
58
|
+
}
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -5,12 +5,9 @@ module AppMap
|
|
5
5
|
# RemoteRecording adds `/_appmap/record` routes to control recordings via HTTP requests
|
6
6
|
class RemoteRecording
|
7
7
|
def initialize(app)
|
8
|
-
require 'appmap/command/record'
|
9
8
|
require 'json'
|
10
9
|
|
11
10
|
@app = app
|
12
|
-
@config = AppMap.configure
|
13
|
-
AppMap::Hook.hook(@config)
|
14
11
|
end
|
15
12
|
|
16
13
|
def event_loop
|
@@ -63,15 +60,14 @@ module AppMap
|
|
63
60
|
@events.delete_if(&is_control_command_event)
|
64
61
|
@events.delete_if(&is_return_from_control_command_event)
|
65
62
|
|
66
|
-
|
67
|
-
metadata = AppMap::Command::Record.detect_metadata
|
63
|
+
metadata = AppMap.detect_metadata
|
68
64
|
metadata[:recorder] = {
|
69
65
|
name: 'remote_recording'
|
70
66
|
}
|
71
67
|
|
72
68
|
response = JSON.generate \
|
73
69
|
version: AppMap::APPMAP_FORMAT_VERSION,
|
74
|
-
classMap: AppMap.class_map(
|
70
|
+
classMap: AppMap.class_map(tracer.event_methods),
|
75
71
|
metadata: metadata,
|
76
72
|
events: @events
|
77
73
|
|
@@ -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
|