appmap 0.37.0 → 0.40.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (211) hide show
  1. checksums.yaml +4 -4
  2. data/.dockerignore +1 -1
  3. data/.rubocop.yml +12 -1
  4. data/.travis.yml +2 -23
  5. data/CHANGELOG.md +24 -0
  6. data/CONTRIBUTING.md +22 -0
  7. data/README.md +79 -50
  8. data/Rakefile +3 -3
  9. data/lib/appmap/class_map.rb +25 -8
  10. data/lib/appmap/config.rb +22 -10
  11. data/lib/appmap/event.rb +16 -1
  12. data/lib/appmap/hook.rb +11 -3
  13. data/lib/appmap/hook/method.rb +18 -12
  14. data/lib/appmap/rails/request_handler.rb +25 -4
  15. data/lib/appmap/railtie.rb +1 -5
  16. data/lib/appmap/trace.rb +18 -7
  17. data/lib/appmap/version.rb +2 -2
  18. data/spec/abstract_controller_base_spec.rb +132 -67
  19. data/spec/fixtures/hook/instance_method.rb +4 -0
  20. data/spec/fixtures/hook/labels.rb +6 -0
  21. data/spec/fixtures/{rails_users_app → rails5_users_app}/.dockerignore +0 -0
  22. data/spec/fixtures/{rails_users_app → rails5_users_app}/.gitignore +0 -0
  23. data/spec/fixtures/{rails_users_app → rails5_users_app}/.rspec +0 -0
  24. data/spec/fixtures/{rails_users_app → rails5_users_app}/.ruby-version +0 -0
  25. data/spec/fixtures/{rails_users_app → rails5_users_app}/Dockerfile +0 -0
  26. data/spec/fixtures/{rails_users_app → rails5_users_app}/Dockerfile.pg +0 -0
  27. data/spec/fixtures/rails5_users_app/Gemfile +50 -0
  28. data/spec/fixtures/{rails_users_app → rails5_users_app}/Rakefile +0 -0
  29. data/spec/fixtures/{rails_users_app → rails5_users_app}/app/controllers/api/users_controller.rb +0 -0
  30. data/spec/fixtures/{rails_users_app → rails5_users_app}/app/controllers/application_controller.rb +0 -0
  31. data/spec/fixtures/{rails4_users_app/app/assets/images → rails5_users_app/app/controllers/concerns}/.keep +0 -0
  32. data/spec/fixtures/{rails_users_app → rails5_users_app}/app/controllers/health_controller.rb +0 -0
  33. data/spec/fixtures/rails5_users_app/app/controllers/users_controller.rb +13 -0
  34. data/spec/fixtures/{rails4_users_app/app/models → rails5_users_app/app/models/activerecord}/user.rb +0 -0
  35. data/spec/fixtures/{rails4_users_app/app/controllers → rails5_users_app/app/models}/concerns/.keep +0 -0
  36. data/spec/fixtures/{rails_users_app → rails5_users_app}/app/models/sequel/user.rb +0 -0
  37. data/spec/fixtures/{rails4_users_app → rails5_users_app}/app/views/layouts/application.html.haml +0 -0
  38. data/spec/fixtures/{rails4_users_app → rails5_users_app}/app/views/users/index.html.haml +0 -0
  39. data/spec/fixtures/rails5_users_app/appmap.yml +4 -0
  40. data/spec/fixtures/{rails_users_app → rails5_users_app}/bin/appmap +0 -0
  41. data/spec/fixtures/{rails_users_app → rails5_users_app}/bin/byebug +0 -0
  42. data/spec/fixtures/{rails_users_app → rails5_users_app}/bin/gli +0 -0
  43. data/spec/fixtures/{rails_users_app → rails5_users_app}/bin/htmldiff +0 -0
  44. data/spec/fixtures/{rails_users_app → rails5_users_app}/bin/ldiff +0 -0
  45. data/spec/fixtures/{rails_users_app → rails5_users_app}/bin/nokogiri +0 -0
  46. data/spec/fixtures/{rails_users_app → rails5_users_app}/bin/rackup +0 -0
  47. data/spec/fixtures/{rails_users_app → rails5_users_app}/bin/rails +0 -0
  48. data/spec/fixtures/{rails_users_app → rails5_users_app}/bin/rake +0 -0
  49. data/spec/fixtures/{rails_users_app → rails5_users_app}/bin/rspec +0 -0
  50. data/spec/fixtures/{rails_users_app → rails5_users_app}/bin/ruby-parse +0 -0
  51. data/spec/fixtures/{rails_users_app → rails5_users_app}/bin/ruby-rewrite +0 -0
  52. data/spec/fixtures/{rails_users_app → rails5_users_app}/bin/sequel +0 -0
  53. data/spec/fixtures/{rails_users_app → rails5_users_app}/bin/setup +0 -0
  54. data/spec/fixtures/{rails_users_app → rails5_users_app}/bin/sprockets +0 -0
  55. data/spec/fixtures/{rails_users_app → rails5_users_app}/bin/thor +0 -0
  56. data/spec/fixtures/{rails_users_app → rails5_users_app}/bin/update +0 -0
  57. data/spec/fixtures/{rails_users_app → rails5_users_app}/config.ru +0 -0
  58. data/spec/fixtures/{rails_users_app → rails5_users_app}/config/application.rb +2 -0
  59. data/spec/fixtures/{rails_users_app → rails5_users_app}/config/boot.rb +0 -0
  60. data/spec/fixtures/{rails_users_app → rails5_users_app}/config/credentials.yml.enc +0 -0
  61. data/spec/fixtures/{rails_users_app → rails5_users_app}/config/database.yml +0 -0
  62. data/spec/fixtures/{rails_users_app → rails5_users_app}/config/environment.rb +0 -0
  63. data/spec/fixtures/{rails_users_app → rails5_users_app}/config/environments/development.rb +0 -0
  64. data/spec/fixtures/{rails_users_app → rails5_users_app}/config/environments/production.rb +0 -0
  65. data/spec/fixtures/{rails_users_app → rails5_users_app}/config/environments/test.rb +0 -0
  66. data/spec/fixtures/{rails_users_app → rails5_users_app}/config/initializers/application_controller_renderer.rb +0 -0
  67. data/spec/fixtures/{rails4_users_app → rails5_users_app}/config/initializers/backtrace_silencers.rb +0 -0
  68. data/spec/fixtures/{rails_users_app → rails5_users_app}/config/initializers/cors.rb +0 -0
  69. data/spec/fixtures/{rails4_users_app → rails5_users_app}/config/initializers/filter_parameter_logging.rb +0 -0
  70. data/spec/fixtures/{rails4_users_app → rails5_users_app}/config/initializers/inflections.rb +0 -0
  71. data/spec/fixtures/{rails4_users_app → rails5_users_app}/config/initializers/mime_types.rb +0 -0
  72. data/spec/fixtures/{rails_users_app → rails5_users_app}/config/initializers/record_button.rb +0 -0
  73. data/spec/fixtures/{rails_users_app → rails5_users_app}/config/initializers/wrap_parameters.rb +0 -0
  74. data/spec/fixtures/{rails_users_app → rails5_users_app}/config/locales/en.yml +0 -0
  75. data/spec/fixtures/{rails4_users_app → rails5_users_app}/config/routes.rb +1 -2
  76. data/spec/fixtures/{rails_users_app → rails5_users_app}/create_app +0 -0
  77. data/spec/fixtures/{rails_users_app → rails5_users_app}/db/migrate/20190728211408_create_users.rb +0 -0
  78. data/spec/fixtures/{rails_users_app → rails5_users_app}/db/schema.rb +0 -0
  79. data/spec/fixtures/{rails_users_app → rails5_users_app}/docker-compose.yml +0 -0
  80. data/spec/fixtures/{rails_users_app → rails5_users_app}/features/api_users.feature +0 -0
  81. data/spec/fixtures/{rails_users_app → rails5_users_app}/features/support/env.rb +0 -0
  82. data/spec/fixtures/{rails_users_app → rails5_users_app}/features/support/hooks.rb +0 -0
  83. data/spec/fixtures/{rails_users_app → rails5_users_app}/features/support/steps.rb +0 -0
  84. data/spec/fixtures/{rails4_users_app/app/mailers → rails5_users_app/lib/tasks}/.keep +0 -0
  85. data/spec/fixtures/{rails4_users_app/app/models → rails5_users_app/log}/.keep +0 -0
  86. data/spec/fixtures/{rails_users_app → rails5_users_app}/public/robots.txt +0 -0
  87. data/spec/fixtures/{rails_users_app → rails5_users_app}/spec/controllers/users_controller_api_spec.rb +1 -1
  88. data/spec/fixtures/rails5_users_app/spec/controllers/users_controller_spec.rb +27 -0
  89. data/spec/fixtures/{rails_users_app → rails5_users_app}/spec/models/user_spec.rb +0 -0
  90. data/spec/fixtures/{rails_users_app → rails5_users_app}/spec/rails_helper.rb +0 -0
  91. data/spec/fixtures/{rails4_users_app → rails5_users_app}/spec/spec_helper.rb +0 -0
  92. data/spec/fixtures/{rails_users_app → rails5_users_app}/users_app/.gitignore +0 -0
  93. data/spec/fixtures/rails6_users_app/.dockerignore +1 -0
  94. data/spec/fixtures/rails6_users_app/.gitignore +39 -0
  95. data/spec/fixtures/rails6_users_app/.rspec +1 -0
  96. data/spec/fixtures/rails6_users_app/.ruby-version +1 -0
  97. data/spec/fixtures/{rails4_users_app → rails6_users_app}/Dockerfile +3 -4
  98. data/spec/fixtures/{rails4_users_app → rails6_users_app}/Dockerfile.pg +1 -1
  99. data/spec/fixtures/{rails_users_app → rails6_users_app}/Gemfile +3 -4
  100. data/spec/fixtures/{rails4_users_app → rails6_users_app}/Rakefile +1 -1
  101. data/spec/fixtures/{rails4_users_app → rails6_users_app}/app/controllers/api/users_controller.rb +1 -1
  102. data/spec/fixtures/rails6_users_app/app/controllers/application_controller.rb +2 -0
  103. data/spec/fixtures/{rails4_users_app/app/models → rails6_users_app/app/controllers}/concerns/.keep +0 -0
  104. data/spec/fixtures/{rails4_users_app → rails6_users_app}/app/controllers/health_controller.rb +1 -1
  105. data/spec/fixtures/rails6_users_app/app/controllers/users_controller.rb +13 -0
  106. data/spec/fixtures/{rails_users_app → rails6_users_app}/app/models/activerecord/user.rb +0 -0
  107. data/spec/fixtures/{rails4_users_app/lib/assets → rails6_users_app/app/models/concerns}/.keep +0 -0
  108. data/spec/fixtures/rails6_users_app/app/models/sequel/user.rb +25 -0
  109. data/spec/fixtures/{rails_users_app → rails6_users_app}/app/views/layouts/application.html.haml +0 -0
  110. data/spec/fixtures/{rails_users_app → rails6_users_app}/app/views/users/index.html.haml +0 -0
  111. data/spec/fixtures/rails6_users_app/appmap.yml +5 -0
  112. data/spec/fixtures/rails6_users_app/bin/appmap +29 -0
  113. data/spec/fixtures/rails6_users_app/bin/byebug +29 -0
  114. data/spec/fixtures/rails6_users_app/bin/gli +29 -0
  115. data/spec/fixtures/rails6_users_app/bin/htmldiff +29 -0
  116. data/spec/fixtures/rails6_users_app/bin/ldiff +29 -0
  117. data/spec/fixtures/rails6_users_app/bin/nokogiri +29 -0
  118. data/spec/fixtures/rails6_users_app/bin/rackup +29 -0
  119. data/spec/fixtures/rails6_users_app/bin/rails +4 -0
  120. data/spec/fixtures/rails6_users_app/bin/rake +29 -0
  121. data/spec/fixtures/rails6_users_app/bin/rspec +29 -0
  122. data/spec/fixtures/rails6_users_app/bin/ruby-parse +29 -0
  123. data/spec/fixtures/rails6_users_app/bin/ruby-rewrite +29 -0
  124. data/spec/fixtures/rails6_users_app/bin/sequel +29 -0
  125. data/spec/fixtures/rails6_users_app/bin/setup +25 -0
  126. data/spec/fixtures/rails6_users_app/bin/sprockets +29 -0
  127. data/spec/fixtures/rails6_users_app/bin/thor +29 -0
  128. data/spec/fixtures/rails6_users_app/bin/update +25 -0
  129. data/spec/fixtures/{rails4_users_app → rails6_users_app}/config.ru +2 -1
  130. data/spec/fixtures/rails6_users_app/config/application.rb +53 -0
  131. data/spec/fixtures/rails6_users_app/config/boot.rb +3 -0
  132. data/spec/fixtures/rails6_users_app/config/credentials.yml.enc +1 -0
  133. data/spec/fixtures/{rails4_users_app → rails6_users_app}/config/database.yml +2 -2
  134. data/spec/fixtures/{rails4_users_app → rails6_users_app}/config/environment.rb +1 -1
  135. data/spec/fixtures/rails6_users_app/config/environments/development.rb +40 -0
  136. data/spec/fixtures/{rails4_users_app → rails6_users_app}/config/environments/production.rb +19 -30
  137. data/spec/fixtures/{rails4_users_app → rails6_users_app}/config/environments/test.rb +5 -11
  138. data/spec/fixtures/rails6_users_app/config/initializers/application_controller_renderer.rb +8 -0
  139. data/spec/fixtures/{rails_users_app → rails6_users_app}/config/initializers/backtrace_silencers.rb +0 -0
  140. data/spec/fixtures/rails6_users_app/config/initializers/cors.rb +16 -0
  141. data/spec/fixtures/{rails_users_app → rails6_users_app}/config/initializers/filter_parameter_logging.rb +0 -0
  142. data/spec/fixtures/{rails_users_app → rails6_users_app}/config/initializers/inflections.rb +0 -0
  143. data/spec/fixtures/{rails_users_app → rails6_users_app}/config/initializers/mime_types.rb +0 -0
  144. data/spec/fixtures/rails6_users_app/config/initializers/record_button.rb +3 -0
  145. data/spec/fixtures/{rails4_users_app → rails6_users_app}/config/initializers/wrap_parameters.rb +1 -6
  146. data/spec/fixtures/{rails4_users_app → rails6_users_app}/config/locales/en.yml +10 -0
  147. data/spec/fixtures/{rails_users_app → rails6_users_app}/config/routes.rb +1 -1
  148. data/spec/fixtures/{rails4_users_app → rails6_users_app}/create_app +7 -3
  149. data/spec/fixtures/rails6_users_app/db/migrate/20190728211408_create_users.rb +9 -0
  150. data/spec/fixtures/rails6_users_app/db/schema.rb +23 -0
  151. data/spec/fixtures/{rails4_users_app → rails6_users_app}/docker-compose.yml +3 -1
  152. data/spec/fixtures/rails6_users_app/features/api_users.feature +13 -0
  153. data/spec/fixtures/rails6_users_app/features/support/env.rb +4 -0
  154. data/spec/fixtures/rails6_users_app/features/support/hooks.rb +11 -0
  155. data/spec/fixtures/rails6_users_app/features/support/steps.rb +18 -0
  156. data/spec/fixtures/{rails4_users_app → rails6_users_app}/lib/tasks/.keep +0 -0
  157. data/spec/fixtures/{rails4_users_app → rails6_users_app}/log/.keep +0 -0
  158. data/spec/fixtures/rails6_users_app/public/robots.txt +1 -0
  159. data/spec/fixtures/rails6_users_app/spec/controllers/users_controller_api_spec.rb +29 -0
  160. data/spec/fixtures/rails6_users_app/spec/controllers/users_controller_spec.rb +27 -0
  161. data/spec/fixtures/rails6_users_app/spec/models/user_spec.rb +39 -0
  162. data/spec/fixtures/{rails4_users_app → rails6_users_app}/spec/rails_helper.rb +3 -32
  163. data/spec/fixtures/{rails_users_app → rails6_users_app}/spec/spec_helper.rb +0 -0
  164. data/spec/fixtures/{rails4_users_app → rails6_users_app/users_app}/.gitignore +8 -1
  165. data/spec/hook_spec.rb +111 -57
  166. data/spec/rails_spec_helper.rb +5 -5
  167. data/spec/railtie_spec.rb +31 -32
  168. data/spec/record_sql_rails_pg_spec.rb +62 -63
  169. data/spec/remote_recording_spec.rb +90 -89
  170. data/spec/rspec_feature_metadata_spec.rb +17 -18
  171. data/spec/spec_helper.rb +1 -0
  172. metadata +148 -139
  173. data/spec/abstract_controller4_base_spec.rb +0 -67
  174. data/spec/fixtures/rails4_users_app/.rbenv-gemsets +0 -2
  175. data/spec/fixtures/rails4_users_app/.ruby-version +0 -1
  176. data/spec/fixtures/rails4_users_app/Gemfile +0 -77
  177. data/spec/fixtures/rails4_users_app/README.rdoc +0 -28
  178. data/spec/fixtures/rails4_users_app/app/assets/javascripts/application.js +0 -16
  179. data/spec/fixtures/rails4_users_app/app/assets/stylesheets/application.css +0 -15
  180. data/spec/fixtures/rails4_users_app/app/controllers/application_controller.rb +0 -5
  181. data/spec/fixtures/rails4_users_app/app/controllers/users_controller.rb +0 -5
  182. data/spec/fixtures/rails4_users_app/app/helpers/application_helper.rb +0 -2
  183. data/spec/fixtures/rails4_users_app/appmap.yml +0 -3
  184. data/spec/fixtures/rails4_users_app/bin/rails +0 -9
  185. data/spec/fixtures/rails4_users_app/bin/setup +0 -29
  186. data/spec/fixtures/rails4_users_app/bin/spring +0 -17
  187. data/spec/fixtures/rails4_users_app/config/application.rb +0 -26
  188. data/spec/fixtures/rails4_users_app/config/boot.rb +0 -3
  189. data/spec/fixtures/rails4_users_app/config/environments/development.rb +0 -41
  190. data/spec/fixtures/rails4_users_app/config/initializers/assets.rb +0 -11
  191. data/spec/fixtures/rails4_users_app/config/initializers/cookies_serializer.rb +0 -3
  192. data/spec/fixtures/rails4_users_app/config/initializers/session_store.rb +0 -3
  193. data/spec/fixtures/rails4_users_app/config/initializers/to_time_preserves_timezone.rb +0 -10
  194. data/spec/fixtures/rails4_users_app/config/secrets.yml +0 -22
  195. data/spec/fixtures/rails4_users_app/db/migrate/20191127112304_create_users.rb +0 -10
  196. data/spec/fixtures/rails4_users_app/db/schema.rb +0 -26
  197. data/spec/fixtures/rails4_users_app/db/seeds.rb +0 -7
  198. data/spec/fixtures/rails4_users_app/public/404.html +0 -67
  199. data/spec/fixtures/rails4_users_app/public/422.html +0 -67
  200. data/spec/fixtures/rails4_users_app/public/500.html +0 -66
  201. data/spec/fixtures/rails4_users_app/public/favicon.ico +0 -0
  202. data/spec/fixtures/rails4_users_app/public/robots.txt +0 -5
  203. data/spec/fixtures/rails4_users_app/spec/controllers/users_controller_api_spec.rb +0 -49
  204. data/spec/fixtures/rails4_users_app/test/fixtures/users.yml +0 -9
  205. data/spec/fixtures/rails_users_app/app/controllers/concerns/.keep +0 -0
  206. data/spec/fixtures/rails_users_app/app/controllers/users_controller.rb +0 -5
  207. data/spec/fixtures/rails_users_app/app/models/concerns/.keep +0 -0
  208. data/spec/fixtures/rails_users_app/appmap.yml +0 -3
  209. data/spec/fixtures/rails_users_app/lib/tasks/.keep +0 -0
  210. data/spec/fixtures/rails_users_app/log/.keep +0 -0
  211. data/spec/record_sql_rails4_pg_spec.rb +0 -76
