appmap 0.28.1 → 0.34.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +32 -1
  3. data/README.md +54 -2
  4. data/Rakefile +1 -1
  5. data/appmap.gemspec +2 -0
  6. data/lib/appmap.rb +25 -14
  7. data/lib/appmap/class_map.rb +25 -27
  8. data/lib/appmap/config.rb +115 -0
  9. data/lib/appmap/cucumber.rb +19 -2
  10. data/lib/appmap/event.rb +25 -16
  11. data/lib/appmap/hook.rb +89 -139
  12. data/lib/appmap/hook/method.rb +83 -0
  13. data/lib/appmap/metadata.rb +1 -1
  14. data/lib/appmap/minitest.rb +141 -0
  15. data/lib/appmap/open.rb +57 -0
  16. data/lib/appmap/rails/action_handler.rb +7 -7
  17. data/lib/appmap/rails/sql_handler.rb +10 -8
  18. data/lib/appmap/record.rb +27 -0
  19. data/lib/appmap/rspec.rb +2 -2
  20. data/lib/appmap/trace.rb +16 -8
  21. data/lib/appmap/util.rb +19 -0
  22. data/lib/appmap/version.rb +1 -1
  23. data/spec/abstract_controller4_base_spec.rb +1 -1
  24. data/spec/abstract_controller_base_spec.rb +9 -2
  25. data/spec/config_spec.rb +3 -3
  26. data/spec/fixtures/hook/compare.rb +7 -0
  27. data/spec/fixtures/hook/instance_method.rb +4 -0
  28. data/spec/hook_spec.rb +222 -37
  29. data/spec/open_spec.rb +19 -0
  30. data/spec/record_sql_rails_pg_spec.rb +56 -33
  31. data/spec/util_spec.rb +1 -1
  32. data/test/cli_test.rb +12 -2
  33. data/test/fixtures/minitest_recorder/Gemfile +5 -0
  34. data/test/fixtures/minitest_recorder/appmap.yml +3 -0
  35. data/test/fixtures/minitest_recorder/lib/hello.rb +5 -0
  36. data/test/fixtures/minitest_recorder/test/hello_test.rb +12 -0
  37. data/test/fixtures/openssl_recorder/Gemfile +3 -0
  38. data/test/fixtures/openssl_recorder/appmap.yml +3 -0
  39. data/test/fixtures/openssl_recorder/lib/openssl_cert_sign.rb +94 -0
  40. data/test/fixtures/openssl_recorder/lib/openssl_encrypt.rb +34 -0
  41. data/test/fixtures/openssl_recorder/lib/openssl_key_sign.rb +28 -0
  42. data/test/fixtures/process_recorder/appmap.yml +3 -0
  43. data/test/fixtures/process_recorder/hello.rb +9 -0
  44. data/test/minitest_test.rb +38 -0
  45. data/test/openssl_test.rb +203 -0
  46. data/test/record_process_test.rb +35 -0
  47. data/test/test_helper.rb +1 -0
  48. metadata +51 -2
@@ -3,7 +3,7 @@
3
3
  require 'spec_helper'
4
4
  require 'appmap/util'
5
5
 
6
- describe AppMap::Util do
6
+ describe AppMap::Util, docker: false do
7
7
  let(:subject) { AppMap::Util.method(:scenario_filename) }
8
8
  describe 'scenario_filename' do
9
9
  it 'leaves short names alone' do
