private_please 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.
- data/.ruby-version +1 -0
- data/CHANGELOG +6 -0
- data/README.md +51 -7
- data/TODO +11 -13
- data/lib/private_please/candidate.rb +41 -0
- data/lib/private_please/report/reporter.rb +52 -0
- data/lib/private_please/report/templates/simple.txt.erb +61 -0
- data/lib/private_please/ruby_backports.rb +22 -0
- data/lib/private_please/storage/calls_store.rb +41 -0
- data/lib/private_please/storage/candidates_store.rb +48 -0
- data/lib/private_please/storage/methods_names.rb +9 -0
- data/lib/private_please/storage/methods_names_bucket.rb +69 -0
- data/lib/private_please/tracking/extension.rb +19 -0
- data/lib/private_please/tracking/instrumentor.rb +71 -0
- data/lib/private_please/tracking/instruments_all_below.rb +41 -0
- data/lib/private_please/tracking/line_change_tracker.rb +36 -0
- data/lib/private_please/version.rb +1 -1
- data/lib/private_please.rb +33 -50
- data/private_please.gemspec +1 -0
- data/sample.rb +68 -0
- data/spec/01_marking_candidate_methods_to_observe_spec.rb +153 -0
- data/spec/02_logging_calls_on_candidate_methods_spec.rb +39 -0
- data/spec/04_instrumented_program_activity_observation_result_spec.rb +89 -0
- data/spec/fixtures/sample_class_for_report.rb +54 -0
- data/spec/fixtures/sample_class_with_all_calls_combinations.rb +69 -0
- data/spec/spec_helper.rb +52 -1
- data/spec/units/calls_store_spec.rb +15 -0
- data/spec/units/candidates_store_spec.rb +55 -0
- metadata +58 -28
- data/lib/private_please/candidates.rb +0 -37
- data/lib/private_please/configuration.rb +0 -21
- data/lib/private_please/line_change_tracker.rb +0 -19
- data/lib/private_please/recorder.rb +0 -34
- data/lib/private_please/report/template.txt.erb +0 -15
- data/lib/private_please/report.rb +0 -47
- data/spec/01_marking_methods_spec.rb +0 -30
- data/spec/02_calling_methods_spec.rb +0 -58
- data/spec/03_configuration_spec.rb +0 -52
- data/spec/04_at_exit_report_printing_spec.rb +0 -47
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.8.7
|
data/CHANGELOG
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
v0.0.3
|
2
|
+
- private_please without parameters observes all the methods defined afterwards
|
3
|
+
- PP is active once you require the gem
|
4
|
+
- modules' instance methods are observable too
|
5
|
+
- big code cleanup (development)
|
6
|
+
|
1
7
|
v0.0.2
|
2
8
|
- only display the report if PrivatePlease.active?
|
3
9
|
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# PrivatePlease
|
2
2
|
|
3
|
-
|
3
|
+
limitation : Ruby 1.8.7
|
4
4
|
|
5
5
|
## Installation
|
6
6
|
|
@@ -8,17 +8,61 @@ Add this line to your application's Gemfile:
|
|
8
8
|
|
9
9
|
gem 'private_please'
|
10
10
|
|
11
|
-
|
11
|
+
## Usage
|
12
12
|
|
13
|
-
|
13
|
+
```ruby
|
14
14
|
|
15
|
-
|
15
|
+
require 'private_please' # step 1
|
16
16
|
|
17
|
-
|
17
|
+
class CouldBeMorePrivate
|
18
|
+
def to_s # not observed -> won't appear in the report.
|
19
|
+
# ...
|
20
|
+
end
|
18
21
|
|
19
|
-
|
22
|
+
private_please # step 2 : start observing
|
23
|
+
|
24
|
+
def do_the_thing # is called by class' users => must stay public (case #1)
|
25
|
+
part_1
|
26
|
+
part_2
|
27
|
+
end
|
28
|
+
def part_1 ; end # only called by #do_the_thing => should be private (case #2)
|
29
|
+
def part_2 ; end # only called by #do_the_thing => should be private (case #2)
|
30
|
+
|
31
|
+
def part_3 ; end # is never used -> will be detected. (case #3)
|
32
|
+
end
|
33
|
+
|
34
|
+
c = CouldBeMorePrivate.new
|
35
|
+
c.do_the_thing # step 3 : execute the code, so PP can observe and deduce.
|
36
|
+
```
|
37
|
+
A report is automatically printed in the console when the program exits.
|
38
|
+
For the code above, the report would be :
|
39
|
+
|
40
|
+
====================================================================================
|
41
|
+
= PrivatePlease report : =
|
42
|
+
====================================================================================
|
43
|
+
|
44
|
+
**********************************************************
|
45
|
+
CouldBeMorePrivate
|
46
|
+
**********************************************************
|
47
|
+
|
48
|
+
* Good candidates : can be made private :
|
49
|
+
------------------------------------------
|
50
|
+
|
51
|
+
#part_1
|
52
|
+
#part_2
|
53
|
+
|
54
|
+
* Bad candidates : must stay public/protected
|
55
|
+
------------------------------------------
|
56
|
+
|
57
|
+
#do_the_thing
|
58
|
+
|
59
|
+
* Methods that were never called
|
60
|
+
------------------------------------------
|
61
|
+
|
62
|
+
#part_3
|
63
|
+
|
64
|
+
====================================================================================
|
20
65
|
|
21
|
-
TODO: Write usage instructions here
|
22
66
|
|
23
67
|
## Contributing
|
24
68
|
|
data/TODO
CHANGED
@@ -1,22 +1,20 @@
|
|
1
1
|
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
2
|
+
install only in classes (? and modules)
|
3
|
+
def self.install
|
4
|
+
Object.send :include, PrivatePlease::Tracking::Extension
|
5
|
+
^^^^^^
|
6
|
+
TOO WIDE
|
7
|
+
end
|
6
8
|
|
7
|
-
private_please
|
8
|
-
def foo ..
|
9
|
-
def bar ..
|
10
|
-
public
|
11
9
|
|
12
|
-
|
13
|
-
|
14
|
-
def foo ..
|
15
|
-
def bar ..
|
16
|
-
private_please :foo, :bar
|
10
|
+
DSL
|
11
|
+
- observe class methods too
|
17
12
|
|
18
13
|
Report :
|
14
|
+
- option : show file and line number of good candidate definition
|
15
|
+
- option : show code snippet around the good candidate
|
19
16
|
- display list of methods that were never called
|
17
|
+
- display candidates that are already private
|
20
18
|
|
21
19
|
Doc
|
22
20
|
- write README
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# Holds the details of 1 method that was marked via `private_please`.
|
2
|
+
|
3
|
+
module PrivatePlease
|
4
|
+
class Candidate
|
5
|
+
|
6
|
+
def initialize(klass, method_name, is_instance_method)
|
7
|
+
@klass, @method_name, @is_instance_method = klass, method_name, is_instance_method
|
8
|
+
@klass_name = klass.to_s
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.for_instance_method(klass, method_name)
|
12
|
+
new(klass, method_name, true)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.for_class_method(klass, method_name)
|
16
|
+
new(klass, method_name, false)
|
17
|
+
end
|
18
|
+
|
19
|
+
#----------------------------------------------------------------------------
|
20
|
+
# QUERIES:
|
21
|
+
#----------------------------------------------------------------------------
|
22
|
+
|
23
|
+
attr_reader :klass,
|
24
|
+
:klass_name,
|
25
|
+
:method_name,
|
26
|
+
:is_instance_method
|
27
|
+
|
28
|
+
alias_method :instance_method?, :is_instance_method
|
29
|
+
|
30
|
+
def already_instrumented?
|
31
|
+
candidates_store.stored?(self)
|
32
|
+
end
|
33
|
+
|
34
|
+
#----------------------------------------------------------------------------
|
35
|
+
private
|
36
|
+
|
37
|
+
def candidates_store
|
38
|
+
PrivatePlease.candidates_store
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'erb'
|
2
|
+
module PrivatePlease ; module Report
|
3
|
+
class Reporter
|
4
|
+
|
5
|
+
TEMPLATE_PATH = File.expand_path(File.dirname(__FILE__) + '/templates/simple.txt.erb')
|
6
|
+
|
7
|
+
attr_reader :candidates_store, :calls_store,
|
8
|
+
:good_candidates, :bad_candidates,
|
9
|
+
:good_candidates_c, :bad_candidates_c,
|
10
|
+
:never_called_candidates, :never_called_candidates_c,
|
11
|
+
:building_time
|
12
|
+
|
13
|
+
|
14
|
+
def initialize(candidates_store, calls_store)
|
15
|
+
@candidates_store = candidates_store
|
16
|
+
@calls_store = calls_store
|
17
|
+
|
18
|
+
prepare_report_data
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_s
|
22
|
+
erb = ERB.new(File.read(TEMPLATE_PATH), 0, "%<>")
|
23
|
+
erb.result(binding)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def prepare_report_data
|
29
|
+
start_time = Time.now
|
30
|
+
@bad_candidates = calls_store.external_calls .clone
|
31
|
+
@bad_candidates_c = calls_store.class_external_calls.clone
|
32
|
+
# TODO : optimize
|
33
|
+
@good_candidates = calls_store.internal_calls .clone.remove(@bad_candidates)
|
34
|
+
@good_candidates_c= calls_store.class_internal_calls.clone.remove(@bad_candidates_c)
|
35
|
+
|
36
|
+
@never_called_candidates = candidates_store.instance_methods.clone.
|
37
|
+
remove(@good_candidates).
|
38
|
+
remove(@bad_candidates )
|
39
|
+
|
40
|
+
@never_called_candidates_c = candidates_store.class_methods.clone.
|
41
|
+
remove(@good_candidates_c).
|
42
|
+
remove(@bad_candidates_c )
|
43
|
+
@building_time = Time.now - start_time
|
44
|
+
|
45
|
+
@candidates_classes_names = (candidates_store.instance_methods.classes_names +
|
46
|
+
candidates_store.class_methods .classes_names ).uniq.sort
|
47
|
+
@good_candidates_classes_names = (@good_candidates_c.classes_names + @good_candidates.classes_names).uniq.sort
|
48
|
+
@bad_candidates_classes_names = (@bad_candidates_c .classes_names + @bad_candidates .classes_names).uniq.sort
|
49
|
+
@never_called_candidates_classes_names = (@never_called_candidates_c .classes_names + @never_called_candidates.classes_names).uniq.sort
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
====================================================================================
|
2
|
+
= PrivatePlease report : =
|
3
|
+
====================================================================================
|
4
|
+
% @candidates_classes_names .sort.each do |class_name|
|
5
|
+
%
|
6
|
+
% good_lines =
|
7
|
+
% [good_candidates_c.get_methods_names(class_name).collect{|n|".#{n}"},
|
8
|
+
% good_candidates .get_methods_names(class_name).collect{|n|"##{n}"}
|
9
|
+
% ].reject(&:empty?)
|
10
|
+
% good_lines.insert(1, '') if good_lines.length == 2
|
11
|
+
% good_lines.flatten!
|
12
|
+
%
|
13
|
+
% bad_lines =
|
14
|
+
% [bad_candidates_c.get_methods_names(class_name).collect{|n|".#{n}"},
|
15
|
+
% bad_candidates .get_methods_names(class_name).collect{|n|"##{n}"}
|
16
|
+
% ].reject(&:empty?)
|
17
|
+
% bad_lines.insert(1, '') if bad_lines.length == 2
|
18
|
+
% bad_lines.flatten!
|
19
|
+
|
20
|
+
% never_called_lines =
|
21
|
+
% [never_called_candidates_c.get_methods_names(class_name).collect{|n|".#{n}"},
|
22
|
+
% never_called_candidates .get_methods_names(class_name).collect{|n|"##{n}"}
|
23
|
+
% ]
|
24
|
+
% never_called_lines.insert(1, '') if never_called_lines.length == 2
|
25
|
+
|
26
|
+
%
|
27
|
+
**********************************************************
|
28
|
+
<%= class_name
|
29
|
+
%>
|
30
|
+
**********************************************************
|
31
|
+
%
|
32
|
+
% unless good_lines.empty?
|
33
|
+
|
34
|
+
* Good candidates : can be made private :
|
35
|
+
------------------------------------------
|
36
|
+
|
37
|
+
% good_lines.each do |line|
|
38
|
+
<%= line %>
|
39
|
+
% end
|
40
|
+
% end
|
41
|
+
% unless bad_lines.empty?
|
42
|
+
|
43
|
+
* Bad candidates : must stay public/protected
|
44
|
+
------------------------------------------
|
45
|
+
|
46
|
+
% bad_lines.each do |line|
|
47
|
+
<%= line %>
|
48
|
+
% end
|
49
|
+
% end
|
50
|
+
%
|
51
|
+
% unless never_called_lines.empty?
|
52
|
+
|
53
|
+
* Methods that were never called
|
54
|
+
------------------------------------------
|
55
|
+
% never_called_lines.each do |line|
|
56
|
+
<%= line %>
|
57
|
+
% end
|
58
|
+
% end
|
59
|
+
|
60
|
+
% end
|
61
|
+
====================================================================================
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# Unify the Ruby APIs across versions
|
2
|
+
#
|
3
|
+
|
4
|
+
# src : https://github.com/marcandre/backports/blob/master/lib/backports/1.9.1/kernel/define_singleton_method.rb
|
5
|
+
unless Kernel.method_defined? :define_singleton_method
|
6
|
+
module Kernel
|
7
|
+
def define_singleton_method(*args, &block)
|
8
|
+
class << self
|
9
|
+
self
|
10
|
+
end.send(:define_method, *args, &block)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# src: https://github.com/marcandre/backports/blob/master/lib/backports/1.9.2/kernel/singleton_class.rb
|
16
|
+
unless Kernel.method_defined? :singleton_class
|
17
|
+
module Kernel
|
18
|
+
def singleton_class
|
19
|
+
class << self; self; end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# As the main program runs, the various method calls are logged here
|
2
|
+
# (only for the methods marked with `private_please`)
|
3
|
+
|
4
|
+
module PrivatePlease
|
5
|
+
module Storage
|
6
|
+
|
7
|
+
class CallsStore
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@internal_calls = MethodsNamesBucket.new
|
11
|
+
@external_calls = MethodsNamesBucket.new
|
12
|
+
@class_internal_calls = MethodsNamesBucket.new
|
13
|
+
@class_external_calls = MethodsNamesBucket.new
|
14
|
+
end
|
15
|
+
|
16
|
+
#--------------------------------------------------------------------------
|
17
|
+
# QUERIES:
|
18
|
+
#--------------------------------------------------------------------------
|
19
|
+
|
20
|
+
attr_reader :internal_calls,
|
21
|
+
:external_calls,
|
22
|
+
:class_internal_calls,
|
23
|
+
:class_external_calls
|
24
|
+
|
25
|
+
#--------------------------------------------------------------------------
|
26
|
+
# COMMANDS:
|
27
|
+
#--------------------------------------------------------------------------
|
28
|
+
|
29
|
+
def store_outside_call(candidate)
|
30
|
+
bucket = candidate.instance_method? ? external_calls : class_external_calls
|
31
|
+
bucket.add_method_name(candidate.klass_name, candidate.method_name)
|
32
|
+
end
|
33
|
+
|
34
|
+
def store_inside_call(candidate)
|
35
|
+
bucket = candidate.instance_method? ? internal_calls : class_internal_calls
|
36
|
+
bucket.add_method_name(candidate.klass_name, candidate.method_name)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# Holds in 2 "buckets" the details (class name + methods names) of the
|
2
|
+
# methods that are candidate for privatization.
|
3
|
+
# Those methods were marked via `private_please` in the code.
|
4
|
+
# Instance methods and class methods are kept separate.
|
5
|
+
|
6
|
+
module PrivatePlease
|
7
|
+
module Storage
|
8
|
+
|
9
|
+
class CandidatesStore
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@instance_methods = MethodsNamesBucket.new
|
13
|
+
@class_methods = MethodsNamesBucket.new
|
14
|
+
end
|
15
|
+
|
16
|
+
#--------------------------------------------------------------------------
|
17
|
+
# QUERIES:
|
18
|
+
#--------------------------------------------------------------------------
|
19
|
+
|
20
|
+
attr_reader :instance_methods,
|
21
|
+
:class_methods
|
22
|
+
|
23
|
+
def empty?
|
24
|
+
instance_methods.empty? && class_methods.empty?
|
25
|
+
end
|
26
|
+
|
27
|
+
def stored?(candidate)
|
28
|
+
bucket_for(candidate).get_methods_names(candidate.klass_name).include?(candidate.method_name)
|
29
|
+
end
|
30
|
+
|
31
|
+
#--------------------------------------------------------------------------
|
32
|
+
# COMMANDS:
|
33
|
+
#--------------------------------------------------------------------------
|
34
|
+
|
35
|
+
def store(candidate)
|
36
|
+
bucket_for(candidate).add_method_name(candidate.klass_name, candidate.method_name)
|
37
|
+
end
|
38
|
+
|
39
|
+
#--------------------------------------------------------------------------
|
40
|
+
private
|
41
|
+
|
42
|
+
def bucket_for(candidate)
|
43
|
+
candidate.instance_method? ? @instance_methods : @class_methods
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# Associates and indexes classes (name) and some of their methods (names).
|
2
|
+
# Ex:
|
3
|
+
# +-------------+---------------------------------------+
|
4
|
+
# | class name => 1+ methods names |
|
5
|
+
# +-------------+---------------------------------------+
|
6
|
+
# | 'Foo' | MethodsNames.new('foo to_baz to_bar') |
|
7
|
+
# | 'Bar' | MethodsNames.new('qux') |
|
8
|
+
# +-------------+---------------------------------------+
|
9
|
+
|
10
|
+
module PrivatePlease
|
11
|
+
module Storage
|
12
|
+
|
13
|
+
class MethodsNamesBucket < Hash
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
super{|hash, class_name|
|
17
|
+
#hash[class_name] = PrivatePlease::Storage::MethodsNames.new
|
18
|
+
hash.set_methods_names(class_name, MethodsNames.new)
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
def clone
|
23
|
+
(self.class).new.tap do |klone|
|
24
|
+
classes_names.each do |class_name|
|
25
|
+
klone.set_methods_names(class_name, (self).get_methods_names(class_name))
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
#--------------------------------------------------------------------------
|
31
|
+
# QUERIES:
|
32
|
+
#--------------------------------------------------------------------------
|
33
|
+
|
34
|
+
alias_method :classes_names, :keys
|
35
|
+
alias_method :get_methods_names, :[]
|
36
|
+
# undef :[], :keys # should be undef-ed, but that complexifies the test #TODO : undef :[], :keys
|
37
|
+
# ( => must define ==(other) and cast type of 'other')
|
38
|
+
|
39
|
+
#--------------------------------------------------------------------------
|
40
|
+
# COMMANDS:
|
41
|
+
#--------------------------------------------------------------------------
|
42
|
+
|
43
|
+
alias_method :set_methods_names, :[]=
|
44
|
+
undef :[]=
|
45
|
+
|
46
|
+
def add_method_name(class_name, method_name)
|
47
|
+
self.get_methods_names(class_name).add(method_name)
|
48
|
+
end
|
49
|
+
|
50
|
+
def remove(other)
|
51
|
+
other.classes_names.each do |cn|
|
52
|
+
next if (methods_to_remove = other.get_methods_names(cn)).empty?
|
53
|
+
next if (methods_before = self .get_methods_names(cn)).empty?
|
54
|
+
difference = methods_before - methods_to_remove
|
55
|
+
self.set_methods_names(cn, difference)
|
56
|
+
end
|
57
|
+
prune!
|
58
|
+
self
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def prune!
|
64
|
+
self.reject!{|_, v|v.empty?}
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module PrivatePlease ; module Tracking
|
2
|
+
|
3
|
+
module Extension
|
4
|
+
|
5
|
+
def private_please(*methods_to_observe)
|
6
|
+
parameterless_call = methods_to_observe.empty?
|
7
|
+
klass = self
|
8
|
+
|
9
|
+
if parameterless_call
|
10
|
+
klass.send :include, PrivatePlease::Tracking::InstrumentsAllBelow
|
11
|
+
|
12
|
+
else
|
13
|
+
Instrumentor.instrument_instance_methods_for_pp_observation(klass, methods_to_observe)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
end end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module PrivatePlease ; module Tracking
|
2
|
+
|
3
|
+
module Instrumentor
|
4
|
+
|
5
|
+
def self.instrument_instance_methods_for_pp_observation(klass, methods_to_observe)
|
6
|
+
class_instance_methods = klass.instance_methods.collect(&:to_sym)
|
7
|
+
methods_to_observe = methods_to_observe.collect(&:to_sym)
|
8
|
+
# reject invalid methods names
|
9
|
+
methods_to_observe.reject! do |m|
|
10
|
+
already_defined_instance_method = class_instance_methods.include?(m)
|
11
|
+
invalid = !already_defined_instance_method
|
12
|
+
end
|
13
|
+
|
14
|
+
methods_to_observe.each do |method_name|
|
15
|
+
candidate = Candidate.for_instance_method(klass, method_name)
|
16
|
+
instrument_candidate_for_pp_observation(candidate) # end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
def self.instrument_candidate_for_pp_observation(candidate)
|
22
|
+
return if candidate.already_instrumented?
|
23
|
+
PrivatePlease.remember_candidate(candidate)
|
24
|
+
|
25
|
+
klass, method_name = candidate.klass, candidate.method_name
|
26
|
+
candidate.instance_method? ?
|
27
|
+
instrument_instance_method_with_pp_observation(klass, method_name) :
|
28
|
+
instrument_class_method_with_pp_observation( klass, method_name)
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
|
33
|
+
def self.instrument_class_method_with_pp_observation(klass, method_name)
|
34
|
+
orig_method = klass.singleton_class.instance_method(method_name)
|
35
|
+
klass.class_eval <<RUBY
|
36
|
+
define_singleton_method(method_name) do |*args, &blk| # def self.observed_method_i(..)
|
37
|
+
set_trace_func(nil) #don't track activity while here #
|
38
|
+
#
|
39
|
+
zelf_class=self #
|
40
|
+
candidate = PrivatePlease::Candidate.for_class_method(zelf_class, method_name)
|
41
|
+
PrivatePlease.after_method_call(candidate, LineChangeTracker.outside_class_method_call_detected?(zelf_class))
|
42
|
+
#
|
43
|
+
set_trace_func(LineChangeTracker::MY_TRACE_FUN) #
|
44
|
+
# make the call : #
|
45
|
+
orig_method.bind(self).call(*args, &blk) # <call original method>
|
46
|
+
end # end
|
47
|
+
RUBY
|
48
|
+
end
|
49
|
+
private_class_method :instrument_class_method_with_pp_observation
|
50
|
+
|
51
|
+
|
52
|
+
def self.instrument_instance_method_with_pp_observation(klass, method_name)
|
53
|
+
orig_method = klass.instance_method(method_name)
|
54
|
+
klass.class_eval <<RUBY
|
55
|
+
define_method(method_name) do |*args, &blk| # def observed_method_i(..)
|
56
|
+
set_trace_func(nil) #don't track activity while here #
|
57
|
+
#
|
58
|
+
candidate = PrivatePlease::Candidate.for_instance_method(self.class, method_name)
|
59
|
+
PrivatePlease.after_method_call(candidate, LineChangeTracker.outside_instance_method_call_detected?(self)) #
|
60
|
+
#
|
61
|
+
set_trace_func(LineChangeTracker::MY_TRACE_FUN) #
|
62
|
+
# make the call : #
|
63
|
+
orig_method.bind(self).call(*args, &blk) # <call original method>
|
64
|
+
end # end
|
65
|
+
RUBY
|
66
|
+
end
|
67
|
+
private_class_method :instrument_instance_method_with_pp_observation
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
end end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# Usage :
|
2
|
+
# class MarkingTest::Automatic2
|
3
|
+
# def foo ; end <---- but not this one.
|
4
|
+
# include PrivatePlease::Tracking::InstrumentsAllBelow <-- add this line
|
5
|
+
#
|
6
|
+
# def baz ; end <---- to observe this method
|
7
|
+
# protected
|
8
|
+
# def self.qux ; end <---- and this one too
|
9
|
+
# end
|
10
|
+
|
11
|
+
module PrivatePlease ; module Tracking
|
12
|
+
|
13
|
+
module InstrumentsAllBelow
|
14
|
+
include PrivatePlease::Tracking::Extension
|
15
|
+
|
16
|
+
def self.included(base)
|
17
|
+
|
18
|
+
def base.singleton_method_added(method_name)
|
19
|
+
return if [:method_added, :singleton_method_added].include?(method_name)
|
20
|
+
return if [:included].include?(method_name) && !self.is_a?(Class)
|
21
|
+
|
22
|
+
is_private_class_method = singleton_class.private_method_defined?(method_name)
|
23
|
+
return if is_private_class_method
|
24
|
+
|
25
|
+
candidate = Candidate.for_class_method(klass = self, method_name)
|
26
|
+
Tracking::Instrumentor.instrument_candidate_for_pp_observation(candidate)
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
def base.method_added(method_name)
|
31
|
+
is_private_instance_method = self.private_method_defined?(method_name)
|
32
|
+
return if is_private_instance_method
|
33
|
+
|
34
|
+
candidate = Candidate.for_instance_method(klass = self, method_name)
|
35
|
+
Tracking::Instrumentor.instrument_candidate_for_pp_observation(candidate)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
end end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module PrivatePlease ; module Tracking
|
2
|
+
|
3
|
+
class LineChangeTracker
|
4
|
+
class << self
|
5
|
+
attr_accessor :prev_prev_self, :prev_self, :curr_self
|
6
|
+
@@prev_self = @@curr_self = nil
|
7
|
+
end
|
8
|
+
|
9
|
+
MY_TRACE_FUN = lambda do |event, file, line, id, binding, klass|
|
10
|
+
return unless 'line'==event
|
11
|
+
LineChangeTracker.prev_prev_self = LineChangeTracker.prev_self
|
12
|
+
LineChangeTracker.prev_self = LineChangeTracker.curr_self
|
13
|
+
LineChangeTracker.curr_self = (eval 'self', binding)
|
14
|
+
#puts "my : #{event} in #{file}/#{line} id:#{id} klass:#{klass} - self = #{(eval'self', binding).inspect}"
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.outside_instance_method_call_detected?(zelf)
|
18
|
+
caller_class != zelf.class
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.outside_class_method_call_detected?(zelf_class)
|
22
|
+
caller_class != zelf_class
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def self.caller_class
|
28
|
+
call_initiator = LineChangeTracker.prev_self
|
29
|
+
(caller_is_class_method = call_initiator.is_a?(Class)) ?
|
30
|
+
call_initiator :
|
31
|
+
call_initiator.class
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end end
|
35
|
+
|
36
|
+
set_trace_func(PrivatePlease::Tracking::LineChangeTracker::MY_TRACE_FUN) #
|