appmap 0.45.1 → 0.48.1

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.
@@ -95,7 +95,7 @@ module AppMap
95
95
  def after_hook(_receiver, call_event, start_time, return_value, exception)
96
96
  elapsed = TIME_NOW.call - start_time
97
97
  return_event = hook_package.handler_class.handle_return(call_event.id, elapsed, return_value, exception)
98
- AppMap.tracing.record_event return_event
98
+ AppMap.tracing.record_event(return_event) if return_event
99
99
  nil
100
100
  end
101
101
 
@@ -98,7 +98,7 @@ module AppMap
98
98
  if exception
99
99
  m[:exception] = {
100
100
  class: exception.class.name,
101
- message: exception.to_s
101
+ message: AppMap::Event::MethodEvent.display_string(exception.to_s)
102
102
  }
103
103
  end
104
104
  end
@@ -3,37 +3,15 @@
3
3
  module AppMap
4
4
  # Railtie connects the AppMap recorder to Rails-specific features.
5
5
  class Railtie < ::Rails::Railtie
6
- config.appmap = ActiveSupport::OrderedOptions.new
7
-
8
6
  # appmap.subscribe subscribes to ActiveSupport Notifications so that they can be recorded as
9
7
  # AppMap events.
10
8
  initializer 'appmap.subscribe' do |_| # params: app
11
- require 'appmap/rails/sql_handler'
12
- require 'appmap/rails/request_handler'
13
- ActiveSupport::Notifications.subscribe 'sql.sequel', AppMap::Rails::SQLHandler.new
14
- ActiveSupport::Notifications.subscribe 'sql.active_record', AppMap::Rails::SQLHandler.new
15
-
16
- AppMap::Rails::RequestHandler::HookMethod.new.activate
17
- end
18
-
19
- # appmap.trace begins recording an AppMap trace and writes it to appmap.json.
20
- # This behavior is only activated if the configuration setting app.config.appmap.enabled
21
- # is truthy.
22
- initializer 'appmap.trace', after: 'appmap.subscribe' do |app|
23
- lambda do
24
- return unless app.config.appmap.enabled
9
+ require 'appmap/handler/rails/sql_handler'
10
+ require 'appmap/handler/rails/request_handler'
11
+ ActiveSupport::Notifications.subscribe 'sql.sequel', AppMap::Handler::Rails::SQLHandler.new
12
+ ActiveSupport::Notifications.subscribe 'sql.active_record', AppMap::Handler::Rails::SQLHandler.new
25
13
 
26
- require 'appmap/command/record'
27
- require 'json'
28
- AppMap::Command::Record.new(AppMap.configuration).perform do |version, metadata, class_map, events|
29
- appmap = JSON.generate \
30
- version: version,
31
- metadata: metadata,
32
- classMap: class_map,
33
- events: events
34
- File.open('appmap.json', 'w').write(appmap)
35
- end
36
- end.call
14
+ AppMap::Handler::Rails::RequestHandler::HookMethod.new.activate
37
15
  end
38
16
  end
39
- end unless ENV['APPMAP_INITIALIZE'] == 'false'
17
+ end if ENV['APPMAP'] == 'true'
data/lib/appmap/rspec.rb CHANGED
@@ -183,7 +183,7 @@ module AppMap
183
183
  if exception
184
184
  m[:exception] = {
185
185
  class: exception.class.name,
186
- message: exception.to_s
186
+ message: AppMap::Event::MethodEvent.display_string(exception.to_s)
187
187
  }
188
188
  end
189
189
  end
data/lib/appmap/trace.rb CHANGED
@@ -2,14 +2,36 @@
2
2
 
3
3
  module AppMap
4
4
  module Trace
5
- class ScopedMethod < SimpleDelegator
6
- attr_reader :package, :defined_class, :static
5
+ class RubyMethod
6
+ attr_reader :class_name, :static
7
7
 
8
- def initialize(package, defined_class, method, static)
8
+ def initialize(package, class_name, method, static)
9
9
  @package = package
10
- @defined_class = defined_class
10
+ @class_name = class_name
11
+ @method = method
11
12
  @static = static
12
- super(method)
13
+ end
14
+
15
+ def source_location
16
+ @method.source_location
17
+ end
18
+
19
+ def comment
20
+ @method.comment
21
+ rescue MethodSource::SourceNotFoundError
22
+ nil
23
+ end
24
+
25
+ def package
26
+ @package.name
27
+ end
28
+
29
+ def name
30
+ @method.name
31
+ end
32
+
33
+ def labels
34
+ @package.labels
13
35
  end
14
36
  end
15
37
 
@@ -43,6 +65,12 @@ module AppMap
43
65
  end
44
66
  end
45
67
 
68
+ def record_method(method)
69
+ @tracers.each do |tracer|
70
+ tracer.record_method(method)
71
+ end
72
+ end
73
+
46
74
  def delete(tracer)
47
75
  return unless @tracers.member?(tracer)
48
76
 
