appmap 0.41.0 → 0.43.0

Sign up to get free protection for your applications and to get access to all the features.
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