appmap 0.42.1 → 0.46.0
Sign up to get free protection for your applications and to get access to all the features.
- 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/config.rb
CHANGED
@@ -1,8 +1,32 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'appmap/handler/net_http'
|
4
|
+
require 'appmap/handler/rails/template'
|
5
|
+
|
3
6
|
module AppMap
|
4
7
|
class Config
|
8
|
+
# Specifies a code +path+ to be mapped.
|
9
|
+
# Options:
|
10
|
+
#
|
11
|
+
# * +gem+ may indicate a gem name that "owns" the path
|
12
|
+
# * +package_name+ can be used to make sure that the code is required so that it can be loaded. This is generally used with
|
13
|
+
# builtins, or when the path to be required is not automatically required when bundler requires the gem.
|
14
|
+
# * +exclude+ can be used used to exclude sub-paths. Generally not used with +gem+.
|
15
|
+
# * +labels+ is used to apply labels to matching code. This is really only useful when the package will be applied to
|
16
|
+
# specific functions, via TargetMethods.
|
17
|
+
# * +shallow+ indicates shallow mapping, in which only the entrypoint to a gem is recorded.
|
5
18
|
Package = Struct.new(:path, :gem, :package_name, :exclude, :labels, :shallow) do
|
19
|
+
# This is for internal use only.
|
20
|
+
private_methods :gem
|
21
|
+
|
22
|
+
# Specifies the class that will convert code events into event objects.
|
23
|
+
attr_writer :handler_class
|
24
|
+
|
25
|
+
def handler_class
|
26
|
+
require 'appmap/handler/function'
|
27
|
+
@handler_class || AppMap::Handler::Function
|
28
|
+
end
|
29
|
+
|
6
30
|
# Indicates that only the entry points to a package will be recorded.
|
7
31
|
# Once the code has entered a package, subsequent calls within the package will not be
|
8
32
|
# recorded unless the code leaves the package and re-enters it.
|
@@ -11,25 +35,36 @@ module AppMap
|
|
11
35
|
end
|
12
36
|
|
13
37
|
class << self
|
38
|
+
# Builds a package for a path, such as `app/models` in a Rails app. Generally corresponds to a `path:` entry
|
39
|
+
# in appmap.yml. Also used for mapping specific methods via TargetMethods.
|
14
40
|
def build_from_path(path, shallow: false, package_name: nil, exclude: [], labels: [])
|
15
41
|
Package.new(path, nil, package_name, exclude, labels, shallow)
|
16
42
|
end
|
17
43
|
|
18
|
-
|
19
|
-
|
44
|
+
# Builds a package for gem. Generally corresponds to a `gem:` entry in appmap.yml. Also used when mapping
|
45
|
+
# a builtin.
|
46
|
+
def build_from_gem(gem, shallow: true, package_name: nil, exclude: [], labels: [], optional: false, force: false)
|
47
|
+
if !force && %w[method_source activesupport].member?(gem)
|
20
48
|
warn "WARNING: #{gem} cannot be AppMapped because it is a dependency of the appmap gem"
|
21
49
|
return
|
22
50
|
end
|
23
|
-
|
51
|
+
path = gem_path(gem, optional)
|
52
|
+
if path
|
53
|
+
Package.new(path, gem, package_name, exclude, labels, shallow)
|
54
|
+
else
|
55
|
+
warn "#{gem} is not available in the bundle" if AppMap::Hook::LOG
|
56
|
+
end
|
24
57
|
end
|
25
58
|
|
26
59
|
private_class_method :new
|
27
60
|
|
28
61
|
protected
|
29
62
|
|
30
|
-
def gem_path(gem)
|
31
|
-
gemspec = Gem.loaded_specs[gem]
|
32
|
-
|
63
|
+
def gem_path(gem, optional)
|
64
|
+
gemspec = Gem.loaded_specs[gem]
|
65
|
+
# This exception will notify a user that their appmap.yml contains non-existent gems.
|
66
|
+
raise "Gem #{gem.inspect} not found" unless gemspec || optional
|
67
|
+
gemspec ? gemspec.gem_dir : nil
|
33
68
|
end
|
34
69
|
end
|
35
70
|
|
@@ -42,6 +77,7 @@ module AppMap
|
|
42
77
|
path: path,
|
43
78
|
package_name: package_name,
|
44
79
|
gem: gem,
|
80
|
+
handler_class: handler_class.name,
|
45
81
|
exclude: exclude.blank? ? nil : exclude,
|
46
82
|
labels: labels.blank? ? nil : labels,
|
47
83
|
shallow: shallow
|
@@ -49,44 +85,109 @@ module AppMap
|
|
49
85
|
end
|
50
86
|
end
|
51
87
|
|
52
|
-
|
88
|
+
Function = Struct.new(:package, :cls, :labels, :function_names) do # :nodoc:
|
89
|
+
def to_h
|
90
|
+
{
|
91
|
+
package: package,
|
92
|
+
class: cls,
|
93
|
+
labels: labels,
|
94
|
+
functions: function_names.map(&:to_sym)
|
95
|
+
}.compact
|
96
|
+
end
|
53
97
|
end
|
98
|
+
private_constant :Function
|
54
99
|
|
55
|
-
|
100
|
+
class TargetMethods # :nodoc:
|
101
|
+
attr_reader :method_names, :package
|
102
|
+
|
103
|
+
def initialize(method_names, package)
|
104
|
+
@method_names = method_names
|
105
|
+
@package = package
|
106
|
+
end
|
107
|
+
|
108
|
+
def include_method?(method_name)
|
109
|
+
Array(method_names).include?(method_name)
|
110
|
+
end
|
111
|
+
|
112
|
+
def to_h
|
113
|
+
{
|
114
|
+
package: package.name,
|
115
|
+
method_names: method_names
|
116
|
+
}
|
117
|
+
end
|
118
|
+
end
|
119
|
+
private_constant :TargetMethods
|
120
|
+
|
121
|
+
OPENSSL_PACKAGES = ->(labels) { Package.build_from_path('openssl', package_name: 'openssl', labels: labels) }
|
56
122
|
|
57
123
|
# Methods that should always be hooked, with their containing
|
58
124
|
# package and labels that should be applied to them.
|
59
125
|
HOOKED_METHODS = {
|
60
|
-
'
|
61
|
-
|
62
|
-
|
63
|
-
'
|
64
|
-
|
65
|
-
|
126
|
+
'ActionView::Renderer' => TargetMethods.new(:render, Package.build_from_gem('actionview', shallow: false, package_name: 'action_view', labels: %w[mvc.view], optional: true).tap do |package|
|
127
|
+
package.handler_class = AppMap::Handler::Rails::Template::RenderHandler if package
|
128
|
+
end),
|
129
|
+
'ActionView::Resolver' => TargetMethods.new(%i[find_all find_all_anywhere], Package.build_from_gem('actionview', shallow: false, package_name: 'action_view', labels: %w[mvc.template.resolver], optional: true).tap do |package|
|
130
|
+
package.handler_class = AppMap::Handler::Rails::Template::ResolverHandler if package
|
131
|
+
end),
|
132
|
+
'ActionDispatch::Request::Session' => TargetMethods.new(%i[destroy [] dig values []= clear update delete fetch merge], Package.build_from_gem('actionpack', shallow: false, package_name: 'action_dispatch', labels: %w[http.session], optional: true)),
|
133
|
+
'ActionDispatch::Cookies::CookieJar' => TargetMethods.new(%i[[]= clear update delete recycle], Package.build_from_gem('actionpack', shallow: false, package_name: 'action_dispatch', labels: %w[http.cookie], optional: true)),
|
134
|
+
'ActionDispatch::Cookies::EncryptedCookieJar' => TargetMethods.new(%i[[]=], Package.build_from_gem('actionpack', shallow: false, package_name: 'action_dispatch', labels: %w[http.cookie crypto.encrypt], optional: true)),
|
135
|
+
'CanCan::ControllerAdditions' => TargetMethods.new(%i[authorize! can? cannot?], Package.build_from_gem('cancancan', shallow: false, labels: %w[security.authorization], optional: true)),
|
136
|
+
'CanCan::Ability' => TargetMethods.new(%i[authorize!], Package.build_from_gem('cancancan', shallow: false, labels: %w[security.authorization], optional: true)),
|
137
|
+
'ActionController::Instrumentation' => [
|
138
|
+
TargetMethods.new(%i[process_action send_file send_data redirect_to], Package.build_from_gem('actionpack', shallow: false, package_name: 'action_controller', labels: %w[mvc.controller], optional: true))
|
139
|
+
]
|
66
140
|
}.freeze
|
67
141
|
|
68
142
|
BUILTIN_METHODS = {
|
69
|
-
'OpenSSL::PKey::PKey' =>
|
70
|
-
'OpenSSL::X509::Request' =>
|
71
|
-
'OpenSSL::PKCS5' =>
|
72
|
-
'OpenSSL::Cipher' =>
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
'
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
'
|
81
|
-
'
|
143
|
+
'OpenSSL::PKey::PKey' => TargetMethods.new(:sign, OPENSSL_PACKAGES.(%w[crypto.pkey])),
|
144
|
+
'OpenSSL::X509::Request' => TargetMethods.new(%i[sign verify], OPENSSL_PACKAGES.(%w[crypto.x509])),
|
145
|
+
'OpenSSL::PKCS5' => TargetMethods.new(%i[pbkdf2_hmac_sha1 pbkdf2_hmac], OPENSSL_PACKAGES.(%w[crypto.pkcs5])),
|
146
|
+
'OpenSSL::Cipher' => [
|
147
|
+
TargetMethods.new(%i[encrypt], OPENSSL_PACKAGES.(%w[crypto.encrypt])),
|
148
|
+
TargetMethods.new(%i[decrypt], OPENSSL_PACKAGES.(%w[crypto.decrypt]))
|
149
|
+
],
|
150
|
+
'ActiveSupport::Callbacks::CallbackSequence' => [
|
151
|
+
TargetMethods.new(:invoke_before, Package.build_from_gem('activesupport', force: true, package_name: 'active_support', labels: %w[mvc.before_action])),
|
152
|
+
TargetMethods.new(:invoke_after, Package.build_from_gem('activesupport', force: true, package_name: 'active_support', labels: %w[mvc.after_action])),
|
153
|
+
],
|
154
|
+
'ActiveSupport::SecurityUtils' => TargetMethods.new(:secure_compare, Package.build_from_gem('activesupport', force: true, package_name: 'active_support/security_utils', labels: %w[crypto.secure_compare])),
|
155
|
+
'OpenSSL::X509::Certificate' => TargetMethods.new(:sign, OPENSSL_PACKAGES.(%w[crypto.x509])),
|
156
|
+
'Net::HTTP' => TargetMethods.new(:request, Package.build_from_path('net/http', package_name: 'net/http', labels: %w[protocol.http]).tap do |package|
|
157
|
+
package.handler_class = AppMap::Handler::NetHTTP
|
158
|
+
end),
|
159
|
+
'Net::SMTP' => TargetMethods.new(:send, Package.build_from_path('net/smtp', package_name: 'net/smtp', labels: %w[protocol.email.smtp])),
|
160
|
+
'Net::POP3' => TargetMethods.new(:mails, Package.build_from_path('net/pop3', package_name: 'net/pop', labels: %w[protocol.email.pop])),
|
161
|
+
# This is happening: Method send_command not found on Net::IMAP
|
162
|
+
# 'Net::IMAP' => TargetMethods.new(:send_command, Package.build_from_path('net/imap', package_name: 'net/imap', labels: %w[protocol.email.imap])),
|
163
|
+
# 'Marshal' => TargetMethods.new(%i[dump load], Package.build_from_path('marshal', labels: %w[format.marshal])),
|
164
|
+
'Psych' => TargetMethods.new(%i[dump dump_stream load load_stream parse parse_stream], Package.build_from_path('yaml', package_name: 'psych', labels: %w[format.yaml])),
|
165
|
+
'JSON::Ext::Parser' => TargetMethods.new(:parse, Package.build_from_path('json', package_name: 'json', labels: %w[format.json])),
|
166
|
+
'JSON::Ext::Generator::State' => TargetMethods.new(:generate, Package.build_from_path('json', package_name: 'json', labels: %w[format.json])),
|
82
167
|
}.freeze
|
83
168
|
|
84
|
-
attr_reader :name, :packages, :exclude
|
169
|
+
attr_reader :name, :packages, :exclude, :hooked_methods, :builtin_methods
|
85
170
|
|
86
|
-
def initialize(name, packages
|
171
|
+
def initialize(name, packages, exclude: [], functions: [])
|
87
172
|
@name = name
|
88
173
|
@packages = packages
|
174
|
+
@hook_paths = packages.map(&:path)
|
89
175
|
@exclude = exclude
|
176
|
+
@builtin_methods = BUILTIN_METHODS
|
177
|
+
@functions = functions
|
178
|
+
@hooked_methods = HOOKED_METHODS.dup
|
179
|
+
functions.each do |func|
|
180
|
+
package_options = {}
|
181
|
+
package_options[:labels] = func.labels if func.labels
|
182
|
+
@hooked_methods[func.cls] ||= []
|
183
|
+
@hooked_methods[func.cls] << TargetMethods.new(func.function_names, Package.build_from_path(func.package, package_options))
|
184
|
+
end
|
185
|
+
|
186
|
+
@hooked_methods.each_value do |hooks|
|
187
|
+
Array(hooks).each do |hook|
|
188
|
+
@hook_paths << hook.package.path if hook.package
|
189
|
+
end
|
190
|
+
end
|
90
191
|
end
|
91
192
|
|
92
193
|
class << self
|
@@ -98,6 +199,16 @@ module AppMap
|
|
98
199
|
|
99
200
|
# Loads configuration from a Hash.
|
100
201
|
def load(config_data)
|
202
|
+
functions = (config_data['functions'] || []).map do |function_data|
|
203
|
+
package = function_data['package']
|
204
|
+
cls = function_data['class']
|
205
|
+
functions = function_data['function'] || function_data['functions']
|
206
|
+
raise 'AppMap class configuration should specify package, class and function(s)' unless package && cls && functions
|
207
|
+
functions = Array(functions).map(&:to_sym)
|
208
|
+
labels = function_data['label'] || function_data['labels']
|
209
|
+
labels = Array(labels).map(&:to_s) if labels
|
210
|
+
Function.new(package, cls, labels, functions)
|
211
|
+
end
|
101
212
|
packages = (config_data['packages'] || []).map do |package|
|
102
213
|
gem = package['gem']
|
103
214
|
path = package['path']
|
@@ -112,7 +223,8 @@ module AppMap
|
|
112
223
|
Package.build_from_path(path, exclude: package['exclude'] || [], shallow: package['shallow'])
|
113
224
|
end
|
114
225
|
end.compact
|
115
|
-
|
226
|
+
exclude = config_data['exclude'] || []
|
227
|
+
Config.new config_data['name'], packages, exclude: exclude, functions: functions
|
116
228
|
end
|
117
229
|
end
|
118
230
|
|
@@ -120,58 +232,59 @@ module AppMap
|
|
120
232
|
{
|
121
233
|
name: name,
|
122
234
|
packages: packages.map(&:to_h),
|
235
|
+
functions: @functions.map(&:to_h),
|
123
236
|
exclude: exclude
|
124
237
|
}
|
125
238
|
end
|
126
239
|
|
127
|
-
#
|
128
|
-
|
129
|
-
|
130
|
-
|
240
|
+
# Determines if methods defined in a file path should possibly be hooked.
|
241
|
+
def path_enabled?(path)
|
242
|
+
path = AppMap::Util.normalize_path(path)
|
243
|
+
@hook_paths.find { |hook_path| path.index(hook_path) == 0 }
|
131
244
|
end
|
132
245
|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
246
|
+
# Looks up a class and method in the config, to find the matching Package configuration.
|
247
|
+
# This class is only used after +path_enabled?+ has returned `true`.
|
248
|
+
LookupPackage = Struct.new(:config, :cls, :method) do
|
249
|
+
def package
|
250
|
+
# Global "excludes" configuration can be used to ignore any class/method.
|
251
|
+
return if config.never_hook?(cls, method)
|
137
252
|
|
138
|
-
|
139
|
-
location = method.source_location
|
140
|
-
location_file, = location
|
141
|
-
return unless location_file
|
142
|
-
|
143
|
-
location_file = location_file[Dir.pwd.length + 1..-1] if location_file.index(Dir.pwd) == 0
|
144
|
-
packages.select { |pkg| pkg.path }.find do |pkg|
|
145
|
-
(location_file.index(pkg.path) == 0) &&
|
146
|
-
!pkg.exclude.find { |p| location_file.index(p) }
|
253
|
+
package_for_code_object || package_for_location
|
147
254
|
end
|
148
|
-
end
|
149
255
|
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
256
|
+
# Hook a method which is specified by class and method name.
|
257
|
+
def package_for_code_object
|
258
|
+
Array(config.hooked_methods[cls.name])
|
259
|
+
.compact
|
260
|
+
.find { |hook| hook.include_method?(method.name) }
|
261
|
+
&.package
|
262
|
+
end
|
154
263
|
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
264
|
+
# Hook a method which is specified by code location (i.e. path).
|
265
|
+
def package_for_location
|
266
|
+
location = method.source_location
|
267
|
+
location_file, = location
|
268
|
+
return unless location_file
|
159
269
|
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
270
|
+
location_file = AppMap::Util.normalize_path(location_file)
|
271
|
+
config
|
272
|
+
.packages
|
273
|
+
.select { |pkg| pkg.path }
|
274
|
+
.find do |pkg|
|
275
|
+
(location_file.index(pkg.path) == 0) &&
|
276
|
+
!pkg.exclude.find { |p| location_file.index(p) }
|
277
|
+
end
|
278
|
+
end
|
164
279
|
end
|
165
280
|
|
166
|
-
def
|
167
|
-
|
168
|
-
return nil unless hook
|
169
|
-
|
170
|
-
Array(hook.method_names).include?(method_name) ? hook.package : nil
|
281
|
+
def lookup_package(cls, method)
|
282
|
+
LookupPackage.new(self, cls, method).package
|
171
283
|
end
|
172
284
|
|
173
|
-
def
|
174
|
-
|
285
|
+
def never_hook?(cls, method)
|
286
|
+
_, separator, = ::AppMap::Hook.qualify_method_name(method)
|
287
|
+
return true if exclude.member?(cls.name) || exclude.member?([ cls.name, separator, method.name ].join)
|
175
288
|
end
|
176
289
|
end
|
177
290
|
end
|
data/lib/appmap/cucumber.rb
CHANGED
@@ -50,7 +50,7 @@ module AppMap
|
|
50
50
|
appmap['metadata'] = update_metadata(scenario, appmap['metadata'])
|
51
51
|
scenario_filename = AppMap::Util.scenario_filename(appmap['metadata']['name'])
|
52
52
|
|
53
|
-
|
53
|
+
AppMap::Util.write_appmap(File.join(APPMAP_OUTPUT_DIR, scenario_filename), JSON.generate(appmap))
|
54
54
|
end
|
55
55
|
|
56
56
|
def enabled?
|
data/lib/appmap/event.rb
CHANGED
@@ -21,10 +21,10 @@ module AppMap
|
|
21
21
|
LIMIT = 100
|
22
22
|
|
23
23
|
class << self
|
24
|
-
def build_from_invocation(
|
25
|
-
|
26
|
-
|
27
|
-
|
24
|
+
def build_from_invocation(event_type, event:)
|
25
|
+
event.id = AppMap::Event.next_id_counter
|
26
|
+
event.event = event_type
|
27
|
+
event.thread_id = Thread.current.object_id
|
28
28
|
end
|
29
29
|
|
30
30
|
# Gets a display string for a value. This is not meant to be a machine deserializable value.
|
@@ -36,7 +36,17 @@ module AppMap
|
|
36
36
|
(value_string||'')[0...LIMIT].encode('utf-8', invalid: :replace, undef: :replace, replace: '_')
|
37
37
|
end
|
38
38
|
|
39
|
-
|
39
|
+
def object_properties(hash_like)
|
40
|
+
hash = hash_like.to_h
|
41
|
+
hash.keys.map do |key|
|
42
|
+
{
|
43
|
+
name: key,
|
44
|
+
class: hash[key].class.name,
|
45
|
+
}
|
46
|
+
end
|
47
|
+
rescue
|
48
|
+
nil
|
49
|
+
end
|
40
50
|
|
41
51
|
# Heuristic for dynamically defined class whose name can be nil
|
42
52
|
def best_class_name(value)
|
@@ -79,25 +89,32 @@ module AppMap
|
|
79
89
|
end
|
80
90
|
end
|
81
91
|
end
|
92
|
+
|
93
|
+
protected
|
94
|
+
|
95
|
+
def object_properties(hash_like)
|
96
|
+
self.class.object_properties(hash_like)
|
97
|
+
end
|
82
98
|
end
|
83
99
|
|
84
100
|
class MethodCall < MethodEvent
|
85
101
|
attr_accessor :defined_class, :method_id, :path, :lineno, :parameters, :receiver, :static
|
86
102
|
|
87
103
|
class << self
|
88
|
-
def build_from_invocation(
|
104
|
+
def build_from_invocation(defined_class, method, receiver, arguments, event: MethodCall.new)
|
105
|
+
event ||= MethodCall.new
|
89
106
|
defined_class ||= 'Class'
|
90
|
-
|
107
|
+
event.tap do
|
91
108
|
static = receiver.is_a?(Module)
|
92
|
-
|
93
|
-
|
109
|
+
event.defined_class = defined_class
|
110
|
+
event.method_id = method.name.to_s
|
94
111
|
if method.source_location
|
95
112
|
path = method.source_location[0]
|
96
113
|
path = path[Dir.pwd.length + 1..-1] if path.index(Dir.pwd) == 0
|
97
|
-
|
98
|
-
|
114
|
+
event.path = path
|
115
|
+
event.lineno = method.source_location[1]
|
99
116
|
else
|
100
|
-
|
117
|
+
event.path = [ defined_class, static ? '.' : '#', method.name ].join
|
101
118
|
end
|
102
119
|
|
103
120
|
# Check if the method has key parameters. If there are any they'll always be last.
|
@@ -105,7 +122,7 @@ module AppMap
|
|
105
122
|
has_key = [[:dummy], *method.parameters].last.first.to_s.start_with?('key') && arguments[-1].is_a?(Hash)
|
106
123
|
kwargs = has_key && arguments[-1].dup || {}
|
107
124
|
|
108
|
-
|
125
|
+
event.parameters = method.parameters.map.with_index do |method_param, idx|
|
109
126
|
param_type, param_name = method_param
|
110
127
|
param_name ||= 'arg'
|
111
128
|
value = case param_type
|
@@ -126,13 +143,13 @@ module AppMap
|
|
126
143
|
kind: param_type
|
127
144
|
}
|
128
145
|
end
|
129
|
-
|
146
|
+
event.receiver = {
|
130
147
|
class: best_class_name(receiver),
|
131
148
|
object_id: receiver.__id__,
|
132
149
|
value: display_string(receiver)
|
133
150
|
}
|
134
|
-
|
135
|
-
MethodEvent.build_from_invocation(
|
151
|
+
event.static = static
|
152
|
+
MethodEvent.build_from_invocation(:call, event: event)
|
136
153
|
end
|
137
154
|
end
|
138
155
|
end
|
@@ -157,11 +174,12 @@ module AppMap
|
|
157
174
|
attr_accessor :parent_id, :elapsed
|
158
175
|
|
159
176
|
class << self
|
160
|
-
def build_from_invocation(
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
177
|
+
def build_from_invocation(parent_id, elapsed: nil, event: MethodReturnIgnoreValue.new)
|
178
|
+
event ||= MethodReturnIgnoreValue.new
|
179
|
+
event.tap do |_|
|
180
|
+
event.parent_id = parent_id
|
181
|
+
event.elapsed = elapsed
|
182
|
+
MethodEvent.build_from_invocation(:return, event: event)
|
165
183
|
end
|
166
184
|
end
|
167
185
|
end
|
@@ -169,7 +187,7 @@ module AppMap
|
|
169
187
|
def to_h
|
170
188
|
super.tap do |h|
|
171
189
|
h[:parent_id] = parent_id
|
172
|
-
h[:elapsed] = elapsed
|
190
|
+
h[:elapsed] = elapsed if elapsed
|
173
191
|
end
|
174
192
|
end
|
175
193
|
end
|
@@ -178,10 +196,11 @@ module AppMap
|
|
178
196
|
attr_accessor :return_value, :exceptions
|
179
197
|
|
180
198
|
class << self
|
181
|
-
def build_from_invocation(
|
182
|
-
|
199
|
+
def build_from_invocation(parent_id, return_value, exception, elapsed: nil, event: MethodReturn.new)
|
200
|
+
event ||= MethodReturn.new
|
201
|
+
event.tap do |_|
|
183
202
|
if return_value
|
184
|
-
|
203
|
+
event.return_value = {
|
185
204
|
class: best_class_name(return_value),
|
186
205
|
value: display_string(return_value),
|
187
206
|
object_id: return_value.__id__
|
@@ -202,9 +221,9 @@ module AppMap
|
|
202
221
|
next_exception = next_exception.cause
|
203
222
|
end
|
204
223
|
|
205
|
-
|
224
|
+
event.exceptions = exceptions
|
206
225
|
end
|
207
|
-
MethodReturnIgnoreValue.build_from_invocation(
|
226
|
+
MethodReturnIgnoreValue.build_from_invocation(parent_id, elapsed: elapsed, event: event)
|
208
227
|
end
|
209
228
|
end
|
210
229
|
end
|