appmap 0.65.0 → 0.66.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9ff92977defe997917c8b7c2d9d5974d5e743c02b637bcfffa5548e087d4644f
4
- data.tar.gz: 4889a8d717b0167737719acc75c12f5709404b6445b6dd633523cd76e4992a96
3
+ metadata.gz: 2dd5295e0bbe8c9a4281b27bd712b61640672e93a03d6c3e31af3e201c9ca592
4
+ data.tar.gz: 8fb74d28bb2367918831c6d3af029440591da8ee694f1645fc2ef8baf5b28b60
5
5
  SHA512:
6
- metadata.gz: 4a78ced9dc2d5c0e80fe0f68d01508cff0af59bfc65f9a747ec5f981d7bd5a7523a59230427ddf465b39db1768f2443c856a93c852ddb444bc80eec4a539f460
7
- data.tar.gz: 21a54adacd2b5b27a5f2a6143db0c0684716a9d1db6add75d4e05fdd73054bb9576fc2bac3ce68067230d1fb04caf177c8e4366e05a061301bd1a9dd84214bf5
6
+ metadata.gz: ef07a3169b2d1f4dcabb406296275506250840cc767440b60a106f48ad5ee251017a9e9f7cd9769e7d0d133a3b9704f76bfef571fd69feef85163979cec80a80
7
+ data.tar.gz: 34c9c18e230dc46d868b759422eb7aeaba79b99fd1cca610bbc1a8bdc70a7cfa18e34a064a43cc50847c8c452bf267a6003a5b98cb3a2e2e8fa40028552fe112
data/.travis.yml CHANGED
@@ -1,4 +1,5 @@
1
1
  language: ruby
2
+ dist: bionic
2
3
 
3
4
  rbenv:
4
5
  - 2.6
@@ -15,6 +16,7 @@ services:
15
16
  - docker
16
17
 
17
18
  before_install:
19
+ - sudo apt-get update && sudo apt-get install apt-transport-https ca-certificates -y && sudo update-ca-certificates
18
20
  # see https://blog.travis-ci.com/docker-rate-limits
19
21
  # and also https://www.docker.com/blog/what-you-need-to-know-about-upcoming-docker-hub-rate-limiting/
20
22
  # if we do not use authorized account,
