appmap 0.41.2 → 0.45.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.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/.releaserc.yml +11 -0
  3. data/.travis.yml +23 -2
  4. data/CHANGELOG.md +40 -0
  5. data/README.md +36 -6
  6. data/README_CI.md +29 -0
  7. data/Rakefile +4 -2
  8. data/appmap.gemspec +5 -3
  9. data/lib/appmap.rb +4 -2
  10. data/lib/appmap/class_map.rb +7 -10
  11. data/lib/appmap/config.rb +98 -28
  12. data/lib/appmap/cucumber.rb +1 -1
  13. data/lib/appmap/event.rb +18 -0
  14. data/lib/appmap/handler/function.rb +19 -0
  15. data/lib/appmap/handler/net_http.rb +107 -0
  16. data/lib/appmap/hook.rb +42 -22
  17. data/lib/appmap/hook/method.rb +5 -7
  18. data/lib/appmap/minitest.rb +35 -30
  19. data/lib/appmap/rails/request_handler.rb +30 -17
  20. data/lib/appmap/record.rb +1 -1
  21. data/lib/appmap/rspec.rb +32 -96
  22. data/lib/appmap/trace.rb +2 -1
  23. data/lib/appmap/util.rb +39 -2
  24. data/lib/appmap/version.rb +2 -2
  25. data/release.sh +17 -0
  26. data/spec/abstract_controller_base_spec.rb +76 -29
  27. data/spec/class_map_spec.rb +3 -11
  28. data/spec/config_spec.rb +33 -1
  29. data/spec/fixtures/hook/custom_instance_method.rb +11 -0
  30. data/spec/fixtures/hook/method_named_call.rb +11 -0
  31. data/spec/fixtures/rails5_users_app/Gemfile +7 -3
  32. data/spec/fixtures/rails5_users_app/app/controllers/api/users_controller.rb +2 -0
  33. data/spec/fixtures/rails5_users_app/app/controllers/users_controller.rb +9 -1
  34. data/spec/fixtures/rails5_users_app/config/application.rb +2 -0
  35. data/spec/fixtures/rails5_users_app/create_app +8 -2
  36. data/spec/fixtures/rails5_users_app/spec/controllers/users_controller_api_spec.rb +16 -3
  37. data/spec/fixtures/rails5_users_app/spec/controllers/users_controller_spec.rb +2 -2
  38. data/spec/fixtures/rails5_users_app/spec/models/user_spec.rb +2 -12
  39. data/spec/fixtures/rails5_users_app/spec/rails_helper.rb +3 -9
  40. data/spec/fixtures/rails6_users_app/Gemfile +5 -4
  41. data/spec/fixtures/rails6_users_app/app/controllers/api/users_controller.rb +1 -0
  42. data/spec/fixtures/rails6_users_app/app/controllers/users_controller.rb +9 -1
  43. data/spec/fixtures/rails6_users_app/config/application.rb +2 -0
  44. data/spec/fixtures/rails6_users_app/create_app +8 -2
  45. data/spec/fixtures/rails6_users_app/spec/controllers/users_controller_api_spec.rb +16 -3
  46. data/spec/fixtures/rails6_users_app/spec/controllers/users_controller_spec.rb +2 -2
  47. data/spec/fixtures/rails6_users_app/spec/models/user_spec.rb +2 -12
  48. data/spec/fixtures/rails6_users_app/spec/rails_helper.rb +3 -9
  49. data/spec/hook_spec.rb +135 -18
  50. data/spec/record_net_http_spec.rb +160 -0
  51. data/spec/record_sql_rails_pg_spec.rb +1 -1
  52. data/spec/spec_helper.rb +16 -0
  53. data/test/expectations/openssl_test_key_sign1.json +2 -4
  54. data/test/fixtures/rspec_recorder/spec/decorated_hello_spec.rb +3 -3
  55. data/test/fixtures/rspec_recorder/spec/plain_hello_spec.rb +1 -1
  56. data/test/gem_test.rb +1 -1
  57. data/test/minitest_test.rb +1 -2
  58. data/test/rspec_test.rb +1 -20
  59. metadata +17 -13
  60. data/exe/appmap +0 -154
  61. data/spec/rspec_feature_metadata_spec.rb +0 -31
  62. data/test/cli_test.rb +0 -116
@@ -45,7 +45,6 @@ RSpec.configure do |config|
45
45
  # arbitrary gems may also be filtered via:
46
46
  # config.filter_gems_from_backtrace("gem name")
47
47
 
48
-
49
48
  DatabaseCleaner.allow_remote_database_url = true
50
49
 
51
50
  config.before(:suite) do
@@ -54,13 +53,8 @@ RSpec.configure do |config|
54
53
  end
55
54
 
56
55
  config.around :each do |example|
