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