appmap 0.34.1 → 0.34.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 39576d59d2648a009f888fa9c5720d8fdb91b04fcb4d08949a05a0a5c453d0c8
4
- data.tar.gz: 8b1d5442421d9dd37c3db4fa0329321e6b6e6ebc87e94bb67be3da9b3f1467b0
3
+ metadata.gz: 81605d2c95140e963991e04af89235ee903cce0e3a86b2477f0f80961704e2d6
4
+ data.tar.gz: 7e526d9ffbc559bb3968b8a973dcd757147676d17a5028f737192fd8956d1b43
5
5
  SHA512:
6
- metadata.gz: 40bc1a630b65a83f7516085ec4cd25a72f83e574a9c2f150f8e302c9756c376a1fc820d490393d9625aeb3f5b781ca44d34431efe2722a7a0031b49334ccbca7
7
- data.tar.gz: 9d32eb02deebdfb16e99ee0f157f5c9de6cb77cafa46233867e0173577609d4390b98a675f67c11868d57370066666bf9f9da39b2ee2ab25ba16cbaf4ebcd8ca
6
+ metadata.gz: 6a34ba2662d48a7e0083e16d6abe4693b57711f9ac0020da6631ca6c40172aeb8f3ef734edd504cec00f2f437742e45fc0cf631b425cb960c9d51239556bc461
7
+ data.tar.gz: 156f126aac6f63e819c175d6b4d481679cbced9184007871c12fb174f187198afd8e7788befc4ab0ff35b10293e1e163eee25a985b35bd93853c03aa8268d192
data/.gitignore CHANGED
@@ -14,4 +14,4 @@ Gemfile.lock
14
14
  appmap.json
15
15
  .vscode
16
16
  .byebug_history
17
-
17
+ /lib/appmap/appmap.bundle
@@ -0,0 +1 @@
1
+ appmap-ruby
@@ -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(:minitest) do |t|
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: "spec:all"
135
+ task spec: %i[spec:all]
129
136
 
130
137
  task test: %i[spec:all minitest]
131
138
 
@@ -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'
@@ -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
+ }
@@ -0,0 +1,6 @@
1
+ require "mkmf"
2
+
3
+ $CFLAGS='-Werror'
4
+ extension_name = "appmap"
5
+ dir_config(extension_name)
6
+ create_makefile(File.join(extension_name, extension_name))
@@ -16,6 +16,9 @@ require 'appmap/metadata'
16
16
  require 'appmap/util'
17
17
  require 'appmap/open'
18
18
 
19
+ # load extension
20
+ require 'appmap/appmap'
21
+
19
22
  module AppMap
20
23
  class << self
21
24
  @configuration = nil
@@ -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
- # Singleton class names can take two forms:
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 #{hook_method.method_display_name}" if LOG
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?(hook_method.defined_class, method.name) ||
61
+ config.always_hook?(hook_cls, method.name) ||
73
62
  config.included_by_location?(method)
74
63
 
75
64
  hook_method.activate
@@ -1,7 +1,12 @@
1
1
  module AppMap
2
2
  class Hook
3
3
  class Method
4
- attr_reader :hook_class, :hook_method, :defined_class, :method_display_name
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
- warn "AppMap: Hooking #{method_display_name}" if Hook::LOG
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
@@ -3,7 +3,7 @@
3
3
  module AppMap
4
4
  URL = 'https://github.com/applandinc/appmap-ruby'
5
5
 
6
- VERSION = '0.34.1'
6
+ VERSION = '0.34.2'
7
7
 
8
8
  APPMAP_FORMAT_VERSION = '1.2'
9
9
  end
@@ -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
- module AddMethod
42
- def self.included(base)
43
- base.module_eval do
44
- define_method "added_method" do
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 _added_method
51
- 'defined by including a module'
58
+ def to_s
59
+ 'Singleton Method fixture'
52
60
  end
53
61
  end
54
62
 
63
+
@@ -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: 44
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: 50
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.1
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-02 00:00:00.000000000 Z
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