appmap 0.44.0 → 0.47.1

Sign up to get free protection for your applications and to get access to all the features.
data/lib/appmap/util.rb CHANGED
@@ -61,8 +61,16 @@ module AppMap
61
61
  delete_object_id = ->(obj) { (obj || {}).delete(:object_id) }
62
62
  delete_object_id.call(event[:receiver])
63
63
  delete_object_id.call(event[:return_value])
64
- (event[:parameters] || []).each(&delete_object_id)
65
- (event[:exceptions] || []).each(&delete_object_id)
64
+ %i[parameters exceptions message].each do |field|
65
+ (event[field] || []).each(&delete_object_id)
66
+ end
67
+ %i[http_client_request http_client_response http_server_request http_server_response].each do |field|
68
+ headers = event.dig(field, :headers)
69
+ next unless headers
70
+
71
+ headers['Date'] = '<instanceof date>' if headers['Date']
72
+ headers['Server'] = headers['Server'].match(/^(\w+)/)[0] if headers['Server']
73
+ end
66
74
 
67
75
  case event[:event]
68
76
  when :call
@@ -72,6 +80,37 @@ module AppMap
72
80
  event
73
81
  end
74
82
 
83
+ def select_headers(env)
84
+ # Rack prepends HTTP_ to all client-sent headers.
85
+ matching_headers = env
86
+ .select { |k,v| k.start_with? 'HTTP_'}
87
+ .reject { |k,v| v.blank? }
88
+ .each_with_object({}) do |kv, memo|
89
+ key = kv[0].sub(/^HTTP_/, '').split('_').map(&:capitalize).join('-')
90
+ value = kv[1]
91
+ memo[key] = value
92
+ end
93
+ matching_headers.blank? ? nil : matching_headers
94
+ end
95
+
96
+ def normalize_path(path)
97
+ if path.index(Dir.pwd) == 0
98
+ path[Dir.pwd.length + 1..-1]
99
+ else
100
+ path
101
+ end
102
+ end
103
+
104
+ # Convert a Rails-style path from /org/:org_id(.:format)
105
+ # to Swagger-style paths like /org/{org_id}
106
+ def swaggerize_path(path)
107
+ path = path.split('(.')[0]
108
+ tokens = path.split('/')
109
+ tokens.map do |token|
110
+ token.gsub /^:(.*)/, '{\1}'
111
+ end.join('/')
112
+ end
113
+
75
114
  # Atomically writes AppMap data to +filename+.
76
115
  def write_appmap(filename, appmap)
77
116
  require 'fileutils'
@@ -3,7 +3,7 @@
3
3
  module AppMap
4
4
  URL = 'https://github.com/applandinc/appmap-ruby'
5
5
 
6
- VERSION = '0.44.0'
6
+ VERSION = '0.47.1'
7
7
 
8
- APPMAP_FORMAT_VERSION = '1.4'
8
+ APPMAP_FORMAT_VERSION = '1.5.0'
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",
data/release.sh ADDED
@@ -0,0 +1,17 @@
1
+ #!/bin/bash
2
+ # using bash wrapper as Rake blows up in `require/extentiontask` (line 10)
3
+
4
+ RELEASE_FLAGS=""
5
+ if [ ! -z "$TRAVIS_REPO_SLUG" ]; then
6
+ RELEASE_FLAGS="-r git+https://github.com/${TRAVIS_REPO_SLUG}.git"
7
+ fi
8
+
9
+ if [ ! -z "$GEM_ALTERNATIVE_NAME" ]; then
10
+ echo "Release: GEM_ALTERNATIVE_NAME=$GEM_ALTERNATIVE_NAME"
11
+ else
12
+ echo "No GEM_ALTERNATIVE_NAME is provided, releasing gem with default name ('appmap')"
13
+ fi
14
+
15
+ set -x
16
+ exec semantic-release $RELEASE_FLAGS
17
+
@@ -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(
@@ -67,8 +67,8 @@ describe 'Rails' do
67
67
  expect(events).to include(
68
68
  hash_including(
69
69
  'http_server_response' => hash_including(
70
- 'status' => 201,
71
- 'mime_type' => 'application/json; charset=utf-8'
70
+ 'status_code' => 201,
71
+ 'mime_type' => 'application/json; charset=utf-8',
72
72
  )
73
73
  )
74
74
  )
@@ -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,25 +197,46 @@ 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}',
201
+ 'headers' => {
202
+ 'Host' => 'test.host',
203
+ 'User-Agent' => 'Rails Testing'
204
+ }
159
205
  }
