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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4209d3f9422b61ba07ca1776597eac220374e8308a303cdf001604c90903dd31
4
- data.tar.gz: 55a755af5bd85255ce882633d3089a0ca308d137b15945e0a1fff4d5479c4994
3
+ metadata.gz: 7e87a42863f68312980ff6e9689b74489b147b283854463abbf1e2cf6be4fe29
4
+ data.tar.gz: 0d5a7e86d9cea6ba154e6bc1ceb920fcba978074afd4351663832946d0ab0f30
5
5
  SHA512:
6
- metadata.gz: 4a7ce04424eac8292b0457c99bc3b6f699c0e80a6222450e52f37071e078c2c574c33e8a89249c584412c50da5b26d07f98864f50ecc13e0318da6272fafb879
7
- data.tar.gz: 6c8ba9b9a67f09ce662d1bdf0569238537087709cff6f9be2562d50fe06d8ca4199268a2bc3c0d9136dd8a6ab6ff0e66f0d85683b4d3c15e95ba5e10dbcdeb71
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,4 @@
1
+ - method: JSON::Ext::Parser#parse
2
+ label: format.json.parse
3
+ - method: JSON::Ext::Generator::State#generate
4
+ label: format.json.generate
@@ -0,0 +1,3 @@
1
+ - method: Net::HTTP#request
2
+ label: protocol.http
3
+ handler_class: AppMap::Handler::NetHTTP
@@ -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
@@ -0,0 +1,10 @@
1
+ - methods:
2
+ - Psych#load
3
+ - Psych#load_stream
4
+ - Psych#parse
5
+ - Psych#parse_stream
6
+ label: format.yaml.parse
7
+ - methods:
8
+ - Psych#dump
9
+ - Psych#dump_stream
10
+ label: format.yaml.generate
@@ -10,7 +10,14 @@ module AppMap
10
10
 
11
11
  class Validate < ValidateStruct
12
12
  def perform
13
- puts JSON.pretty_generate(config_validator.valid? ? [] : config_validator.violations.map(&:to_h))
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
@@ -13,7 +13,6 @@ module AppMap
13
13
 
14
14
  def inspect(arguments)
15
15
  detect_nodejs
16
- index_appmaps
17
16
 
18
17
  arguments.unshift 'inspect'
19
18
  arguments.unshift APPMAP_JS
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 +path+ to be mapped.
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
- # * +package_name+ can be used to make sure that the code is required so that it can be loaded. This is generally used with
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, :package_name, :exclude, :labels, :shallow) do
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, package_name: nil, exclude: [], labels: [])
47
- Package.new(path, nil, package_name, exclude, labels, shallow)
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, package_name: nil, exclude: [], labels: [], optional: false, force: false)
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, package_name, exclude, labels, shallow)
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
- handler_class: handler_class.name,
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
- Array(method_names).include?(method_name)
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, :package_name) do # :nodoc:
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
- package_name: package_name,
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(gem_name, methods, handler_class: nil, package_name: nil)
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 = Package.build_from_gem(gem_name, package_name: package_name, labels: method.labels, shallow: false, optional: true)
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
- # Hook well-known functions. When a function configured here is available in the bundle, it will be hooked with the
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
- @hooked_methods = METHOD_HOOKS.each_with_object(Hash.new { |h,k| h[k] = [] }) do |cls_target_methods, hooked_methods|
253
- hooked_methods[cls_target_methods.cls] << cls_target_methods.target_methods
254
- end
255
-
256
- functions.each do |func|
257
- package_options = {}
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
- @hooked_methods[func.cls] << hook
291
+ @gem_hooks
267
292
  end
293
+ hooks[cls_target_methods.cls] << cls_target_methods.target_methods
268
294
  end
269
295
 
270
- @hooked_methods.each_value do |hooks|
271
- Array(hooks).each do |hook|
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 |function_data|
338
- function_name = function_data['name']
339
- package, cls, functions = []
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
- package = function_data['package']
345
- cls = function_data['class']
346
- functions = function_data['function'] || function_data['functions']
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
- Package.build_from_gem(gem, package_name: package['package'], exclude: package['exclude'] || [], shallow: shallow)
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
- Array(config.hooked_methods[cls.name])
421
- .compact
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
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'weakref'
4
+
3
5
  module AppMap
4
6
  module Event
5
7
  @@id_counter = 0
