appmap 0.77.3 → 0.79.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 +1 -0
- data/.travis.yml +4 -23
- data/CHANGELOG.md +21 -0
- data/{spec/fixtures/rails5_users_app/Dockerfile.pg → Dockerfile.pg} +0 -0
- data/README.md +14 -44
- data/README_CI.md +0 -7
- data/Rakefile +12 -150
- data/appmap.gemspec +3 -2
- data/docker-compose.yml +10 -0
- data/ext/appmap/appmap.c +21 -2
- data/lib/appmap/builtin_hooks/ruby.yml +6 -3
- data/lib/appmap/handler/eval.rb +41 -0
- data/lib/appmap/handler/function.rb +8 -8
- data/lib/appmap/handler/net_http.rb +19 -18
- data/lib/appmap/handler/rails/request_handler.rb +3 -4
- data/lib/appmap/handler/rails/template.rb +68 -62
- data/lib/appmap/hook/method/ruby2.rb +56 -0
- data/lib/appmap/hook/method/ruby3.rb +56 -0
- data/lib/appmap/hook/method.rb +42 -98
- data/lib/appmap/hook.rb +2 -2
- data/lib/appmap/version.rb +1 -1
- data/spec/config_spec.rb +1 -1
- data/spec/depends/api_spec.rb +13 -5
- data/spec/depends/spec_helper.rb +0 -9
- data/spec/fixtures/database.yml +11 -0
- data/spec/fixtures/rails5_users_app/config/database.yml +1 -0
- data/spec/fixtures/rails6_users_app/Gemfile +1 -25
- data/spec/fixtures/rails6_users_app/config/database.yml +1 -0
- data/spec/fixtures/rails7_users_app/Gemfile +1 -25
- data/spec/fixtures/rails7_users_app/config/database.yml +1 -0
- data/spec/handler/eval_spec.rb +66 -0
- data/spec/hook_spec.rb +3 -3
- data/spec/rails_recording_spec.rb +4 -20
- data/spec/rails_spec_helper.rb +76 -63
- data/spec/rails_test_spec.rb +7 -17
- data/spec/railtie_spec.rb +4 -18
- data/spec/record_sql_rails_pg_spec.rb +44 -75
- data/spec/remote_recording_spec.rb +18 -30
- data/spec/spec_helper.rb +1 -0
- data/spec/swagger/swagger_spec.rb +1 -16
- data/spec/util_spec.rb +1 -1
- metadata +25 -21
- data/Dockerfile.appmap +0 -5
- data/spec/fixtures/rack_users_app/Dockerfile +0 -32
- data/spec/fixtures/rack_users_app/docker-compose.yml +0 -9
- data/spec/fixtures/rails5_users_app/Dockerfile +0 -29
- data/spec/fixtures/rails5_users_app/config/database.yml +0 -18
- data/spec/fixtures/rails5_users_app/create_app +0 -33
- data/spec/fixtures/rails5_users_app/docker-compose.yml +0 -31
- data/spec/fixtures/rails6_users_app/.ruby-version +0 -1
- data/spec/fixtures/rails6_users_app/Dockerfile +0 -44
- data/spec/fixtures/rails6_users_app/Dockerfile.pg +0 -3
- data/spec/fixtures/rails6_users_app/config/database.yml +0 -18
- data/spec/fixtures/rails6_users_app/create_app +0 -33
- data/spec/fixtures/rails6_users_app/docker-compose.yml +0 -31
- data/spec/fixtures/rails7_users_app/.ruby-version +0 -1
- data/spec/fixtures/rails7_users_app/Dockerfile +0 -30
- data/spec/fixtures/rails7_users_app/Dockerfile.pg +0 -3
- data/spec/fixtures/rails7_users_app/config/database.yml +0 -86
- data/spec/fixtures/rails7_users_app/create_app +0 -31
- data/spec/fixtures/rails7_users_app/docker-compose.yml +0 -31
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'appmap/handler/function'
|
3
4
|
require 'appmap/event'
|
4
5
|
|
5
6
|
module AppMap
|
@@ -9,7 +10,7 @@ module AppMap
|
|
9
10
|
LOG = (ENV['APPMAP_TEMPLATE_DEBUG'] == 'true' || ENV['DEBUG'] == 'true')
|
10
11
|
|
11
12
|
# All the code which is touched by the AppMap is recorded in the classMap.
|
12
|
-
# This duck-typed 'method' is used to represent a view template as a package,
|
13
|
+
# This duck-typed 'method' is used to represent a view template as a package,
|
13
14
|
# class, and method in the classMap.
|
14
15
|
# The class name is generated from the template path. The package name is
|
15
16
|
# 'app/views', and the method name is 'render'. The source location of the method
|
@@ -41,31 +42,31 @@ module AppMap
|
|
41
42
|
def package
|
42
43
|
'app/views'
|
43
44
|
end
|
44
|
-
|
45
|
+
|
45
46
|
def name
|
46
47
|
'render'
|
47
48
|
end
|
48
|
-
|
49
|
+
|
49
50
|
def source_location
|
50
51
|
path
|
51
52
|
end
|
52
|
-
|
53
|
+
|
53
54
|
def static
|
54
55
|
true
|
55
56
|
end
|
56
|
-
|
57
|
+
|
57
58
|
def comment
|
58
59
|
nil
|
59
60
|
end
|
60
|
-
|
61
|
+
|
61
62
|
def labels
|
62
63
|
[ 'mvc.template' ]
|
63
64
|
end
|
64
65
|
end
|
65
|
-
|
66
|
+
|
66
67
|
# TemplateCall is a type of function call which is specialized to view template rendering. Since
|
67
68
|
# there isn't really a perfect method in Rails to hook, this one is synthesized from the available
|
68
|
-
# information.
|
69
|
+
# information.
|
69
70
|
class TemplateCall < AppMap::Event::MethodEvent
|
70
71
|
# This is basically the +self+ parameter.
|
71
72
|
attr_reader :render_instance
|
@@ -75,19 +76,19 @@ module AppMap
|
|
75
76
|
attr_accessor :ready
|
76
77
|
|
77
78
|
alias ready? ready
|
78
|
-
|
79
|
+
|
79
80
|
def initialize(render_instance)
|
80
81
|
super :call
|
81
|
-
|
82
|
+
|
82
83
|
AppMap::Event::MethodEvent.build_from_invocation(:call, event: self)
|
83
84
|
@ready = false
|
84
85
|
@render_instance = render_instance
|
85
86
|
end
|
86
|
-
|
87
|
+
|
87
88
|
def static?
|
88
89
|
true
|
89
90
|
end
|
90
|
-
|
91
|
+
|
91
92
|
def to_h
|
92
93
|
super.tap do |h|
|
93
94
|
h[:defined_class] = path ? path.parameterize.underscore : 'inline_template'
|
@@ -103,71 +104,76 @@ module AppMap
|
|
103
104
|
end.compact
|
104
105
|
end
|
105
106
|
end
|
106
|
-
|
107
|
+
|
107
108
|
TEMPLATE_RENDERER = 'appmap.handler.rails.template.renderer'
|
108
109
|
|
109
110
|
# Hooks the ActionView::Resolver methods +find_all+, +find_all_anywhere+. The resolver is used
|
110
111
|
# during template rendering to lookup the template file path from parameters such as the
|
111
112
|
# template name, prefix, and partial (boolean).
|
112
|
-
class ResolverHandler
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
name, prefix, partial = args
|
117
|
-
warn "Resolver: #{{ name: name, prefix: prefix, partial: partial }}" if LOG
|
118
|
-
|
119
|
-
AppMap::Handler::Function.handle_call(defined_class, hook_method, receiver, args)
|
120
|
-
end
|
113
|
+
class ResolverHandler < AppMap::Handler::Function
|
114
|
+
def handle_call(receiver, args)
|
115
|
+
name, prefix, partial = args
|
116
|
+
warn "Resolver: #{{ name: name, prefix: prefix, partial: partial }}" if LOG
|
121
117
|
|
122
|
-
|
123
|
-
|
124
|
-
# template will be recorded in the classMap.
|
125
|
-
def handle_return(call_event_id, elapsed, return_value, exception)
|
126
|
-
renderer = Array(Thread.current[TEMPLATE_RENDERER]).last
|
127
|
-
path_obj = Array(return_value).first
|
128
|
-
|
129
|
-
warn "Resolver return: #{path_obj}" if LOG
|
130
|
-
|
131
|
-
if path_obj
|
132
|
-
path = if path_obj.respond_to?(:identifier) && path_obj.inspect.index('#<')
|
133
|
-
path_obj.identifier
|
134
|
-
else
|
135
|
-
path_obj.inspect
|
136
|
-
end
|
137
|
-
path = path[Dir.pwd.length + 1..-1] if path.index(Dir.pwd) == 0
|
138
|
-
AppMap.tracing.record_method(TemplateMethod.new(path))
|
139
|
-
renderer.path ||= path if renderer
|
140
|
-
end
|
118
|
+
super
|
119
|
+
end
|
141
120
|
|
142
|
-
|
143
|
-
|
121
|
+
# When the resolver returns, look to see if there is template rendering underway.
|
122
|
+
# If so, populate the template path. In all cases, add a TemplateMethod so that the
|
123
|
+
# template will be recorded in the classMap.
|
124
|
+
def handle_return(call_event_id, elapsed, return_value, exception)
|
125
|
+
renderer = Array(Thread.current[TEMPLATE_RENDERER]).last
|
126
|
+
path_obj = Array(return_value).first
|
127
|
+
|
128
|
+
warn "Resolver return: #{path_obj}" if LOG
|
129
|
+
|
130
|
+
record_template_path renderer, path_obj
|
131
|
+
|
132
|
+
super
|
133
|
+
end
|
134
|
+
|
135
|
+
def record_template_path(renderer, path_obj)
|
136
|
+
return unless path_obj
|
137
|
+
|
138
|
+
path = path_from_obj path_obj
|
139
|
+
AppMap.tracing.record_method(TemplateMethod.new(path))
|
140
|
+
renderer.path ||= path if renderer
|
141
|
+
end
|
142
|
+
|
143
|
+
def path_from_obj(path_obj)
|
144
|
+
path =
|
145
|
+
if path_obj.respond_to?(:identifier) && path_obj.inspect.index('#<')
|
146
|
+
path_obj.identifier
|
147
|
+
else
|
148
|
+
path_obj.inspect
|
149
|
+
end
|
150
|
+
path = path[Dir.pwd.length + 1..] if path.index(Dir.pwd) == 0
|
151
|
+
path
|
144
152
|
end
|
145
153
|
end
|
146
154
|
|
147
155
|
# Hooks the ActionView::Renderer method +render+. This method is used by Rails to perform
|
148
156
|
# template rendering. The TemplateCall event which is emitted by this handler has a
|
149
|
-
# +path+ parameter, which is nil until it's filled in by a ResolverHandler.
|
150
|
-
class RenderHandler
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
warn "Renderer: #{options}" if LOG
|
157
|
-
|
158
|
-
TemplateCall.new(receiver).tap do |call|
|
159
|
-
Thread.current[TEMPLATE_RENDERER] ||= []
|
160
|
-
Thread.current[TEMPLATE_RENDERER] << call
|
161
|
-
end
|
162
|
-
end
|
163
|
-
|
164
|
-
def handle_return(call_event_id, elapsed, return_value, exception)
|
165
|
-
template_call = Array(Thread.current[TEMPLATE_RENDERER]).pop
|
166
|
-
template_call.ready = true
|
157
|
+
# +path+ parameter, which is nil until it's filled in by a ResolverHandler.
|
158
|
+
class RenderHandler < AppMap::Hook::Method
|
159
|
+
def handle_call(receiver, args)
|
160
|
+
# context, options
|
161
|
+
_, options = args
|
162
|
+
|
163
|
+
warn "Renderer: #{options}" if LOG
|
167
164
|
|
168
|
-
|
165
|
+
TemplateCall.new(receiver).tap do |call|
|
166
|
+
Thread.current[TEMPLATE_RENDERER] ||= []
|
167
|
+
Thread.current[TEMPLATE_RENDERER] << call
|
169
168
|
end
|
170
169
|
end
|
170
|
+
|
171
|
+
def handle_return(call_event_id, elapsed, _return_value, _exception)
|
172
|
+
template_call = Array(Thread.current[TEMPLATE_RENDERER]).pop
|
173
|
+
template_call.ready = true
|
174
|
+
|
175
|
+
AppMap::Event::MethodReturnIgnoreValue.build_from_invocation(call_event_id, elapsed: elapsed)
|
176
|
+
end
|
171
177
|
end
|
172
178
|
end
|
173
179
|
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
def ruby2_keywords(*); end unless respond_to?(:ruby2_keywords, true)
|
4
|
+
|
5
|
+
module AppMap
|
6
|
+
class Hook
|
7
|
+
# Delegation methods for Ruby 2.
|
8
|
+
# cf. https://eregon.me/blog/2019/11/10/the-delegation-challenge-of-ruby27.html
|
9
|
+
class Method
|
10
|
+
ruby2_keywords def call(receiver, *args, &block)
|
11
|
+
return do_call(receiver, *args, &block) unless trace?
|
12
|
+
|
13
|
+
call_event = with_disabled_hook { before_hook receiver, *args }
|
14
|
+
trace_call call_event, receiver, *args, &block
|
15
|
+
end
|
16
|
+
|
17
|
+
protected
|
18
|
+
|
19
|
+
def before_hook(receiver, *args)
|
20
|
+
call_event = handle_call(receiver, args)
|
21
|
+
if call_event
|
22
|
+
AppMap.tracing.record_event \
|
23
|
+
call_event,
|
24
|
+
package: hook_package,
|
25
|
+
defined_class: defined_class,
|
26
|
+
method: hook_method
|
27
|
+
end
|
28
|
+
call_event
|
29
|
+
end
|
30
|
+
|
31
|
+
ruby2_keywords def do_call(receiver, *args, &block)
|
32
|
+
hook_method.bind(receiver).call(*args, &block)
|
33
|
+
end
|
34
|
+
|
35
|
+
ruby2_keywords def trace_call(call_event, receiver, *args, &block)
|
36
|
+
start_time = gettime
|
37
|
+
begin
|
38
|
+
return_value = do_call(receiver, *args, &block)
|
39
|
+
rescue # rubocop:disable Style/RescueStandardError
|
40
|
+
exception = $ERROR_INFO
|
41
|
+
raise
|
42
|
+
ensure
|
43
|
+
with_disabled_hook { after_hook receiver, call_event, gettime - start_time, return_value, exception } \
|
44
|
+
if call_event
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def hook_method_def
|
49
|
+
this = self
|
50
|
+
proc { |*args, &block| this.call self, *args, &block }.tap do |hook|
|
51
|
+
hook.ruby2_keywords if hook.respond_to? :ruby2_keywords
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AppMap
|
4
|
+
class Hook
|
5
|
+
# Delegation methods for Ruby 3.
|
6
|
+
class Method
|
7
|
+
def call(receiver, *args, **kwargs, &block)
|
8
|
+
return do_call(receiver, *args, **kwargs, &block) unless trace?
|
9
|
+
|
10
|
+
call_event = with_disabled_hook { before_hook receiver, *args, **kwargs }
|
11
|
+
trace_call call_event, receiver, *args, **kwargs, &block
|
12
|
+
end
|
13
|
+
|
14
|
+
protected
|
15
|
+
|
16
|
+
def before_hook(receiver, *args, **kwargs)
|
17
|
+
args = [*args, kwargs] if !kwargs.empty? || keyrest?
|
18
|
+
call_event = handle_call(receiver, args)
|
19
|
+
if call_event
|
20
|
+
AppMap.tracing.record_event \
|
21
|
+
call_event,
|
22
|
+
package: hook_package,
|
23
|
+
defined_class: defined_class,
|
24
|
+
method: hook_method
|
25
|
+
end
|
26
|
+
call_event
|
27
|
+
end
|
28
|
+
|
29
|
+
def keyrest?
|
30
|
+
@keyrest ||= parameters.map(&:last).include? :keyrest
|
31
|
+
end
|
32
|
+
|
33
|
+
def do_call(receiver, *args, **kwargs, &block)
|
34
|
+
hook_method.bind_call(receiver, *args, **kwargs, &block)
|
35
|
+
end
|
36
|
+
|
37
|
+
def trace_call(call_event, receiver, *args, **kwargs, &block)
|
38
|
+
start_time = gettime
|
39
|
+
begin
|
40
|
+
return_value = do_call(receiver, *args, **kwargs, &block)
|
41
|
+
rescue # rubocop:disable Style/RescueStandardError
|
42
|
+
exception = $ERROR_INFO
|
43
|
+
raise
|
44
|
+
ensure
|
45
|
+
with_disabled_hook { after_hook receiver, call_event, gettime - start_time, return_value, exception } \
|
46
|
+
if call_event
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def hook_method_def
|
51
|
+
this = self
|
52
|
+
proc { |*args, **kwargs, &block| this.call self, *args, **kwargs, &block }
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/lib/appmap/hook/method.rb
CHANGED
@@ -1,17 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module AppMap
|
4
|
-
NEW_RUBY = Util.ruby_minor_version >= 2.7
|
5
|
-
if NEW_RUBY && !Proc.instance_methods.include?(:ruby2_keywords)
|
6
|
-
warn "Ruby is #{RUBY_VERSION}, but Procs don't respond to #ruby2_keywords"
|
7
|
-
end
|
8
|
-
|
9
4
|
class Hook
|
10
5
|
SIGNATURES = {}
|
11
|
-
|
12
6
|
LOOKUP_SIGNATURE = lambda do |id|
|
13
7
|
method = super(id)
|
14
|
-
|
8
|
+
|
15
9
|
signature = SIGNATURES[[ method.owner, method.name ]]
|
16
10
|
if signature
|
17
11
|
method.singleton_class.module_eval do
|
@@ -20,36 +14,24 @@ module AppMap
|
|
20
14
|
end
|
21
15
|
end
|
22
16
|
end
|
23
|
-
|
17
|
+
|
24
18
|
method
|
25
19
|
end
|
26
|
-
|
27
|
-
class Method
|
28
|
-
attr_reader :hook_package, :hook_class, :hook_method
|
29
20
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
attr_reader :
|
21
|
+
# Single hooked method.
|
22
|
+
# Call #activate to override the original.
|
23
|
+
class Method
|
24
|
+
attr_reader :hook_package, :hook_class, :hook_method, :parameters, :arity
|
34
25
|
|
35
26
|
HOOK_DISABLE_KEY = 'AppMap::Hook.disable'
|
36
27
|
private_constant :HOOK_DISABLE_KEY
|
37
28
|
|
38
|
-
# Grab the definition of Time.now here, to avoid interfering
|
39
|
-
# with the method we're hooking.
|
40
|
-
TIME_NOW = Time.method(:now)
|
41
|
-
private_constant :TIME_NOW
|
42
|
-
|
43
|
-
ARRAY_OF_EMPTY_HASH = [{}.freeze].freeze
|
44
|
-
|
45
29
|
def initialize(hook_package, hook_class, hook_method)
|
46
30
|
@hook_package = hook_package
|
47
31
|
@hook_class = hook_class
|
48
32
|
@hook_method = hook_method
|
49
|
-
|
50
|
-
|
51
|
-
@defined_class, method_symbol = Hook.qualify_method_name(@hook_method)
|
52
|
-
@method_display_name = [@defined_class, method_symbol, @hook_method.name].join if @defined_class
|
33
|
+
@parameters = hook_method.parameters
|
34
|
+
@arity = hook_method.arity
|
53
35
|
end
|
54
36
|
|
55
37
|
def activate
|
@@ -62,64 +44,6 @@ module AppMap
|
|
62
44
|
warn "AppMap: Hooking #{msg} at line #{(hook_method.source_location || []).join(':')}"
|
63
45
|
end
|
64
46
|
|
65
|
-
defined_class = @defined_class
|
66
|
-
hook_package = self.hook_package
|
67
|
-
hook_method = self.hook_method
|
68
|
-
before_hook = self.method(:before_hook)
|
69
|
-
after_hook = self.method(:after_hook)
|
70
|
-
with_disabled_hook = self.method(:with_disabled_hook)
|
71
|
-
|
72
|
-
is_array_containing_empty_hash = ->(obj) {
|
73
|
-
obj.is_a?(Array) && obj.length == 1 && obj[0].is_a?(Hash) && obj[0].size == 0
|
74
|
-
}
|
75
|
-
|
76
|
-
call_instance_method = lambda do |receiver, args, &block|
|
77
|
-
# https://github.com/applandinc/appmap-ruby/issues/153
|
78
|
-
if NEW_RUBY && is_array_containing_empty_hash.(args) && hook_method.arity == 1
|
79
|
-
hook_method.bind_call(receiver, {}, &block)
|
80
|
-
else
|
81
|
-
if NEW_RUBY
|
82
|
-
hook_method.bind_call(receiver, *args, &block)
|
83
|
-
else
|
84
|
-
hook_method.bind(receiver).call(*args, &block)
|
85
|
-
end
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
hook_method_def = Proc.new do |*args, &block|
|
90
|
-
# We may not have gotten the class for the method during
|
91
|
-
# initialization (e.g. for a singleton method on an embedded
|
92
|
-
# struct), so make sure we have it now.
|
93
|
-
defined_class, = Hook.qualify_method_name(hook_method) unless defined_class
|
94
|
-
|
95
|
-
reentrant = Thread.current[HOOK_DISABLE_KEY]
|
96
|
-
disabled_by_shallow_flag = \
|
97
|
-
-> { hook_package&.shallow? && AppMap.tracing.last_package_for_current_thread == hook_package }
|
98
|
-
|
99
|
-
enabled = true if AppMap.tracing.enabled? && !reentrant && !disabled_by_shallow_flag.call
|
100
|
-
|
101
|
-
enabled = false if %i[instance_eval instance_exec].member?(hook_method.name) && args.empty?
|
102
|
-
|
103
|
-
return call_instance_method.call(self, args, &block) unless enabled
|
104
|
-
|
105
|
-
call_event, start_time = with_disabled_hook.call do
|
106
|
-
before_hook.call(self, defined_class, args)
|
107
|
-
end
|
108
|
-
return_value = nil
|
109
|
-
exception = nil
|
110
|
-
begin
|
111
|
-
return_value = call_instance_method.call(self, args, &block)
|
112
|
-
rescue
|
113
|
-
exception = $ERROR_INFO
|
114
|
-
raise
|
115
|
-
ensure
|
116
|
-
with_disabled_hook.call do
|
117
|
-
after_hook.call(self, call_event, start_time, return_value, exception) if call_event
|
118
|
-
end
|
119
|
-
end
|
120
|
-
end
|
121
|
-
hook_method_def = hook_method_def.ruby2_keywords if hook_method_def.respond_to?(:ruby2_keywords)
|
122
|
-
|
123
47
|
hook_method_parameters = hook_method.parameters.dup.freeze
|
124
48
|
SIGNATURES[[ hook_class, hook_method.name ]] = hook_method_parameters
|
125
49
|
|
@@ -140,28 +64,42 @@ module AppMap
|
|
140
64
|
|
141
65
|
protected
|
142
66
|
|
143
|
-
def
|
144
|
-
|
145
|
-
|
146
|
-
|
67
|
+
def gettime
|
68
|
+
Process.clock_gettime Process::CLOCK_MONOTONIC
|
69
|
+
end
|
70
|
+
|
71
|
+
def trace?
|
72
|
+
return false unless AppMap.tracing.enabled?
|
73
|
+
return false if Thread.current[HOOK_DISABLE_KEY]
|
74
|
+
return false if hook_package&.shallow? && AppMap.tracing.last_package_for_current_thread == hook_package
|
75
|
+
|
76
|
+
true
|
147
77
|
end
|
148
78
|
|
149
|
-
def
|
150
|
-
|
151
|
-
|
79
|
+
def method_display_name
|
80
|
+
return @method_display_name if @method_display_name
|
81
|
+
|
82
|
+
return @method_display_name = [defined_class, method_symbol, hook_method.name].join if defined_class
|
83
|
+
|
84
|
+
"#{hook_method.name} (class resolution deferred)"
|
85
|
+
end
|
86
|
+
|
87
|
+
def defined_class
|
88
|
+
@defined_class ||= Hook.qualify_method_name(hook_method)&.first
|
89
|
+
end
|
90
|
+
|
91
|
+
def after_hook(_receiver, call_event, elapsed, return_value, exception)
|
92
|
+
return_event = handle_return(call_event.id, elapsed, return_value, exception)
|
152
93
|
AppMap.tracing.record_event(return_event) if return_event
|
153
|
-
nil
|
154
94
|
end
|
155
95
|
|
156
|
-
def with_disabled_hook
|
96
|
+
def with_disabled_hook
|
157
97
|
# Don't record functions, such as to_s and inspect, that might be called
|
158
98
|
# by the fn. Otherwise there can be a stack overflow.
|
159
99
|
Thread.current[HOOK_DISABLE_KEY] = true
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
Thread.current[HOOK_DISABLE_KEY] = false
|
164
|
-
end
|
100
|
+
yield
|
101
|
+
ensure
|
102
|
+
Thread.current[HOOK_DISABLE_KEY] = false
|
165
103
|
end
|
166
104
|
end
|
167
105
|
end
|
@@ -189,3 +127,9 @@ unless ENV['APPMAP_NO_PATCH_MODULE'] == 'true'
|
|
189
127
|
prepend AppMap::ModuleMethods
|
190
128
|
end
|
191
129
|
end
|
130
|
+
|
131
|
+
if RUBY_VERSION < '3'
|
132
|
+
require 'appmap/hook/method/ruby2'
|
133
|
+
else
|
134
|
+
require 'appmap/hook/method/ruby3'
|
135
|
+
end
|
data/lib/appmap/hook.rb
CHANGED
@@ -118,7 +118,7 @@ module AppMap
|
|
118
118
|
cls, method = entry
|
119
119
|
return false if config.never_hook?(cls, method)
|
120
120
|
|
121
|
-
|
121
|
+
hook.package.handler_class.new(hook.package, cls, method).activate
|
122
122
|
end
|
123
123
|
|
124
124
|
methods = []
|
@@ -209,7 +209,7 @@ module AppMap
|
|
209
209
|
package = config.lookup_package(hook_cls, method)
|
210
210
|
next unless package
|
211
211
|
|
212
|
-
|
212
|
+
package.handler_class.new(package, hook_cls, method).activate
|
213
213
|
end
|
214
214
|
end
|
215
215
|
|
data/lib/appmap/version.rb
CHANGED
data/spec/config_spec.rb
CHANGED
data/spec/depends/api_spec.rb
CHANGED
@@ -33,7 +33,7 @@ module AppMap
|
|
33
33
|
end
|
34
34
|
end
|
35
35
|
end
|
36
|
-
end
|
36
|
+
end
|
37
37
|
|
38
38
|
describe 'Depends API' do
|
39
39
|
let(:api) { AppMap::Depends::API.new(ENV['DEBUG'] == 'true') }
|
@@ -112,12 +112,12 @@ describe 'Depends API' do
|
|
112
112
|
around do |test|
|
113
113
|
@minitest_test_command_method = AppMap.configuration.depends_config.minitest_test_command_method
|
114
114
|
AppMap.configuration.depends_config.minitest_test_command_method = 'AppMap::Depends::APISpec.minitest_test_command'
|
115
|
-
|
115
|
+
|
116
116
|
test.call
|
117
117
|
ensure
|
118
118
|
AppMap.configuration.depends_config.minitest_test_command_method = @minitest_test_command
|
119
119
|
end
|
120
|
-
|
120
|
+
|
121
121
|
it 'passes a smoke test' do
|
122
122
|
run_tests
|
123
123
|
end
|
@@ -175,10 +175,18 @@ describe 'Depends API' do
|
|
175
175
|
# At this point, we would run tests to bring the AppMaps up to date
|
176
176
|
# Then once the tests have finished, remove any AppMaps that weren't refreshed
|
177
177
|
removed = api.remove_out_of_date_appmaps(since, appmap_dir: DEPENDS_TEST_DIR, base_dir: DEPENDS_BASE_DIR)
|
178
|
-
expect(removed).to eq([ appmap_path.split('.')[0] ])
|
178
|
+
expect(removed).to eq([ appmap_path.split('.')[0] ])
|
179
179
|
ensure
|
180
|
-
File.write(appmap_path, appmap)
|
180
|
+
File.write(appmap_path, appmap)
|
181
181
|
end
|
182
182
|
end
|
183
183
|
end
|
184
|
+
|
185
|
+
before do
|
186
|
+
Dir.glob("#{DEPENDS_TEST_DIR}/*.appmap.json").each { |fname| FileUtils.touch fname }
|
187
|
+
update_appmap_index
|
188
|
+
|
189
|
+
FileUtils.rm_rf 'spec/tmp'
|
190
|
+
FileUtils.mkdir_p 'spec/tmp'
|
191
|
+
end
|
184
192
|
end
|
data/spec/depends/spec_helper.rb
CHANGED
@@ -16,12 +16,3 @@ def update_appmap_index
|
|
16
16
|
system cmd.join(' ') or raise "Failed to update AppMap index in #{DEPENDS_TEST_DIR}"
|
17
17
|
end
|
18
18
|
|
19
|
-
RSpec.configure do |rspec|
|
20
|
-
rspec.before do
|
21
|
-
Dir.glob("#{DEPENDS_TEST_DIR}/*.appmap.json").each { |fname| FileUtils.touch fname }
|
22
|
-
update_appmap_index
|
23
|
-
|
24
|
-
FileUtils.rm_rf 'spec/tmp'
|
25
|
-
FileUtils.mkdir_p 'spec/tmp'
|
26
|
-
end
|
27
|
-
end
|
@@ -0,0 +1 @@
|
|
1
|
+
../../database.yml
|
@@ -11,32 +11,8 @@ gem 'sequel', '>= 5.43.0', require: false
|
|
11
11
|
gem 'sequel-rails', require: false
|
12
12
|
gem 'sequel_secure_password', require: false
|
13
13
|
|
14
|
-
appmap_path = \
|
15
|
-
# Support debugging inside the container with volume-mounted source
|
16
|
-
if File.directory?('/src/appmap-ruby')
|
17
|
-
'/src/appmap-ruby'
|
18
|
-
elsif File.exist?('../../../appmap.gemspec')
|
19
|
-
'../../..'
|
20
|
-
end
|
21
|
-
|
22
|
-
if appmap_path
|
23
|
-
# Set the branch parameter, so that 'bundle config local.appmap' will work
|
24
|
-
appmap_branch = Dir.chdir appmap_path do
|
25
|
-
`git rev-parse --abbrev-ref HEAD`.strip
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
appmap_options = \
|
30
|
-
if appmap_path && appmap_branch
|
31
|
-
{ git: appmap_path, branch: appmap_branch }
|
32
|
-
elsif appmap_path
|
33
|
-
{ path: appmap_path }
|
34
|
-
else
|
35
|
-
{}
|
36
|
-
end.merge(require: %w[appmap])
|
37
|
-
|
38
14
|
group :development, :test do
|
39
|
-
gem 'appmap',
|
15
|
+
gem 'appmap', path: '../../..'
|
40
16
|
gem 'cucumber-rails', require: false
|
41
17
|
gem 'rspec-rails'
|
42
18
|
# Call 'byebug' anywhere in the code to stop execution and get a debugger console
|
@@ -0,0 +1 @@
|
|
1
|
+
../../database.yml
|