160
206
  )
161
207
  )
162
208
  end
163
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
+
164
230
  it 'records and labels view rendering' do
165
231
  expect(events).to include hash_including(
166
232
  'event' => 'call',
167
233
  'thread_id' => Numeric,
168
- 'defined_class' => 'ActionView::Renderer',
169
- 'method_id' => 'render',
170
- 'path' => String,
171
- 'lineno' => Integer,
172
- 'static' => false
234
+ 'defined_class' => 'inline_template',
235
+ 'method_id' => 'render'
173
236
  )
174
237
 
175
238
  expect(appmap['classMap']).to include hash_including(
176
- 'name' => 'action_view',
239
+ 'name' => 'actionview',
177
240
  'children' => include(hash_including(
178
241
  'name' => 'ActionView',
179
242
  'children' => 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)
data/spec/config_spec.rb CHANGED
@@ -34,10 +34,12 @@ describe AppMap::Config, docker: false do
34
34
  name: 'test',
35
35
  packages: [
36
36
  {
37
- path: 'path-1'
37
+ path: 'path-1',
38
+ handler_class: 'AppMap::Handler::Function'
38
39
  },
39
40
  {
40
41
  path: 'path-2',
42
+ handler_class: 'AppMap::Handler::Function',
41
43
  exclude: [ 'exclude-1' ]
42
44
  }
43
45
  ],
data/spec/hook_spec.rb CHANGED
@@ -16,14 +16,7 @@ end
16
16
  Psych::Visitors::YAMLTree.prepend(ShowYamlNulls)
17
17
 
18
18
  describe 'AppMap class Hooking', docker: false do
19
- require 'appmap/util'
20
- def collect_events(tracer)
21
- [].tap do |events|
22
- while tracer.event?
23
- events << tracer.next_event.to_h
24
- end
25
- end.map(&AppMap::Util.method(:sanitize_event))
26
- end
19
+ include_context 'collect events'
27
20
 
28
21
  def invoke_test_file(file, setup: nil, &block)
29
22
  AppMap.configuration = nil
@@ -67,69 +60,18 @@ describe 'AppMap class Hooking', docker: false do
67
60
  config = AppMap::Config.new('hook_spec', [ package ], exclude: %w[ExcludeTest])
68
61
  AppMap.configuration = config
69
62
 
70
- expect(config.never_hook?(ExcludeTest.new.method(:instance_method))).to be_truthy
71
- 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
72
65
  end
73
66
 
74
- it "handles an instance method named 'call' without issues" do
67
+ it "an instance method named 'call' will be ignored" do
75
68
  events_yaml = <<~YAML
76
- ---
77
- - :id: 1
78
- :event: :call
79
- :defined_class: MethodNamedCall
80
- :method_id: call
81
- :path: spec/fixtures/hook/method_named_call.rb
82
- :lineno: 8
83
- :static: false
84
- :parameters:
85
- - :name: :a
86
- :class: Integer
87
- :value: '1'
88
- :kind: :req
89
- - :name: :b
90
- :class: Integer
91
- :value: '2'
92
- :kind: :req
93
- - :name: :c
94
- :class: Integer
95
- :value: '3'
96
- :kind: :req
97
- - :name: :d
98
- :class: Integer
99
- :value: '4'
100
- :kind: :req
101
- - :name: :e
102
- :class: Integer
103
- :value: '5'
104
- :kind: :req
105
- :receiver:
106
- :class: MethodNamedCall
107
- :value: MethodNamedCall
108
- - :id: 2
109
- :event: :return
110
- :parent_id: 1
111
- :return_value:
112
- :class: String
113
- :value: 1 2 3 4 5
69
+ --- []
114
70
  YAML
115
71
 
116
72
  _, tracer = test_hook_behavior 'spec/fixtures/hook/method_named_call.rb', events_yaml do
117
73
  expect(MethodNamedCall.new.call(1, 2, 3, 4, 5)).to eq('1 2 3 4 5')
118
74
  end
119
- class_map = AppMap.class_map(tracer.event_methods)
120
- expect(Diffy::Diff.new(<<~CLASSMAP, YAML.dump(class_map)).to_s).to eq('')
121
- ---
122
- - :name: spec/fixtures/hook/method_named_call.rb
123
- :type: package
124
- :children:
125
- - :name: MethodNamedCall
126
- :type: class
127
- :children:
128
- - :name: call
129
- :type: function
130
- :location: spec/fixtures/hook/method_named_call.rb:8
131
- :static: false
132
- CLASSMAP
133
75
  end
134
76
 
135
77
  it 'can custom hook and label a function' do
@@ -170,7 +112,9 @@ describe 'AppMap class Hooking', docker: false do
170
112
  method = hook_cls.instance_method(:say_default)
171
113
 
172
114
  require 'appmap/hook/method'
173
- 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)
174
118
  hook_method.activate
175
119
 
176
120
  tracer = AppMap.tracing.trace
@@ -255,8 +199,8 @@ describe 'AppMap class Hooking', docker: false do
255
199
  _, tracer = invoke_test_file 'spec/fixtures/hook/instance_method.rb' do
256
200
  InstanceMethod.new.say_default
257
201
  end
258
- expect(tracer.event_methods.to_a.map(&:defined_class)).to eq([ 'InstanceMethod' ])
259
- 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 ])
260
204
  end
