appmap 0.77.3 → 0.79.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -0
  3. data/.travis.yml +4 -23
  4. data/CHANGELOG.md +21 -0
  5. data/{spec/fixtures/rails5_users_app/Dockerfile.pg → Dockerfile.pg} +0 -0
  6. data/README.md +14 -44
  7. data/README_CI.md +0 -7
  8. data/Rakefile +12 -150
  9. data/appmap.gemspec +3 -2
  10. data/docker-compose.yml +10 -0
  11. data/ext/appmap/appmap.c +21 -2
  12. data/lib/appmap/builtin_hooks/ruby.yml +6 -3
  13. data/lib/appmap/handler/eval.rb +41 -0
  14. data/lib/appmap/handler/function.rb +8 -8
  15. data/lib/appmap/handler/net_http.rb +19 -18
  16. data/lib/appmap/handler/rails/request_handler.rb +3 -4
  17. data/lib/appmap/handler/rails/template.rb +68 -62
  18. data/lib/appmap/hook/method/ruby2.rb +56 -0
  19. data/lib/appmap/hook/method/ruby3.rb +56 -0
  20. data/lib/appmap/hook/method.rb +42 -98
  21. data/lib/appmap/hook.rb +2 -2
  22. data/lib/appmap/version.rb +1 -1
  23. data/spec/config_spec.rb +1 -1
  24. data/spec/depends/api_spec.rb +13 -5
  25. data/spec/depends/spec_helper.rb +0 -9
  26. data/spec/fixtures/database.yml +11 -0
  27. data/spec/fixtures/rails5_users_app/config/database.yml +1 -0
  28. data/spec/fixtures/rails6_users_app/Gemfile +1 -25
  29. data/spec/fixtures/rails6_users_app/config/database.yml +1 -0
  30. data/spec/fixtures/rails7_users_app/Gemfile +1 -25
  31. data/spec/fixtures/rails7_users_app/config/database.yml +1 -0
  32. data/spec/handler/eval_spec.rb +66 -0
  33. data/spec/hook_spec.rb +3 -3
  34. data/spec/rails_recording_spec.rb +4 -20
  35. data/spec/rails_spec_helper.rb +76 -63
  36. data/spec/rails_test_spec.rb +7 -17
  37. data/spec/railtie_spec.rb +4 -18
  38. data/spec/record_sql_rails_pg_spec.rb +44 -75
  39. data/spec/remote_recording_spec.rb +18 -30
  40. data/spec/spec_helper.rb +1 -0
  41. data/spec/swagger/swagger_spec.rb +1 -16
  42. data/spec/util_spec.rb +1 -1
  43. metadata +25 -21
  44. data/Dockerfile.appmap +0 -5
  45. data/spec/fixtures/rack_users_app/Dockerfile +0 -32
  46. data/spec/fixtures/rack_users_app/docker-compose.yml +0 -9
  47. data/spec/fixtures/rails5_users_app/Dockerfile +0 -29
  48. data/spec/fixtures/rails5_users_app/config/database.yml +0 -18
  49. data/spec/fixtures/rails5_users_app/create_app +0 -33
  50. data/spec/fixtures/rails5_users_app/docker-compose.yml +0 -31
  51. data/spec/fixtures/rails6_users_app/.ruby-version +0 -1
  52. data/spec/fixtures/rails6_users_app/Dockerfile +0 -44
  53. data/spec/fixtures/rails6_users_app/Dockerfile.pg +0 -3
  54. data/spec/fixtures/rails6_users_app/config/database.yml +0 -18
  55. data/spec/fixtures/rails6_users_app/create_app +0 -33
  56. data/spec/fixtures/rails6_users_app/docker-compose.yml +0 -31
  57. data/spec/fixtures/rails7_users_app/.ruby-version +0 -1
  58. data/spec/fixtures/rails7_users_app/Dockerfile +0 -30
  59. data/spec/fixtures/rails7_users_app/Dockerfile.pg +0 -3
  60. data/spec/fixtures/rails7_users_app/config/database.yml +0 -86
  61. data/spec/fixtures/rails7_users_app/create_app +0 -31
  62. data/spec/fixtures/rails7_users_app/docker-compose.yml +0 -31