@@ -89,10 +89,25 @@ module AppMap
89
89
  else
90
90
  mc.path = [ defined_class, static ? '.' : '#', method.name ].join
91
91
  end
92
+
93
+ # Check if the method has key parameters. If there are any they'll always be last.
94
+ # If yes, then extract it from arguments.
95
+ has_key = [[:dummy], *method.parameters].last.first.to_s.start_with?('key') && arguments[-1].is_a?(Hash)
96
+ kwargs = has_key && arguments[-1].dup || {}
97
+
92
98
  mc.parameters = method.parameters.map.with_index do |method_param, idx|
93
99
  param_type, param_name = method_param
94
100
  param_name ||= 'arg'
95
- value = arguments[idx]
101
+ value = case param_type
102
+ when :keyrest
103
+ kwargs
104
+ when /^key/
105
+ kwargs.delete param_name
106
+ when :rest
107
+ arguments[idx..(has_key ? -2 : -1)]
108
+ else
109
+ arguments[idx]
110
+ end
96
111
  {
97
112
  name: param_name,
98
113
  class: value.class.name,
@@ -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 = hook_cls.public_instance_method(method_id)
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
 
@@ -39,6 +39,7 @@ module AppMap
39
39
  end
40
40
 
41
41
  defined_class = @defined_class
42
+ hook_package = self.hook_package
42
43
  hook_method = self.hook_method
43
44
  before_hook = self.method(:before_hook)
44
45
  after_hook = self.method(:after_hook)
@@ -48,29 +49,34 @@ module AppMap
48
49
  hook_class.instance_eval do
49
50
  hook_method_def = Proc.new do |*args, &block|
50
51
  instance_method = hook_method.bind(self).to_proc
52
+ call_instance_method = -> { instance_method.call(*args, &block) }
51
53
 
52
54
  # We may not have gotten the class for the method during
53
55
  # initialization (e.g. for a singleton method on an embedded
54
56
  # struct), so make sure we have it now.
55
- defined_class,_ = Hook.qualify_method_name(hook_method) unless defined_class
57
+ defined_class, = Hook.qualify_method_name(hook_method) unless defined_class
56
58
 
57
- hook_disabled = Thread.current[HOOK_DISABLE_KEY]
58
- enabled = true if !hook_disabled && AppMap.tracing.enabled?
59
- return instance_method.call(*args, &block) unless enabled
59
+ reentrant = Thread.current[HOOK_DISABLE_KEY]
60
+ disabled_by_shallow_flag = \
61
+ -> { hook_package&.shallow? && AppMap.tracing.last_package_for_current_thread == hook_package }
60
62
 
61
- call_event, start_time = with_disabled_hook.() do
62
- before_hook.(self, defined_class, args)
63
+ enabled = true if AppMap.tracing.enabled? && !reentrant && !disabled_by_shallow_flag.call
64
+
65
+ return call_instance_method.call unless enabled
66
+
67
+ call_event, start_time = with_disabled_hook.call do
68
+ before_hook.call(self, defined_class, args)
63
69
  end
64
70
  return_value = nil
65
71
  exception = nil
66
72
  begin
67
- return_value = instance_method.(*args, &block)
73
+ return_value = call_instance_method.call
68
74
  rescue
69
75
  exception = $ERROR_INFO
70
76
  raise
71
77
  ensure
72
- with_disabled_hook.() do
73
- after_hook.(self, call_event, start_time, return_value, exception)
78
+ with_disabled_hook.call do
79
+ after_hook.call(self, call_event, start_time, return_value, exception)
74
80
  end
75
81
  end
76
82
  end
@@ -87,7 +93,7 @@ module AppMap
87
93
  [ call_event, TIME_NOW.call ]
88
94
  end
89
95
 
90
- def after_hook(receiver, call_event, start_time, return_value, exception)
96
+ def after_hook(_receiver, call_event, start_time, return_value, exception)
91
97
  require 'appmap/event'
92
98
  elapsed = TIME_NOW.call - start_time
93
99
  return_event = \
@@ -95,12 +101,12 @@ module AppMap
95
101
  AppMap.tracing.record_event return_event
96
102
  end
97
103
 
98
- def with_disabled_hook(&fn)
104
+ def with_disabled_hook(&function)
99
105
  # Don't record functions, such as to_s and inspect, that might be called
100
106
  # by the fn. Otherwise there can be a stack overflow.
101
107
  Thread.current[HOOK_DISABLE_KEY] = true
102
108
  begin
103
- fn.call
109
+ function.call
104
110
  ensure
105
111
  Thread.current[HOOK_DISABLE_KEY] = false
106
112
  end
@@ -7,22 +7,31 @@ 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
- @params = ActionDispatch::Http::ParameterFilter.new(::Rails.application.config.filter_parameters).filter(request.params)
18
+ # ActionDispatch::Http::ParameterFilter is deprecated
19
+ parameter_filter_cls = \
20
+ if defined?(ActiveSupport::ParameterFilter)
21
+ ActiveSupport::ParameterFilter
22
+ else
23
+ ActionDispatch::Http::ParameterFilter
24
+ end
25
+ @params = parameter_filter_cls.new(::Rails.application.config.filter_parameters).filter(request.params)
18
26
  end
19
27
 
20
28
  def to_h
21
29
  super.tap do |h|
22
30
  h[:http_server_request] = {
23
31
  request_method: request_method,
24
- path_info: path_info
25
- }
32
+ path_info: path_info,
33
+ normalized_path_info: normalized_path_info
34
+ }.compact
26
35
 
