gun_dog 0.0.2 → 0.0.3

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
  SHA1:
3
- metadata.gz: 8553b5cae7081c781faffb51279789d50c10b352
4
- data.tar.gz: f1dae43fce48c756e6bc004a7708879d3f1e7d94
3
+ metadata.gz: efabc10a53ec099193771fc99c5f2b81f058c4d6
4
+ data.tar.gz: fa3ddf6614c1ba10ab5d7edaf96fc86e118592cc
5
5
  SHA512:
6
- metadata.gz: bab212ec57b6f8d3b10b8fbb4adda795a932aa6b539ecd53b35b3977d628040696fe9544d49508f24be2c4827195b86100269694a8a05cb1ffd8f421c955cc70
7
- data.tar.gz: 53117f89da46453a9e567265ab8bb781c2c6431b859b713fdefa57c502b5f62355ef9e1456001fe5c88439505bfa0704bcd7f432cd7ca4610f0155e6d71107b8
6
+ metadata.gz: 56fbd2df15fef26b10fcd08d6e38698e00bb82016f98584ccc031dfd71bfb62a55eaf3c64f8718a2b4d71316bee315dc5bd7f8c29ee6c34e7dd30786dbae34e6
7
+ data.tar.gz: b822005d9050925dce5979fb35d89237daea139422ab22dd45c6803b9b4e2e1051b69325a69cf01d85ecccb118680c39e6183850917253cd2afeff78f7a656ae
data/gun_dog.gemspec CHANGED
@@ -22,6 +22,7 @@ Gem::Specification.new do |spec|
22
22
 
23
23
  spec.add_dependency "multi_json", "~> 1.12"
24
24
  spec.add_dependency "activesupport", ">= 4.2.9"
25
+ spec.add_dependency "method_source"
25
26
 
26
27
  spec.add_development_dependency "bundler", "~> 1.15"
27
28
  spec.add_development_dependency "rake", "~> 10.0"
@@ -2,7 +2,7 @@ module GunDog
2
2
  class CallRecord
3
3
  using ClassEncoding
4
4
 
5
- attr_accessor :args, :return_value, :method_name
5
+ attr_accessor :args, :return_value, :method_name, :extra_module
6
6
  attr_accessor :stack
7
7
 
8
8
  attr_writer :internal, :cyclical, :dynamic
@@ -11,8 +11,8 @@ module GunDog
11
11
  cr = new(
12
12
  Utilities.get_class(json['klass']),
13
13
  json['method_name'],
14
- class_method: json['class_method'],
15
- generated: json['generated']
14
+ origin: json['origin'],
15
+ extra_module: ( Utilities.get_class(json['extra_module']) if json['extra_module'] )
16
16
  )
17
17
 
18
18
  cr.instance_eval do
@@ -26,11 +26,11 @@ module GunDog
26
26
  cr
27
27
  end
28
28
 
29
- def initialize(klass, method_name, class_method: false, generated: false)
29
+ def initialize(klass, method_name, origin: false, extra_module: nil)
30
30
  @klass = klass
31
31
  @method_name = method_name
32
- @class_method = class_method
33
- @generated = generated
32
+ @origin = origin
33
+ @extra_module = extra_module
34
34
  end
35
35
 
36
36
  def method_location
@@ -50,11 +50,32 @@ module GunDog
50
50
  end
51
51
 
52
52
  def class_method?
53
- !!@class_method
53
+ @origin == :eigen
54
54
  end
55
55
 
56
- def generated?
57
- !!@generated
56
+ def origin
57
+ @origin
58
+ end
59
+
60
+ def special_origin?
61
+ [:meta, :included, :refinement].include?(@origin)
62
+ end
63
+
64
+ def origin_string
65
+ case @origin
66
+ when :meta
67
+ '[generated]'
68
+ when :included
69
+ "[#{unbound_method.owner.name.to_s}]"
70
+ when :extended
71
+ "[#{extra_module.name.to_s} (extended)]"
72
+ when :prepended
73
+ "[#{unbound_method.owner.name.to_s} (prepended)]"
74
+ when :refinement
75
+ "[using #{extra_module.split('@')[1][0..-2]}]"
76
+ else
77
+ nil
78
+ end
58
79
  end
59
80
 
60
81
  def unbound_method