@@ -69,31 +69,7 @@ group :test do
69
69
  gem "webdrivers"
70
70
  end
71
71
 
72
- appmap_path = \
73
- # Support debugging inside the container with volume-mounted source
74
- if File.directory?('/src/appmap-ruby')
75
- '/src/appmap-ruby'
76
- elsif File.exist?('../../../appmap.gemspec')
77
- '../../..'
78
- end
79
-
80
- if appmap_path
81
- # Set the branch parameter, so that 'bundle config local.appmap' will work
82
- appmap_branch = Dir.chdir appmap_path do
83
- `git rev-parse --abbrev-ref HEAD`.strip
84
- end
85
- end
86
-
87
- appmap_options = \
88
- if appmap_path && appmap_branch
89
- { git: appmap_path, branch: appmap_branch }
90
- elsif appmap_path
91
- { path: appmap_path }
92
- else
93
- {}
94
- end.merge(require: %w[appmap])
95
-
96
72
  group :development, :test do
97
- gem 'appmap', appmap_options
73
+ gem 'appmap', path: '../../..'
98
74
  gem 'pry-byebug', '>=0', '< 99'
99
75
  end
@@ -0,0 +1 @@
1
+ ../../database.yml
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rubocop:disable Security/Eval, Style/EvalWithLocation
4
+
5
+ require 'spec_helper'
6
+ require 'appmap/config'
7
+
8
+ describe 'AppMap::Handler::Eval' do
9
+ include_context 'collect events'
10
+ let!(:config) { AppMap::Config.new('hook_spec') }
11
+ before { AppMap.configuration = config }
12
+ after { AppMap.configuration = nil }
13
+
14
+ def record_block
15
+ AppMap::Hook.new(config).enable do
16
+ tracer = AppMap.tracing.trace
17
+ AppMap::Event.reset_id_counter
18
+ begin
19
+ yield
20
+ ensure
21
+ AppMap.tracing.delete(tracer)
22
+ end
23
+ tracer
24
+ end
25
+ end
26
+
27
+ it 'produces a simple result' do
28
+ tracer = record_block do
29
+ expect(eval('12')).to eq(12)
30
+ end
31
+ events = collect_events(tracer)
32
+ expect(events[0]).to match hash_including \
33
+ defined_class: 'Kernel',
34
+ method_id: 'eval',
35
+ parameters: [{ class: 'Array', kind: :rest, name: 'arg', value: '[12]' }]
36
+ end
37
+
38
+ # a la Ruby 2.6.3 ruby-token.rb
39
+ # token_c = eval("class #{token_n} < #{super_token}; end; #{token_n}")
40
+ it 'can define a new class' do
41
+ num = (Random.random_number * 10_000).to_i
42
+ class_name = "Cls_#{num}"
43
+ m = ClassMaker
44
+ cls = nil
45
+ record_block do
46
+ cls = m.make_class class_name
47
+ end
48
+ expect(cls).to be_instance_of(Class)
49
+ # If execution context wasn't substituted, the class would be defined as
50
+ # eg. AppMap::Handler::Eval::Cls_7566
51
+ expect { AppMap::Handler::Eval.const_get(class_name) }.to raise_error(NameError)
52
+ # This would be the right behavior
53
+ expect(m.const_get(class_name)).to be_instance_of(Class)
54
+ expect(m.const_get(class_name)).to eq(cls)
55
+ new_cls = Class.new do
56
+ include m
57
+ end
58
+ expect(new_cls.const_get(class_name)).to eq(cls)
59
+ end
60
+ end
61
+
62
+ module ClassMaker
63
+ def self.make_class(class_name)
64
+ eval "class #{class_name}; end; #{class_name}"
65
+ end
66
+ end
data/spec/hook_spec.rb CHANGED
@@ -16,7 +16,7 @@ module ShowYamlNulls
16
16
  end
