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.
- 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
|
-
[](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
|