261
205
 
262
206
  it 'builds a class map of invoked methods' do
@@ -868,7 +812,9 @@ describe 'AppMap class Hooking', docker: false do
868
812
  _, _, events = test_hook_behavior 'spec/fixtures/hook/compare.rb', nil do
869
813
  expect(Compare.compare('string', 'string')).to be_truthy
870
814
  end
815
+
871
816
  secure_compare_event = YAML.load(events).find { |evt| evt[:defined_class] == 'ActiveSupport::SecurityUtils' }
817
+ expect(secure_compare_event).to be_truthy
872
818
  secure_compare_event.delete(:lineno)
873
819
  secure_compare_event.delete(:path)
874
820
 
@@ -0,0 +1,160 @@
1
+ require 'spec_helper'
2
+ require 'diffy'
3
+ require 'rack'
4
+ require 'rack/handler/webrick'
5
+
6
+ class HelloWorldApp
7
+ def call(env)
8
+ req = Rack::Request.new(env)
9
+ case req.path_info
10
+ when /hello/
11
+ [200, {"Content-Type" => "text/html"}, ["Hello World!"]]
12
+ when /goodbye/
13
+ [500, {"Content-Type" => "text/html"}, ["Goodbye Cruel World!"]]
14
+ else
15
+ [404, {"Content-Type" => "text/html"}, ["I'm Lost!"]]
16
+ end
17
+ end
18
+ end
19
+
20
+ describe 'Net::HTTP handler' do
21
+ include_context 'collect events'
22
+
23
+ def get_hello(params: nil)
24
+ http = Net::HTTP.new('localhost', 19292)
25
+ http.get [ '/hello', params ].compact.join('?')
26
+ end
27
+
28
+ before(:all) do
29
+ @rack_thread = Thread.new do
30
+ Rack::Handler::WEBrick.run HelloWorldApp.new, Port: 19292
31
+ end
32
+ 10.times do
33
+ sleep 0.1
34
+ break if get_hello.code.to_i == 200
35
+ end
36
+ raise "Web server didn't start" unless get_hello.code.to_i == 200
37
+ end
38
+
39
+ after(:all) do
40
+ @rack_thread.kill
41
+ end
42
+
43
+ def start_recording
44
+ AppMap.configuration = configuration
45
+ AppMap::Hook.new(configuration).enable
46
+
47
+ @tracer = AppMap.tracing.trace
48
+ AppMap::Event.reset_id_counter
49
+ end
50
+
51
+ def record(&block)
52
+ start_recording
53
+ begin
54
+ yield
55
+ ensure
56
+ stop_recording
57
+ end
58
+ end
59
+
60
+ def stop_recording
61
+ AppMap.tracing.delete(@tracer)
62
+ end
63
+
64
+ context 'with trace enabled' do
65
+ let(:configuration) { AppMap::Config.new('record_net_http_spec', []) }
66
+
67
+ after do
68
+ AppMap.configuration = nil
69
+ end
70
+
71
+ describe 'GET request' do
72
+ it 'with a single query parameter' do
73
+ record do
74
+ get_hello(params: 'msg=hi')
75
+ end
76
+
77
+ events = collect_events(@tracer).to_yaml
78
+ expect(Diffy::Diff.new(<<~EVENTS, events).to_s).to eq('')
79
+ ---
80
+ - :id: 1
81
+ :event: :call
82
+ :http_client_request:
83
+ :request_method: GET
84
+ :url: http://localhost:19292/hello
85
+ :headers:
86
+ Accept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3
87
+ Accept: "*/*"
88
+ User-Agent: Ruby
89
+ Connection: close
90
+ :message:
91
+ - :name: msg
92
+ :class: String
93
+ :value: hi
94
+ - :id: 2
95
+ :event: :return
96
+ :parent_id: 1
97
+ :http_client_response:
98
+ :status_code: 200
99
+ :headers:
100
+ Content-Type: text/html
101
+ Server: WEBrick
102
+ Date: "<instanceof date>"
103
+ Content-Length: '12'
104
+ Connection: close
105
+ EVENTS
106
+ end
107
+
108
+ it 'with a multi-valued query parameter' do
109
+ record do
110
+ get_hello(params: 'ary[]=1&ary[]=2')
111
+ end
112
+
113
+ event = collect_events(@tracer).first.to_yaml
114
+ expect(Diffy::Diff.new(<<~EVENT, event).to_s).to eq('')
115
+ ---
116
+ :id: 1
117
+ :event: :call
118
+ :http_client_request:
119
+ :request_method: GET
120
+ :url: http://localhost:19292/hello
121
+ :headers:
122
+ Accept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3
123
+ Accept: "*/*"
124
+ User-Agent: Ruby
125
+ Connection: close
126
+ :message:
127
+ - :name: ary
128
+ :class: Array
129
+ :value: '["1", "2"]'
130
+ EVENT
131
+ end
132
+
133
+ it 'with a URL encoded query parameter' do
134
+ msg = 'foo/bar?baz'
135
+ record do
136
+ get_hello(params: "msg=#{CGI.escape msg}")
137
+ end
138
+
139
+ event = collect_events(@tracer).first.to_yaml
140
+ expect(Diffy::Diff.new(<<~EVENT, event).to_s).to eq('')
141
+ ---
142
+ :id: 1
143
+ :event: :call
144
+ :http_client_request:
145
+ :request_method: GET
146
+ :url: http://localhost:19292/hello
147
+ :headers:
148
+ Accept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3
149
+ Accept: "*/*"
150
+ User-Agent: Ruby
151
+ Connection: close
152
+ :message:
153
+ - :name: msg
154
+ :class: String
155
+ :value: #{msg}
156
+ EVENT
157
+ end
158
+ end
159
+ end
160
+ end