17
17
  Psych::Visitors::YAMLTree.prepend(ShowYamlNulls)
18
18
 
19
- describe 'AppMap class Hooking', docker: false do
19
+ describe 'AppMap class Hooking' do
20
20
  include_context 'collect events'
21
21
 
22
22
  def invoke_test_file(file, setup: nil, packages: nil)
@@ -115,7 +115,7 @@ describe 'AppMap class Hooking', docker: false do
115
115
  require 'appmap/hook/method'
116
116
  package = config.lookup_package(hook_cls, method)
117
117
  expect(package).to be
118
- hook_method = AppMap::Hook::Method.new(package, hook_cls, method)
118
+ hook_method = AppMap::Handler::Function.new(package, hook_cls, method)
119
119
  hook_method.activate
120
120
 
121
121
  tracer = AppMap.tracing.trace
@@ -1188,7 +1188,7 @@ describe 'AppMap class Hooking', docker: false do
1188
1188
  require 'appmap/hook/method'
1189
1189
 
1190
1190
  pkg = AppMap::Config::Package.new('fixtures/hook/prependend_override')
1191
- AppMap::Hook::Method.new(pkg, PrependedClass, PrependedClass.public_instance_method(:say_hello)).activate
1191
+ AppMap::Handler::Function.new(pkg, PrependedClass, PrependedClass.public_instance_method(:say_hello)).activate
1192
1192
 
1193
1193
  tracer = AppMap.tracing.trace
1194
1194
  AppMap::Event.reset_id_counter
@@ -15,14 +15,6 @@ describe 'Rails' do
15
15
  include_context 'Rails app pg database', "spec/fixtures/rails#{rails_major_version}_users_app" unless use_existing_data?
16
16
  include_context 'rails integration test setup'
17
17
 
18
- def run_spec(spec_name)
19
- cmd = <<~CMD.gsub "\n", ' '
20
- docker-compose run --rm -e RAILS_ENV=test -e APPMAP=true
21
- -v #{File.absolute_path tmpdir}:/app/tmp app ./bin/rspec #{spec_name}
22
- CMD
23
- run_cmd cmd, chdir: fixture_dir
24
- end
25
-
26
18
  describe 'an API route' do
27
19
  describe 'creating an object' do
28
20
  let(:appmap_json_file) do
@@ -123,7 +115,7 @@ describe 'Rails' do
123
115
  )
124
116
  )
125
117
  )
126
- )
118
+ )
127
119
  end
128
120
  end
129
121
  end
@@ -201,7 +193,7 @@ describe 'Rails' do
201
193
  'path_info' => '/users/alice',
202
194
  'normalized_path_info' => '/users/{id}',
203
195
  'headers' => {
204
- 'Host' => 'test.host',
196
+ 'Host' => 'test.host',
205
197
  'User-Agent' => 'Rails Testing'
206
198
  }
207
199
  }
@@ -236,7 +228,7 @@ describe 'Rails' do
236
228
  'defined_class' => 'inline_template',
237
229
  'method_id' => 'render'
238
230
  )
239
-
231
+
240
232
  expect(appmap['classMap']).to include hash_including(
241
233
  'name' => 'actionview',
242
234
  'children' => include(hash_including(
@@ -251,7 +243,7 @@ describe 'Rails' do
251
243
  ))
252
244
  ))
253
245
  )
254
- end
246
+ end
255
247
  end
256
248
  end
257
249
  end
@@ -261,14 +253,6 @@ describe 'Rails' do
261
253
  include_context 'Rails app pg database', "spec/fixtures/rails6_users_app" unless use_existing_data?
262
254
  include_context 'rails integration test setup'
263
255
 
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
256
  let(:appmap_json_file) do
273
257
  'Api_UsersController_POST_api_users_with_required_parameters_creates_a_user.appmap.json'