@@ -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,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'appmap', git: 'applandinc/appmap-ruby', branch: `git rev-parse --abbrev-ref HEAD`.strip
4
+ gem 'byebug'
5
+ gem 'minitest'
@@ -0,0 +1,3 @@
1
+ name: minitest_recorder
2
+ packages:
3
+ - path: .
@@ -0,0 +1,5 @@
1
+ class Hello
2
+ def say_hello
3
+ 'Hello!'
4
+ end
5
+ end
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'minitest/autorun'
5
+ require 'appmap/minitest'
6
+ require 'hello'
7
+
8
+ class HelloTest < ::Minitest::Test
9
+ def test_hello
10
+ assert_equal 'Hello!', Hello.new.say_hello
11
+ end
12
+ end
@@ -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
@@ -0,0 +1,94 @@
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.sign
9
+ ca_key = OpenSSL::PKey::RSA.new 2048
10
+ pass_phrase = 'my secure pass phrase goes here'
11
+
12
+ cipher = OpenSSL::Cipher.new 'AES-256-CBC'
13
+
14
+ open 'tmp/ca_key.pem', 'w', 0644 do |io|
15
+ io.write ca_key.export(cipher, pass_phrase)
16
+ end
17
+
18
+ ca_name = OpenSSL::X509::Name.parse '/CN=ca/DC=example'
19
+
20
+ ca_cert = OpenSSL::X509::Certificate.new
21
+ ca_cert.serial = 0
22
+ ca_cert.version = 2
23
+ ca_cert.not_before = Time.now
24
+ ca_cert.not_after = Time.now + 86400
25
+
26
+ ca_cert.public_key = ca_key.public_key
27
+ ca_cert.subject = ca_name
28
+ ca_cert.issuer = ca_name
29
+
30
+ extension_factory = OpenSSL::X509::ExtensionFactory.new
31
+ extension_factory.subject_certificate = ca_cert
32
+ extension_factory.issuer_certificate = ca_cert
33
+
34
+ ca_cert.add_extension extension_factory.create_extension('subjectKeyIdentifier', 'hash')
35
+ ca_cert.add_extension extension_factory.create_extension('basicConstraints', 'CA:TRUE', true)
36
+
37
+ ca_cert.add_extension extension_factory.create_extension(
38
+ 'keyUsage', 'cRLSign,keyCertSign', true)
39
+
40
+ ca_cert.sign ca_key, OpenSSL::Digest::SHA1.new
41
+
42
+ open 'tmp/ca_cert.pem', 'w' do |io|
43
+ io.write ca_cert.to_pem
44
+ end
45
+
46
+ csr = OpenSSL::X509::Request.new
47
+ csr.version = 0
48
+ csr.subject = OpenSSL::X509::Name.new([ ['CN', 'the name to sign', OpenSSL::ASN1::UTF8STRING] ])
49
+ csr.public_key = ca_key.public_key
50
+ csr.sign ca_key, OpenSSL::Digest::SHA1.new
51
+
52
+ open 'tmp/csr.pem', 'w' do |io|
53
+ io.write csr.to_pem
54
+ end
55
+
56
+ csr = OpenSSL::X509::Request.new File.read 'tmp/csr.pem'
57
+
58
+ raise 'CSR can not be verified' unless csr.verify csr.public_key
59
+
60
+ csr_cert = OpenSSL::X509::Certificate.new
61
+ csr_cert.serial = 0
62
+ csr_cert.version = 2
63
+ csr_cert.not_before = Time.now
64
+ csr_cert.not_after = Time.now + 600
65
+
66
+ csr_cert.subject = csr.subject
67
+ csr_cert.public_key = csr.public_key
68
+ csr_cert.issuer = ca_cert.subject
69
+
70
+ extension_factory = OpenSSL::X509::ExtensionFactory.new
71
+ extension_factory.subject_certificate = csr_cert
72
+ extension_factory.issuer_certificate = ca_cert
73
+
74
+ csr_cert.add_extension extension_factory.create_extension('basicConstraints', 'CA:FALSE')
75
+
76
+ csr_cert.add_extension extension_factory.create_extension(
77
+ 'keyUsage', 'keyEncipherment,dataEncipherment,digitalSignature')
78
+
79
+ csr_cert.add_extension extension_factory.create_extension('subjectKeyIdentifier', 'hash')
80
+
81
+ csr_cert.sign ca_key, OpenSSL::Digest::SHA1.new
82
+
83
+ 'tmp/csr_cert.pem'.tap do |fname|
84
+ open fname, 'w' do |io|
85
+ io.write csr_cert.to_pem
86
+ end
87
+ end
88
+ end
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,3 @@
1
+ name: process_recorder
2
+ packages:
3
+ - path: .
@@ -0,0 +1,9 @@
1
+ require 'appmap/record'
2
+
3
+ class Hello
4
+ def say_hello
5
+ 'Hello!'
6
+ end
7
+ end
8
+
9
+ Hello.new.say_hello
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'test_helper'
5
+ require 'English'
6
+
7
+ class MinitestTest < Minitest::Test
8
+ def perform_test(test_name)
9
+ Bundler.with_clean_env do
10
+ Dir.chdir 'test/fixtures/minitest_recorder' do
11
+ FileUtils.rm_rf 'tmp'
12
+ system 'bundle config --local local.appmap ../../..'
13
+ system 'bundle'
14
+ system({ 'APPMAP' => 'true' }, %(bundle exec ruby -Ilib -Itest test/#{test_name}_test.rb))
15
+
16
+ yield
17
+ end
18
+ end
19
+ end
20
+
21
+ def test_hello
22
+ perform_test 'hello' do
23
+ appmap_file = 'tmp/appmap/minitest/Hello_hello.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_includes appmap.keys, 'metadata'
29
+ metadata = appmap['metadata']
30
+ assert_equal 'minitest_recorder', metadata['app']
31
+ assert_equal 'minitest', metadata['recorder']['name']
32
+ assert_equal 'ruby', metadata['language']['name']
33
+ assert_equal 'Hello', metadata['feature_group']
34
+ assert_equal 'hello', metadata['feature']
35
+ assert_equal 'Hello hello', metadata['name']
36
+ end
37
+ end
38
+ 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