appmap 0.46.0 → 0.48.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|