appmap 0.41.0 → 0.43.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +28 -0
- data/README.md +32 -6
- data/lib/appmap.rb +5 -0
- data/lib/appmap/command/record.rb +1 -1
- data/lib/appmap/config.rb +16 -12
- data/lib/appmap/event.rb +32 -4
- data/lib/appmap/hook.rb +17 -2
- data/lib/appmap/hook/method.rb +1 -1
- data/lib/appmap/middleware/remote_recording.rb +1 -1
- data/lib/appmap/minitest.rb +17 -14
- data/lib/appmap/rails/request_handler.rb +41 -10
- data/lib/appmap/rspec.rb +13 -78
- data/lib/appmap/version.rb +1 -1
- data/spec/abstract_controller_base_spec.rb +67 -22
- data/spec/fixtures/rails5_users_app/Gemfile +7 -3
- data/spec/fixtures/rails5_users_app/app/controllers/api/users_controller.rb +2 -0
- data/spec/fixtures/rails5_users_app/app/controllers/users_controller.rb +9 -1
- data/spec/fixtures/rails5_users_app/config/application.rb +2 -0
- data/spec/fixtures/rails5_users_app/create_app +8 -2
- data/spec/fixtures/rails5_users_app/docker-compose.yml +3 -0
- data/spec/fixtures/rails5_users_app/spec/controllers/users_controller_api_spec.rb +16 -3
- data/spec/fixtures/rails5_users_app/spec/controllers/users_controller_spec.rb +2 -2
- data/spec/fixtures/rails5_users_app/spec/models/user_spec.rb +2 -12
- data/spec/fixtures/rails5_users_app/spec/rails_helper.rb +3 -9
- data/spec/fixtures/rails6_users_app/Gemfile +5 -4
- data/spec/fixtures/rails6_users_app/app/controllers/api/users_controller.rb +1 -0
- data/spec/fixtures/rails6_users_app/app/controllers/users_controller.rb +9 -1
- data/spec/fixtures/rails6_users_app/config/application.rb +2 -0
- data/spec/fixtures/rails6_users_app/create_app +8 -2
- data/spec/fixtures/rails6_users_app/docker-compose.yml +3 -0
- data/spec/fixtures/rails6_users_app/spec/controllers/users_controller_api_spec.rb +16 -3
- data/spec/fixtures/rails6_users_app/spec/controllers/users_controller_spec.rb +2 -2
- data/spec/fixtures/rails6_users_app/spec/models/user_spec.rb +2 -12
- data/spec/fixtures/rails6_users_app/spec/rails_helper.rb +3 -9
- data/spec/record_sql_rails_pg_spec.rb +1 -1
- data/spec/spec_helper.rb +6 -0
- data/test/fixtures/gem_test/appmap.yml +1 -1
- data/test/fixtures/gem_test/test/parser_test.rb +12 -0
- data/test/fixtures/rspec_recorder/spec/decorated_hello_spec.rb +3 -3
- data/test/fixtures/rspec_recorder/spec/plain_hello_spec.rb +1 -1
- data/test/gem_test.rb +4 -4
- data/test/minitest_test.rb +1 -2
- data/test/rspec_test.rb +1 -7
- metadata +3 -4
- data/spec/rspec_feature_metadata_spec.rb +0 -31
- 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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4007f0eb67a3a25088e8b3677de8d0388fd1bfec4b326c43a51c503f640325b5
|
4
|
+
data.tar.gz: 1b593251a7a072b7a1483e7af172b9157d6913208658fb324630bc52b0b4fe73
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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,
|
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/
|
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
|
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
|
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
|
-
|
20
|
-
|
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
|
30
|
+
def gem_path(gem)
|
29
31
|
gemspec = Gem.loaded_specs[gem] or raise "Gem #{gem.inspect} not found"
|
30
|
-
gemspec.
|
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',
|
61
|
-
'ActionView::Renderer' => Hook.new(:render, Package.build_from_path('action_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
|
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
|
-
|
112
|
+
Package.build_from_path(path, exclude: package['exclude'] || [], shallow: package['shallow'])
|
109
113
|
end
|
110
|
-
end.
|
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
|
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
|
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
|
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
|
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
|
-
|
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
|
-
|
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)
|
data/lib/appmap/hook/method.rb
CHANGED
@@ -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
|
|
data/lib/appmap/minitest.rb
CHANGED
@@ -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 =
|
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
|
-
|
43
|
-
|
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,
|
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
|
-
|
16
|
-
|
17
|
-
|
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
|
-
|
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
|
-
|
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
|