appmap 0.45.1 → 0.48.1
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/.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
|