appmap 0.31.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (259) hide show
  1. checksums.yaml +7 -0
  2. data/.dockerignore +5 -0
  3. data/.gitignore +17 -0
  4. data/.rubocop.yml +27 -0
  5. data/.ruby-version +1 -0
  6. data/.travis.yml +44 -0
  7. data/CHANGELOG.md +199 -0
  8. data/Dockerfile.appmap +5 -0
  9. data/Gemfile +5 -0
  10. data/LICENSE.txt +21 -0
  11. data/README.md +300 -0
  12. data/Rakefile +132 -0
  13. data/appmap.gemspec +44 -0
  14. data/appmap.yml +8 -0
  15. data/examples/install.rb +76 -0
  16. data/examples/mock_webapp/Gemfile +1 -0
  17. data/examples/mock_webapp/appmap.yml +2 -0
  18. data/examples/mock_webapp/exe/mock_webapp_request +12 -0
  19. data/examples/mock_webapp/lib/mock_webapp/controller.rb +23 -0
  20. data/examples/mock_webapp/lib/mock_webapp/request.rb +12 -0
  21. data/examples/mock_webapp/lib/mock_webapp/user.rb +18 -0
  22. data/exe/appmap +154 -0
  23. data/lib/appmap.rb +87 -0
  24. data/lib/appmap/algorithm/prune_class_map.rb +67 -0
  25. data/lib/appmap/algorithm/stats.rb +91 -0
  26. data/lib/appmap/class_map.rb +135 -0
  27. data/lib/appmap/command/record.rb +38 -0
  28. data/lib/appmap/command/stats.rb +14 -0
  29. data/lib/appmap/config.rb +91 -0
  30. data/lib/appmap/cucumber.rb +89 -0
  31. data/lib/appmap/event.rb +168 -0
  32. data/lib/appmap/hook.rb +130 -0
  33. data/lib/appmap/metadata.rb +62 -0
  34. data/lib/appmap/middleware/remote_recording.rb +114 -0
  35. data/lib/appmap/minitest.rb +141 -0
  36. data/lib/appmap/rails/action_handler.rb +91 -0
  37. data/lib/appmap/rails/sql_handler.rb +145 -0
  38. data/lib/appmap/railtie.rb +45 -0
  39. data/lib/appmap/record.rb +27 -0
  40. data/lib/appmap/rspec.rb +301 -0
  41. data/lib/appmap/trace.rb +96 -0
  42. data/lib/appmap/util.rb +40 -0
  43. data/lib/appmap/version.rb +9 -0
  44. data/lore/pages/2019-05-21-install-and-record/index.pug +51 -0
  45. data/lore/pages/2019-05-21-install-and-record/install_example_appmap.png +0 -0
  46. data/lore/pages/2019-05-21-install-and-record/metadata.yml +5 -0
  47. data/lore/pages/layout.pug +66 -0
  48. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-grid.css +1912 -0
  49. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-grid.css.map +1 -0
  50. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-grid.min.css +7 -0
  51. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-grid.min.css.map +1 -0
  52. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-reboot.css +331 -0
  53. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-reboot.css.map +1 -0
  54. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-reboot.min.css +8 -0
  55. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-reboot.min.css.map +1 -0
  56. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap.css +9030 -0
  57. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap.css.map +1 -0
  58. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap.min.css +7 -0
  59. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap.min.css.map +1 -0
  60. data/lore/public/stylesheets/style.css +8 -0
  61. data/package-lock.json +1064 -0
  62. data/package.json +24 -0
  63. data/spec/abstract_controller4_base_spec.rb +67 -0
  64. data/spec/abstract_controller_base_spec.rb +72 -0
  65. data/spec/config_spec.rb +25 -0
  66. data/spec/fixtures/hook/attr_accessor.rb +5 -0
  67. data/spec/fixtures/hook/compare.rb +7 -0
  68. data/spec/fixtures/hook/constructor.rb +7 -0
  69. data/spec/fixtures/hook/exception_method.rb +11 -0
  70. data/spec/fixtures/hook/instance_method.rb +23 -0
  71. data/spec/fixtures/hook/openssl_sign.rb +87 -0
  72. data/spec/fixtures/hook/singleton_method.rb +54 -0
  73. data/spec/fixtures/rack_users_app/.dockerignore +2 -0
  74. data/spec/fixtures/rack_users_app/.gitignore +2 -0
  75. data/spec/fixtures/rack_users_app/Dockerfile +32 -0
  76. data/spec/fixtures/rack_users_app/Gemfile +10 -0
  77. data/spec/fixtures/rack_users_app/appmap.yml +3 -0
  78. data/spec/fixtures/rack_users_app/config.ru +2 -0
  79. data/spec/fixtures/rack_users_app/docker-compose.yml +9 -0
  80. data/spec/fixtures/rack_users_app/lib/app.rb +36 -0
  81. data/spec/fixtures/rails4_users_app/.gitignore +13 -0
  82. data/spec/fixtures/rails4_users_app/.rbenv-gemsets +2 -0
  83. data/spec/fixtures/rails4_users_app/.ruby-version +1 -0
  84. data/spec/fixtures/rails4_users_app/Dockerfile +30 -0
  85. data/spec/fixtures/rails4_users_app/Dockerfile.pg +3 -0
  86. data/spec/fixtures/rails4_users_app/Gemfile +77 -0
  87. data/spec/fixtures/rails4_users_app/README.rdoc +28 -0
  88. data/spec/fixtures/rails4_users_app/Rakefile +6 -0
  89. data/spec/fixtures/rails4_users_app/app/assets/images/.keep +0 -0
  90. data/spec/fixtures/rails4_users_app/app/assets/javascripts/application.js +16 -0
  91. data/spec/fixtures/rails4_users_app/app/assets/stylesheets/application.css +15 -0
  92. data/spec/fixtures/rails4_users_app/app/controllers/api/users_controller.rb +27 -0
  93. data/spec/fixtures/rails4_users_app/app/controllers/application_controller.rb +5 -0
  94. data/spec/fixtures/rails4_users_app/app/controllers/concerns/.keep +0 -0
  95. data/spec/fixtures/rails4_users_app/app/controllers/health_controller.rb +5 -0
  96. data/spec/fixtures/rails4_users_app/app/controllers/users_controller.rb +5 -0
  97. data/spec/fixtures/rails4_users_app/app/helpers/application_helper.rb +2 -0
  98. data/spec/fixtures/rails4_users_app/app/mailers/.keep +0 -0
  99. data/spec/fixtures/rails4_users_app/app/models/.keep +0 -0
  100. data/spec/fixtures/rails4_users_app/app/models/concerns/.keep +0 -0
  101. data/spec/fixtures/rails4_users_app/app/models/user.rb +18 -0
  102. data/spec/fixtures/rails4_users_app/app/views/layouts/application.html.haml +7 -0
  103. data/spec/fixtures/rails4_users_app/app/views/users/index.html.haml +7 -0
  104. data/spec/fixtures/rails4_users_app/appmap.yml +3 -0
  105. data/spec/fixtures/rails4_users_app/bin/rails +9 -0
  106. data/spec/fixtures/rails4_users_app/bin/setup +29 -0
  107. data/spec/fixtures/rails4_users_app/bin/spring +17 -0
  108. data/spec/fixtures/rails4_users_app/config.ru +4 -0
  109. data/spec/fixtures/rails4_users_app/config/application.rb +26 -0
  110. data/spec/fixtures/rails4_users_app/config/boot.rb +3 -0
  111. data/spec/fixtures/rails4_users_app/config/database.yml +18 -0
  112. data/spec/fixtures/rails4_users_app/config/environment.rb +5 -0
  113. data/spec/fixtures/rails4_users_app/config/environments/development.rb +41 -0
  114. data/spec/fixtures/rails4_users_app/config/environments/production.rb +79 -0
  115. data/spec/fixtures/rails4_users_app/config/environments/test.rb +42 -0
  116. data/spec/fixtures/rails4_users_app/config/initializers/assets.rb +11 -0
  117. data/spec/fixtures/rails4_users_app/config/initializers/backtrace_silencers.rb +7 -0
  118. data/spec/fixtures/rails4_users_app/config/initializers/cookies_serializer.rb +3 -0
  119. data/spec/fixtures/rails4_users_app/config/initializers/filter_parameter_logging.rb +4 -0
  120. data/spec/fixtures/rails4_users_app/config/initializers/inflections.rb +16 -0
  121. data/spec/fixtures/rails4_users_app/config/initializers/mime_types.rb +4 -0
  122. data/spec/fixtures/rails4_users_app/config/initializers/session_store.rb +3 -0
  123. data/spec/fixtures/rails4_users_app/config/initializers/to_time_preserves_timezone.rb +10 -0
  124. data/spec/fixtures/rails4_users_app/config/initializers/wrap_parameters.rb +14 -0
  125. data/spec/fixtures/rails4_users_app/config/locales/en.yml +23 -0
  126. data/spec/fixtures/rails4_users_app/config/routes.rb +12 -0
  127. data/spec/fixtures/rails4_users_app/config/secrets.yml +22 -0
  128. data/spec/fixtures/rails4_users_app/create_app +23 -0
  129. data/spec/fixtures/rails4_users_app/db/migrate/20191127112304_create_users.rb +10 -0
  130. data/spec/fixtures/rails4_users_app/db/schema.rb +26 -0
  131. data/spec/fixtures/rails4_users_app/db/seeds.rb +7 -0
  132. data/spec/fixtures/rails4_users_app/docker-compose.yml +26 -0
  133. data/spec/fixtures/rails4_users_app/lib/assets/.keep +0 -0
  134. data/spec/fixtures/rails4_users_app/lib/tasks/.keep +0 -0
  135. data/spec/fixtures/rails4_users_app/log/.keep +0 -0
  136. data/spec/fixtures/rails4_users_app/public/404.html +67 -0
  137. data/spec/fixtures/rails4_users_app/public/422.html +67 -0
  138. data/spec/fixtures/rails4_users_app/public/500.html +66 -0
  139. data/spec/fixtures/rails4_users_app/public/favicon.ico +0 -0
  140. data/spec/fixtures/rails4_users_app/public/robots.txt +5 -0
  141. data/spec/fixtures/rails4_users_app/spec/controllers/users_controller_api_spec.rb +49 -0
  142. data/spec/fixtures/rails4_users_app/spec/rails_helper.rb +95 -0
  143. data/spec/fixtures/rails4_users_app/spec/spec_helper.rb +96 -0
  144. data/spec/fixtures/rails4_users_app/test/fixtures/users.yml +9 -0
  145. data/spec/fixtures/rails_users_app/.dockerignore +1 -0
  146. data/spec/fixtures/rails_users_app/.gitignore +39 -0
  147. data/spec/fixtures/rails_users_app/.rspec +1 -0
  148. data/spec/fixtures/rails_users_app/.ruby-version +1 -0
  149. data/spec/fixtures/rails_users_app/Dockerfile +29 -0
  150. data/spec/fixtures/rails_users_app/Dockerfile.pg +3 -0
  151. data/spec/fixtures/rails_users_app/Gemfile +52 -0
  152. data/spec/fixtures/rails_users_app/Rakefile +6 -0
  153. data/spec/fixtures/rails_users_app/app/controllers/api/users_controller.rb +27 -0
  154. data/spec/fixtures/rails_users_app/app/controllers/application_controller.rb +2 -0
  155. data/spec/fixtures/rails_users_app/app/controllers/concerns/.keep +0 -0
  156. data/spec/fixtures/rails_users_app/app/controllers/health_controller.rb +5 -0
  157. data/spec/fixtures/rails_users_app/app/controllers/users_controller.rb +5 -0
  158. data/spec/fixtures/rails_users_app/app/models/activerecord/user.rb +18 -0
  159. data/spec/fixtures/rails_users_app/app/models/concerns/.keep +0 -0
  160. data/spec/fixtures/rails_users_app/app/models/sequel/user.rb +25 -0
  161. data/spec/fixtures/rails_users_app/app/views/layouts/application.html.haml +7 -0
  162. data/spec/fixtures/rails_users_app/app/views/users/index.html.haml +7 -0
  163. data/spec/fixtures/rails_users_app/appmap.yml +3 -0
  164. data/spec/fixtures/rails_users_app/bin/appmap +29 -0
  165. data/spec/fixtures/rails_users_app/bin/byebug +29 -0
  166. data/spec/fixtures/rails_users_app/bin/gli +29 -0
  167. data/spec/fixtures/rails_users_app/bin/htmldiff +29 -0
  168. data/spec/fixtures/rails_users_app/bin/ldiff +29 -0
  169. data/spec/fixtures/rails_users_app/bin/nokogiri +29 -0
  170. data/spec/fixtures/rails_users_app/bin/rackup +29 -0
  171. data/spec/fixtures/rails_users_app/bin/rails +4 -0
  172. data/spec/fixtures/rails_users_app/bin/rake +29 -0
  173. data/spec/fixtures/rails_users_app/bin/rspec +29 -0
  174. data/spec/fixtures/rails_users_app/bin/ruby-parse +29 -0
  175. data/spec/fixtures/rails_users_app/bin/ruby-rewrite +29 -0
  176. data/spec/fixtures/rails_users_app/bin/sequel +29 -0
  177. data/spec/fixtures/rails_users_app/bin/setup +25 -0
  178. data/spec/fixtures/rails_users_app/bin/sprockets +29 -0
  179. data/spec/fixtures/rails_users_app/bin/thor +29 -0
  180. data/spec/fixtures/rails_users_app/bin/update +25 -0
  181. data/spec/fixtures/rails_users_app/config.ru +5 -0
  182. data/spec/fixtures/rails_users_app/config/application.rb +51 -0
  183. data/spec/fixtures/rails_users_app/config/boot.rb +3 -0
  184. data/spec/fixtures/rails_users_app/config/credentials.yml.enc +1 -0
  185. data/spec/fixtures/rails_users_app/config/database.yml +18 -0
  186. data/spec/fixtures/rails_users_app/config/environment.rb +5 -0
  187. data/spec/fixtures/rails_users_app/config/environments/development.rb +40 -0
  188. data/spec/fixtures/rails_users_app/config/environments/production.rb +68 -0
  189. data/spec/fixtures/rails_users_app/config/environments/test.rb +36 -0
  190. data/spec/fixtures/rails_users_app/config/initializers/application_controller_renderer.rb +8 -0
  191. data/spec/fixtures/rails_users_app/config/initializers/backtrace_silencers.rb +7 -0
  192. data/spec/fixtures/rails_users_app/config/initializers/cors.rb +16 -0
  193. data/spec/fixtures/rails_users_app/config/initializers/filter_parameter_logging.rb +4 -0
  194. data/spec/fixtures/rails_users_app/config/initializers/inflections.rb +16 -0
  195. data/spec/fixtures/rails_users_app/config/initializers/mime_types.rb +4 -0
  196. data/spec/fixtures/rails_users_app/config/initializers/record_button.rb +3 -0
  197. data/spec/fixtures/rails_users_app/config/initializers/wrap_parameters.rb +9 -0
  198. data/spec/fixtures/rails_users_app/config/locales/en.yml +33 -0
  199. data/spec/fixtures/rails_users_app/config/routes.rb +11 -0
  200. data/spec/fixtures/rails_users_app/create_app +27 -0
  201. data/spec/fixtures/rails_users_app/db/migrate/20190728211408_create_users.rb +9 -0
  202. data/spec/fixtures/rails_users_app/db/schema.rb +23 -0
  203. data/spec/fixtures/rails_users_app/docker-compose.yml +28 -0
  204. data/spec/fixtures/rails_users_app/features/api_users.feature +13 -0
  205. data/spec/fixtures/rails_users_app/features/support/env.rb +4 -0
  206. data/spec/fixtures/rails_users_app/features/support/hooks.rb +11 -0
  207. data/spec/fixtures/rails_users_app/features/support/steps.rb +18 -0
  208. data/spec/fixtures/rails_users_app/lib/tasks/.keep +0 -0
  209. data/spec/fixtures/rails_users_app/log/.keep +0 -0
  210. data/spec/fixtures/rails_users_app/public/robots.txt +1 -0
  211. data/spec/fixtures/rails_users_app/spec/controllers/users_controller_api_spec.rb +29 -0
  212. data/spec/fixtures/rails_users_app/spec/models/user_spec.rb +39 -0
  213. data/spec/fixtures/rails_users_app/spec/rails_helper.rb +66 -0
  214. data/spec/fixtures/rails_users_app/spec/spec_helper.rb +96 -0
  215. data/spec/fixtures/rails_users_app/users_app/.gitignore +20 -0
  216. data/spec/hook_spec.rb +576 -0
  217. data/spec/rails_spec_helper.rb +60 -0
  218. data/spec/railtie_spec.rb +44 -0
  219. data/spec/record_sql_rails4_pg_spec.rb +76 -0
  220. data/spec/record_sql_rails_pg_spec.rb +68 -0
  221. data/spec/remote_recording_spec.rb +117 -0
  222. data/spec/rspec_feature_metadata_spec.rb +32 -0
  223. data/spec/spec_helper.rb +15 -0
  224. data/spec/util_spec.rb +21 -0
  225. data/test/cli_test.rb +116 -0
  226. data/test/cucumber_test.rb +72 -0
  227. data/test/fixtures/cli_record_test/appmap.yml +3 -0
  228. data/test/fixtures/cli_record_test/lib/cli_record_test/main.rb +9 -0
  229. data/test/fixtures/cucumber4_recorder/Gemfile +5 -0
  230. data/test/fixtures/cucumber4_recorder/appmap.yml +3 -0
  231. data/test/fixtures/cucumber4_recorder/features/say_hello.feature +5 -0
  232. data/test/fixtures/cucumber4_recorder/features/support/env.rb +5 -0
  233. data/test/fixtures/cucumber4_recorder/features/support/hooks.rb +11 -0
  234. data/test/fixtures/cucumber4_recorder/features/support/steps.rb +9 -0
  235. data/test/fixtures/cucumber4_recorder/lib/hello.rb +7 -0
  236. data/test/fixtures/cucumber_recorder/Gemfile +5 -0
  237. data/test/fixtures/cucumber_recorder/appmap.yml +3 -0
  238. data/test/fixtures/cucumber_recorder/features/say_hello.feature +5 -0
  239. data/test/fixtures/cucumber_recorder/features/support/env.rb +5 -0
  240. data/test/fixtures/cucumber_recorder/features/support/hooks.rb +11 -0
  241. data/test/fixtures/cucumber_recorder/features/support/steps.rb +9 -0
  242. data/test/fixtures/cucumber_recorder/lib/hello.rb +7 -0
  243. data/test/fixtures/minitest_recorder/Gemfile +5 -0
  244. data/test/fixtures/minitest_recorder/appmap.yml +3 -0
  245. data/test/fixtures/minitest_recorder/lib/hello.rb +5 -0
  246. data/test/fixtures/minitest_recorder/test/hello_test.rb +12 -0
  247. data/test/fixtures/process_recorder/appmap.yml +3 -0
  248. data/test/fixtures/process_recorder/hello.rb +9 -0
  249. data/test/fixtures/rspec_recorder/Gemfile +5 -0
  250. data/test/fixtures/rspec_recorder/appmap.yml +3 -0
  251. data/test/fixtures/rspec_recorder/lib/hello.rb +5 -0
  252. data/test/fixtures/rspec_recorder/spec/decorated_hello_spec.rb +21 -0
  253. data/test/fixtures/rspec_recorder/spec/labeled_hello_spec.rb +9 -0
  254. data/test/fixtures/rspec_recorder/spec/plain_hello_spec.rb +9 -0
  255. data/test/minitest_test.rb +38 -0
  256. data/test/record_process_test.rb +35 -0
  257. data/test/rspec_test.rb +82 -0
  258. data/test/test_helper.rb +4 -0
  259. metadata +525 -0
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AppMap
4
+ # Railtie connects the AppMap recorder to Rails-specific features.
5
+ class Railtie < ::Rails::Railtie
6
+ config.appmap = ActiveSupport::OrderedOptions.new
7
+
8
+ initializer 'appmap.init' do |_| # params: app
9
+ require 'appmap'
10
+ end
11
+
12
+ # appmap.subscribe subscribes to ActiveSupport Notifications so that they can be recorded as
13
+ # AppMap events.
14
+ initializer 'appmap.subscribe', after: 'appmap.init' do |_| # params: app
15
+ require 'appmap/rails/sql_handler'
16
+ require 'appmap/rails/action_handler'
17
+ ActiveSupport::Notifications.subscribe 'sql.sequel', AppMap::Rails::SQLHandler.new
18
+ ActiveSupport::Notifications.subscribe 'sql.active_record', AppMap::Rails::SQLHandler.new
19
+ ActiveSupport::Notifications.subscribe \
20
+ 'start_processing.action_controller', AppMap::Rails::ActionHandler::HTTPServerRequest.new
21
+ ActiveSupport::Notifications.subscribe \
22
+ 'process_action.action_controller', AppMap::Rails::ActionHandler::HTTPServerResponse.new
23
+ end
24
+
25
+ # appmap.trace begins recording an AppMap trace and writes it to appmap.json.
26
+ # This behavior is only activated if the configuration setting app.config.appmap.enabled
27
+ # is truthy.
28
+ initializer 'appmap.trace', after: 'appmap.subscribe' do |app|
29
+ lambda do
30
+ return unless app.config.appmap.enabled
31
+
32
+ require 'appmap/command/record'
33
+ require 'json'
34
+ AppMap::Command::Record.new(AppMap.configuration).perform do |version, metadata, class_map, events|
35
+ appmap = JSON.generate \
36
+ version: version,
37
+ metadata: metadata,
38
+ classMap: class_map,
39
+ events: events
40
+ File.open('appmap.json', 'w').write(appmap)
41
+ end
42
+ end.call
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'appmap'
4
+ require 'json'
5
+
6
+ tracer = AppMap.tracing.trace
7
+
8
+ at_exit do
9
+ AppMap.tracing.delete(tracer)
10
+
11
+ events = [].tap do |event_list|
12
+ event_list << tracer.next_event.to_h while tracer.event?
13
+ end
14
+
15
+ metadata = AppMap.detect_metadata
16
+ metadata[:recorder] = {
17
+ name: 'record_process'
18
+ }
19
+
20
+ appmap = {
21
+ 'version' => AppMap::APPMAP_FORMAT_VERSION,
22
+ 'metadata' => metadata,
23
+ 'classMap' => AppMap.class_map(tracer.event_methods),
24
+ 'events' => events
25
+ }
26
+ File.write 'appmap.json', JSON.generate(appmap)
27
+ end
@@ -0,0 +1,301 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'appmap/util'
4
+
5
+ module AppMap
6
+ # Integration of AppMap with RSpec. When enabled with APPMAP=true, the AppMap tracer will
7
+ # be activated around each scenario which has the metadata key `:appmap`.
8
+ module RSpec
9
+ APPMAP_OUTPUT_DIR = 'tmp/appmap/rspec'
10
+ LOG = false
11
+
12
+ def self.metadata
13
+ AppMap.detect_metadata
14
+ end
15
+
16
+ module FeatureAnnotations
17
+ def feature
18
+ return nil unless annotations
19
+
20
+ annotations[:feature]
21
+ end
22
+
23
+ def labels
24
+ labels = metadata[:appmap]
25
+ if labels.is_a?(Array)
26
+ labels
27
+ elsif labels.is_a?(String) || labels.is_a?(Symbol)
28
+ [ labels ]
29
+ else
30
+ []
31
+ end
32
+ end
33
+
34
+ def feature_group
35
+ return nil unless annotations
36
+
37
+ annotations[:feature_group]
38
+ end
39
+
40
+ def annotations
41
+ metadata.tap do |md|
42
+ description_args_hashes.each do |h|
43
+ md.merge! h
44
+ end
45
+ end
46
+ end
47
+
48
+ protected
49
+
50
+ def metadata
51
+ return {} unless example_obj.respond_to?(:metadata)
52
+
53
+ example_obj.metadata
54
+ end
55
+
56
+ def description_args_hashes
57
+ return [] unless example_obj.respond_to?(:metadata)
58
+
59
+ (example_obj.metadata[:description_args] || []).select { |arg| arg.is_a?(Hash) }
60
+ end
61
+ end
62
+
63
+ # ScopeExample and ScopeExampleGroup is a way to handle the weird way that RSpec
64
+ # stores the nested example names.
65
+ ScopeExample = Struct.new(:example) do
66
+ include FeatureAnnotations
67
+
68
+ alias_method :example_obj, :example
69
+
70
+ def description?
71
+ true
72
+ end
73
+
74
+ def description
75
+ example.description
76
+ end
77
+
78
+ def parent
79
+ ScopeExampleGroup.new(example.example_group)
80
+ end
81
+ end
82
+
83
+ # As you can see here, the way that RSpec stores the example description and
84
+ # represents the example group hierarchy is pretty weird.
85
+ ScopeExampleGroup = Struct.new(:example_group) do
86
+ include FeatureAnnotations
87
+
88
+ alias_method :example_obj, :example_group
89
+
90
+ def description_args
91
+ # Don't stringify any hashes that RSpec considers part of the example group description.
92
+ example_group.metadata[:description_args].reject { |arg| arg.is_a?(Hash) }
93
+ end
94
+
95
+ def description?
96
+ return true if example_group.respond_to?(:described_class) && example_group.described_class
97
+
98
+ return true if example_group.respond_to?(:description) && !description_args.empty?
99
+
100
+ false
101
+ end
102
+
103
+ def description
104
+ description? ? description_args.join(' ') : nil
105
+ end
106
+
107
+ def parent
108
+ # An example group always has a parent; but it might be 'self'...
109
+
110
+ # DEPRECATION WARNING: `Module#parent` has been renamed to `module_parent`. `parent` is deprecated and will be
111
+ # removed in Rails 6.1. (called from parent at /Users/kgilpin/source/appland/appmap-ruby/lib/appmap/rspec.rb:110)
112
+ example_group_parent = \
113
+ if example_group.respond_to?(:module_parent)
114
+ example_group.module_parent
115
+ else
116
+ example_group.parent
117
+ end
118
+
119
+ example_group_parent != example_group ? ScopeExampleGroup.new(example_group_parent) : nil
120
+ end
121
+ end
122
+
123
+ Recording = Struct.new(:example) do
124
+ def initialize(example)
125
+ super
126
+
127
+ warn "Starting recording of example #{example}" if AppMap::RSpec::LOG
128
+ @trace = AppMap.tracing.trace
129
+ end
130
+
131
+ def finish
132
+ warn "Finishing recording of example #{example}" if AppMap::RSpec::LOG
133
+
134
+ events = []
135
+ AppMap.tracing.delete @trace
136
+
137
+ events << @trace.next_event.to_h while @trace.event?
138
+
139
+ AppMap::RSpec.add_event_methods @trace.event_methods
140
+
141
+ class_map = AppMap.class_map(@trace.event_methods)
142
+
143
+ description = []
144
+ scope = ScopeExample.new(example)
145
+ feature_group = feature = nil
146
+
147
+ labels = []
148
+ while scope
149
+ labels += scope.labels
150
+ description << scope.description
151
+ feature ||= scope.feature
152
+ feature_group ||= scope.feature_group
153
+ scope = scope.parent
154
+ end
155
+
156
+ labels = labels.map(&:to_s).map(&:strip).reject(&:blank?).map(&:downcase).uniq
157
+ description.reject!(&:nil?).reject(&:blank?)
158
+ default_description = description.last
159
+ description.reverse!
160
+
161
+ normalize = lambda do |desc|
162
+ desc.gsub('it should behave like', '')
163
+ .gsub(/Controller$/, '')
164
+ .gsub(/\s+/, ' ')
165
+ .strip
166
+ end
167
+
168
+ full_description = normalize.call(description.join(' '))
169
+
170
+ compute_feature_name = lambda do
171
+ return 'unknown' if description.empty?
172
+
173
+ feature_description = description.dup
174
+ num_tokens = [2, feature_description.length - 1].min
175
+ feature_description[0...num_tokens].map(&:strip).join(' ')
176
+ end
177
+
178
+ feature_group ||= normalize.call(default_description).underscore.gsub('/', '_').humanize
179
+ feature_name = feature || compute_feature_name.call if feature_group
180
+ feature_name = normalize.call(feature_name) if feature_name
181
+
182
+ AppMap::RSpec.save full_description,
183
+ class_map,
184
+ events: events,
185
+ feature_name: feature_name,
186
+ feature_group_name: feature_group,
187
+ labels: labels.blank? ? nil : labels
188
+ end
189
+ end
190
+
191
+ @recordings_by_example = {}
192
+ @event_methods = Set.new
193
+
194
+ class << self
195
+ def init
196
+ warn 'Configuring AppMap recorder for RSpec'
197
+
198
+ FileUtils.mkdir_p APPMAP_OUTPUT_DIR
199
+ end
200
+
201
+ def begin_spec(example)
202
+ @recordings_by_example[example.object_id] = Recording.new(example)
203
+ end
204
+
205
+ def end_spec(example)
206
+ recording = @recordings_by_example.delete(example.object_id)
207
+ return warn "No recording found for #{example}" unless recording
208
+
209
+ recording.finish
210
+ end
211
+
212
+ def config
213
+ @config or raise "AppMap is not configured"
214
+ end
215
+
216
+ def add_event_methods(event_methods)
217
+ @event_methods += event_methods
218
+ end
219
+
220
+ def save(example_name, class_map, events: nil, feature_name: nil, feature_group_name: nil, labels: nil)
221
+ metadata = AppMap::RSpec.metadata.tap do |m|
222
+ m[:name] = example_name
223
+ m[:app] = AppMap.configuration.name
224
+ m[:feature] = feature_name if feature_name
225
+ m[:feature_group] = feature_group_name if feature_group_name
226
+ m[:labels] = labels if labels
227
+ m[:frameworks] ||= []
228
+ m[:frameworks] << {
229
+ name: 'rspec',
230
+ version: Gem.loaded_specs['rspec-core']&.version&.to_s
231
+ }
232
+ m[:recorder] = {
233
+ name: 'rspec'
234
+ }
235
+ end
236
+
237
+ appmap = {
238
+ version: AppMap::APPMAP_FORMAT_VERSION,
239
+ metadata: metadata,
240
+ classMap: class_map,
241
+ events: events
242
+ }.compact
243
+ fname = AppMap::Util.scenario_filename(example_name)
244
+
245
+ File.write(File.join(APPMAP_OUTPUT_DIR, fname), JSON.generate(appmap))
246
+ end
247
+
248
+ def print_inventory
249
+ class_map = AppMap.class_map(@event_methods)
250
+ save 'Inventory', class_map, labels: %w[inventory]
251
+ end
252
+
253
+ def enabled?
254
+ ENV['APPMAP'] == 'true'
255
+ end
256
+
257
+ def run
258
+ init
259
+ at_exit do
260
+ print_inventory
261
+ end
262
+ end
263
+ end
264
+ end
265
+ end
266
+
267
+ if AppMap::RSpec.enabled?
268
+ require 'appmap'
269
+ require 'active_support/inflector/transliterate'
270
+ require 'rspec/core'
271
+ require 'rspec/core/example'
272
+
273
+ module RSpec
274
+ module Core
275
+ class Example
276
+ class << self
277
+ def wrap_example_block(example, fn)
278
+ proc do
279
+ AppMap::RSpec.begin_spec example
280
+ begin
281
+ instance_exec(&fn)
282
+ ensure
283
+ AppMap::RSpec.end_spec example
284
+ end
285
+ end
286
+ end
287
+ end
288
+
289
+ def self.new(*arguments, &block)
290
+ warn "Wrapping example_block for #{name}" if AppMap::RSpec::LOG
291
+ allocate.tap do |obj|
292
+ arguments[arguments.length - 1] = wrap_example_block(obj, arguments.last) if arguments.last.is_a?(Proc)
293
+ obj.send :initialize, *arguments, &block
294
+ end
295
+ end
296
+ end
297
+ end
298
+ end
299
+
300
+ AppMap::RSpec.run
301
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AppMap
4
+ module Trace
5
+ class ScopedMethod < SimpleDelegator
6
+ attr_reader :defined_class, :static
7
+
8
+ def initialize(defined_class, method, static)
9
+ @defined_class = defined_class
10
+ @static = static
11
+ super(method)
12
+ end
13
+ end
14
+
15
+ class Tracing
16
+ def initialize
17
+ @Tracing = []
18
+ end
19
+
20
+ def empty?
21
+ @Tracing.empty?
22
+ end
23
+
24
+ def trace(enable: true)
25
+ Tracer.new.tap do |tracer|
26
+ @Tracing << tracer
27
+ tracer.enable if enable
28
+ end
29
+ end
30
+
31
+ def enabled?
32
+ @Tracing.any?(&:enabled?)
33
+ end
34
+
35
+ def record_event(event, defined_class: nil, method: nil)
36
+ @Tracing.each do |tracer|
37
+ tracer.record_event(event, defined_class: defined_class, method: method)
38
+ end
39
+ end
40
+
41
+ def delete(tracer)
42
+ return unless @Tracing.member?(tracer)
43
+
44
+ @Tracing.delete(tracer)
45
+ tracer.disable
46
+ end
47
+ end
48
+ end
49
+
50
+ class Tracer
51
+ # Records the events which happen in a program.
52
+ def initialize
53
+ @events = []
54
+ @methods = Set.new
55
+ @enabled = false
56
+ end
57
+
58
+ def enable
59
+ @enabled = true
60
+ end
61
+
62
+ def enabled?
63
+ @enabled
64
+ end
65
+
66
+ # Private function. Use AppMap.tracing#delete.
67
+ def disable # :nodoc:
68
+ @enabled = false
69
+ end
70
+
71
+ # Record a program execution event.
72
+ #
73
+ # The event should be one of the MethodEvent subclasses.
74
+ def record_event(event, defined_class: nil, method: nil)
75
+ return unless @enabled
76
+
77
+ @events << event
78
+ @methods << Trace::ScopedMethod.new(defined_class, method, event.static) if (defined_class && method && event.event == :call)
79
+ end
80
+
81
+ # Gets a unique list of the methods that were invoked by the program.
82
+ def event_methods
83
+ @methods.to_a
84
+ end
85
+
86
+ # Whether there is an event available for processing.
87
+ def event?
88
+ !@events.empty?
89
+ end
90
+
91
+ # Gets the next available event, if any.
92
+ def next_event
93
+ @events.shift
94
+ end
95
+ end
96
+ end