data/CHANGELOG.md CHANGED
@@ -1,3 +1,37 @@
1
+ ## [0.66.2](https://github.com/applandinc/appmap-ruby/compare/v0.66.1...v0.66.2) (2021-10-07)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * fix Travis for Ruby 3.0 ([8ec7359](https://github.com/applandinc/appmap-ruby/commit/8ec7359287f5b204ae9fb0724d8b683adfb79df5))
7
+ * Fix warning of circular import ([84d456d](https://github.com/applandinc/appmap-ruby/commit/84d456d8ac26ef3fc7a81ca6517e4363aac9916d))
8
+ * Properly handle headers which aren't mangled by Rack ([8e78e13](https://github.com/applandinc/appmap-ruby/commit/8e78e138776cb563f984e3592cf5024af16da2b7))
9
+ * replace deprecated File.exists? method ([80ce5b5](https://github.com/applandinc/appmap-ruby/commit/80ce5b59fd010a806ca6320365f453f1e74f095d))
10
+ * Validate presence package configuration ([f478d6b](https://github.com/applandinc/appmap-ruby/commit/f478d6b60a786608c21217755cec9a8185e084d3))
11
+
12
+ ## [0.66.1](https://github.com/applandinc/appmap-ruby/compare/v0.66.0...v0.66.1) (2021-09-29)
13
+
14
+
15
+ ### Bug Fixes
16
+
17
+ * Fix compilation on macOS with Xcode 13 ([8c66e08](https://github.com/applandinc/appmap-ruby/commit/8c66e08393bf8d9efac9635ad7a150329797729d))
18
+
19
+ # [0.66.0](https://github.com/applandinc/appmap-ruby/compare/v0.65.1...v0.66.0) (2021-09-28)
20
+
21
+
22
+ ### Features
23
+
24
+ * Add option for explicit 'require' in function config ([1cf6c2a](https://github.com/applandinc/appmap-ruby/commit/1cf6c2aed8ee2d89c900f2959484b88b6fd3eb93))
25
+ * Builtin code such as Ruby Logger can be hooked via appmap.yml ([779c9e5](https://github.com/applandinc/appmap-ruby/commit/779c9e5e4177d58ea7b63e663e7c5a0810a78c60))
26
+
27
+ ## [0.65.1](https://github.com/applandinc/appmap-ruby/compare/v0.65.0...v0.65.1) (2021-09-16)
28
+
29
+
30
+ ### Performance Improvements
31
+
32
+ * Cache method metadata ([d11e0f3](https://github.com/applandinc/appmap-ruby/commit/d11e0f3361057b7cada204656ca833c12bb704c1))
33
+ * Don't scan the backtrace on every SQL query ([9bb7457](https://github.com/applandinc/appmap-ruby/commit/9bb74576d24f7954a388f09f33e69ae9d11a4188))
34
+
1
35
  # [0.65.0](https://github.com/applandinc/appmap-ruby/compare/v0.64.0...v0.65.0) (2021-09-14)
2
36
 
3
37
 
@@ -1,6 +1,17 @@
1
1
  require "mkmf"
2
2
 
3
+
3
4
  $CFLAGS='-Werror'
5
+
6
+ # Per https://bugs.ruby-lang.org/issues/17865,
7
+ # compound-token-split-by-macro was added in clang 12 and broke
8
+ # compilation with some of the ruby headers. If the current compiler
9
+ # supports the new warning, turn it off.
10
+ new_warning = '-Wno-error=compound-token-split-by-macro'
11
+ if try_cflags(new_warning)
12
+ $CFLAGS += ' ' + new_warning
13
+ end
14
+
4
15
  extension_name = "appmap"
5
16
  dir_config(extension_name)
6
17
  create_makefile(File.join(extension_name, extension_name))
data/lib/appmap/config.rb CHANGED
@@ -117,13 +117,15 @@ module AppMap
117
117
  # entry in appmap.yml. When the Config is initialized, each Function is converted into
118
118
  # a Package and TargetMethods. It's called a Function rather than a Method, because Function
119
119
  # is the AppMap terminology.
120
- Function = Struct.new(:package, :cls, :labels, :function_names) do # :nodoc:
120
+ Function = Struct.new(:package, :cls, :labels, :function_names, :builtin, :package_name) do # :nodoc:
121
121
  def to_h
122
122
  {
123
123
  package: package,
124
+ package_name: package_name,
124
125
  class: cls,
125
126
  labels: labels,
126
- functions: function_names.map(&:to_sym)
127
+ functions: function_names.map(&:to_sym),
128
+ builtin: builtin
127
129
  }.compact
128
130
  end
129
131
  end
@@ -174,8 +176,8 @@ module AppMap
174
176
  package_hooks('actionpack',
175
177
  [
176
178
  method_hook('ActionDispatch::Request::Session', %i[[] dig values fetch], %w[http.session.read]),
177
- method_hook('ActionDispatch::Request::Session', %i[destroy[]= clear update delete merge], %w[http.session.write]),
178
- method_hook('ActionDispatch::Cookies::CookieJar', %i[[]= clear update delete recycle], %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]),
179
181
  method_hook('ActionDispatch::Cookies::CookieJar', %i[[]= clear update delete recycle], %w[http.session.write]),
180
182
  method_hook('ActionDispatch::Cookies::EncryptedCookieJar', %i[[]= clear update delete recycle], %w[http.cookie crypto.encrypt])
181
183
  ],
@@ -244,7 +246,7 @@ module AppMap
244
246
  @depends_config = depends_config
245
247
  @hook_paths = Set.new(packages.map(&:path))
246
248
  @exclude = exclude
247
- @builtin_hooks = BUILTIN_HOOKS
249
+ @builtin_hooks = BUILTIN_HOOKS.dup
248
250
  @functions = functions
249
251
 
250
252
  @hooked_methods = METHOD_HOOKS.each_with_object(Hash.new { |h,k| h[k] = [] }) do |cls_target_methods, hooked_methods|
@@ -254,7 +256,15 @@ module AppMap
254
256
  functions.each do |func|
255
257
  package_options = {}
256
258
  package_options[:labels] = func.labels if func.labels
257
- @hooked_methods[func.cls] << TargetMethods.new(func.function_names, Package.build_from_path(func.package, **package_options))
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
265
+ else
266
+ @hooked_methods[func.cls] << hook
267
+ end
258
268
  end
259
269
 
260
270
  @hooked_methods.each_value do |hooks|
@@ -277,7 +287,7 @@ module AppMap
277
287
  LOGO
278
288
  end
279
289
 
280
- config_present = true if File.exists?(config_file_name)
290
+ config_present = true if File.exist?(config_file_name)
281
291
 
282
292
  config_data = if config_present
283
293
  YAML.safe_load(::File.read(config_file_name))
@@ -300,6 +310,7 @@ module AppMap
300
310
  MISSING_FILE_MSG
301
311
  {}
302
312
  end
313
+
303
314
  load(config_data).tap do |config|
304
315
  config_yaml = {
305
316
  'name' => config.name,
@@ -324,15 +335,22 @@ module AppMap
324
335
 
325
336
  if config_data['functions']
326
337
  config_params[:functions] = config_data['functions'].map do |function_data|
327
- package = function_data['package']
328
- cls = function_data['class']
329
- functions = function_data['function'] || function_data['functions']
330
- raise %q(AppMap config 'function' element should specify 'package', 'class' and 'function' or 'functions') unless package && cls && functions
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)
343
+ 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
348
+ end
331
349
 
332
350
  functions = Array(functions).map(&:to_sym)
333
351
  labels = function_data['label'] || function_data['labels']
334
352
  labels = Array(labels).map(&:to_s) if labels
335
- Function.new(package, cls, labels, functions)
353
+ Function.new(package, cls, labels, functions, function_data['builtin'], function_data['require'])
336
354
  end
337
355
  end
338
356
 
@@ -342,6 +360,7 @@ module AppMap
342
360
  gem = package['gem']
343
361
  path = package['path']
344
362
  raise %q(AppMap config 'package' element should specify 'gem' or 'path', not both) if gem && path
363
+ raise %q(AppMap config 'package' element should specify 'gem' or 'path') unless gem || path
345
364
 
346
365
  if gem
347
366
  shallow = package['shallow']
@@ -63,7 +63,7 @@ module AppMap
63
63
  removed = []
64
64
  out_of_date_appmaps.each do |appmap_path|
65
65
  mtime_path = File.join(appmap_path, 'mtime')
66
- next unless File.exists?(mtime_path)
66
+ next unless File.exist?(mtime_path)
67
67
 
68
68
  appmap_mtime = File.read(mtime_path).to_i
69
69
  if appmap_mtime < since_ms
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'appmap'
4
-
5
3
  module AppMap
6
4
  module Depends
7
5
  class Configuration
@@ -16,7 +16,7 @@ module AppMap
16
16
  def delete_appmap(appmap_path)
17
17
  FileUtils.rm_rf(appmap_path)
18
18
  appmap_file_path = [ appmap_path, 'appmap.json' ].join('.')
19
- File.unlink(appmap_file_path) if File.exists?(appmap_file_path)
19
+ File.unlink(appmap_file_path) if File.exist?(appmap_file_path)
20
20
  rescue
21
21
  warn "Unable to delete AppMap: #{$!}"
22
22
  end
data/lib/appmap/event.rb CHANGED
@@ -53,7 +53,7 @@ module AppMap
53
53
  @times[best_class_name(value)] += elapsed
54
54
  end
55
55
 
56
- encode_dislay_string(value_string)
56
+ encode_display_string(value_string)
57
57
  end
58
58
 
59
59
  def object_properties(hash_like)
@@ -77,7 +77,7 @@ module AppMap
77
77
  value_cls.name
78
78
  end
79
79
 
80
- def encode_dislay_string(value)
80
+ def encode_display_string(value)
81
81
  (value||'')[0...LIMIT].encode('utf-8', invalid: :replace, undef: :replace, replace: '_')
82
82
  end
83
83
 
@@ -138,22 +138,46 @@ module AppMap
138
138
  class MethodCall < MethodEvent
139
139
  attr_accessor :defined_class, :method_id, :path, :lineno, :parameters, :receiver, :static
140
140
 
141
+ MethodMetadata = Struct.new(:defined_class, :method_id, :path, :lineno, :static)
142
+
143
+ @@method_metadata = {}
144
+
141
145
  class << self
146
+ private
147
+
148
+ def method_metadata(defined_class, method, receiver)
149
+ result = @@method_metadata[method]
150
+ return result if result
151
+
152
+ result = MethodMetadata.new
153
+ result.static = receiver.is_a?(Module)
154
+ result.defined_class = defined_class
155
+ result.method_id = method.name.to_s
156
+ if method.source_location
157
+ path = method.source_location[0]
158
+ path = path[Dir.pwd.length + 1..-1] if path.index(Dir.pwd) == 0
159
+ result.path = path
160
+ result.lineno = method.source_location[1]
161
+ else
162
+ result.path = [ defined_class, result.static ? '.' : '#', method.name ].join
163
+ end
164
+ @@method_metadata[method] = result
165
+ end
166
+
167
+ public
168
+
142
169
  def build_from_invocation(defined_class, method, receiver, arguments, event: MethodCall.new)
143
170
  event ||= MethodCall.new
144
171
  defined_class ||= 'Class'
172
+
145
173
  event.tap do
146
- static = receiver.is_a?(Module)
147
- event.defined_class = defined_class
148
- event.method_id = method.name.to_s
149
- if method.source_location
150
- path = method.source_location[0]
151
- path = path[Dir.pwd.length + 1..-1] if path.index(Dir.pwd) == 0
152
- event.path = path
153
- event.lineno = method.source_location[1]
154
- else
155
- event.path = [ defined_class, static ? '.' : '#', method.name ].join
156
- end
174
+ metadata = method_metadata(defined_class, method, receiver)
175
+
176
+ event.defined_class = metadata.defined_class
177
+ event.method_id = metadata.method_id
178
+ event.path = metadata.path
179
+ event.lineno = metadata.lineno
180
+ event.static = metadata.static
157
181
 
158
182
  # Check if the method has key parameters. If there are any they'll always be last.
159
183
  # If yes, then extract it from arguments.
@@ -186,7 +210,7 @@ module AppMap
186
210
  object_id: receiver.__id__,
187
211
  value: display_string(receiver)
188
212
  }
189
- event.static = static
213
+
190
214
  MethodEvent.build_from_invocation(:call, event: event)
191
215
  end
192
216
  end
@@ -27,7 +27,7 @@ module AppMap
27
27
 
28
28
  self.request_method = request.method
29
29
  self.url = url
30
- self.headers = AppMap::Util.select_headers(NetHTTP.request_headers(request))
30
+ self.headers = NetHTTP.copy_headers(request)
31
31
  self.params = Rack::Utils.parse_nested_query(query)
32
32
  end
33
33
 
@@ -55,7 +55,7 @@ module AppMap
55
55
  end
56
56
 
57
57
  class HTTPClientResponse < AppMap::Event::MethodReturnIgnoreValue
58
- attr_accessor :status, :mime_type, :headers
58
+ attr_accessor :status, :headers
59
59
 
60
60
  def initialize(response, parent_id, elapsed)
61
61
  super AppMap::Event.next_id_counter, :return, Thread.current.object_id
@@ -63,14 +63,13 @@ module AppMap
63
63
  self.status = response.code.to_i
64
64
  self.parent_id = parent_id
65
65
  self.elapsed = elapsed
66
- self.headers = AppMap::Util.select_headers(NetHTTP.response_headers(response))
66
+ self.headers = NetHTTP.copy_headers(response)
67
67
  end
68
68
 
69
69
  def to_h
70
70
  super.tap do |h|
71
71
  h[:http_client_response] = {
72
72
  status_code: status,
73
- mime_type: mime_type,
74
73
  headers: headers
75
74
  }.compact
76
75
  end
@@ -79,17 +78,15 @@ module AppMap
79
78
 
80
79
  class NetHTTP
81
80
  class << self
82
- def request_headers(request)
81
+ def copy_headers(obj)
83
82
  {}.tap do |headers|
84
- request.each_header do |k,v|
85
- key = [ 'HTTP', Util.underscore(k).upcase ].join('_')
86
- headers[key] = v
83
+ obj.each_header do |key, value|
84
+ key = key.split('-').map(&:capitalize).join('-')
85
+ headers[key] = value
87
86
  end
88
87
  end
89
88
  end
90
-
91
- alias response_headers request_headers
92
-
89
+
93
90
  def handle_call(defined_class, hook_method, receiver, args)
94
91
  # request will call itself again in a start block if it's not already started.
95
92
  return unless receiver.started?
@@ -9,16 +9,14 @@ module AppMap
9
9
  module Rails
10
10
  module RequestHandler
11
11
  class HTTPServerRequest < AppMap::Event::MethodEvent
12
- attr_accessor :normalized_path_info, :request_method, :path_info, :params, :mime_type, :headers, :authorization
12
+ attr_accessor :normalized_path_info, :request_method, :path_info, :params, :headers
13
13
 
14
14
  def initialize(request)
15
15
  super AppMap::Event.next_id_counter, :call, Thread.current.object_id
16
16
 
17
17
  self.request_method = request.request_method
18
18
  self.normalized_path_info = normalized_path(request)
19
- self.mime_type = request.headers['Content-Type']
20
- self.headers = AppMap::Util.select_headers(request.env)
21
- self.authorization = request.headers['Authorization']
19
+ self.headers = AppMap::Util.select_rack_headers(request.env)
22
20
  self.path_info = request.path_info.split('?')[0]
23
21
  # ActionDispatch::Http::ParameterFilter is deprecated
24
22
  parameter_filter_cls = \
@@ -35,9 +33,7 @@ module AppMap
35
33
  h[:http_server_request] = {
36
34
  request_method: request_method,
37
35
  path_info: path_info,
38
- mime_type: mime_type,
39
36
  normalized_path_info: normalized_path_info,
40
- authorization: authorization,
41
37
  headers: headers,
42
38
  }.compact
43
39
 
@@ -72,23 +68,21 @@ module AppMap
72
68
  end
73
69
 
74
70
  class HTTPServerResponse < AppMap::Event::MethodReturnIgnoreValue
75
- attr_accessor :status, :mime_type, :headers
71
+ attr_accessor :status, :headers
76
72
 
77
73
  def initialize(response, parent_id, elapsed)
78
74
  super AppMap::Event.next_id_counter, :return, Thread.current.object_id
79
75
 
80
76
  self.status = response.status
81
- self.mime_type = response.headers['Content-Type']
82
77
  self.parent_id = parent_id
83
78
  self.elapsed = elapsed
84
- self.headers = AppMap::Util.select_headers(response.headers)
79
+ self.headers = response.headers.dup
85
80
  end
86
81
 
87
82
  def to_h
88
83
  super.tap do |h|
89
84
  h[:http_server_response] = {
90
85
  status_code: status,
91
- mime_type: mime_type,
92
86
  headers: headers
93
87
  }.compact
94
88
  end
@@ -109,34 +109,6 @@ module AppMap
109
109
  begin
110
110
  sql = payload[:sql].strip
111
111
 
112
- # Detect whether a function call within a specified filename is present in the call stack.
113
- find_in_backtrace = lambda do |file_name, function_name = nil|
114
- Thread.current.backtrace.find do |line|
115
- tokens = line.split(':')
116
- matches_file = tokens.find { |t| t.rindex(file_name) == (t.length - file_name.length) }
117
- matches_function = function_name.nil? || tokens.find { |t| t == "in `#{function_name}'" }
118
- matches_file && matches_function
119
- end
120
- end
121
-
122
- # Ignore SQL calls which are made while establishing a new connection.
123
- #
124
- # Example:
125
- # /path/to/ruby/2.6.0/gems/sequel-5.20.0/lib/sequel/connection_pool.rb:122:in `make_new'
126
- return if find_in_backtrace.call('lib/sequel/connection_pool.rb', 'make_new')
127
- # lib/active_record/connection_adapters/abstract/connection_pool.rb:811:in `new_connection'
128
- return if find_in_backtrace.call('lib/active_record/connection_adapters/abstract/connection_pool.rb', 'new_connection')
129
-
130
- # Ignore SQL calls which are made while inspecting the DB schema.
131
- #
132
- # Example:
133
- # /path/to/ruby/2.6.0/gems/sequel-5.20.0/lib/sequel/model/base.rb:812:in `get_db_schema'
134
- return if find_in_backtrace.call('lib/sequel/model/base.rb', 'get_db_schema')
135
- # /usr/local/bundle/gems/activerecord-5.2.3/lib/active_record/model_schema.rb:466:in `load_schema!'
136
- return if find_in_backtrace.call('lib/active_record/model_schema.rb', 'load_schema!')
137
- return if find_in_backtrace.call('lib/active_model/attribute_methods.rb', 'define_attribute_methods')
138
- return if find_in_backtrace.call('lib/active_record/connection_adapters/schema_cache.rb')
139
-
140
112
  SQLExaminer.examine payload, sql: sql
141
113
 
142
114
  call = SQLCall.new(payload)
data/lib/appmap/hook.rb CHANGED
@@ -89,7 +89,7 @@ module AppMap
89
89
 
90
90
  config.builtin_hooks.each do |class_name, hooks|
91
91
  Array(hooks).each do |hook|
92
- require hook.package.package_name if hook.package.package_name
92
+ require hook.package.package_name if hook.package.package_name && hook.package.package_name != 'ruby'
93
93
  Array(hook.method_names).each do |method_name|
94
94
  method_name = method_name.to_sym
95
95
  base_cls = class_from_string.(class_name)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'yaml'
2
4
 
3
5
  module AppMap
data/lib/appmap/util.rb CHANGED
@@ -21,6 +21,16 @@ module AppMap
21
21
  WHITE = "\e[37m"
22
22
 
23
23
  class << self
24
+ def parse_function_name(name)
25
+ package_tokens = name.split('/')
26
+
27
+ class_and_name = package_tokens.pop
28
+ class_name, function_name, static = class_and_name.include?('.') ? class_and_name.split('.', 2) + [ true ] : class_and_name.split('#', 2) + [ false ]
29
+
30
+ raise "Malformed fully-qualified function name #{name}" unless function_name
31
+ [ package_tokens.empty? ? 'ruby' : package_tokens.join('/'), class_name, static, function_name ]
32
+ end
33
+
24
34
  # scenario_filename builds a suitable file name from a scenario name.
25
35
  # Special characters are removed, and the file name is truncated to fit within
26
36
  # shell limitations.
@@ -98,8 +108,18 @@ module AppMap
98
108
  event
99
109
  end
100
110
 
101
- def select_headers(env)
111
+ def select_rack_headers(env)
112
+ finalize_headers = lambda do |headers|
113
+ blank?(headers) ? nil : headers
114
+ end
115
+
102
116
  # Rack prepends HTTP_ to all client-sent headers.
117
+
118
+ if !env['rack.version']
119
+ warn "Request headers does not contain rack.version. HTTP_ prefix is not expected."
120
+ return finalize_headers.call(env.dup)
121
+ end
122
+
103
123
  matching_headers = env
104
124
  .select { |k,v| k.start_with? 'HTTP_'}
105
125
  .reject { |k,v| blank?(v) }
@@ -108,7 +128,7 @@ module AppMap
108
128
  value = kv[1]
109
129
  memo[key] = value
110
130
  end
111
- blank?(matching_headers) ? nil : matching_headers
131
+ finalize_headers.call(matching_headers)
112
132
  end
113
133
 
114
134
  def normalize_path(path)
@@ -3,7 +3,7 @@
3
3
  module AppMap
4
4
  URL = 'https://github.com/applandinc/appmap-ruby'
5
5
 
6
- VERSION = '0.65.0'
6
+ VERSION = '0.66.2'
7
7
 
8
8
  APPMAP_FORMAT_VERSION = '1.5.1'
9
9
 
data/spec/config_spec.rb CHANGED
@@ -55,6 +55,34 @@ describe AppMap::Config, docker: false do
55
55
  expect(config.to_h.deep_stringify_keys!).to eq(config_expectation)
56
56
  end
57
57
 
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)
69
+
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!
82
+
83
+ expect(config.to_h.deep_stringify_keys!).to eq(config_expectation)
84
+ end
85
+
58
86
  context do
59
87
  let(:warnings) { @warnings ||= [] }
60
88
  let(:warning) { warnings.join }
@@ -93,9 +93,9 @@ describe 'Depends API' do
93
93
 
94
94
  test_report.clean_appmaps
95
95
 
96
- expect(File.exists?(new_spec_file)).to be_falsey
96
+ expect(File.exist?(new_spec_file)).to be_falsey
97
97
  ensure
98
- FileUtils.rm_f new_spec_file if File.exists?(new_spec_file)
98
+ FileUtils.rm_f new_spec_file if File.exist?(new_spec_file)
99
99
  FileUtils.rm_rf new_spec_file.split('.')[0]
100
100
  end
101
101
  end
@@ -0,0 +1,3 @@
1
+ name: name
2
+ packages:
3
+ - dogs: are friendly
@@ -5,3 +5,7 @@ packages:
5
5
  - path: app/controllers
6
6
  labels: [ mvc.controller ]
7
7
  - gem: sequel
8
+ functions:
9
+ - name: logger/Logger::LogDevice#write
10
+ builtin: true
11
+ label: log
@@ -5,6 +5,10 @@ packages:
5
5
  - path: app/controllers
6
6
  labels: [ mvc.controller ]
7
7
  - gem: sequel
8
+ functions:
9
+ - name: logger/Logger::LogDevice#write
10
+ builtin: true
11
+ label: log
8
12
  swagger:
9
13
  project_version: 1.1.0
10
14
  output_dir: tmp/swagger
@@ -1,7 +1,11 @@
1
1
  require 'rails_spec_helper'
2
2
 
3
+ def default_rails_versions
4
+ ruby_2? ? [ 5, 6 ] : [ 6 ]
5
+ end
6
+
3
7
  # Rails5 doesn't work with Ruby 3.x
4
- RailsVersions = ruby_2? ? [ 5, 6 ] : [ 6 ]
8
+ RailsVersions = ENV['RAILS_VERSIONS'] || default_rails_versions
5
9
 
6
10
  describe 'Rails' do
7
11
  RailsVersions.each do |rails_major_version| # rubocop:disable Metrics/BlockLength
@@ -54,7 +58,7 @@ describe 'Rails' do
54
58
  hash_including(
55
59
  'http_server_response' => hash_including(
56
60
  'status_code' => 201,
57
- 'mime_type' => 'application/json; charset=utf-8',
61
+ 'headers' => hash_including('Content-Type' => 'application/json; charset=utf-8'),
58
62
  )
59
63
  )
60
64
  )
@@ -91,6 +95,15 @@ describe 'Rails' do
91
95
  )
92
96
  end
93
97
 
98
+ it 'captures log events' do
99
+ expect(events).to include hash_including(
100
+ 'event' => 'call',
101
+ 'defined_class' => 'Logger::LogDevice',
102
+ 'method_id' => 'write',
103
+ 'static' => false
104
+ )
105
+ end
106
+
94
107
  context 'with an object-style message' do
95
108
  let(:appmap_json_file) { 'Api_UsersController_POST_api_users_with_required_parameters_with_object-style_parameters_creates_a_user.appmap.json' }
96
109
 
@@ -6,6 +6,7 @@ class AgentSetupValidateTest < Minitest::Test
6
6
  NON_EXISTING_CONFIG_FILENAME = '123.yml'
7
7
  INVALID_YAML_CONFIG_FILENAME = 'spec/fixtures/config/invalid_yaml_config.yml'
8
8
  INVALID_CONFIG_FILENAME = 'spec/fixtures/config/invalid_config.yml'
9
+ MISSING_PATH_OR_GEM_CONFIG_FILENAME = 'spec/fixtures/config/missing_path_or_gem.yml'
9
10
 
10
11
  def test_init_when_config_exists
11
12
  output = `./exe/appmap-agent-validate`
@@ -72,4 +73,22 @@ class AgentSetupValidateTest < Minitest::Test
72
73
  ])
73
74
  assert_equal expected, output.strip
74
75
  end
76
+
77
+ def test_init_with_missing_package_key
78
+ output = `./exe/appmap-agent-validate -c #{MISSING_PATH_OR_GEM_CONFIG_FILENAME}`
79
+ assert_equal 0, $CHILD_STATUS.exitstatus
80
+ expected = JSON.pretty_generate([
81
+ {
82
+ level: :error,
83
+ message: 'AppMap auto-configuration is currently not available for non Rails projects'
84
+ },
85
+ {
86
+ level: :error,
87
+ filename: MISSING_PATH_OR_GEM_CONFIG_FILENAME,
88
+ message: "AppMap configuration #{MISSING_PATH_OR_GEM_CONFIG_FILENAME} could not be loaded",
89
+ detailed_message: "AppMap config 'package' element should specify 'gem' or 'path'"
90
+ }
91
+ ])
92
+ assert_equal expected, output.strip
93
+ end
75
94
  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.65.0
4
+ version: 0.66.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-09-14 00:00:00.000000000 Z
11
+ date: 2021-10-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -3462,6 +3462,7 @@ files:
3462
3462
  - spec/fixtures/config/incomplete_config.yml
3463
3463
  - spec/fixtures/config/invalid_config.yml
3464
3464
  - spec/fixtures/config/invalid_yaml_config.yml
3465
+ - spec/fixtures/config/missing_path_or_gem.yml
3465
3466
  - spec/fixtures/config/valid_config.yml
3466
3467
  - spec/fixtures/depends/.gitignore
3467
3468
  - spec/fixtures/depends/app/controllers/api/api_keys_controller.rb
@@ -3738,7 +3739,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
3738
3739
  - !ruby/object:Gem::Version
3739
3740
  version: '0'
3740
3741
  requirements: []
3741
- rubygems_version: 3.0.8
3742
+ rubygems_version: 3.0.6
3742
3743
  signing_key:
3743
3744
  specification_version: 4
3744
3745
  summary: Record the operation of a Ruby program, using the AppLand 'AppMap' format.