appmap 0.31.0 → 0.34.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -1
  3. data/.rbenv-gemsets +1 -0
  4. data/CHANGELOG.md +22 -0
  5. data/README.md +38 -4
  6. data/Rakefile +10 -3
  7. data/appmap.gemspec +5 -0
  8. data/ext/appmap/appmap.c +26 -0
  9. data/ext/appmap/extconf.rb +6 -0
  10. data/lib/appmap.rb +23 -10
  11. data/lib/appmap/class_map.rb +13 -7
  12. data/lib/appmap/config.rb +54 -30
  13. data/lib/appmap/cucumber.rb +19 -2
  14. data/lib/appmap/event.rb +25 -16
  15. data/lib/appmap/hook.rb +52 -77
  16. data/lib/appmap/hook/method.rb +103 -0
  17. data/lib/appmap/open.rb +57 -0
  18. data/lib/appmap/rails/action_handler.rb +7 -7
  19. data/lib/appmap/rails/sql_handler.rb +10 -8
  20. data/lib/appmap/rspec.rb +1 -1
  21. data/lib/appmap/trace.rb +7 -7
  22. data/lib/appmap/util.rb +19 -0
  23. data/lib/appmap/version.rb +1 -1
  24. data/spec/abstract_controller4_base_spec.rb +1 -1
  25. data/spec/abstract_controller_base_spec.rb +9 -2
  26. data/spec/fixtures/hook/instance_method.rb +4 -0
  27. data/spec/fixtures/hook/singleton_method.rb +21 -12
  28. data/spec/hook_spec.rb +140 -44
  29. data/spec/open_spec.rb +19 -0
  30. data/spec/record_sql_rails_pg_spec.rb +56 -33
  31. data/test/cli_test.rb +12 -2
  32. data/test/fixtures/openssl_recorder/Gemfile +3 -0
  33. data/test/fixtures/openssl_recorder/appmap.yml +3 -0
  34. data/{spec/fixtures/hook/openssl_sign.rb → test/fixtures/openssl_recorder/lib/openssl_cert_sign.rb} +11 -4
  35. data/test/fixtures/openssl_recorder/lib/openssl_encrypt.rb +34 -0
  36. data/test/fixtures/openssl_recorder/lib/openssl_key_sign.rb +28 -0
  37. data/test/openssl_test.rb +203 -0
  38. data/test/test_helper.rb +1 -0
  39. metadata +58 -4
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe AppMap::Open do
6
+ context 'a block of Ruby code' do
7
+ it 'opens in the browser' do
8
+ appmap = AppMap.record do
9
+ File.read __FILE__
10
+ end
11
+
12
+ open = AppMap::Open.new(appmap)
13
+ server = open.run_server
14
+ page = Net::HTTP.get URI.parse("http://localhost:#{open.port}")
15
+ expect(page).to include(%(name="data" value='{"version))
16
+ server.kill
17
+ end
18
+ end
19
+ end
@@ -1,6 +1,6 @@
1
1
  require 'rails_spec_helper'
2
2
 
3
- describe 'Record SQL queries in a Rails app' do
3
+ describe 'SQL events' do
4
4
  before(:all) { @fixture_dir = 'spec/fixtures/rails_users_app' }
5
5
  include_context 'Rails app pg database'
6
6
 
@@ -14,54 +14,77 @@ describe 'Record SQL queries in a Rails app' do
14
14
  end
15
15
 
16
16
  let(:tmpdir) { "tmp/spec/record_sql_rails_pg_spec" }
17
- let(:appmap) { JSON.parse(File.read(appmap_json)).to_yaml }
18
17
 
19
- context 'while creating a new record' do
18
+ describe 'fields' do
20
19
  let(:test_line_number) { 8 }
21
20
  let(:appmap_json) { File.join(tmpdir, 'appmap/rspec/Api_UsersController_POST_api_users_with_required_parameters_creates_a_user.appmap.json') }
21
+ let(:orm_module) { 'sequel' }
22
+ let(:appmap) { JSON.parse(File.read(appmap_json)) }
23
+ describe 'on a call event' do
24
+ let(:event) do
25
+ appmap['events'].find do |event|
26
+ event['event'] == 'call' &&
27
+ event.keys.include?('sql_query')
28
+ end
29
+ end
30
+ it 'do not include function-only fields' do
31
+ expect(event.keys).to_not include('defined_class')
32
+ expect(event.keys).to_not include('method_id')
33
+ expect(event.keys).to_not include('path')
34
+ expect(event.keys).to_not include('lineno')
35
+ end
36
+ end
37
+ end
38
+
39
+ describe 'in a Rails app' do
40
+ let(:appmap) { JSON.parse(File.read(appmap_json)).to_yaml }
41
+ context 'while creating a new record' do
42
+ let(:test_line_number) { 8 }
43
+ let(:appmap_json) { File.join(tmpdir, 'appmap/rspec/Api_UsersController_POST_api_users_with_required_parameters_creates_a_user.appmap.json') }
22
44
 
23
- context 'using Sequel ORM' do
24
- let(:orm_module) { 'sequel' }
25
- it 'detects the sql INSERT' do
26
- expect(appmap).to include(<<-SQL_QUERY.strip)
45
+ context 'using Sequel ORM' do
46
+ let(:orm_module) { 'sequel' }
47
+ it 'detects the sql INSERT' do
48
+ expect(appmap).to include(<<-SQL_QUERY.strip)
27
49
  sql_query:
28
50
  sql: INSERT INTO "users" ("login") VALUES ('alice') RETURNING *
29
- SQL_QUERY
51
+ SQL_QUERY
52
+ end
30
53
  end
31
- end
32
- context 'using ActiveRecord ORM' do
33
- let(:orm_module) { 'activerecord' }
34
- it 'detects the sql INSERT' do
35
- expect(appmap).to include(<<-SQL_QUERY.strip)
54
+ context 'using ActiveRecord ORM' do
55
+ let(:orm_module) { 'activerecord' }
56
+ it 'detects the sql INSERT' do
57
+ expect(appmap).to include(<<-SQL_QUERY.strip)
36
58
  sql_query:
37
59
  sql: INSERT INTO "users" ("login") VALUES ($1) RETURNING "id"
38
- SQL_QUERY
60
+ SQL_QUERY
61
+ end
39
62
  end
40
63
  end
41
- end
42
-
43
- context 'while listing records' do
44
- let(:test_line_number) { 23 }
45
- let(:appmap_json) { File.join(tmpdir, 'appmap/rspec/Api_UsersController_GET_api_users_lists_the_users.appmap.json') }
46
-
47
- context 'using Sequel ORM' do
48
- let(:orm_module) { 'sequel' }
49
- it 'detects the sql SELECT' do
50
- expect(appmap).to include(<<-SQL_QUERY.strip)
64
+
65
+ context 'while listing records' do
66
+ let(:test_line_number) { 23 }
67
+ let(:appmap_json) { File.join(tmpdir, 'appmap/rspec/Api_UsersController_GET_api_users_lists_the_users.appmap.json') }
68
+
69
+ context 'using Sequel ORM' do
70
+ let(:orm_module) { 'sequel' }
71
+ it 'detects the sql SELECT' do
72
+ expect(appmap).to include(<<-SQL_QUERY.strip)
51
73
  sql_query:
52
74
  sql: SELECT * FROM "users"
53
- SQL_QUERY
54
-
55
- expect(appmap).to include('sql:')
75
+ SQL_QUERY
76
+
77
+ expect(appmap).to include('sql:')
78
+ end
56
79
  end
57
- end
58
- context 'using ActiveRecord ORM' do
59
- let(:orm_module) { 'activerecord' }
60
- it 'detects the sql SELECT' do
61
- expect(appmap).to include(<<-SQL_QUERY.strip)
80
+ context 'using ActiveRecord ORM' do
81
+ let(:orm_module) { 'activerecord' }
82
+ it 'detects the sql SELECT' do
83
+ expect(appmap).to include(<<-SQL_QUERY.strip)
62
84
  sql_query:
63
85
  sql: SELECT "users".* FROM "users"
64
- SQL_QUERY
86
+ SQL_QUERY
87
+ end
65
88
  end
66
89
  end
67
90
  end
@@ -55,11 +55,13 @@ class CLITest < Minitest::Test
55
55
  assert_equal <<~OUTPUT.strip, output.strip
56
56
  Class frequency:
57
57
  ----------------
58
- 2 Main
58
+ 1 Main
59
+ 1 IO
59
60
 
60
61
  Method frequency:
61
62
  ----------------
62
63
  1 Main.say_hello
64
+ 1 IO#write
63
65
  OUTPUT
64
66
  end
65
67
 
@@ -79,13 +81,21 @@ class CLITest < Minitest::Test
79
81
  "class_frequency": [
80
82
  {
81
83
  "name": "Main",
82
- "count": 2
84
+ "count": 1
85
+ },
86
+ {
87
+ "name": "IO",
88
+ "count": 1
83
89
  }
84
90
  ],
85
91
  "method_frequency": [
86
92
  {
87
93
  "name": "Main.say_hello",
88
94
  "count": 1
95
+ },
96
+ {
97
+ "name": "IO#write",
98
+ "count": 1
89
99
  }
90
100
  ]
91
101
  }
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'appmap', git: 'applandinc/appmap-ruby', branch: `git rev-parse --abbrev-ref HEAD`.strip
@@ -0,0 +1,3 @@
1
+ name: openssl_recorder
2
+ packages:
3
+ - path: lib
@@ -4,8 +4,8 @@
4
4
 
5
5
  require 'openssl'
6
6
 
7
- module OpenSSLExample
8
- def OpenSSLExample.example
7
+ module Example
8
+ def Example.sign
9
9
  ca_key = OpenSSL::PKey::RSA.new 2048
10
10
  pass_phrase = 'my secure pass phrase goes here'
11
11
 
@@ -80,8 +80,15 @@ module OpenSSLExample
80
80
 
81
81
  csr_cert.sign ca_key, OpenSSL::Digest::SHA1.new
82
82
 
83
- open 'tmp/csr_cert.pem', 'w' do |io|
84
- io.write csr_cert.to_pem
83
+ 'tmp/csr_cert.pem'.tap do |fname|
84
+ open fname, 'w' do |io|
85
+ io.write csr_cert.to_pem
86
+ end
85
87
  end
86
88
  end
87
89
  end
90
+
91
+ if __FILE__ == $0
92
+ cert_file = Example.sign
93
+ puts "Wrote cert file #{cert_file}"
94
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ # From the manual page https://ruby-doc.org/stdlib-2.5.1/libdoc/openssl/rdoc/OpenSSL.html
4
+
5
+ require 'openssl'
6
+
7
+ module Example
8
+ def Example.encrypt
9
+ cipher = OpenSSL::Cipher.new 'AES-256-CBC'
10
+ cipher.encrypt
11
+ iv = cipher.random_iv
12
+
13
+ pwd = 'some hopefully not to easily guessable password'
14
+ salt = OpenSSL::Random.random_bytes 16
15
+ iter = 20000
16
+ key_len = cipher.key_len
17
+ digest = OpenSSL::Digest::SHA256.new
18
+
19
+ key = OpenSSL::PKCS5.pbkdf2_hmac(pwd, salt, iter, key_len, digest)
20
+ cipher.key = key
21
+
22
+ document = 'the document'
23
+
24
+ encrypted = cipher.update document
25
+ encrypted << cipher.final
26
+ encrypted
27
+ end
28
+ end
29
+
30
+ if __FILE__ == $0
31
+ ciphertext = Example.encrypt
32
+ require 'base64'
33
+ puts "Computed ciphertext #{Base64.urlsafe_encode64(ciphertext)}"
34
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ # From the manual page https://ruby-doc.org/stdlib-2.5.1/libdoc/openssl/rdoc/OpenSSL.html
4
+
5
+ require 'appmap'
6
+ require 'openssl'
7
+ require 'openssl/digest'
8
+
9
+ module Example
10
+ def Example.sign
11
+ key = OpenSSL::PKey::RSA.new 2048
12
+
13
+ document = 'the document'
14
+
15
+ digest = OpenSSL::Digest::SHA256.new
16
+ key.sign digest, document
17
+ end
18
+ end
19
+
20
+ if __FILE__ == $0
21
+ appmap = AppMap.record do
22
+ Example.sign
23
+ puts 'Computed signature'
24
+ end
25
+ appmap['metadata'] = [ 'recorder' => __FILE__ ]
26
+
27
+ File.write('appmap.json', JSON.generate(appmap))
28
+ end
@@ -0,0 +1,203 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'test_helper'
5
+ require 'English'
6
+
7
+ class OpenSSLTest < Minitest::Test
8
+ def perform_test(test_name)
9
+ Bundler.with_clean_env do
10
+ Dir.chdir 'test/fixtures/openssl_recorder' do
11
+ FileUtils.rm_rf 'tmp'
12
+ system 'bundle config --local local.appmap ../../..'
13
+ system 'bundle'
14
+ system({ 'APPMAP' => 'true', 'DEBUG' => 'true' }, %(bundle exec ruby lib/openssl_#{test_name}.rb))
15
+
16
+ yield
17
+ end
18
+ end
19
+ end
20
+
21
+ def test_key_sign
22
+ perform_test 'key_sign' do
23
+ appmap_file = 'appmap.json'
24
+
25
+ assert File.file?(appmap_file), 'appmap output file does not exist'
26
+ appmap = JSON.parse(File.read(appmap_file))
27
+ assert_equal AppMap::APPMAP_FORMAT_VERSION, appmap['version']
28
+ assert_equal [ { 'recorder' => 'lib/openssl_key_sign.rb' } ], appmap['metadata']
29
+ assert_equal JSON.parse(<<~JSON), appmap['classMap']
30
+ [
31
+ {
32
+ "name": "lib",
33
+ "type": "package",
34
+ "children": [
35
+ {
36
+ "name": "Example",
37
+ "type": "class",
38
+ "children": [
39
+ {
40
+ "name": "sign",
41
+ "type": "function",
42
+ "location": "lib/openssl_key_sign.rb:10",
43
+ "static": true
44
+ }
45
+ ]
46
+ }
47
+ ]
48
+ },
49
+ {
50
+ "name": "openssl",
51
+ "type": "package",
52
+ "children": [
53
+ {
54
+ "name": "OpenSSL",
55
+ "type": "class",
56
+ "children": [
57
+ {
58
+ "name": "PKey",
59
+ "type": "class",
60
+ "children": [
61
+ {
62
+ "name": "PKey",
63
+ "type": "class",
64
+ "children": [
65
+ {
66
+ "name": "sign",
67
+ "type": "function",
68
+ "location": "OpenSSL::PKey::PKey#sign",
69
+ "static": false,
70
+ "labels": [
71
+ "security",
72
+ "crypto"
73
+ ]
74
+ }
75
+ ]
76
+ }
77
+ ]
78
+ }
79
+ ]
80
+ }
81
+ ]
82
+ },
83
+ {
84
+ "name": "io",
85
+ "type": "package",
86
+ "children": [
87
+ {
88
+ "name": "IO",
89
+ "type": "class",
90
+ "children": [
91
+ {
92
+ "name": "write",
93
+ "type": "function",
94
+ "location": "IO#write",
95
+ "static": false,
96
+ "labels": [
97
+ "io"
98
+ ]
99
+ }
100
+ ]
101
+ }
102
+ ]
103
+ }
104
+ ]
105
+ JSON
106
+ sanitized_events = appmap['events'].map(&:deep_symbolize_keys).map(&AppMap::Util.method(:sanitize_event)).map do |event|
107
+ delete_value = ->(obj) { (obj || {}).delete(:value) }
108
+ delete_value.call(event[:receiver])
109
+ delete_value.call(event[:return_value])
110
+ event
111
+ end
112
+
113
+ diff = Diffy::Diff.new(<<~JSON.strip, JSON.pretty_generate(sanitized_events).strip)
114
+ [
115
+ {
116
+ "id": 1,
117
+ "event": "call",
118
+ "defined_class": "Example",
119
+ "method_id": "sign",
120
+ "path": "lib/openssl_key_sign.rb",
121
+ "lineno": 10,
122
+ "static": true,
123
+ "parameters": [
124
+
125
+ ],
126
+ "receiver": {
127
+ "class": "Module"
128
+ }
129
+ },
130
+ {
131
+ "id": 2,
132
+ "event": "call",
133
+ "defined_class": "OpenSSL::PKey::PKey",
134
+ "method_id": "sign",
135
+ "path": "OpenSSL::PKey::PKey#sign",
136
+ "static": false,
137
+ "parameters": [
138
+ {
139
+ "name": "arg",
140
+ "class": "OpenSSL::Digest::SHA256",
141
+ "value": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
142
+ "kind": "req"
143
+ },
144
+ {
145
+ "name": "arg",
146
+ "class": "String",
147
+ "value": "the document",
148
+ "kind": "req"
149
+ }
150
+ ],
151
+ "receiver": {
152
+ "class": "OpenSSL::PKey::RSA"
153
+ }
154
+ },
155
+ {
156
+ "id": 3,
157
+ "event": "return",
158
+ "parent_id": 2,
159
+ "return_value": {
160
+ "class": "String"
161
+ }
162
+ },
163
+ {
164
+ "id": 4,
165
+ "event": "return",
166
+ "parent_id": 1,
167
+ "return_value": {
168
+ "class": "String"
169
+ }
170
+ },
171
+ {
172
+ "id": 5,
173
+ "event": "call",
174
+ "defined_class": "IO",
175
+ "method_id": "write",
176
+ "path": "IO#write",
177
+ "static": false,
178
+ "parameters": [
179
+ {
180
+ "name": "arg",
181
+ "class": "String",
182
+ "value": "Computed signature",
183
+ "kind": "rest"
184
+ }
185
+ ],
186
+ "receiver": {
187
+ "class": "IO"
188
+ }
189
+ },
190
+ {
191
+ "id": 6,
192
+ "event": "return",
193
+ "parent_id": 5,
194
+ "return_value": {
195
+ "class": "Integer"
196
+ }
197
+ }
198
+ ]
199
+ JSON
200
+ assert_equal '', diff.to_s
201
+ end
202
+ end
203
+ end