57
- # Enable the use of 'return' from a guard
58
- -> {
59
- return example.run unless %i[model controller].member?(example.metadata[:type])
60
-
61
- DatabaseCleaner.cleaning do
62
- example.run
63
- end
64
- }.call
56
+ DatabaseCleaner.cleaning do
57
+ example.run
58
+ end
65
59
  end
66
60
  end
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
@@ -64,13 +57,144 @@ describe 'AppMap class Hooking', docker: false do
64
57
  it 'excludes named classes and methods' do
65
58
  load 'spec/fixtures/hook/exclude.rb'
66
59
  package = AppMap::Config::Package.build_from_path('spec/fixtures/hook/exclude.rb')
67
- config = AppMap::Config.new('hook_spec', [ package ], %w[ExcludeTest])
60
+ config = AppMap::Config.new('hook_spec', [ package ], exclude: %w[ExcludeTest])
68
61
  AppMap.configuration = config
69
62
 
70
63
  expect(config.never_hook?(ExcludeTest.new.method(:instance_method))).to be_truthy
71
64
  expect(config.never_hook?(ExcludeTest.method(:cls_method))).to be_truthy
72
65
  end
73
66
 
67
+ it "handles an instance method named 'call' without issues" do
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
107
+ YAML
108
+
109
+ _, tracer = test_hook_behavior 'spec/fixtures/hook/method_named_call.rb', events_yaml do
110
+ expect(MethodNamedCall.new.call(1, 2, 3, 4, 5)).to eq('1 2 3 4 5')
111
+ 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
+ end
127
+
128
+ it 'can custom hook and label a function' do
129
+ events_yaml = <<~YAML
130
+ ---
131
+ - :id: 1
132
+ :event: :call
133
+ :defined_class: CustomInstanceMethod
134
+ :method_id: say_default
135
+ :path: spec/fixtures/hook/custom_instance_method.rb
136
+ :lineno: 8
137
+ :static: false
138
+ :parameters: []
139
+ :receiver:
140
+ :class: CustomInstanceMethod
141
+ :value: CustomInstance Method fixture
142
+ - :id: 2
143
+ :event: :return
144
+ :parent_id: 1
145
+ :return_value:
146
+ :class: String
147
+ :value: default
148
+ YAML
149
+
150
+ config = AppMap::Config.load({
151
+ functions: [
152
+ {
153
+ package: 'hook_spec',
154
+ class: 'CustomInstanceMethod',
155
+ functions: [ :say_default ],
156
+ labels: ['cowsay']
157
+ }
158
+ ]
159
+ }.deep_stringify_keys)
160
+
161
+ load 'spec/fixtures/hook/custom_instance_method.rb'
162
+ hook_cls = CustomInstanceMethod
163
+ method = hook_cls.instance_method(:say_default)
164
+
165
+ require 'appmap/hook/method'
166
+ hook_method = AppMap::Hook::Method.new(config.package_for_method(method), hook_cls, method)
167
+ hook_method.activate
168
+
169
+ tracer = AppMap.tracing.trace
170
+ AppMap::Event.reset_id_counter
171
+ begin
172
+ expect(CustomInstanceMethod.new.say_default).to eq('default')
173
+ ensure
174
+ AppMap.tracing.delete(tracer)
175
+ end
176
+
177
+ events = collect_events(tracer).to_yaml
178
+
179
+ expect(Diffy::Diff.new(events_yaml, events).to_s).to eq('')
180
+ class_map = AppMap.class_map(tracer.event_methods)
181
+ expect(Diffy::Diff.new(<<~CLASSMAP, YAML.dump(class_map)).to_s).to eq('')
182
+ ---
183
+ - :name: hook_spec
184
+ :type: package
185
+ :children:
186
+ - :name: CustomInstanceMethod
187
+ :type: class
188
+ :children:
189
+ - :name: say_default
190
+ :type: function
191
+ :location: spec/fixtures/hook/custom_instance_method.rb:8
192
+ :static: false
193
+ :labels:
194
+ - cowsay
195
+ CLASSMAP
196
+ end
197
+
74
198
  it 'parses labels from comments' do
75
199
  _, tracer = invoke_test_file 'spec/fixtures/hook/labels.rb' do
76
200
  ClassWithLabel.new.fn_with_label
@@ -91,9 +215,6 @@ describe 'AppMap class Hooking', docker: false do
91
215
  :labels:
92
216
  - has-fn-label
93
217
  :comment: "# @label has-fn-label\\n"
94
- :source: |2
95
- def fn_with_label
96
- end
97
218
  YAML
98
219
  end
99
220
 
@@ -148,10 +269,6 @@ describe 'AppMap class Hooking', docker: false do
148
269
  :type: function