27
36
  h[:message] = params.keys.map do |key|
28
37
  val = params[key]
@@ -35,6 +44,18 @@ module AppMap
35
44
  end
36
45
  end
37
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
38
59
  end
39
60
 
40
61
  class HTTPServerResponse < AppMap::Event::MethodReturnIgnoreValue
@@ -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', after: 'appmap.init' do |_| # params: app
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
@@ -15,34 +15,38 @@ module AppMap
15
15
 
16
16
  class Tracing
17
17
  def initialize
18
- @tracing = []
18
+ @tracers = []
19
19
  end
20
20
 
21
21
  def empty?
22
- @tracing.empty?
22
+ @tracers.empty?
23
23
  end
24
24
 
25
25
  def trace(enable: true)
26
26
  Tracer.new.tap do |tracer|
27
- @tracing << tracer
27
+ @tracers << tracer
28
28
  tracer.enable if enable
29
29
  end
30
30
  end
31
31
 
32
32
  def enabled?
33
- @tracing.any?(&:enabled?)
33
+ @tracers.any?(&:enabled?)
34
+ end
35
+
36
+ def last_package_for_current_thread
37
+ @tracers.first&.last_package_for_current_thread
34
38
  end
35
39
 
36
40
  def record_event(event, package: nil, defined_class: nil, method: nil)
