appmap 0.38.1 → 0.41.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/.rubocop.yml +5 -1
- data/.travis.yml +2 -23
- data/CHANGELOG.md +26 -1
- data/CONTRIBUTING.md +22 -0
- data/README.md +119 -53
- data/Rakefile +3 -3
- data/lib/appmap/class_map.rb +25 -8
- data/lib/appmap/config.rb +41 -21
- data/lib/appmap/event.rb +14 -4
- data/lib/appmap/hook.rb +18 -3
- data/lib/appmap/rails/request_handler.rb +17 -3
- data/lib/appmap/railtie.rb +1 -5
- data/lib/appmap/version.rb +2 -2
- data/spec/abstract_controller_base_spec.rb +116 -86
- data/spec/config_spec.rb +1 -0
- data/spec/fixtures/hook/exclude.rb +15 -0
- data/spec/fixtures/hook/labels.rb +6 -0
- data/spec/fixtures/rails5_users_app/Gemfile +2 -3
- data/spec/fixtures/rails5_users_app/app/controllers/users_controller.rb +8 -0
- data/spec/fixtures/rails5_users_app/appmap.yml +4 -1
- data/spec/fixtures/rails5_users_app/config/application.rb +2 -0
- data/spec/fixtures/rails5_users_app/config/routes.rb +1 -1
- data/spec/fixtures/rails5_users_app/spec/controllers/users_controller_spec.rb +11 -0
- data/spec/fixtures/rails6_users_app/Gemfile +2 -3
- data/spec/fixtures/rails6_users_app/app/controllers/users_controller.rb +8 -0
- data/spec/fixtures/rails6_users_app/appmap.yml +4 -1
- data/spec/fixtures/rails6_users_app/config/application.rb +2 -0
- data/spec/fixtures/rails6_users_app/config/routes.rb +1 -1
- data/spec/fixtures/rails6_users_app/spec/controllers/users_controller_spec.rb +11 -0
- data/spec/hook_spec.rb +41 -41
- data/spec/rails_spec_helper.rb +2 -2
- data/spec/record_sql_rails_pg_spec.rb +1 -1
- data/spec/rspec_feature_metadata_spec.rb +1 -1
- data/spec/spec_helper.rb +1 -0
- data/test/fixtures/gem_test/appmap.yml +1 -1
- data/test/fixtures/gem_test/test/parser_test.rb +12 -0
- data/test/gem_test.rb +4 -4
- metadata +6 -69
- data/spec/abstract_controller4_base_spec.rb +0 -66
- data/spec/fixtures/rails4_users_app/.gitignore +0 -13
- data/spec/fixtures/rails4_users_app/.rbenv-gemsets +0 -2
- data/spec/fixtures/rails4_users_app/.ruby-version +0 -1
- data/spec/fixtures/rails4_users_app/Dockerfile +0 -30
- data/spec/fixtures/rails4_users_app/Dockerfile.pg +0 -3
- data/spec/fixtures/rails4_users_app/Gemfile +0 -77
- data/spec/fixtures/rails4_users_app/README.rdoc +0 -28
- data/spec/fixtures/rails4_users_app/Rakefile +0 -6
- data/spec/fixtures/rails4_users_app/app/assets/images/.keep +0 -0
- data/spec/fixtures/rails4_users_app/app/assets/javascripts/application.js +0 -16
- data/spec/fixtures/rails4_users_app/app/assets/stylesheets/application.css +0 -15
- data/spec/fixtures/rails4_users_app/app/controllers/api/users_controller.rb +0 -27
- data/spec/fixtures/rails4_users_app/app/controllers/application_controller.rb +0 -5
- data/spec/fixtures/rails4_users_app/app/controllers/concerns/.keep +0 -0
- data/spec/fixtures/rails4_users_app/app/controllers/health_controller.rb +0 -5
- data/spec/fixtures/rails4_users_app/app/controllers/users_controller.rb +0 -5
- data/spec/fixtures/rails4_users_app/app/helpers/application_helper.rb +0 -2
- data/spec/fixtures/rails4_users_app/app/mailers/.keep +0 -0
- data/spec/fixtures/rails4_users_app/app/models/.keep +0 -0
- data/spec/fixtures/rails4_users_app/app/models/concerns/.keep +0 -0
- data/spec/fixtures/rails4_users_app/app/models/user.rb +0 -18
- data/spec/fixtures/rails4_users_app/app/views/layouts/application.html.haml +0 -7
- data/spec/fixtures/rails4_users_app/app/views/users/index.html.haml +0 -7
- data/spec/fixtures/rails4_users_app/appmap.yml +0 -3
- data/spec/fixtures/rails4_users_app/bin/rails +0 -9
- data/spec/fixtures/rails4_users_app/bin/setup +0 -29
- data/spec/fixtures/rails4_users_app/bin/spring +0 -17
- data/spec/fixtures/rails4_users_app/config.ru +0 -4
- data/spec/fixtures/rails4_users_app/config/application.rb +0 -26
- data/spec/fixtures/rails4_users_app/config/boot.rb +0 -3
- data/spec/fixtures/rails4_users_app/config/database.yml +0 -18
- data/spec/fixtures/rails4_users_app/config/environment.rb +0 -5
- data/spec/fixtures/rails4_users_app/config/environments/development.rb +0 -41
- data/spec/fixtures/rails4_users_app/config/environments/production.rb +0 -79
- data/spec/fixtures/rails4_users_app/config/environments/test.rb +0 -42
- data/spec/fixtures/rails4_users_app/config/initializers/assets.rb +0 -11
- data/spec/fixtures/rails4_users_app/config/initializers/backtrace_silencers.rb +0 -7
- data/spec/fixtures/rails4_users_app/config/initializers/cookies_serializer.rb +0 -3
- data/spec/fixtures/rails4_users_app/config/initializers/filter_parameter_logging.rb +0 -4
- data/spec/fixtures/rails4_users_app/config/initializers/inflections.rb +0 -16
- data/spec/fixtures/rails4_users_app/config/initializers/mime_types.rb +0 -4
- data/spec/fixtures/rails4_users_app/config/initializers/session_store.rb +0 -3
- data/spec/fixtures/rails4_users_app/config/initializers/to_time_preserves_timezone.rb +0 -10
- data/spec/fixtures/rails4_users_app/config/initializers/wrap_parameters.rb +0 -14
- data/spec/fixtures/rails4_users_app/config/locales/en.yml +0 -23
- data/spec/fixtures/rails4_users_app/config/routes.rb +0 -12
- data/spec/fixtures/rails4_users_app/config/secrets.yml +0 -22
- data/spec/fixtures/rails4_users_app/create_app +0 -23
- data/spec/fixtures/rails4_users_app/db/migrate/20191127112304_create_users.rb +0 -10
- data/spec/fixtures/rails4_users_app/db/schema.rb +0 -26
- data/spec/fixtures/rails4_users_app/db/seeds.rb +0 -7
- data/spec/fixtures/rails4_users_app/docker-compose.yml +0 -26
- data/spec/fixtures/rails4_users_app/lib/assets/.keep +0 -0
- data/spec/fixtures/rails4_users_app/lib/tasks/.keep +0 -0
- data/spec/fixtures/rails4_users_app/log/.keep +0 -0
- data/spec/fixtures/rails4_users_app/public/404.html +0 -67
- data/spec/fixtures/rails4_users_app/public/422.html +0 -67
- data/spec/fixtures/rails4_users_app/public/500.html +0 -66
- data/spec/fixtures/rails4_users_app/public/favicon.ico +0 -0
- data/spec/fixtures/rails4_users_app/public/robots.txt +0 -5
- data/spec/fixtures/rails4_users_app/spec/controllers/users_controller_api_spec.rb +0 -49
- data/spec/fixtures/rails4_users_app/spec/rails_helper.rb +0 -95
- data/spec/fixtures/rails4_users_app/spec/spec_helper.rb +0 -96
- data/spec/fixtures/rails4_users_app/test/fixtures/users.yml +0 -9
- data/spec/record_sql_rails4_pg_spec.rb +0 -75
- data/test/fixtures/gem_test/test/to_param_test.rb +0 -14
data/lib/appmap/config.rb
CHANGED
|
@@ -16,6 +16,10 @@ module AppMap
|
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
def build_from_gem(gem, shallow: true, package_name: nil, exclude: [], labels: [])
|
|
19
|
+
if %w[method_source activesupport].member?(gem)
|
|
20
|
+
warn "WARNING: #{gem} cannot be AppMapped because it is a dependency of the appmap gem"
|
|
21
|
+
return
|
|
22
|
+
end
|
|
19
23
|
gem_paths(gem).map do |gem_path|
|
|
20
24
|
Package.new(gem_path, gem, package_name, exclude, labels, shallow)
|
|
21
25
|
end
|
|
@@ -57,32 +61,32 @@ module AppMap
|
|
|
57
61
|
# Methods that should always be hooked, with their containing
|
|
58
62
|
# package and labels that should be applied to them.
|
|
59
63
|
HOOKED_METHODS = {
|
|
60
|
-
'ActiveSupport::SecurityUtils' => Hook.new(:secure_compare, Package.build_from_path('active_support', package_name: 'active_support', labels: %w[
|
|
61
|
-
'ActionView::Renderer' => Hook.new(:render, Package.build_from_path('action_view', package_name: 'action_view', labels: %w[view]))
|
|
64
|
+
'ActiveSupport::SecurityUtils' => Hook.new(:secure_compare, Package.build_from_path('active_support', package_name: 'active_support', labels: %w[provider.secure_compare])),
|
|
65
|
+
'ActionView::Renderer' => Hook.new(:render, Package.build_from_path('action_view', package_name: 'action_view', labels: %w[mvc.view]))
|
|
62
66
|
}.freeze
|
|
63
67
|
|
|
64
68
|
BUILTIN_METHODS = {
|
|
65
69
|
'OpenSSL::PKey::PKey' => Hook.new(:sign, OPENSSL_PACKAGES),
|
|
66
|
-
'Digest::Instance' => Hook.new(:digest, OPENSSL_PACKAGES),
|
|
67
70
|
'OpenSSL::X509::Request' => Hook.new(%i[sign verify], OPENSSL_PACKAGES),
|
|
68
71
|
'OpenSSL::PKCS5' => Hook.new(%i[pbkdf2_hmac_sha1 pbkdf2_hmac], OPENSSL_PACKAGES),
|
|
69
72
|
'OpenSSL::Cipher' => Hook.new(%i[encrypt decrypt final], OPENSSL_PACKAGES),
|
|
70
73
|
'OpenSSL::X509::Certificate' => Hook.new(:sign, OPENSSL_PACKAGES),
|
|
71
|
-
'Net::HTTP' => Hook.new(:request, Package.build_from_path('net/http', package_name: 'net/http', labels: %w[http io])),
|
|
72
|
-
'Net::SMTP' => Hook.new(:send, Package.build_from_path('net/smtp', package_name: 'net/smtp', labels: %w[smtp email io])),
|
|
73
|
-
'Net::POP3' => Hook.new(:mails, Package.build_from_path('net/pop3', package_name: 'net/pop', labels: %w[pop
|
|
74
|
-
'Net::IMAP' => Hook.new(:send_command, Package.build_from_path('net/imap', package_name: 'net/imap', labels: %w[imap email io])),
|
|
75
|
-
'Marshal' => Hook.new(%i[dump load], Package.build_from_path('marshal', labels: %w[serialization marshal])),
|
|
76
|
-
'Psych' => Hook.new(%i[dump dump_stream load load_stream parse parse_stream], Package.build_from_path('yaml', package_name: 'psych', labels: %w[serialization
|
|
77
|
-
'JSON::Ext::Parser' => Hook.new(:parse, Package.build_from_path('json', package_name: 'json', labels: %w[serialization
|
|
78
|
-
'JSON::Ext::Generator::State' => Hook.new(:generate, Package.build_from_path('json', package_name: 'json', labels: %w[serialization
|
|
74
|
+
'Net::HTTP' => Hook.new(:request, Package.build_from_path('net/http', package_name: 'net/http', labels: %w[protocol.http io])),
|
|
75
|
+
'Net::SMTP' => Hook.new(:send, Package.build_from_path('net/smtp', package_name: 'net/smtp', labels: %w[protocol.smtp protocol.email io])),
|
|
76
|
+
'Net::POP3' => Hook.new(:mails, Package.build_from_path('net/pop3', package_name: 'net/pop', labels: %w[protocol.pop protocol.email io])),
|
|
77
|
+
'Net::IMAP' => Hook.new(:send_command, Package.build_from_path('net/imap', package_name: 'net/imap', labels: %w[protocol.imap protocol.email io])),
|
|
78
|
+
'Marshal' => Hook.new(%i[dump load], Package.build_from_path('marshal', labels: %w[format.marshal provider.serialization marshal])),
|
|
79
|
+
'Psych' => Hook.new(%i[dump dump_stream load load_stream parse parse_stream], Package.build_from_path('yaml', package_name: 'psych', labels: %w[format.yaml provider.serialization])),
|
|
80
|
+
'JSON::Ext::Parser' => Hook.new(:parse, Package.build_from_path('json', package_name: 'json', labels: %w[format.json provider.serialization])),
|
|
81
|
+
'JSON::Ext::Generator::State' => Hook.new(:generate, Package.build_from_path('json', package_name: 'json', labels: %w[format.json provider.serialization])),
|
|
79
82
|
}.freeze
|
|
80
83
|
|
|
81
|
-
attr_reader :name, :packages
|
|
84
|
+
attr_reader :name, :packages, :exclude
|
|
82
85
|
|
|
83
|
-
def initialize(name, packages = [])
|
|
86
|
+
def initialize(name, packages = [], exclude = [])
|
|
84
87
|
@name = name
|
|
85
88
|
@packages = packages
|
|
89
|
+
@exclude = exclude
|
|
86
90
|
end
|
|
87
91
|
|
|
88
92
|
class << self
|
|
@@ -107,42 +111,58 @@ module AppMap
|
|
|
107
111
|
else
|
|
108
112
|
[ Package.build_from_path(path, exclude: package['exclude'] || [], shallow: package['shallow']) ]
|
|
109
113
|
end
|
|
110
|
-
end.flatten
|
|
111
|
-
Config.new config_data['name'], packages
|
|
114
|
+
end.flatten.compact
|
|
115
|
+
Config.new config_data['name'], packages, config_data['exclude'] || []
|
|
112
116
|
end
|
|
113
117
|
end
|
|
114
118
|
|
|
115
119
|
def to_h
|
|
116
120
|
{
|
|
117
121
|
name: name,
|
|
118
|
-
packages: packages.map(&:to_h)
|
|
122
|
+
packages: packages.map(&:to_h),
|
|
123
|
+
exclude: exclude
|
|
119
124
|
}
|
|
120
125
|
end
|
|
121
126
|
|
|
127
|
+
# package_for_method finds the Package, if any, which configures the hook
|
|
128
|
+
# for a method.
|
|
122
129
|
def package_for_method(method)
|
|
130
|
+
package_hooked_by_class(method) || package_hooked_by_source_location(method)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def package_hooked_by_class(method)
|
|
123
134
|
defined_class, _, method_name = ::AppMap::Hook.qualify_method_name(method)
|
|
124
|
-
|
|
125
|
-
|
|
135
|
+
return find_package(defined_class, method_name)
|
|
136
|
+
end
|
|
126
137
|
|
|
138
|
+
def package_hooked_by_source_location(method)
|
|
127
139
|
location = method.source_location
|
|
128
140
|
location_file, = location
|
|
129
141
|
return unless location_file
|
|
130
142
|
|
|
131
143
|
location_file = location_file[Dir.pwd.length + 1..-1] if location_file.index(Dir.pwd) == 0
|
|
132
|
-
packages.find do |pkg|
|
|
144
|
+
packages.select { |pkg| pkg.path }.find do |pkg|
|
|
133
145
|
(location_file.index(pkg.path) == 0) &&
|
|
134
146
|
!pkg.exclude.find { |p| location_file.index(p) }
|
|
135
147
|
end
|
|
136
148
|
end
|
|
137
149
|
|
|
138
|
-
def
|
|
139
|
-
|
|
150
|
+
def never_hook?(method)
|
|
151
|
+
defined_class, separator, method_name = ::AppMap::Hook.qualify_method_name(method)
|
|
152
|
+
return true if exclude.member?(defined_class) || exclude.member?([ defined_class, separator, method_name ].join)
|
|
140
153
|
end
|
|
141
154
|
|
|
155
|
+
# always_hook? indicates a method that should always be hooked.
|
|
142
156
|
def always_hook?(defined_class, method_name)
|
|
143
157
|
!!find_package(defined_class, method_name)
|
|
144
158
|
end
|
|
145
159
|
|
|
160
|
+
# included_by_location? indicates a method whose source location matches a method definition that has been
|
|
161
|
+
# configured for inclusion.
|
|
162
|
+
def included_by_location?(method)
|
|
163
|
+
!!package_for_method(method)
|
|
164
|
+
end
|
|
165
|
+
|
|
146
166
|
def find_package(defined_class, method_name)
|
|
147
167
|
hook = find_hook(defined_class)
|
|
148
168
|
return nil unless hook
|
data/lib/appmap/event.rb
CHANGED
|
@@ -38,6 +38,15 @@ module AppMap
|
|
|
38
38
|
|
|
39
39
|
protected
|
|
40
40
|
|
|
41
|
+
# Heuristic for dynamically defined class whose name can be nil
|
|
42
|
+
def best_class_name(value)
|
|
43
|
+
value_cls = value.class
|
|
44
|
+
while value_cls.name.nil?
|
|
45
|
+
value_cls = value_cls.superclass
|
|
46
|
+
end
|
|
47
|
+
value_cls.name
|
|
48
|
+
end
|
|
49
|
+
|
|
41
50
|
def custom_display_string(value)
|
|
42
51
|
case value
|
|
43
52
|
when File
|
|
@@ -77,6 +86,7 @@ module AppMap
|
|
|
77
86
|
|
|
78
87
|
class << self
|
|
79
88
|
def build_from_invocation(mc = MethodCall.new, defined_class, method, receiver, arguments)
|
|
89
|
+
defined_class ||= 'Class'
|
|
80
90
|
mc.tap do
|
|
81
91
|
static = receiver.is_a?(Module)
|
|
82
92
|
mc.defined_class = defined_class
|
|
@@ -110,14 +120,14 @@ module AppMap
|
|
|
110
120
|
end
|
|
111
121
|
{
|
|
112
122
|
name: param_name,
|
|
113
|
-
class: value
|
|
123
|
+
class: best_class_name(value),
|
|
114
124
|
object_id: value.__id__,
|
|
115
125
|
value: display_string(value),
|
|
116
126
|
kind: param_type
|
|
117
127
|
}
|
|
118
128
|
end
|
|
119
129
|
mc.receiver = {
|
|
120
|
-
class: receiver
|
|
130
|
+
class: best_class_name(receiver),
|
|
121
131
|
object_id: receiver.__id__,
|
|
122
132
|
value: display_string(receiver)
|
|
123
133
|
}
|
|
@@ -172,7 +182,7 @@ module AppMap
|
|
|
172
182
|
mr.tap do |_|
|
|
173
183
|
if return_value
|
|
174
184
|
mr.return_value = {
|
|
175
|
-
class: return_value
|
|
185
|
+
class: best_class_name(return_value),
|
|
176
186
|
value: display_string(return_value),
|
|
177
187
|
object_id: return_value.__id__
|
|
178
188
|
}
|
|
@@ -183,7 +193,7 @@ module AppMap
|
|
|
183
193
|
while next_exception
|
|
184
194
|
exception_backtrace = next_exception.backtrace_locations.try(:[], 0)
|
|
185
195
|
exceptions << {
|
|
186
|
-
class: next_exception
|
|
196
|
+
class: best_class_name(next_exception),
|
|
187
197
|
message: next_exception.message,
|
|
188
198
|
object_id: next_exception.__id__,
|
|
189
199
|
path: exception_backtrace&.path,
|
data/lib/appmap/hook.rb
CHANGED
|
@@ -6,6 +6,9 @@ module AppMap
|
|
|
6
6
|
class Hook
|
|
7
7
|
LOG = (ENV['APPMAP_DEBUG'] == 'true' || ENV['DEBUG'] == 'true')
|
|
8
8
|
|
|
9
|
+
OBJECT_INSTANCE_METHODS = %i[! != !~ <=> == === =~ __id__ __send__ class clone define_singleton_method display dup enum_for eql? equal? extend freeze frozen? hash inspect instance_eval instance_exec instance_of? instance_variable_defined? instance_variable_get instance_variable_set instance_variables is_a? itself kind_of? method methods nil? object_id private_methods protected_methods public_method public_methods public_send remove_instance_variable respond_to? send singleton_class singleton_method singleton_methods taint tainted? tap then to_enum to_s to_h to_a trust untaint untrust untrusted? yield_self].freeze
|
|
10
|
+
OBJECT_STATIC_METHODS = %i[! != !~ < <= <=> == === =~ > >= __id__ __send__ alias_method allocate ancestors attr attr_accessor attr_reader attr_writer autoload autoload? class class_eval class_exec class_variable_defined? class_variable_get class_variable_set class_variables clone const_defined? const_get const_missing const_set constants define_method define_singleton_method deprecate_constant display dup enum_for eql? equal? extend freeze frozen? hash include include? included_modules inspect instance_eval instance_exec instance_method instance_methods instance_of? instance_variable_defined? instance_variable_get instance_variable_set instance_variables is_a? itself kind_of? method method_defined? methods module_eval module_exec name new nil? object_id prepend private_class_method private_constant private_instance_methods private_method_defined? private_methods protected_instance_methods protected_method_defined? protected_methods public_class_method public_constant public_instance_method public_instance_methods public_method public_method_defined? public_methods public_send remove_class_variable remove_instance_variable remove_method respond_to? send singleton_class singleton_class? singleton_method singleton_methods superclass taint tainted? tap then to_enum to_s trust undef_method untaint untrust untrusted? yield_self].freeze
|
|
11
|
+
|
|
9
12
|
@unbound_method_arity = ::UnboundMethod.instance_method(:arity)
|
|
10
13
|
@method_arity = ::Method.instance_method(:arity)
|
|
11
14
|
|
|
@@ -42,12 +45,17 @@ module AppMap
|
|
|
42
45
|
tp = TracePoint.new(:end) do |trace_point|
|
|
43
46
|
cls = trace_point.self
|
|
44
47
|
|
|
45
|
-
instance_methods = cls.public_instance_methods(false)
|
|
46
|
-
class_methods = cls.singleton_class.public_instance_methods(false) - instance_methods
|
|
48
|
+
instance_methods = cls.public_instance_methods(false) - OBJECT_INSTANCE_METHODS
|
|
49
|
+
class_methods = cls.singleton_class.public_instance_methods(false) - instance_methods - OBJECT_STATIC_METHODS
|
|
47
50
|
|
|
48
51
|
hook = lambda do |hook_cls|
|
|
49
52
|
lambda do |method_id|
|
|
50
|
-
method =
|
|
53
|
+
method = begin
|
|
54
|
+
hook_cls.public_instance_method(method_id)
|
|
55
|
+
rescue NameError
|
|
56
|
+
warn "AppMap: Method #{hook_cls} #{method.name} is not accessible" if LOG
|
|
57
|
+
return
|
|
58
|
+
end
|
|
51
59
|
|
|
52
60
|
warn "AppMap: Examining #{hook_cls} #{method.name}" if LOG
|
|
53
61
|
|
|
@@ -55,6 +63,8 @@ module AppMap
|
|
|
55
63
|
# Skip methods that have no instruction sequence, as they are obviously trivial.
|
|
56
64
|
next unless disasm
|
|
57
65
|
|
|
66
|
+
next if config.never_hook?(method)
|
|
67
|
+
|
|
58
68
|
next unless \
|
|
59
69
|
config.always_hook?(hook_cls, method.name) ||
|
|
60
70
|
config.included_by_location?(method)
|
|
@@ -76,6 +86,8 @@ module AppMap
|
|
|
76
86
|
tp.enable(&block)
|
|
77
87
|
end
|
|
78
88
|
|
|
89
|
+
# hook_builtins builds hooks for code that is built in to the Ruby standard library.
|
|
90
|
+
# No TracePoint events are emitted for builtins, so a separate hooking mechanism is needed.
|
|
79
91
|
def hook_builtins
|
|
80
92
|
return unless self.class.lock_builtins
|
|
81
93
|
|
|
@@ -89,6 +101,7 @@ module AppMap
|
|
|
89
101
|
require hook.package.package_name if hook.package.package_name
|
|
90
102
|
Array(hook.method_names).each do |method_name|
|
|
91
103
|
method_name = method_name.to_sym
|
|
104
|
+
|
|
92
105
|
cls = class_from_string.(class_name)
|
|
93
106
|
method = \
|
|
94
107
|
begin
|
|
@@ -97,6 +110,8 @@ module AppMap
|
|
|
97
110
|
cls.method(method_name) rescue nil
|
|
98
111
|
end
|
|
99
112
|
|
|
113
|
+
next if config.never_hook?(method)
|
|
114
|
+
|
|
100
115
|
if method
|
|
101
116
|
Hook::Method.new(hook.package, cls, method).activate
|
|
102
117
|
else
|
|
@@ -7,12 +7,13 @@ module AppMap
|
|
|
7
7
|
module Rails
|
|
8
8
|
module RequestHandler
|
|
9
9
|
class HTTPServerRequest < AppMap::Event::MethodEvent
|
|
10
|
-
attr_accessor :request_method, :path_info, :params
|
|
10
|
+
attr_accessor :normalized_path_info, :request_method, :path_info, :params
|
|
11
11
|
|
|
12
12
|
def initialize(request)
|
|
13
13
|
super AppMap::Event.next_id_counter, :call, Thread.current.object_id
|
|
14
14
|
|
|
15
15
|
@request_method = request.request_method
|
|
16
|
+
@normalized_path_info = normalized_path request
|
|
16
17
|
@path_info = request.path_info.split('?')[0]
|
|
17
18
|
# ActionDispatch::Http::ParameterFilter is deprecated
|
|
18
19
|
parameter_filter_cls = \
|
|
@@ -28,8 +29,9 @@ module AppMap
|
|
|
28
29
|
super.tap do |h|
|
|
29
30
|
h[:http_server_request] = {
|
|
30
31
|
request_method: request_method,
|
|
31
|
-
path_info: path_info
|
|
32
|
-
|
|
32
|
+
path_info: path_info,
|
|
33
|
+
normalized_path_info: normalized_path_info
|
|
34
|
+
}.compact
|
|
33
35
|
|
|
34
36
|
h[:message] = params.keys.map do |key|
|
|
35
37
|
val = params[key]
|
|
@@ -42,6 +44,18 @@ module AppMap
|
|
|
42
44
|
end
|
|
43
45
|
end
|
|
44
46
|
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def normalized_path(request, router = ::Rails.application.routes.router)
|
|
51
|
+
router.recognize request do |route, _|
|
|
52
|
+
app = route.app
|
|
53
|
+
next unless app.matches? request
|
|
54
|
+
return normalized_path request, app.rack_app.routes.router if app.engine?
|
|
55
|
+
|
|
56
|
+
return route.path.spec.to_s
|
|
57
|
+
end
|
|
58
|
+
end
|
|
45
59
|
end
|
|
46
60
|
|
|
47
61
|
class HTTPServerResponse < AppMap::Event::MethodReturnIgnoreValue
|
data/lib/appmap/railtie.rb
CHANGED
|
@@ -5,13 +5,9 @@ module AppMap
|
|
|
5
5
|
class Railtie < ::Rails::Railtie
|
|
6
6
|
config.appmap = ActiveSupport::OrderedOptions.new
|
|
7
7
|
|
|
8
|
-
initializer 'appmap.init' do |_| # params: app
|
|
9
|
-
require 'appmap'
|
|
10
|
-
end
|
|
11
|
-
|
|
12
8
|
# appmap.subscribe subscribes to ActiveSupport Notifications so that they can be recorded as
|
|
13
9
|
# AppMap events.
|
|
14
|
-
initializer 'appmap.subscribe'
|
|
10
|
+
initializer 'appmap.subscribe' do |_| # params: app
|
|
15
11
|
require 'appmap/rails/sql_handler'
|
|
16
12
|
require 'appmap/rails/request_handler'
|
|
17
13
|
ActiveSupport::Notifications.subscribe 'sql.sequel', AppMap::Rails::SQLHandler.new
|
data/lib/appmap/version.rb
CHANGED
|
@@ -1,120 +1,150 @@
|
|
|
1
1
|
require 'rails_spec_helper'
|
|
2
2
|
|
|
3
3
|
describe 'AbstractControllerBase' do
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
%w[5 6].each do |rails_major_version| # rubocop:disable Metrics/BlockLength
|
|
5
|
+
context "in Rails #{rails_major_version}" do
|
|
6
|
+
include_context 'Rails app pg database', "spec/fixtures/rails#{rails_major_version}_users_app"
|
|
6
7
|
def run_spec(spec_name)
|
|
7
|
-
|
|
8
|
+
FileUtils.rm_rf tmpdir
|
|
9
|
+
FileUtils.mkdir_p tmpdir
|
|
10
|
+
cmd = <<~CMD.gsub "\n", ' '
|
|
11
|
+
docker-compose run --rm -e RAILS_ENV=test -e APPMAP=true
|
|
12
|
+
-v #{File.absolute_path tmpdir}:/app/tmp app ./bin/rspec #{spec_name}
|
|
13
|
+
CMD
|
|
8
14
|
run_cmd cmd, chdir: fixture_dir
|
|
9
15
|
end
|
|
10
16
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
FileUtils.mkdir_p tmpdir
|
|
14
|
-
run_spec spec_name
|
|
17
|
+
def tmpdir
|
|
18
|
+
'tmp/spec/AbstractControllerBase'
|
|
15
19
|
end
|
|
16
20
|
|
|
17
|
-
let(:
|
|
21
|
+
let(:appmap) { JSON.parse File.read File.join tmpdir, 'appmap/rspec', appmap_json_file }
|
|
22
|
+
let(:events) { appmap['events'] }
|
|
18
23
|
|
|
19
24
|
describe 'testing with rspec' do
|
|
20
|
-
let(:spec_name) { 'spec/controllers/users_controller_api_spec.rb:8' }
|
|
21
|
-
let(:appmap_json_file) { File.join(tmpdir, 'appmap/rspec/Api_UsersController_POST_api_users_with_required_parameters_creates_a_user.appmap.json') }
|
|
22
|
-
|
|
23
25
|
describe 'creating a user' do
|
|
26
|
+
before(:all) { run_spec 'spec/controllers/users_controller_api_spec.rb:8' }
|
|
27
|
+
let(:appmap_json_file) do
|
|
28
|
+
'Api_UsersController_POST_api_users_with_required_parameters_creates_a_user.appmap.json'
|
|
29
|
+
end
|
|
30
|
+
|
|
24
31
|
it 'inventory file is printed' do
|
|
25
32
|
expect(File).to exist(File.join(tmpdir, 'appmap/rspec/Inventory.appmap.json'))
|
|
26
33
|
end
|
|
27
34
|
|
|
28
35
|
it 'message fields are recorded in the appmap' do
|
|
29
|
-
expect(
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
SERVER_RESPONSE
|
|
36
|
+
expect(events).to include(
|
|
37
|
+
hash_including(
|
|
38
|
+
'http_server_request' => hash_including(
|
|
39
|
+
'request_method' => 'POST',
|
|
40
|
+
'path_info' => '/api/users'
|
|
41
|
+
),
|
|
42
|
+
'message' => include(
|
|
43
|
+
hash_including(
|
|
44
|
+
'name' => 'login',
|
|
45
|
+
'class' => 'String',
|
|
46
|
+
'value' => 'alice',
|
|
47
|
+
'object_id' => Integer
|
|
48
|
+
),
|
|
49
|
+
hash_including(
|
|
50
|
+
'name' => 'password',
|
|
51
|
+
'class' => 'String',
|
|
52
|
+
'value' => '[FILTERED]',
|
|
53
|
+
'object_id' => Integer
|
|
54
|
+
)
|
|
55
|
+
)
|
|
56
|
+
),
|
|
57
|
+
hash_including(
|
|
58
|
+
'http_server_response' => {
|
|
59
|
+
'status' => 201,
|
|
60
|
+
'mime_type' => 'application/json; charset=utf-8'
|
|
61
|
+
}
|
|
62
|
+
)
|
|
63
|
+
)
|
|
58
64
|
end
|
|
59
65
|
|
|
60
66
|
it 'properly captures method parameters in the appmap' do
|
|
61
|
-
expect(
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
receiver:
|
|
79
|
-
CREATE_CALL
|
|
67
|
+
expect(events).to include hash_including(
|
|
68
|
+
'event' => 'call',
|
|
69
|
+
'thread_id' => Integer,
|
|
70
|
+
'defined_class' => 'Api::UsersController',
|
|
71
|
+
'method_id' => 'build_user',
|
|
72
|
+
'path' => 'app/controllers/api/users_controller.rb',
|
|
73
|
+
'lineno' => 23,
|
|
74
|
+
'static' => false,
|
|
75
|
+
'parameters' => include(
|
|
76
|
+
'name' => 'params',
|
|
77
|
+
'class' => 'ActiveSupport::HashWithIndifferentAccess',
|
|
78
|
+
'object_id' => Integer,
|
|
79
|
+
'value' => '{"login"=>"alice"}',
|
|
80
|
+
'kind' => 'req'
|
|
81
|
+
),
|
|
82
|
+
'receiver' => anything
|
|
83
|
+
)
|
|
80
84
|
end
|
|
81
85
|
|
|
82
86
|
it 'returns a minimal event' do
|
|
83
|
-
expect(
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
+
expect(events).to include hash_including(
|
|
88
|
+
'event' => 'return',
|
|
89
|
+
'return_value' => Hash,
|
|
90
|
+
'id' => Integer,
|
|
91
|
+
'thread_id' => Integer,
|
|
92
|
+
'parent_id' => Integer,
|
|
93
|
+
'elapsed' => Numeric
|
|
94
|
+
)
|
|
87
95
|
end
|
|
88
96
|
end
|
|
89
97
|
|
|
90
|
-
describe '
|
|
91
|
-
|
|
92
|
-
let(:appmap_json_file)
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
appmap = JSON.parse(File.read(appmap_json_file)).to_yaml
|
|
98
|
+
describe 'showing a user' do
|
|
99
|
+
before(:all) { run_spec 'spec/controllers/users_controller_spec.rb:22' }
|
|
100
|
+
let(:appmap_json_file) do
|
|
101
|
+
'UsersController_GET_users_login_shows_the_user.appmap.json'
|
|
102
|
+
end
|
|
96
103
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
104
|
+
it 'records the normalized path info' do
|
|
105
|
+
expect(events).to include(
|
|
106
|
+
hash_including(
|
|
107
|
+
'http_server_request' => {
|
|
108
|
+
'request_method' => 'GET',
|
|
109
|
+
'path_info' => '/users/alice',
|
|
110
|
+
'normalized_path_info' => '/users/:id(.:format)'
|
|
111
|
+
}
|
|
112
|
+
)
|
|
113
|
+
)
|
|
114
|
+
end
|
|
115
|
+
end
|
|
106
116
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
117
|
+
describe 'listing users' do
|
|
118
|
+
before(:all) { run_spec 'spec/controllers/users_controller_spec.rb:11' }
|
|
119
|
+
let(:appmap_json_file) { 'UsersController_GET_users_lists_the_users.appmap.json' }
|
|
120
|
+
|
|
121
|
+
it 'records and labels view rendering' do
|
|
122
|
+
expect(events).to include hash_including(
|
|
123
|
+
'event' => 'call',
|
|
124
|
+
'thread_id' => Numeric,
|
|
125
|
+
'defined_class' => 'ActionView::Renderer',
|
|
126
|
+
'method_id' => 'render',
|
|
127
|
+
'path' => String,
|
|
128
|
+
'lineno' => Integer,
|
|
129
|
+
'static' => false
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
expect(appmap['classMap']).to include hash_including(
|
|
133
|
+
'name' => 'action_view',
|
|
134
|
+
'children' => include(hash_including(
|
|
135
|
+
'name' => 'ActionView',
|
|
136
|
+
'children' => include(hash_including(
|
|
137
|
+
'name' => 'Renderer',
|
|
138
|
+
'children' => include(hash_including(
|
|
139
|
+
'name' => 'render',
|
|
140
|
+
'labels' => ['mvc.view']
|
|
141
|
+
))
|
|
142
|
+
))
|
|
143
|
+
))
|
|
144
|
+
)
|
|
111
145
|
end
|
|
112
146
|
end
|
|
113
147
|
end
|
|
114
148
|
end
|
|
115
149
|
end
|
|
116
|
-
|
|
117
|
-
%w[5 6].each do |version|
|
|
118
|
-
it_behaves_like 'rails version', version
|
|
119
|
-
end
|
|
120
150
|
end
|