appmap 0.33.0 → 0.34.5
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 +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
|