37
- @tracing.each do |tracer|
41
+ @tracers.each do |tracer|
38
42
  tracer.record_event(event, package: package, defined_class: defined_class, method: method)
39
43
  end
40
44
  end
41
45
 
42
46
  def delete(tracer)
43
- return unless @tracing.member?(tracer)
47
+ return unless @tracers.member?(tracer)
44
48
 
45
- @tracing.delete(tracer)
49
+ @tracers.delete(tracer)
46
50
  tracer.disable
47
51
  end
48
52
  end
@@ -52,6 +56,7 @@ module AppMap
52
56
  # Records the events which happen in a program.
53
57
  def initialize
54
58
  @events = []
59
+ @last_package_for_thread = {}
55
60
  @methods = Set.new
56
61
  @enabled = false
57
62
  end
@@ -75,11 +80,17 @@ module AppMap
75
80
  def record_event(event, package: nil, defined_class: nil, method: nil)
76
81
  return unless @enabled
77
82
 
83
+ @last_package_for_thread[Thread.current.object_id] = package if package
78
84
  @events << event
79
85
  @methods << Trace::ScopedMethod.new(package, defined_class, method, event.static) \
80
86
  if package && defined_class && method && (event.event == :call)
81
87
  end
82
88
 
89
+ # Gets the last package which was observed on the current thread.
90
+ def last_package_for_current_thread
91
+ @last_package_for_thread[Thread.current.object_id]
92
+ end
93
+
83
94
  # Gets a unique list of the methods that were invoked by the program.