274
258
  end
@@ -1,93 +1,106 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'open3'
4
+
3
5
  require 'spec_helper'
4
6
  require 'active_support'
5
7
  require 'active_support/core_ext'
6
- require 'open3'
7
8
 
8
- # The RUBY_VERSION global variable indicates the version of the
9
- # runtime. The RUBY_VERSION environment variable is the version we're
10
- # testing, so it needs to be used to pick a fixture directory.
11
9
  def testing_ruby_2?
12
- ENV['RUBY_VERSION'].split('.')[0].to_i == 2
10
+ RUBY_VERSION.split('.')[0].to_i == 2
13
11
  end
14
12
 
15
- # docker compose v2 replaced the --filter flag with --status
16
- PS_CMD=`docker-compose --version` =~ /version v2/ ?
17
- "docker-compose ps -q --status running" :
18
- "docker-compose ps -q --filter health=healthy"
13
+ class TestRailsApp
14
+ def initialize(fixture_dir)
15
+ @fixture_dir = fixture_dir
16
+ end
19
17
 
20
- def wait_for_container(app_name)
21
- start_time = Time.now
22
- until `#{PS_CMD} #{app_name}`.strip != ''
23
- elapsed = Time.now - start_time
24
- raise "Timeout waiting for container #{app_name} to be ready" if elapsed > 10
18
+ attr_reader :fixture_dir
25
19
 
26
- $stderr.write '.' if elapsed > 3
27
- sleep 0.25
20
+ def run_cmd(cmd, env = {})
21
+ run_process method(:system), cmd, env, exception: true
28
22
  end
29
- end
30
23
 
31
- def run_cmd(*cmd, &failed)
32
- out, status = Open3.capture2e(*cmd)
33
- return [ out, status ] if status.success?
34
-
35
- warn <<~WARNING
36
- Command failed:
37
- #{cmd}
38
- <<< Output:
39
- #{out}
40
- >>> End of output
41
- WARNING
42
- failed&.call
43
- raise 'Command failed'
44
- end
24
+ def spawn_cmd(cmd, env = {})
25
+ puts "Spawning `#{cmd}` in #{fixture_dir}..."
26
+ run_process Process.method(:spawn), cmd, env
27
+ end
45
28
 
46
- shared_context 'Rails app pg database' do |fixture_dir|
47
- define_method(:fixture_dir) { fixture_dir }
29
+ def capture_cmd(cmd, env = {})
30
+ puts "Capturing `#{cmd}` in #{fixture_dir}..."
31
+ run_process(Open3.method(:capture2), cmd, env).first
32
+ end
48
33
 
49
- before(:all) do
50
- print_pg_logs = lambda do
51
- logs, = run_cmd 'docker-compose logs pg'
52
- puts "docker-compose logs for pg:"
53
- puts
54
- puts logs
55
- end
34
+ def database_name
35
+ # This is used locally too, so make the name nice and unique.
36
+ @database_name ||= "appland-rails-test-#{Random.bytes(8).unpack1('h*')}"
37
+ end
56
38
 
57
- Dir.chdir fixture_dir do
58
- run_cmd 'docker-compose down -v'
59
- cmd = 'docker-compose up -d pg'
60
- run_cmd cmd
61
- wait_for_container 'pg'
39
+ def bundle
40
+ return if @bundled
62
41
 
63
- cmd = 'docker-compose run --rm app ./create_app'
64
- run_cmd cmd, &print_pg_logs
65
- end
42
+ run_cmd 'bundle'
43
+ @bundled = true
66
44
  end
67
45
 
68
- after(:all) do
69
- if ENV['NOKILL'] != 'true'
70
- cmd = 'docker-compose down -v'
71
- run_cmd cmd, chdir: fixture_dir
72
- end
46
+ def prepare_db
47
+ return if @db_prepared
48
+
49
+ bundle
50
+ run_cmd './bin/rake db:create db:schema:load'
51
+ @db_prepared = true
52
+ at_exit { drop_db }
53
+ end
54
+
55
+ def drop_db
56
+ return unless @db_prepared
57
+
58
+ run_cmd './bin/rake db:drop'
59
+ @db_prepared = false
73
60
  end
