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 +4 -4
- data/.dockerignore +0 -1
- data/CHANGELOG.md +37 -0
- data/README.md +2 -18
- data/lib/appmap.rb +1 -1
- data/lib/appmap/config.rb +93 -38
- data/lib/appmap/event.rb +1 -1
- data/lib/appmap/handler/rails/request_handler.rb +1 -1
- data/lib/appmap/handler/rails/template.rb +10 -4
- data/lib/appmap/hook.rb +18 -31
- data/lib/appmap/minitest.rb +1 -1
- data/lib/appmap/railtie.rb +1 -23
- data/lib/appmap/rspec.rb +1 -1
- data/lib/appmap/util.rb +13 -1
- data/lib/appmap/version.rb +2 -2
- data/spec/abstract_controller_base_spec.rb +2 -2
- data/spec/fixtures/hook/exception_method.rb +6 -0
- data/spec/fixtures/rails5_users_app/config/application.rb +0 -2
- data/spec/fixtures/rails6_users_app/config/application.rb +0 -2
- data/spec/hook_spec.rb +35 -54
- data/spec/railtie_spec.rb +7 -11
- data/spec/util_spec.rb +18 -1
- data/test/bundle_vendor_test.rb +35 -0
- data/test/fixtures/bundle_vendor_app/Gemfile +8 -0
- data/test/fixtures/bundle_vendor_app/appmap.yml +4 -0
- data/test/fixtures/bundle_vendor_app/cli.rb +54 -0
- data/test/gem_test.rb +1 -1
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 455ac48f4acf31694cb571912703f62b95687b8b833be0ced7b8ab5008c3960b
|
4
|
+
data.tar.gz: 272c68f804affe1a461ec504475b95d1c7968e506644442437a45dd28ff7d664
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3bfbb562490380393c669aaeb2813389a52bf18f8e769f69a496ffe9fc26a493d9f55cfa161926d26deca370b1e18f99896eb0e269e59cbd6647801b7238c565
|
7
|
+
data.tar.gz: ba4b69852fc4be1850b5fd20c713e21799848bd32a251cdcc3bae87e4bf25605621dccc381e32018397ff52bbc442859065d8f1d8313211c135c1a9fb3ecb6d7
|
data/.dockerignore
CHANGED
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 `
|
314
|
+
3. Start your Rails application server, with `APPMAP=true`. For example:
|
331
315
|
|
332
316
|
```sh-session
|
333
|
-
$
|
317
|
+
$ APPMAP=true bundle exec rails server
|
334
318
|
```
|
335
319
|
|
336
320
|
4. Start the recording
|
data/lib/appmap.rb
CHANGED
data/lib/appmap/config.rb
CHANGED
@@ -85,18 +85,7 @@ module AppMap
|
|
85
85
|
end
|
86
86
|
end
|
87
87
|
|
88
|
-
|
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
|
-
|
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
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
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
|
-
|
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, :
|
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
|
-
@
|
228
|
+
@builtin_hooks = BUILTIN_HOOKS
|
177
229
|
@functions = functions
|
178
|
-
|
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
|
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
|
@@ -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
|
-
|
109
|
+
path_obj = Array(return_value).first
|
110
|
+
|
111
|
+
warn "Resolver return: #{path_obj}" if LOG
|
112
112
|
|
113
|
-
if
|
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
|
-
@
|
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.
|
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
|
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
|
data/lib/appmap/minitest.rb
CHANGED
data/lib/appmap/railtie.rb
CHANGED
@@ -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
|
17
|
+
end if ENV['APPMAP'] == 'true'
|
data/lib/appmap/rspec.rb
CHANGED
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'
|
data/lib/appmap/version.rb
CHANGED
@@ -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
|
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
|
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 "
|
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 '
|
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
|
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
|
-
|
27
|
-
|
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
|
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,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
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: appmap
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.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-
|
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
|