appmap 0.31.0

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.
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