74
- end
75
61
 
76
- shared_context 'rails integration test setup' do
77
62
  def tmpdir
78
- 'tmp/spec/AbstractControllerBase'
63
+ @tmpdir ||= File.join(fixture_dir, 'tmp')
64
+ end
65
+
66
+ def run_specs
67
+ return if @specs_ran or use_existing_data?
68
+
69
+ prepare_db
70
+ FileUtils.rm_rf tmpdir
71
+ run_cmd \
72
+ './bin/rspec spec/controllers/users_controller_spec.rb spec/controllers/users_controller_api_spec.rb',
73
+ 'APPMAP' => 'true'
74
+ @specs_ran = true
75
+ end
76
+
77
+ def self.for_fixture(fixture_dir)
78
+ @apps ||= {}
79
+ @apps[fixture_dir] ||= TestRailsApp.new fixture_dir
79
80
  end
80
81
 
81
- unless use_existing_data?
82
- before(:all) do
83
- FileUtils.rm_rf tmpdir
84
- FileUtils.mkdir_p tmpdir
85
- run_spec 'spec/controllers/users_controller_spec.rb'
86
- run_spec 'spec/controllers/users_controller_api_spec.rb'
82
+ protected
83
+
84
+ def run_process(method, cmd, env, options = {})
85
+ Bundler.with_clean_env do
86
+ method.call \
87
+ env.merge('TEST_DATABASE' => database_name),
88
+ cmd,
89
+ options.merge(chdir: fixture_dir)
87
90
  end
88
91
  end
92
+ end
93
+
94
+ shared_context 'Rails app pg database' do |dir|
95
+ before(:all) { @app = TestRailsApp.for_fixture dir }
96
+ let(:app) { @app }
97
+ end
98
+
99
+ shared_context 'rails integration test setup' do
100
+ let(:tmpdir) { app.tmpdir }
101
+
102
+ before(:all) { @app.run_specs } unless use_existing_data?
89
103
 
90
- let(:appmap) { JSON.parse File.read File.join tmpdir, 'appmap/rspec', appmap_json_file }
91
104
  let(:appmap_json_path) { File.join(tmpdir, 'appmap/rspec', appmap_json_file) }
92
105
  let(:appmap) { JSON.parse File.read(appmap_json_path) }
93
106
  let(:events) { appmap['events'] }
@@ -3,7 +3,7 @@ require 'rails_spec_helper'
3
3
  # Rails5 doesn't work with Ruby 3.x, Rails 7 doesn't work with Ruby < 2.7.
4
4
  def default_rails_versions
5
5
  if testing_ruby_2?
6
- if Gem::Requirement.create('>= 2.7') =~ Gem::Version.new(ENV['RUBY_VERSION'])
6
+ if Gem::Requirement.create('>= 2.7') =~ Gem::Version.new(RUBY_VERSION)
7
7
  [ 5, 6, 7 ]
8
8
  else
9
9
  [ 5, 6 ]
@@ -21,24 +21,14 @@ describe 'Rails' do
21
21
  rails_versions.each do |rails_major_version| # rubocop:disable Metrics/BlockLength
22
22
  context "#{rails_major_version}" do
23
23
  include_context 'Rails app pg database', "spec/fixtures/rails#{rails_major_version}_users_app" unless use_existing_data?
24
- def tmpdir
25
- 'tmp/spec/rails_test_spec'
26
- end
27
- before(:all) do
28
- FileUtils.rm_rf tmpdir
29
- FileUtils.mkdir_p tmpdir
30
- end
31
-
32
- def run_tests
33
- cmd = <<~CMD.gsub "\n", ' '
34
- docker-compose run --rm -e RAILS_ENV=test -e APPMAP=true -e TEST_OPTS=--verbose
35
- -v #{File.absolute_path tmpdir}:/app/tmp app bundle exec rake
36
- CMD
37
- run_cmd cmd, chdir: fixture_dir
38
- end
39
24
 
