police-dataflow 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +31 -0
- data/LICENSE.txt +20 -0
- data/README.markdown +9 -0
- data/Rakefile +38 -0
- data/VERSION +1 -0
- data/lib/police-dataflow.rb +2 -0
- data/lib/police/dataflow.rb +16 -0
- data/lib/police/dataflow/core_extensions.rb +12 -0
- data/lib/police/dataflow/guarding.rb +16 -0
- data/lib/police/dataflow/label.rb +86 -0
- data/lib/police/dataflow/labeling.rb +71 -0
- data/lib/police/dataflow/proxies.rb +51 -0
- data/lib/police/dataflow/proxy_base.rb +132 -0
- data/lib/police/dataflow/proxying.rb +179 -0
- data/police-dataflow.gemspec +62 -0
- data/test/dataflow/core_extensions_test.rb +51 -0
- data/test/dataflow/labeling_test.rb +74 -0
- data/test/dataflow/proxies_test.rb +40 -0
- data/test/dataflow/proxy_base_test.rb +145 -0
- data/test/dataflow/proxying_test.rb +287 -0
- data/test/helper.rb +23 -0
- data/test/helpers/auto_flow_fixture.rb +6 -0
- data/test/helpers/no_flow_fixture.rb +14 -0
- data/test/helpers/proxying_fixture.rb +45 -0
- metadata +76 -0
@@ -0,0 +1,179 @@
|
|
1
|
+
module Police
|
2
|
+
|
3
|
+
module DataFlow
|
4
|
+
|
5
|
+
# The complex method dispatching logic used by ProxyBase.
|
6
|
+
#
|
7
|
+
# ProxyBase is the superclass for all proxy classes, which makes it visible to
|
8
|
+
# application code. For this reason, we avoid defining any methods there.
|
9
|
+
module Proxying
|
10
|
+
# Creates a label-holding proxy for an object.
|
11
|
+
#
|
12
|
+
# @param [Object] proxied the object to be proxied
|
13
|
+
# @param [Array<Integer>] label_keys
|
14
|
+
# @return [Police::DataFlow::ProxyBase] an object that can carry labels, and
|
15
|
+
# performs label-propagation as it redirects received messages to the
|
16
|
+
# proxied object
|
17
|
+
def self.proxy(proxied, label_set, autoflow_set)
|
18
|
+
proxy_class = Police::DataFlow::Proxies.for proxied.class, label_set
|
19
|
+
proxy_class.new proxied, proxy_class, label_set, autoflow_set
|
20
|
+
end
|
21
|
+
|
22
|
+
# Creates proxies for a class' instance methods.
|
23
|
+
#
|
24
|
+
# The proxy methods are defined as instance methods for the proxying class,
|
25
|
+
# because all the proxied objects that have the same class will need the same
|
26
|
+
# proxies.
|
27
|
+
#
|
28
|
+
# @param [Class] proxy_class a Police::DataFlow::Proxy subclass that will
|
29
|
+
# receive the new proxy method definitions
|
30
|
+
# @param [Class] klass the class whose instance methods will be proxied
|
31
|
+
# @return [NilClass] nil
|
32
|
+
def self.add_class_methods(proxy_class, klass)
|
33
|
+
# NOTE: this is thread-safe because, at worst, the effort of adding methods
|
34
|
+
# will be re-duplicated
|
35
|
+
klass.public_instance_methods(true).each do |method|
|
36
|
+
add_class_method proxy_class, klass.instance_method(method), :public
|
37
|
+
end
|
38
|
+
klass.protected_instance_methods(true).each do |method|
|
39
|
+
add_class_method proxy_class, klass.instance_method(method), :protected
|
40
|
+
end
|
41
|
+
klass.private_instance_methods(true).each do |method|
|
42
|
+
add_class_method proxy_class, klass.instance_method(method), :private
|
43
|
+
end
|
44
|
+
nil
|
45
|
+
end
|
46
|
+
|
47
|
+
# Adds a method to a proxy class.
|
48
|
+
#
|
49
|
+
# @param [Module] proxy_class the class that will receive the proxy method
|
50
|
+
# @param [Method] method_def the definition of the method to be proxied
|
51
|
+
# @param [Symbol] access the proxied method's access level (:public,
|
52
|
+
# :protected, or :private)
|
53
|
+
def self.add_class_method(proxy_class, method_def, access)
|
54
|
+
# Avoid redefining methods, because that blows up VM caches.
|
55
|
+
if proxy_class.method_defined?(method_def.name) ||
|
56
|
+
proxy_class.private_method_defined?(method_def.name)
|
57
|
+
return
|
58
|
+
end
|
59
|
+
|
60
|
+
# Define the method.
|
61
|
+
proxy_class.class_eval proxy_method_definition(
|
62
|
+
proxy_class.__police_classes__, method_def, access)
|
63
|
+
# Set its access level.
|
64
|
+
proxy_class.__send__ access, method_def.name
|
65
|
+
end
|
66
|
+
|
67
|
+
# The full definition of a proxy method.
|
68
|
+
#
|
69
|
+
# @param [Array<Police::DataFlow::Label>] label_classes the label classes
|
70
|
+
# supported by the proxy class
|
71
|
+
# @param [Method] method_def the definition of the method to be proxied
|
72
|
+
# @param [Symbol] access the proxied method's access level (:public,
|
73
|
+
# :protected, or :private)
|
74
|
+
# @return [String] a chunk of Ruby that can be eval'ed in the context of a
|
75
|
+
# proxy class to define a proxy for the given method
|
76
|
+
def self.proxy_method_definition(label_classes, method_def, access)
|
77
|
+
# NOTE: it might be tempting to attempt to pass a block to the proxied
|
78
|
+
# method at all times, and try to yield to the original block when our
|
79
|
+
# block is invoked; this would work most of the time, but it would
|
80
|
+
# break methods such as Enumerable#map and String#scan, whose behavior
|
81
|
+
# changes depending on whether or not a block is passed to them
|
82
|
+
["def #{method_def.name}(#{proxy_argument_list(method_def, true)})",
|
83
|
+
"return_value = if block",
|
84
|
+
proxy_method_call(method_def, access, false) + " do |*yield_args|",
|
85
|
+
proxy_yield_args_decorating(label_classes, method_def),
|
86
|
+
"block_return = yield(*yield_args)",
|
87
|
+
# TODO(pwnall): consider adding a yield value filter
|
88
|
+
"next block_return",
|
89
|
+
"end",
|
90
|
+
"else",
|
91
|
+
proxy_method_call(method_def, access, false),
|
92
|
+
"end",
|
93
|
+
proxy_return_decorating(label_classes, method_def),
|
94
|
+
"return return_value",
|
95
|
+
"end"].join ';'
|
96
|
+
end
|
97
|
+
|
98
|
+
# The proxying call to a method.
|
99
|
+
#
|
100
|
+
# @param [Method] method_def the definition of the method to be proxied
|
101
|
+
# @param [Symbol] access the proxied method's access level (:public,
|
102
|
+
# :protected, or :private)
|
103
|
+
# @param [Boolean] include_block if true, the method call passes the block
|
104
|
+
# that the proxy has received; if false, the block is ignored
|
105
|
+
# @return [String] a chunk of Ruby that can be used to call the given method
|
106
|
+
# when defining a proxy for it
|
107
|
+
def self.proxy_method_call(method_def, access, include_block)
|
108
|
+
arg_list = proxy_argument_list method_def, include_block
|
109
|
+
|
110
|
+
if access == :public
|
111
|
+
"@__police_proxied__.#{method_def.name}(#{arg_list})"
|
112
|
+
else
|
113
|
+
"@__police_proxied__.__send__(:#{method_def.name}, #{arg_list})"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# Code that labels the values yielded by a decorated method to its block.
|
118
|
+
#
|
119
|
+
# @param [Array<Police::DataFlow::Label>] label_classes the label classes
|
120
|
+
# supported by the proxy class
|
121
|
+
# @param [Method] method_def the definition of the decorated method
|
122
|
+
# @return [String] a chunk of Ruby that can be used to invoke the yield args
|
123
|
+
# decorators of the labels held by a labeled object's proxy
|
124
|
+
def self.proxy_yield_args_decorating(label_classes, method_def)
|
125
|
+
method_name = method_def.name
|
126
|
+
arg_list = proxy_argument_list method_def, false
|
127
|
+
code_lines = ['labels = @__police_labels__']
|
128
|
+
label_classes.each do |label_class|
|
129
|
+
next unless hook = label_class.yield_args_hook(method_name)
|
130
|
+
label_key = label_class.__id__
|
131
|
+
code_lines << "labels[#{label_key}].each { |label, _| " \
|
132
|
+
"label.#{hook}(self, yield_args, #{arg_list}) }"
|
133
|
+
end
|
134
|
+
(code_lines.length > 1) ? code_lines.join('; ') : ''
|
135
|
+
end
|
136
|
+
|
137
|
+
# Code that labels return value of a decorated method.
|
138
|
+
#
|
139
|
+
# @param [Array<Police::DataFlow::Label>] label_classes the label classes
|
140
|
+
# supported by the proxy class
|
141
|
+
# @param [Method] method_def the definition of the method to be proxied
|
142
|
+
# @return [String] a chunk of Ruby that can be used to invoke the return value
|
143
|
+
# decorators of the labels held by a labeled object's proxy
|
144
|
+
def self.proxy_return_decorating(label_classes, method_def)
|
145
|
+
method_name = method_def.name
|
146
|
+
arg_list = proxy_argument_list method_def, false
|
147
|
+
code_lines = ['labels = @__police_labels__']
|
148
|
+
label_classes.each do |label_class|
|
149
|
+
next unless hook = label_class.return_hook(method_name)
|
150
|
+
label_key = label_class.__id__
|
151
|
+
code_lines << "labels[#{label_key}].each { |label, _| " \
|
152
|
+
"return_value = label.#{hook}(return_value, self, #{arg_list}) }"
|
153
|
+
end
|
154
|
+
(code_lines.length > 1) ? code_lines.join('; ') : ''
|
155
|
+
end
|
156
|
+
|
157
|
+
# The list of arguments used to define a proxy for the given method.
|
158
|
+
#
|
159
|
+
# @param [Method] method_def the definition of the method to be proxied
|
160
|
+
# @param [Boolean] captue_block if true, the method captures the block that it
|
161
|
+
# receives
|
162
|
+
# @return [String] a chunk of Ruby that can be used as the argument list when
|
163
|
+
# defining a proxy for the given method
|
164
|
+
def self.proxy_argument_list(method_def, capture_block)
|
165
|
+
arg_list = if method_def.arity >= 0
|
166
|
+
# Fixed number of arguments.
|
167
|
+
(1..method_def.arity).map { |i| "arg#{i}" }
|
168
|
+
else
|
169
|
+
# Variable number of arguments.
|
170
|
+
((1..(-method_def.arity - 1)).map { |i| "arg#{i}" } << '*args')
|
171
|
+
end
|
172
|
+
arg_list << '&block' if capture_block
|
173
|
+
arg_list.join ', '
|
174
|
+
end
|
175
|
+
end # namespace Police::DataFlow::Proxying
|
176
|
+
|
177
|
+
end # namespace Police::DataFlow
|
178
|
+
|
179
|
+
end # namespace Police
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "police-dataflow"
|
8
|
+
s.version = "0.0.1"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Victor Costan"]
|
12
|
+
s.date = "2012-03-28"
|
13
|
+
s.description = "Pure Ruby implementtion of data flow label propagation."
|
14
|
+
s.email = "victor@costan.us"
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE.txt",
|
17
|
+
"README.markdown"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".document",
|
21
|
+
"Gemfile",
|
22
|
+
"Gemfile.lock",
|
23
|
+
"LICENSE.txt",
|
24
|
+
"README.markdown",
|
25
|
+
"Rakefile",
|
26
|
+
"VERSION",
|
27
|
+
"lib/police-dataflow.rb",
|
28
|
+
"lib/police/dataflow.rb",
|
29
|
+
"lib/police/dataflow/core_extensions.rb",
|
30
|
+
"lib/police/dataflow/guarding.rb",
|
31
|
+
"lib/police/dataflow/label.rb",
|
32
|
+
"lib/police/dataflow/labeling.rb",
|
33
|
+
"lib/police/dataflow/proxies.rb",
|
34
|
+
"lib/police/dataflow/proxy_base.rb",
|
35
|
+
"lib/police/dataflow/proxying.rb",
|
36
|
+
"police-dataflow.gemspec",
|
37
|
+
"test/dataflow/core_extensions_test.rb",
|
38
|
+
"test/dataflow/labeling_test.rb",
|
39
|
+
"test/dataflow/proxies_test.rb",
|
40
|
+
"test/dataflow/proxy_base_test.rb",
|
41
|
+
"test/dataflow/proxying_test.rb",
|
42
|
+
"test/helper.rb",
|
43
|
+
"test/helpers/auto_flow_fixture.rb",
|
44
|
+
"test/helpers/no_flow_fixture.rb",
|
45
|
+
"test/helpers/proxying_fixture.rb"
|
46
|
+
]
|
47
|
+
s.homepage = "http://github.com/csail/police"
|
48
|
+
s.licenses = ["MIT"]
|
49
|
+
s.require_paths = ["lib"]
|
50
|
+
s.rubygems_version = "1.8.21"
|
51
|
+
s.summary = "Data flow label propagation"
|
52
|
+
|
53
|
+
if s.respond_to? :specification_version then
|
54
|
+
s.specification_version = 3
|
55
|
+
|
56
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
57
|
+
else
|
58
|
+
end
|
59
|
+
else
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require File.expand_path('../helper.rb', File.dirname(__FILE__))
|
2
|
+
|
3
|
+
describe Police::DataFlow do
|
4
|
+
before do
|
5
|
+
@object = 'Hello'
|
6
|
+
@class = @object.class
|
7
|
+
@label = AutoFlowFixture.new
|
8
|
+
end
|
9
|
+
|
10
|
+
describe '#label' do
|
11
|
+
describe 'with a single label' do
|
12
|
+
before do
|
13
|
+
@object = Police::DataFlow.label @object, @label
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'labels the object correctly' do
|
17
|
+
Police::DataFlow.labels(@object).must_equal [@label]
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'is idempotent' do
|
21
|
+
same_object = Police::DataFlow.label @object, @label
|
22
|
+
same_object.must_equal @object
|
23
|
+
Police::DataFlow.labels(@object).must_equal [@label]
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'returns a working proxy' do
|
27
|
+
@object.length.must_equal 5
|
28
|
+
@object.class.must_equal @class
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'returns a label-preserving proxy' do
|
32
|
+
@object << ' world'
|
33
|
+
@object.length.must_equal 11
|
34
|
+
Police::DataFlow.labels(@object).must_equal [@label]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe 'with two labels' do
|
39
|
+
before do
|
40
|
+
@label2 = NoFlowFixture.new
|
41
|
+
@object = Police::DataFlow.label @object, @label
|
42
|
+
@object = Police::DataFlow.label @object, @label2
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'labels the object correctly' do
|
46
|
+
Set.new(Police::DataFlow.labels(@object)).
|
47
|
+
must_equal Set.new([@label, @label2])
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require File.expand_path('../helper.rb', File.dirname(__FILE__))
|
2
|
+
|
3
|
+
describe Police::DataFlow do
|
4
|
+
before do
|
5
|
+
@object = 'Hello'
|
6
|
+
@class = @object.class
|
7
|
+
@p_label = AutoFlowFixture.new
|
8
|
+
end
|
9
|
+
|
10
|
+
describe '#label' do
|
11
|
+
describe 'with a single label' do
|
12
|
+
before do
|
13
|
+
@object = Police::DataFlow.label @object, @p_label
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'labels the object correctly' do
|
17
|
+
Police::DataFlow.labels(@object).must_equal [@p_label]
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'is idempotent' do
|
21
|
+
same_object = Police::DataFlow.label @object, @p_label
|
22
|
+
same_object.must_equal @object
|
23
|
+
Police::DataFlow.labels(@object).must_equal [@p_label]
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'is idempotent vs label type' do
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'returns a working proxy' do
|
31
|
+
@object.length.must_equal 5
|
32
|
+
@object.class.must_equal @class
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'returns a label-preserving proxy' do
|
36
|
+
@object << ' world'
|
37
|
+
@object.length.must_equal 11
|
38
|
+
Police::DataFlow.labels(@object).must_equal [@p_label]
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'returns a label-propagating proxy' do
|
42
|
+
Police::DataFlow.labels(@object[2..5]).must_equal [@p_label]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe 'with two labels' do
|
47
|
+
before do
|
48
|
+
@n_label = NoFlowFixture.new
|
49
|
+
@object = Police::DataFlow.label @object, @p_label
|
50
|
+
@object = Police::DataFlow.label @object, @n_label
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'labels the object correctly' do
|
54
|
+
Set.new(Police::DataFlow.labels(@object)).
|
55
|
+
must_equal Set.new([@p_label, @n_label])
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'propagates the labels correctly' do
|
59
|
+
Police::DataFlow.labels(@object[2..5]).must_equal [@p_label]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe '#labels' do
|
65
|
+
it 'returns an empty array for un-labeled objects' do
|
66
|
+
Police::DataFlow.labels(@object).must_equal []
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'returns a populated array for labeled objects' do
|
70
|
+
labeled = Police::DataFlow.label @object, @p_label
|
71
|
+
Police::DataFlow.labels(labeled).must_equal [@p_label]
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require File.expand_path('../helper.rb', File.dirname(__FILE__))
|
2
|
+
|
3
|
+
describe Police::DataFlow::Proxies do
|
4
|
+
describe '#for' do
|
5
|
+
before do
|
6
|
+
@label_set = {}
|
7
|
+
@autoflow_set = {}
|
8
|
+
Police::DataFlow::Labeling.add_label_to_set NoFlowFixture.new,
|
9
|
+
@label_set, @autoflow_set
|
10
|
+
end
|
11
|
+
|
12
|
+
let(:result) do
|
13
|
+
Police::DataFlow::Proxies.for ProxyingFixture, @label_set
|
14
|
+
end
|
15
|
+
after { Police::DataFlow::Proxies.clear_cache }
|
16
|
+
|
17
|
+
it 'creates a Police::DataFlow::ProxyBase subclass' do
|
18
|
+
result.superclass.must_equal Police::DataFlow::ProxyBase
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'caches classes' do
|
22
|
+
Police::DataFlow::Proxies.for(ProxyingFixture, @label_set).
|
23
|
+
must_equal result
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'creates different proxies for different classes' do
|
27
|
+
Police::DataFlow::Proxies.for(Class.new(ProxyingFixture), @label_set).
|
28
|
+
wont_equal result
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'creates different proxies for sets of different label classes' do
|
32
|
+
label_set = @label_set.clone
|
33
|
+
autoflow_set = @autoflow_set.clone
|
34
|
+
Police::DataFlow::Labeling.add_label_to_set AutoFlowFixture.new,
|
35
|
+
label_set, autoflow_set
|
36
|
+
Police::DataFlow::Proxies.for(ProxyingFixture, label_set).
|
37
|
+
wont_equal result
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
require File.expand_path('../helper.rb', File.dirname(__FILE__))
|
2
|
+
|
3
|
+
describe Police::DataFlow::ProxyBase do
|
4
|
+
before do
|
5
|
+
@label = AutoFlowFixture.new
|
6
|
+
@label_set = {}
|
7
|
+
@autoflow_set = {}
|
8
|
+
Police::DataFlow::Labeling.add_label_to_set @label, @label_set,
|
9
|
+
@autoflow_set
|
10
|
+
@proxied = ProxyingFixture.new
|
11
|
+
@proxy_class = Police::DataFlow::Proxies.for ProxyingFixture, @label_set
|
12
|
+
@proxy = @proxy_class.new @proxied, @proxy_class, @label_set, @autoflow_set
|
13
|
+
end
|
14
|
+
after { Police::DataFlow::Proxies.clear_cache }
|
15
|
+
|
16
|
+
it 'proxies public methods' do
|
17
|
+
# NOTE: this test exercises the slow path in method_missing
|
18
|
+
@proxy.route('One', 'Two').must_equal ['One', 'Two']
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'allows labels to filter the return value of public methods' do
|
22
|
+
# NOTE: this test exercises the slow path in method_missing
|
23
|
+
Police::DataFlow.labels(@proxy.route('One', 'Two')).must_equal [@label]
|
24
|
+
end
|
25
|
+
|
26
|
+
describe 'after proxying public methods' do
|
27
|
+
before { @proxy.route 'One', 'Two' }
|
28
|
+
|
29
|
+
it 'defines proxied methods on the fly' do
|
30
|
+
@proxy_class.public_method_defined?(:route).must_equal true
|
31
|
+
@proxy_class.instance_method(:route).owner.must_equal @proxy_class
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'still proxies public methods' do
|
35
|
+
# NOTE: this test exercises the auto-generated proxy method's fast path
|
36
|
+
@proxy.route('One', 'Two').must_equal ['One', 'Two']
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'still allows labels to filter the return value of public methods' do
|
40
|
+
# NOTE: this test exercises the auto-generated proxy method's fast path
|
41
|
+
Police::DataFlow.labels(@proxy.route('One', 'Two')).must_equal [@label]
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'can still build proxies' do
|
45
|
+
other_proxy = @proxy_class.new ProxyingFixture.new, @proxy_class,
|
46
|
+
@label_set, @autoflow_set
|
47
|
+
other_proxy.route('One', 'Two').must_equal ['One', 'Two']
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'proxies public methods with blocks' do
|
52
|
+
# NOTE: this test exercises the slow path in method_missing
|
53
|
+
result = []
|
54
|
+
@proxy.route('One', 'Two') { |*args| result << args }
|
55
|
+
result.must_equal [['One', 'Two']]
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'allows labels to filter the yielded values of public methods' do
|
59
|
+
# NOTE: this test exercises the slow path in method_missing
|
60
|
+
@proxy.route('One', 'Two') do |*args|
|
61
|
+
args.each { |arg| Police::DataFlow.labels(arg).must_equal [@label] }
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe 'after proxying public methods with blocks' do
|
66
|
+
before { @proxy.route('One', 'Two') { |*args| } }
|
67
|
+
|
68
|
+
it 'defines proxied methods on the fly' do
|
69
|
+
@proxy_class.public_method_defined?(:route).must_equal true
|
70
|
+
@proxy_class.instance_method(:route).owner.must_equal @proxy_class
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'still proxies public methods with blocks' do
|
74
|
+
# NOTE: this test exercises the auto-generated proxy method's fast path
|
75
|
+
result = []
|
76
|
+
@proxy.route('One', 'Two') { |*args| result << args }
|
77
|
+
result.must_equal [['One', 'Two']]
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'still allows labels to filter the yielded values of public methods' do
|
81
|
+
# NOTE: this test exercises the auto-generated proxy method's fast path
|
82
|
+
@proxy.route('One', 'Two') do |*args|
|
83
|
+
args.each { |arg| Police::DataFlow.labels(arg).must_equal [@label] }
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'proxies protected methods' do
|
89
|
+
# NOTE: this test exercises the slow path in method_missing
|
90
|
+
@proxy.__send__(:add, 'One', 'Two').must_equal 'One, Two'
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'allows labels to filter the return value of protected methods' do
|
94
|
+
# NOTE: this test exercises the slow path in method_missing
|
95
|
+
Police::DataFlow.labels(@proxy.__send__(:add, 'One', 'Two')).
|
96
|
+
must_equal [@label]
|
97
|
+
end
|
98
|
+
|
99
|
+
|
100
|
+
describe 'after proxying protected methods' do
|
101
|
+
before { @proxy.__send__ :add, 'One', 'Two' }
|
102
|
+
|
103
|
+
it 'defines proxied methods on the fly' do
|
104
|
+
@proxy_class.protected_method_defined?(:add).must_equal true
|
105
|
+
@proxy_class.instance_method(:add).owner.must_equal @proxy_class
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'still proxies protected methods' do
|
109
|
+
# NOTE: this test exercises the auto-generated proxy method's fast path
|
110
|
+
@proxy.__send__(:add, 'One', 'Two').must_equal 'One, Two'
|
111
|
+
end
|
112
|
+
|
113
|
+
it 'still allows labels to filter the return value of protected methods' do
|
114
|
+
# NOTE: this test exercises the auto-generated proxy method's fast path
|
115
|
+
Police::DataFlow.labels(@proxy.__send__(:add, 'One', 'Two')).
|
116
|
+
must_equal [@label]
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'proxies magic methods' do
|
121
|
+
@proxy.magic_meth('One', 'Two').must_equal ['meth', 'One', 'Two']
|
122
|
+
end
|
123
|
+
|
124
|
+
it 'allows labels to filter the return value of magic methods' do
|
125
|
+
Police::DataFlow.labels(@proxy.magic_meth('One', 'Two')).must_equal [@label]
|
126
|
+
end
|
127
|
+
|
128
|
+
describe 'after proxying magic methods' do
|
129
|
+
before do
|
130
|
+
@proxy.magic_meth 'One', 'Two'
|
131
|
+
end
|
132
|
+
|
133
|
+
it 'does not define magic proxied methods' do
|
134
|
+
@proxy_class.public_method_defined?(:magic_meth).must_equal false
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
it 'proxies ==' do
|
139
|
+
(@proxy == nil).must_equal '== proxied'
|
140
|
+
end
|
141
|
+
|
142
|
+
it 'proxies !=' do
|
143
|
+
(@proxy != nil).must_equal '!= proxied'
|
144
|
+
end
|
145
|
+
end
|