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
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: false)
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
- events: events,
195
- feature_name: feature_name,
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, events: nil, feature_name: nil, feature_group_name: nil, labels: nil)
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] << {
@@ -3,7 +3,7 @@
3
3
  module AppMap
4
4
  URL = 'https://github.com/applandinc/appmap-ruby'
5
5
 
6
- VERSION = '0.41.0'
6
+ VERSION = '0.43.0'
7
7
 
8
8
  APPMAP_FORMAT_VERSION = '1.4'
9
9
  end
@@ -1,12 +1,11 @@
1
1
  require 'rails_spec_helper'
2
2
 
3
- describe 'AbstractControllerBase' do
3
+ describe 'Rails' do
4
4
  %w[5 6].each do |rails_major_version| # rubocop:disable Metrics/BlockLength
5
- context "in Rails #{rails_major_version}" do
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 'testing with rspec' do
25
- describe 'creating a user' do
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 'message fields are recorded in the appmap' do
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' => 23,
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 'showing a user' do
99
- before(:all) { run_spec 'spec/controllers/users_controller_spec.rb:22' }
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
@@ -6,6 +6,8 @@ module Api
6
6
  end
7
7
 
8
8
  def create
9
+ params = self.params.key?(:user) ? self.params[:user] : self.params
10
+
9
11
  @user = build_user(params.slice(:login).to_unsafe_h)
10
12
  unless @user.valid?
11
13
  error = {
@@ -4,7 +4,15 @@ class UsersController < ApplicationController
4
4
  end
5
5
 
6
6
  def show
7
- if (@user = User[login: params[:id]])
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
- psql -h pg -U postgres -c "create database app_development"
20
- psql -h pg -U postgres -c "create database app_test"
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
 
@@ -19,6 +19,9 @@ services:
19
19
  environment:
20
20
  RAILS_ENV:
21
21
  ORM_MODULE:
22
+ PGHOST: pg
23
+ PGPORT: '5432'
24
+ DATABASE_URL: postgres://postgres@pg
22
25
  APPMAP:
23
26
  volumes:
24
27
  - .:/src/app
@@ -1,13 +1,19 @@
1
1
  require 'rails_helper'
2
2
  require 'rack/test'
3
3
 
4
- RSpec.describe Api::UsersController, feature_group: 'Users', type: :controller, appmap: true do
5
- describe 'POST /api/users', feature: 'Create a user' do
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', feature: 'List users' do
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