40
25
  it 'runs tests with APPMAP=true' do
41
- run_tests
26
+ app.prepare_db
27
+ app.run_cmd \
28
+ 'bundle exec rake',
29
+ 'RAILS_ENV' => 'test',
30
+ 'APPMAP' => 'true',
31
+ 'TEST_OPTS' => '--verbose'
42
32
  end
43
33
  end
44
34
  end
data/spec/railtie_spec.rb CHANGED
@@ -1,27 +1,13 @@
1
1
  require 'rails_spec_helper'
2
2
 
3
3
  describe 'AppMap tracer via Railtie' do
4
- include_context 'Rails app pg database', 'spec/fixtures/rails6_users_app' do
4
+ include_context 'Rails app pg database', 'spec/fixtures/rails6_users_app' do
5
5
  let(:env) { {} }
6
6
 
7
- let(:cmd) { %(docker-compose run --rm -e RAILS_ENV=development -e APPMAP app ./bin/rails r "puts AppMap.instance_variable_get('@configuration').nil?") }
8
- let(:command_capture2) do
9
- require 'open3'
10
- Open3.capture3(env, cmd, chdir: fixture_dir).tap do |result|
11
- unless result[2] == 0
12
- warn <<~STDERR
13
- Failed to run rails6_users_app container
14
- <<< Output:
15
- #{result[0]}
16
- #{result[1]}
17
- >>> End of output
18
- STDERR
19
- raise 'Failed to run rails6_users_app container'
20
- end
21
- end
7
+ let(:command_output) do
8
+ app.prepare_db
9
+ app.capture_cmd(%{./bin/rails r "puts AppMap.instance_variable_get('@configuration').nil?"}, env).strip
22
10
  end
23
- let(:command_output) { command_capture2[0].strip }
24
- let(:command_result) { command_capture2[2] }
25
11
 
26
12
  describe 'with APPMAP=false' do
27
13
  let(:env) { { 'APPMAP' => 'false' } }
@@ -2,89 +2,58 @@ require 'rails_spec_helper'
2
2
 
3
3
  describe 'SQL events' do
4
4
  include_context 'Rails app pg database', 'spec/fixtures/rails6_users_app' do
5
- around(:each) do |example|
6
- FileUtils.rm_rf tmpdir
7
- FileUtils.mkdir_p tmpdir
8
- cmd = "docker-compose run --rm -e ORM_MODULE=#{orm_module} -e RAILS_ENV=test -e APPMAP=true -v #{File.absolute_path tmpdir}:/app/tmp app ./bin/rspec spec/controllers/users_controller_api_spec.rb:#{test_line_number}"
9
- run_cmd cmd, chdir: fixture_dir
10
-
11
- example.run
12
- end
13
-
14
- let(:tmpdir) { "tmp/spec/record_sql_rails_pg_spec" }
15
-
16
- describe 'fields' do
17
- let(:test_line_number) { 8 }
18
- let(:appmap_json) { File.join(tmpdir, 'appmap/rspec/Api_UsersController_POST_api_users_with_required_parameters_creates_a_user.appmap.json') }
19
- let(:orm_module) { 'sequel' }
20
- let(:appmap) { JSON.parse(File.read(appmap_json)) }
21
- describe 'on a call event' do
22
- let(:event) do
23
- appmap['events'].find do |event|
24
- event['event'] == 'call' &&
25
- event.keys.include?('sql_query')
5
+ def self.check_queries(cases)
6
+ cases.each do |test_case, query|
7
+ context "in #{test_case}" do
8
+ let(:test_case) { test_case }
9
+ it "captures #{query}" do
10
+ expect(sql_events).to include sql_query query
26
11
  end
27
12
  end
