appmap 0.45.1 → 0.48.1

Sign up to get free protection for your applications and to get access to all the features.
data/spec/railtie_spec.rb CHANGED
@@ -4,7 +4,7 @@ describe 'AppMap tracer via Railtie' do
4
4
  include_context 'Rails app pg database', 'spec/fixtures/rails5_users_app' do
5
5
  let(:env) { {} }
6
6
 
7
- let(:cmd) { %(docker-compose run --rm -e RAILS_ENV -e APPMAP app ./bin/rails r "puts Rails.configuration.appmap.enabled.inspect") }
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
8
  let(:command_capture2) do
9
9
  require 'open3'
10
10
  Open3.capture3(env, cmd, chdir: fixture_dir).tap do |result|
@@ -23,20 +23,16 @@ describe 'AppMap tracer via Railtie' do
23
23
  let(:command_output) { command_capture2[0].strip }
24
24
  let(:command_result) { command_capture2[2] }
25
25
 
26
- it 'is disabled by default' do
27
- expect(command_output).to eq('nil')
26
+ describe 'with APPMAP=false' do
27
+ let(:env) { { 'APPMAP' => 'false' } }
28
+ it 'is disabled' do
29
+ expect(command_output).to eq('true')
30
+ end
28
31
  end
29
-
30
32
  describe 'with APPMAP=true' do
31
33
  let(:env) { { 'APPMAP' => 'true' } }
32
34
  it 'is enabled' do
33
- expect(command_output.split("\n")).to include('true')
34
- end
35
- context 'and RAILS_ENV=test' do
36
- let(:env) { { 'APPMAP' => 'true', 'RAILS_ENV' => 'test' } }
37
- it 'is disabled' do
38
- expect(command_output).to eq('nil')
39
- end
35
+ expect(command_output).to eq('false')
40
36
  end
41
37
  end
42
38
  end
data/spec/util_spec.rb CHANGED
@@ -4,8 +4,8 @@ require 'spec_helper'
4
4
  require 'appmap/util'
5
5
 
6
6
  describe AppMap::Util, docker: false do
7
- let(:subject) { AppMap::Util.method(:scenario_filename) }
8
7
  describe 'scenario_filename' do
8
+ let(:subject) { AppMap::Util.method(:scenario_filename) }
9
9
  it 'leaves short names alone' do
10
10
  expect(subject.call('foobar')).to eq('foobar.appmap.json')
11
11
  end
@@ -18,4 +18,21 @@ describe AppMap::Util, docker: false do
18
18
  expect(subject.call(fname, max_length: 50)).to eq('abcdefghijklmno-RAd_SFbH1sUZ_OXfwPsfzw.appmap.json')
19
19
  end
20
20
  end
