appmap 0.26.0 → 0.31.0
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 -2
- data/CHANGELOG.md +38 -0
- data/README.md +144 -31
- data/Rakefile +1 -1
- data/exe/appmap +3 -1
- data/lib/appmap.rb +55 -35
- data/lib/appmap/algorithm/stats.rb +2 -1
- data/lib/appmap/class_map.rb +16 -24
- data/lib/appmap/command/record.rb +2 -61
- data/lib/appmap/config.rb +91 -0
- data/lib/appmap/cucumber.rb +89 -0
- data/lib/appmap/event.rb +6 -6
- data/lib/appmap/hook.rb +94 -116
- data/lib/appmap/metadata.rb +62 -0
- data/lib/appmap/middleware/remote_recording.rb +2 -6
- data/lib/appmap/minitest.rb +141 -0
- data/lib/appmap/rails/action_handler.rb +2 -2
- data/lib/appmap/rails/sql_handler.rb +2 -2
- data/lib/appmap/railtie.rb +2 -2
- data/lib/appmap/record.rb +27 -0
- data/lib/appmap/rspec.rb +20 -38
- data/lib/appmap/trace.rb +19 -11
- data/lib/appmap/util.rb +40 -0
- data/lib/appmap/version.rb +1 -1
- data/package-lock.json +3 -3
- data/spec/abstract_controller4_base_spec.rb +1 -1
- data/spec/abstract_controller_base_spec.rb +1 -1
- data/spec/config_spec.rb +3 -3
- data/spec/fixtures/hook/compare.rb +7 -0
- data/spec/fixtures/hook/openssl_sign.rb +87 -0
- data/spec/fixtures/hook/singleton_method.rb +54 -0
- data/spec/fixtures/rails_users_app/Gemfile +1 -0
- data/spec/fixtures/rails_users_app/features/api_users.feature +13 -0
- data/spec/fixtures/rails_users_app/features/support/env.rb +4 -0
- data/spec/fixtures/rails_users_app/features/support/hooks.rb +11 -0
- data/spec/fixtures/rails_users_app/features/support/steps.rb +18 -0
- data/spec/hook_spec.rb +243 -36
- data/spec/rails_spec_helper.rb +2 -0
- data/spec/rspec_feature_metadata_spec.rb +2 -0
- data/spec/spec_helper.rb +4 -0
- data/spec/util_spec.rb +21 -0
- data/test/cli_test.rb +2 -2
- data/test/cucumber_test.rb +72 -0
- data/test/fixtures/cucumber4_recorder/Gemfile +5 -0
- data/test/fixtures/cucumber4_recorder/appmap.yml +3 -0
- data/test/fixtures/cucumber4_recorder/features/say_hello.feature +5 -0
- data/test/fixtures/cucumber4_recorder/features/support/env.rb +5 -0
- data/test/fixtures/cucumber4_recorder/features/support/hooks.rb +11 -0
- data/test/fixtures/cucumber4_recorder/features/support/steps.rb +9 -0
- data/test/fixtures/cucumber4_recorder/lib/hello.rb +7 -0
- data/test/fixtures/cucumber_recorder/Gemfile +5 -0
- data/test/fixtures/cucumber_recorder/appmap.yml +3 -0
- data/test/fixtures/cucumber_recorder/features/say_hello.feature +5 -0
- data/test/fixtures/cucumber_recorder/features/support/env.rb +5 -0
- data/test/fixtures/cucumber_recorder/features/support/hooks.rb +11 -0
- data/test/fixtures/cucumber_recorder/features/support/steps.rb +9 -0
- data/test/fixtures/cucumber_recorder/lib/hello.rb +7 -0
- data/test/fixtures/minitest_recorder/Gemfile +5 -0
- data/test/fixtures/minitest_recorder/appmap.yml +3 -0
- data/test/fixtures/minitest_recorder/lib/hello.rb +5 -0
- data/test/fixtures/minitest_recorder/test/hello_test.rb +12 -0
- data/test/fixtures/process_recorder/appmap.yml +3 -0
- data/test/fixtures/process_recorder/hello.rb +9 -0
- data/test/fixtures/rspec_recorder/Gemfile +1 -1
- data/test/fixtures/rspec_recorder/spec/decorated_hello_spec.rb +12 -0
- data/test/minitest_test.rb +38 -0
- data/test/record_process_test.rb +35 -0
- data/test/rspec_test.rb +5 -0
- metadata +39 -3
- data/spec/fixtures/hook/class_method.rb +0 -17
data/lib/appmap/util.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AppMap
|
4
|
+
module Util
|
5
|
+
class << self
|
6
|
+
# scenario_filename builds a suitable file name from a scenario name.
|
7
|
+
# Special characters are removed, and the file name is truncated to fit within
|
8
|
+
# shell limitations.
|
9
|
+
def scenario_filename(name, max_length: 255, separator: '_', extension: '.appmap.json')
|
10
|
+
# Cribbed from v5 version of ActiveSupport:Inflector#parameterize:
|
11
|
+
# https://github.com/rails/rails/blob/v5.2.4/activesupport/lib/active_support/inflector/transliterate.rb#L92
|
12
|
+
# Replace accented chars with their ASCII equivalents.
|
13
|
+
|
14
|
+
fname = name.encode('utf-8', invalid: :replace, undef: :replace, replace: '_')
|
15
|
+
|
16
|
+
# Turn unwanted chars into the separator.
|
17
|
+
fname.gsub!(/[^a-z0-9\-_]+/i, separator)
|
18
|
+
|
19
|
+
re_sep = Regexp.escape(separator)
|
20
|
+
re_duplicate_separator = /#{re_sep}{2,}/
|
21
|
+
re_leading_trailing_separator = /^#{re_sep}|#{re_sep}$/i
|
22
|
+
|
23
|
+
# No more than one of the separator in a row.
|
24
|
+
fname.gsub!(re_duplicate_separator, separator)
|
25
|
+
|
26
|
+
# Finally, Remove leading/trailing separator.
|
27
|
+
fname.gsub!(re_leading_trailing_separator, '')
|
28
|
+
|
29
|
+
if (fname.length + extension.length) > max_length
|
30
|
+
require 'base64'
|
31
|
+
require 'digest'
|
32
|
+
fname_digest = Base64.urlsafe_encode64 Digest::MD5.digest(fname), padding: false
|
33
|
+
fname[max_length - fname_digest.length - extension.length - 1..-1] = [ '-', fname_digest ].join
|
34
|
+
end
|
35
|
+
|
36
|
+
[ fname, extension ].join
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/appmap/version.rb
CHANGED
data/package-lock.json
CHANGED
@@ -551,9 +551,9 @@
|
|
551
551
|
}
|
552
552
|
},
|
553
553
|
"lodash": {
|
554
|
-
"version": "4.17.
|
555
|
-
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.
|
556
|
-
"integrity": "sha512-
|
554
|
+
"version": "4.17.19",
|
555
|
+
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
|
556
|
+
"integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ=="
|
557
557
|
},
|
558
558
|
"longest": {
|
559
559
|
"version": "1.0.1",
|
@@ -57,8 +57,8 @@ describe 'AbstractControllerBase' do
|
|
57
57
|
method_id: build_user
|
58
58
|
path: app/controllers/api/users_controller.rb
|
59
59
|
lineno: 23
|
60
|
-
static: false
|
61
60
|
thread_id: .*
|
61
|
+
static: false
|
62
62
|
parameters:
|
63
63
|
- name: params
|
64
64
|
class: ActiveSupport::HashWithIndifferentAccess
|
data/spec/config_spec.rb
CHANGED
@@ -2,9 +2,9 @@
|
|
2
2
|
|
3
3
|
require 'rails_spec_helper'
|
4
4
|
require 'active_support/core_ext'
|
5
|
-
require 'appmap/
|
5
|
+
require 'appmap/config'
|
6
6
|
|
7
|
-
describe AppMap::
|
7
|
+
describe AppMap::Config, docker: false do
|
8
8
|
it 'loads from a Hash' do
|
9
9
|
config_data = {
|
10
10
|
name: 'test',
|
@@ -18,7 +18,7 @@ describe AppMap::Hook::Config do
|
|
18
18
|
}
|
19
19
|
]
|
20
20
|
}.deep_stringify_keys!
|
21
|
-
config = AppMap::
|
21
|
+
config = AppMap::Config.load(config_data)
|
22
22
|
|
23
23
|
expect(config.to_h.deep_stringify_keys!).to eq(config_data)
|
24
24
|
end
|
@@ -0,0 +1,87 @@
|
|
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 OpenSSLExample
|
8
|
+
def OpenSSLExample.example
|
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
|
+
open 'tmp/csr_cert.pem', 'w' do |io|
|
84
|
+
io.write csr_cert.to_pem
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class SingletonMethod
|
4
|
+
class << self
|
5
|
+
def say_default
|
6
|
+
'default'
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def SingletonMethod.say_class_defined
|
11
|
+
'defined with explicit class scope'
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.say_self_defined
|
15
|
+
'defined with self class scope'
|
16
|
+
end
|
17
|
+
|
18
|
+
# When called, do_include calls +include+ to bring in the module
|
19
|
+
# AddMethod. AddMethod defines a new instance method, which gets
|
20
|
+
# added to the singleton class of SingletonMethod.
|
21
|
+
def do_include
|
22
|
+
class << self
|
23
|
+
SingletonMethod.include(AddMethod)
|
24
|
+
end
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.new_with_instance_method
|
29
|
+
SingletonMethod.new.tap do |m|
|
30
|
+
def m.say_instance_defined
|
31
|
+
'defined for an instance'
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_s
|
37
|
+
'Singleton Method fixture'
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
module AddMethod
|
42
|
+
def self.included(base)
|
43
|
+
base.module_eval do
|
44
|
+
define_method "added_method" do
|
45
|
+
_added_method
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def _added_method
|
51
|
+
'defined by including a module'
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
@@ -39,6 +39,7 @@ appmap_options = \
|
|
39
39
|
gem 'appmap', appmap_options
|
40
40
|
|
41
41
|
group :development, :test do
|
42
|
+
gem 'cucumber-rails', require: false
|
42
43
|
gem 'rspec-rails'
|
43
44
|
# Required for Sequel, since without ActiveRecord, the Rails transactional fixture support
|
44
45
|
# isn't activated.
|
@@ -0,0 +1,13 @@
|
|
1
|
+
Feature: /api/users
|
2
|
+
|
3
|
+
@appmap-disable
|
4
|
+
Scenario: A user can be created
|
5
|
+
When I create a user
|
6
|
+
Then the response status should be 201
|
7
|
+
|
8
|
+
Scenario: When a user is created, it should be in the user list
|
9
|
+
Given I create a user
|
10
|
+
And the response status should be 201
|
11
|
+
When I list the users
|
12
|
+
Then the response status should be 200
|
13
|
+
And the response should include the user
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
When 'I create a user' do
|
4
|
+
@response = post '/api/users', login: 'alice'
|
5
|
+
end
|
6
|
+
|
7
|
+
Then(/the response status should be (\d+)/) do |status|
|
8
|
+
expect(@response.status).to eq(status.to_i)
|
9
|
+
end
|
10
|
+
|
11
|
+
When 'I list the users' do
|
12
|
+
@response = get '/api/users'
|
13
|
+
@users = JSON.parse(@response.body)
|
14
|
+
end
|
15
|
+
|
16
|
+
Then 'the response should include the user' do
|
17
|
+
expect(@users.map { |u| u['login'] }).to include('alice')
|
18
|
+
end
|
data/spec/hook_spec.rb
CHANGED
@@ -5,7 +5,17 @@ require 'appmap/hook'
|
|
5
5
|
require 'appmap/event'
|
6
6
|
require 'diffy'
|
7
7
|
|
8
|
-
|
8
|
+
# Show nulls as the literal +null+, rather than just leaving the field
|
9
|
+
# empty. This make some of the expected YAML below easier to
|
10
|
+
# understand.
|
11
|
+
module ShowYamlNulls
|
12
|
+
def visit_NilClass(o)
|
13
|
+
@emitter.scalar('null', nil, 'tag:yaml.org,2002:null', true, false, Psych::Nodes::Scalar::ANY)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
Psych::Visitors::YAMLTree.prepend(ShowYamlNulls)
|
17
|
+
|
18
|
+
describe 'AppMap class Hooking', docker: false do
|
9
19
|
def collect_events(tracer)
|
10
20
|
[].tap do |events|
|
11
21
|
while tracer.event?
|
@@ -20,7 +30,10 @@ describe 'AppMap class Hooking' do
|
|
20
30
|
(event[:parameters] || []).each(&delete_object_id)
|
21
31
|
(event[:exceptions] || []).each(&delete_object_id)
|
22
32
|
|
23
|
-
|
33
|
+
case event[:event]
|
34
|
+
when :call
|
35
|
+
event[:path] = event[:path].gsub(Gem.dir + '/', '')
|
36
|
+
when :return
|
24
37
|
# These should be removed from the appmap spec
|
25
38
|
%i[defined_class method_id path lineno static].each do |obsolete_field|
|
26
39
|
event.delete(obsolete_field)
|
@@ -30,24 +43,30 @@ describe 'AppMap class Hooking' do
|
|
30
43
|
end.to_yaml
|
31
44
|
end
|
32
45
|
|
33
|
-
def invoke_test_file(file, &block)
|
34
|
-
|
35
|
-
|
36
|
-
AppMap::
|
37
|
-
|
38
|
-
tracer =
|
39
|
-
AppMap::
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
46
|
+
def invoke_test_file(file, setup: nil, &block)
|
47
|
+
AppMap.configuration = nil
|
48
|
+
package = AppMap::Package.new(file, [])
|
49
|
+
config = AppMap::Config.new('hook_spec', [ package ])
|
50
|
+
AppMap.configuration = config
|
51
|
+
tracer = nil
|
52
|
+
AppMap::Hook.new(config).enable do
|
53
|
+
setup_result = setup.call if setup
|
54
|
+
|
55
|
+
tracer = AppMap.tracing.trace
|
56
|
+
AppMap::Event.reset_id_counter
|
57
|
+
begin
|
58
|
+
load file
|
59
|
+
yield setup_result
|
60
|
+
ensure
|
61
|
+
AppMap.tracing.delete(tracer)
|
62
|
+
end
|
45
63
|
end
|
64
|
+
|
46
65
|
[ config, tracer ]
|
47
66
|
end
|
48
67
|
|
49
|
-
def test_hook_behavior(file, events_yaml, &block)
|
50
|
-
config, tracer = invoke_test_file(file, &block)
|
68
|
+
def test_hook_behavior(file, events_yaml, setup: nil, &block)
|
69
|
+
config, tracer = invoke_test_file(file, setup: setup, &block)
|
51
70
|
|
52
71
|
events = collect_events(tracer)
|
53
72
|
expect(Diffy::Diff.new(events, events_yaml).to_s).to eq('')
|
@@ -55,6 +74,10 @@ describe 'AppMap class Hooking' do
|
|
55
74
|
[ config, tracer ]
|
56
75
|
end
|
57
76
|
|
77
|
+
after do
|
78
|
+
AppMap.configuration = nil
|
79
|
+
end
|
80
|
+
|
58
81
|
it 'hooks an instance method that takes no arguments' do
|
59
82
|
events_yaml = <<~YAML
|
60
83
|
---
|
@@ -86,14 +109,14 @@ describe 'AppMap class Hooking' do
|
|
86
109
|
InstanceMethod.new.say_default
|
87
110
|
end
|
88
111
|
expect(tracer.event_methods.to_a.map(&:defined_class)).to eq([ 'InstanceMethod' ])
|
89
|
-
expect(tracer.event_methods.to_a.map(&:
|
112
|
+
expect(tracer.event_methods.to_a.map(&:to_s)).to eq([ InstanceMethod.public_instance_method(:say_default).to_s ])
|
90
113
|
end
|
91
114
|
|
92
115
|
it 'builds a class map of invoked methods' do
|
93
|
-
|
116
|
+
_, tracer = invoke_test_file 'spec/fixtures/hook/instance_method.rb' do
|
94
117
|
InstanceMethod.new.say_default
|
95
118
|
end
|
96
|
-
class_map = AppMap.class_map(
|
119
|
+
class_map = AppMap.class_map(tracer.event_methods).to_yaml
|
97
120
|
expect(Diffy::Diff.new(class_map, <<~YAML).to_s).to eq('')
|
98
121
|
---
|
99
122
|
- :name: spec/fixtures/hook/instance_method.rb
|
@@ -202,7 +225,7 @@ describe 'AppMap class Hooking' do
|
|
202
225
|
:parameters:
|
203
226
|
- :name: :kw
|
204
227
|
:class: NilClass
|
205
|
-
:value:
|
228
|
+
:value: null
|
206
229
|
:kind: :key
|
207
230
|
:receiver:
|
208
231
|
:class: InstanceMethod
|
@@ -232,7 +255,7 @@ describe 'AppMap class Hooking' do
|
|
232
255
|
:parameters:
|
233
256
|
- :name: :block
|
234
257
|
:class: NilClass
|
235
|
-
:value:
|
258
|
+
:value: null
|
236
259
|
:kind: :block
|
237
260
|
:receiver:
|
238
261
|
:class: InstanceMethod
|
@@ -254,15 +277,15 @@ describe 'AppMap class Hooking' do
|
|
254
277
|
---
|
255
278
|
- :id: 1
|
256
279
|
:event: :call
|
257
|
-
:defined_class:
|
280
|
+
:defined_class: SingletonMethod
|
258
281
|
:method_id: say_default
|
259
|
-
:path: spec/fixtures/hook/
|
282
|
+
:path: spec/fixtures/hook/singleton_method.rb
|
260
283
|
:lineno: 5
|
261
284
|
:static: true
|
262
285
|
:parameters: []
|
263
286
|
:receiver:
|
264
287
|
:class: Class
|
265
|
-
:value:
|
288
|
+
:value: SingletonMethod
|
266
289
|
- :id: 2
|
267
290
|
:event: :return
|
268
291
|
:parent_id: 1
|
@@ -270,8 +293,8 @@ describe 'AppMap class Hooking' do
|
|
270
293
|
:class: String
|
271
294
|
:value: default
|
272
295
|
YAML
|
273
|
-
test_hook_behavior 'spec/fixtures/hook/
|
274
|
-
expect(
|
296
|
+
test_hook_behavior 'spec/fixtures/hook/singleton_method.rb', events_yaml do
|
297
|
+
expect(SingletonMethod.say_default).to eq('default')
|
275
298
|
end
|
276
299
|
end
|
277
300
|
|
@@ -280,15 +303,15 @@ describe 'AppMap class Hooking' do
|
|
280
303
|
---
|
281
304
|
- :id: 1
|
282
305
|
:event: :call
|
283
|
-
:defined_class:
|
306
|
+
:defined_class: SingletonMethod
|
284
307
|
:method_id: say_class_defined
|
285
|
-
:path: spec/fixtures/hook/
|
308
|
+
:path: spec/fixtures/hook/singleton_method.rb
|
286
309
|
:lineno: 10
|
287
310
|
:static: true
|
288
311
|
:parameters: []
|
289
312
|
:receiver:
|
290
313
|
:class: Class
|
291
|
-
:value:
|
314
|
+
:value: SingletonMethod
|
292
315
|
- :id: 2
|
293
316
|
:event: :return
|
294
317
|
:parent_id: 1
|
@@ -296,8 +319,8 @@ describe 'AppMap class Hooking' do
|
|
296
319
|
:class: String
|
297
320
|
:value: defined with explicit class scope
|
298
321
|
YAML
|
299
|
-
test_hook_behavior 'spec/fixtures/hook/
|
300
|
-
expect(
|
322
|
+
test_hook_behavior 'spec/fixtures/hook/singleton_method.rb', events_yaml do
|
323
|
+
expect(SingletonMethod.say_class_defined).to eq('defined with explicit class scope')
|
301
324
|
end
|
302
325
|
end
|
303
326
|
|
@@ -306,15 +329,15 @@ describe 'AppMap class Hooking' do
|
|
306
329
|
---
|
307
330
|
- :id: 1
|
308
331
|
:event: :call
|
309
|
-
:defined_class:
|
332
|
+
:defined_class: SingletonMethod
|
310
333
|
:method_id: say_self_defined
|
311
|
-
:path: spec/fixtures/hook/
|
334
|
+
:path: spec/fixtures/hook/singleton_method.rb
|
312
335
|
:lineno: 14
|
313
336
|
:static: true
|
314
337
|
:parameters: []
|
315
338
|
:receiver:
|
316
339
|
:class: Class
|
317
|
-
:value:
|
340
|
+
:value: SingletonMethod
|
318
341
|
- :id: 2
|
319
342
|
:event: :return
|
320
343
|
:parent_id: 1
|
@@ -322,8 +345,74 @@ describe 'AppMap class Hooking' do
|
|
322
345
|
:class: String
|
323
346
|
:value: defined with self class scope
|
324
347
|
YAML
|
325
|
-
test_hook_behavior 'spec/fixtures/hook/
|
326
|
-
expect(
|
348
|
+
test_hook_behavior 'spec/fixtures/hook/singleton_method.rb', events_yaml do
|
349
|
+
expect(SingletonMethod.say_self_defined).to eq('defined with self class scope')
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
|
354
|
+
it 'hooks an included method' do
|
355
|
+
events_yaml = <<~YAML
|
356
|
+
---
|
357
|
+
- :id: 1
|
358
|
+
:event: :call
|
359
|
+
:defined_class: SingletonMethod
|
360
|
+
:method_id: added_method
|
361
|
+
:path: spec/fixtures/hook/singleton_method.rb
|
362
|
+
:lineno: 44
|
363
|
+
:static: false
|
364
|
+
:parameters: []
|
365
|
+
:receiver:
|
366
|
+
:class: SingletonMethod
|
367
|
+
:value: Singleton Method fixture
|
368
|
+
- :id: 2
|
369
|
+
:event: :call
|
370
|
+
:defined_class: AddMethod
|
371
|
+
:method_id: _added_method
|
372
|
+
:path: spec/fixtures/hook/singleton_method.rb
|
373
|
+
:lineno: 50
|
374
|
+
:static: false
|
375
|
+
:parameters: []
|
376
|
+
:receiver:
|
377
|
+
:class: SingletonMethod
|
378
|
+
:value: Singleton Method fixture
|
379
|
+
- :id: 3
|
380
|
+
:event: :return
|
381
|
+
:parent_id: 2
|
382
|
+
:return_value:
|
383
|
+
:class: String
|
384
|
+
:value: defined by including a module
|
385
|
+
- :id: 4
|
386
|
+
:event: :return
|
387
|
+
:parent_id: 1
|
388
|
+
:return_value:
|
389
|
+
:class: String
|
390
|
+
:value: defined by including a module
|
391
|
+
YAML
|
392
|
+
|
393
|
+
load 'spec/fixtures/hook/singleton_method.rb'
|
394
|
+
setup = -> { SingletonMethod.new.do_include }
|
395
|
+
test_hook_behavior 'spec/fixtures/hook/singleton_method.rb', events_yaml, setup: setup do |s|
|
396
|
+
expect(s.added_method).to eq('defined by including a module')
|
397
|
+
end
|
398
|
+
end
|
399
|
+
|
400
|
+
it "doesn't hook a singleton method defined for an instance" do
|
401
|
+
# Ideally, Ruby would fire a TracePoint event when a singleton
|
402
|
+
# class gets created by defining a method on an instance. It
|
403
|
+
# currently doesn't, though, so there's no way for us to hook such
|
404
|
+
# a method.
|
405
|
+
#
|
406
|
+
# This example will fail if Ruby's behavior changes at some point
|
407
|
+
# in the future.
|
408
|
+
events_yaml = <<~YAML
|
409
|
+
--- []
|
410
|
+
YAML
|
411
|
+
|
412
|
+
load 'spec/fixtures/hook/singleton_method.rb'
|
413
|
+
setup = -> { SingletonMethod.new_with_instance_method }
|
414
|
+
test_hook_behavior 'spec/fixtures/hook/singleton_method.rb', events_yaml, setup: setup do |s|
|
415
|
+
expect(s.say_instance_defined).to eq('defined for an instance')
|
327
416
|
end
|
328
417
|
end
|
329
418
|
|
@@ -366,4 +455,122 @@ describe 'AppMap class Hooking' do
|
|
366
455
|
expect { ExceptionMethod.new.raise_exception }.to raise_exception
|
367
456
|
end
|
368
457
|
end
|
458
|
+
|
459
|
+
context 'OpenSSL::X509::Certificate.sign' do
|
460
|
+
# OpenSSL::X509 is not being hooked.
|
461
|
+
# This might be because the class is being loaded before AppMap, and so the TracePoint
|
462
|
+
# set by AppMap doesn't see it.
|
463
|
+
xit 'is hooked' do
|
464
|
+
events_yaml = <<~YAML
|
465
|
+
---
|
466
|
+
YAML
|
467
|
+
test_hook_behavior 'spec/fixtures/hook/openssl_sign.rb', events_yaml do
|
468
|
+
expect(OpenSSLExample.example).to be_truthy
|
469
|
+
end
|
470
|
+
end
|
471
|
+
end
|
472
|
+
|
473
|
+
context 'ActiveSupport::SecurityUtils.secure_compare' do
|
474
|
+
it 'is hooked' do
|
475
|
+
events_yaml = <<~YAML
|
476
|
+
---
|
477
|
+
- :id: 1
|
478
|
+
:event: :call
|
479
|
+
:defined_class: Compare
|
480
|
+
:method_id: compare
|
481
|
+
:path: spec/fixtures/hook/compare.rb
|
482
|
+
:lineno: 4
|
483
|
+
:static: true
|
484
|
+
:parameters:
|
485
|
+
- :name: :s1
|
486
|
+
:class: String
|
487
|
+
:value: string
|
488
|
+
:kind: :req
|
489
|
+
- :name: :s2
|
490
|
+
:class: String
|
491
|
+
:value: string
|
492
|
+
:kind: :req
|
493
|
+
:receiver:
|
494
|
+
:class: Class
|
495
|
+
:value: Compare
|
496
|
+
- :id: 2
|
497
|
+
:event: :call
|
498
|
+
:defined_class: ActiveSupport::SecurityUtils
|
499
|
+
:method_id: secure_compare
|
500
|
+
:path: gems/activesupport-6.0.3.2/lib/active_support/security_utils.rb
|
501
|
+
:lineno: 26
|
502
|
+
:static: true
|
503
|
+
:parameters:
|
504
|
+
- :name: :a
|
505
|
+
:class: String
|
506
|
+
:value: string
|
507
|
+
:kind: :req
|
508
|
+
- :name: :b
|
509
|
+
:class: String
|
510
|
+
:value: string
|
511
|
+
:kind: :req
|
512
|
+
:receiver:
|
513
|
+
:class: Module
|
514
|
+
:value: ActiveSupport::SecurityUtils
|
515
|
+
- :id: 3
|
516
|
+
:event: :return
|
517
|
+
:parent_id: 2
|
518
|
+
:return_value:
|
519
|
+
:class: TrueClass
|
520
|
+
:value: 'true'
|
521
|
+
- :id: 4
|
522
|
+
:event: :return
|
523
|
+
:parent_id: 1
|
524
|
+
:return_value:
|
525
|
+
:class: TrueClass
|
526
|
+
:value: 'true'
|
527
|
+
YAML
|
528
|
+
|
529
|
+
test_hook_behavior 'spec/fixtures/hook/compare.rb', events_yaml do
|
530
|
+
expect(Compare.compare('string', 'string')).to be_truthy
|
531
|
+
end
|
532
|
+
end
|
533
|
+
|
534
|
+
it 'gets labeled in the classmap' do
|
535
|
+
classmap_yaml = <<~YAML
|
536
|
+
---
|
537
|
+
- :name: spec/fixtures/hook/compare.rb
|
538
|
+
:type: package
|
539
|
+
:children:
|
540
|
+
- :name: Compare
|
541
|
+
:type: class
|
542
|
+
:children:
|
543
|
+
- :name: compare
|
544
|
+
:type: function
|
545
|
+
:location: spec/fixtures/hook/compare.rb:4
|
546
|
+
:static: true
|
547
|
+
- :name: active_support
|
548
|
+
:type: package
|
549
|
+
:children:
|
550
|
+
- :name: ActiveSupport
|
551
|
+
:type: class
|
552
|
+
:children:
|
553
|
+
- :name: SecurityUtils
|
554
|
+
:type: class
|
555
|
+
:children:
|
556
|
+
- :name: secure_compare
|
557
|
+
:type: function
|
558
|
+
:location: gems/activesupport-6.0.3.2/lib/active_support/security_utils.rb:26
|
559
|
+
:static: true
|
560
|
+
:labels:
|
561
|
+
- security
|
562
|
+
YAML
|
563
|
+
|
564
|
+
config, tracer = invoke_test_file 'spec/fixtures/hook/compare.rb' do
|
565
|
+
expect(Compare.compare('string', 'string')).to be_truthy
|
566
|
+
end
|
567
|
+
cm = AppMap::ClassMap.build_from_methods(config, tracer.event_methods)
|
568
|
+
entry = cm[1][:children][0][:children][0][:children][0]
|
569
|
+
# Sanity check, make sure we got the right one
|
570
|
+
expect(entry[:name]).to eq('secure_compare')
|
571
|
+
spec = Gem::Specification.find_by_name('activesupport')
|
572
|
+
entry[:location].gsub!(spec.base_dir + '/', '')
|
573
|
+
expect(Diffy::Diff.new(cm.to_yaml, classmap_yaml).to_s).to eq('')
|
574
|
+
end
|
575
|
+
end
|
369
576
|
end
|