28
- it 'do not include function-only fields' do
29
- expect(event.keys).to_not include('defined_class')
30
- expect(event.keys).to_not include('method_id')
31
- expect(event.keys).to_not include('path')
32
- expect(event.keys).to_not include('lineno')
33
- end
34
13
  end
35
14
  end
36
15
 
37
- describe 'in a Rails app' do
38
- let(:appmap) { JSON.parse(File.read(appmap_json)).to_yaml }
39
- context 'while creating a new record' do
40
- let(:test_line_number) { 8 }
41
- let(:appmap_json) { File.join(tmpdir, 'appmap/rspec/Api_UsersController_POST_api_users_with_required_parameters_creates_a_user.appmap.json') }
16
+ context 'with Sequel' do
17
+ before(:context) { run_specs 'sequel' }
42
18
 
43
- context 'using Sequel ORM' do
44
- let(:orm_module) { 'sequel' }
45
- it 'detects the sql INSERT' do
46
- expect(appmap).to include(<<-SQL_QUERY.strip)
47
- sql_query:
48
- sql: INSERT INTO "users" ("login") VALUES ('alice') RETURNING *
49
- SQL_QUERY
50
- end
51
- end
52
- context 'using ActiveRecord ORM' do
53
- let(:orm_module) { 'activerecord' }
54
- it 'detects the sql INSERT' do
55
- expect(appmap).to include(<<-SQL_QUERY.strip)
56
- sql_query:
57
- sql: INSERT INTO "users" ("login") VALUES ($1) RETURNING "id"
58
- SQL_QUERY
59
- end
60
- end
61
- end
19
+ check_queries(
20
+ 'Api_UsersController_POST_api_users_with_required_parameters_creates_a_user' =>
21
+ %(INSERT INTO "users" ("login") VALUES ('alice') RETURNING *),
22
+ 'Api_UsersController_GET_api_users_lists_the_users' => %(SELECT * FROM "users")
23
+ )
24
+ end
62
25
 
63
- context 'while listing records' do
64
- let(:test_line_number) { 29 }
65
- let(:appmap_json) { File.join(tmpdir, 'appmap/rspec/Api_UsersController_GET_api_users_lists_the_users.appmap.json') }
26
+ context 'with ActiveRecord' do
27
+ before(:context) { run_specs 'activerecord' }
66
28
 
67
- context 'using Sequel ORM' do
68
- let(:orm_module) { 'sequel' }
69
- it 'detects the sql SELECT' do
70
- expect(appmap).to include(<<-SQL_QUERY.strip)
71
- sql_query:
72
- sql: SELECT * FROM "users"
73
- SQL_QUERY
74
-
75
- expect(appmap).to include('sql:')
76
- end
77
- end
78
- context 'using ActiveRecord ORM' do
79
- let(:orm_module) { 'activerecord' }
80
- it 'detects the sql SELECT' do
81
- expect(appmap).to include(<<-SQL_QUERY.strip)
82
- sql_query:
83
- sql: SELECT "users".* FROM "users"
84
- SQL_QUERY
85
- end
86
- end
87
- end
29
+ check_queries(
30
+ 'Api_UsersController_POST_api_users_with_required_parameters_creates_a_user' =>
31
+ %(INSERT INTO "users" ("login") VALUES ($1) RETURNING "id"),
32
+ 'Api_UsersController_GET_api_users_lists_the_users' => %(SELECT "users".* FROM "users")
33
+ )
34
+ end
35
+
36
+ def run_specs(orm_module)
37
+ @app.prepare_db
38
+ @app.run_cmd \
39
+ './bin/rspec spec/controllers/users_controller_api_spec.rb:8 spec/controllers/users_controller_api_spec.rb:29',
40
+ 'ORM_MODULE' => orm_module,
41
+ 'RAILS_ENV' => 'test',
42
+ 'APPMAP' => 'true'
43
+ end
44
+
45
+ let(:appmap_json) { File.join tmpdir, "appmap/rspec/#{test_case}.appmap.json" }
46
+ let(:appmap) { JSON.parse(File.read(appmap_json)) }
47
+ let(:tmpdir) { app.tmpdir }
48
+ let(:sql_events) { appmap['events'].select { |ev| ev.include? 'sql_query' } }
49
+
50
+ RSpec::Matchers.define_negated_matcher :not_include, :include
51
+ def sql_query(query)
52
+ (include('sql_query' => (include 'sql' => query)))
53
+ .and(not_include('defined_class'))
54
+ .and(not_include('method_id'))
55
+ .and(not_include('path'))
56
+ .and(not_include('lineno'))
88
57
  end