21
+ describe 'swaggerize path' do
22
+ it 'replaces rails-style parameters' do
23
+ expect(AppMap::Util.swaggerize_path('/org/:org_id(.:format)')).to eq('/org/{org_id}')
24
+ end
25
+
26
+ it 'strips the format specifier' do
27
+ expect(AppMap::Util.swaggerize_path('/org(.:format)')).to eq('/org')
28
+ end
29
+
30
+ it 'ignores malformed parameter specs' do
31
+ expect(AppMap::Util.swaggerize_path('/org/o:rg_id')).to eq('/org/o:rg_id')
32
+ end
33
+
34
+ it 'ignores already swaggerized paths' do
35
+ expect(AppMap::Util.swaggerize_path('/org/{org_id}')).to eq('/org/{org_id}')
36
+ end
37
+ end
21
38
  end
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'test_helper'
5
+ require 'English'
6
+
7
+ class BundleVendorTest < Minitest::Test
8
+ def perform_bundle_vendor_app(test_name)
9
+ Bundler.with_clean_env do
10
+ Dir.chdir 'test/fixtures/bundle_vendor_app' do
11
+ FileUtils.rm_rf 'tmp'
12
+ FileUtils.mkdir_p 'tmp'
13
+ system 'bundle config --local local.appmap ../../..'
14
+ system 'bundle'
15
+ system(%(bundle exec ruby -Ilib -Itest cli.rb add foobar))
16
+ system({ 'APPMAP' => 'true' }, %(bundle exec ruby -Ilib -Itest cli.rb list))
17
+
18
+ yield
19
+ end
20
+ end
21
+ end
22
+
23
+ def test_record_gem
24
+ perform_bundle_vendor_app 'parser' do
25
+ appmap_file = 'tmp/bundle_vendor_app.appmap.json'
26
+ appmap = JSON.parse(File.read(appmap_file))
27
+ assert appmap['classMap'].find { |co| co['name'] == 'gli' }
28
+ assert appmap['events'].find do |e|
29
+ e['event'] == 'call' &&
30
+ e['defined_class'] = 'Hacer::Todolist' &&
31
+ e['method_id'] == 'list'
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'gli'
4
+ gem 'hacer'
5
+
6
+ appmap_gem_opts = {}
7
+ appmap_gem_opts[:path] = '../../..' if File.exist?('../../../appmap.gemspec')
8
+ gem 'appmap', appmap_gem_opts
@@ -0,0 +1,4 @@
1
+ name: bundle_vendor_app
2
+ packages:
3
+ - gem: gli
4
+ - gem: hacer
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env ruby
2
+ require 'appmap'
3
+ require 'gli'
4
+ require 'hacer'
5
+
6
+ class App
7
+ extend GLI::App
8
+
9
+ program_desc 'A simple todo list'
10
+
11
+ flag [:t,:tasklist], :default_value => File.join(ENV['HOME'],'.todolist')
12
+
13
+ pre do |global_options,command,options,args|
14
+ $todo_list = Hacer::Todolist.new(global_options[:tasklist])
15
+ end
16
+
17
+ command :add do |c|
18
+ c.action do |global_options,options,args|
19
+ $todo_list.create(args)
20
+ end
21
+ end
22
+
23
+ command :list do |c|
24
+ c.action do
25
+ $todo_list.list.each do |todo|
26
+ printf("%5d - %s\n",todo.todo_id,todo.text)
27
+ end
28
+ end
29
+ end
30
+
31
+ command :done do |c|
32
+ c.action do |global_options,options,args|
33
+ id = args.shift.to_i
34
+ $todo_list.list.each do |todo|
35
+ $todo_list.complete(todo) if todo.todo_id == id
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ exit_status = nil
42
+ invoke = -> { exit_status = App.run(ARGV) }
43
+ do_appmap = -> { ENV['APPMAP'] == 'true' }
44
+
45
+ if do_appmap.()
46
+ appmap = AppMap.record do
47
+ invoke.()
48
+ end
49
+ File.write('tmp/bundle_vendor_app.appmap.json', JSON.pretty_generate(appmap))
50
+ else
51
+ invoke.()
52
+ end
53
+ exit exit_status
54
+
data/test/gem_test.rb CHANGED
@@ -4,7 +4,7 @@
4
4
  require 'test_helper'
5
5
  require 'English'
6
6
 
7
- class MinitestTest < Minitest::Test
7
+ class GemTest < Minitest::Test
8
8
  def perform_gem_test(test_name)
9
9
  Bundler.with_clean_env do
10
10
  Dir.chdir 'test/fixtures/gem_test' do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: appmap
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.45.1
4
+ version: 0.48.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kevin Gilpin
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-05-04 00:00:00.000000000 Z
11
+ date: 2021-05-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -348,14 +348,15 @@ files:
348
348
  - lib/appmap/event.rb
349
349
  - lib/appmap/handler/function.rb
350
350
  - lib/appmap/handler/net_http.rb
351
+ - lib/appmap/handler/rails/request_handler.rb
352
+ - lib/appmap/handler/rails/sql_handler.rb
353
+ - lib/appmap/handler/rails/template.rb
351
354
  - lib/appmap/hook.rb
352
355
  - lib/appmap/hook/method.rb
353
356
  - lib/appmap/metadata.rb
354
357
  - lib/appmap/middleware/remote_recording.rb
355
358
  - lib/appmap/minitest.rb
356
359
  - lib/appmap/open.rb
357
- - lib/appmap/rails/request_handler.rb
358
- - lib/appmap/rails/sql_handler.rb
359
360
  - lib/appmap/railtie.rb
360
361
  - lib/appmap/record.rb
361
362
  - lib/appmap/rspec.rb
@@ -556,9 +557,13 @@ files:
556
557
  - spec/remote_recording_spec.rb
557
558
  - spec/spec_helper.rb
558
559
  - spec/util_spec.rb
560
+ - test/bundle_vendor_test.rb
559
561
  - test/cucumber_test.rb
560
562
  - test/expectations/openssl_test_key_sign1.json
561
563
  - test/expectations/openssl_test_key_sign2.json
564
+ - test/fixtures/bundle_vendor_app/Gemfile
565
+ - test/fixtures/bundle_vendor_app/appmap.yml
566
+ - test/fixtures/bundle_vendor_app/cli.rb
562
567
  - test/fixtures/cli_record_test/appmap.yml
563
568
  - test/fixtures/cli_record_test/lib/cli_record_test/main.rb
564
569
  - test/fixtures/cucumber4_recorder/Gemfile
