appmap 0.33.0 → 0.34.5
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 +27 -0
- data/README.md +17 -2
- data/Rakefile +10 -3
- data/appmap.gemspec +5 -0
- data/ext/appmap/appmap.c +95 -0
- data/ext/appmap/extconf.rb +6 -0
- data/lib/appmap.rb +3 -0
- data/lib/appmap/class_map.rb +11 -6
- data/lib/appmap/config.rb +51 -25
- data/lib/appmap/cucumber.rb +19 -2
- data/lib/appmap/hook.rb +44 -16
- data/lib/appmap/hook/method.rb +53 -25
- data/lib/appmap/rails/sql_handler.rb +5 -10
- data/lib/appmap/rspec.rb +1 -1
- data/lib/appmap/util.rb +18 -1
- data/lib/appmap/version.rb +1 -1
- data/spec/fixtures/hook/instance_method.rb +4 -0
- data/spec/fixtures/hook/singleton_method.rb +21 -12
- data/spec/hook_spec.rb +148 -13
- data/test/cli_test.rb +10 -0
- data/test/fixtures/openssl_recorder/Gemfile +3 -0
- data/test/fixtures/openssl_recorder/appmap.yml +3 -0
- data/test/fixtures/openssl_recorder/lib/openssl_cert_sign.rb +94 -0
- 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
- metadata +55 -3
data/lib/appmap/hook.rb
CHANGED
@@ -6,23 +6,21 @@ module AppMap
|
|
6
6
|
class Hook
|
7
7
|
LOG = (ENV['DEBUG'] == 'true')
|
8
8
|
|
9
|
+
@unbound_method_arity = ::UnboundMethod.instance_method(:arity)
|
10
|
+
@method_arity = ::Method.instance_method(:arity)
|
11
|
+
|
9
12
|
class << self
|
13
|
+
def lock_builtins
|
14
|
+
return if @builtins_hooked
|
15
|
+
|
16
|
+
@builtins_hooked = true
|
17
|
+
end
|
18
|
+
|
10
19
|
# Return the class, separator ('.' or '#'), and method name for
|
11
20
|
# the given method.
|
12
21
|
def qualify_method_name(method)
|
13
22
|
if method.owner.singleton_class?
|
14
|
-
|
15
|
-
# #<Class:Foo> or
|
16
|
-
# #<Class:#<Bar:0x0123ABC>>. Retrieve the name of
|
17
|
-
# the class from the string.
|
18
|
-
#
|
19
|
-
# (There really isn't a better way to do this. The
|
20
|
-
# singleton's reference to the class it was created
|
21
|
-
# from is stored in an instance variable named
|
22
|
-
# '__attached__'. It doesn't have the '@' prefix, so
|
23
|
-
# it's internal only, and not accessible from user
|
24
|
-
# code.)
|
25
|
-
class_name = /#<Class:((#<(?<cls>.*?):)|((?<cls>.*?)>))/.match(method.owner.to_s)['cls']
|
23
|
+
class_name = singleton_method_owner_name(method)
|
26
24
|
[ class_name, '.', method.name ]
|
27
25
|
else
|
28
26
|
[ method.owner.name, '#', method.name ]
|
@@ -39,6 +37,8 @@ module AppMap
|
|
39
37
|
def enable &block
|
40
38
|
require 'appmap/hook/method'
|
41
39
|
|
40
|
+
hook_builtins
|
41
|
+
|
42
42
|
tp = TracePoint.new(:end) do |trace_point|
|
43
43
|
cls = trace_point.self
|
44
44
|
|
@@ -47,12 +47,10 @@ module AppMap
|
|
47
47
|
|
48
48
|
hook = lambda do |hook_cls|
|
49
49
|
lambda do |method_id|
|
50
|
-
next if method_id.to_s =~ /_hooked_by_appmap$/
|
51
|
-
|
52
50
|
method = hook_cls.public_instance_method(method_id)
|
53
51
|
hook_method = Hook::Method.new(hook_cls, method)
|
54
52
|
|
55
|
-
warn "AppMap: Examining #{
|
53
|
+
warn "AppMap: Examining #{hook_cls} #{method.name}" if LOG
|
56
54
|
|
57
55
|
disasm = RubyVM::InstructionSequence.disasm(method)
|
58
56
|
# Skip methods that have no instruction sequence, as they are obviously trivial.
|
@@ -63,7 +61,7 @@ module AppMap
|
|
63
61
|
next if /\AAppMap[:\.]/.match?(hook_method.method_display_name)
|
64
62
|
|
65
63
|
next unless \
|
66
|
-
config.always_hook?(
|
64
|
+
config.always_hook?(hook_cls, method.name) ||
|
67
65
|
config.included_by_location?(method)
|
68
66
|
|
69
67
|
hook_method.activate
|
@@ -76,5 +74,35 @@ module AppMap
|
|
76
74
|
|
77
75
|
tp.enable(&block)
|
78
76
|
end
|
77
|
+
|
78
|
+
def hook_builtins
|
79
|
+
return unless self.class.lock_builtins
|
80
|
+
|
81
|
+
class_from_string = lambda do |fq_class|
|
82
|
+
fq_class.split('::').inject(Object) do |mod, class_name|
|
83
|
+
mod.const_get(class_name)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
Config::BUILTIN_METHODS.each do |class_name, hook|
|
88
|
+
require hook.package.package_name if hook.package.package_name
|
89
|
+
Array(hook.method_names).each do |method_name|
|
90
|
+
method_name = method_name.to_sym
|
91
|
+
cls = class_from_string.(class_name)
|
92
|
+
method = \
|
93
|
+
begin
|
94
|
+
cls.instance_method(method_name)
|
95
|
+
rescue NameError
|
96
|
+
cls.method(method_name) rescue nil
|
97
|
+
end
|
98
|
+
|
99
|
+
if method
|
100
|
+
Hook::Method.new(cls, method).activate
|
101
|
+
else
|
102
|
+
warn "Method #{method_name} not found on #{cls.name}"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
79
107
|
end
|
80
108
|
end
|
data/lib/appmap/hook/method.rb
CHANGED
@@ -1,63 +1,91 @@
|
|
1
1
|
module AppMap
|
2
2
|
class Hook
|
3
3
|
class Method
|
4
|
-
attr_reader :hook_class, :hook_method
|
4
|
+
attr_reader :hook_class, :hook_method
|
5
|
+
|
6
|
+
# +method_display_name+ may be nil if name resolution gets
|
7
|
+
# deferred until runtime (e.g. for a singleton method on an
|
8
|
+
# embedded Struct).
|
9
|
+
attr_reader :method_display_name
|
5
10
|
|
6
11
|
HOOK_DISABLE_KEY = 'AppMap::Hook.disable'
|
7
12
|
private_constant :HOOK_DISABLE_KEY
|
8
13
|
|
14
|
+
# Grab the definition of Time.now here, to avoid interfering
|
15
|
+
# with the method we're hooking.
|
16
|
+
TIME_NOW = Time.method(:now)
|
17
|
+
private_constant :TIME_NOW
|
18
|
+
|
9
19
|
def initialize(hook_class, hook_method)
|
10
20
|
@hook_class = hook_class
|
11
21
|
@hook_method = hook_method
|
22
|
+
|
23
|
+
# Get the class for the method, if it's known.
|
12
24
|
@defined_class, method_symbol = Hook.qualify_method_name(@hook_method)
|
13
|
-
@method_display_name = [@defined_class, method_symbol, @hook_method.name].join
|
25
|
+
@method_display_name = [@defined_class, method_symbol, @hook_method.name].join if @defined_class
|
14
26
|
end
|
15
27
|
|
16
28
|
def activate
|
17
|
-
|
29
|
+
if Hook::LOG
|
30
|
+
msg = if method_display_name
|
31
|
+
"#{method_display_name}"
|
32
|
+
else
|
33
|
+
"#{hook_method.name} (class resolution deferred)"
|
34
|
+
end
|
35
|
+
warn "AppMap: Hooking " + msg
|
36
|
+
end
|
18
37
|
|
38
|
+
defined_class = @defined_class
|
19
39
|
hook_method = self.hook_method
|
20
40
|
before_hook = self.method(:before_hook)
|
21
41
|
after_hook = self.method(:after_hook)
|
22
42
|
with_disabled_hook = self.method(:with_disabled_hook)
|
23
43
|
|
24
|
-
|
25
|
-
|
44
|
+
hook_method_def = nil
|
45
|
+
hook_class.instance_eval do
|
46
|
+
hook_method_def = Proc.new do |*args, &block|
|
47
|
+
instance_method = hook_method.bind(self).to_proc
|
26
48
|
|
27
|
-
|
28
|
-
|
29
|
-
|
49
|
+
# We may not have gotten the class for the method during
|
50
|
+
# initialization (e.g. for a singleton method on an embedded
|
51
|
+
# struct), so make sure we have it now.
|
52
|
+
defined_class,_ = Hook.qualify_method_name(hook_method) unless defined_class
|
30
53
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
exception =
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
54
|
+
hook_disabled = Thread.current[HOOK_DISABLE_KEY]
|
55
|
+
enabled = true if !hook_disabled && AppMap.tracing.enabled?
|
56
|
+
return instance_method.call(*args, &block) unless enabled
|
57
|
+
|
58
|
+
call_event, start_time = with_disabled_hook.() do
|
59
|
+
before_hook.(self, defined_class, args)
|
60
|
+
end
|
61
|
+
return_value = nil
|
62
|
+
exception = nil
|
63
|
+
begin
|
64
|
+
return_value = instance_method.(*args, &block)
|
65
|
+
rescue
|
66
|
+
exception = $ERROR_INFO
|
67
|
+
raise
|
68
|
+
ensure
|
69
|
+
with_disabled_hook.() do
|
70
|
+
after_hook.(call_event, start_time, return_value, exception)
|
71
|
+
end
|
44
72
|
end
|
45
73
|
end
|
46
74
|
end
|
75
|
+
hook_class.define_method_with_arity(hook_method.name, hook_method.arity, hook_method_def)
|
47
76
|
end
|
48
|
-
|
49
77
|
protected
|
50
78
|
|
51
|
-
def before_hook(receiver, args)
|
79
|
+
def before_hook(receiver, defined_class, args)
|
52
80
|
require 'appmap/event'
|
53
81
|
call_event = AppMap::Event::MethodCall.build_from_invocation(defined_class, hook_method, receiver, args)
|
54
82
|
AppMap.tracing.record_event call_event, defined_class: defined_class, method: hook_method
|
55
|
-
[ call_event,
|
83
|
+
[ call_event, TIME_NOW.call ]
|
56
84
|
end
|
57
85
|
|
58
86
|
def after_hook(call_event, start_time, return_value, exception)
|
59
87
|
require 'appmap/event'
|
60
|
-
elapsed =
|
88
|
+
elapsed = TIME_NOW.call - start_time
|
61
89
|
return_event = \
|
62
90
|
AppMap::Event::MethodReturn.build_from_invocation call_event.id, elapsed, return_value, exception
|
63
91
|
AppMap.tracing.record_event return_event
|
@@ -73,20 +73,15 @@ module AppMap
|
|
73
73
|
|
74
74
|
class ActiveRecordExaminer
|
75
75
|
def server_version
|
76
|
-
|
77
|
-
|
78
|
-
ActiveRecord::Base.connection.postgresql_version
|
79
|
-
when :sqlite
|
80
|
-
ActiveRecord::Base.connection.database_version.to_s
|
81
|
-
else
|
82
|
-
warn "Unable to determine database version for #{database_type.inspect}"
|
83
|
-
end
|
76
|
+
ActiveRecord::Base.connection.try(:database_version) ||\
|
77
|
+
warn("Unable to determine database version for #{database_type.inspect}")
|
84
78
|
end
|
85
79
|
|
86
80
|
def database_type
|
87
|
-
|
81
|
+
type = ActiveRecord::Base.connection.adapter_name.downcase.to_sym
|
82
|
+
type = :postgres if type == :postgresql
|
88
83
|
|
89
|
-
|
84
|
+
type
|
90
85
|
end
|
91
86
|
|
92
87
|
def execute_query(sql)
|
data/lib/appmap/rspec.rb
CHANGED
@@ -154,7 +154,7 @@ module AppMap
|
|
154
154
|
end
|
155
155
|
|
156
156
|
labels = labels.map(&:to_s).map(&:strip).reject(&:blank?).map(&:downcase).uniq
|
157
|
-
description.reject!(&:nil?).reject(&:blank?)
|
157
|
+
description.reject!(&:nil?).reject!(&:blank?)
|
158
158
|
default_description = description.last
|
159
159
|
description.reverse!
|
160
160
|
|
data/lib/appmap/util.rb
CHANGED
@@ -36,6 +36,23 @@ module AppMap
|
|
36
36
|
[ fname, extension ].join
|
37
37
|
end
|
38
38
|
|
39
|
+
# sanitize_paths removes ephemeral values from objects with
|
40
|
+
# embedded paths (e.g. an event or a classmap), making events
|
41
|
+
# easier to compare across runs.
|
42
|
+
def sanitize_paths(h)
|
43
|
+
require 'hashie'
|
44
|
+
h.extend(Hashie::Extensions::DeepLocate)
|
45
|
+
keys = %i(path location)
|
46
|
+
h.deep_locate ->(k,v,o) {
|
47
|
+
next unless keys.include?(k)
|
48
|
+
|
49
|
+
fix = ->(v) {v.gsub(%r{#{Gem.dir}/gems/.*(?=lib)}, '')}
|
50
|
+
keys.each {|k| o[k] = fix.(o[k]) if o[k] }
|
51
|
+
}
|
52
|
+
|
53
|
+
h
|
54
|
+
end
|
55
|
+
|
39
56
|
# sanitize_event removes ephemeral values from an event, making
|
40
57
|
# events easier to compare across runs.
|
41
58
|
def sanitize_event(event, &block)
|
@@ -49,7 +66,7 @@ module AppMap
|
|
49
66
|
|
50
67
|
case event[:event]
|
51
68
|
when :call
|
52
|
-
event
|
69
|
+
sanitize_paths(event)
|
53
70
|
end
|
54
71
|
|
55
72
|
event
|
data/lib/appmap/version.rb
CHANGED
@@ -15,6 +15,20 @@ class SingletonMethod
|
|
15
15
|
'defined with self class scope'
|
16
16
|
end
|
17
17
|
|
18
|
+
module AddMethod
|
19
|
+
def self.included(base)
|
20
|
+
base.module_eval do
|
21
|
+
define_method "added_method" do
|
22
|
+
_added_method
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def _added_method
|
28
|
+
'defined by including a module'
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
18
32
|
# When called, do_include calls +include+ to bring in the module
|
19
33
|
# AddMethod. AddMethod defines a new instance method, which gets
|
20
34
|
# added to the singleton class of SingletonMethod.
|
@@ -32,23 +46,18 @@ class SingletonMethod
|
|
32
46
|
end
|
33
47
|
end
|
34
48
|
end
|
35
|
-
|
36
|
-
def to_s
|
37
|
-
'Singleton Method fixture'
|
38
|
-
end
|
39
|
-
end
|
40
49
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
_added_method
|
50
|
+
STRUCT_TEST = Struct.new(:attr) do
|
51
|
+
class << self
|
52
|
+
def say_struct_singleton
|
53
|
+
'singleton for a struct'
|
46
54
|
end
|
47
55
|
end
|
48
56
|
end
|
49
57
|
|
50
|
-
def
|
51
|
-
'
|
58
|
+
def to_s
|
59
|
+
'Singleton Method fixture'
|
52
60
|
end
|
53
61
|
end
|
54
62
|
|
63
|
+
|
data/spec/hook_spec.rb
CHANGED
@@ -22,12 +22,12 @@ describe 'AppMap class Hooking', docker: false do
|
|
22
22
|
while tracer.event?
|
23
23
|
events << tracer.next_event.to_h
|
24
24
|
end
|
25
|
-
end.map(&AppMap::Util.method(:sanitize_event))
|
25
|
+
end.map(&AppMap::Util.method(:sanitize_event))
|
26
26
|
end
|
27
27
|
|
28
28
|
def invoke_test_file(file, setup: nil, &block)
|
29
29
|
AppMap.configuration = nil
|
30
|
-
package = AppMap::Package.new(file
|
30
|
+
package = AppMap::Config::Package.new(file)
|
31
31
|
config = AppMap::Config.new('hook_spec', [ package ])
|
32
32
|
AppMap.configuration = config
|
33
33
|
tracer = nil
|
@@ -50,8 +50,9 @@ describe 'AppMap class Hooking', docker: false do
|
|
50
50
|
def test_hook_behavior(file, events_yaml, setup: nil, &block)
|
51
51
|
config, tracer = invoke_test_file(file, setup: setup, &block)
|
52
52
|
|
53
|
-
events = collect_events(tracer)
|
54
|
-
|
53
|
+
events = collect_events(tracer).to_yaml
|
54
|
+
|
55
|
+
expect(Diffy::Diff.new(events_yaml, events).to_s).to eq('')
|
55
56
|
|
56
57
|
[ config, tracer ]
|
57
58
|
end
|
@@ -99,7 +100,7 @@ describe 'AppMap class Hooking', docker: false do
|
|
99
100
|
InstanceMethod.new.say_default
|
100
101
|
end
|
101
102
|
class_map = AppMap.class_map(tracer.event_methods).to_yaml
|
102
|
-
expect(Diffy::Diff.new(
|
103
|
+
expect(Diffy::Diff.new(<<~YAML, class_map).to_s).to eq('')
|
103
104
|
---
|
104
105
|
- :name: spec/fixtures/hook/instance_method.rb
|
105
106
|
:type: package
|
@@ -341,7 +342,7 @@ describe 'AppMap class Hooking', docker: false do
|
|
341
342
|
:defined_class: SingletonMethod
|
342
343
|
:method_id: added_method
|
343
344
|
:path: spec/fixtures/hook/singleton_method.rb
|
344
|
-
:lineno:
|
345
|
+
:lineno: 21
|
345
346
|
:static: false
|
346
347
|
:parameters: []
|
347
348
|
:receiver:
|
@@ -349,10 +350,10 @@ describe 'AppMap class Hooking', docker: false do
|
|
349
350
|
:value: Singleton Method fixture
|
350
351
|
- :id: 2
|
351
352
|
:event: :call
|
352
|
-
:defined_class: AddMethod
|
353
|
+
:defined_class: SingletonMethod::AddMethod
|
353
354
|
:method_id: _added_method
|
354
355
|
:path: spec/fixtures/hook/singleton_method.rb
|
355
|
-
:lineno:
|
356
|
+
:lineno: 27
|
356
357
|
:static: false
|
357
358
|
:parameters: []
|
358
359
|
:receiver:
|
@@ -394,10 +395,44 @@ describe 'AppMap class Hooking', docker: false do
|
|
394
395
|
load 'spec/fixtures/hook/singleton_method.rb'
|
395
396
|
setup = -> { SingletonMethod.new_with_instance_method }
|
396
397
|
test_hook_behavior 'spec/fixtures/hook/singleton_method.rb', events_yaml, setup: setup do |s|
|
398
|
+
# Make sure we're testing the right thing
|
399
|
+
say_instance_defined = s.method(:say_instance_defined)
|
400
|
+
expect(say_instance_defined.owner.to_s).to start_with('#<Class:#<SingletonMethod:')
|
401
|
+
|
402
|
+
# Verify the native extension works as expected
|
403
|
+
expect(AppMap::Hook.singleton_method_owner_name(say_instance_defined)).to eq('SingletonMethod')
|
404
|
+
|
397
405
|
expect(s.say_instance_defined).to eq('defined for an instance')
|
398
406
|
end
|
399
407
|
end
|
400
408
|
|
409
|
+
it 'hooks a singleton method on an embedded struct' do
|
410
|
+
events_yaml = <<~YAML
|
411
|
+
---
|
412
|
+
- :id: 1
|
413
|
+
:event: :call
|
414
|
+
:defined_class: SingletonMethod::STRUCT_TEST
|
415
|
+
:method_id: say_struct_singleton
|
416
|
+
:path: spec/fixtures/hook/singleton_method.rb
|
417
|
+
:lineno: 52
|
418
|
+
:static: true
|
419
|
+
:parameters: []
|
420
|
+
:receiver:
|
421
|
+
:class: Class
|
422
|
+
:value: SingletonMethod::STRUCT_TEST
|
423
|
+
- :id: 2
|
424
|
+
:event: :return
|
425
|
+
:parent_id: 1
|
426
|
+
:return_value:
|
427
|
+
:class: String
|
428
|
+
:value: singleton for a struct
|
429
|
+
YAML
|
430
|
+
|
431
|
+
test_hook_behavior 'spec/fixtures/hook/singleton_method.rb', events_yaml do
|
432
|
+
expect(SingletonMethod::STRUCT_TEST.say_struct_singleton).to eq('singleton for a struct')
|
433
|
+
end
|
434
|
+
end
|
435
|
+
|
401
436
|
it 'Reports exceptions' do
|
402
437
|
events_yaml = <<~YAML
|
403
438
|
---
|
@@ -465,7 +500,7 @@ describe 'AppMap class Hooking', docker: false do
|
|
465
500
|
:event: :call
|
466
501
|
:defined_class: ActiveSupport::SecurityUtils
|
467
502
|
:method_id: secure_compare
|
468
|
-
:path:
|
503
|
+
:path: lib/active_support/security_utils.rb
|
469
504
|
:lineno: 26
|
470
505
|
:static: true
|
471
506
|
:parameters:
|
@@ -481,12 +516,52 @@ describe 'AppMap class Hooking', docker: false do
|
|
481
516
|
:class: Module
|
482
517
|
:value: ActiveSupport::SecurityUtils
|
483
518
|
- :id: 3
|
519
|
+
:event: :call
|
520
|
+
:defined_class: Digest::Instance
|
521
|
+
:method_id: digest
|
522
|
+
:path: Digest::Instance#digest
|
523
|
+
:static: false
|
524
|
+
:parameters:
|
525
|
+
- :name: arg
|
526
|
+
:class: String
|
527
|
+
:value: string
|
528
|
+
:kind: :rest
|
529
|
+
:receiver:
|
530
|
+
:class: Digest::SHA256
|
531
|
+
:value: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
|
532
|
+
- :id: 4
|
533
|
+
:event: :return
|
534
|
+
:parent_id: 3
|
535
|
+
:return_value:
|
536
|
+
:class: String
|
537
|
+
:value: "G2__)__qc____X____3_].\\x02y__.___/_"
|
538
|
+
- :id: 5
|
539
|
+
:event: :call
|
540
|
+
:defined_class: Digest::Instance
|
541
|
+
:method_id: digest
|
542
|
+
:path: Digest::Instance#digest
|
543
|
+
:static: false
|
544
|
+
:parameters:
|
545
|
+
- :name: arg
|
546
|
+
:class: String
|
547
|
+
:value: string
|
548
|
+
:kind: :rest
|
549
|
+
:receiver:
|
550
|
+
:class: Digest::SHA256
|
551
|
+
:value: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
|
552
|
+
- :id: 6
|
553
|
+
:event: :return
|
554
|
+
:parent_id: 5
|
555
|
+
:return_value:
|
556
|
+
:class: String
|
557
|
+
:value: "G2__)__qc____X____3_].\\x02y__.___/_"
|
558
|
+
- :id: 7
|
484
559
|
:event: :return
|
485
560
|
:parent_id: 2
|
486
561
|
:return_value:
|
487
562
|
:class: TrueClass
|
488
563
|
:value: 'true'
|
489
|
-
- :id:
|
564
|
+
- :id: 8
|
490
565
|
:event: :return
|
491
566
|
:parent_id: 1
|
492
567
|
:return_value:
|
@@ -523,22 +598,82 @@ describe 'AppMap class Hooking', docker: false do
|
|
523
598
|
:children:
|
524
599
|
- :name: secure_compare
|
525
600
|
:type: function
|
526
|
-
:location:
|
601
|
+
:location: lib/active_support/security_utils.rb:26
|
527
602
|
:static: true
|
528
603
|
:labels:
|
529
604
|
- security
|
605
|
+
- crypto
|
606
|
+
- :name: openssl
|
607
|
+
:type: package
|
608
|
+
:children:
|
609
|
+
- :name: Digest
|
610
|
+
:type: class
|
611
|
+
:children:
|
612
|
+
- :name: Instance
|
613
|
+
:type: class
|
614
|
+
:children:
|
615
|
+
- :name: digest
|
616
|
+
:type: function
|
617
|
+
:location: Digest::Instance#digest
|
618
|
+
:static: false
|
619
|
+
:labels:
|
620
|
+
- security
|
621
|
+
- crypto
|
530
622
|
YAML
|
531
623
|
|
532
624
|
config, tracer = invoke_test_file 'spec/fixtures/hook/compare.rb' do
|
533
625
|
expect(Compare.compare('string', 'string')).to be_truthy
|
534
626
|
end
|
535
|
-
cm = AppMap::ClassMap.build_from_methods(config, tracer.event_methods)
|
627
|
+
cm = AppMap::Util.sanitize_paths(AppMap::ClassMap.build_from_methods(config, tracer.event_methods))
|
536
628
|
entry = cm[1][:children][0][:children][0][:children][0]
|
537
629
|
# Sanity check, make sure we got the right one
|
538
630
|
expect(entry[:name]).to eq('secure_compare')
|
539
631
|
spec = Gem::Specification.find_by_name('activesupport')
|
540
632
|
entry[:location].gsub!(spec.base_dir + '/', '')
|
541
|
-
expect(Diffy::Diff.new(cm.to_yaml
|
633
|
+
expect(Diffy::Diff.new(classmap_yaml, cm.to_yaml).to_s).to eq('')
|
634
|
+
end
|
635
|
+
end
|
636
|
+
|
637
|
+
it "doesn't cause expectations on Time.now to fail" do
|
638
|
+
events_yaml = <<~YAML
|
639
|
+
---
|
640
|
+
- :id: 1
|
641
|
+
:event: :call
|
642
|
+
:defined_class: InstanceMethod
|
643
|
+
:method_id: say_the_time
|
644
|
+
:path: spec/fixtures/hook/instance_method.rb
|
645
|
+
:lineno: 24
|
646
|
+
:static: false
|
647
|
+
:parameters: []
|
648
|
+
:receiver:
|
649
|
+
:class: InstanceMethod
|
650
|
+
:value: Instance Method fixture
|
651
|
+
- :id: 2
|
652
|
+
:event: :return
|
653
|
+
:parent_id: 1
|
654
|
+
:return_value:
|
655
|
+
:class: String
|
656
|
+
:value: '2020-01-01 00:00:00 +0000'
|
657
|
+
YAML
|
658
|
+
test_hook_behavior 'spec/fixtures/hook/instance_method.rb', events_yaml do
|
659
|
+
require 'timecop'
|
660
|
+
begin
|
661
|
+
tz = ENV['TZ']
|
662
|
+
ENV['TZ'] = 'UTC'
|
663
|
+
Timecop.freeze(Time.utc('2020-01-01')) do
|
664
|
+
expect(Time).to receive(:now).exactly(3).times.and_call_original
|
665
|
+
expect(InstanceMethod.new.say_the_time).to be
|
666
|
+
end
|
667
|
+
ensure
|
668
|
+
ENV['TZ'] = tz
|
669
|
+
end
|
670
|
+
end
|
671
|
+
end
|
672
|
+
|
673
|
+
it "preserves the arity of hooked methods" do
|
674
|
+
invoke_test_file 'spec/fixtures/hook/instance_method.rb' do
|
675
|
+
expect(InstanceMethod.instance_method(:say_echo).arity).to be(1)
|
676
|
+
expect(InstanceMethod.new.method(:say_echo).arity).to be(1)
|
542
677
|
end
|
543
678
|
end
|
544
679
|
end
|