89
58
  end
90
59
  end
@@ -1,41 +1,27 @@
1
1
  require 'rails_spec_helper'
2
+
3
+ require 'random-port'
4
+
2
5
  require 'net/http'
3
6
  require 'socket'
4
7
 
5
8
  describe 'remote recording', :order => :defined do
6
9
  include_context 'Rails app pg database', 'spec/fixtures/rails6_users_app' do
7
10
  before(:all) do
8
- fixture_dir = 'spec/fixtures/rails6_users_app'
9
- start_cmd = 'docker-compose up -d app'
10
- run_cmd({ 'ORM_MODULE' => 'sequel', 'APPMAP' => 'true' }, start_cmd, chdir: fixture_dir)
11
- Dir.chdir fixture_dir do
12
- wait_for_container 'app'
13
- end
11
+ @service_port = RandomPort::Pool::SINGLETON.acquire
12
+ @app.prepare_db
13
+ @server = @app.spawn_cmd \
14
+ "./bin/rails server -p #{@service_port}",
15
+ 'ORM_MODULE' => 'sequel',
16
+ 'APPMAP' => 'true'
14
17
 
15
- port_cmd = 'docker-compose port app 3000'
16
- port_out, = run_cmd port_cmd, chdir: fixture_dir
17
- @service_port = port_out.strip.split(':')[1]
18
-
19
- service_running = false
20
- retry_count = 0
21
18
  uri = URI("http://localhost:#{@service_port}/health")
22
19
 
23
- until service_running
24
- sleep(0.25)
25
- begin
26
- res = Net::HTTP.start(uri.hostname, uri.port) do |http|
27
- http.request(Net::HTTP::Get.new(uri))
28
- end
29
-
30
- status = res.response.code.to_i
31
- service_running = true if status >= 200 && status < 300
32
-
33
- # give up after a certain error threshold is met
34
- # we don't want to wait forever if there's an unrecoverable issue
35
- raise 'gave up waiting on fixture service' if (retry_count += 1) == 10
36
- rescue Errno::ETIMEDOUT, Errno::ECONNRESET, EOFError
37
- $stderr.print('.')
38
- end
20
+ 100.times do
21
+ Net::HTTP.get(uri)
22
+ break
23
+ rescue Errno::ECONNREFUSED
24
+ sleep 0.1
39
25
  end
40
26
  end
41
27
 
@@ -44,8 +30,10 @@ describe 'remote recording', :order => :defined do
44
30
  end
45
31
 
46
32
  after(:all) do
47
- fixture_dir = 'spec/fixtures/rails6_users_app'
48
- run_cmd 'docker-compose rm -fs app', chdir: fixture_dir
33
+ if @server
34
+ Process.kill 'INT', @server
35
+ Process.wait @server
36
+ end
49
37
  end
50
38
 
51
39
  let(:service_address) { URI("http://localhost:#{@service_port}") }
data/spec/spec_helper.rb CHANGED
@@ -13,6 +13,7 @@ require 'appmap'
13
13
 
14
14
  RSpec.configure do |config|
15
15
  config.example_status_persistence_file_path = "tmp/rspec_failed_examples.txt"
16
+ config.profile_examples = true
16
17
  end
17
18
 
18
19
  # Re-run the Rails specs without re-generating the data. This is useful for efficiently enhancing and