84
95
  def event_methods
85
96
  @methods.to_a
@@ -3,7 +3,7 @@
3
3
  module AppMap
4
4
  URL = 'https://github.com/applandinc/appmap-ruby'
5
5
 
6
- VERSION = '0.37.0'
6
+ VERSION = '0.40.0'
7
7
 
8
- APPMAP_FORMAT_VERSION = '1.3'
8
+ APPMAP_FORMAT_VERSION = '1.4'
9
9
  end
@@ -1,85 +1,150 @@
1
1
  require 'rails_spec_helper'
2
2
 
3
3
  describe 'AbstractControllerBase' do
4
- before(:all) { @fixture_dir = 'spec/fixtures/rails_users_app' }
5
- include_context 'Rails app pg database'
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"
7
+ def run_spec(spec_name)
8
+ FileUtils.rm_rf tmpdir
9
+ FileUtils.mkdir_p tmpdir
10
+ cmd = <<~CMD.gsub "\n", ' '
11
+ docker-compose run --rm -e APPMAP=true
12
+ -v #{File.absolute_path tmpdir}:/app/tmp app ./bin/rspec #{spec_name}
13
+ CMD
14
+ run_cmd cmd, chdir: fixture_dir
15
+ end
6
16
 
7
- around(:each) do |example|
8
- FileUtils.rm_rf tmpdir
9
- FileUtils.mkdir_p tmpdir
10
- cmd = "docker-compose run --rm -e APPMAP=true -v #{File.absolute_path tmpdir}:/app/tmp app ./bin/rspec spec/controllers/users_controller_api_spec.rb:8"
11
- run_cmd cmd, chdir: @fixture_dir
17
+ def tmpdir
18
+ 'tmp/spec/AbstractControllerBase'
19
+ end
12
20
 
