appmap 0.48.0 → 0.51.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.dockerignore +0 -1
- data/CHANGELOG.md +43 -0
- data/README.md +3 -332
- data/lib/appmap.rb +33 -6
- data/lib/appmap/config.rb +123 -33
- data/lib/appmap/event.rb +1 -1
- data/lib/appmap/hook.rb +15 -30
- data/lib/appmap/minitest.rb +9 -3
- data/lib/appmap/railtie.rb +7 -0
- data/lib/appmap/rspec.rb +9 -3
- data/lib/appmap/util.rb +24 -1
- data/lib/appmap/version.rb +3 -1
- data/spec/abstract_controller_base_spec.rb +57 -18
- data/spec/config_spec.rb +21 -0
- data/spec/fixtures/hook/exception_method.rb +6 -0
- data/spec/fixtures/rails5_users_app/config/application.rb +0 -8
- data/spec/fixtures/rails5_users_app/spec/rails_helper.rb +0 -2
- data/spec/fixtures/rails6_users_app/config/application.rb +0 -8
- data/spec/fixtures/rails6_users_app/spec/rails_helper.rb +0 -2
- data/spec/hook_spec.rb +35 -3
- data/spec/record_net_http_spec.rb +1 -1
- data/test/bundle_vendor_test.rb +35 -0
- data/test/fixtures/bundle_vendor_app/Gemfile +8 -0
- data/test/fixtures/bundle_vendor_app/appmap.yml +4 -0
- data/test/fixtures/bundle_vendor_app/cli.rb +54 -0
- data/test/gem_test.rb +1 -1
- metadata +6 -4
- data/spec/fixtures/rails5_users_app/config/initializers/record_button.rb +0 -3
- data/spec/fixtures/rails6_users_app/config/initializers/record_button.rb +0 -3
data/lib/appmap/config.rb
CHANGED
@@ -167,8 +167,10 @@ module AppMap
|
|
167
167
|
),
|
168
168
|
package_hooks('actionpack',
|
169
169
|
[
|
170
|
-
method_hook('ActionDispatch::Request::Session', %i[
|
171
|
-
method_hook('ActionDispatch::
|
170
|
+
method_hook('ActionDispatch::Request::Session', %i[[] dig values fetch], %w[http.session.read]),
|
171
|
+
method_hook('ActionDispatch::Request::Session', %i[destroy[]= clear update delete merge], %w[http.session.write]),
|
172
|
+
method_hook('ActionDispatch::Cookies::CookieJar', %i[[]= clear update delete recycle], %w[http.session.read]),
|
173
|
+
method_hook('ActionDispatch::Cookies::CookieJar', %i[[]= clear update delete recycle], %w[http.session.write]),
|
172
174
|
method_hook('ActionDispatch::Cookies::EncryptedCookieJar', %i[[]= clear update delete recycle], %w[http.cookie crypto.encrypt])
|
173
175
|
],
|
174
176
|
package_name: 'action_dispatch'
|
@@ -213,15 +215,22 @@ module AppMap
|
|
213
215
|
# This is happening: Method send_command not found on Net::IMAP
|
214
216
|
# 'Net::IMAP' => TargetMethods.new(:send_command, Package.build_from_path('net/imap', package_name: 'net/imap', labels: %w[protocol.email.imap])),
|
215
217
|
# 'Marshal' => TargetMethods.new(%i[dump load], Package.build_from_path('marshal', labels: %w[format.marshal])),
|
216
|
-
'Psych' =>
|
217
|
-
|
218
|
-
|
218
|
+
'Psych' => [
|
219
|
+
TargetMethods.new(%i[load load_stream parse parse_stream], Package.build_from_path('yaml', package_name: 'psych', labels: %w[format.yaml.parse])),
|
220
|
+
TargetMethods.new(%i[dump dump_stream], Package.build_from_path('yaml', package_name: 'psych', labels: %w[format.yaml.generate])),
|
221
|
+
],
|
222
|
+
'JSON::Ext::Parser' => TargetMethods.new(:parse, Package.build_from_path('json', package_name: 'json', labels: %w[format.json.parse])),
|
223
|
+
'JSON::Ext::Generator::State' => TargetMethods.new(:generate, Package.build_from_path('json', package_name: 'json', labels: %w[format.json.generate])),
|
219
224
|
}.freeze
|
220
225
|
|
221
|
-
attr_reader :name, :packages, :exclude, :hooked_methods, :builtin_hooks
|
226
|
+
attr_reader :name, :appmap_dir, :packages, :exclude, :hooked_methods, :builtin_hooks
|
222
227
|
|
223
|
-
def initialize(name,
|
228
|
+
def initialize(name,
|
229
|
+
packages: [],
|
230
|
+
exclude: [],
|
231
|
+
functions: [])
|
224
232
|
@name = name
|
233
|
+
@appmap_dir = AppMap::DEFAULT_APPMAP_DIR
|
225
234
|
@packages = packages
|
226
235
|
@hook_paths = Set.new(packages.map(&:path))
|
227
236
|
@exclude = exclude
|
@@ -248,38 +257,119 @@ module AppMap
|
|
248
257
|
class << self
|
249
258
|
# Loads configuration data from a file, specified by the file name.
|
250
259
|
def load_from_file(config_file_name)
|
251
|
-
|
252
|
-
|
260
|
+
logo = lambda do
|
261
|
+
Util.color(<<~LOGO, :magenta)
|
262
|
+
___ __ ___
|
263
|
+
/ _ | ___ ___ / |/ /__ ____
|
264
|
+
/ __ |/ _ \\/ _ \\/ /|_/ / _ `/ _ \\
|
265
|
+
/_/ |_/ .__/ .__/_/ /_/\\_,_/ .__/
|
266
|
+
/_/ /_/ /_/
|
267
|
+
LOGO
|
268
|
+
end
|
269
|
+
|
270
|
+
config_present = true if File.exists?(config_file_name)
|
271
|
+
|
272
|
+
config_data = if config_present
|
273
|
+
require 'yaml'
|
274
|
+
YAML.safe_load(::File.read(config_file_name))
|
275
|
+
else
|
276
|
+
warn logo.()
|
277
|
+
warn ''
|
278
|
+
warn Util.color(%Q|NOTICE: The AppMap config file #{config_file_name} was not found!|, :magenta, bold: true)
|
279
|
+
warn ''
|
280
|
+
warn Util.color(<<~MISSING_FILE_MSG, :magenta)
|
281
|
+
AppMap uses this file to customize its behavior. For example, you can use
|
282
|
+
the 'packages' setting to indicate which local file paths and dependency
|
283
|
+
gems you want to include in the AppMap. Since you haven't provided specific
|
284
|
+
settings, the appmap gem will try and guess some reasonable defaults.
|
285
|
+
To suppress this message, create the file:
|
286
|
+
|
287
|
+
#{Pathname.new(config_file_name).expand_path}.
|
288
|
+
|
289
|
+
Here are the default settings that will be used in the meantime. You can
|
290
|
+
copy and paste this example to start your appmap.yml.
|
291
|
+
MISSING_FILE_MSG
|
292
|
+
{}
|
293
|
+
end
|
294
|
+
load(config_data).tap do |config|
|
295
|
+
config_yaml = {
|
296
|
+
'name' => config.name,
|
297
|
+
'packages' => config.packages.select{|p| p.path}.map do |pkg|
|
298
|
+
{ 'path' => pkg.path }
|
299
|
+
end,
|
300
|
+
'exclude' => []
|
301
|
+
}.compact
|
302
|
+
unless config_present
|
303
|
+
warn Util.color(YAML.dump(config_yaml), :magenta)
|
304
|
+
warn logo.()
|
305
|
+
end
|
306
|
+
end
|
253
307
|
end
|
254
308
|
|
255
309
|
# Loads configuration from a Hash.
|
256
310
|
def load(config_data)
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
311
|
+
name = config_data['name'] || guess_name
|
312
|
+
config_params = {
|
313
|
+
exclude: config_data['exclude']
|
314
|
+
}.compact
|
315
|
+
|
316
|
+
if config_data['functions']
|
317
|
+
config_params[:functions] = config_data['functions'].map do |function_data|
|
318
|
+
package = function_data['package']
|
319
|
+
cls = function_data['class']
|
320
|
+
functions = function_data['function'] || function_data['functions']
|
321
|
+
raise %q(AppMap config 'function' element should specify 'package', 'class' and 'function' or 'functions') unless package && cls && functions
|
322
|
+
|
323
|
+
functions = Array(functions).map(&:to_sym)
|
324
|
+
labels = function_data['label'] || function_data['labels']
|
325
|
+
labels = Array(labels).map(&:to_s) if labels
|
326
|
+
Function.new(package, cls, labels, functions)
|
327
|
+
end
|
266
328
|
end
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
329
|
+
|
330
|
+
config_params[:packages] = \
|
331
|
+
if config_data['packages']
|
332
|
+
config_data['packages'].map do |package|
|
333
|
+
gem = package['gem']
|
334
|
+
path = package['path']
|
335
|
+
raise %q(AppMap config 'package' element should specify 'gem' or 'path', not both) if gem && path
|
336
|
+
|
337
|
+
if gem
|
338
|
+
shallow = package['shallow']
|
339
|
+
# shallow is true by default for gems
|
340
|
+
shallow = true if shallow.nil?
|
341
|
+
Package.build_from_gem(gem, exclude: package['exclude'] || [], shallow: shallow)
|
342
|
+
else
|
343
|
+
Package.build_from_path(path, exclude: package['exclude'] || [], shallow: package['shallow'])
|
344
|
+
end
|
345
|
+
end.compact
|
277
346
|
else
|
278
|
-
|
347
|
+
Array(guess_paths).map do |path|
|
348
|
+
Package.build_from_path(path)
|
349
|
+
end
|
279
350
|
end
|
280
|
-
|
281
|
-
|
282
|
-
|
351
|
+
|
352
|
+
Config.new name, config_params
|
353
|
+
end
|
354
|
+
|
355
|
+
def guess_name
|
356
|
+
reponame = lambda do
|
357
|
+
next unless File.directory?('.git')
|
358
|
+
|
359
|
+
repo_name = `git config --get remote.origin.url`.strip
|
360
|
+
repo_name.split('/').last.split('.').first unless repo_name == ''
|
361
|
+
end
|
362
|
+
dirname = -> { Dir.pwd.split('/').last }
|
363
|
+
|
364
|
+
reponame.() || dirname.()
|
365
|
+
end
|
366
|
+
|
367
|
+
def guess_paths
|
368
|
+
if defined?(::Rails)
|
369
|
+
%w[app/controllers app/models]
|
370
|
+
elsif File.directory?('lib')
|
371
|
+
%w[lib]
|
372
|
+
end
|
283
373
|
end
|
284
374
|
end
|
285
375
|
|
@@ -289,7 +379,7 @@ module AppMap
|
|
289
379
|
packages: packages.map(&:to_h),
|
290
380
|
functions: @functions.map(&:to_h),
|
291
381
|
exclude: exclude
|
292
|
-
}
|
382
|
+
}.compact
|
293
383
|
end
|
294
384
|
|
295
385
|
# Determines if methods defined in a file path should possibly be hooked.
|
data/lib/appmap/event.rb
CHANGED
@@ -213,7 +213,7 @@ module AppMap
|
|
213
213
|
exception_backtrace = next_exception.backtrace_locations.try(:[], 0)
|
214
214
|
exceptions << {
|
215
215
|
class: best_class_name(next_exception),
|
216
|
-
message: next_exception.message,
|
216
|
+
message: display_string(next_exception.message),
|
217
217
|
object_id: next_exception.__id__,
|
218
218
|
path: exception_backtrace&.path,
|
219
219
|
lineno: exception_backtrace&.lineno
|
data/lib/appmap/hook.rb
CHANGED
@@ -36,7 +36,7 @@ module AppMap
|
|
36
36
|
|
37
37
|
def initialize(config)
|
38
38
|
@config = config
|
39
|
-
@
|
39
|
+
@trace_enabled = []
|
40
40
|
# Paths that are known to be non-tracing
|
41
41
|
@notrace_paths = Set.new
|
42
42
|
end
|
@@ -47,10 +47,8 @@ module AppMap
|
|
47
47
|
|
48
48
|
hook_builtins
|
49
49
|
|
50
|
-
@trace_begin = TracePoint.new(:class, &method(:trace_class))
|
51
50
|
@trace_end = TracePoint.new(:end, &method(:trace_end))
|
52
|
-
|
53
|
-
@trace_begin.enable(&block)
|
51
|
+
@trace_end.enable(&block)
|
54
52
|
end
|
55
53
|
|
56
54
|
# hook_builtins builds hooks for code that is built in to the Ruby standard library.
|
@@ -96,29 +94,22 @@ module AppMap
|
|
96
94
|
|
97
95
|
protected
|
98
96
|
|
99
|
-
def trace_class(trace_point)
|
100
|
-
path = trace_point.path
|
101
|
-
|
102
|
-
return if @notrace_paths.member?(path)
|
103
|
-
|
104
|
-
if config.path_enabled?(path)
|
105
|
-
location = trace_location(trace_point)
|
106
|
-
warn "Entering hook-enabled location #{location}" if Hook::LOG || Hook::LOG_HOOK
|
107
|
-
@trace_locations << location
|
108
|
-
unless @trace_end.enabled?
|
109
|
-
warn "Enabling hooking" if Hook::LOG || Hook::LOG_HOOK
|
110
|
-
@trace_end.enable
|
111
|
-
end
|
112
|
-
else
|
113
|
-
@notrace_paths << path
|
114
|
-
end
|
115
|
-
end
|
116
|
-
|
117
97
|
def trace_location(trace_point)
|
118
98
|
[ trace_point.path, trace_point.lineno ].join(':')
|
119
99
|
end
|
120
100
|
|
121
101
|
def trace_end(trace_point)
|
102
|
+
location = trace_location(trace_point)
|
103
|
+
warn "Class or module ends at location #{trace_location(trace_point)}" if Hook::LOG || Hook::LOG_HOOK
|
104
|
+
|
105
|
+
path = trace_point.path
|
106
|
+
enabled = !@notrace_paths.member?(path) && config.path_enabled?(path)
|
107
|
+
if !enabled
|
108
|
+
warn "Not hooking - path is not enabled" if Hook::LOG || Hook::LOG_HOOK
|
109
|
+
@notrace_paths << path
|
110
|
+
return
|
111
|
+
end
|
112
|
+
|
122
113
|
cls = trace_point.self
|
123
114
|
|
124
115
|
instance_methods = cls.public_instance_methods(false) - OBJECT_INSTANCE_METHODS
|
@@ -151,7 +142,8 @@ module AppMap
|
|
151
142
|
warn "AppMap: Examining #{hook_cls} #{method.name}" if LOG
|
152
143
|
|
153
144
|
disasm = RubyVM::InstructionSequence.disasm(method)
|
154
|
-
# Skip methods that have no instruction sequence, as they are
|
145
|
+
# Skip methods that have no instruction sequence, as they are either have no body or they are or native.
|
146
|
+
# TODO: Figure out how to tell the difference?
|
155
147
|
next unless disasm
|
156
148
|
|
157
149
|
package = config.lookup_package(hook_cls, method)
|
@@ -170,13 +162,6 @@ module AppMap
|
|
170
162
|
# uninitialized constant Faraday::Connection
|
171
163
|
warn "NameError in #{__FILE__}: #{$!.message}"
|
172
164
|
end
|
173
|
-
|
174
|
-
location = @trace_locations.pop
|
175
|
-
warn "Leaving hook-enabled location #{location}" if Hook::LOG || Hook::LOG_HOOK
|
176
|
-
if @trace_locations.empty?
|
177
|
-
warn "Disabling hooking" if Hook::LOG || Hook::LOG_HOOK
|
178
|
-
@trace_end.disable
|
179
|
-
end
|
180
165
|
end
|
181
166
|
end
|
182
167
|
end
|
data/lib/appmap/minitest.rb
CHANGED
@@ -54,15 +54,21 @@ module AppMap
|
|
54
54
|
|
55
55
|
@recordings_by_test = {}
|
56
56
|
@event_methods = Set.new
|
57
|
+
@recording_count = 0
|
57
58
|
|
58
59
|
class << self
|
59
60
|
def init
|
60
|
-
warn 'Configuring AppMap recorder for Minitest'
|
61
|
-
|
62
61
|
FileUtils.mkdir_p APPMAP_OUTPUT_DIR
|
63
62
|
end
|
64
63
|
|
64
|
+
def first_recording?
|
65
|
+
@recording_count == 0
|
66
|
+
end
|
67
|
+
|
65
68
|
def begin_test(test, name)
|
69
|
+
AppMap.info 'Configuring AppMap recorder for Minitest' if first_recording?
|
70
|
+
@recording_count += 1
|
71
|
+
|
66
72
|
@recordings_by_test[test.object_id] = Recording.new(test, name)
|
67
73
|
end
|
68
74
|
|
@@ -98,7 +104,7 @@ module AppMap
|
|
98
104
|
if exception
|
99
105
|
m[:exception] = {
|
100
106
|
class: exception.class.name,
|
101
|
-
message: exception.to_s
|
107
|
+
message: AppMap::Event::MethodEvent.display_string(exception.to_s)
|
102
108
|
}
|
103
109
|
end
|
104
110
|
end
|
data/lib/appmap/railtie.rb
CHANGED
@@ -3,6 +3,13 @@
|
|
3
3
|
module AppMap
|
4
4
|
# Railtie connects the AppMap recorder to Rails-specific features.
|
5
5
|
class Railtie < ::Rails::Railtie
|
6
|
+
initializer 'appmap.remote_recording' do
|
7
|
+
require 'appmap/middleware/remote_recording'
|
8
|
+
Rails.application.config.middleware.insert_after \
|
9
|
+
Rails::Rack::Logger,
|
10
|
+
AppMap::Middleware::RemoteRecording
|
11
|
+
end
|
12
|
+
|
6
13
|
# appmap.subscribe subscribes to ActiveSupport Notifications so that they can be recorded as
|
7
14
|
# AppMap events.
|
8
15
|
initializer 'appmap.subscribe' do |_| # params: app
|
data/lib/appmap/rspec.rb
CHANGED
@@ -139,15 +139,21 @@ module AppMap
|
|
139
139
|
|
140
140
|
@recordings_by_example = {}
|
141
141
|
@event_methods = Set.new
|
142
|
+
@recording_count = 0
|
142
143
|
|
143
144
|
class << self
|
144
145
|
def init
|
145
|
-
warn 'Configuring AppMap recorder for RSpec'
|
146
|
-
|
147
146
|
FileUtils.mkdir_p APPMAP_OUTPUT_DIR
|
148
147
|
end
|
149
148
|
|
149
|
+
def first_recording?
|
150
|
+
@recording_count == 0
|
151
|
+
end
|
152
|
+
|
150
153
|
def begin_spec(example)
|
154
|
+
AppMap.info 'Configuring AppMap recorder for RSpec' if first_recording?
|
155
|
+
@recording_count += 1
|
156
|
+
|
151
157
|
@recordings_by_example[example.object_id] = Recording.new(example)
|
152
158
|
end
|
153
159
|
|
@@ -183,7 +189,7 @@ module AppMap
|
|
183
189
|
if exception
|
184
190
|
m[:exception] = {
|
185
191
|
class: exception.class.name,
|
186
|
-
message: exception.to_s
|
192
|
+
message: AppMap::Event::MethodEvent.display_string(exception.to_s)
|
187
193
|
}
|
188
194
|
end
|
189
195
|
end
|
data/lib/appmap/util.rb
CHANGED
@@ -1,7 +1,24 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'bundler'
|
4
|
+
|
3
5
|
module AppMap
|
4
6
|
module Util
|
7
|
+
# https://wynnnetherland.com/journal/a-stylesheet-author-s-guide-to-terminal-colors/
|
8
|
+
# Embed in a String to clear all previous ANSI sequences.
|
9
|
+
CLEAR = "\e[0m"
|
10
|
+
BOLD = "\e[1m"
|
11
|
+
|
12
|
+
# Colors
|
13
|
+
BLACK = "\e[30m"
|
14
|
+
RED = "\e[31m"
|
15
|
+
GREEN = "\e[32m"
|
16
|
+
YELLOW = "\e[33m"
|
17
|
+
BLUE = "\e[34m"
|
18
|
+
MAGENTA = "\e[35m"
|
19
|
+
CYAN = "\e[36m"
|
20
|
+
WHITE = "\e[37m"
|
21
|
+
|
5
22
|
class << self
|
6
23
|
# scenario_filename builds a suitable file name from a scenario name.
|
7
24
|
# Special characters are removed, and the file name is truncated to fit within
|
@@ -94,7 +111,7 @@ module AppMap
|
|
94
111
|
end
|
95
112
|
|
96
113
|
def normalize_path(path)
|
97
|
-
if path.index(Dir.pwd) == 0
|
114
|
+
if path.index(Dir.pwd) == 0 && !path.index(Bundler.bundle_path.to_s)
|
98
115
|
path[Dir.pwd.length + 1..-1]
|
99
116
|
else
|
100
117
|
path
|
@@ -126,6 +143,12 @@ module AppMap
|
|
126
143
|
FileUtils.mv tempfile.path, filename
|
127
144
|
end
|
128
145
|
end
|
146
|
+
|
147
|
+
def color(text, color, bold: false)
|
148
|
+
color = Util.const_get(color.to_s.upcase) if color.is_a?(Symbol)
|
149
|
+
bold = bold ? BOLD : ""
|
150
|
+
"#{bold}#{color}#{text}#{CLEAR}"
|
151
|
+
end
|
129
152
|
end
|
130
153
|
end
|
131
154
|
end
|
data/lib/appmap/version.rb
CHANGED
@@ -1,9 +1,30 @@
|
|
1
1
|
require 'rails_spec_helper'
|
2
2
|
|
3
3
|
describe 'Rails' do
|
4
|
+
shared_context 'rails integration test setup' do
|
5
|
+
def tmpdir
|
6
|
+
'tmp/spec/AbstractControllerBase'
|
7
|
+
end
|
8
|
+
|
9
|
+
unless use_existing_data?
|
10
|
+
before(:all) do
|
11
|
+
FileUtils.rm_rf tmpdir
|
12
|
+
FileUtils.mkdir_p tmpdir
|
13
|
+
run_spec 'spec/controllers/users_controller_spec.rb'
|
14
|
+
run_spec 'spec/controllers/users_controller_api_spec.rb'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
let(:appmap) { JSON.parse File.read File.join tmpdir, 'appmap/rspec', appmap_json_file }
|
19
|
+
let(:appmap_json_path) { File.join(tmpdir, 'appmap/rspec', appmap_json_file) }
|
20
|
+
let(:appmap) { JSON.parse File.read(appmap_json_path) }
|
21
|
+
let(:events) { appmap['events'] }
|
22
|
+
end
|
23
|
+
|
4
24
|
%w[5 6].each do |rails_major_version| # rubocop:disable Metrics/BlockLength
|
5
25
|
context "#{rails_major_version}" do
|
6
26
|
include_context 'Rails app pg database', "spec/fixtures/rails#{rails_major_version}_users_app" unless use_existing_data?
|
27
|
+
include_context 'rails integration test setup'
|
7
28
|
|
8
29
|
def run_spec(spec_name)
|
9
30
|
cmd = <<~CMD.gsub "\n", ' '
|
@@ -13,24 +34,6 @@ describe 'Rails' do
|
|
13
34
|
run_cmd cmd, chdir: fixture_dir
|
14
35
|
end
|
15
36
|
|
16
|
-
def tmpdir
|
17
|
-
'tmp/spec/AbstractControllerBase'
|
18
|
-
end
|
19
|
-
|
20
|
-
unless use_existing_data?
|
21
|
-
before(:all) do
|
22
|
-
FileUtils.rm_rf tmpdir
|
23
|
-
FileUtils.mkdir_p tmpdir
|
24
|
-
run_spec 'spec/controllers/users_controller_spec.rb'
|
25
|
-
run_spec 'spec/controllers/users_controller_api_spec.rb'
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
let(:appmap) { JSON.parse File.read File.join tmpdir, 'appmap/rspec', appmap_json_file }
|
30
|
-
let(:appmap_json_path) { File.join(tmpdir, 'appmap/rspec', appmap_json_file) }
|
31
|
-
let(:appmap) { JSON.parse File.read(appmap_json_path) }
|
32
|
-
let(:events) { appmap['events'] }
|
33
|
-
|
34
37
|
describe 'an API route' do
|
35
38
|
describe 'creating an object' do
|
36
39
|
let(:appmap_json_file) do
|
@@ -253,4 +256,40 @@ describe 'Rails' do
|
|
253
256
|
end
|
254
257
|
end
|
255
258
|
end
|
259
|
+
|
260
|
+
describe 'with default appmap.yml' do
|
261
|
+
include_context 'Rails app pg database', "spec/fixtures/rails5_users_app" unless use_existing_data?
|
262
|
+
include_context 'rails integration test setup'
|
263
|
+
|
264
|
+
def run_spec(spec_name)
|
265
|
+
cmd = <<~CMD.gsub "\n", ' '
|
266
|
+
docker-compose run --rm -e RAILS_ENV=test -e APPMAP=true -e APPMAP_CONFIG_FILE=no/such/file
|
267
|
+
-v #{File.absolute_path tmpdir}:/app/tmp app ./bin/rspec #{spec_name}
|
268
|
+
CMD
|
269
|
+
run_cmd cmd, chdir: fixture_dir
|
270
|
+
end
|
271
|
+
|
272
|
+
let(:appmap_json_file) do
|
273
|
+
'Api_UsersController_POST_api_users_with_required_parameters_creates_a_user.appmap.json'
|
274
|
+
end
|
275
|
+
|
276
|
+
it 'http_server_request is recorded' do
|
277
|
+
expect(events).to include(
|
278
|
+
hash_including(
|
279
|
+
'http_server_request' => hash_including(
|
280
|
+
'request_method' => 'POST',
|
281
|
+
'path_info' => '/api/users'
|
282
|
+
)
|
283
|
+
)
|
284
|
+
)
|
285
|
+
end
|
286
|
+
|
287
|
+
it 'controller method is recorded' do
|
288
|
+
expect(events).to include hash_including(
|
289
|
+
'defined_class' => 'Api::UsersController',
|
290
|
+
'method_id' => 'build_user',
|
291
|
+
'path' => 'app/controllers/api/users_controller.rb',
|
292
|
+
)
|
293
|
+
end
|
294
|
+
end
|
256
295
|
end
|