@@ -66,7 +87,7 @@ module GunDog
66
87
  end
67
88
 
68
89
  def call_record_signature
69
- "#{generated? ? "[generated] " : nil } \
90
+ "#{origin_string} \
70
91
  def #{class_method? ? "self." : nil}#{method_name}(#{type_signatures(args)}) : #{return_value.class} \
71
92
  #{ internal? ? " (internal)" : nil } \
72
93
  #{ cyclical? ? " (cyclical)" : nil } \
@@ -77,8 +98,8 @@ module GunDog
77
98
  {
78
99
  "klass" => @klass.json_encoded,
79
100
  "method_name" => method_name.to_s,
80
- "class_method" => class_method?,
81
- "generated" => generated?,
101
+ "origin" => origin,
102
+ "extra_module" => @extra_module,
82
103
  "internal" => internal?,
83
104
  "cyclical" => cyclical?,
84
105
  "dynamic" => dynamic?,
@@ -95,6 +116,8 @@ module GunDog
95
116
  #TODO pass v here back through this method to show the type signatures for each method rather than
96
117
  # individual arguments
97
118
  args.each_pair.map { |k,v| "#{k} : #{v ? v.to_s : v.class}" }.join(', ')
119
+ elsif origin == :refinement
120
+ '?'
98
121
  else
99
122
  args.each_pair.map { |k,v| "#{k} : #{v.class}" }.join(', ')
100
123
  end
@@ -1,19 +1,25 @@
1
1
  require 'active_support/core_ext/module/anonymous'
2
+ require 'method_source'
2
3
 
3
4
  module GunDog
4
5
  class TraceMaker
5
- attr_reader :trace_report, :return_trace, :call_trace, :klass, :complete_call_list
6
+ using GunDog::Utilities::RefinementIntrospection
7
+
8
+ attr_reader :trace_report, :return_trace, :call_trace, :klass, :complete_call_list, :c_call_trace
6
9
 
7
10
  def initialize(klass, suppress: [], &exec_block)
8
11
  @klass = klass
9
12
  @trace_report = TraceReport.new(klass, suppression_set: build_suppression_set(suppress))
10
13
  @trace_report.stack << MethodOwnerStackFrame.new(GunDog, :trace)
11
14
  @ancestor_cache = {}
15
+ @included_cache = {}
12
16
  @exec_block = exec_block
17
+ @mrt_tracers = []
13
18
  end
14
19
 
15
20
  def exec
16
21
  set_trace
22
+ wrap_accessor_functions
17
23
 
18
24
  call_trace.enable do
19
25
  return_trace.enable do
@@ -24,6 +30,8 @@ module GunDog
24
30
  trace_report.finalize_report
25
31
  trace_report
26
32
  ensure
33
+ @mrt_tracers.each(&:disable)
34
+ unwrap_accessor_functions
27
35
  call_trace.disable
28
36
  return_trace.disable
29
37
  end
@@ -33,6 +41,36 @@ module GunDog
33
41
  set_call_trace
34
42
  end
35
43
 
44
+ def unwrap_accessor_functions
45
+ accessor_methods.map do |m|
46
+ klass.send(:remove_method, m)
47
+
48
+ if m.to_s[-1] == '='
49
+ klass.send :attr_writer, m[0..-2]
50
+ else
51
+ klass.send :attr_reader, m
52
+ end
53
+ end
54
+ end
55
+
56
+ def wrap_accessor_functions
57
+ accessor_methods.map do |m|
58
+ if m.to_s[-1] == '='
59
+ klass.module_eval <<~RUBY, __FILE__, __LINE__
60
+ def #{m.to_s}(arg1)
61
+ @#{m.to_s[0..-2]} = arg1
62
+ end
63
+ RUBY
64
+ else
65
+ klass.module_eval <<~RUBY, __FILE__, __LINE__
66
+ def #{m.to_s}
67
+ @#{m.to_s}
68
+ end
69
+ RUBY
70
+ end
71
+ end
72
+ end
73
+
36
74
  def set_return_trace
37
75
  @return_trace ||= TracePoint.new(:return) do |tp|
38
76
  trace_report.stack.pop
@@ -41,52 +79,75 @@ module GunDog
41
79
 
42
80
  def set_call_trace
43
81
  @call_trace ||= TracePoint.new(:call) do |tp|
44
- trace_report.stack << MethodOwnerStackFrame.new(tp.defined_class, tp.method_id)
82
+ begin
83
+ tp.disable
45
84
 
46
- tp.disable
85
+ trace_report.stack << MethodOwnerStackFrame.new(tp.defined_class, tp.method_id)
47
86
 
48
- binding_class = tp.binding.eval('self').class
49
- trace_type = trace_method(binding_class, tp.defined_class)
87
+ binding_class = tp.binding.receiver.class
88
+ trace_type = trace_method(binding_class, tp.defined_class, tp.self)
89
+ method_id = tp.binding.eval('__callee__') || tp.method_id
50
90
 
51
- unless trace_type
52
- tp.enable
53
- next
54
- end
91
+ unless trace_type
92
+ tp.enable
93
+ next
94
+ end
55
95
 
56
- method_id = tp.binding.eval('__callee__') || tp.method_id
96
+ call_record = nil
57
97
 
58
- called_method = if trace_type == :meta
59
- tp.binding.eval('self').method(method_id)
60
- else
61
- tp.self.method(tp.method_id)
62
- end
98
+ if trace_type == :refinement
99
+ call_record = CallRecord.new(
100
+ klass,
101
+ method_id,
102
+ origin: trace_type,
103
+ extra_module: tp.defined_class.to_s
104
+ )
105
+ else
106
+ called_method = if trace_type == :meta || trace_type == :included
107
+ tp.binding.eval('self').method(method_id)
108
+ else
109
+ tp.self.method(tp.method_id)
110
+ end
111
+
112
+ call_record = CallRecord.new(
113
+ klass,
114
+ called_method.name,
115
+ origin: trace_type,
116
+ extra_module: (tp.defined_class if trace_type == :extended)
117
+ )
118
+
119
+ call_record.args = called_method.parameters.each.with_object({}) do |p, memo|
120
+ memo[p.last] = tp.binding.local_variable_get(p.last)
121
+ end
122
+ end
63
123
 
64
- # next if trace_report.suppression_set.include?(called_method.unbind)
124
+ trace_report.call_records << call_record
65
125
 
66
- call_record = CallRecord.new(
67
- klass,
68
- called_method.name,
69
- class_method: trace_type == :eigen,
70
- generated: trace_type == :meta
71
- )
126
+ mrt = set_method_return_trace(call_record)
127
+ @mrt_tracers << mrt
128
+ mrt.enable
72
129
 
73
- call_record.args = called_method.parameters.each.with_object({}) do |p, memo|
74
- memo[p.last] = tp.binding.local_variable_get(p.last)
130
+ tp.enable
131
+ rescue => e
132
+ STDOUT.puts "#{tp.method_id} => #{trace_type} (#{tp.defined_class})"
133
+ puts "#{e.class} => #{e.message}"
134
+ puts "=============================================================================="
135
+ puts e.backtrace
136
+ puts "=============================================================================="
137
+ tp.disable
75
138
  end
76
-
77
- trace_report.call_records << call_record
78
-
79
- set_method_return_trace(call_record).enable
80
-
81
- tp.enable
82
139
  end
83
140
  end
84
141
 
85
- def trace_method(binding_class, defined_class)
142
+ def trace_method(binding_class, defined_class, obj)
86
143
  return :eigen if defined_class == klass.singleton_class
87
144
  return false if binding_class != klass
88
- return :meta if klass < defined_class && (defined_class.anonymous? || after_super?(defined_class))
145
+ return :refinement if defined_class.is_refinement?
146
+ return :meta if klass < defined_class && defined_class.anonymous?
147
+ return :prepended if klass < defined_class && prepended?(defined_class)
148
+ return :included if klass < defined_class && after_super?(defined_class)
89
149
  return :instance if defined_class == klass
150
+ return :extended if obj.singleton_class < defined_class && after_super?(defined_class)
90
151
  false
91
152
  end
92
153
 
@@ -101,16 +162,25 @@ module GunDog
101
162
  end
102
163
  end
103
164
 
165
+ def prepended?(defined_class)
166
+ if @included_cache.has_key?(defined_class)
167
+ @included_cache[defined_class]
168
+ else
169
+ ancestors = klass.ancestors
170
+ @included_cache[defined_class] = ancestors.index(klass) > ancestors.index(defined_class)
171
+ end
172
+ end
173
+
104
174
  def set_method_return_trace(call_record)
105
175
  # instantiate a new return tracepoint to watch for the return of this
106
176
  # method only
107
- #
108
177
  TracePoint.new(:return) do |mrt|
109
178
  method_id = mrt.binding.eval('__callee__') || mrt.method_id
110
179
  next if method_id != call_record.method_name
111
180
  mrt.disable
112
181
  call_record.return_value = mrt.return_value
113
182
 
183
+
114
184
  if trace_report.stack.internal_stack?
115
185
  call_record.internal = true
116
186
  call_record.stack = trace_report.stack.since_first_klass_entry
@@ -126,6 +196,15 @@ module GunDog
126
196
  end
127
197
  end
128
198
 
199
+ private
200
+
201
+ def accessor_methods
202
+ @accessor_methods ||= klass.instance_methods.map { |m| klass.instance_method(m) }
203
+ .select { |m| m.owner == klass }
204
+ .select { |m| m.source =~ /attr/ }
205
+ .map { |m| m.name }
206
+ end
207
+
129
208
  def build_suppression_set(suppression_list)
130
209
  suppression_list.map { |method_id|
131
210
  begin
@@ -1,5 +1,7 @@
1
1
  module GunDog
2
2
  class TraceStack < Array
3
+ using Utilities::RefinementIntrospection
4
+
3
5
  attr_reader :collaborating_classes, :klass
4
6
 
5
7
  def self.from_json(json)
@@ -22,7 +24,13 @@ module GunDog
22
24
  end
23
25
 
24
26
  def classes_in_stack
25
- self.group_by(&:klass).keys.to_set
27
+ self.group_by { |frame|
28
+ if refined_class = frame.klass.to_s[/#<refinement:(.*?)@/,1]
29
+ Kernel.const_get(refined_class)
30
+ else
31
+ frame.klass
32
+ end
33
+ }.keys.to_set
26
34
  end
27
35
 
28
36
  def initialize(klass)
@@ -51,6 +59,7 @@ module GunDog
51
59
  end
52
60
 
53
61
  def preceded_by_traced_klass?
62
+ # FIXME this method is terribly ineffcient
54
63
  traced_klasses = self.map(&:klass)
55
64
  traced_klasses.include?(klass) || traced_klasses.include?(klass.singleton_class)
56
65
  end
@@ -85,7 +94,9 @@ module GunDog
85
94
  end
86
95
 
87
96
  def frame_owned_by_traced_klass?(frame)
88
- frame.klass == klass || frame.klass.singleton_class == klass.singleton_class
97
+ return true if frame.klass == klass
98
+ return true if frame.klass.singleton_class == klass.singleton_class
99
+ return true if frame.klass.refined_class == klass
89
100
  end
90
101
 
91
102
  def as_json
@@ -1,5 +1,22 @@
1
1
  module GunDog
2
2
  module Utilities
3
+ module RefinementIntrospection
4
+ refine Module do
5
+ def is_refinement?
6
+ to_s.present? && to_s =~ /refinement/
7
+ end
8
+
9
+ def refined_class
10
+ target = to_s[/#<refinement:(.*?)@/,1]
11
+ return nil unless target
12
+ const_get(target)
13
+ rescue NoMethodError => e
14
+ # hack - this is raised on AR objects if you call to_s before the
15
+ # the inherited hooks are finished running
16
+ end
17
+ end
18
+ end
19
+
3
20
  class UnencodableClass; end
4
21
 
5
22
  def self.get_class(str)
@@ -13,7 +30,6 @@ module GunDog
13
30
  begin
14
31
  eval(str)
15
32
  rescue => e
16
- require 'pry'; binding.pry
17
33
  e.message
18
34
  end
19
35
  end
@@ -1,3 +1,3 @@
1
1
  module GunDog
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.3"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gun_dog
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stephen Prater
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-10-19 00:00:00.000000000 Z
11
+ date: 2017-11-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: multi_json
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: 4.2.9
41
+ - !ruby/object:Gem::Dependency
42
+ name: method_source
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: bundler
43
57
  requirement: !ruby/object:Gem::Requirement