appmap 0.41.2 → 0.45.0

Sign up to get free protection for your applications and to get access to all the features.
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