@@ -83,10 +111,22 @@ module AppMap
83
111
  @last_package_for_thread[Thread.current.object_id] = package if package
84
112
  @events << event
85
113
  static = event.static if event.respond_to?(:static)
86
- @methods << Trace::ScopedMethod.new(package, defined_class, method, static) \
114
+ @methods << Trace::RubyMethod.new(package, defined_class, method, static) \
87
115
  if package && defined_class && method && (event.event == :call)
88
116
  end
89
117
 
118
+ # +method+ should be duck-typed to respond to the following:
119
+ # * package
120
+ # * defined_class
121
+ # * name
122
+ # * static
123
+ # * comment
124
+ # * labels
125
+ # * source_location
126
+ def record_method(method)
127
+ @methods << method
128
+ end
129
+
90
130
  # Gets the last package which was observed on the current thread.
91
131
  def last_package_for_current_thread
92
132
  @last_package_for_thread[Thread.current.object_id]
data/lib/appmap/util.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'bundler'
4
+
3
5
  module AppMap
4
6
  module Util
5
7
  class << self
@@ -94,13 +96,23 @@ module AppMap
94
96
  end
95
97
 
96
98
  def normalize_path(path)
97
- if path.index(Dir.pwd) == 0
99
+ if path.index(Dir.pwd) == 0 && !path.index(Bundler.bundle_path.to_s)
98
100
  path[Dir.pwd.length + 1..-1]
99
101
  else
100
102
  path
101
103
  end
102
104
  end
103
105
 
106
+ # Convert a Rails-style path from /org/:org_id(.:format)
107
+ # to Swagger-style paths like /org/{org_id}
108
+ def swaggerize_path(path)
109
+ path = path.split('(.')[0]
110
+ tokens = path.split('/')
111
+ tokens.map do |token|
112
+ token.gsub /^:(.*)/, '{\1}'
113
+ end.join('/')
114
+ end
115
+
104
116
  # Atomically writes AppMap data to +filename+.
105
117
  def write_appmap(filename, appmap)
106
118
  require 'fileutils'
@@ -3,7 +3,7 @@
3
3
  module AppMap
4
4
  URL = 'https://github.com/applandinc/appmap-ruby'
5
5
 
6
- VERSION = '0.45.1'
6
+ VERSION = '0.48.1'
7
7
 
8
- APPMAP_FORMAT_VERSION = '1.5.0'
8
+ APPMAP_FORMAT_VERSION = '1.5.1'
9
9
  end
data/package-lock.json CHANGED
@@ -551,9 +551,9 @@
551
551
  }
552
552
  },
553
553
  "lodash": {
554
- "version": "4.17.19",
555
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
556
- "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ=="
554
+ "version": "4.17.21",
555
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
556
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
557
557
  },