@@ -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
@@ -0,0 +1,6 @@
1
+ - methods:
2
+ - CanCan::ControllerAdditions#authorize!
3
+ - CanCan::ControllerAdditions#can?
4
+ - CanCan::ControllerAdditions#cannot?
5
+ - CanCan::Ability#authorize?
6
+ label: security.authorization
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 lock_builtins
19
- return if @builtins_hooked
18
+ def hook_builtins?
19
+ Mutex.new.synchronize do
20
+ @hook_builtins = true if @hook_builtins.nil?
20
21
 
21
- @builtins_hooked = true
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.lock_builtins
95
+ return unless self.class.hook_builtins?
83
96
 
84
- class_from_string = lambda do |fq_class|
85
- fq_class.split('::').inject(Object) do |mod, class_name|
86
- mod.const_get(class_name)
87
- end
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
- config.builtin_hooks.each do |class_name, hooks|
91
- Array(hooks).each do |hook|
92
- require hook.package.package_name if hook.package.package_name && hook.package.package_name != 'ruby'
93
- Array(hook.method_names).each do |method_name|
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
- hook_method = lambda do |entry|
98
- cls, method = entry
99
- return false if config.never_hook?(cls, method)
107
+ hook_method = lambda do |entry|
108
+ cls, method = entry
109
+ return false if config.never_hook?(cls, method)
100
110
 
101
- Hook::Method.new(hook.package, cls, method).activate
102
- end
111
+ Hook::Method.new(hook.package, cls, method).activate
112
+ end
103
113
 
104
- methods = []
105
- methods << [ base_cls, base_cls.public_instance_method(method_name) ] rescue nil
106
- if base_cls.respond_to?(:singleton_class)
107
- methods << [ base_cls.singleton_class, base_cls.singleton_class.public_instance_method(method_name) ] rescue nil
108
- end
109
- methods.compact!
110
- if methods.empty?
111
- warn "Method #{method_name} not found on #{base_cls.name}"
112
- else
113
- methods.each(&hook_method)
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
- end
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
@@ -3,7 +3,7 @@
3
3
  module AppMap
4
4
  URL = 'https://github.com/applandinc/appmap-ruby'
5
5
 
6
- VERSION = '0.67.0'
6
+ VERSION = '0.68.2'
7
7
 
8
8
  APPMAP_FORMAT_VERSION = '1.5.1'
9
9
 
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 from a Hash' do
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
- path: 'path-1'
13
+ name: 'pkg/cls#fn',
14
14
  },
15
15
  {
16
- path: 'path-2',
17
- exclude: [ 'exclude-1' ]
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
- config_expectation = {
32
- exclude: [],
33
- name: 'test',
34
- packages: [
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
- path: 'path-1',
37
- handler_class: 'AppMap::Handler::Function'
64
+ "cls": "cls",
65
+ "target_methods": {
66
+ "package": "pkg",
67
+ "method_names": [
68
+ "fn"
69
+ ]
70
+ }
38
71
  },
39
72
  {
40
- path: 'path-2',
41
- handler_class: 'AppMap::Handler::Function',
42
- exclude: [ 'exclude-1' ]
73
+ "cls": "cls",
74
+ "target_methods": {
75
+ "package": "pkg",
76
+ "method_names": [
77
+ "new_fn"
78
+ ]
79
+ }
43
80
  }
44
81
  ],
45
- functions: [
46
- {
47
- package: 'pkg',
48
- class: 'cls',
49
- functions: [ :fn ],
50
- labels: ['lbl']
51
- }
52
- ]
53
- }.deep_stringify_keys!
54
-
55
- expect(config.to_h.deep_stringify_keys!).to eq(config_expectation)
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
- it 'interprets a function in canonical name format' do
59
- config_data = {
60
- name: 'test',
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
- config_expectation = {
71
- exclude: [],
72
- name: 'test',
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
- expect(config.to_h.deep_stringify_keys!).to eq(config_expectation)
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
- - :path: lib
272
+ - :name: lib
273
+ :path: lib
98
274
  :handler_class: AppMap::Handler::Function
99
275
  :shallow: false
100
276
  :functions: []
@@ -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(DEPENDS_TEST_DIR).expand_path.to_s)
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
 
@@ -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 `docker-compose ps -q --filter health=healthy #{app_name}`.strip != ''
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
- expected = JSON.pretty_generate([
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
- expected = JSON.pretty_generate([
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
- expected = JSON.pretty_generate([
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
- expected = JSON.pretty_generate([
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
- expected = JSON.pretty_generate([
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.67.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-10-21 00:00:00.000000000 Z
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