appmap 0.45.0 → 0.48.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -5,7 +5,7 @@ require 'spec_helper'
5
5
  describe 'AppMap::ClassMap' do
6
6
  describe '.build_from_methods' do
7
7
  it 'includes method comment' do
8
- map = AppMap.class_map([scoped_method((method :test_method))])
8
+ map = AppMap.class_map([ruby_method((method :test_method))])
9
9
  function = dig_map(map, 5)[0]
10
10
  expect(function).to include(:comment)
11
11
  end
@@ -15,8 +15,8 @@ describe 'AppMap::ClassMap' do
15
15
  'test method body'
16
16
  end
17
17
 
18
- def scoped_method(method)
19
- AppMap::Trace::ScopedMethod.new AppMap::Config::Package.new, method.receiver.class.name, method, false
18
+ def ruby_method(method)
19
+ AppMap::Trace::RubyMethod.new AppMap::Config::Package.new, method.receiver.class.name, method, false
20
20
  end
21
21
 
22
22
  def dig_map(map, depth)
@@ -38,8 +38,6 @@ module UsersApp
38
38
  # Initialize configuration defaults for originally generated Rails version.
39
39
  config.load_defaults 5.2
40
40
 
41
- config.appmap.enabled = true if ENV['APPMAP'] == 'true' && !Rails.env.test?
42
-
43
41
  # Settings in config/environments/* take precedence over those specified here.
44
42
  # Application configuration can go into files in config/initializers
45
43
  # -- all .rb files in that directory are automatically loaded after loading
@@ -38,8 +38,6 @@ module UsersApp
38
38
  # Initialize configuration defaults for originally generated Rails version.
39
39
  config.load_defaults 5.2
40
40
 
41
- config.appmap.enabled = true if ENV['APPMAP'] == 'true' && !Rails.env.test?
42
-
43
41
  # Settings in config/environments/* take precedence over those specified here.
44
42
  # Application configuration can go into files in config/initializers
45
43
  # -- all .rb files in that directory are automatically loaded after loading
data/spec/hook_spec.rb CHANGED
@@ -60,69 +60,18 @@ describe 'AppMap class Hooking', docker: false do
60
60
  config = AppMap::Config.new('hook_spec', [ package ], exclude: %w[ExcludeTest])
61
61
  AppMap.configuration = config
62
62
 
63
- expect(config.never_hook?(ExcludeTest.new.method(:instance_method))).to be_truthy
64
- expect(config.never_hook?(ExcludeTest.method(:cls_method))).to be_truthy
63
+ expect(config.never_hook?(ExcludeTest, ExcludeTest.new.method(:instance_method))).to be_truthy
64
+ expect(config.never_hook?(ExcludeTest, ExcludeTest.method(:cls_method))).to be_truthy
65
65
  end
66
66
 
67
- it "handles an instance method named 'call' without issues" do
67
+ it "an instance method named 'call' will be ignored" do
68
68
  events_yaml = <<~YAML
69
- ---
70
- - :id: 1
71
- :event: :call
72
- :defined_class: MethodNamedCall
73
- :method_id: call
74
- :path: spec/fixtures/hook/method_named_call.rb
75
- :lineno: 8
76
- :static: false
77
- :parameters:
78
- - :name: :a
79
- :class: Integer
80
- :value: '1'
81
- :kind: :req
82
- - :name: :b
83
- :class: Integer
84
- :value: '2'
85
- :kind: :req
86
- - :name: :c
87
- :class: Integer
88
- :value: '3'
89
- :kind: :req
90
- - :name: :d
91
- :class: Integer
92
- :value: '4'
93
- :kind: :req
94
- - :name: :e
95
- :class: Integer
96
- :value: '5'
97
- :kind: :req
98
- :receiver:
99
- :class: MethodNamedCall
100
- :value: MethodNamedCall
101
- - :id: 2
102
- :event: :return
103
- :parent_id: 1
104
- :return_value:
105
- :class: String
106
- :value: 1 2 3 4 5
69
+ --- []
107
70
  YAML
108
71
 
109
72
  _, tracer = test_hook_behavior 'spec/fixtures/hook/method_named_call.rb', events_yaml do
110
73
  expect(MethodNamedCall.new.call(1, 2, 3, 4, 5)).to eq('1 2 3 4 5')
111
74
  end
112
- class_map = AppMap.class_map(tracer.event_methods)
113
- expect(Diffy::Diff.new(<<~CLASSMAP, YAML.dump(class_map)).to_s).to eq('')
114
- ---
115
- - :name: spec/fixtures/hook/method_named_call.rb
116
- :type: package
117
- :children:
118
- - :name: MethodNamedCall
119
- :type: class
120
- :children:
121
- - :name: call
122
- :type: function
123
- :location: spec/fixtures/hook/method_named_call.rb:8
124
- :static: false
125
- CLASSMAP
126
75
  end
127
76
 
128
77
  it 'can custom hook and label a function' do
@@ -163,7 +112,9 @@ describe 'AppMap class Hooking', docker: false do
163
112
  method = hook_cls.instance_method(:say_default)
164
113
 
165
114
  require 'appmap/hook/method'
166
- hook_method = AppMap::Hook::Method.new(config.package_for_method(method), hook_cls, method)
115
+ package = config.lookup_package(hook_cls, method)
116
+ expect(package).to be
117
+ hook_method = AppMap::Hook::Method.new(package, hook_cls, method)
167
118
  hook_method.activate
168
119
 
169
120
  tracer = AppMap.tracing.trace
@@ -248,8 +199,8 @@ describe 'AppMap class Hooking', docker: false do
248
199
  _, tracer = invoke_test_file 'spec/fixtures/hook/instance_method.rb' do
249
200
  InstanceMethod.new.say_default
250
201
  end
251
- expect(tracer.event_methods.to_a.map(&:defined_class)).to eq([ 'InstanceMethod' ])
252
- expect(tracer.event_methods.to_a.map(&:to_s)).to eq([ InstanceMethod.public_instance_method(:say_default).to_s ])
202
+ expect(tracer.event_methods.to_a.map(&:class_name)).to eq([ 'InstanceMethod' ])
203
+ expect(tracer.event_methods.to_a.map(&:name)).to eq([ InstanceMethod.public_instance_method(:say_default).name ])
253
204
  end
254
205
 
255
206
  it 'builds a class map of invoked methods' do
@@ -861,7 +812,9 @@ describe 'AppMap class Hooking', docker: false do
861
812
  _, _, events = test_hook_behavior 'spec/fixtures/hook/compare.rb', nil do
862
813
  expect(Compare.compare('string', 'string')).to be_truthy
863
814
  end
815
+
864
816
  secure_compare_event = YAML.load(events).find { |evt| evt[:defined_class] == 'ActiveSupport::SecurityUtils' }
817
+ expect(secure_compare_event).to be_truthy
865
818
  secure_compare_event.delete(:lineno)
866
819
  secure_compare_event.delete(:path)
867
820
 
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
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.0
4
+ version: 0.48.0
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-03 00:00:00.000000000 Z
11
+ date: 2021-05-19 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
@@ -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