appmap 0.67.0 → 0.68.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +34 -0
- data/lib/appmap/builtin_hooks/json.yml +4 -0
- data/lib/appmap/builtin_hooks/net/http.yml +3 -0
- data/lib/appmap/builtin_hooks/openssl.yml +16 -0
- data/lib/appmap/builtin_hooks/yaml.yml +10 -0
- data/lib/appmap/command/agent_setup/validate.rb +8 -1
- data/lib/appmap/command/inspect.rb +0 -1
- data/lib/appmap/config.rb +156 -137
- data/lib/appmap/event.rb +2 -0
- data/lib/appmap/gem_hooks/actionpack.yml +40 -0
- data/lib/appmap/gem_hooks/actionview.yml +13 -0
- data/lib/appmap/gem_hooks/activesupport.yml +12 -0
- data/lib/appmap/gem_hooks/cancancan.yml +6 -0
- data/lib/appmap/hook.rb +46 -30
- data/lib/appmap/util.rb +11 -1
- data/lib/appmap/version.rb +1 -1
- data/spec/config_spec.rb +235 -59
- data/spec/depends/api_spec.rb +1 -1
- data/spec/rails_spec_helper.rb +6 -1
- data/test/agent_setup_validate_test.rb +18 -10
- metadata +10 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7e87a42863f68312980ff6e9689b74489b147b283854463abbf1e2cf6be4fe29
|
4
|
+
data.tar.gz: 0d5a7e86d9cea6ba154e6bc1ceb920fcba978074afd4351663832946d0ab0f30
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bc509802c9b69cca4cc88560b23b0d56e6c72f852e21dc414f5732e6e4cbfe8867b7dcfa132673e2a647f072bcafbe0086e2cbc15b2280eb3b5e3fcaef2db577
|
7
|
+
data.tar.gz: e48934b5719439fa61970e7d239b163c6f42c0e9f5bd7c4a7959f73b62ff9e610fb967b124698129c96271a124d8288e9069e073f1cdeeffc53060270cf30c26
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,37 @@
|
|
1
|
+
## [0.68.2](https://github.com/applandinc/appmap-ruby/compare/v0.68.1...v0.68.2) (2021-11-25)
|
2
|
+
|
3
|
+
|
4
|
+
### Bug Fixes
|
5
|
+
|
6
|
+
* Missing gems will no longer attempt to be hooked ([ac6cf26](https://github.com/applandinc/appmap-ruby/commit/ac6cf264897e492c73ba4b66233709eb4eaf7b36))
|
7
|
+
|
8
|
+
## [0.68.1](https://github.com/applandinc/appmap-ruby/compare/v0.68.0...v0.68.1) (2021-11-12)
|
9
|
+
|
10
|
+
|
11
|
+
### Bug Fixes
|
12
|
+
|
13
|
+
* Support new style of `functions` syntax in appmap.yml ([dca327c](https://github.com/applandinc/appmap-ruby/commit/dca327c98db1bddf849056995541306a5fc07eea))
|
14
|
+
|
15
|
+
# [0.68.0](https://github.com/applandinc/appmap-ruby/compare/v0.67.1...v0.68.0) (2021-11-05)
|
16
|
+
|
17
|
+
|
18
|
+
### Bug Fixes
|
19
|
+
|
20
|
+
* Require weakref ([2f94f80](https://github.com/applandinc/appmap-ruby/commit/2f94f808bd3327aa3fc7fd8e6a3428a5da3a29bb))
|
21
|
+
|
22
|
+
|
23
|
+
### Features
|
24
|
+
|
25
|
+
* Externalize config of hooks ([8080222](https://github.com/applandinc/appmap-ruby/commit/8080222ce5b61d9824eaf20410d7b9b94b679890))
|
26
|
+
* Support loading hook config via path env vars ([4856483](https://github.com/applandinc/appmap-ruby/commit/48564837784f8b0e87c4286ad3e2f6cb2d272dcf))
|
27
|
+
|
28
|
+
## [0.67.1](https://github.com/applandinc/appmap-ruby/compare/v0.67.0...v0.67.1) (2021-11-02)
|
29
|
+
|
30
|
+
|
31
|
+
### Bug Fixes
|
32
|
+
|
33
|
+
* Don't try to index AppMaps when inspecting ([ca18861](https://github.com/applandinc/appmap-ruby/commit/ca188619bd7085caa75a06eeeb5d5a92213251ac))
|
34
|
+
|
1
35
|
# [0.67.0](https://github.com/applandinc/appmap-ruby/compare/v0.66.2...v0.67.0) (2021-10-21)
|
2
36
|
|
3
37
|
|
@@ -0,0 +1,16 @@
|
|
1
|
+
- method: OpenSSL::PKey::PKey#sign
|
2
|
+
label: crypto.pkey
|
3
|
+
- methods:
|
4
|
+
- OpenSSL::X509::Request#sign
|
5
|
+
- OpenSSL::X509::Request#verify
|
6
|
+
label: crypto.x509
|
7
|
+
- method: OpenSSL::X509::Certificate#sign
|
8
|
+
label: crypto.x509
|
9
|
+
- methods:
|
10
|
+
- OpenSSL::PKCS5#pbkdf2_hmac
|
11
|
+
- OpenSSL::PKCS5#pbkdf2_hmac_sha1
|
12
|
+
label: crypto.pkcs5
|
13
|
+
- method: OpenSSL::Cipher#encrypt
|
14
|
+
label: crypto.encrypt
|
15
|
+
- method: OpenSSL::Cipher#decrypt
|
16
|
+
label: crypto.decrypt
|
@@ -10,7 +10,14 @@ module AppMap
|
|
10
10
|
|
11
11
|
class Validate < ValidateStruct
|
12
12
|
def perform
|
13
|
-
|
13
|
+
schema_path = File.expand_path('../../../../../config-schema.yml', __FILE__)
|
14
|
+
schema = YAML.safe_load(File.read(schema_path))
|
15
|
+
result = {
|
16
|
+
version: 2,
|
17
|
+
errors: config_validator.valid? ? [] : config_validator.violations.map(&:to_h),
|
18
|
+
schema: schema
|
19
|
+
}
|
20
|
+
puts JSON.pretty_generate(result)
|
14
21
|
end
|
15
22
|
|
16
23
|
private
|
data/lib/appmap/config.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'pathname'
|
3
4
|
require 'set'
|
4
5
|
require 'yaml'
|
5
6
|
require 'appmap/util'
|
@@ -11,17 +12,20 @@ require 'appmap/depends/configuration'
|
|
11
12
|
|
12
13
|
module AppMap
|
13
14
|
class Config
|
14
|
-
# Specifies a code
|
15
|
+
# Specifies a logical code package be mapped.
|
16
|
+
# This can be a project source folder, a Gem, or a builtin.
|
17
|
+
#
|
15
18
|
# Options:
|
16
19
|
#
|
20
|
+
# * +path+ indicates a relative path to a code folder.
|
17
21
|
# * +gem+ may indicate a gem name that "owns" the path
|
18
|
-
# * +
|
22
|
+
# * +require_name+ can be used to make sure that the code is required so that it can be loaded. This is generally used with
|
19
23
|
# builtins, or when the path to be required is not automatically required when bundler requires the gem.
|
20
24
|
# * +exclude+ can be used used to exclude sub-paths. Generally not used with +gem+.
|
21
25
|
# * +labels+ is used to apply labels to matching code. This is really only useful when the package will be applied to
|
22
26
|
# specific functions, via TargetMethods.
|
23
27
|
# * +shallow+ indicates shallow mapping, in which only the entrypoint to a gem is recorded.
|
24
|
-
Package = Struct.new(:path, :gem, :
|
28
|
+
Package = Struct.new(:name, :path, :gem, :require_name, :exclude, :labels, :shallow, :builtin) do
|
25
29
|
# This is for internal use only.
|
26
30
|
private_methods :gem
|
27
31
|
|
@@ -43,20 +47,24 @@ module AppMap
|
|
43
47
|
class << self
|
44
48
|
# Builds a package for a path, such as `app/models` in a Rails app. Generally corresponds to a `path:` entry
|
45
49
|
# in appmap.yml. Also used for mapping specific methods via TargetMethods.
|
46
|
-
def build_from_path(path, shallow: false,
|
47
|
-
Package.new(path, nil,
|
50
|
+
def build_from_path(path, shallow: false, require_name: nil, exclude: [], labels: [])
|
51
|
+
Package.new(path, path, nil, require_name, exclude, labels, shallow)
|
52
|
+
end
|
53
|
+
|
54
|
+
def build_from_builtin(path, shallow: false, require_name: nil, exclude: [], labels: [])
|
55
|
+
Package.new(path, path, nil, require_name, exclude, labels, shallow, true)
|
48
56
|
end
|
49
57
|
|
50
58
|
# Builds a package for gem. Generally corresponds to a `gem:` entry in appmap.yml. Also used when mapping
|
51
59
|
# a builtin.
|
52
|
-
def build_from_gem(gem, shallow: true,
|
60
|
+
def build_from_gem(gem, shallow: true, require_name: nil, exclude: [], labels: [], optional: false, force: false)
|
53
61
|
if !force && %w[method_source activesupport].member?(gem)
|
54
62
|
warn "WARNING: #{gem} cannot be AppMapped because it is a dependency of the appmap gem"
|
55
63
|
return
|
56
64
|
end
|
57
65
|
path = gem_path(gem, optional)
|
58
66
|
if path
|
59
|
-
Package.new(path, gem,
|
67
|
+
Package.new(gem, path, gem, require_name, exclude, labels, shallow)
|
60
68
|
else
|
61
69
|
AppMap::Util.startup_message "#{gem} is not available in the bundle"
|
62
70
|
end
|
@@ -74,19 +82,16 @@ module AppMap
|
|
74
82
|
end
|
75
83
|
end
|
76
84
|
|
77
|
-
def name
|
78
|
-
gem || path
|
79
|
-
end
|
80
|
-
|
81
85
|
def to_h
|
82
86
|
{
|
87
|
+
name: name,
|
83
88
|
path: path,
|
84
|
-
package_name: package_name,
|
85
89
|
gem: gem,
|
86
|
-
|
90
|
+
require_name: require_name,
|
91
|
+
handler_class: handler_class ? handler_class.name : nil,
|
87
92
|
exclude: Util.blank?(exclude) ? nil : exclude,
|
88
93
|
labels: Util.blank?(labels) ? nil : labels,
|
89
|
-
shallow: shallow
|
94
|
+
shallow: shallow.nil? ? nil : shallow,
|
90
95
|
}.compact
|
91
96
|
end
|
92
97
|
end
|
@@ -96,12 +101,12 @@ module AppMap
|
|
96
101
|
attr_reader :method_names, :package
|
97
102
|
|
98
103
|
def initialize(method_names, package)
|
99
|
-
@method_names = method_names
|
104
|
+
@method_names = Array(method_names).map(&:to_sym)
|
100
105
|
@package = package
|
101
106
|
end
|
102
107
|
|
103
108
|
def include_method?(method_name)
|
104
|
-
|
109
|
+
method_names.include?(method_name.to_sym)
|
105
110
|
end
|
106
111
|
|
107
112
|
def to_h
|
@@ -110,6 +115,8 @@ module AppMap
|
|
110
115
|
method_names: method_names
|
111
116
|
}
|
112
117
|
end
|
118
|
+
|
119
|
+
alias as_json to_h
|
113
120
|
end
|
114
121
|
private_constant :TargetMethods
|
115
122
|
|
@@ -117,11 +124,11 @@ module AppMap
|
|
117
124
|
# entry in appmap.yml. When the Config is initialized, each Function is converted into
|
118
125
|
# a Package and TargetMethods. It's called a Function rather than a Method, because Function
|
119
126
|
# is the AppMap terminology.
|
120
|
-
Function = Struct.new(:package, :cls, :labels, :function_names, :builtin, :
|
127
|
+
Function = Struct.new(:package, :cls, :labels, :function_names, :builtin, :require_name) do # :nodoc:
|
121
128
|
def to_h
|
122
129
|
{
|
123
130
|
package: package,
|
124
|
-
|
131
|
+
require_name: require_name,
|
125
132
|
class: cls,
|
126
133
|
labels: labels,
|
127
134
|
functions: function_names.map(&:to_sym),
|
@@ -138,9 +145,15 @@ module AppMap
|
|
138
145
|
private_constant :MethodHook
|
139
146
|
|
140
147
|
class << self
|
141
|
-
def package_hooks(
|
148
|
+
def package_hooks(methods, path: nil, gem: nil, force: false, builtin: false, handler_class: nil, require_name: nil)
|
142
149
|
Array(methods).map do |method|
|
143
|
-
package =
|
150
|
+
package = if builtin
|
151
|
+
Package.build_from_builtin(path, require_name: require_name, labels: method.labels, shallow: false)
|
152
|
+
elsif gem
|
153
|
+
Package.build_from_gem(gem, require_name: require_name, labels: method.labels, shallow: false, force: force, optional: true)
|
154
|
+
elsif path
|
155
|
+
Package.build_from_path(path, require_name: require_name, labels: method.labels, shallow: false)
|
156
|
+
end
|
144
157
|
next unless package
|
145
158
|
|
146
159
|
package.handler_class = handler_class if handler_class
|
@@ -151,87 +164,107 @@ module AppMap
|
|
151
164
|
def method_hook(cls, method_names, labels)
|
152
165
|
MethodHook.new(cls, method_names, labels)
|
153
166
|
end
|
167
|
+
|
168
|
+
def declare_hook(hook_decl)
|
169
|
+
hook_decl = YAML.load(hook_decl) if hook_decl.is_a?(String)
|
170
|
+
|
171
|
+
methods_decl = hook_decl['methods'] || hook_decl['method']
|
172
|
+
methods_decl = Array(methods_decl) unless methods_decl.is_a?(Hash)
|
173
|
+
labels_decl = Array(hook_decl['labels'] || hook_decl['label'])
|
174
|
+
|
175
|
+
methods = methods_decl.map do |name|
|
176
|
+
class_name, method_name, static = name.include?('.') ? name.split('.', 2) + [ true ] : name.split('#', 2) + [ false ]
|
177
|
+
method_hook class_name, [ method_name ], labels_decl
|
178
|
+
end
|
179
|
+
|
180
|
+
require_name = hook_decl['require_name']
|
181
|
+
gem_name = hook_decl['gem']
|
182
|
+
path = hook_decl['path']
|
183
|
+
builtin = hook_decl['builtin']
|
184
|
+
|
185
|
+
options = {
|
186
|
+
builtin: builtin,
|
187
|
+
gem: gem_name,
|
188
|
+
path: path,
|
189
|
+
require_name: require_name || gem_name || path,
|
190
|
+
force: hook_decl['force']
|
191
|
+
}.compact
|
192
|
+
|
193
|
+
handler_class = hook_decl['handler_class']
|
194
|
+
options[:handler_class] = Util::class_from_string(handler_class) if handler_class
|
195
|
+
|
196
|
+
package_hooks(methods, **options)
|
197
|
+
end
|
198
|
+
|
199
|
+
def declare_hook_deprecated(hook_decl)
|
200
|
+
function_name = hook_decl['name']
|
201
|
+
package, cls, functions = []
|
202
|
+
if function_name
|
203
|
+
package, cls, _, function = Util.parse_function_name(function_name)
|
204
|
+
functions = Array(function)
|
205
|
+
else
|
206
|
+
package = hook_decl['package']
|
207
|
+
cls = hook_decl['class']
|
208
|
+
functions = hook_decl['function'] || hook_decl['functions']
|
209
|
+
raise %q(AppMap config 'function' element should specify 'package', 'class' and 'function' or 'functions') unless package && cls && functions
|
210
|
+
end
|
211
|
+
|
212
|
+
functions = Array(functions).map(&:to_sym)
|
213
|
+
labels = hook_decl['label'] || hook_decl['labels']
|
214
|
+
req = hook_decl['require']
|
215
|
+
builtin = hook_decl['builtin']
|
216
|
+
|
217
|
+
package_options = {}
|
218
|
+
package_options[:labels] = Array(labels).map(&:to_s) if labels if labels
|
219
|
+
package_options[:require_name] = req
|
220
|
+
package_options[:require_name] ||= package if builtin
|
221
|
+
tm = TargetMethods.new(functions, Package.build_from_path(package, **package_options))
|
222
|
+
ClassTargetMethods.new(cls, tm)
|
223
|
+
end
|
224
|
+
|
225
|
+
def builtin_hooks_path
|
226
|
+
[ [ __dir__, 'builtin_hooks' ].join('/') ] + ( ENV['APPMAP_BUILTIN_HOOKS_PATH'] || '').split(/[;:]/)
|
227
|
+
end
|
228
|
+
|
229
|
+
def gem_hooks_path
|
230
|
+
[ [ __dir__, 'gem_hooks' ].join('/') ] + ( ENV['APPMAP_GEM_HOOKS_PATH'] || '').split(/[;:]/)
|
231
|
+
end
|
232
|
+
|
233
|
+
def load_hooks
|
234
|
+
loader = lambda do |dir, &block|
|
235
|
+
basename = dir.split('/').compact.join('/')
|
236
|
+
[].tap do |hooks|
|
237
|
+
Dir.glob(Pathname.new(dir).join('**').join('*.yml').to_s).each do |yaml_file|
|
238
|
+
path = yaml_file[basename.length + 1...-4]
|
239
|
+
YAML.load(File.read(yaml_file)).map do |config|
|
240
|
+
block.call path, config
|
241
|
+
config
|
242
|
+
end.each do |config|
|
243
|
+
hooks << declare_hook(config)
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end.compact
|
247
|
+
end
|
248
|
+
|
249
|
+
builtin_hooks = builtin_hooks_path.map do |path|
|
250
|
+
loader.(path) do |path, config|
|
251
|
+
config['path'] = path
|
252
|
+
config['builtin'] = true
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
gem_hooks = gem_hooks_path.map do |path|
|
257
|
+
loader.(path) do |path, config|
|
258
|
+
config['gem'] = path
|
259
|
+
config['builtin'] = false
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
(builtin_hooks + gem_hooks).flatten
|
264
|
+
end
|
154
265
|
end
|
155
266
|
|
156
|
-
|
157
|
-
# predefined labels specified here. If any of these hooks are not desired, they can be disabled in the +exclude+ section
|
158
|
-
# of appmap.yml.
|
159
|
-
METHOD_HOOKS = [
|
160
|
-
package_hooks('actionview',
|
161
|
-
[
|
162
|
-
method_hook('ActionView::Renderer', :render, %w[mvc.view]),
|
163
|
-
method_hook('ActionView::TemplateRenderer', :render, %w[mvc.view]),
|
164
|
-
method_hook('ActionView::PartialRenderer', :render, %w[mvc.view])
|
165
|
-
],
|
166
|
-
handler_class: AppMap::Handler::Rails::Template::RenderHandler,
|
167
|
-
package_name: 'action_view'
|
168
|
-
),
|
169
|
-
package_hooks('actionview',
|
170
|
-
[
|
171
|
-
method_hook('ActionView::Resolver', %i[find_all find_all_anywhere], %w[mvc.template.resolver])
|
172
|
-
],
|
173
|
-
handler_class: AppMap::Handler::Rails::Template::ResolverHandler,
|
174
|
-
package_name: 'action_view'
|
175
|
-
),
|
176
|
-
package_hooks('actionpack',
|
177
|
-
[
|
178
|
-
method_hook('ActionDispatch::Request::Session', %i[[] dig values fetch], %w[http.session.read]),
|
179
|
-
method_hook('ActionDispatch::Request::Session', %i[destroy []= clear update delete merge], %w[http.session.write]),
|
180
|
-
method_hook('ActionDispatch::Cookies::CookieJar', %i[[] fetch], %w[http.session.read]),
|
181
|
-
method_hook('ActionDispatch::Cookies::CookieJar', %i[[]= clear update delete recycle], %w[http.session.write]),
|
182
|
-
method_hook('ActionDispatch::Cookies::EncryptedCookieJar', %i[[]= clear update delete recycle], %w[http.cookie crypto.encrypt])
|
183
|
-
],
|
184
|
-
package_name: 'action_dispatch'
|
185
|
-
),
|
186
|
-
package_hooks('cancancan',
|
187
|
-
[
|
188
|
-
method_hook('CanCan::ControllerAdditions', %i[authorize! can? cannot?], %w[security.authorization]),
|
189
|
-
method_hook('CanCan::Ability', %i[authorize?], %w[security.authorization])
|
190
|
-
]
|
191
|
-
),
|
192
|
-
package_hooks('actionpack',
|
193
|
-
[
|
194
|
-
method_hook('ActionController::Instrumentation', %i[process_action send_file send_data redirect_to], %w[mvc.controller])
|
195
|
-
],
|
196
|
-
package_name: 'action_controller'
|
197
|
-
)
|
198
|
-
].flatten.freeze
|
199
|
-
|
200
|
-
OPENSSL_PACKAGES = ->(labels) { Package.build_from_path('openssl', package_name: 'openssl', labels: labels) }
|
201
|
-
|
202
|
-
# Hook functions which are builtin to Ruby. Because they are builtins, they may be loaded before appmap.
|
203
|
-
# Therefore, we can't rely on TracePoint to report the loading of this code.
|
204
|
-
BUILTIN_HOOKS = {
|
205
|
-
'OpenSSL::PKey::PKey' => TargetMethods.new(:sign, OPENSSL_PACKAGES.(%w[crypto.pkey])),
|
206
|
-
'OpenSSL::X509::Request' => TargetMethods.new(%i[sign verify], OPENSSL_PACKAGES.(%w[crypto.x509])),
|
207
|
-
'OpenSSL::PKCS5' => TargetMethods.new(%i[pbkdf2_hmac_sha1 pbkdf2_hmac], OPENSSL_PACKAGES.(%w[crypto.pkcs5])),
|
208
|
-
'OpenSSL::Cipher' => [
|
209
|
-
TargetMethods.new(%i[encrypt], OPENSSL_PACKAGES.(%w[crypto.encrypt])),
|
210
|
-
TargetMethods.new(%i[decrypt], OPENSSL_PACKAGES.(%w[crypto.decrypt]))
|
211
|
-
],
|
212
|
-
'ActiveSupport::Callbacks::CallbackSequence' => [
|
213
|
-
TargetMethods.new(:invoke_before, Package.build_from_gem('activesupport', force: true, package_name: 'active_support', labels: %w[mvc.before_action])),
|
214
|
-
TargetMethods.new(:invoke_after, Package.build_from_gem('activesupport', force: true, package_name: 'active_support', labels: %w[mvc.after_action])),
|
215
|
-
],
|
216
|
-
'ActiveSupport::SecurityUtils' => TargetMethods.new(:secure_compare, Package.build_from_gem('activesupport', force: true, package_name: 'active_support/security_utils', labels: %w[crypto.secure_compare])),
|
217
|
-
'OpenSSL::X509::Certificate' => TargetMethods.new(:sign, OPENSSL_PACKAGES.(%w[crypto.x509])),
|
218
|
-
'Net::HTTP' => TargetMethods.new(:request, Package.build_from_path('net/http', package_name: 'net/http', labels: %w[protocol.http]).tap do |package|
|
219
|
-
package.handler_class = AppMap::Handler::NetHTTP
|
220
|
-
end),
|
221
|
-
'Net::SMTP' => TargetMethods.new(:send, Package.build_from_path('net/smtp', package_name: 'net/smtp', labels: %w[protocol.email.smtp])),
|
222
|
-
'Net::POP3' => TargetMethods.new(:mails, Package.build_from_path('net/pop3', package_name: 'net/pop', labels: %w[protocol.email.pop])),
|
223
|
-
# This is happening: Method send_command not found on Net::IMAP
|
224
|
-
# 'Net::IMAP' => TargetMethods.new(:send_command, Package.build_from_path('net/imap', package_name: 'net/imap', labels: %w[protocol.email.imap])),
|
225
|
-
# 'Marshal' => TargetMethods.new(%i[dump load], Package.build_from_path('marshal', labels: %w[format.marshal])),
|
226
|
-
'Psych' => [
|
227
|
-
TargetMethods.new(%i[load load_stream parse parse_stream], Package.build_from_path('yaml', package_name: 'psych', labels: %w[format.yaml.parse])),
|
228
|
-
TargetMethods.new(%i[dump dump_stream], Package.build_from_path('yaml', package_name: 'psych', labels: %w[format.yaml.generate])),
|
229
|
-
],
|
230
|
-
'JSON::Ext::Parser' => TargetMethods.new(:parse, Package.build_from_path('json', package_name: 'json', labels: %w[format.json.parse])),
|
231
|
-
'JSON::Ext::Generator::State' => TargetMethods.new(:generate, Package.build_from_path('json', package_name: 'json', labels: %w[format.json.generate])),
|
232
|
-
}.freeze
|
233
|
-
|
234
|
-
attr_reader :name, :appmap_dir, :packages, :exclude, :swagger_config, :depends_config, :hooked_methods, :builtin_hooks
|
267
|
+
attr_reader :name, :appmap_dir, :packages, :exclude, :swagger_config, :depends_config, :gem_hooks, :builtin_hooks
|
235
268
|
|
236
269
|
def initialize(name,
|
237
270
|
packages: [],
|
@@ -246,31 +279,22 @@ module AppMap
|
|
246
279
|
@depends_config = depends_config
|
247
280
|
@hook_paths = Set.new(packages.map(&:path))
|
248
281
|
@exclude = exclude
|
249
|
-
@builtin_hooks = BUILTIN_HOOKS.dup
|
250
282
|
@functions = functions
|
251
283
|
|
252
|
-
@
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
package_options[:labels] = func.labels if func.labels
|
259
|
-
package_options[:package_name] = func.package_name
|
260
|
-
package_options[:package_name] ||= func.package if func.builtin
|
261
|
-
hook = TargetMethods.new(func.function_names, Package.build_from_path(func.package, **package_options))
|
262
|
-
if func.builtin
|
263
|
-
@builtin_hooks[func.cls] ||= []
|
264
|
-
@builtin_hooks[func.cls] << hook
|
284
|
+
@builtin_hooks = Hash.new { |h,k| h[k] = [] }
|
285
|
+
@gem_hooks = Hash.new { |h,k| h[k] = [] }
|
286
|
+
|
287
|
+
(functions + self.class.load_hooks).each_with_object(Hash.new { |h,k| h[k] = [] }) do |cls_target_methods, gem_hooks|
|
288
|
+
hooks = if cls_target_methods.target_methods.package.builtin
|
289
|
+
@builtin_hooks
|
265
290
|
else
|
266
|
-
@
|
291
|
+
@gem_hooks
|
267
292
|
end
|
293
|
+
hooks[cls_target_methods.cls] << cls_target_methods.target_methods
|
268
294
|
end
|
269
295
|
|
270
|
-
@
|
271
|
-
Array(hooks).
|
272
|
-
@hook_paths << hook.package.path
|
273
|
-
end
|
296
|
+
@gem_hooks.each_value do |hooks|
|
297
|
+
@hook_paths += Array(hooks).map { |hook| hook.package.path }.compact
|
274
298
|
end
|
275
299
|
end
|
276
300
|
|
@@ -334,24 +358,15 @@ module AppMap
|
|
334
358
|
}.compact
|
335
359
|
|
336
360
|
if config_data['functions']
|
337
|
-
config_params[:functions] = config_data['functions'].map do |
|
338
|
-
|
339
|
-
|
340
|
-
if function_name
|
341
|
-
package, cls, _, function = Util.parse_function_name(function_name)
|
342
|
-
functions = Array(function)
|
361
|
+
config_params[:functions] = config_data['functions'].map do |hook_decl|
|
362
|
+
if hook_decl['name'] || hook_decl['package']
|
363
|
+
declare_hook_deprecated(hook_decl)
|
343
364
|
else
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
raise %q(AppMap config 'function' element should specify 'package', 'class' and 'function' or 'functions') unless package && cls && functions
|
365
|
+
# Support the same syntax within the 'functions' that's used for externalized
|
366
|
+
# hook config.
|
367
|
+
declare_hook(hook_decl)
|
348
368
|
end
|
349
|
-
|
350
|
-
functions = Array(functions).map(&:to_sym)
|
351
|
-
labels = function_data['label'] || function_data['labels']
|
352
|
-
labels = Array(labels).map(&:to_s) if labels
|
353
|
-
Function.new(package, cls, labels, functions, function_data['builtin'], function_data['require'])
|
354
|
-
end
|
369
|
+
end.flatten
|
355
370
|
end
|
356
371
|
|
357
372
|
config_params[:packages] = \
|
@@ -366,7 +381,11 @@ module AppMap
|
|
366
381
|
shallow = package['shallow']
|
367
382
|
# shallow is true by default for gems
|
368
383
|
shallow = true if shallow.nil?
|
369
|
-
|
384
|
+
|
385
|
+
require_name = \
|
386
|
+
package['package'] || #deprecated
|
387
|
+
package['require_name']
|
388
|
+
Package.build_from_gem(gem, require_name: require_name, exclude: package['exclude'] || [], shallow: shallow)
|
370
389
|
else
|
371
390
|
Package.build_from_path(path, exclude: package['exclude'] || [], shallow: package['shallow'])
|
372
391
|
end
|
@@ -417,8 +436,8 @@ module AppMap
|
|
417
436
|
|
418
437
|
# Hook a method which is specified by class and method name.
|
419
438
|
def package_for_code_object
|
420
|
-
|
421
|
-
|
439
|
+
class_name = cls.to_s.index('#<Class:') == 0 ? cls.to_s['#<Class:'.length...-1] : cls.name
|
440
|
+
Array(config.gem_hooks[class_name])
|
422
441
|
.find { |hook| hook.include_method?(method.name) }
|
423
442
|
&.package
|
424
443
|
end
|
data/lib/appmap/event.rb
CHANGED
@@ -0,0 +1,40 @@
|
|
1
|
+
- methods:
|
2
|
+
- ActionDispatch::Request::Session#[]
|
3
|
+
- ActionDispatch::Request::Session#dig
|
4
|
+
- ActionDispatch::Request::Session#values
|
5
|
+
- ActionDispatch::Request::Session#fetch
|
6
|
+
- ActionDispatch::Cookies::CookieJar#[]
|
7
|
+
- ActionDispatch::Cookies::CookieJar#fetch
|
8
|
+
label: http.session.read
|
9
|
+
require_name: action_dispatch
|
10
|
+
- methods:
|
11
|
+
- ActionDispatch::Request::Session#destroy
|
12
|
+
- ActionDispatch::Request::Session#[]=
|
13
|
+
- ActionDispatch::Request::Session#clear
|
14
|
+
- ActionDispatch::Request::Session#update
|
15
|
+
- ActionDispatch::Request::Session#delete
|
16
|
+
- ActionDispatch::Request::Session#merge
|
17
|
+
- ActionDispatch::Cookies::CookieJar#[]=
|
18
|
+
- ActionDispatch::Cookies::CookieJar#clear
|
19
|
+
- ActionDispatch::Cookies::CookieJar#update
|
20
|
+
- ActionDispatch::Cookies::CookieJar#delete
|
21
|
+
- ActionDispatch::Cookies::CookieJar#recycle!
|
22
|
+
label: http.session.write
|
23
|
+
require_name: action_dispatch
|
24
|
+
- methods:
|
25
|
+
- ActionDispatch::Cookies::EncryptedCookieJar#[]=
|
26
|
+
- ActionDispatch::Cookies::EncryptedCookieJar#clear
|
27
|
+
- ActionDispatch::Cookies::EncryptedCookieJar#update
|
28
|
+
- ActionDispatch::Cookies::EncryptedCookieJar#delete
|
29
|
+
- ActionDispatch::Cookies::EncryptedCookieJar#recycle
|
30
|
+
labels:
|
31
|
+
- http.cookie
|
32
|
+
- crypto.encrypt
|
33
|
+
require_name: action_dispatch
|
34
|
+
- methods:
|
35
|
+
- ActionController::Instrumentation#process_action
|
36
|
+
- ActionController::Instrumentation#send_file
|
37
|
+
- ActionController::Instrumentation#send_data
|
38
|
+
- ActionController::Instrumentation#redirect_to
|
39
|
+
label: mvc.controller
|
40
|
+
require_name: action_controller
|
@@ -0,0 +1,13 @@
|
|
1
|
+
- methods:
|
2
|
+
- ActionView::Renderer#render
|
3
|
+
- ActionView::TemplateRenderer#render
|
4
|
+
- ActionView::PartialRenderer#render
|
5
|
+
label: mvc.view
|
6
|
+
handler_class: AppMap::Handler::Rails::Template::RenderHandler
|
7
|
+
require_name: action_view
|
8
|
+
- methods:
|
9
|
+
- ActionView::Resolver#find_all
|
10
|
+
- ActionView::Resolver#find_all_anywhere
|
11
|
+
label: mvc.template.resolver
|
12
|
+
handler_class: AppMap::Handler::Rails::Template::ResolverHandler
|
13
|
+
require_name: action_view
|
@@ -0,0 +1,12 @@
|
|
1
|
+
- method: ActiveSupport::Callbacks::CallbackSequence#invoke_before
|
2
|
+
label: mvc.before_action
|
3
|
+
require_name: active_support
|
4
|
+
force: true
|
5
|
+
- method: ActiveSupport::Callbacks::CallbackSequence#invoke_after
|
6
|
+
label: mvc.after_action
|
7
|
+
require_name: active_support
|
8
|
+
force: true
|
9
|
+
- method: ActiveSupport::SecurityUtils#secure_compare
|
10
|
+
label: crypto.secure_compare
|
11
|
+
require_name: active_support/security_utils
|
12
|
+
force: true
|
data/lib/appmap/hook.rb
CHANGED
@@ -15,10 +15,23 @@ module AppMap
|
|
15
15
|
@method_arity = ::Method.instance_method(:arity)
|
16
16
|
|
17
17
|
class << self
|
18
|
-
def
|
19
|
-
|
18
|
+
def hook_builtins?
|
19
|
+
Mutex.new.synchronize do
|
20
|
+
@hook_builtins = true if @hook_builtins.nil?
|
20
21
|
|
21
|
-
|
22
|
+
return false unless @hook_builtins
|
23
|
+
|
24
|
+
@hook_builtins = false
|
25
|
+
true
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def already_hooked?(method)
|
30
|
+
# After a method is defined, the statement "module_function <the-method>" can convert that method
|
31
|
+
# into a module (class) method. The method is hooked first when it's defined, then AppMap will attempt to
|
32
|
+
# hook it again when it's redefined as a module method. So we check the method source location - if it's
|
33
|
+
# part of the AppMap source tree, we ignore it.
|
34
|
+
method.source_location && method.source_location[0].index(__dir__) == 0
|
22
35
|
end
|
23
36
|
|
24
37
|
# Return the class, separator ('.' or '#'), and method name for
|
@@ -79,42 +92,43 @@ module AppMap
|
|
79
92
|
# hook_builtins builds hooks for code that is built in to the Ruby standard library.
|
80
93
|
# No TracePoint events are emitted for builtins, so a separate hooking mechanism is needed.
|
81
94
|
def hook_builtins
|
82
|
-
return unless self.class.
|
95
|
+
return unless self.class.hook_builtins?
|
83
96
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
end
|
97
|
+
hook_loaded_code = lambda do |hooks_by_class, builtin|
|
98
|
+
hooks_by_class.each do |class_name, hooks|
|
99
|
+
Array(hooks).each do |hook|
|
100
|
+
require hook.package.require_name if builtin && hook.package.require_name && hook.package.require_name != 'ruby'
|
89
101
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
method_name = method_name.to_sym
|
95
|
-
base_cls = class_from_string.(class_name)
|
102
|
+
Array(hook.method_names).each do |method_name|
|
103
|
+
method_name = method_name.to_sym
|
104
|
+
base_cls = Util::class_from_string(class_name, must: false)
|
105
|
+
next unless base_cls
|
96
106
|
|
97
|
-
|
98
|
-
|
99
|
-
|
107
|
+
hook_method = lambda do |entry|
|
108
|
+
cls, method = entry
|
109
|
+
return false if config.never_hook?(cls, method)
|
100
110
|
|
101
|
-
|
102
|
-
|
111
|
+
Hook::Method.new(hook.package, cls, method).activate
|
112
|
+
end
|
103
113
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
+
methods = []
|
115
|
+
methods << [ base_cls, base_cls.public_instance_method(method_name) ] rescue nil
|
116
|
+
if base_cls.respond_to?(:singleton_class)
|
117
|
+
methods << [ base_cls.singleton_class, base_cls.singleton_class.public_instance_method(method_name) ] rescue nil
|
118
|
+
end
|
119
|
+
methods.compact!
|
120
|
+
if methods.empty?
|
121
|
+
warn "Method #{method_name} not found on #{base_cls.name}" if LOG
|
122
|
+
else
|
123
|
+
methods.each(&hook_method)
|
124
|
+
end
|
114
125
|
end
|
115
126
|
end
|
116
127
|
end
|
117
128
|
end
|
129
|
+
|
130
|
+
hook_loaded_code.(config.builtin_hooks, true)
|
131
|
+
hook_loaded_code.(config.gem_hooks, false)
|
118
132
|
end
|
119
133
|
|
120
134
|
protected
|
@@ -165,6 +179,8 @@ module AppMap
|
|
165
179
|
next
|
166
180
|
end
|
167
181
|
|
182
|
+
next if self.class.already_hooked?(method)
|
183
|
+
|
168
184
|
warn "AppMap: Examining #{hook_cls} #{method.name}" if LOG
|
169
185
|
|
170
186
|
disasm = RubyVM::InstructionSequence.disasm(method)
|
data/lib/appmap/util.rb
CHANGED
@@ -21,6 +21,14 @@ module AppMap
|
|
21
21
|
WHITE = "\e[37m"
|
22
22
|
|
23
23
|
class << self
|
24
|
+
def class_from_string(fq_class, must: true)
|
25
|
+
fq_class.split('::').inject(Object) do |mod, class_name|
|
26
|
+
mod.const_get(class_name)
|
27
|
+
end
|
28
|
+
rescue NameError
|
29
|
+
raise if must
|
30
|
+
end
|
31
|
+
|
24
32
|
def parse_function_name(name)
|
25
33
|
package_tokens = name.split('/')
|
26
34
|
|
@@ -218,7 +226,9 @@ module AppMap
|
|
218
226
|
elsif ENV['DEBUG'] == 'true'
|
219
227
|
warn msg
|
220
228
|
end
|
221
|
-
|
229
|
+
|
230
|
+
nil
|
231
|
+
end
|
222
232
|
|
223
233
|
def ruby_minor_version
|
224
234
|
@ruby_minor_version ||= RUBY_VERSION.split('.')[0..1].join('.').to_f
|
data/lib/appmap/version.rb
CHANGED
data/spec/config_spec.rb
CHANGED
@@ -4,83 +4,258 @@ require 'rails_spec_helper'
|
|
4
4
|
require 'appmap/config'
|
5
5
|
|
6
6
|
describe AppMap::Config, docker: false do
|
7
|
-
it 'loads
|
7
|
+
it 'loads as expected' do
|
8
8
|
config_data = {
|
9
|
-
exclude: [],
|
10
9
|
name: 'test',
|
11
|
-
packages: [
|
10
|
+
packages: [],
|
11
|
+
functions: [
|
12
12
|
{
|
13
|
-
|
13
|
+
name: 'pkg/cls#fn',
|
14
14
|
},
|
15
15
|
{
|
16
|
-
|
17
|
-
|
18
|
-
}
|
19
|
-
],
|
20
|
-
functions: [
|
21
|
-
{
|
22
|
-
package: 'pkg',
|
23
|
-
class: 'cls',
|
24
|
-
function: 'fn',
|
25
|
-
label: 'lbl'
|
16
|
+
methods: ['cls#new_fn'],
|
17
|
+
path: 'pkg'
|
26
18
|
}
|
27
19
|
]
|
28
20
|
}.deep_stringify_keys!
|
29
21
|
config = AppMap::Config.load(config_data)
|
30
22
|
|
31
|
-
|
32
|
-
|
33
|
-
name:
|
34
|
-
|
23
|
+
expect(JSON.parse(JSON.generate(config.as_json))).to eq(JSON.parse(<<~FIXTURE))
|
24
|
+
{
|
25
|
+
"name": "test",
|
26
|
+
"appmap_dir": "tmp/appmap",
|
27
|
+
"packages": [
|
28
|
+
],
|
29
|
+
"swagger_config": {
|
30
|
+
"project_name": null,
|
31
|
+
"project_version": "1.0",
|
32
|
+
"output_dir": "swagger",
|
33
|
+
"description": "Generate Swagger from AppMaps"
|
34
|
+
},
|
35
|
+
"depends_config": {
|
36
|
+
"base_dir": null,
|
37
|
+
"base_branches": [
|
38
|
+
"remotes/origin/main",
|
39
|
+
"remotes/origin/master"
|
40
|
+
],
|
41
|
+
"test_file_patterns": [
|
42
|
+
"spec/**/*_spec.rb",
|
43
|
+
"test/**/*_test.rb"
|
44
|
+
],
|
45
|
+
"dependent_tasks": [
|
46
|
+
"swagger"
|
47
|
+
],
|
48
|
+
"description": "Bring AppMaps up to date with local file modifications, and updated derived data such as Swagger files",
|
49
|
+
"rspec_environment_method": "AppMap::Depends.test_env",
|
50
|
+
"minitest_environment_method": "AppMap::Depends.test_env",
|
51
|
+
"rspec_select_tests_method": "AppMap::Depends.select_rspec_tests",
|
52
|
+
"minitest_select_tests_method": "AppMap::Depends.select_minitest_tests",
|
53
|
+
"rspec_test_command_method": "AppMap::Depends.rspec_test_command",
|
54
|
+
"minitest_test_command_method": "AppMap::Depends.minitest_test_command"
|
55
|
+
},
|
56
|
+
"hook_paths": [
|
57
|
+
"pkg",
|
58
|
+
"#{Gem.loaded_specs['activesupport'].gem_dir}"
|
59
|
+
],
|
60
|
+
"exclude": [
|
61
|
+
],
|
62
|
+
"functions": [
|
35
63
|
{
|
36
|
-
|
37
|
-
|
64
|
+
"cls": "cls",
|
65
|
+
"target_methods": {
|
66
|
+
"package": "pkg",
|
67
|
+
"method_names": [
|
68
|
+
"fn"
|
69
|
+
]
|
70
|
+
}
|
38
71
|
},
|
39
72
|
{
|
40
|
-
|
41
|
-
|
42
|
-
|
73
|
+
"cls": "cls",
|
74
|
+
"target_methods": {
|
75
|
+
"package": "pkg",
|
76
|
+
"method_names": [
|
77
|
+
"new_fn"
|
78
|
+
]
|
79
|
+
}
|
43
80
|
}
|
44
81
|
],
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
82
|
+
"builtin_hooks": {
|
83
|
+
"JSON::Ext::Parser": [
|
84
|
+
{
|
85
|
+
"package": "json",
|
86
|
+
"method_names": [
|
87
|
+
"parse"
|
88
|
+
]
|
89
|
+
}
|
90
|
+
],
|
91
|
+
"JSON::Ext::Generator::State": [
|
92
|
+
{
|
93
|
+
"package": "json",
|
94
|
+
"method_names": [
|
95
|
+
"generate"
|
96
|
+
]
|
97
|
+
}
|
98
|
+
],
|
99
|
+
"Net::HTTP": [
|
100
|
+
{
|
101
|
+
"package": "net/http",
|
102
|
+
"method_names": [
|
103
|
+
"request"
|
104
|
+
]
|
105
|
+
}
|
106
|
+
],
|
107
|
+
"OpenSSL::PKey::PKey": [
|
108
|
+
{
|
109
|
+
"package": "openssl",
|
110
|
+
"method_names": [
|
111
|
+
"sign"
|
112
|
+
]
|
113
|
+
}
|
114
|
+
],
|
115
|
+
"OpenSSL::X509::Request": [
|
116
|
+
{
|
117
|
+
"package": "openssl",
|
118
|
+
"method_names": [
|
119
|
+
"sign"
|
120
|
+
]
|
121
|
+
},
|
122
|
+
{
|
123
|
+
"package": "openssl",
|
124
|
+
"method_names": [
|
125
|
+
"verify"
|
126
|
+
]
|
127
|
+
}
|
128
|
+
],
|
129
|
+
"OpenSSL::X509::Certificate": [
|
130
|
+
{
|
131
|
+
"package": "openssl",
|
132
|
+
"method_names": [
|
133
|
+
"sign"
|
134
|
+
]
|
135
|
+
}
|
136
|
+
],
|
137
|
+
"OpenSSL::PKCS5": [
|
138
|
+
{
|
139
|
+
"package": "openssl",
|
140
|
+
"method_names": [
|
141
|
+
"pbkdf2_hmac"
|
142
|
+
]
|
143
|
+
},
|
144
|
+
{
|
145
|
+
"package": "openssl",
|
146
|
+
"method_names": [
|
147
|
+
"pbkdf2_hmac_sha1"
|
148
|
+
]
|
149
|
+
}
|
150
|
+
],
|
151
|
+
"OpenSSL::Cipher": [
|
152
|
+
{
|
153
|
+
"package": "openssl",
|
154
|
+
"method_names": [
|
155
|
+
"encrypt"
|
156
|
+
]
|
157
|
+
},
|
158
|
+
{
|
159
|
+
"package": "openssl",
|
160
|
+
"method_names": [
|
161
|
+
"decrypt"
|
162
|
+
]
|
163
|
+
}
|
164
|
+
],
|
165
|
+
"Psych": [
|
166
|
+
{
|
167
|
+
"package": "yaml",
|
168
|
+
"method_names": [
|
169
|
+
"load"
|
170
|
+
]
|
171
|
+
},
|
172
|
+
{
|
173
|
+
"package": "yaml",
|
174
|
+
"method_names": [
|
175
|
+
"load_stream"
|
176
|
+
]
|
177
|
+
},
|
178
|
+
{
|
179
|
+
"package": "yaml",
|
180
|
+
"method_names": [
|
181
|
+
"parse"
|
182
|
+
]
|
183
|
+
},
|
184
|
+
{
|
185
|
+
"package": "yaml",
|
186
|
+
"method_names": [
|
187
|
+
"parse_stream"
|
188
|
+
]
|
189
|
+
},
|
190
|
+
{
|
191
|
+
"package": "yaml",
|
192
|
+
"method_names": [
|
193
|
+
"dump"
|
194
|
+
]
|
195
|
+
},
|
196
|
+
{
|
197
|
+
"package": "yaml",
|
198
|
+
"method_names": [
|
199
|
+
"dump_stream"
|
200
|
+
]
|
201
|
+
}
|
202
|
+
]
|
203
|
+
},
|
204
|
+
"gem_hooks": {
|
205
|
+
"cls": [
|
206
|
+
{
|
207
|
+
"package": "pkg",
|
208
|
+
"method_names": [
|
209
|
+
"fn"
|
210
|
+
]
|
211
|
+
},
|
212
|
+
{
|
213
|
+
"package": "pkg",
|
214
|
+
"method_names": [
|
215
|
+
"new_fn"
|
216
|
+
]
|
217
|
+
}
|
218
|
+
],
|
219
|
+
"ActiveSupport::Callbacks::CallbackSequence": [
|
220
|
+
{
|
221
|
+
"package": "activesupport",
|
222
|
+
"method_names": [
|
223
|
+
"invoke_before"
|
224
|
+
]
|
225
|
+
},
|
226
|
+
{
|
227
|
+
"package": "activesupport",
|
228
|
+
"method_names": [
|
229
|
+
"invoke_after"
|
230
|
+
]
|
231
|
+
}
|
232
|
+
],
|
233
|
+
"ActiveSupport::SecurityUtils": [
|
234
|
+
{
|
235
|
+
"package": "activesupport",
|
236
|
+
"method_names": [
|
237
|
+
"secure_compare"
|
238
|
+
]
|
239
|
+
}
|
240
|
+
]
|
241
|
+
}
|
242
|
+
}
|
243
|
+
FIXTURE
|
56
244
|
end
|
57
245
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
packages: [],
|
62
|
-
functions: [
|
63
|
-
{
|
64
|
-
name: 'pkg/cls#fn',
|
65
|
-
}
|
66
|
-
]
|
67
|
-
}.deep_stringify_keys!
|
68
|
-
config = AppMap::Config.load(config_data)
|
246
|
+
describe AppMap::Config::Package do
|
247
|
+
describe :build_from_gem do
|
248
|
+
let(:mock_rails) { double(logger: double(info: true)) }
|
69
249
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
packages: [],
|
74
|
-
functions: [
|
75
|
-
{
|
76
|
-
package: 'pkg',
|
77
|
-
class: 'cls',
|
78
|
-
functions: [ :fn ],
|
79
|
-
}
|
80
|
-
]
|
81
|
-
}.deep_stringify_keys!
|
250
|
+
before do
|
251
|
+
stub_const('Rails', mock_rails)
|
252
|
+
end
|
82
253
|
|
83
|
-
|
254
|
+
it 'does not return a truthy value on failure' do
|
255
|
+
result = AppMap::Config::Package.build_from_gem('some_missing_gem_name', optional: true)
|
256
|
+
expect(result).to_not be_truthy
|
257
|
+
end
|
258
|
+
end
|
84
259
|
end
|
85
260
|
|
86
261
|
context do
|
@@ -94,7 +269,8 @@ describe AppMap::Config, docker: false do
|
|
94
269
|
expect(config.to_h).to eq(YAML.load(<<~CONFIG))
|
95
270
|
:name: appmap-ruby
|
96
271
|
:packages:
|
97
|
-
- :
|
272
|
+
- :name: lib
|
273
|
+
:path: lib
|
98
274
|
:handler_class: AppMap::Handler::Function
|
99
275
|
:shallow: false
|
100
276
|
:functions: []
|
data/spec/depends/api_spec.rb
CHANGED
@@ -104,7 +104,7 @@ describe 'Depends API' do
|
|
104
104
|
describe '.run_tests' do
|
105
105
|
def run_tests
|
106
106
|
Dir.chdir 'spec/fixtures/depends' do
|
107
|
-
api.run_tests([ 'spec/actual_rspec_test.rb', 'test/actual_minitest_test.rb' ], appmap_dir: Pathname.new(
|
107
|
+
api.run_tests([ 'spec/actual_rspec_test.rb', 'test/actual_minitest_test.rb' ], appmap_dir: Pathname.new('.').expand_path.to_s)
|
108
108
|
end
|
109
109
|
end
|
110
110
|
|
data/spec/rails_spec_helper.rb
CHANGED
@@ -5,9 +5,14 @@ require 'active_support'
|
|
5
5
|
require 'active_support/core_ext'
|
6
6
|
require 'open3'
|
7
7
|
|
8
|
+
# docker compose v2 replaced the --filter flag with --status
|
9
|
+
PS_CMD=`docker-compose --version` =~ /version v2/ ?
|
10
|
+
"docker-compose ps -q --status running" :
|
11
|
+
"docker-compose ps -q --filter health=healthy"
|
12
|
+
|
8
13
|
def wait_for_container(app_name)
|
9
14
|
start_time = Time.now
|
10
|
-
until
|
15
|
+
until `#{PS_CMD} #{app_name}`.strip != ''
|
11
16
|
elapsed = Time.now - start_time
|
12
17
|
raise "Timeout waiting for container #{app_name} to be ready" if elapsed > 10
|
13
18
|
|
@@ -2,28 +2,40 @@
|
|
2
2
|
|
3
3
|
require 'test_helper'
|
4
4
|
|
5
|
+
schema_path = File.expand_path('../../config-schema.yml', __FILE__)
|
6
|
+
CONFIG_SCHEMA = YAML.safe_load(File.read(schema_path))
|
5
7
|
class AgentSetupValidateTest < Minitest::Test
|
6
8
|
NON_EXISTING_CONFIG_FILENAME = '123.yml'
|
7
9
|
INVALID_YAML_CONFIG_FILENAME = 'spec/fixtures/config/invalid_yaml_config.yml'
|
8
10
|
INVALID_CONFIG_FILENAME = 'spec/fixtures/config/invalid_config.yml'
|
9
11
|
MISSING_PATH_OR_GEM_CONFIG_FILENAME = 'spec/fixtures/config/missing_path_or_gem.yml'
|
10
12
|
|
13
|
+
def check_output(output, expected_errors)
|
14
|
+
expected = JSON.pretty_generate(
|
15
|
+
{
|
16
|
+
version: 2,
|
17
|
+
errors: expected_errors,
|
18
|
+
schema: CONFIG_SCHEMA
|
19
|
+
}
|
20
|
+
)
|
21
|
+
assert_equal(expected, output.strip)
|
22
|
+
end
|
23
|
+
|
11
24
|
def test_init_when_config_exists
|
12
25
|
output = `./exe/appmap-agent-validate`
|
13
26
|
assert_equal 0, $CHILD_STATUS.exitstatus
|
14
|
-
|
27
|
+
check_output(output, [
|
15
28
|
{
|
16
29
|
level: :error,
|
17
30
|
message: 'AppMap auto-configuration is currently not available for non Rails projects'
|
18
31
|
}
|
19
32
|
])
|
20
|
-
assert_equal expected, output.strip
|
21
33
|
end
|
22
34
|
|
23
35
|
def test_init_with_non_existing_config_file
|
24
36
|
output = `./exe/appmap-agent-validate -c #{NON_EXISTING_CONFIG_FILENAME}`
|
25
37
|
assert_equal 0, $CHILD_STATUS.exitstatus
|
26
|
-
|
38
|
+
check_output(output, [
|
27
39
|
{
|
28
40
|
level: :error,
|
29
41
|
message: 'AppMap auto-configuration is currently not available for non Rails projects'
|
@@ -34,13 +46,12 @@ class AgentSetupValidateTest < Minitest::Test
|
|
34
46
|
message: "AppMap configuration #{NON_EXISTING_CONFIG_FILENAME} file does not exist"
|
35
47
|
}
|
36
48
|
])
|
37
|
-
assert_equal expected, output.strip
|
38
49
|
end
|
39
50
|
|
40
51
|
def test_init_with_invalid_YAML
|
41
52
|
output = `./exe/appmap-agent-validate -c #{INVALID_YAML_CONFIG_FILENAME}`
|
42
53
|
assert_equal 0, $CHILD_STATUS.exitstatus
|
43
|
-
|
54
|
+
check_output(output, [
|
44
55
|
{
|
45
56
|
level: :error,
|
46
57
|
message: 'AppMap auto-configuration is currently not available for non Rails projects'
|
@@ -53,13 +64,12 @@ class AgentSetupValidateTest < Minitest::Test
|
|
53
64
|
'did not find expected key while parsing a block mapping at line 1 column 1'
|
54
65
|
}
|
55
66
|
])
|
56
|
-
assert_equal expected, output.strip
|
57
67
|
end
|
58
68
|
|
59
69
|
def test_init_with_invalid_data_config
|
60
70
|
output = `./exe/appmap-agent-validate -c #{INVALID_CONFIG_FILENAME}`
|
61
71
|
assert_equal 0, $CHILD_STATUS.exitstatus
|
62
|
-
|
72
|
+
check_output(output, [
|
63
73
|
{
|
64
74
|
level: :error,
|
65
75
|
message: 'AppMap auto-configuration is currently not available for non Rails projects'
|
@@ -71,13 +81,12 @@ class AgentSetupValidateTest < Minitest::Test
|
|
71
81
|
detailed_message: "no implicit conversion of String into Integer"
|
72
82
|
}
|
73
83
|
])
|
74
|
-
assert_equal expected, output.strip
|
75
84
|
end
|
76
85
|
|
77
86
|
def test_init_with_missing_package_key
|
78
87
|
output = `./exe/appmap-agent-validate -c #{MISSING_PATH_OR_GEM_CONFIG_FILENAME}`
|
79
88
|
assert_equal 0, $CHILD_STATUS.exitstatus
|
80
|
-
|
89
|
+
check_output(output, [
|
81
90
|
{
|
82
91
|
level: :error,
|
83
92
|
message: 'AppMap auto-configuration is currently not available for non Rails projects'
|
@@ -89,6 +98,5 @@ class AgentSetupValidateTest < Minitest::Test
|
|
89
98
|
detailed_message: "AppMap config 'package' element should specify 'gem' or 'path'"
|
90
99
|
}
|
91
100
|
])
|
92
|
-
assert_equal expected, output.strip
|
93
101
|
end
|
94
102
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: appmap
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.68.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kevin Gilpin
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-11-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -3405,6 +3405,10 @@ files:
|
|
3405
3405
|
- ext/appmap/extconf.rb
|
3406
3406
|
- lib/appmap.rb
|
3407
3407
|
- lib/appmap/agent.rb
|
3408
|
+
- lib/appmap/builtin_hooks/json.yml
|
3409
|
+
- lib/appmap/builtin_hooks/net/http.yml
|
3410
|
+
- lib/appmap/builtin_hooks/openssl.yml
|
3411
|
+
- lib/appmap/builtin_hooks/yaml.yml
|
3408
3412
|
- lib/appmap/class_map.rb
|
3409
3413
|
- lib/appmap/command/agent_setup/init.rb
|
3410
3414
|
- lib/appmap/command/agent_setup/status.rb
|
@@ -3423,6 +3427,10 @@ files:
|
|
3423
3427
|
- lib/appmap/depends/test_runner.rb
|
3424
3428
|
- lib/appmap/depends/util.rb
|
3425
3429
|
- lib/appmap/event.rb
|
3430
|
+
- lib/appmap/gem_hooks/actionpack.yml
|
3431
|
+
- lib/appmap/gem_hooks/actionview.yml
|
3432
|
+
- lib/appmap/gem_hooks/activesupport.yml
|
3433
|
+
- lib/appmap/gem_hooks/cancancan.yml
|
3426
3434
|
- lib/appmap/handler/function.rb
|
3427
3435
|
- lib/appmap/handler/net_http.rb
|
3428
3436
|
- lib/appmap/handler/rails/request_handler.rb
|