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 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