private_please 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- 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) #
|