appmap 0.41.0 → 0.43.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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +28 -0
  3. data/README.md +32 -6
  4. data/lib/appmap.rb +5 -0
  5. data/lib/appmap/command/record.rb +1 -1
  6. data/lib/appmap/config.rb +16 -12
  7. data/lib/appmap/event.rb +32 -4
  8. data/lib/appmap/hook.rb +17 -2
  9. data/lib/appmap/hook/method.rb +1 -1
  10. data/lib/appmap/middleware/remote_recording.rb +1 -1
  11. data/lib/appmap/minitest.rb +17 -14
  12. data/lib/appmap/rails/request_handler.rb +41 -10
  13. data/lib/appmap/rspec.rb +13 -78
  14. data/lib/appmap/version.rb +1 -1
  15. data/spec/abstract_controller_base_spec.rb +67 -22
  16. data/spec/fixtures/rails5_users_app/Gemfile +7 -3
  17. data/spec/fixtures/rails5_users_app/app/controllers/api/users_controller.rb +2 -0
  18. data/spec/fixtures/rails5_users_app/app/controllers/users_controller.rb +9 -1
  19. data/spec/fixtures/rails5_users_app/config/application.rb +2 -0
  20. data/spec/fixtures/rails5_users_app/create_app +8 -2
  21. data/spec/fixtures/rails5_users_app/docker-compose.yml +3 -0
  22. data/spec/fixtures/rails5_users_app/spec/controllers/users_controller_api_spec.rb +16 -3
  23. data/spec/fixtures/rails5_users_app/spec/controllers/users_controller_spec.rb +2 -2
  24. data/spec/fixtures/rails5_users_app/spec/models/user_spec.rb +2 -12
  25. data/spec/fixtures/rails5_users_app/spec/rails_helper.rb +3 -9
  26. data/spec/fixtures/rails6_users_app/Gemfile +5 -4
  27. data/spec/fixtures/rails6_users_app/app/controllers/api/users_controller.rb +1 -0
  28. data/spec/fixtures/rails6_users_app/app/controllers/users_controller.rb +9 -1
  29. data/spec/fixtures/rails6_users_app/config/application.rb +2 -0
  30. data/spec/fixtures/rails6_users_app/create_app +8 -2
  31. data/spec/fixtures/rails6_users_app/docker-compose.yml +3 -0
  32. data/spec/fixtures/rails6_users_app/spec/controllers/users_controller_api_spec.rb +16 -3
  33. data/spec/fixtures/rails6_users_app/spec/controllers/users_controller_spec.rb +2 -2
  34. data/spec/fixtures/rails6_users_app/spec/models/user_spec.rb +2 -12
  35. data/spec/fixtures/rails6_users_app/spec/rails_helper.rb +3 -9
  36. data/spec/record_sql_rails_pg_spec.rb +1 -1
  37. data/spec/spec_helper.rb +6 -0
  38. data/test/fixtures/gem_test/appmap.yml +1 -1
  39. data/test/fixtures/gem_test/test/parser_test.rb +12 -0
  40. data/test/fixtures/rspec_recorder/spec/decorated_hello_spec.rb +3 -3
  41. data/test/fixtures/rspec_recorder/spec/plain_hello_spec.rb +1 -1
  42. data/test/gem_test.rb +4 -4
  43. data/test/minitest_test.rb +1 -2
  44. data/test/rspec_test.rb +1 -7
  45. metadata +3 -4
  46. data/spec/rspec_feature_metadata_spec.rb +0 -31
  47. data/test/fixtures/gem_test/test/to_param_test.rb +0 -14
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d747c0e65dc1efb45769ae9118a7b97015b0378d79e20eec855e74c20b622af2
4
- data.tar.gz: dcc3b91f32246aaefd02fbcf47672bfdf13acf6cadb715fbc5ff0040dbcfbf72
3
+ metadata.gz: 4007f0eb67a3a25088e8b3677de8d0388fd1bfec4b326c43a51c503f640325b5
4
+ data.tar.gz: 1b593251a7a072b7a1483e7af172b9157d6913208658fb324630bc52b0b4fe73
5
5
  SHA512:
6
- metadata.gz: 5f82a6a0cf8075537b0835e783779c827c5afed81e83e242bc97cbfd5b6ef0320fe352235436f57009a63db321223b9f7f82e8bcdced558d9b935a2ce4e4cbf3
7
- data.tar.gz: aaa4676d24b6bb1bcbd34fb7f0e3a0244d005c36dd89782b774b342b7e30e1c9637795d0d492522b2a5e525de413e3adb9da54bd5ace2699373ffdfadf3a8157
6
+ metadata.gz: 6af64a17bf69655ae8bdc9b4b67b7e2c9bd1b87d07a23e266dcabd2a927311224a4b27a77355674559828a08ae1df2e7231e2a68a2734c0f620de8efc48ec6d4
7
+ data.tar.gz: 7725cb0c79d8be13fddf7a3b688113f4fd31941c4f08f64ee44d4fbb3f5255dd3a308b08ef578413938052ee3b9eeecedc3e58bc092b12e23bfa872eac119eb7
data/CHANGELOG.md CHANGED
@@ -1,3 +1,31 @@
1
+ # v0.43.0
2
+
3
+ * Record `name` and `class` of each entry in Hash-like parameters, messages, and return values.
4
+ * Record client-sent headers in HTTP server request and response.
5
+ * Record HTTP server request `mime_type`.
6
+ * Record HTTP server request `authorization`.
7
+
8
+ # v0.42.1
9
+
10
+ * Add missing require `set`.
11
+ * Check `cls.respond_to?(:singleton_class)`, since it oddly, may not.
12
+
13
+ # v0.42.0
14
+
15
+ * Remove `feature_group` and `feature` metadata from minitest and RSpec AppMaps.
16
+ * Add `metadata.source_location`.
17
+
18
+ # v0.41.2
19
+
20
+ * Don't rely on `gemspec.source_paths` to list all the source locations in a gem. Hook any code that's loaded
21
+ from inside the `gem_dir`.
22
+
23
+ # v0.41.1
24
+
25
+ * Make best effort to ensure that class name is not `null` in the appmap.json.
26
+ * Don't try and instrument gems which are a dependency of the this gem.
27
+ * Fix a nil exception when applying the exclude list to builtins.
28
+
1
29
  # v0.41.0
2
30
 
3
31
  * Adjust some label names to match `provider.*`, `format.*`.