558
558
  "longest": {
559
559
  "version": "1.0.1",
@@ -42,7 +42,7 @@ describe 'Rails' do
42
42
  hash_including(
43
43
  'http_server_request' => hash_including(
44
44
  'request_method' => 'POST',
45
- 'normalized_path_info' => '/api/users(.:format)',
45
+ 'normalized_path_info' => '/api/users',
46
46
  'path_info' => '/api/users'
47
47
  ),
48
48
  'message' => include(
@@ -144,7 +144,49 @@ describe 'Rails' do
144
144
  end
145
145
 
146
146
  describe 'a UI route' do
147
- describe 'rendering a page' do
147
+ describe 'rendering a page using a template file' do
148
+ let(:appmap_json_file) do
149
+ 'UsersController_GET_users_lists_the_users.appmap.json'
150
+ end
151
+
152
+ it 'records the template file' do
153
+ expect(events).to include hash_including(
154
+ 'event' => 'call',
155
+ 'defined_class' => 'app_views_users_index_html_haml',
156
+ 'method_id' => 'render',
157
+ 'path' => 'app/views/users/index.html.haml'
158
+ )
159
+
160
+ expect(appmap['classMap']).to include hash_including(
161
+ 'name' => 'app/views',
162
+ 'children' => include(hash_including(
163
+ 'name' => 'app_views_users_index_html_haml',
164
+ 'children' => include(hash_including(
165
+ 'name' => 'render',
166
+ 'type' => 'function',
167
+ 'location' => 'app/views/users/index.html.haml',
168
+ 'static' => true,
169
+ 'labels' => [ 'mvc.template' ]
170
+ ))
171
+ ))
172
+ )
173
+ expect(appmap['classMap']).to include hash_including(
174
+ 'name' => 'app/views',
175
+ 'children' => include(hash_including(
176
+ 'name' => 'app_views_layouts_application_html_haml',
177
+ 'children' => include(hash_including(
178
+ 'name' => 'render',
179
+ 'type' => 'function',
180
+ 'location' => 'app/views/layouts/application.html.haml',
181
+ 'static' => true,
182
+ 'labels' => [ 'mvc.template' ]
183
+ ))
184
+ ))
185
+ )
186
+ end
187
+ end
188
+
189
+ describe 'rendering a page using a text template' do
148
190
  let(:appmap_json_file) do
149
191
  'UsersController_GET_users_login_shows_the_user.appmap.json'
150
192
  end
@@ -155,7 +197,7 @@ describe 'Rails' do
155
197
  'http_server_request' => {
156
198
  'request_method' => 'GET',
157
199
  'path_info' => '/users/alice',
158
- 'normalized_path_info' => '/users/:id(.:format)',
200
+ 'normalized_path_info' => '/users/{id}',
159
201
  'headers' => {
160
202
  'Host' => 'test.host',
161
203
  'User-Agent' => 'Rails Testing'
@@ -165,15 +207,32 @@ describe 'Rails' do
165
207
  )
166
208
  end
167
209
 
210
+ it 'ignores the text template' do
211
+ expect(events).to_not include hash_including(
212
+ 'event' => 'call',
213
+ 'method_id' => 'render',
214
+ 'render_template' => anything
215
+ )
216
+
217
+ expect(appmap['classMap']).to_not include hash_including(
218
+ 'name' => 'views',
219
+ 'children' => include(hash_including(
220
+ 'name' => 'ViewTemplate',
221
+ 'children' => include(hash_including(
222
+ 'name' => 'render',
223
+ 'type' => 'function',
224
+ 'location' => 'text template'
225
+ ))
226
+ ))
227
+ )
228
+ end
229
+
168
230
  it 'records and labels view rendering' do
169
231
  expect(events).to include hash_including(
170
232
  'event' => 'call',
171
233
  'thread_id' => Numeric,
172
- 'defined_class' => 'ActionView::Renderer',
173
- 'method_id' => 'render',
174
- 'path' => String,
175
- 'lineno' => Integer,
176
- 'static' => false
234
+ 'defined_class' => 'inline_template',
235
+ 'method_id' => 'render'
177
236
  )
178
237
 
179
238
  expect(appmap['classMap']).to include hash_including(
@@ -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)
@@ -53,3 +53,9 @@ class ToSRaises
53
53
  "hello"
54
54
  end
55
55
  end
56
+
57
+ class ExceptionMethod
58
+ def raise_illegal_utf8_message
59
+ raise "809: unexpected token at 'x\x9C\xED=\x8Bv\xD3ƶ\xBF2\xB8]\xC5\xE9qdI\x96eǫ4\xA4h΅\x84\xE5z\x96\xAA\xD8\xE3\xE3D\xB2\xE4J2\x90E\xF8\xF7\xBB\xF7\xCC\xE81\x92\xE2\x88ā'"
60
+ end
61
+ end
@@ -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
@@ -64,65 +64,14 @@ describe 'AppMap class Hooking', docker: false do
64
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
@@ -250,8 +199,8 @@ describe 'AppMap class Hooking', docker: false do
250
199
  _, tracer = invoke_test_file 'spec/fixtures/hook/instance_method.rb' do
251
200
  InstanceMethod.new.say_default
252
201
  end
253
- expect(tracer.event_methods.to_a.map(&:defined_class)).to eq([ 'InstanceMethod' ])
254
- 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 ])
255
204
  end
256
205
 
257
206
  it 'builds a class map of invoked methods' do
@@ -634,7 +583,7 @@ describe 'AppMap class Hooking', docker: false do
634
583
  end
635
584
  end
636
585
 
637
- it 'Reports exceptions' do
586
+ it 'reports exceptions' do
638
587
  events_yaml = <<~YAML
639
588
  ---
640
589
  - :id: 1
@@ -666,6 +615,38 @@ describe 'AppMap class Hooking', docker: false do
666
615
  end
667
616
  end
668
617
 
618
+ it 'sanitizes exception messages' do
619
+ events_yaml = <<~YAML
620
+ ---
621
+ - :id: 1
622
+ :event: :call
623
+ :defined_class: ExceptionMethod
624
+ :method_id: raise_illegal_utf8_message
625
+ :path: spec/fixtures/hook/exception_method.rb
626
+ :lineno: 58
627
+ :static: false
628
+ :parameters: []
629
+ :receiver:
630
+ :class: ExceptionMethod
631
+ :value: Exception Method fixture
632
+ - :id: 2
633
+ :event: :return
634
+ :parent_id: 1
635
+ :exceptions:
636
+ - :class: RuntimeError
637
+ :message: '809: unexpected token at ''x__=_v_ƶ_2_]__qdI_eǫ4_h΅__z_____D__J2_E______1__ā'''
638
+ :path: spec/fixtures/hook/exception_method.rb
639
+ :lineno: 59
640
+ YAML
641
+ test_hook_behavior 'spec/fixtures/hook/exception_method.rb', events_yaml do
642
+ begin
643
+ ExceptionMethod.new.raise_illegal_utf8_message
644
+ rescue
645
+ # don't let the exception fail the test
646
+ end
647
+ end
648
+ end
649
+
669
650
  context 'string conversions works for the receiver when' do
670
651
 
671
652
  it 'is missing #to_s' do