appmap 0.45.1 → 0.48.1
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/.travis.yml +10 -0
- data/CHANGELOG.md +37 -0
- data/README.md +10 -27
- data/lib/appmap.rb +1 -2
- data/lib/appmap/class_map.rb +7 -15
- data/lib/appmap/config.rb +96 -34
- data/lib/appmap/event.rb +30 -29
- data/lib/appmap/handler/function.rb +1 -1
- data/lib/appmap/handler/rails/request_handler.rb +124 -0
- data/lib/appmap/handler/rails/sql_handler.rb +152 -0
- data/lib/appmap/handler/rails/template.rb +155 -0
- data/lib/appmap/hook.rb +3 -1
- data/lib/appmap/hook/method.rb +1 -1
- data/lib/appmap/minitest.rb +1 -1
- data/lib/appmap/railtie.rb +6 -28
- data/lib/appmap/rspec.rb +1 -1
- data/lib/appmap/trace.rb +46 -6
- data/lib/appmap/util.rb +13 -1
- data/lib/appmap/version.rb +2 -2
- data/package-lock.json +3 -3
- data/spec/abstract_controller_base_spec.rb +67 -8
- data/spec/class_map_spec.rb +3 -3
- 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 +37 -56
- 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 +9 -4
- data/lib/appmap/rails/request_handler.rb +0 -122
- data/lib/appmap/rails/sql_handler.rb +0 -150
data/lib/appmap/hook/method.rb
CHANGED
@@ -95,7 +95,7 @@ module AppMap
|
|
95
95
|
def after_hook(_receiver, call_event, start_time, return_value, exception)
|
96
96
|
elapsed = TIME_NOW.call - start_time
|
97
97
|
return_event = hook_package.handler_class.handle_return(call_event.id, elapsed, return_value, exception)
|
98
|
-
AppMap.tracing.record_event return_event
|
98
|
+
AppMap.tracing.record_event(return_event) if return_event
|
99
99
|
nil
|
100
100
|
end
|
101
101
|
|
data/lib/appmap/minitest.rb
CHANGED
data/lib/appmap/railtie.rb
CHANGED
@@ -3,37 +3,15 @@
|
|
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
|
11
|
-
require 'appmap/rails/sql_handler'
|
12
|
-
require 'appmap/rails/request_handler'
|
13
|
-
ActiveSupport::Notifications.subscribe 'sql.sequel', AppMap::Rails::SQLHandler.new
|
14
|
-
ActiveSupport::Notifications.subscribe 'sql.active_record', AppMap::Rails::SQLHandler.new
|
15
|
-
|
16
|
-
AppMap::Rails::RequestHandler::HookMethod.new.activate
|
17
|
-
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
|
9
|
+
require 'appmap/handler/rails/sql_handler'
|
10
|
+
require 'appmap/handler/rails/request_handler'
|
11
|
+
ActiveSupport::Notifications.subscribe 'sql.sequel', AppMap::Handler::Rails::SQLHandler.new
|
12
|
+
ActiveSupport::Notifications.subscribe 'sql.active_record', AppMap::Handler::Rails::SQLHandler.new
|
25
13
|
|
26
|
-
|
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
|
14
|
+
AppMap::Handler::Rails::RequestHandler::HookMethod.new.activate
|
37
15
|
end
|
38
16
|
end
|
39
|
-
end
|
17
|
+
end if ENV['APPMAP'] == 'true'
|
data/lib/appmap/rspec.rb
CHANGED
data/lib/appmap/trace.rb
CHANGED
@@ -2,14 +2,36 @@
|
|
2
2
|
|
3
3
|
module AppMap
|
4
4
|
module Trace
|
5
|
-
class
|
6
|
-
attr_reader :
|
5
|
+
class RubyMethod
|
6
|
+
attr_reader :class_name, :static
|
7
7
|
|
8
|
-
def initialize(package,
|
8
|
+
def initialize(package, class_name, method, static)
|
9
9
|
@package = package
|
10
|
-
@
|
10
|
+
@class_name = class_name
|
11
|
+
@method = method
|
11
12
|
@static = static
|
12
|
-
|
13
|
+
end
|
14
|
+
|
15
|
+
def source_location
|
16
|
+
@method.source_location
|
17
|
+
end
|
18
|
+
|
19
|
+
def comment
|
20
|
+
@method.comment
|
21
|
+
rescue MethodSource::SourceNotFoundError
|
22
|
+
nil
|
23
|
+
end
|
24
|
+
|
25
|
+
def package
|
26
|
+
@package.name
|
27
|
+
end
|
28
|
+
|
29
|
+
def name
|
30
|
+
@method.name
|
31
|
+
end
|
32
|
+
|
33
|
+
def labels
|
34
|
+
@package.labels
|
13
35
|
end
|
14
36
|
end
|
15
37
|
|
@@ -43,6 +65,12 @@ module AppMap
|
|
43
65
|
end
|
44
66
|
end
|
45
67
|
|
68
|
+
def record_method(method)
|
69
|
+
@tracers.each do |tracer|
|
70
|
+
tracer.record_method(method)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
46
74
|
def delete(tracer)
|
47
75
|
return unless @tracers.member?(tracer)
|
48
76
|
|
@@ -83,10 +111,22 @@ module AppMap
|
|
83
111
|
@last_package_for_thread[Thread.current.object_id] = package if package
|
84
112
|
@events << event
|
85
113
|
static = event.static if event.respond_to?(:static)
|
86
|
-
@methods << Trace::
|
114
|
+
@methods << Trace::RubyMethod.new(package, defined_class, method, static) \
|
87
115
|
if package && defined_class && method && (event.event == :call)
|
88
116
|
end
|
89
117
|
|
118
|
+
# +method+ should be duck-typed to respond to the following:
|
119
|
+
# * package
|
120
|
+
# * defined_class
|
121
|
+
# * name
|
122
|
+
# * static
|
123
|
+
# * comment
|
124
|
+
# * labels
|
125
|
+
# * source_location
|
126
|
+
def record_method(method)
|
127
|
+
@methods << method
|
128
|
+
end
|
129
|
+
|
90
130
|
# Gets the last package which was observed on the current thread.
|
91
131
|
def last_package_for_current_thread
|
92
132
|
@last_package_for_thread[Thread.current.object_id]
|
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
data/package-lock.json
CHANGED
@@ -551,9 +551,9 @@
|
|
551
551
|
}
|
552
552
|
},
|
553
553
|
"lodash": {
|
554
|
-
"version": "4.17.
|
555
|
-
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.
|
556
|
-
"integrity": "sha512-
|
554
|
+
"version": "4.17.21",
|
555
|
+
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
556
|
+
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
557
557
|
},
|
558
558
|
"longest": {
|
559
559
|
"version": "1.0.1",
|
@@ -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(
|
@@ -144,7 +144,49 @@ describe 'Rails' do
|
|
144
144
|
end
|
145
145
|
|
146
146
|
describe 'a UI route' do
|
147
|
-
describe 'rendering a page' do
|
147
|
+
describe 'rendering a page using a template file' do
|
148
|
+
let(:appmap_json_file) do
|
149
|
+
'UsersController_GET_users_lists_the_users.appmap.json'
|
150
|
+
end
|
151
|
+
|
152
|
+
it 'records the template file' do
|
153
|
+
expect(events).to include hash_including(
|
154
|
+
'event' => 'call',
|
155
|
+
'defined_class' => 'app_views_users_index_html_haml',
|
156
|
+
'method_id' => 'render',
|
157
|
+
'path' => 'app/views/users/index.html.haml'
|
158
|
+
)
|
159
|
+
|
160
|
+
expect(appmap['classMap']).to include hash_including(
|
161
|
+
'name' => 'app/views',
|
162
|
+
'children' => include(hash_including(
|
163
|
+
'name' => 'app_views_users_index_html_haml',
|
164
|
+
'children' => include(hash_including(
|
165
|
+
'name' => 'render',
|
166
|
+
'type' => 'function',
|
167
|
+
'location' => 'app/views/users/index.html.haml',
|
168
|
+
'static' => true,
|
169
|
+
'labels' => [ 'mvc.template' ]
|
170
|
+
))
|
171
|
+
))
|
172
|
+
)
|
173
|
+
expect(appmap['classMap']).to include hash_including(
|
174
|
+
'name' => 'app/views',
|
175
|
+
'children' => include(hash_including(
|
176
|
+
'name' => 'app_views_layouts_application_html_haml',
|
177
|
+
'children' => include(hash_including(
|
178
|
+
'name' => 'render',
|
179
|
+
'type' => 'function',
|
180
|
+
'location' => 'app/views/layouts/application.html.haml',
|
181
|
+
'static' => true,
|
182
|
+
'labels' => [ 'mvc.template' ]
|
183
|
+
))
|
184
|
+
))
|
185
|
+
)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
describe 'rendering a page using a text template' do
|
148
190
|
let(:appmap_json_file) do
|
149
191
|
'UsersController_GET_users_login_shows_the_user.appmap.json'
|
150
192
|
end
|
@@ -155,7 +197,7 @@ describe 'Rails' do
|
|
155
197
|
'http_server_request' => {
|
156
198
|
'request_method' => 'GET',
|
157
199
|
'path_info' => '/users/alice',
|
158
|
-
'normalized_path_info' => '/users
|
200
|
+
'normalized_path_info' => '/users/{id}',
|
159
201
|
'headers' => {
|
160
202
|
'Host' => 'test.host',
|
161
203
|
'User-Agent' => 'Rails Testing'
|
@@ -165,15 +207,32 @@ describe 'Rails' do
|
|
165
207
|
)
|
166
208
|
end
|
167
209
|
|
210
|
+
it 'ignores the text template' do
|
211
|
+
expect(events).to_not include hash_including(
|
212
|
+
'event' => 'call',
|
213
|
+
'method_id' => 'render',
|
214
|
+
'render_template' => anything
|
215
|
+
)
|
216
|
+
|
217
|
+
expect(appmap['classMap']).to_not include hash_including(
|
218
|
+
'name' => 'views',
|
219
|
+
'children' => include(hash_including(
|
220
|
+
'name' => 'ViewTemplate',
|
221
|
+
'children' => include(hash_including(
|
222
|
+
'name' => 'render',
|
223
|
+
'type' => 'function',
|
224
|
+
'location' => 'text template'
|
225
|
+
))
|
226
|
+
))
|
227
|
+
)
|
228
|
+
end
|
229
|
+
|
168
230
|
it 'records and labels view rendering' do
|
169
231
|
expect(events).to include hash_including(
|
170
232
|
'event' => 'call',
|
171
233
|
'thread_id' => Numeric,
|
172
|
-
'defined_class' => '
|
173
|
-
'method_id' => 'render'
|
174
|
-
'path' => String,
|
175
|
-
'lineno' => Integer,
|
176
|
-
'static' => false
|
234
|
+
'defined_class' => 'inline_template',
|
235
|
+
'method_id' => 'render'
|
177
236
|
)
|
178
237
|
|
179
238
|
expect(appmap['classMap']).to include hash_including(
|
data/spec/class_map_spec.rb
CHANGED
@@ -5,7 +5,7 @@ require 'spec_helper'
|
|
5
5
|
describe 'AppMap::ClassMap' do
|
6
6
|
describe '.build_from_methods' do
|
7
7
|
it 'includes method comment' do
|
8
|
-
map = AppMap.class_map([
|
8
|
+
map = AppMap.class_map([ruby_method((method :test_method))])
|
9
9
|
function = dig_map(map, 5)[0]
|
10
10
|
expect(function).to include(:comment)
|
11
11
|
end
|
@@ -15,8 +15,8 @@ describe 'AppMap::ClassMap' do
|
|
15
15
|
'test method body'
|
16
16
|
end
|
17
17
|
|
18
|
-
def
|
19
|
-
AppMap::Trace::
|
18
|
+
def ruby_method(method)
|
19
|
+
AppMap::Trace::RubyMethod.new AppMap::Config::Package.new, method.receiver.class.name, method, false
|
20
20
|
end
|
21
21
|
|
22
22
|
def dig_map(map, depth)
|
@@ -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
|
@@ -250,8 +199,8 @@ describe 'AppMap class Hooking', docker: false do
|
|
250
199
|
_, tracer = invoke_test_file 'spec/fixtures/hook/instance_method.rb' do
|
251
200
|
InstanceMethod.new.say_default
|
252
201
|
end
|
253
|
-
expect(tracer.event_methods.to_a.map(&:
|
254
|
-
expect(tracer.event_methods.to_a.map(&:
|
202
|
+
expect(tracer.event_methods.to_a.map(&:class_name)).to eq([ 'InstanceMethod' ])
|
203
|
+
expect(tracer.event_methods.to_a.map(&:name)).to eq([ InstanceMethod.public_instance_method(:say_default).name ])
|
255
204
|
end
|
256
205
|
|
257
206
|
it 'builds a class map of invoked methods' 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
|