data/README.md CHANGED
@@ -9,6 +9,7 @@
9
9
  - [Minitest](#minitest)
10
10
  - [Cucumber](#cucumber)
11
11
  - [Remote recording](#remote-recording)
12
+ - [Server process recording](#server-process-recording)
12
13
  - [AppMap for VSCode](#appmap-for-vscode)
13
14
  - [Uploading AppMaps](#uploading-appmaps)
14
15
  - [Development](#development)
@@ -81,6 +82,22 @@ If you are using Ruby on Rails, require the railtie after Rails is loaded.
81
82
  require 'appmap/railtie' if defined?(AppMap).
82
83
  ```
83
84
 
85
+ **application.rb**
86
+
87
+ Add this line to *application.rb*, to enable server recording with `APPMAP_RECORD=true`:
88
+
89
+ ```ruby
90
+ module MyApp
91
+ class Application < Rails::Application
92
+ ...
93
+
94
+ config.appmap.enabled = true if ENV['APPMAP_RECORD']
95
+
96
+ ...
97
+ end
98
+ end
99
+ ```
100
+
84
101
  # Configuration
85
102
 
86
103
  When you run your program, the `appmap` gem reads configuration settings from `appmap.yml`. Here's a sample configuration
@@ -306,6 +323,12 @@ $ bundle exec rails server
306
323
 
307
324
  6. Open the AppLand browser extension and push `Stop`. The recording will be transferred to the AppLand website and opened in your browser.
308
325
 
326
+ ## Server process recording
327
+
328
+ Run your Rails server with `APPMAP_RECORD=true`. When the server exits, an *appmap.json* file will be written to the project directory. This is a great way to start the server, interact with your app as a user (or through it's API), and then view an AppMap of everything that happened.
329
+
330
+ Be sure and set `WEB_CONCURRENCY=1`, if you are using a webserver that can run multiple processes. You only want there to be one process while you are recording, otherwise they will both try and write *appmap.json* and one of them will clobber the other.
331
+
309
332
  # AppMap for VSCode
310
333
 
311
334
  The [AppMap extension for VSCode](https://marketplace.visualstudio.com/items?itemName=appland.appmap) is a great way to onboard developers to new code, and troubleshoot hard-to-understand bugs with visuals.
@@ -317,7 +340,7 @@ The [AppMap extension for VSCode](https://marketplace.visualstudio.com/items?ite
317
340
  For instructions on uploading, see the documentation of the [AppLand CLI](https://github.com/applandinc/appland-cli).
318
341
 
319
342
  # Development
320
- [![Build Status](https://travis-ci.org/applandinc/appmap-ruby.svg?branch=master)](https://travis-ci.org/applandinc/appmap-ruby)
343
+ [![Build Status](https://travis-ci.com/applandinc/appmap-ruby.svg?branch=master)](https://travis-ci.com/applandinc/appmap-ruby)
321
344
 
322
345
  ## Running tests
323
346
 
@@ -346,7 +369,7 @@ The fixture apps in `test/fixtures` are plain Ruby projects that exercise the ba
346
369
 
347
370
  ### `spec/fixtures`
348
371
 
349
- The fixture apps in `spec/fixtures` are simple Rack, Rails4, and Rails5 apps.
372
+ The fixture apps in `spec/fixtures` are simple Rack, Rails5, and Rails6 apps.
350
373
  You can use them to interactively develop and test the recording features of the `appmap` gem.
351
374
  These fixture apps are more sophisticated than `test/fixtures`, because they include additional
352
375
  resources such as a PostgreSQL database.
@@ -372,11 +395,15 @@ $ docker-compose run --rm app ./create_app
372
395
  Now you can start a development container.
373
396
 
374
397
  ```sh-session
375
- $ docker-compose run --rm -v $PWD/../../..:/src/appmap-ruby app bash
398
+ $ docker-compose run --rm -v $PWD:/app -v $PWD/../../..:/src/appmap-ruby app bash
376
399
  Starting rails_users_app_pg_1 ... done
377
- root@6fab5f89125f:/app# cd /src/app
400
+ root@6fab5f89125f:/app# cd /src/appmap-ruby
401
+ root@6fab5f89125f:/src/appmap-ruby# rm ext/appmap/*.so ext/appmap/*.o
402
+ root@6fab5f89125f:/src/appmap-ruby# bundle
403
+ root@6fab5f89125f:/src/appmap-ruby# bundle exec rake compile
404
+ root@6fab5f89125f:/src/appmap-ruby# cd /src/app
378
405
  root@6fab5f89125f:/src/app# bundle config local.appmap /src/appmap-ruby
379
- root@6fab5f89125f:/src/app# bundle update appmap
406
+ root@6fab5f89125f:/src/app# bundle
380
407
  ```
381
408
 
382
409
  At this point, the bundle is built with the `appmap` gem located in `/src/appmap`, which is volume-mounted from the host.
@@ -391,4 +418,3 @@ Configuring AppMap from path appmap.yml
391
418
  Finished in 0.07357 seconds (files took 2.1 seconds to load)
392
419
  4 examples, 0 failures
393
420
  ```
394
-
data/lib/appmap.rb CHANGED
@@ -50,6 +50,11 @@ module AppMap
50
50
  end
51
51
  end
52
52
 
53
+ # Whether to include source and comments in all class maps.
54
+ def include_source?
55
+ ENV['APPMAP_SOURCE'] == 'true'
56
+ end
57
+
53
58
  # Used to start tracing, stop tracing, and record events.
54
59
  def tracing
55
60
  @tracing ||= Trace::Tracing.new
@@ -27,7 +27,7 @@ module AppMap
27
27
  event_thread.join
28
28
  yield AppMap::APPMAP_FORMAT_VERSION,
29
29
  AppMap.detect_metadata,
30
- AppMap.class_map(tracer.event_methods),
30
+ AppMap.class_map(tracer.event_methods, include_source: AppMap.include_source?),
31
31
  events
32
32
  end
33
33
 
data/lib/appmap/config.rb CHANGED
@@ -16,20 +16,20 @@ module AppMap
16
16
  end
17
17
 
18
18
  def build_from_gem(gem, shallow: true, package_name: nil, exclude: [], labels: [])
19
- gem_paths(gem).map do |gem_path|
20
- Package.new(gem_path, gem, package_name, exclude, labels, shallow)
19
+ if %w[method_source activesupport].member?(gem)
20
+ warn "WARNING: #{gem} cannot be AppMapped because it is a dependency of the appmap gem"
21
+ return
21
22
  end
23
+ Package.new(gem_path(gem), gem, package_name, exclude, labels, shallow)
22
24
  end
23
25
 
24
26
  private_class_method :new
25
27
 
26
28
  protected
27
29
 
28
- def gem_paths(gem)
30
+ def gem_path(gem)
29
31
  gemspec = Gem.loaded_specs[gem] or raise "Gem #{gem.inspect} not found"
30
- gemspec.source_paths.map do |path|
31
- File.join(gemspec.gem_dir, path)
32
- end
32
+ gemspec.gem_dir
33
33
  end
34
34
  end
35
35
 
@@ -57,8 +57,12 @@ module AppMap
57
57
  # Methods that should always be hooked, with their containing
58
58
  # package and labels that should be applied to them.
59
59
  HOOKED_METHODS = {
60
- 'ActiveSupport::SecurityUtils' => Hook.new(:secure_compare, Package.build_from_path('active_support', package_name: 'active_support', labels: %w[provider.secure_compare])),
61
- 'ActionView::Renderer' => Hook.new(:render, Package.build_from_path('action_view', package_name: 'action_view', labels: %w[mvc.view]))
60
+ 'ActiveSupport::SecurityUtils' => Hook.new(:secure_compare, Package.build_from_path('active_support', labels: %w[provider.secure_compare])),
61
+ 'ActionView::Renderer' => Hook.new(:render, Package.build_from_path('action_view', labels: %w[mvc.view])),
62
+ 'ActionDispatch::Cookies::CookieJar' => Hook.new(%i[[]= clear update delete recycle], Package.build_from_path('action_pack', labels: %w[provider.http.cookie])),
63
+ 'ActionDispatch::Cookies::EncryptedCookieJar' => Hook.new(%i[[]=], Package.build_from_path('action_pack', labels: %w[provider.http.cookie crypto])),
64
+ 'CanCan::ControllerAdditions' => Hook.new(%i[authorize! can? cannot?], Package.build_from_path('cancancan', labels: %w[provider.authorization])),
65
+ 'CanCan::Ability' => Hook.new(%i[authorize!], Package.build_from_path('cancancan', labels: %w[provider.authorization])),
62
66
  }.freeze
63
67
 
64
68
  BUILTIN_METHODS = {
@@ -71,7 +75,7 @@ module AppMap
71
75
  'Net::SMTP' => Hook.new(:send, Package.build_from_path('net/smtp', package_name: 'net/smtp', labels: %w[protocol.smtp protocol.email io])),
72
76
  'Net::POP3' => Hook.new(:mails, Package.build_from_path('net/pop3', package_name: 'net/pop', labels: %w[protocol.pop protocol.email io])),
73
77
  'Net::IMAP' => Hook.new(:send_command, Package.build_from_path('net/imap', package_name: 'net/imap', labels: %w[protocol.imap protocol.email io])),
74
- 'Marshal' => Hook.new(%i[dump load], Package.build_from_path('marshal', labels: %w[format.marshal provider.serialization marshal])),
78
+ 'Marshal' => Hook.new(%i[dump load], Package.build_from_path('marshal', labels: %w[format.marshal provider.serialization])),
75
79
  'Psych' => Hook.new(%i[dump dump_stream load load_stream parse parse_stream], Package.build_from_path('yaml', package_name: 'psych', labels: %w[format.yaml provider.serialization])),
76
80
  'JSON::Ext::Parser' => Hook.new(:parse, Package.build_from_path('json', package_name: 'json', labels: %w[format.json provider.serialization])),
77
81
  'JSON::Ext::Generator::State' => Hook.new(:generate, Package.build_from_path('json', package_name: 'json', labels: %w[format.json provider.serialization])),
@@ -105,9 +109,9 @@ module AppMap
105
109
  shallow = true if shallow.nil?
106
110
  Package.build_from_gem(gem, exclude: package['exclude'] || [], shallow: shallow)
107
111
  else
108
- [ Package.build_from_path(path, exclude: package['exclude'] || [], shallow: package['shallow']) ]
112
+ Package.build_from_path(path, exclude: package['exclude'] || [], shallow: package['shallow'])
109
113
  end
110
- end.flatten
114
+ end.compact
111
115
  Config.new config_data['name'], packages, config_data['exclude'] || []
112
116
  end
113
117
  end
@@ -137,7 +141,7 @@ module AppMap
137
141
  return unless location_file
138
142
 
139
143
  location_file = location_file[Dir.pwd.length + 1..-1] if location_file.index(Dir.pwd) == 0
140
- packages.find do |pkg|
144
+ packages.select { |pkg| pkg.path }.find do |pkg|
141
145
  (location_file.index(pkg.path) == 0) &&
142
146
  !pkg.exclude.find { |p| location_file.index(p) }
143
147
  end
data/lib/appmap/event.rb CHANGED
@@ -36,8 +36,29 @@ module AppMap
36
36
  (value_string||'')[0...LIMIT].encode('utf-8', invalid: :replace, undef: :replace, replace: '_')
37
37
  end
38
38
 
39
+ def object_properties(hash_like)
40
+ hash = hash_like.to_h
41
+ hash.keys.map do |key|
42
+ {
43
+ name: key,
44
+ class: hash[key].class.name,
45
+ }
46
+ end
47
+ rescue
48
+ nil
49
+ end
50
+
39
51
  protected
40
52
 
53
+ # Heuristic for dynamically defined class whose name can be nil
54
+ def best_class_name(value)
55
+ value_cls = value.class
56
+ while value_cls.name.nil?
57
+ value_cls = value_cls.superclass
58
+ end
59
+ value_cls.name
60
+ end
61
+
41
62
  def custom_display_string(value)
42
63
  case value
43
64
  when File
@@ -70,6 +91,12 @@ module AppMap
70
91
  end
71
92
  end
72
93
  end
94
+
95
+ protected
96
+
97
+ def object_properties(hash_like)
98
+ self.class.object_properties(hash_like)
99
+ end
73
100
  end
74
101
 
75
102
  class MethodCall < MethodEvent
@@ -77,6 +104,7 @@ module AppMap
77
104
 
78
105
  class << self
79
106
  def build_from_invocation(mc = MethodCall.new, defined_class, method, receiver, arguments)
107
+ defined_class ||= 'Class'
80
108
  mc.tap do
81
109
  static = receiver.is_a?(Module)
82
110
  mc.defined_class = defined_class
@@ -110,14 +138,14 @@ module AppMap
110
138
  end
111
139
  {
112
140
  name: param_name,
113
- class: value.class.name,
141
+ class: best_class_name(value),
114
142
  object_id: value.__id__,
115
143
  value: display_string(value),
116
144
  kind: param_type
117
145
  }
118
146
  end
119
147
  mc.receiver = {
120
- class: receiver.class.name,
148
+ class: best_class_name(receiver),
121
149
  object_id: receiver.__id__,
122
150
  value: display_string(receiver)
123
151
  }
@@ -172,7 +200,7 @@ module AppMap
172
200
  mr.tap do |_|
173
201
  if return_value
174
202
  mr.return_value = {
175
- class: return_value.class.name,
203
+ class: best_class_name(return_value),
176
204
  value: display_string(return_value),
177
205
  object_id: return_value.__id__
178
206
  }
@@ -183,7 +211,7 @@ module AppMap
183
211
  while next_exception
184
212
  exception_backtrace = next_exception.backtrace_locations.try(:[], 0)
185
213
  exceptions << {
186
- class: next_exception.class.name,
214
+ class: best_class_name(next_exception),
187
215
  message: next_exception.message,
188
216
  object_id: next_exception.__id__,
189
217
  path: exception_backtrace&.path,
data/lib/appmap/hook.rb CHANGED
@@ -46,7 +46,16 @@ module AppMap
46
46
  cls = trace_point.self
47
47
 
48
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
49
+ # NoMethodError: private method `singleton_class' called for Rack::MiniProfiler:Class
50
+ class_methods = begin
51
+ if cls.respond_to?(:singleton_class)
52
+ cls.singleton_class.public_instance_methods(false) - instance_methods - OBJECT_STATIC_METHODS
53
+ else
54
+ []
55
+ end
56
+ rescue NameError
57
+ []
58
+ end
50
59
 
51
60
  hook = lambda do |hook_cls|
52
61
  lambda do |method_id|
@@ -80,7 +89,13 @@ module AppMap
80
89
  end
81
90
 
82
91
  instance_methods.each(&hook.(cls))
83
- class_methods.each(&hook.(cls.singleton_class))
92
+ # NoMethodError: private method `singleton_class' called for Rack::MiniProfiler:Class
93
+ begin
94
+ class_methods.each(&hook.(cls.singleton_class)) if cls.respond_to?(:singleton_class)
95
+ rescue NameError
96
+ # NameError:
97
+ # uninitialized constant Faraday::Connection
98
+ end
84
99
  end
85
100
 
86
101
  tp.enable(&block)
@@ -35,7 +35,7 @@ module AppMap
35
35
  else
36
36
  "#{hook_method.name} (class resolution deferred)"
37
37
  end
38
- warn "AppMap: Hooking " + msg
38
+ warn "AppMap: Hooking #{msg} at line #{(hook_method.source_location || []).join(':')}"
39
39
  end
40
40
 
41
41
  defined_class = @defined_class
@@ -67,7 +67,7 @@ module AppMap
67
67
 
68
68
  response = JSON.generate \
69
69
  version: AppMap::APPMAP_FORMAT_VERSION,
70
- classMap: AppMap.class_map(tracer.event_methods),
70
+ classMap: AppMap.class_map(tracer.event_methods, include_source: AppMap.include_source?),
71
71
  metadata: metadata,
72
72
  events: @events
73
73
 
@@ -7,20 +7,25 @@ module AppMap
7
7
  # be activated around each test.
8
8
  module Minitest
9
9
  APPMAP_OUTPUT_DIR = 'tmp/appmap/minitest'
10
- LOG = false
10
+ LOG = ( ENV['APPMAP_DEBUG'] == 'true' || ENV['DEBUG'] == 'true' )
11
11
 
12
12
  def self.metadata
13
13
  AppMap.detect_metadata
14
14
  end
15
15
 
16
- Recording = Struct.new(:test) do
17
- def initialize(test)
16
+ Recording = Struct.new(:test, :test_name) do
17
+ def initialize(test, test_name)
18
18
  super
19
19
 
20
- warn "Starting recording of test #{test.class}.#{test.name}" if AppMap::Minitest::LOG
20
+ warn "Starting recording of test #{test.class}.#{test.name}@#{source_location}" if AppMap::Minitest::LOG
21
21
  @trace = AppMap.tracing.trace
22
22
  end
23
23
 
24
+ def source_location
25
+ test.method(test_name).source_location.join(':')
26
+ end
27
+
28
+
24
29
  def finish
25
30
  warn "Finishing recording of test #{test.class}.#{test.name}" if AppMap::Minitest::LOG
26
31
 
@@ -31,7 +36,7 @@ module AppMap
31
36
 
32
37
  AppMap::Minitest.add_event_methods @trace.event_methods
33
38
 
34
- class_map = AppMap.class_map(@trace.event_methods)
39
+ class_map = AppMap.class_map(@trace.event_methods, include_source: AppMap.include_source?)
35
40
 
36
41
  feature_group = test.class.name.underscore.split('_')[0...-1].join('_').capitalize
37
42
  feature_name = test.name.split('_')[1..-1].join(' ')
@@ -39,9 +44,8 @@ module AppMap
39
44
 
40
45
  AppMap::Minitest.save scenario_name,
41
46
  class_map,
42
- events: events,
43
- feature_name: feature_name,
44
- feature_group_name: feature_group
47
+ source_location,
48
+ events: events
45
49
  end
46
50
  end
47
51
 
@@ -55,8 +59,8 @@ module AppMap
55
59
  FileUtils.mkdir_p APPMAP_OUTPUT_DIR
56
60
  end
57
61
 
58
- def begin_test(test)
59
- @recordings_by_test[test.object_id] = Recording.new(test)
62
+ def begin_test(test, name)
63
+ @recordings_by_test[test.object_id] = Recording.new(test, name)
60
64
  end
61
65
 
62
66
  def end_test(test)
@@ -74,12 +78,11 @@ module AppMap
74
78
  @event_methods += event_methods
75
79
  end
76
80
 
77
- def save(example_name, class_map, events: nil, feature_name: nil, feature_group_name: nil, labels: nil)
81
+ def save(example_name, class_map, source_location, events: nil, labels: nil)
78
82
  metadata = AppMap::Minitest.metadata.tap do |m|
79
83
  m[:name] = example_name
84
+ m[:source_location] = source_location
80
85
  m[:app] = AppMap.configuration.name
81
- m[:feature] = feature_name if feature_name
82
- m[:feature_group] = feature_group_name if feature_group_name
83
86
  m[:frameworks] ||= []
84
87
  m[:frameworks] << {
85
88
  name: 'minitest',
@@ -128,7 +131,7 @@ if AppMap::Minitest.enabled?
128
131
  alias run_without_hook run
129
132
 
130
133
  def run
131
- AppMap::Minitest.begin_test self
134
+ AppMap::Minitest.begin_test self, name
132
135
  begin
133
136
  run_without_hook
134
137
  ensure
@@ -6,15 +6,38 @@ require 'appmap/hook'
6
6
  module AppMap
7
7
  module Rails
8
8
  module RequestHandler
9
+ # Host and User-Agent will just introduce needless variation.
10
+ # Content-Type and Authorization get their own fields in the request.
11
+ IGNORE_HEADERS = %w[host user_agent content_type authorization].map(&:upcase).map {|h| "HTTP_#{h}"}.freeze
12
+
13
+ class << self
14
+ def selected_headers(env)
15
+ # Rack prepends HTTP_ to all client-sent headers.
16
+ matching_headers = env
17
+ .select { |k,v| k.start_with? 'HTTP_'}
18
+ .reject { |k,v| IGNORE_HEADERS.member?(k) }
19
+ .reject { |k,v| v.blank? }
20
+ .each_with_object({}) do |kv, memo|
21
+ key = kv[0].sub(/^HTTP_/, '').split('_').map(&:capitalize).join('-')
22
+ value = kv[1]
23
+ memo[key] = value
24
+ end
25
+ matching_headers.blank? ? nil : matching_headers
26
+ end
27
+ end
28
+
9
29
  class HTTPServerRequest < AppMap::Event::MethodEvent
10
- attr_accessor :normalized_path_info, :request_method, :path_info, :params
30
+ attr_accessor :normalized_path_info, :request_method, :path_info, :params, :mime_type, :headers, :authorization
11
31
 
12
32
  def initialize(request)
13
33
  super AppMap::Event.next_id_counter, :call, Thread.current.object_id
14
34
 
15
- @request_method = request.request_method
16
- @normalized_path_info = normalized_path request
17
- @path_info = request.path_info.split('?')[0]
35
+ self.request_method = request.request_method
36
+ self.normalized_path_info = normalized_path(request)
37
+ self.mime_type = request.headers['Content-Type']
38
+ self.headers = RequestHandler.selected_headers(request.env)
39
+ self.authorization = request.headers['Authorization']
40
+ self.path_info = request.path_info.split('?')[0]
18
41
  # ActionDispatch::Http::ParameterFilter is deprecated
19
42
  parameter_filter_cls = \
20
43
  if defined?(ActiveSupport::ParameterFilter)
@@ -22,7 +45,7 @@ module AppMap
22
45
  else
23
46
  ActionDispatch::Http::ParameterFilter
24
47
  end
25
- @params = parameter_filter_cls.new(::Rails.application.config.filter_parameters).filter(request.params)
48
+ self.params = parameter_filter_cls.new(::Rails.application.config.filter_parameters).filter(request.params)
26
49
  end
27
50
 
28
51
  def to_h
@@ -30,7 +53,10 @@ module AppMap
30
53
  h[:http_server_request] = {
31
54
  request_method: request_method,
32
55
  path_info: path_info,
33
- normalized_path_info: normalized_path_info
56
+ mime_type: mime_type,
57
+ normalized_path_info: normalized_path_info,
58
+ authorization: authorization,
59
+ headers: headers,
34
60
  }.compact
35
61
 
36
62
  h[:message] = params.keys.map do |key|
@@ -39,8 +65,11 @@ module AppMap
39
65
  name: key,
40
66
  class: val.class.name,
41
67
  value: self.class.display_string(val),
42
- object_id: val.__id__
43
- }
68
+ object_id: val.__id__,
69
+ }.tap do |message|
70
+ properties = object_properties(val)
71
+ message[:properties] = properties if properties
72
+ end
44
73
  end
45
74
  end
46
75
  end
@@ -59,7 +88,7 @@ module AppMap
59
88
  end
60
89
 
61
90
  class HTTPServerResponse < AppMap::Event::MethodReturnIgnoreValue
62
- attr_accessor :status, :mime_type
91
+ attr_accessor :status, :mime_type, :headers
63
92
 
64
93
  def initialize(response, parent_id, elapsed)
65
94
  super AppMap::Event.next_id_counter, :return, Thread.current.object_id
@@ -68,13 +97,15 @@ module AppMap
68
97
  self.mime_type = response.headers['Content-Type']
69
98
  self.parent_id = parent_id
70
99
  self.elapsed = elapsed
100
+ self.headers = RequestHandler.selected_headers(response.headers)
71
101
  end
72
102
 
73
103
  def to_h
74
104
  super.tap do |h|
75
105
  h[:http_server_response] = {
76
106
  status: status,
77
- mime_type: mime_type
107
+ mime_type: mime_type,
108
+ headers: headers
78
109
  }.compact
79
110
  end
80
111
  end