@@ -1,122 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'appmap/event'
4
- require 'appmap/hook'
5
-
6
- module AppMap
7
- module Rails
8
- module RequestHandler
9
- class HTTPServerRequest < AppMap::Event::MethodEvent
10
- attr_accessor :normalized_path_info, :request_method, :path_info, :params, :mime_type, :headers, :authorization
11
-
12
- def initialize(request)
13
- super AppMap::Event.next_id_counter, :call, Thread.current.object_id
14
-
15
- self.request_method = request.request_method
16
- self.normalized_path_info = normalized_path(request)
17
- self.mime_type = request.headers['Content-Type']
18
- self.headers = AppMap::Util.select_headers(request.env)
19
- self.authorization = request.headers['Authorization']
20
- self.path_info = request.path_info.split('?')[0]
21
- # ActionDispatch::Http::ParameterFilter is deprecated
22
- parameter_filter_cls = \
23
- if defined?(ActiveSupport::ParameterFilter)
24
- ActiveSupport::ParameterFilter
25
- else
26
- ActionDispatch::Http::ParameterFilter
27
- end
28
- self.params = parameter_filter_cls.new(::Rails.application.config.filter_parameters).filter(request.params)
29
- end
30
-
31
- def to_h
32
- super.tap do |h|
33
- h[:http_server_request] = {
34
- request_method: request_method,
35
- path_info: path_info,
36
- mime_type: mime_type,
37
- normalized_path_info: normalized_path_info,
38
- authorization: authorization,
39
- headers: headers,
40
- }.compact
41
-
42
- unless params.blank?
43
- h[:message] = params.keys.map do |key|
44
- val = params[key]
45
- {
46
- name: key,
47
- class: val.class.name,
48
- value: self.class.display_string(val),
49
- object_id: val.__id__,
50
- }.tap do |message|
51
- properties = object_properties(val)
52
- message[:properties] = properties if properties
53
- end
54
- end
55
- end
56
- end
57
- end
58
-
59
- private
60
-
61
- def normalized_path(request, router = ::Rails.application.routes.router)
62
- router.recognize request do |route, _|
63
- app = route.app
64
- next unless app.matches? request
65
- return normalized_path request, app.rack_app.routes.router if app.engine?
66
-
67
- return route.path.spec.to_s
68
- end
69
- end
70
- end
71
-
72
- class HTTPServerResponse < AppMap::Event::MethodReturnIgnoreValue
73
- attr_accessor :status, :mime_type, :headers
74
-
75
- def initialize(response, parent_id, elapsed)
76
- super AppMap::Event.next_id_counter, :return, Thread.current.object_id
77
-
78
- self.status = response.status
79
- self.mime_type = response.headers['Content-Type']
80
- self.parent_id = parent_id
81
- self.elapsed = elapsed
82
- self.headers = AppMap::Util.select_headers(response.headers)
83
- end
84
-
85
- def to_h
86
- super.tap do |h|
87
- h[:http_server_response] = {
88
- status_code: status,
89
- mime_type: mime_type,
90
- headers: headers
91
- }.compact
92
- end
93
- end
94
- end
95
-
96
- class HookMethod < AppMap::Hook::Method
97
- def initialize
98
- # ActionController::Instrumentation has issued start_processing.action_controller and
99
- # process_action.action_controller since Rails 3. Therefore it's a stable place to hook
100
- # the request. Rails controller notifications can't be used directly because they don't
101
- # provide response headers, and we want the Content-Type.
102
- super(nil, ActionController::Instrumentation, ActionController::Instrumentation.instance_method(:process_action))
103
- end
104
-
105
- protected
106
-
107
- def before_hook(receiver, defined_class, _) # args
108
- call_event = HTTPServerRequest.new(receiver.request)
109
- # http_server_request events are i/o and do not require a package name.
110
- AppMap.tracing.record_event call_event, defined_class: defined_class, method: hook_method
111
- [ call_event, TIME_NOW.call ]
112
- end
113
-
114
- def after_hook(receiver, call_event, start_time, _, _) # return_value, exception
115
- elapsed = TIME_NOW.call - start_time
116
- return_event = HTTPServerResponse.new receiver.response, call_event.id, elapsed
117
- AppMap.tracing.record_event return_event
118
- end
119
- end
120
- end
121
- end
122
- end
@@ -1,150 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'appmap/event'
4
-
5
- module AppMap
6
- module Rails
7
- class SQLHandler
8
- class SQLCall < AppMap::Event::MethodCall
9
- attr_accessor :payload
10
-
11
- def initialize(payload)
12
- super AppMap::Event.next_id_counter, :call, Thread.current.object_id
13
-
14
- self.payload = payload
15
- end
16
-
17
- def to_h
18
- super.tap do |h|
19
- h[:sql_query] = {
20
- sql: payload[:sql],
21
- database_type: payload[:database_type]
22
- }.tap do |sql_query|
23
- %i[server_version].each do |attribute|
24
- sql_query[attribute] = payload[attribute] if payload[attribute]
25
- end
26
- end
27
- end
28
- end
29
- end
30
-
31
- class SQLReturn < AppMap::Event::MethodReturnIgnoreValue
32
- def initialize(parent_id, elapsed)
33
- super AppMap::Event.next_id_counter, :return, Thread.current.object_id
34
-
35
- self.parent_id = parent_id
36
- self.elapsed = elapsed
37
- end
38
- end
39
-
40
- module SQLExaminer
41
- class << self
42
- def examine(payload, sql:)
43
- return unless (examiner = build_examiner)
44
-
45
- payload[:server_version] = examiner.server_version
46
- payload[:database_type] = examiner.database_type.to_s
47
- end
48
-
49
- protected
50
-
51
- def build_examiner
52
- if defined?(Sequel)
53
- SequelExaminer.new
54
- elsif defined?(ActiveRecord)
55
- ActiveRecordExaminer.new
56
- end
57
- end
58
- end
59
-
60
- class SequelExaminer
61
- def server_version
62
- Sequel::Model.db.server_version
63
- end
64
-
65
- def database_type
66
- Sequel::Model.db.database_type.to_sym
67
- end
68
-
69
- def execute_query(sql)
70
- Sequel::Model.db[sql].all
71
- end
72
- end
73
-
74
- class ActiveRecordExaminer
75
- @@db_version_warning_issued = {}
76
-
77
- def issue_warning
78
- db_type = database_type
79
- return if @@db_version_warning_issued[db_type]
80
- warn("AppMap: Unable to determine database version for #{db_type.inspect}")
81
- @@db_version_warning_issued[db_type] = true
82
- end
83
-
84
- def server_version
85
- ActiveRecord::Base.connection.try(:database_version) || issue_warning
86
- end
87
-
88
- def database_type
89
- type = ActiveRecord::Base.connection.adapter_name.downcase.to_sym
90
- type = :postgres if type == :postgresql
91
-
92
- type
93
- end
94
-
95
- def execute_query(sql)
96
- ActiveRecord::Base.connection.execute(sql).inject([]) { |memo, r| memo << r; memo }
97
- end
98
- end
99
- end
100
-
101
- def call(_, started, finished, _, payload) # (name, started, finished, unique_id, payload)
102
- return if AppMap.tracing.empty?
103
-
104
- reentry_key = "#{self.class.name}#call"
105
- return if Thread.current[reentry_key] == true
106
-
107
- Thread.current[reentry_key] = true
108
- begin
109
- sql = payload[:sql].strip
110
-
111
- # Detect whether a function call within a specified filename is present in the call stack.
112
- find_in_backtrace = lambda do |file_name, function_name = nil|
113
- Thread.current.backtrace.find do |line|
114
- tokens = line.split(':')
115
- matches_file = tokens.find { |t| t.rindex(file_name) == (t.length - file_name.length) }
116
- matches_function = function_name.nil? || tokens.find { |t| t == "in `#{function_name}'" }
117
- matches_file && matches_function
118
- end
119
- end
120
-
121
- # Ignore SQL calls which are made while establishing a new connection.
122
- #
123
- # Example:
124
- # /path/to/ruby/2.6.0/gems/sequel-5.20.0/lib/sequel/connection_pool.rb:122:in `make_new'
125
- return if find_in_backtrace.call('lib/sequel/connection_pool.rb', 'make_new')
126
- # lib/active_record/connection_adapters/abstract/connection_pool.rb:811:in `new_connection'
127
- return if find_in_backtrace.call('lib/active_record/connection_adapters/abstract/connection_pool.rb', 'new_connection')
128
-
129
- # Ignore SQL calls which are made while inspecting the DB schema.
130
- #
131
- # Example:
132
- # /path/to/ruby/2.6.0/gems/sequel-5.20.0/lib/sequel/model/base.rb:812:in `get_db_schema'
133
- return if find_in_backtrace.call('lib/sequel/model/base.rb', 'get_db_schema')
134
- # /usr/local/bundle/gems/activerecord-5.2.3/lib/active_record/model_schema.rb:466:in `load_schema!'
135
- return if find_in_backtrace.call('lib/active_record/model_schema.rb', 'load_schema!')
136
- return if find_in_backtrace.call('lib/active_model/attribute_methods.rb', 'define_attribute_methods')
137
- return if find_in_backtrace.call('lib/active_record/connection_adapters/schema_cache.rb')
138
-
139
- SQLExaminer.examine payload, sql: sql
140
-
141
- call = SQLCall.new(payload)
142
- AppMap.tracing.record_event(call)
143
- AppMap.tracing.record_event(SQLReturn.new(call.id, finished - started))
144
- ensure
145
- Thread.current[reentry_key] = nil
146
- end
147
- end
148
- end
149
- end
150
- end