13
- example.run
14
- end
21
+ let(:appmap) { JSON.parse File.read File.join tmpdir, 'appmap/rspec', appmap_json_file }
22
+ let(:events) { appmap['events'] }
15
23
 
16
- let(:tmpdir) { 'tmp/spec/AbstractControllerBase' }
17
- let(:appmap_json) { File.join(tmpdir, 'appmap/rspec/Api_UsersController_POST_api_users_with_required_parameters_creates_a_user.appmap.json') }
24
+ describe 'testing with rspec' do
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
18
30
 
19
- describe 'testing with rspec' do
20
- it 'inventory file is printed' do
21
- expect(File).to exist(File.join(tmpdir, 'appmap/rspec/Inventory.appmap.json'))
22
- end
31
+ it 'inventory file is printed' do
32
+ expect(File).to exist(File.join(tmpdir, 'appmap/rspec/Inventory.appmap.json'))
33
+ end
23
34
 
24
- it 'message fields are recorded in the appmap' do
25
- expect(File).to exist(appmap_json)
26
- appmap = JSON.parse(File.read(appmap_json)).to_yaml
35
+ it 'message fields are recorded in the appmap' do
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
+ )
64
+ end
27
65
 
28
- expect(appmap).to include(<<-MESSAGE.strip)
29
- message:
30
- - name: login
31
- class: String
32
- value: alice
33
- object_id:
34
- MESSAGE
66
+ it 'properly captures method parameters in the appmap' do
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
+ )
84
+ end
35
85
 
