appmap 0.31.0 → 0.34.2
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.
- checksums.yaml +4 -4
- data/.gitignore +1 -1
- data/.rbenv-gemsets +1 -0
- data/CHANGELOG.md +22 -0
- data/README.md +38 -4
- data/Rakefile +10 -3
- data/appmap.gemspec +5 -0
- data/ext/appmap/appmap.c +26 -0
- data/ext/appmap/extconf.rb +6 -0
- data/lib/appmap.rb +23 -10
- data/lib/appmap/class_map.rb +13 -7
- data/lib/appmap/config.rb +54 -30
- data/lib/appmap/cucumber.rb +19 -2
- data/lib/appmap/event.rb +25 -16
- data/lib/appmap/hook.rb +52 -77
- data/lib/appmap/hook/method.rb +103 -0
- data/lib/appmap/open.rb +57 -0
- data/lib/appmap/rails/action_handler.rb +7 -7
- data/lib/appmap/rails/sql_handler.rb +10 -8
- data/lib/appmap/rspec.rb +1 -1
- data/lib/appmap/trace.rb +7 -7
- data/lib/appmap/util.rb +19 -0
- data/lib/appmap/version.rb +1 -1
- data/spec/abstract_controller4_base_spec.rb +1 -1
- data/spec/abstract_controller_base_spec.rb +9 -2
- data/spec/fixtures/hook/instance_method.rb +4 -0
- data/spec/fixtures/hook/singleton_method.rb +21 -12
- data/spec/hook_spec.rb +140 -44
- data/spec/open_spec.rb +19 -0
- data/spec/record_sql_rails_pg_spec.rb +56 -33
- data/test/cli_test.rb +12 -2
- data/test/fixtures/openssl_recorder/Gemfile +3 -0
- data/test/fixtures/openssl_recorder/appmap.yml +3 -0
- data/{spec/fixtures/hook/openssl_sign.rb → test/fixtures/openssl_recorder/lib/openssl_cert_sign.rb} +11 -4
- data/test/fixtures/openssl_recorder/lib/openssl_encrypt.rb +34 -0
- data/test/fixtures/openssl_recorder/lib/openssl_key_sign.rb +28 -0
- data/test/openssl_test.rb +203 -0
- data/test/test_helper.rb +1 -0
- metadata +58 -4
data/spec/open_spec.rb
ADDED
@@ -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 '
|
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
|
-
|
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
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
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
|
-
|
51
|
+
SQL_QUERY
|
52
|
+
end
|
30
53
|
end
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
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
|
-
|
60
|
+
SQL_QUERY
|
61
|
+
end
|
39
62
|
end
|
40
63
|
end
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
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
|
-
|
54
|
-
|
55
|
-
|
75
|
+
SQL_QUERY
|
76
|
+
|
77
|
+
expect(appmap).to include('sql:')
|
78
|
+
end
|
56
79
|
end
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
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
|
-
|
86
|
+
SQL_QUERY
|
87
|
+
end
|
65
88
|
end
|
66
89
|
end
|
67
90
|
end
|
data/test/cli_test.rb
CHANGED
@@ -55,11 +55,13 @@ class CLITest < Minitest::Test
|
|
55
55
|
assert_equal <<~OUTPUT.strip, output.strip
|
56
56
|
Class frequency:
|
57
57
|
----------------
|
58
|
-
|
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":
|
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
|
}
|
data/{spec/fixtures/hook/openssl_sign.rb → test/fixtures/openssl_recorder/lib/openssl_cert_sign.rb}
RENAMED
@@ -4,8 +4,8 @@
|
|
4
4
|
|
5
5
|
require 'openssl'
|
6
6
|
|
7
|
-
module
|
8
|
-
def
|
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
|
-
|
84
|
-
io
|
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
|