appmap 0.34.1 → 0.34.2
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 +4 -0
- data/README.md +8 -2
- data/Rakefile +10 -3
- data/appmap.gemspec +3 -0
- data/ext/appmap/appmap.c +26 -0
- data/ext/appmap/extconf.rb +6 -0
- data/lib/appmap.rb +3 -0
- data/lib/appmap/hook.rb +3 -14
- data/lib/appmap/hook/method.rb +25 -5
- data/lib/appmap/version.rb +1 -1
- data/spec/fixtures/hook/singleton_method.rb +21 -12
- data/spec/hook_spec.rb +37 -3
- metadata +21 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 81605d2c95140e963991e04af89235ee903cce0e3a86b2477f0f80961704e2d6
|
4
|
+
data.tar.gz: 7e526d9ffbc559bb3968b8a973dcd757147676d17a5028f737192fd8956d1b43
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6a34ba2662d48a7e0083e16d6abe4693b57711f9ac0020da6631ca6c40172aeb8f3ef734edd504cec00f2f437742e45fc0cf631b425cb960c9d51239556bc461
|
7
|
+
data.tar.gz: 156f126aac6f63e819c175d6b4d481679cbced9184007871c12fb174f187198afd8e7788befc4ab0ff35b10293e1e163eee25a985b35bd93853c03aa8268d192
|
data/.gitignore
CHANGED
data/.rbenv-gemsets
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
appmap-ruby
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
# v0.34.2
|
2
|
+
* Add an extension that gets the name of the owner of a singleton method without calling
|
3
|
+
any methods that may have been redefined (e.g. `#to_s` or `.name`).
|
4
|
+
|
1
5
|
# v0.34.1
|
2
6
|
* Ensure that capturing events doesn't change the behavior of a hooked method that uses
|
3
7
|
`Time.now`. For example, if a test expects that `Time.now` will be called a certain
|
data/README.md
CHANGED
@@ -267,7 +267,13 @@ $ bundle config local.appmap $(pwd)
|
|
267
267
|
Run the tests via `rake`:
|
268
268
|
```
|
269
269
|
$ bundle exec rake test
|
270
|
-
```
|
270
|
+
```
|
271
|
+
|
272
|
+
The `test` target will build the native extension first, then run the tests. If you need
|
273
|
+
to build the extension separately, run
|
274
|
+
```
|
275
|
+
$ bundle exec rake compile
|
276
|
+
```
|
271
277
|
|
272
278
|
## Using fixture apps
|
273
279
|
|
@@ -286,7 +292,7 @@ resources such as a PostgreSQL database.
|
|
286
292
|
To build the fixture container images, first run:
|
287
293
|
|
288
294
|
```sh-session
|
289
|
-
$ bundle exec rake fixtures:all
|
295
|
+
$ bundle exec rake build:fixtures:all
|
290
296
|
```
|
291
297
|
|
292
298
|
This will build the `appmap.gem`, along with a Docker image for each fixture app.
|
data/Rakefile
CHANGED
@@ -6,6 +6,13 @@ require 'rdoc/task'
|
|
6
6
|
|
7
7
|
require 'open3'
|
8
8
|
|
9
|
+
require "rake/extensiontask"
|
10
|
+
|
11
|
+
desc 'build the native extension'
|
12
|
+
Rake::ExtensionTask.new("appmap") do |ext|
|
13
|
+
ext.lib_dir = "lib/appmap"
|
14
|
+
end
|
15
|
+
|
9
16
|
namespace 'gem' do
|
10
17
|
require 'bundler/gem_tasks'
|
11
18
|
end
|
@@ -104,7 +111,7 @@ end
|
|
104
111
|
namespace :spec do
|
105
112
|
RUBY_VERSIONS.each do |ruby_version|
|
106
113
|
desc ruby_version
|
107
|
-
task ruby_version, [:specs] => ["build:fixtures:#{ruby_version}:all"] do |_, task_args|
|
114
|
+
task ruby_version, [:specs] => ["compile", "build:fixtures:#{ruby_version}:all"] do |_, task_args|
|
108
115
|
run_specs(ruby_version, task_args)
|
109
116
|
end.tap do|t|
|
110
117
|
desc "Run all specs"
|
@@ -119,13 +126,13 @@ Rake::RDocTask.new do |rd|
|
|
119
126
|
rd.title = 'AppMap'
|
120
127
|
end
|
121
128
|
|
122
|
-
Rake::TestTask.new(:
|
129
|
+
Rake::TestTask.new(minitest: 'compile') do |t|
|
123
130
|
t.libs << 'test'
|
124
131
|
t.libs << 'lib'
|
125
132
|
t.test_files = FileList['test/*_test.rb']
|
126
133
|
end
|
127
134
|
|
128
|
-
task spec:
|
135
|
+
task spec: %i[spec:all]
|
129
136
|
|
130
137
|
task test: %i[spec:all minitest]
|
131
138
|
|
data/appmap.gemspec
CHANGED
@@ -18,6 +18,8 @@ Gem::Specification.new do |spec|
|
|
18
18
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
19
19
|
spec.files = `git ls-files --no-deleted`.split("
|
20
20
|
")
|
21
|
+
spec.extensions << "ext/appmap/extconf.rb"
|
22
|
+
|
21
23
|
spec.bindir = 'exe'
|
22
24
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
23
25
|
spec.require_paths = ['lib']
|
@@ -34,6 +36,7 @@ Gem::Specification.new do |spec|
|
|
34
36
|
spec.add_development_dependency 'rake', '>= 12.3.3'
|
35
37
|
spec.add_development_dependency 'rdoc'
|
36
38
|
spec.add_development_dependency 'rubocop'
|
39
|
+
spec.add_development_dependency "rake-compiler"
|
37
40
|
|
38
41
|
# Testing
|
39
42
|
spec.add_development_dependency 'climate_control'
|
data/ext/appmap/appmap.c
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
#include <ruby.h>
|
2
|
+
#include <ruby/intern.h>
|
3
|
+
|
4
|
+
// Seems like CLASS_OR_MODULE_P should really be in a header file in
|
5
|
+
// the ruby source -- it's in object.c and duplicated in eval.c. In
|
6
|
+
// the future, we'll fail if it does get moved to a header.
|
7
|
+
#define CLASS_OR_MODULE_P(obj) \
|
8
|
+
(!SPECIAL_CONST_P(obj) && \
|
9
|
+
(BUILTIN_TYPE(obj) == T_CLASS || BUILTIN_TYPE(obj) == T_MODULE))
|
10
|
+
|
11
|
+
static VALUE singleton_method_owner_name(VALUE klass, VALUE method)
|
12
|
+
{
|
13
|
+
VALUE owner = rb_funcall(method, rb_intern("owner"), 0);
|
14
|
+
VALUE attached = rb_ivar_get(owner, rb_intern("__attached__"));
|
15
|
+
if (!CLASS_OR_MODULE_P(attached)) {
|
16
|
+
attached = rb_funcall(attached, rb_intern("class"), 0);
|
17
|
+
}
|
18
|
+
return rb_mod_name(attached);
|
19
|
+
}
|
20
|
+
|
21
|
+
void Init_appmap() {
|
22
|
+
VALUE appmap = rb_define_module("AppMap");
|
23
|
+
VALUE hook = rb_define_class_under(appmap, "Hook", rb_cObject);
|
24
|
+
|
25
|
+
rb_define_singleton_method(hook, "singleton_method_owner_name", singleton_method_owner_name, 1);
|
26
|
+
}
|
data/lib/appmap.rb
CHANGED
data/lib/appmap/hook.rb
CHANGED
@@ -17,18 +17,7 @@ module AppMap
|
|
17
17
|
# the given method.
|
18
18
|
def qualify_method_name(method)
|
19
19
|
if method.owner.singleton_class?
|
20
|
-
|
21
|
-
# #<Class:Foo> or
|
22
|
-
# #<Class:#<Bar:0x0123ABC>>. Retrieve the name of
|
23
|
-
# the class from the string.
|
24
|
-
#
|
25
|
-
# (There really isn't a better way to do this. The
|
26
|
-
# singleton's reference to the class it was created
|
27
|
-
# from is stored in an instance variable named
|
28
|
-
# '__attached__'. It doesn't have the '@' prefix, so
|
29
|
-
# it's internal only, and not accessible from user
|
30
|
-
# code.)
|
31
|
-
class_name = /#<Class:((#<(?<cls>.*?):)|((?<cls>.*?)>))/.match(method.owner.to_s)['cls']
|
20
|
+
class_name = singleton_method_owner_name(method)
|
32
21
|
[ class_name, '.', method.name ]
|
33
22
|
else
|
34
23
|
[ method.owner.name, '#', method.name ]
|
@@ -58,7 +47,7 @@ module AppMap
|
|
58
47
|
method = hook_cls.public_instance_method(method_id)
|
59
48
|
hook_method = Hook::Method.new(hook_cls, method)
|
60
49
|
|
61
|
-
warn "AppMap: Examining #{
|
50
|
+
warn "AppMap: Examining #{hook_cls} #{method.name}" if LOG
|
62
51
|
|
63
52
|
disasm = RubyVM::InstructionSequence.disasm(method)
|
64
53
|
# Skip methods that have no instruction sequence, as they are obviously trivial.
|
@@ -69,7 +58,7 @@ module AppMap
|
|
69
58
|
next if /\AAppMap[:\.]/.match?(hook_method.method_display_name)
|
70
59
|
|
71
60
|
next unless \
|
72
|
-
config.always_hook?(
|
61
|
+
config.always_hook?(hook_cls, method.name) ||
|
73
62
|
config.included_by_location?(method)
|
74
63
|
|
75
64
|
hook_method.activate
|
data/lib/appmap/hook/method.rb
CHANGED
@@ -1,7 +1,12 @@
|
|
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
|
@@ -14,13 +19,23 @@ module AppMap
|
|
14
19
|
def initialize(hook_class, hook_method)
|
15
20
|
@hook_class = hook_class
|
16
21
|
@hook_method = hook_method
|
22
|
+
|
23
|
+
# Get the class for the method, if it's known.
|
17
24
|
@defined_class, method_symbol = Hook.qualify_method_name(@hook_method)
|
18
|
-
@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
|
19
26
|
end
|
20
27
|
|
21
28
|
def activate
|
22
|
-
|
29
|
+
if Hook::LOG
|
30
|
+
msg = if method_display_name
|
31
|
+
"#{method_display_name}"
|
32
|
+
else
|
33
|
+
"#{hook_method.name} (class resolution deferrred)"
|
34
|
+
end
|
35
|
+
warn "AppMap: Hooking " + msg
|
36
|
+
end
|
23
37
|
|
38
|
+
defined_class = @defined_class
|
24
39
|
hook_method = self.hook_method
|
25
40
|
before_hook = self.method(:before_hook)
|
26
41
|
after_hook = self.method(:after_hook)
|
@@ -29,12 +44,17 @@ module AppMap
|
|
29
44
|
hook_class.define_method hook_method.name do |*args, &block|
|
30
45
|
instance_method = hook_method.bind(self).to_proc
|
31
46
|
|
47
|
+
# We may not have gotten the class for the method during
|
48
|
+
# initialization (e.g. for a singleton method on an embedded
|
49
|
+
# struct), so make sure we have it now.
|
50
|
+
defined_class,_ = Hook.qualify_method_name(hook_method) unless defined_class
|
51
|
+
|
32
52
|
hook_disabled = Thread.current[HOOK_DISABLE_KEY]
|
33
53
|
enabled = true if !hook_disabled && AppMap.tracing.enabled?
|
34
54
|
return instance_method.call(*args, &block) unless enabled
|
35
55
|
|
36
56
|
call_event, start_time = with_disabled_hook.() do
|
37
|
-
before_hook.(self, args)
|
57
|
+
before_hook.(self, defined_class, args)
|
38
58
|
end
|
39
59
|
return_value = nil
|
40
60
|
exception = nil
|
@@ -53,7 +73,7 @@ module AppMap
|
|
53
73
|
|
54
74
|
protected
|
55
75
|
|
56
|
-
def before_hook(receiver, args)
|
76
|
+
def before_hook(receiver, defined_class, args)
|
57
77
|
require 'appmap/event'
|
58
78
|
call_event = AppMap::Event::MethodCall.build_from_invocation(defined_class, hook_method, receiver, args)
|
59
79
|
AppMap.tracing.record_event call_event, defined_class: defined_class, method: hook_method
|
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
@@ -342,7 +342,7 @@ describe 'AppMap class Hooking', docker: false do
|
|
342
342
|
:defined_class: SingletonMethod
|
343
343
|
:method_id: added_method
|
344
344
|
:path: spec/fixtures/hook/singleton_method.rb
|
345
|
-
:lineno:
|
345
|
+
:lineno: 21
|
346
346
|
:static: false
|
347
347
|
:parameters: []
|
348
348
|
:receiver:
|
@@ -350,10 +350,10 @@ describe 'AppMap class Hooking', docker: false do
|
|
350
350
|
:value: Singleton Method fixture
|
351
351
|
- :id: 2
|
352
352
|
:event: :call
|
353
|
-
:defined_class: AddMethod
|
353
|
+
:defined_class: SingletonMethod::AddMethod
|
354
354
|
:method_id: _added_method
|
355
355
|
:path: spec/fixtures/hook/singleton_method.rb
|
356
|
-
:lineno:
|
356
|
+
:lineno: 27
|
357
357
|
:static: false
|
358
358
|
:parameters: []
|
359
359
|
:receiver:
|
@@ -395,10 +395,44 @@ describe 'AppMap class Hooking', docker: false do
|
|
395
395
|
load 'spec/fixtures/hook/singleton_method.rb'
|
396
396
|
setup = -> { SingletonMethod.new_with_instance_method }
|
397
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
|
+
|
398
405
|
expect(s.say_instance_defined).to eq('defined for an instance')
|
399
406
|
end
|
400
407
|
end
|
401
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
|
+
|
402
436
|
it 'Reports exceptions' do
|
403
437
|
events_yaml = <<~YAML
|
404
438
|
---
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: appmap
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.34.
|
4
|
+
version: 0.34.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kevin Gilpin
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-09-
|
11
|
+
date: 2020-09-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -164,6 +164,20 @@ dependencies:
|
|
164
164
|
- - ">="
|
165
165
|
- !ruby/object:Gem::Version
|
166
166
|
version: '0'
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: rake-compiler
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - ">="
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
type: :development
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - ">="
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '0'
|
167
181
|
- !ruby/object:Gem::Dependency
|
168
182
|
name: climate_control
|
169
183
|
requirement: !ruby/object:Gem::Requirement
|
@@ -267,11 +281,13 @@ email:
|
|
267
281
|
- kgilpin@gmail.com
|
268
282
|
executables:
|
269
283
|
- appmap
|
270
|
-
extensions:
|
284
|
+
extensions:
|
285
|
+
- ext/appmap/extconf.rb
|
271
286
|
extra_rdoc_files: []
|
272
287
|
files:
|
273
288
|
- ".dockerignore"
|
274
289
|
- ".gitignore"
|
290
|
+
- ".rbenv-gemsets"
|
275
291
|
- ".rubocop.yml"
|
276
292
|
- ".ruby-version"
|
277
293
|
- ".travis.yml"
|
@@ -291,6 +307,8 @@ files:
|
|
291
307
|
- examples/mock_webapp/lib/mock_webapp/request.rb
|
292
308
|
- examples/mock_webapp/lib/mock_webapp/user.rb
|
293
309
|
- exe/appmap
|
310
|
+
- ext/appmap/appmap.c
|
311
|
+
- ext/appmap/extconf.rb
|
294
312
|
- lib/appmap.rb
|
295
313
|
- lib/appmap/algorithm/prune_class_map.rb
|
296
314
|
- lib/appmap/algorithm/stats.rb
|