36
- expect(appmap).to include(<<-MESSAGE.strip)
37
- - name: password
38
- class: String
39
- value: "[FILTERED]"
40
- object_id:
41
- MESSAGE
86
+ it 'returns a minimal event' do
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
+ )
95
+ end
96
+ end
42
97
 
43
- expect(appmap).to include(<<-SERVER_REQUEST.strip)
44
- http_server_request:
45
- request_method: POST
46
- path_info: "/api/users"
47
- SERVER_REQUEST
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
48
103
 
49
- expect(appmap).to include(<<-SERVER_RESPONSE.strip)
50
- http_server_response:
51
- status: 201
52
- mime_type: application/json; charset=utf-8
53
- SERVER_RESPONSE
54
- end
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
55
116
 
56
- it 'properly captures method parameters in the appmap' do
57
- expect(File).to exist(appmap_json)
58
- appmap = JSON.parse(File.read(appmap_json)).to_yaml
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' }
59
120
 
60
- expect(appmap).to match(<<-CREATE_CALL.strip)
61
- event: call
62
- thread_id: .*
63
- defined_class: Api::UsersController
64
- method_id: build_user
65
- path: app/controllers/api/users_controller.rb
66
- lineno: 23
67
- static: false
68
- parameters:
69
- - name: params
70
- class: ActiveSupport::HashWithIndifferentAccess
71
- object_id: .*
72
- value: '{"login"=>"alice"}'
73
- kind: req
74
- receiver:
75
- CREATE_CALL
76
- end
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
+ )
77
131
 
78
- it 'returns a minimal event' do
79
- expect(File).to exist(appmap_json)
80
- appmap = JSON.parse(File.read(appmap_json))
81
- event = appmap['events'].find { |event| event['event'] == 'return' && event['return_value'] }
82
- expect(event.keys).to eq(%w[id event thread_id parent_id elapsed return_value])
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' => ['view']
141
+ ))
142
+ ))
143
+ ))
144
+ )
145
+ end
146
+ end
147
+ end
83
148
  end
84
149
  end
85
150
  end