appmap 0.42.1 → 0.46.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/.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
|