appmap 0.45.0 → 0.48.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.
@@ -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