appmap 0.46.0 → 0.48.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: b563a80bb77c42dd44d9fd299ed9b1a273abaf9d43256e23a0856fc06849f74c
4
- data.tar.gz: 25dead3dd3c057ee4585c8af0e85e8d495e689a18f3aa02d850bdf470c98d5ff
3
+ metadata.gz: 455ac48f4acf31694cb571912703f62b95687b8b833be0ced7b8ab5008c3960b
4
+ data.tar.gz: 272c68f804affe1a461ec504475b95d1c7968e506644442437a45dd28ff7d664
5
5
  SHA512:
6
- metadata.gz: 6bc94e6ee51daf178d9db411f2393538e91e4cc6a985c9b1d28ccc08feda7b6b59708d9f5f0f2cbd2184b058ff39d245c2c84e4cb25a64d5e6ddac96587c2a76
7
- data.tar.gz: 252d3955b6daedfb687091c8fb735c3f61d4cca3d7fa6ac44182c9c87f2185f53e886fa4e96f3896c35a7b5bb67e3302120dfa4e978324783b72d446f531b630
6
+ metadata.gz: 3bfbb562490380393c669aaeb2813389a52bf18f8e769f69a496ffe9fc26a493d9f55cfa161926d26deca370b1e18f99896eb0e269e59cbd6647801b7238c565
7
+ data.tar.gz: ba4b69852fc4be1850b5fd20c713e21799848bd32a251cdcc3bae87e4bf25605621dccc381e32018397ff52bbc442859065d8f1d8313211c135c1a9fb3ecb6d7
data/.dockerignore CHANGED
@@ -2,4 +2,3 @@ vendor
2
2
  node_modules
3
3
  spec/fixtures/rails*_users_app
4
4
  spec/fixtures/rack_users_app
