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
data/lib/appmap/rspec.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'appmap/util'
|
4
|
+
require 'set'
|
4
5
|
|
5
6
|
module AppMap
|
6
7
|
# Integration of AppMap with RSpec. When enabled with APPMAP=true, the AppMap tracer will
|
@@ -13,58 +14,9 @@ module AppMap
|
|
13
14
|
AppMap.detect_metadata
|
14
15
|
end
|
15
16
|
|
16
|
-
module FeatureAnnotations
|
17
|
-
def feature
|
18
|
-
return nil unless annotations
|
19
|
-
|
20
|
-
annotations[:feature]
|
21
|
-
end
|
22
|
-
|
23
|
-
def labels
|
24
|
-
labels = metadata[:appmap]
|
25
|
-
if labels.is_a?(Array)
|
26
|
-
labels
|
27
|
-
elsif labels.is_a?(String) || labels.is_a?(Symbol)
|
28
|
-
[ labels ]
|
29
|
-
else
|
30
|
-
[]
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
def feature_group
|
35
|
-
return nil unless annotations
|
36
|
-
|
37
|
-
annotations[:feature_group]
|
38
|
-
end
|
39
|
-
|
40
|
-
def annotations
|
41
|
-
metadata.tap do |md|
|
42
|
-
description_args_hashes.each do |h|
|
43
|
-
md.merge! h
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
protected
|
49
|
-
|
50
|
-
def metadata
|
51
|
-
return {} unless example_obj.respond_to?(:metadata)
|
52
|
-
|
53
|
-
example_obj.metadata
|
54
|
-
end
|
55
|
-
|
56
|
-
def description_args_hashes
|
57
|
-
return [] unless example_obj.respond_to?(:metadata)
|
58
|
-
|
59
|
-
(example_obj.metadata[:description_args] || []).select { |arg| arg.is_a?(Hash) }
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
17
|
# ScopeExample and ScopeExampleGroup is a way to handle the weird way that RSpec
|
64
18
|
# stores the nested example names.
|
65
19
|
ScopeExample = Struct.new(:example) do
|
66
|
-
include FeatureAnnotations
|
67
|
-
|
68
20
|
alias_method :example_obj, :example
|
69
21
|
|
70
22
|
def description?
|
@@ -83,8 +35,6 @@ module AppMap
|
|
83
35
|
# As you can see here, the way that RSpec stores the example description and
|
84
36
|
# represents the example group hierarchy is pretty weird.
|
85
37
|
ScopeExampleGroup = Struct.new(:example_group) do
|
86
|
-
include FeatureAnnotations
|
87
|
-
|
88
38
|
alias_method :example_obj, :example_group
|
89
39
|
|
90
40
|
def description_args
|
@@ -133,11 +83,17 @@ module AppMap
|
|
133
83
|
page.driver.options[:http_client].instance_variable_get('@server_url').port
|
134
84
|
end
|
135
85
|
|
136
|
-
warn "Starting recording of example #{example}" if AppMap::RSpec::LOG
|
86
|
+
warn "Starting recording of example #{example}@#{source_location}" if AppMap::RSpec::LOG
|
137
87
|
@trace = AppMap.tracing.trace
|
138
88
|
@webdriver_port = webdriver_port.()
|
139
89
|
end
|
140
90
|
|
91
|
+
def source_location
|
92
|
+
result = example.location_rerun_argument.split(':')[0]
|
93
|
+
result = result[2..-1] if result.index('./') == 0
|
94
|
+
result
|
95
|
+
end
|
96
|
+
|
141
97
|
def finish
|
142
98
|
warn "Finishing recording of example #{example}" if AppMap::RSpec::LOG
|
143
99
|
|
@@ -148,22 +104,16 @@ module AppMap
|
|
148
104
|
|
149
105
|
AppMap::RSpec.add_event_methods @trace.event_methods
|
150
106
|
|
151
|
-
class_map = AppMap.class_map(@trace.event_methods, include_source:
|
107
|
+
class_map = AppMap.class_map(@trace.event_methods, include_source: AppMap.include_source?)
|
152
108
|
|
153
109
|
description = []
|
154
110
|
scope = ScopeExample.new(example)
|
155
|
-
feature_group = feature = nil
|
156
111
|
|
157
|
-
labels = []
|
158
112
|
while scope
|
159
|
-
labels += scope.labels
|
160
113
|
description << scope.description
|
161
|
-
feature ||= scope.feature
|
162
|
-
feature_group ||= scope.feature_group
|
163
114
|
scope = scope.parent
|
164
115
|
end
|
165
116
|
|
166
|
-
labels = labels.map(&:to_s).map(&:strip).reject(&:blank?).map(&:downcase).uniq
|
167
117
|
description.reject!(&:nil?).reject!(&:blank?)
|
168
118
|
default_description = description.last
|
169
119
|
description.reverse!
|
@@ -177,24 +127,10 @@ module AppMap
|
|
177
127
|
|
178
128
|
full_description = normalize.call(description.join(' '))
|
179
129
|
|
180
|
-
compute_feature_name = lambda do
|
181
|
-
return 'unknown' if description.empty?
|
182
|
-
|
183
|
-
feature_description = description.dup
|
184
|
-
num_tokens = [2, feature_description.length - 1].min
|
185
|
-
feature_description[0...num_tokens].map(&:strip).join(' ')
|
186
|
-
end
|
187
|
-
|
188
|
-
feature_group ||= normalize.call(default_description).underscore.gsub('/', '_').humanize
|
189
|
-
feature_name = feature || compute_feature_name.call if feature_group
|
190
|
-
feature_name = normalize.call(feature_name) if feature_name
|
191
|
-
|
192
130
|
AppMap::RSpec.save full_description,
|
193
131
|
class_map,
|
194
|
-
|
195
|
-
|
196
|
-
feature_group_name: feature_group,
|
197
|
-
labels: labels.blank? ? nil : labels
|
132
|
+
source_location,
|
133
|
+
events: events
|
198
134
|
end
|
199
135
|
end
|
200
136
|
|
@@ -227,12 +163,11 @@ module AppMap
|
|
227
163
|
@event_methods += event_methods
|
228
164
|
end
|
229
165
|
|
230
|
-
def save(example_name, class_map,
|
166
|
+
def save(example_name, class_map, source_location, events: nil, labels: nil)
|
231
167
|
metadata = AppMap::RSpec.metadata.tap do |m|
|
232
168
|
m[:name] = example_name
|
169
|
+
m[:source_location] = source_location
|
233
170
|
m[:app] = AppMap.configuration.name
|
234
|
-
m[:feature] = feature_name if feature_name
|
235
|
-
m[:feature_group] = feature_group_name if feature_group_name
|
236
171
|
m[:labels] = labels if labels
|
237
172
|
m[:frameworks] ||= []
|
238
173
|
m[:frameworks] << {
|
data/lib/appmap/version.rb
CHANGED
@@ -1,12 +1,11 @@
|
|
1
1
|
require 'rails_spec_helper'
|
2
2
|
|
3
|
-
describe '
|
3
|
+
describe 'Rails' do
|
4
4
|
%w[5 6].each do |rails_major_version| # rubocop:disable Metrics/BlockLength
|
5
|
-
context "
|
6
|
-
include_context 'Rails app pg database', "spec/fixtures/rails#{rails_major_version}_users_app"
|
5
|
+
context "#{rails_major_version}" do
|
6
|
+
include_context 'Rails app pg database', "spec/fixtures/rails#{rails_major_version}_users_app" unless use_existing_data?
|
7
|
+
|
7
8
|
def run_spec(spec_name)
|
8
|
-
FileUtils.rm_rf tmpdir
|
9
|
-
FileUtils.mkdir_p tmpdir
|
10
9
|
cmd = <<~CMD.gsub "\n", ' '
|
11
10
|
docker-compose run --rm -e RAILS_ENV=test -e APPMAP=true
|
12
11
|
-v #{File.absolute_path tmpdir}:/app/tmp app ./bin/rspec #{spec_name}
|
@@ -18,12 +17,20 @@ describe 'AbstractControllerBase' do
|
|
18
17
|
'tmp/spec/AbstractControllerBase'
|
19
18
|
end
|
20
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
|
+
|
21
29
|
let(:appmap) { JSON.parse File.read File.join tmpdir, 'appmap/rspec', appmap_json_file }
|
22
30
|
let(:events) { appmap['events'] }
|
23
31
|
|
24
|
-
describe '
|
25
|
-
describe 'creating
|
26
|
-
before(:all) { run_spec 'spec/controllers/users_controller_api_spec.rb:8' }
|
32
|
+
describe 'an API route' do
|
33
|
+
describe 'creating an object' do
|
27
34
|
let(:appmap_json_file) do
|
28
35
|
'Api_UsersController_POST_api_users_with_required_parameters_creates_a_user.appmap.json'
|
29
36
|
end
|
@@ -32,11 +39,12 @@ describe 'AbstractControllerBase' do
|
|
32
39
|
expect(File).to exist(File.join(tmpdir, 'appmap/rspec/Inventory.appmap.json'))
|
33
40
|
end
|
34
41
|
|
35
|
-
it '
|
42
|
+
it 'http_server_request is recorded in the appmap' do
|
36
43
|
expect(events).to include(
|
37
44
|
hash_including(
|
38
45
|
'http_server_request' => hash_including(
|
39
46
|
'request_method' => 'POST',
|
47
|
+
'normalized_path_info' => '/api/users(.:format)',
|
40
48
|
'path_info' => '/api/users'
|
41
49
|
),
|
42
50
|
'message' => include(
|
@@ -53,12 +61,17 @@ describe 'AbstractControllerBase' do
|
|
53
61
|
'object_id' => Integer
|
54
62
|
)
|
55
63
|
)
|
56
|
-
)
|
64
|
+
)
|
65
|
+
)
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'http_server_response is recorded in the appmap' do
|
69
|
+
expect(events).to include(
|
57
70
|
hash_including(
|
58
|
-
'http_server_response' =>
|
71
|
+
'http_server_response' => hash_including(
|
59
72
|
'status' => 201,
|
60
73
|
'mime_type' => 'application/json; charset=utf-8'
|
61
|
-
|
74
|
+
)
|
62
75
|
)
|
63
76
|
)
|
64
77
|
end
|
@@ -70,7 +83,7 @@ describe 'AbstractControllerBase' do
|
|
70
83
|
'defined_class' => 'Api::UsersController',
|
71
84
|
'method_id' => 'build_user',
|
72
85
|
'path' => 'app/controllers/api/users_controller.rb',
|
73
|
-
'lineno' =>
|
86
|
+
'lineno' => Integer,
|
74
87
|
'static' => false,
|
75
88
|
'parameters' => include(
|
76
89
|
'name' => 'params',
|
@@ -93,10 +106,47 @@ describe 'AbstractControllerBase' do
|
|
93
106
|
'elapsed' => Numeric
|
94
107
|
)
|
95
108
|
end
|
109
|
+
|
110
|
+
context 'with an object-style message' do
|
111
|
+
let(:appmap_json_file) { 'Api_UsersController_POST_api_users_with_required_parameters_with_object-style_parameters_creates_a_user.appmap.json' }
|
112
|
+
|
113
|
+
it 'message properties are recorded in the appmap' do
|
114
|
+
expect(events).to include(
|
115
|
+
hash_including(
|
116
|
+
'message' => include(
|
117
|
+
hash_including(
|
118
|
+
'name' => 'user',
|
119
|
+
'properties' => [
|
120
|
+
{ 'name' => 'login', 'class' => 'String' },
|
121
|
+
{ 'name' => 'password', 'class' => 'String' }
|
122
|
+
]
|
123
|
+
)
|
124
|
+
)
|
125
|
+
)
|
126
|
+
)
|
127
|
+
end
|
128
|
+
end
|
96
129
|
end
|
97
130
|
|
98
|
-
describe '
|
99
|
-
|
131
|
+
describe 'listing objects' do
|
132
|
+
context 'with a custom header' do
|
133
|
+
let(:appmap_json_file) { 'Api_UsersController_GET_api_users_with_a_custom_header_lists_the_users.appmap.json' }
|
134
|
+
|
135
|
+
it 'custom header is recorded in the appmap' do
|
136
|
+
expect(events).to include(
|
137
|
+
hash_including(
|
138
|
+
'http_server_request' => hash_including(
|
139
|
+
'headers' => hash_including('X-Sandwich' => 'turkey')
|
140
|
+
)
|
141
|
+
)
|
142
|
+
)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
describe 'a UI route' do
|
149
|
+
describe 'rendering a page' do
|
100
150
|
let(:appmap_json_file) do
|
101
151
|
'UsersController_GET_users_login_shows_the_user.appmap.json'
|
102
152
|
end
|
@@ -112,11 +162,6 @@ describe 'AbstractControllerBase' do
|
|
112
162
|
)
|
113
163
|
)
|
114
164
|
end
|
115
|
-
end
|
116
|
-
|
117
|
-
describe 'listing users' do
|
118
|
-
before(:all) { run_spec 'spec/controllers/users_controller_spec.rb:11' }
|
119
|
-
let(:appmap_json_file) { 'UsersController_GET_users_lists_the_users.appmap.json' }
|
120
165
|
|
121
166
|
it 'records and labels view rendering' do
|
122
167
|
expect(events).to include hash_including(
|
@@ -128,7 +173,7 @@ describe 'AbstractControllerBase' do
|
|
128
173
|
'lineno' => Integer,
|
129
174
|
'static' => false
|
130
175
|
)
|
131
|
-
|
176
|
+
|
132
177
|
expect(appmap['classMap']).to include hash_including(
|
133
178
|
'name' => 'action_view',
|
134
179
|
'children' => include(hash_including(
|
@@ -142,7 +187,7 @@ describe 'AbstractControllerBase' do
|
|
142
187
|
))
|
143
188
|
))
|
144
189
|
)
|
145
|
-
end
|
190
|
+
end
|
146
191
|
end
|
147
192
|
end
|
148
193
|
end
|
@@ -39,12 +39,16 @@ group :development, :test do
|
|
39
39
|
gem 'appmap', appmap_options
|
40
40
|
gem 'cucumber-rails', require: false
|
41
41
|
gem 'rspec-rails'
|
42
|
-
# Required for Sequel, since without ActiveRecord, the Rails transactional fixture support
|
43
|
-
# isn't activated.
|
44
|
-
gem 'database_cleaner'
|
45
42
|
# Call 'byebug' anywhere in the code to stop execution and get a debugger console
|
46
43
|
gem 'pry-byebug'
|
47
44
|
end
|
48
45
|
|
46
|
+
group :test do
|
47
|
+
# Require only one of these.
|
48
|
+
# 'database_cleaner' requries 'database_cleaner-active_record', so don't require it.
|
49
|
+
gem 'database_cleaner-active_record', require: false
|
50
|
+
gem 'database_cleaner-sequel', require: false
|
51
|
+
end
|
52
|
+
|
49
53
|
group :development do
|
50
54
|
end
|
@@ -4,7 +4,15 @@ class UsersController < ApplicationController
|
|
4
4
|
end
|
5
5
|
|
6
6
|
def show
|
7
|
-
|
7
|
+
find_user = lambda do |id|
|
8
|
+
if User.respond_to?(:[])
|
9
|
+
User[login: id]
|
10
|
+
else
|
11
|
+
User.find_by_login!(id)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
if (@user = find_user.(params[:id]))
|
8
16
|
render plain: @user
|
9
17
|
else
|
10
18
|
render plain: 'Not found', status: 404
|
@@ -15,8 +15,10 @@ case orm_module
|
|
15
15
|
when 'sequel'
|
16
16
|
require 'sequel-rails'
|
17
17
|
require 'sequel_secure_password'
|
18
|
+
require 'database_cleaner-sequel' if Rails.env.test?
|
18
19
|
when 'activerecord'
|
19
20
|
require 'active_record/railtie'
|
21
|
+
require 'database_cleaner-active_record' if Rails.env.test?
|
20
22
|
end
|
21
23
|
|
22
24
|
require 'appmap/railtie' if defined?(AppMap)
|
@@ -16,11 +16,17 @@ if [[ $? != 0 ]]; then
|
|
16
16
|
exit 1
|
17
17
|
fi
|
18
18
|
|
19
|
-
|
20
|
-
|
19
|
+
# Required for migrations
|
20
|
+
export ORM_MODULE=sequel
|
21
21
|
|
22
|
+
set +e
|
23
|
+
psql -h pg -U postgres -c "drop database if exists app_development"
|
24
|
+
psql -h pg -U postgres -c "drop database if exists app_test"
|
22
25
|
set -e
|
23
26
|
|
27
|
+
psql -h pg -U postgres -c "create database app_development"
|
28
|
+
psql -h pg -U postgres -c "create database app_test"
|
29
|
+
|
24
30
|
RAILS_ENV=development ./bin/rake db:migrate
|
25
31
|
RAILS_ENV=test ./bin/rake db:migrate
|
26
32
|
|
@@ -1,13 +1,19 @@
|
|
1
1
|
require 'rails_helper'
|
2
2
|
require 'rack/test'
|
3
3
|
|
4
|
-
RSpec.describe Api::UsersController,
|
5
|
-
describe 'POST /api/users'
|
4
|
+
RSpec.describe Api::UsersController, type: :controller do
|
5
|
+
describe 'POST /api/users' do
|
6
6
|
describe 'with required parameters' do
|
7
7
|
it 'creates a user' do
|
8
8
|
post :create, params: { login: 'alice', password: 'foobar' }
|
9
9
|
expect(response.status).to eq(201)
|
10
10
|
end
|
11
|
+
describe 'with object-style parameters' do
|
12
|
+
it 'creates a user' do
|
13
|
+
post :create, params: { user: { login: 'alice', password: 'foobar' } }
|
14
|
+
expect(response.status).to eq(201)
|
15
|
+
end
|
16
|
+
end
|
11
17
|
end
|
12
18
|
describe 'with a missing parameter' do
|
13
19
|
it 'reports error 422' do
|
@@ -16,7 +22,7 @@ RSpec.describe Api::UsersController, feature_group: 'Users', type: :controller,
|
|
16
22
|
end
|
17
23
|
end
|
18
24
|
end
|
19
|
-
describe 'GET /api/users'
|
25
|
+
describe 'GET /api/users' do
|
20
26
|
before do
|
21
27
|
post :create, params: { login: 'alice' }
|
22
28
|
end
|
@@ -25,5 +31,12 @@ RSpec.describe Api::UsersController, feature_group: 'Users', type: :controller,
|
|
25
31
|
users = JSON.parse(response.body)
|
26
32
|
expect(users.map { |r| r['login'] }).to include('alice')
|
27
33
|
end
|
34
|
+
describe 'with a custom header' do
|
35
|
+
it 'lists the users' do
|
36
|
+
request.headers['X-Sandwich'] = 'turkey'
|
37
|
+
get :index, params: {}
|
38
|
+
expect(response.status).to eq(200)
|
39
|
+
end
|
40
|
+
end
|
28
41
|
end
|
29
42
|
end
|