149
270
  :location: spec/fixtures/hook/instance_method.rb:8
150
271
  :static: false
151
- :source: |2
152
- def say_default
153
- 'default'
154
- end
155
272
  YAML
156
273
  end
157
274
 
@@ -746,6 +863,7 @@ describe 'AppMap class Hooking', docker: false do
746
863
  end
747
864
  secure_compare_event = YAML.load(events).find { |evt| evt[:defined_class] == 'ActiveSupport::SecurityUtils' }
748
865
  secure_compare_event.delete(:lineno)
866
+ secure_compare_event.delete(:path)
749
867
 
750
868
  expect(Diffy::Diff.new(<<~YAML, secure_compare_event.to_yaml).to_s).to eq('')
751
869
  ---
@@ -753,7 +871,6 @@ describe 'AppMap class Hooking', docker: false do
753
871
  :event: :call
754
872
  :defined_class: ActiveSupport::SecurityUtils
755
873
  :method_id: secure_compare
756
- :path: lib/active_support/security_utils.rb
757
874
  :static: true
758
875
  :parameters:
759
876
  - :name: :a
@@ -837,7 +954,7 @@ describe 'AppMap class Hooking', docker: false do
837
954
  entry = cm[1][:children][0][:children][0][:children][0]
838
955
  # Sanity check, make sure we got the right one
839
956
  expect(entry[:name]).to eq('secure_compare')
840
- expect(entry[:labels]).to eq(%w[provider.secure_compare])
957
+ expect(entry[:labels]).to eq(%w[crypto.secure_compare])
841
958
  end
842
959
  end
843
960
 
@@ -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
@@ -61,7 +61,7 @@ describe 'SQL events' do
61
61
  end
62
62
 
63
63
  context 'while listing records' do
64
- let(:test_line_number) { 23 }
64
+ let(:test_line_number) { 29 }
65
65
  let(:appmap_json) { File.join(tmpdir, 'appmap/rspec/Api_UsersController_GET_api_users_lists_the_users.appmap.json') }
66
66
 
67
67
  context 'using Sequel ORM' do
data/spec/spec_helper.rb CHANGED
@@ -14,3 +14,19 @@ require 'appmap'
14
14
  RSpec.configure do |config|
15
15
  config.example_status_persistence_file_path = "tmp/rspec_failed_examples.txt"
16
16
  end
17
+
18
+ # Re-run the Rails specs without re-generating the data. This is useful for efficiently enhancing and
19
+ # debugging the test itself.
20
+ def use_existing_data?
21
+ ENV['USE_EXISTING_DATA'] == 'true'
22
+ end
23
+
24
+ shared_context 'collect events' do
25
+ def collect_events(tracer)
26
+ [].tap do |events|
27
+ while tracer.event?
28
+ events << tracer.next_event.to_h
29
+ end
30
+ end.map(&AppMap::Util.method(:sanitize_event))
31
+ end
32
+ end
@@ -11,8 +11,7 @@
11
11
  "name": "sign",
12
12
  "type": "function",
13
13
  "location": "lib/openssl_key_sign.rb:10",
14
- "static": true,
15
- "source": " def Example.sign\n key = OpenSSL::PKey::RSA.new 2048\n\n document = 'the document'\n\n digest = OpenSSL::Digest::SHA256.new\n key.sign digest, document\n end\n"
14
+ "static": true
16
15
  }
17
16
  ]
18
17
  }
@@ -40,8 +39,7 @@
40
39
  "location": "OpenSSL::PKey::PKey#sign",
41
40
  "static": false,
42
41
  "labels": [
43
- "security",
44
- "crypto"
42
+ "crypto.pkey"
45
43
  ]
46
44
  }
47
45
  ]
@@ -2,7 +2,7 @@ require 'rspec'
2
2
  require 'appmap/rspec'
3
3
  require 'hello'
4
4
 
5
- describe Hello, feature_group: 'Saying hello' do
5
+ describe Hello do
6
6
  before do
7
7
  # Trick appmap-ruby into thinking we're a Rails app.
8
8
  stub_const('Rails', double('rails', version: 'fake.0'))
@@ -11,11 +11,11 @@ describe Hello, feature_group: 'Saying hello' do
11
11
  # The order of these examples is important. The tests check the
12
12
  # appmap for 'says hello', and we want another example to get run
13
13
  # before it.
14
- it 'does not say goodbye', feature: 'Speak hello', appmap: true do
14
+ it 'does not say goodbye' do
15
15
  expect(Hello.new.say_hello).not_to eq('Goodbye!')
16
16
  end
17
17
 
18
- it 'says hello', feature: 'Speak hello', appmap: true do
18
+ it 'says hello' do
19
19
  expect(Hello.new.say_hello).to eq('Hello!')
20
20
  end
21
21
  end