5
-
data/CHANGELOG.md CHANGED
@@ -1,3 +1,40 @@
1
+ ## [0.48.2](https://github.com/applandinc/appmap-ruby/compare/v0.48.1...v0.48.2) (2021-05-26)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * Correct the method-hooking logic to capture some missing model methods ([be529bd](https://github.com/applandinc/appmap-ruby/commit/be529bdce7d4fdf9f1a2fdd32259d792f29f4f13))
7
+
8
+ ## [0.48.1](https://github.com/applandinc/appmap-ruby/compare/v0.48.0...v0.48.1) (2021-05-25)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * Account for bundle path when normalizing source path ([095c278](https://github.com/applandinc/appmap-ruby/commit/095c27818fc8ae8dfa39b30516d37c6dfd642d9c))
14
+ * Scan exception messages for non-UTF8 characters ([3dcaeae](https://github.com/applandinc/appmap-ruby/commit/3dcaeae44da5e40e432eda41caf5b9ebff5bea12))
15
+
16
+ # [0.48.0](https://github.com/applandinc/appmap-ruby/compare/v0.47.1...v0.48.0) (2021-05-19)
17
+
18
+
19
+ ### Features
20
+
21
+ * Hook the code only when APPMAP=true ([dd9e383](https://github.com/applandinc/appmap-ruby/commit/dd9e383024d1d9205a617d46bd64b90820035533))
22
+ * Remove server process recording from doc and tests ([383ba0a](https://github.com/applandinc/appmap-ruby/commit/383ba0ad444922a0a85409477d11bc7ed06a9160))
23
+
24
+ ## [0.47.1](https://github.com/applandinc/appmap-ruby/compare/v0.47.0...v0.47.1) (2021-05-13)
25
+
26
+
27
+ ### Bug Fixes
28
+
29
+ * Add the proper template function hooks for Rails 6.0.7 ([175f489](https://github.com/applandinc/appmap-ruby/commit/175f489acbaed77ad52a18d805e4b6eeae1abfdb))
30
+
31
+ # [0.47.0](https://github.com/applandinc/appmap-ruby/compare/v0.46.0...v0.47.0) (2021-05-13)
32
+
33
+
34
+ ### Features
35
+
36
+ * Emit swagger-style normalized paths instead of Rails-style ones ([5a93cd7](https://github.com/applandinc/appmap-ruby/commit/5a93cd7096ca195146a84a6733c7d502dbcd0272))
37
+
1
38
  # [0.46.0](https://github.com/applandinc/appmap-ruby/compare/v0.45.1...v0.46.0) (2021-05-12)
2
39
 
3
40
 
data/README.md CHANGED
@@ -83,22 +83,6 @@ If you are using Ruby on Rails, require the railtie after Rails is loaded.
83
83
  require 'appmap/railtie' if defined?(AppMap).
84
84
  ```
85
85
 
86
- **application.rb**
87
-
88
- Add this line to *application.rb*, to enable server recording with `APPMAP_RECORD=true`:
89
-
90
- ```ruby
91
- module MyApp
92
- class Application < Rails::Application
93
- ...
94
-
95
- config.appmap.enabled = true if ENV['APPMAP_RECORD']
96
-
97
- ...
98
- end
99
- end
100
- ```
101
-
102
86
  # Configuration
103
87
 
104
88
  When you run your program, the `appmap` gem reads configuration settings from `appmap.yml`. Here's a sample configuration
@@ -327,10 +311,10 @@ end
327
311
 
328
312
  2. (optional) Download and unpack the [AppLand browser extension](https://github.com/applandinc/appland-browser-extension). Install into Chrome using `chrome://extensions/`. Turn on "Developer Mode" and then load the extension using the "Load unpacked" button.
329
313
 
330
- 3. Start your Rails application server, with `APPMAP_RECORD=true`. For example:
314
+ 3. Start your Rails application server, with `APPMAP=true`. For example:
331
315
 
332
316
  ```sh-session
333
- $ APPMAP_RECORD=true bundle exec rails server
317
+ $ APPMAP=true bundle exec rails server
334
318
  ```
335
319
 
336
320
  4. Start the recording
data/lib/appmap.rb CHANGED
@@ -98,4 +98,4 @@ module AppMap
98
98
  end
99
99
 
100
100
  require 'appmap/railtie' if defined?(::Rails::Railtie)
101
- AppMap.initialize unless ENV['APPMAP_INITIALIZE'] == 'false'
101
+ AppMap.initialize if ENV['APPMAP'] == 'true'
data/lib/appmap/config.rb CHANGED
@@ -85,18 +85,7 @@ module AppMap
85
85
  end
86
86
  end
87
87
 
88
- Function = Struct.new(:package, :cls, :labels, :function_names) do # :nodoc:
89
- def to_h
90
- {
91
- package: package,
92
- class: cls,
93
- labels: labels,
94
- functions: function_names.map(&:to_sym)
95
- }.compact
96
- end
97
- end
98
- private_constant :Function
99
-
88
+ # Identifies specific methods within a package which should be hooked.
100
89
  class TargetMethods # :nodoc:
101
90
  attr_reader :method_names, :package
102
91
 
@@ -118,28 +107,91 @@ module AppMap
118
107
  end
119
108
  private_constant :TargetMethods
120
109
 
121
- OPENSSL_PACKAGES = ->(labels) { Package.build_from_path('openssl', package_name: 'openssl', labels: labels) }
110
+ # Function represents a specific function configured for hooking by the +functions+
111
+ # entry in appmap.yml. When the Config is initialized, each Function is converted into
112
+ # a Package and TargetMethods. It's called a Function rather than a Method, because Function
113
+ # is the AppMap terminology.
114
+ Function = Struct.new(:package, :cls, :labels, :function_names) do # :nodoc:
115
+ def to_h
116
+ {
117
+ package: package,
118
+ class: cls,
119
+ labels: labels,
120
+ functions: function_names.map(&:to_sym)
121
+ }.compact
122
+ end
123
+ end
124
+ private_constant :Function
122
125
 
123
- # Methods that should always be hooked, with their containing
124
- # package and labels that should be applied to them.
125
- HOOKED_METHODS = {
126
- 'ActionView::Renderer' => TargetMethods.new(:render, Package.build_from_gem('actionview', shallow: false, package_name: 'action_view', labels: %w[mvc.view], optional: true).tap do |package|
127
- package.handler_class = AppMap::Handler::Rails::Template::RenderHandler if package
128
- end),
129
- 'ActionView::Resolver' => TargetMethods.new(%i[find_all find_all_anywhere], Package.build_from_gem('actionview', shallow: false, package_name: 'action_view', labels: %w[mvc.template.resolver], optional: true).tap do |package|
130
- package.handler_class = AppMap::Handler::Rails::Template::ResolverHandler if package
131
- end),
132
- 'ActionDispatch::Request::Session' => TargetMethods.new(%i[destroy [] dig values []= clear update delete fetch merge], Package.build_from_gem('actionpack', shallow: false, package_name: 'action_dispatch', labels: %w[http.session], optional: true)),
133
- 'ActionDispatch::Cookies::CookieJar' => TargetMethods.new(%i[[]= clear update delete recycle], Package.build_from_gem('actionpack', shallow: false, package_name: 'action_dispatch', labels: %w[http.cookie], optional: true)),
134
- 'ActionDispatch::Cookies::EncryptedCookieJar' => TargetMethods.new(%i[[]=], Package.build_from_gem('actionpack', shallow: false, package_name: 'action_dispatch', labels: %w[http.cookie crypto.encrypt], optional: true)),
135
- 'CanCan::ControllerAdditions' => TargetMethods.new(%i[authorize! can? cannot?], Package.build_from_gem('cancancan', shallow: false, labels: %w[security.authorization], optional: true)),
136
- 'CanCan::Ability' => TargetMethods.new(%i[authorize!], Package.build_from_gem('cancancan', shallow: false, labels: %w[security.authorization], optional: true)),
137
- 'ActionController::Instrumentation' => [
138
- TargetMethods.new(%i[process_action send_file send_data redirect_to], Package.build_from_gem('actionpack', shallow: false, package_name: 'action_controller', labels: %w[mvc.controller], optional: true))
139
- ]
140
- }.freeze
126
+ ClassTargetMethods = Struct.new(:cls, :target_methods) # :nodoc:
127
+ private_constant :ClassTargetMethods
128
+
129
+ MethodHook = Struct.new(:cls, :method_names, :labels) # :nodoc:
130
+ private_constant :MethodHook
131
+
132
+ class << self
133
+ def package_hooks(gem_name, methods, handler_class: nil, package_name: nil)
134
+ Array(methods).map do |method|
135
+ package = Package.build_from_gem(gem_name, package_name: package_name, labels: method.labels, shallow: false, optional: true)
136
+ next unless package
137
+
138
+ package.handler_class = handler_class if handler_class
139
+ ClassTargetMethods.new(method.cls, TargetMethods.new(Array(method.method_names), package))
140
+ end.compact
141
+ end
142
+
143
+ def method_hook(cls, method_names, labels)
144
+ MethodHook.new(cls, method_names, labels)
145
+ end
146
+ end
147
+
148
+ # Hook well-known functions. When a function configured here is available in the bundle, it will be hooked with the
149
+ # predefined labels specified here. If any of these hooks are not desired, they can be disabled in the +exclude+ section
150
+ # of appmap.yml.
151
+ METHOD_HOOKS = [
152
+ package_hooks('actionview',
153
+ [
154
+ method_hook('ActionView::Renderer', :render, %w[mvc.view]),
155
+ method_hook('ActionView::TemplateRenderer', :render, %w[mvc.view]),
156
+ method_hook('ActionView::PartialRenderer', :render, %w[mvc.view])
157
+ ],
158
+ handler_class: AppMap::Handler::Rails::Template::RenderHandler,
159
+ package_name: 'action_view'
160
+ ),
161
+ package_hooks('actionview',
162
+ [
163
+ method_hook('ActionView::Resolver', %i[find_all find_all_anywhere], %w[mvc.template.resolver])
164
+ ],
165
+ handler_class: AppMap::Handler::Rails::Template::ResolverHandler,
166
+ package_name: 'action_view'
167
+ ),
168
+ package_hooks('actionpack',
169
+ [
170
+ method_hook('ActionDispatch::Request::Session', %i[destroy [] dig values []= clear update delete fetch merge], %w[http.session]),
171
+ method_hook('ActionDispatch::Cookies::CookieJar', %i[[]= clear update delete recycle], %w[http.session]),
172
+ method_hook('ActionDispatch::Cookies::EncryptedCookieJar', %i[[]= clear update delete recycle], %w[http.cookie crypto.encrypt])
173
+ ],
174
+ package_name: 'action_dispatch'
175
+ ),
176
+ package_hooks('cancancan',
177
+ [
178
+ method_hook('CanCan::ControllerAdditions', %i[authorize! can? cannot?], %w[security.authorization]),
179
+ method_hook('CanCan::Ability', %i[authorize?], %w[security.authorization])
180
+ ]
181
+ ),
182
+ package_hooks('actionpack',
183
+ [
184
+ method_hook('ActionController::Instrumentation', %i[process_action send_file send_data redirect_to], %w[mvc.controller])
185
+ ],
186
+ package_name: 'action_controller'
187
+ )
188
+ ].flatten.freeze
141
189
 
142
- BUILTIN_METHODS = {
190
+ OPENSSL_PACKAGES = ->(labels) { Package.build_from_path('openssl', package_name: 'openssl', labels: labels) }
191
+
192
+ # Hook functions which are builtin to Ruby. Because they are builtins, they may be loaded before appmap.
193
+ # Therefore, we can't rely on TracePoint to report the loading of this code.
194
+ BUILTIN_HOOKS = {
143
195
  'OpenSSL::PKey::PKey' => TargetMethods.new(:sign, OPENSSL_PACKAGES.(%w[crypto.pkey])),
144
196
  'OpenSSL::X509::Request' => TargetMethods.new(%i[sign verify], OPENSSL_PACKAGES.(%w[crypto.x509])),
145
197
  'OpenSSL::PKCS5' => TargetMethods.new(%i[pbkdf2_hmac_sha1 pbkdf2_hmac], OPENSSL_PACKAGES.(%w[crypto.pkcs5])),
@@ -166,26 +218,29 @@ module AppMap
166
218
  'JSON::Ext::Generator::State' => TargetMethods.new(:generate, Package.build_from_path('json', package_name: 'json', labels: %w[format.json])),
167
219
  }.freeze
168
220
 
169
- attr_reader :name, :packages, :exclude, :hooked_methods, :builtin_methods
221
+ attr_reader :name, :packages, :exclude, :hooked_methods, :builtin_hooks
170
222
 
171
223
  def initialize(name, packages, exclude: [], functions: [])
172
224
  @name = name
173
225
  @packages = packages
174
- @hook_paths = packages.map(&:path)
226
+ @hook_paths = Set.new(packages.map(&:path))
175
227
  @exclude = exclude
176
- @builtin_methods = BUILTIN_METHODS
228
+ @builtin_hooks = BUILTIN_HOOKS
177
229
  @functions = functions
178
- @hooked_methods = HOOKED_METHODS.dup
230
+
231
+ @hooked_methods = METHOD_HOOKS.each_with_object(Hash.new { |h,k| h[k] = [] }) do |cls_target_methods, hooked_methods|
232
+ hooked_methods[cls_target_methods.cls] << cls_target_methods.target_methods
233
+ end
234
+
179
235
  functions.each do |func|
180
236
  package_options = {}
181
237
  package_options[:labels] = func.labels if func.labels
182
- @hooked_methods[func.cls] ||= []
183
238
  @hooked_methods[func.cls] << TargetMethods.new(func.function_names, Package.build_from_path(func.package, package_options))
184
239
  end
185
240
 
186
241
  @hooked_methods.each_value do |hooks|
187
242
  Array(hooks).each do |hook|
188
- @hook_paths << hook.package.path if hook.package
243
+ @hook_paths << hook.package.path
189
244
  end
190
245
  end
191
246
  end
data/lib/appmap/event.rb CHANGED
@@ -213,7 +213,7 @@ module AppMap
213
213
  exception_backtrace = next_exception.backtrace_locations.try(:[], 0)
214
214
  exceptions << {
215
215
  class: best_class_name(next_exception),
216
- message: next_exception.message,
216
+ message: display_string(next_exception.message),
217
217
  object_id: next_exception.__id__,
218
218
  path: exception_backtrace&.path,
219
219
  lineno: exception_backtrace&.lineno
@@ -65,7 +65,7 @@ module AppMap
65
65
  next unless app.matches? request
66
66
  return normalized_path request, app.rack_app.routes.router if app.engine?
67
67
 
68
- return route.path.spec.to_s
68
+ return AppMap::Util.swaggerize_path(route.path.spec.to_s)
69
69
  end
70
70
  end
71
71
  end
@@ -105,12 +105,18 @@ module AppMap
105
105
  # If so, populate the template path. In all cases, add a TemplateMethod so that the
106
106
  # template will be recorded in the classMap.
107
107
  def handle_return(call_event_id, elapsed, return_value, exception)
108
- warn "Resolver return: #{return_value.inspect}" if LOG
109
-
110
108
  renderer = Array(Thread.current[TEMPLATE_RENDERER]).last
111
- path = Array(return_value).first&.inspect
109
+ path_obj = Array(return_value).first
110
+
111
+ warn "Resolver return: #{path_obj}" if LOG
112
112
 
113
- if path
113
+ if path_obj
114
+ path = if path_obj.respond_to?(:identifier) && path_obj.inspect.index('#<')
115
+ path_obj.identifier
116
+ else
117
+ path_obj.inspect
118
+ end
119
+ path = path[Dir.pwd.length + 1..-1] if path.index(Dir.pwd) == 0
114
120
  AppMap.tracing.record_method(TemplateMethod.new(path))
115
121
  renderer.path ||= path if renderer
116
122
  end
data/lib/appmap/hook.rb CHANGED
@@ -36,7 +36,7 @@ module AppMap
36
36
 
37
37
  def initialize(config)
38
38
  @config = config
39
- @trace_locations = []
39
+ @trace_enabled = []
40
40
  # Paths that are known to be non-tracing
41
41
  @notrace_paths = Set.new
42
42
  end
@@ -47,10 +47,8 @@ module AppMap
47
47
 
48
48
  hook_builtins
49
49
 
50
- @trace_begin = TracePoint.new(:class, &method(:trace_class))
51
50
  @trace_end = TracePoint.new(:end, &method(:trace_end))
52
-
53
- @trace_begin.enable(&block)
51
+ @trace_end.enable(&block)
54
52
  end
55
53
 
56
54
  # hook_builtins builds hooks for code that is built in to the Ruby standard library.
@@ -64,7 +62,7 @@ module AppMap
64
62
  end
65
63
  end
66
64
 
67
- config.builtin_methods.each do |class_name, hooks|
65
+ config.builtin_hooks.each do |class_name, hooks|
68
66
  Array(hooks).each do |hook|
69
67
  require hook.package.package_name if hook.package.package_name
70
68
  Array(hook.method_names).each do |method_name|
@@ -96,29 +94,22 @@ module AppMap
96
94
 
97
95
  protected
98
96
 
99
- def trace_class(trace_point)
100
- path = trace_point.path
101
-
102
- return if @notrace_paths.member?(path)
103
-
104
- if config.path_enabled?(path)
105
- location = trace_location(trace_point)
106
- warn "Entering hook-enabled location #{location}" if Hook::LOG || Hook::LOG_HOOK
107
- @trace_locations << location
108
- unless @trace_end.enabled?
109
- warn "Enabling hooking" if Hook::LOG || Hook::LOG_HOOK
110
- @trace_end.enable
111
- end
112
- else
113
- @notrace_paths << path
114
- end
115
- end
116
-
117
97
  def trace_location(trace_point)
118
98
  [ trace_point.path, trace_point.lineno ].join(':')
119
99
  end
120
100
 
121
101
  def trace_end(trace_point)
102
+ location = trace_location(trace_point)
103
+ warn "Class or module ends at location #{trace_location(trace_point)}" if Hook::LOG || Hook::LOG_HOOK
104
+
105
+ path = trace_point.path
106
+ enabled = !@notrace_paths.member?(path) && config.path_enabled?(path)
107
+ if !enabled
108
+ warn "Not hooking - path is not enabled" if Hook::LOG || Hook::LOG_HOOK
109
+ @notrace_paths << path
110
+ return
111
+ end
112
+
122
113
  cls = trace_point.self
123
114
 
124
115
  instance_methods = cls.public_instance_methods(false) - OBJECT_INSTANCE_METHODS
@@ -139,6 +130,8 @@ module AppMap
139
130
  # a stack overflow in the defined hook method.
140
131
  next if %w[Marshal AppMap ActiveSupport].member?((hook_cls&.name || '').split('::')[0])
141
132
 
133
+ next if method_id == :call
134
+
142
135
  method = begin
143
136
  hook_cls.public_instance_method(method_id)
144
137
  rescue NameError
@@ -149,7 +142,8 @@ module AppMap
149
142
  warn "AppMap: Examining #{hook_cls} #{method.name}" if LOG
150
143
 
151
144
  disasm = RubyVM::InstructionSequence.disasm(method)
152
- # Skip methods that have no instruction sequence, as they are obviously trivial.
145
+ # Skip methods that have no instruction sequence, as they are either have no body or they are or native.
146
+ # TODO: Figure out how to tell the difference?
153
147
  next unless disasm
154
148
 
155
149
  package = config.lookup_package(hook_cls, method)
@@ -168,13 +162,6 @@ module AppMap
168
162
  # uninitialized constant Faraday::Connection
169
163
  warn "NameError in #{__FILE__}: #{$!.message}"
170
164
  end
171
-
172
- location = @trace_locations.pop
173
- warn "Leaving hook-enabled location #{location}" if Hook::LOG || Hook::LOG_HOOK
174
- if @trace_locations.empty?
175
- warn "Disabling hooking" if Hook::LOG || Hook::LOG_HOOK
176
- @trace_end.disable
177
- end
178
165
  end
179
166
  end
180
167
  end
@@ -98,7 +98,7 @@ module AppMap
98
98
  if exception
99
99
  m[:exception] = {
100
100
  class: exception.class.name,
101
- message: exception.to_s
101
+ message: AppMap::Event::MethodEvent.display_string(exception.to_s)
102
102
  }
103
103
  end
104
104
  end
@@ -3,8 +3,6 @@
3
3
  module AppMap
4
4
  # Railtie connects the AppMap recorder to Rails-specific features.
5
5
  class Railtie < ::Rails::Railtie
6
- config.appmap = ActiveSupport::OrderedOptions.new
7
-
8
6
  # appmap.subscribe subscribes to ActiveSupport Notifications so that they can be recorded as
9
7
  # AppMap events.
10
8
  initializer 'appmap.subscribe' do |_| # params: app
@@ -15,25 +13,5 @@ module AppMap
15
13
 
16
14
  AppMap::Handler::Rails::RequestHandler::HookMethod.new.activate
17
15
  end
18
-
19
- # appmap.trace begins recording an AppMap trace and writes it to appmap.json.
20
- # This behavior is only activated if the configuration setting app.config.appmap.enabled
21
- # is truthy.
22
- initializer 'appmap.trace', after: 'appmap.subscribe' do |app|
23
- lambda do
24
- return unless app.config.appmap.enabled
25
-
26
- require 'appmap/command/record'
27
- require 'json'
28
- AppMap::Command::Record.new(AppMap.configuration).perform do |version, metadata, class_map, events|
29
- appmap = JSON.generate \
30
- version: version,
31
- metadata: metadata,
32
- classMap: class_map,
33
- events: events
34
- File.open('appmap.json', 'w').write(appmap)
35
- end
36
- end.call
37
- end
38
16
  end
39
- end unless ENV['APPMAP_INITIALIZE'] == 'false'
17
+ end if ENV['APPMAP'] == 'true'
data/lib/appmap/rspec.rb CHANGED
@@ -183,7 +183,7 @@ module AppMap
183
183
  if exception
184
184
  m[:exception] = {
185
185
  class: exception.class.name,
186
- message: exception.to_s
186
+ message: AppMap::Event::MethodEvent.display_string(exception.to_s)
187
187
  }
188
188
  end
189
189
  end
data/lib/appmap/util.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'bundler'
4
+
3
5
  module AppMap
4
6
  module Util
5
7
  class << self
@@ -94,13 +96,23 @@ module AppMap
94
96
  end
95
97
 
96
98
  def normalize_path(path)
97
- if path.index(Dir.pwd) == 0
99
+ if path.index(Dir.pwd) == 0 && !path.index(Bundler.bundle_path.to_s)
98
100
  path[Dir.pwd.length + 1..-1]
99
101
  else
100
102
  path
101
103
  end
102
104
  end
103
105
 
106
+ # Convert a Rails-style path from /org/:org_id(.:format)
107
+ # to Swagger-style paths like /org/{org_id}
108
+ def swaggerize_path(path)
109
+ path = path.split('(.')[0]
110
+ tokens = path.split('/')
111
+ tokens.map do |token|
112
+ token.gsub /^:(.*)/, '{\1}'
113
+ end.join('/')
114
+ end
115
+
104
116
  # Atomically writes AppMap data to +filename+.
105
117
  def write_appmap(filename, appmap)
106
118
  require 'fileutils'
@@ -3,7 +3,7 @@
3
3
  module AppMap
4
4
  URL = 'https://github.com/applandinc/appmap-ruby'
5
5
 
6
- VERSION = '0.46.0'
6
+ VERSION = '0.48.2'
7
7
 
8
- APPMAP_FORMAT_VERSION = '1.5.0'
8
+ APPMAP_FORMAT_VERSION = '1.5.1'
9
9
  end
@@ -42,7 +42,7 @@ describe 'Rails' do
42
42
  hash_including(
43
43
  'http_server_request' => hash_including(
44
44
  'request_method' => 'POST',
45
- 'normalized_path_info' => '/api/users(.:format)',
45
+ 'normalized_path_info' => '/api/users',
46
46
  'path_info' => '/api/users'
47
47
  ),
48
48
  'message' => include(
@@ -197,7 +197,7 @@ describe 'Rails' do
197
197
  'http_server_request' => {
198
198
  'request_method' => 'GET',
199
199
  'path_info' => '/users/alice',
200
- 'normalized_path_info' => '/users/:id(.:format)',
200
+ 'normalized_path_info' => '/users/{id}',
201
201
  'headers' => {
202
202
  'Host' => 'test.host',
203
203
  'User-Agent' => 'Rails Testing'
@@ -53,3 +53,9 @@ class ToSRaises
53
53
  "hello"
54
54
  end
55
55
  end
56
+
57
+ class ExceptionMethod
58
+ def raise_illegal_utf8_message
59
+ raise "809: unexpected token at 'x\x9C\xED=\x8Bv\xD3ƶ\xBF2\xB8]\xC5\xE9qdI\x96eǫ4\xA4h΅\x84\xE5z\x96\xAA\xD8\xE3\xE3D\xB2\xE4J2\x90E\xF8\xF7\xBB\xF7\xCC\xE81\x92\xE2\x88ā'"
60
+ end
61
+ end
@@ -38,8 +38,6 @@ module UsersApp
38
38
  # Initialize configuration defaults for originally generated Rails version.
39
39
  config.load_defaults 5.2
40
40
 
41
- config.appmap.enabled = true if ENV['APPMAP'] == 'true' && !Rails.env.test?
42
-
43
41
  # Settings in config/environments/* take precedence over those specified here.
44
42
  # Application configuration can go into files in config/initializers
45
43
  # -- all .rb files in that directory are automatically loaded after loading
@@ -38,8 +38,6 @@ module UsersApp
38
38
  # Initialize configuration defaults for originally generated Rails version.
39
39
  config.load_defaults 5.2
40
40
 
41
- config.appmap.enabled = true if ENV['APPMAP'] == 'true' && !Rails.env.test?
42
-
43
41
  # Settings in config/environments/* take precedence over those specified here.
44
42
  # Application configuration can go into files in config/initializers
45
43
  # -- all .rb files in that directory are automatically loaded after loading
data/spec/hook_spec.rb CHANGED
@@ -64,65 +64,14 @@ describe 'AppMap class Hooking', docker: false do
64
64
  expect(config.never_hook?(ExcludeTest, ExcludeTest.method(:cls_method))).to be_truthy
65
65
  end
66
66
 
67
- it "handles an instance method named 'call' without issues" do
67
+ it "an instance method named 'call' will be ignored" do
68
68
  events_yaml = <<~YAML
69
- ---
70
- - :id: 1
71
- :event: :call
72
- :defined_class: MethodNamedCall
73
- :method_id: call
74
- :path: spec/fixtures/hook/method_named_call.rb
75
- :lineno: 8
76
- :static: false
77
- :parameters:
78
- - :name: :a
79
- :class: Integer
80
- :value: '1'
81
- :kind: :req
82
- - :name: :b
83
- :class: Integer
84
- :value: '2'
85
- :kind: :req
86
- - :name: :c
87
- :class: Integer
88
- :value: '3'
89
- :kind: :req
90
- - :name: :d
91
- :class: Integer
92
- :value: '4'
93
- :kind: :req
94
- - :name: :e
95
- :class: Integer
96
- :value: '5'
97
- :kind: :req
98
- :receiver:
99
- :class: MethodNamedCall
100
- :value: MethodNamedCall
101
- - :id: 2
102
- :event: :return
103
- :parent_id: 1
104
- :return_value:
105
- :class: String
106
- :value: 1 2 3 4 5
69
+ --- []
107
70
  YAML
108
71
 
109
72
  _, tracer = test_hook_behavior 'spec/fixtures/hook/method_named_call.rb', events_yaml do
110
73
  expect(MethodNamedCall.new.call(1, 2, 3, 4, 5)).to eq('1 2 3 4 5')
111
74
  end
112
- class_map = AppMap.class_map(tracer.event_methods)
113
- expect(Diffy::Diff.new(<<~CLASSMAP, YAML.dump(class_map)).to_s).to eq('')
114
- ---
115
- - :name: spec/fixtures/hook/method_named_call.rb
116
- :type: package
117
- :children:
118
- - :name: MethodNamedCall
119
- :type: class
120
- :children:
121
- - :name: call
122
- :type: function
123
- :location: spec/fixtures/hook/method_named_call.rb:8
124
- :static: false
125
- CLASSMAP
126
75
  end
127
76
 
128
77
  it 'can custom hook and label a function' do
@@ -634,7 +583,7 @@ describe 'AppMap class Hooking', docker: false do
634
583
  end
635
584
  end
636
585
 
637
- it 'Reports exceptions' do
586
+ it 'reports exceptions' do
638
587
  events_yaml = <<~YAML
639
588
  ---
640
589
  - :id: 1
@@ -666,6 +615,38 @@ describe 'AppMap class Hooking', docker: false do
666
615
  end
667
616
  end
668
617
 
618
+ it 'sanitizes exception messages' do
619
+ events_yaml = <<~YAML
620
+ ---
621
+ - :id: 1
622
+ :event: :call
623
+ :defined_class: ExceptionMethod
624
+ :method_id: raise_illegal_utf8_message
625
+ :path: spec/fixtures/hook/exception_method.rb
626
+ :lineno: 58
627
+ :static: false
628
+ :parameters: []
629
+ :receiver:
630
+ :class: ExceptionMethod
631
+ :value: Exception Method fixture
632
+ - :id: 2
633
+ :event: :return
634
+ :parent_id: 1
635
+ :exceptions:
636
+ - :class: RuntimeError
637
+ :message: '809: unexpected token at ''x__=_v_ƶ_2_]__qdI_eǫ4_h΅__z_____D__J2_E______1__ā'''
638
+ :path: spec/fixtures/hook/exception_method.rb
639
+ :lineno: 59
640
+ YAML
641
+ test_hook_behavior 'spec/fixtures/hook/exception_method.rb', events_yaml do
642
+ begin
643
+ ExceptionMethod.new.raise_illegal_utf8_message
644
+ rescue
645
+ # don't let the exception fail the test
646
+ end
647
+ end
648
+ end
649
+
669
650
  context 'string conversions works for the receiver when' do
670
651
 
671
652
  it 'is missing #to_s' do
data/spec/railtie_spec.rb CHANGED
@@ -4,7 +4,7 @@ describe 'AppMap tracer via Railtie' do
4
4
  include_context 'Rails app pg database', 'spec/fixtures/rails5_users_app' do
5
5
  let(:env) { {} }
6
6
 
7
- let(:cmd) { %(docker-compose run --rm -e RAILS_ENV -e APPMAP app ./bin/rails r "puts Rails.configuration.appmap.enabled.inspect") }
7
+ let(:cmd) { %(docker-compose run --rm -e RAILS_ENV=development -e APPMAP app ./bin/rails r "puts AppMap.instance_variable_get('@configuration').nil?") }
8
8
  let(:command_capture2) do
9
9
  require 'open3'
10
10
  Open3.capture3(env, cmd, chdir: fixture_dir).tap do |result|
@@ -23,20 +23,16 @@ describe 'AppMap tracer via Railtie' do
23
23
  let(:command_output) { command_capture2[0].strip }
24
24
  let(:command_result) { command_capture2[2] }
25
25
 
26
- it 'is disabled by default' do
27
- expect(command_output).to eq('nil')
26
+ describe 'with APPMAP=false' do
27
+ let(:env) { { 'APPMAP' => 'false' } }
28
+ it 'is disabled' do
29
+ expect(command_output).to eq('true')
30
+ end
28
31
  end
29
-
30
32
  describe 'with APPMAP=true' do
31
33
  let(:env) { { 'APPMAP' => 'true' } }
32
34
  it 'is enabled' do
33
- expect(command_output.split("\n")).to include('true')
34
- end
35
- context 'and RAILS_ENV=test' do
36
- let(:env) { { 'APPMAP' => 'true', 'RAILS_ENV' => 'test' } }
37
- it 'is disabled' do
38
- expect(command_output).to eq('nil')
39
- end
35
+ expect(command_output).to eq('false')
40
36
  end
41
37
  end
42
38
  end
data/spec/util_spec.rb CHANGED
@@ -4,8 +4,8 @@ require 'spec_helper'
4
4
  require 'appmap/util'
5
5
 
6
6
  describe AppMap::Util, docker: false do
7
- let(:subject) { AppMap::Util.method(:scenario_filename) }
8
7
  describe 'scenario_filename' do
8
+ let(:subject) { AppMap::Util.method(:scenario_filename) }
9
9
  it 'leaves short names alone' do
10
10
  expect(subject.call('foobar')).to eq('foobar.appmap.json')
11
11
  end
@@ -18,4 +18,21 @@ describe AppMap::Util, docker: false do
18
18
  expect(subject.call(fname, max_length: 50)).to eq('abcdefghijklmno-RAd_SFbH1sUZ_OXfwPsfzw.appmap.json')
19
19
  end
20
20
  end
21
+ describe 'swaggerize path' do
22
+ it 'replaces rails-style parameters' do
23
+ expect(AppMap::Util.swaggerize_path('/org/:org_id(.:format)')).to eq('/org/{org_id}')
24
+ end
25
+
26
+ it 'strips the format specifier' do
27
+ expect(AppMap::Util.swaggerize_path('/org(.:format)')).to eq('/org')
28
+ end
29
+
30
+ it 'ignores malformed parameter specs' do
31
+ expect(AppMap::Util.swaggerize_path('/org/o:rg_id')).to eq('/org/o:rg_id')
32
+ end
33
+
34
+ it 'ignores already swaggerized paths' do
35
+ expect(AppMap::Util.swaggerize_path('/org/{org_id}')).to eq('/org/{org_id}')
36
+ end
37
+ end
21
38
  end
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'test_helper'
5
+ require 'English'
6
+
7
+ class BundleVendorTest < Minitest::Test
8
+ def perform_bundle_vendor_app(test_name)
9
+ Bundler.with_clean_env do
10
+ Dir.chdir 'test/fixtures/bundle_vendor_app' do
11
+ FileUtils.rm_rf 'tmp'
12
+ FileUtils.mkdir_p 'tmp'
13
+ system 'bundle config --local local.appmap ../../..'
14
+ system 'bundle'
15
+ system(%(bundle exec ruby -Ilib -Itest cli.rb add foobar))
16
+ system({ 'APPMAP' => 'true' }, %(bundle exec ruby -Ilib -Itest cli.rb list))
17
+
18
+ yield
19
+ end
20
+ end
21
+ end
22
+
23
+ def test_record_gem
24
+ perform_bundle_vendor_app 'parser' do
25
+ appmap_file = 'tmp/bundle_vendor_app.appmap.json'
26
+ appmap = JSON.parse(File.read(appmap_file))
27
+ assert appmap['classMap'].find { |co| co['name'] == 'gli' }
28
+ assert appmap['events'].find do |e|
29
+ e['event'] == 'call' &&
30
+ e['defined_class'] = 'Hacer::Todolist' &&
31
+ e['method_id'] == 'list'
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'gli'
4
+ gem 'hacer'
5
+
6
+ appmap_gem_opts = {}
7
+ appmap_gem_opts[:path] = '../../..' if File.exist?('../../../appmap.gemspec')
8
+ gem 'appmap', appmap_gem_opts
@@ -0,0 +1,4 @@
1
+ name: bundle_vendor_app
2
+ packages:
3
+ - gem: gli
4
+ - gem: hacer
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env ruby
2
+ require 'appmap'
3
+ require 'gli'
4
+ require 'hacer'
5
+
6
+ class App
7
+ extend GLI::App
8
+
9
+ program_desc 'A simple todo list'
10
+
11
+ flag [:t,:tasklist], :default_value => File.join(ENV['HOME'],'.todolist')
12
+
13
+ pre do |global_options,command,options,args|
14
+ $todo_list = Hacer::Todolist.new(global_options[:tasklist])
15
+ end
16
+
17
+ command :add do |c|
18
+ c.action do |global_options,options,args|
19
+ $todo_list.create(args)
20
+ end
21
+ end
22
+
23
+ command :list do |c|
24
+ c.action do
25
+ $todo_list.list.each do |todo|
26
+ printf("%5d - %s\n",todo.todo_id,todo.text)
27
+ end
28
+ end
29
+ end
30
+
31
+ command :done do |c|
32
+ c.action do |global_options,options,args|
33
+ id = args.shift.to_i
34
+ $todo_list.list.each do |todo|
35
+ $todo_list.complete(todo) if todo.todo_id == id
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ exit_status = nil
42
+ invoke = -> { exit_status = App.run(ARGV) }
43
+ do_appmap = -> { ENV['APPMAP'] == 'true' }
44
+
45
+ if do_appmap.()
46
+ appmap = AppMap.record do
47
+ invoke.()
48
+ end
49
+ File.write('tmp/bundle_vendor_app.appmap.json', JSON.pretty_generate(appmap))
50
+ else
51
+ invoke.()
52
+ end
53
+ exit exit_status
54
+
data/test/gem_test.rb CHANGED
@@ -4,7 +4,7 @@
4
4
  require 'test_helper'
5
5
  require 'English'
6
6
 
7
- class MinitestTest < Minitest::Test
7
+ class GemTest < Minitest::Test
8
8
  def perform_gem_test(test_name)
9
9
  Bundler.with_clean_env do
10
10
  Dir.chdir 'test/fixtures/gem_test' do
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.46.0
4
+ version: 0.48.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kevin Gilpin
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-05-12 00:00:00.000000000 Z
11
+ date: 2021-05-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -557,9 +557,13 @@ files:
557
557
  - spec/remote_recording_spec.rb
558
558
  - spec/spec_helper.rb
559
559
  - spec/util_spec.rb
560
+ - test/bundle_vendor_test.rb
560
561
  - test/cucumber_test.rb
561
562
  - test/expectations/openssl_test_key_sign1.json
562
563
  - test/expectations/openssl_test_key_sign2.json
564
+ - test/fixtures/bundle_vendor_app/Gemfile
565
+ - test/fixtures/bundle_vendor_app/appmap.yml
566
+ - test/fixtures/bundle_vendor_app/cli.rb
563
567
  - test/fixtures/cli_record_test/appmap.yml
564
568
  - test/fixtures/cli_record_test/lib/cli_record_test/main.rb
565
569
  - test/fixtures/cucumber4_recorder/Gemfile