caricature 0.3.1 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Rakefile +100 -0
- data/VERSION +1 -0
- data/caricature.gemspec +177 -0
- data/doc/Array.html +251 -0
- data/doc/Caricature/ArgumentRecording.html +366 -0
- data/doc/Caricature/ClrClassDescriptor.html +303 -0
- data/doc/Caricature/ClrClassMessenger.html +263 -0
- data/doc/Caricature/ClrInterfaceDescriptor.html +295 -0
- data/doc/Caricature/ClrInterfaceIsolator.html +365 -0
- data/doc/Caricature/ClrInterfaceMessenger.html +260 -0
- data/doc/Caricature/ClrIsolator.html +424 -0
- data/doc/Caricature/Expectation.html +549 -0
- data/doc/Caricature/ExpectationBuilder.html +308 -0
- data/doc/Caricature/ExpectationSyntax.html +438 -0
- data/doc/Caricature/Expectations.html +344 -0
- data/doc/Caricature/Interception/ClassMethods.html +246 -0
- data/doc/Caricature/Interception.html +416 -0
- data/doc/Caricature/Isolation.html +578 -0
- data/doc/Caricature/Isolator.html +519 -0
- data/doc/Caricature/MemberDescriptor.html +335 -0
- data/doc/Caricature/Messenger.html +335 -0
- data/doc/Caricature/MethodCallRecorder.html +440 -0
- data/doc/Caricature/MethodCallRecording.html +493 -0
- data/doc/Caricature/RubyIsolator.html +416 -0
- data/doc/Caricature/RubyMessenger.html +260 -0
- data/doc/Caricature/RubyObjectDescriptor.html +289 -0
- data/doc/Caricature/TypeDescriptor.html +378 -0
- data/doc/Caricature/Verification.html +443 -0
- data/doc/Caricature.html +358 -0
- data/doc/Class.html +287 -0
- data/doc/Hash.html +255 -0
- data/doc/Module.html +287 -0
- data/doc/Object.html +322 -0
- data/doc/README_markdown.html +241 -0
- data/doc/String.html +289 -0
- data/doc/System/String.html +289 -0
- data/doc/System/Type.html +289 -0
- data/doc/System.html +207 -0
- data/doc/created.rid +1 -0
- data/doc/images/brick.png +0 -0
- data/doc/images/brick_link.png +0 -0
- data/doc/images/bug.png +0 -0
- data/doc/images/bullet_black.png +0 -0
- data/doc/images/bullet_toggle_minus.png +0 -0
- data/doc/images/bullet_toggle_plus.png +0 -0
- data/doc/images/date.png +0 -0
- data/doc/images/find.png +0 -0
- data/doc/images/loadingAnimation.gif +0 -0
- data/doc/images/macFFBgHack.png +0 -0
- data/doc/images/package.png +0 -0
- data/doc/images/page_green.png +0 -0
- data/doc/images/page_white_text.png +0 -0
- data/doc/images/page_white_width.png +0 -0
- data/doc/images/plugin.png +0 -0
- data/doc/images/ruby.png +0 -0
- data/doc/images/tag_green.png +0 -0
- data/doc/images/wrench.png +0 -0
- data/doc/images/wrench_orange.png +0 -0
- data/doc/images/zoom.png +0 -0
- data/doc/index.html +312 -0
- data/doc/js/darkfish.js +116 -0
- data/doc/js/jquery.js +32 -0
- data/doc/js/quicksearch.js +114 -0
- data/doc/js/thickbox-compressed.js +10 -0
- data/doc/lib/caricature/clr/descriptor_rb.html +52 -0
- data/doc/lib/caricature/clr/isolation_rb.html +52 -0
- data/doc/lib/caricature/clr/isolator_rb.html +52 -0
- data/doc/lib/caricature/clr/messenger_rb.html +52 -0
- data/doc/lib/caricature/clr_rb.html +52 -0
- data/doc/lib/caricature/descriptor_rb.html +52 -0
- data/doc/lib/caricature/expectation_rb.html +52 -0
- data/doc/lib/caricature/isolation_rb.html +52 -0
- data/doc/lib/caricature/isolator_rb.html +52 -0
- data/doc/lib/caricature/messaging_rb.html +52 -0
- data/doc/lib/caricature/method_call_recorder_rb.html +52 -0
- data/doc/lib/caricature/verification_rb.html +52 -0
- data/doc/lib/caricature_rb.html +52 -0
- data/doc/lib/core_ext/array_rb.html +52 -0
- data/doc/lib/core_ext/class_rb.html +52 -0
- data/doc/lib/core_ext/core_ext_rb.html +52 -0
- data/doc/lib/core_ext/hash_rb.html +52 -0
- data/doc/lib/core_ext/module_rb.html +52 -0
- data/doc/lib/core_ext/object_rb.html +52 -0
- data/doc/lib/core_ext/string_rb.html +52 -0
- data/doc/lib/core_ext/system/string_rb.html +52 -0
- data/doc/lib/core_ext/system/type_rb.html +52 -0
- data/doc/rdoc.css +696 -0
- data/irb_init.rb +9 -0
- data/lib/bin/Workarounds.dll +0 -0
- data/lib/bin/Workarounds.pdb +0 -0
- data/lib/caricature/clr/descriptor.rb +55 -0
- data/lib/caricature/clr/isolation.rb +33 -0
- data/lib/caricature/clr/isolator.rb +112 -0
- data/lib/caricature/clr/messenger.rb +46 -0
- data/lib/caricature/clr.rb +6 -0
- data/lib/caricature/descriptor.rb +74 -0
- data/lib/caricature/expectation.rb +159 -0
- data/lib/caricature/isolation.rb +146 -0
- data/lib/caricature/isolator.rb +287 -0
- data/lib/caricature/messenger.rb +57 -0
- data/lib/caricature/method_call_recorder.rb +130 -0
- data/lib/caricature/verification.rb +43 -0
- data/lib/caricature.rb +11 -0
- data/lib/core_ext/array.rb +10 -0
- data/lib/core_ext/class.rb +15 -0
- data/lib/core_ext/core_ext.rb +8 -0
- data/lib/core_ext/hash.rb +13 -0
- data/lib/core_ext/module.rb +15 -0
- data/lib/core_ext/object.rb +19 -0
- data/lib/core_ext/string.rb +17 -0
- data/lib/core_ext/system/string.rb +21 -0
- data/lib/core_ext/system/type.rb +21 -0
- data/pkg/.gitignore +0 -0
- data/pkg/caricature-0.1.0.gem +0 -0
- data/spec/bacon_helper.rb +49 -3
- data/spec/bin/.gitignore +0 -0
- data/spec/core_ext_spec.rb +9 -0
- data/spec/descriptor_spec.rb +142 -0
- data/spec/expectation_spec.rb +11 -7
- data/spec/integration_spec.rb +94 -2
- data/spec/isolation_spec.rb +0 -42
- data/spec/isolator_spec.rb +72 -66
- data/spec/messaging_spec.rb +172 -0
- data/spec/method_call_spec.rb +21 -21
- data/spec/models/ClrModels.cs +185 -0
- data/spec/verification_spec.rb +1 -1
- data/workarounds/ReflectionHelper.cs +30 -0
- metadata +133 -2
@@ -0,0 +1,287 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/messenger'
|
2
|
+
require File.dirname(__FILE__) + '/descriptor'
|
3
|
+
|
4
|
+
module Caricature
|
5
|
+
|
6
|
+
# Groups the methods for interception together
|
7
|
+
# this is a mix-in for the created isolations for classes
|
8
|
+
module Interception
|
9
|
+
|
10
|
+
# the class methods of this intercepting object
|
11
|
+
module ClassMethods
|
12
|
+
|
13
|
+
# the context of this isolation instance.
|
14
|
+
# this context takes care of responding to method calls etc.
|
15
|
+
def isolation_context
|
16
|
+
@___context___
|
17
|
+
end
|
18
|
+
|
19
|
+
# Replaces the call to the proxy with the one you create with this method.
|
20
|
+
# You can specify more specific criteria in the block to configure the expectation.
|
21
|
+
#
|
22
|
+
# Example:
|
23
|
+
#
|
24
|
+
# an_isolation.class.when_receiving(:a_method) do |method_call|
|
25
|
+
# method_call.with(3, "a").return(5)
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# is equivalent to:
|
29
|
+
#
|
30
|
+
# an_isolation.class.when_receiving(:a_method).with(3, "a").return(5)
|
31
|
+
#
|
32
|
+
# You will most likely use this method when you want your stubs to return something else than +nil+
|
33
|
+
# when they get called during the run of the test they are defined in.
|
34
|
+
def when_receiving(method_name, &block)
|
35
|
+
isolation_context.create_class_override method_name, &block
|
36
|
+
end
|
37
|
+
|
38
|
+
# Verifies whether the specified method has been called
|
39
|
+
# You can specify constraints in the block
|
40
|
+
#
|
41
|
+
# The most complex configuration you can make currently is one that is constrained by arguments.
|
42
|
+
# This is most likely to be extended in the future to allow for more complex verifications.
|
43
|
+
#
|
44
|
+
# Example:
|
45
|
+
#
|
46
|
+
# an_isolation.class.did_receive?(:a_method) do |method_call|
|
47
|
+
# method_call.with(3, "a")
|
48
|
+
# end.should.be.successful
|
49
|
+
#
|
50
|
+
# is equivalent to:
|
51
|
+
#
|
52
|
+
# an_isolation.class.did_receive?(:a_method).with(3, "a").should.be.successful
|
53
|
+
#
|
54
|
+
# You will probably be using this method only when you're interested in whether a method has been called
|
55
|
+
# during the course of the test you're running.
|
56
|
+
def did_receive?(method_name, &block)
|
57
|
+
isolation_context.class_verify method_name
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
# mixes in the class methods of this module when it gets included in a class.
|
63
|
+
def self.included(base)
|
64
|
+
base.extend ClassMethods
|
65
|
+
end
|
66
|
+
|
67
|
+
# the context of this isolation instance.
|
68
|
+
# this context takes care of responding to method calls etc.
|
69
|
+
def isolation_context
|
70
|
+
self.class.isolation_context
|
71
|
+
end
|
72
|
+
|
73
|
+
# Replaces the call to the proxy with the one you create with this method.
|
74
|
+
# You can specify more specific criteria in the block to configure the expectation.
|
75
|
+
#
|
76
|
+
# Example:
|
77
|
+
#
|
78
|
+
# an_isolation.when_receiving(:a_method) do |method_call|
|
79
|
+
# method_call.with(3, "a").return(5)
|
80
|
+
# end
|
81
|
+
#
|
82
|
+
# is equivalent to:
|
83
|
+
#
|
84
|
+
# an_isolation.when_receiving(:a_method).with(3, "a").return(5)
|
85
|
+
#
|
86
|
+
# You will most likely use this method when you want your stubs to return something else than +nil+
|
87
|
+
# when they get called during the run of the test they are defined in.
|
88
|
+
def when_receiving(method_name, &block)
|
89
|
+
isolation_context.create_override method_name, &block
|
90
|
+
end
|
91
|
+
|
92
|
+
# Replaces the call to the class of the proxy with the one you create with this method.
|
93
|
+
# You can specify more specific criteria in the block to configure the expectation.
|
94
|
+
#
|
95
|
+
# Example:
|
96
|
+
#
|
97
|
+
# an_isolation.when_class_receives(:a_method) do |method_call|
|
98
|
+
# method_call.with(3, "a").return(5)
|
99
|
+
# end
|
100
|
+
#
|
101
|
+
# is equivalent to:
|
102
|
+
#
|
103
|
+
# an_isolation.when_class_receives(:a_method).with(3, "a").return(5)
|
104
|
+
#
|
105
|
+
# You will most likely use this method when you want your stubs to return something else than +nil+
|
106
|
+
# when they get called during the run of the test they are defined in.
|
107
|
+
def when_class_receives(method_name, &block)
|
108
|
+
self.class.when_receiving method_name, &block
|
109
|
+
end
|
110
|
+
|
111
|
+
# Verifies whether the specified method has been called
|
112
|
+
# You can specify constraints in the block
|
113
|
+
#
|
114
|
+
# The most complex configuration you can make currently is one that is constrained by arguments.
|
115
|
+
# This is most likely to be extended in the future to allow for more complex verifications.
|
116
|
+
#
|
117
|
+
# Example:
|
118
|
+
#
|
119
|
+
# an_isolation.did_receive?(:a_method) do |method_call|
|
120
|
+
# method_call.with(3, "a")
|
121
|
+
# end.should.be.successful
|
122
|
+
#
|
123
|
+
# is equivalent to:
|
124
|
+
#
|
125
|
+
# an_isolation.did_receive?(:a_method).with(3, "a").should.be.successful
|
126
|
+
#
|
127
|
+
# You will probably be using this method only when you're interested in whether a method has been called
|
128
|
+
# during the course of the test you're running.
|
129
|
+
def did_receive?(method_name, &block)
|
130
|
+
isolation_context.verify method_name
|
131
|
+
end
|
132
|
+
|
133
|
+
# Verifies whether the specified class method has been called
|
134
|
+
# You can specify constraints in the block
|
135
|
+
#
|
136
|
+
# The most complex configuration you can make currently is one that is constrained by arguments.
|
137
|
+
# This is most likely to be extended in the future to allow for more complex verifications.
|
138
|
+
#
|
139
|
+
# Example:
|
140
|
+
#
|
141
|
+
# an_isolation.did_class_receive?(:a_method) do |method_call|
|
142
|
+
# method_call.with(3, "a")
|
143
|
+
# end.should.be.successful
|
144
|
+
#
|
145
|
+
# is equivalent to:
|
146
|
+
#
|
147
|
+
# an_isolation.did_class_receive?(:a_method).with(3, "a").should.be.successful
|
148
|
+
#
|
149
|
+
# You will probably be using this method only when you're interested in whether a method has been called
|
150
|
+
# during the course of the test you're running.
|
151
|
+
def did_class_receive?(method_name, &block)
|
152
|
+
self.class.did_receive?(method_name, &block)
|
153
|
+
end
|
154
|
+
|
155
|
+
end
|
156
|
+
|
157
|
+
# A base class for +Isolator+ objects
|
158
|
+
# to stick with the +Isolation+ nomenclature the strategies for creating isolations
|
159
|
+
# are called isolators.
|
160
|
+
# An isolator functions as a barrier between the code in your test and the
|
161
|
+
# underlying type/instance. It allows you to take control over the value
|
162
|
+
# that is returned from a specific method, if you want to pass the method call along to
|
163
|
+
# the underlying instance etc. It also contains the ability to verify if a method
|
164
|
+
# was called, with which arguments etc.
|
165
|
+
class Isolator
|
166
|
+
|
167
|
+
# holds the isolation created by this isolator
|
168
|
+
attr_reader :isolation
|
169
|
+
|
170
|
+
# holds the subject of this isolator
|
171
|
+
attr_reader :subject
|
172
|
+
|
173
|
+
# holds the descriptor for this type of object
|
174
|
+
attr_reader :descriptor
|
175
|
+
|
176
|
+
# creates a new instance of an isolator
|
177
|
+
def initialize(context)
|
178
|
+
@context = context
|
179
|
+
end
|
180
|
+
|
181
|
+
# builds up the isolation class instance
|
182
|
+
def build_isolation(klass, inst=nil)
|
183
|
+
pxy = create_isolation_for klass
|
184
|
+
@isolation = pxy.new
|
185
|
+
@subject = inst
|
186
|
+
initialize_messenger
|
187
|
+
end
|
188
|
+
|
189
|
+
# initializes the messaging strategy for the isolator
|
190
|
+
def initialize_messenger
|
191
|
+
raise NotImplementedError
|
192
|
+
end
|
193
|
+
|
194
|
+
# Creates the new class name for the isolation
|
195
|
+
def class_name(subj)
|
196
|
+
nm = subj.respond_to?(:class_eval) ? subj.demodulize : subj.class.demodulize
|
197
|
+
@class_name = "#{nm}#{System::Guid.new_guid.to_string('n')}"
|
198
|
+
@class_name
|
199
|
+
end
|
200
|
+
|
201
|
+
# Sets up the necessary instance variables for the isolation
|
202
|
+
def initialize_isolation(klass, context)
|
203
|
+
pxy = klass.new
|
204
|
+
pxy.instance_variable_set("@___context___", context)
|
205
|
+
pxy
|
206
|
+
end
|
207
|
+
|
208
|
+
class << self
|
209
|
+
|
210
|
+
# Creates the actual proxy object for the +subject+ and initializes it with a
|
211
|
+
# +recorder+ and +expectations+
|
212
|
+
# This is the actual isolation that will be used to in your tests.
|
213
|
+
# It implements all the methods of the +subject+ so as long as you're in Ruby
|
214
|
+
# and just need to isolate out some classes defined in a statically compiled language
|
215
|
+
# it should get you all the way there for public instance methods at this point.
|
216
|
+
# when you're going to isolation for usage within a statically compiled language type
|
217
|
+
# then you're bound to most of their rules. So you need to either isolate interfaces
|
218
|
+
# or mark the methods you want to isolate as virtual in your implementing classes.
|
219
|
+
def for(context)
|
220
|
+
context.recorder ||= MethodCallRecorder.new
|
221
|
+
context.expectations ||= Expectations.new
|
222
|
+
new(context)
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
# A proxy to Ruby objects that records method calls
|
228
|
+
# this implements all the instance methods that are defined on the class.
|
229
|
+
class RubyIsolator < Isolator
|
230
|
+
|
231
|
+
# implemented template method for creating Ruby isolations
|
232
|
+
def initialize(context)
|
233
|
+
super
|
234
|
+
klass = @context.subject.respond_to?(:class_eval) ? @context.subject : @context.subject.class
|
235
|
+
inst = @context.subject.respond_to?(:class_eval) ? @context.subject.new : @context.subject
|
236
|
+
@descriptor = RubyObjectDescriptor.new klass
|
237
|
+
build_isolation klass, inst
|
238
|
+
end
|
239
|
+
|
240
|
+
# initializes the messaging strategy for the isolator
|
241
|
+
def initialize_messenger
|
242
|
+
@context.messenger = RubyMessenger.new @context.expectations, @subject
|
243
|
+
end
|
244
|
+
|
245
|
+
# creates the ruby isolator for the specified subject
|
246
|
+
def create_isolation_for(subj)
|
247
|
+
imembers = @descriptor.instance_members
|
248
|
+
cmembers = @descriptor.class_members
|
249
|
+
|
250
|
+
klass = Object.const_set(class_name(subj), Class.new(subj))
|
251
|
+
klass.class_eval do
|
252
|
+
|
253
|
+
include Interception
|
254
|
+
|
255
|
+
# access to the proxied subject
|
256
|
+
def ___super___
|
257
|
+
isolation_context.instance
|
258
|
+
end
|
259
|
+
|
260
|
+
imembers.each do |mn|
|
261
|
+
mn = mn.name.to_s.to_sym
|
262
|
+
define_method mn do |*args|
|
263
|
+
b = nil
|
264
|
+
b = Proc.new { yield } if block_given?
|
265
|
+
isolation_context.send_message(mn, nil, *args, &b)
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
cmembers.each do |mn|
|
270
|
+
mn = mn.name.to_s.to_sym
|
271
|
+
define_cmethod mn do |*args|
|
272
|
+
b = nil
|
273
|
+
b = Proc.new { yield } if block_given?
|
274
|
+
isolation_context.send_class_message(mn, nil, *args, &b)
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
|
279
|
+
end
|
280
|
+
|
281
|
+
klass
|
282
|
+
end
|
283
|
+
|
284
|
+
|
285
|
+
end
|
286
|
+
|
287
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Caricature
|
2
|
+
|
3
|
+
# A base class to encapsulate method invocation
|
4
|
+
class Messenger
|
5
|
+
|
6
|
+
# the real instance of the isolated subject
|
7
|
+
# used to forward calls in partial mocks
|
8
|
+
attr_reader :instance
|
9
|
+
|
10
|
+
# the expecations that have been set for the isolation
|
11
|
+
attr_reader :expectations
|
12
|
+
|
13
|
+
# creates a new instance of this messaging strategy
|
14
|
+
def initialize(expectations, instance=nil)
|
15
|
+
@instance, @expectations = instance, expectations
|
16
|
+
end
|
17
|
+
|
18
|
+
# deliver the message to the receiving isolation
|
19
|
+
def deliver(method_name, return_type, *args, &b)
|
20
|
+
internal_deliver(:instance, method_name, return_type, *args, &b)
|
21
|
+
end
|
22
|
+
|
23
|
+
# deliver the message to class of the receiving isolation
|
24
|
+
def deliver_to_class(method_name, return_type, *args, &b)
|
25
|
+
internal_deliver(:class, method_name, return_type, *args, &b)
|
26
|
+
end
|
27
|
+
|
28
|
+
protected
|
29
|
+
|
30
|
+
# template method for looking up the expectation and/or returning a value
|
31
|
+
def internal_deliver(mode, method_name, return_type, *args, &b)
|
32
|
+
raise NotImplementedError.new("Override in an implementing class")
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
# Encapsulates sending messages to Ruby isolations
|
38
|
+
class RubyMessenger < Messenger
|
39
|
+
|
40
|
+
protected
|
41
|
+
|
42
|
+
# implementation of the template method for looking up the expectation and/or returning a value
|
43
|
+
def internal_deliver(mode, method_name, return_type, *args, &b)
|
44
|
+
exp = expectations.find(method_name, mode, *args)
|
45
|
+
if exp
|
46
|
+
res = instance.__send__(method_name, *args, &b) if exp.super_before?
|
47
|
+
res = exp.execute *args
|
48
|
+
res = instance.__send__(method_name, *args, &b) if !exp.super_before? and exp.call_super?
|
49
|
+
res
|
50
|
+
else
|
51
|
+
nil
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
module Caricature
|
2
|
+
|
3
|
+
# A recording of an argument variation.
|
4
|
+
# Every time a method is called with different arguments
|
5
|
+
# the method call recorder will create an ArgumentVariation
|
6
|
+
# these variations are used later in the assertion
|
7
|
+
# to verify against specific argument values
|
8
|
+
class ArgumentRecording
|
9
|
+
|
10
|
+
# contains the arguments of the recorded parameters
|
11
|
+
attr_accessor :args
|
12
|
+
|
13
|
+
# contains the block for the recorded parameters
|
14
|
+
attr_accessor :block
|
15
|
+
|
16
|
+
# the number of the call that has the following parameters
|
17
|
+
attr_accessor :call_number
|
18
|
+
|
19
|
+
# initializes a new instance of an argument recording.
|
20
|
+
# configures it with 1 call count and the args as an +Array+
|
21
|
+
def initialize(args=[], call_number=1, block=nil)
|
22
|
+
@args = args
|
23
|
+
@block = block
|
24
|
+
@call_number = call_number
|
25
|
+
end
|
26
|
+
|
27
|
+
# compares one argument variation to another.
|
28
|
+
# Also takes an array as an argument
|
29
|
+
def ==(other)
|
30
|
+
other = self.class.new(other) if other.respond_to?(:each)
|
31
|
+
return true if other.args.first == :any
|
32
|
+
other.args == args
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# A recording that represents a method call
|
37
|
+
# it contains argument variations that can be matched too
|
38
|
+
class MethodCallRecording
|
39
|
+
|
40
|
+
# gets or sets the method name
|
41
|
+
attr_accessor :method_name
|
42
|
+
|
43
|
+
# gets or sets the amount of times the method was called
|
44
|
+
attr_accessor :count
|
45
|
+
|
46
|
+
# gets or sets the arguments for this method call
|
47
|
+
attr_accessor :args
|
48
|
+
|
49
|
+
# gets or sets the block for this method call
|
50
|
+
attr_accessor :block
|
51
|
+
|
52
|
+
# Initializes a new instance of a method call recording
|
53
|
+
# every time a method gets called in an isolated object
|
54
|
+
# this gets stored in the method call recorder
|
55
|
+
# It expects a +method_name+ at the very least.
|
56
|
+
def initialize(method_name, count=0)
|
57
|
+
@method_name = method_name
|
58
|
+
@count = count
|
59
|
+
@variations = []
|
60
|
+
end
|
61
|
+
|
62
|
+
# add args
|
63
|
+
def args
|
64
|
+
@variations
|
65
|
+
end
|
66
|
+
|
67
|
+
# indicates if it has an argument variation
|
68
|
+
def has_argument_variations?
|
69
|
+
@variations.size > 1
|
70
|
+
end
|
71
|
+
|
72
|
+
# add an argument variation
|
73
|
+
def add_argument_variation(args, block)
|
74
|
+
variation = find_argument_variations args
|
75
|
+
@variations << ArgumentRecording.new(args, @variations.size+1, block) if variation == []
|
76
|
+
end
|
77
|
+
|
78
|
+
# finds an argument variation that matches the provided +args+
|
79
|
+
def find_argument_variations(args)
|
80
|
+
return @variations if args.first == :any
|
81
|
+
@variations.select { |ar| ar.args == args }
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# The recorder that will collect method calls and provides an interface for finding those recordings
|
86
|
+
class MethodCallRecorder
|
87
|
+
|
88
|
+
# gets the collection of method calls. This is a hash with the method name as key
|
89
|
+
attr_reader :method_calls
|
90
|
+
|
91
|
+
|
92
|
+
# Initializes a new instance of a method call recorder
|
93
|
+
# every time a method gets called in an isolated object
|
94
|
+
# this gets stored in the method call recorder
|
95
|
+
def initialize
|
96
|
+
@method_calls = {:instance => {}, :class => {} }
|
97
|
+
end
|
98
|
+
|
99
|
+
# records a method call or increments the count of how many times this method was called.
|
100
|
+
def record_call(method_name, mode=:instance, *args, &block)
|
101
|
+
mn_sym = method_name.to_s.to_sym
|
102
|
+
method_calls[mode][mn_sym] ||= MethodCallRecording.new method_name
|
103
|
+
mc = method_calls[mode][mn_sym]
|
104
|
+
mc.count += 1
|
105
|
+
mc.add_argument_variation args, block
|
106
|
+
end
|
107
|
+
|
108
|
+
# returns whether the method was actually called with the specified constraints
|
109
|
+
def was_called?(method_name, mode=:instance, *args)
|
110
|
+
mc = method_calls[mode][method_name.to_s.to_sym]
|
111
|
+
if mc
|
112
|
+
return mc.find_argument_variations(args).first == args
|
113
|
+
else
|
114
|
+
return !!mc
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# # indexer that gives you access to the recorded method by method name
|
119
|
+
# def [](method_name)
|
120
|
+
# method_calls[:instance][method_name.to_s.to_sym]
|
121
|
+
# end
|
122
|
+
|
123
|
+
# returns the number of different methods that has been recorderd
|
124
|
+
def size
|
125
|
+
@method_calls.size
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
|
130
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Caricature
|
2
|
+
|
3
|
+
# Describes a verification of a method call.
|
4
|
+
# This corresponds kind of to an assertion
|
5
|
+
class Verification
|
6
|
+
|
7
|
+
# Initializes a new instance of a +Verification+
|
8
|
+
def initialize(method_name, recorder, mode=:instance)
|
9
|
+
@method_name, @args, @any_args, @recorder, @mode = method_name, [], true, recorder, mode
|
10
|
+
end
|
11
|
+
|
12
|
+
# indicates whether this verification can be for any arguments
|
13
|
+
def any_args?
|
14
|
+
@any_args
|
15
|
+
end
|
16
|
+
|
17
|
+
# constrain this verification to the provided arguments
|
18
|
+
def with(*args)
|
19
|
+
@any_args = false unless args.first == :any
|
20
|
+
@args = args
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
# allow any arguments ignore the argument constraint
|
25
|
+
def allow_any_arguments
|
26
|
+
@any_args = true
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
# figure out if this argument variation matches the provided args.
|
31
|
+
def matches?(method_name, *args)
|
32
|
+
@method_name == method_name and any_args? or @args == args
|
33
|
+
end
|
34
|
+
|
35
|
+
# indicate that this method verification is successful
|
36
|
+
def successful?
|
37
|
+
a = any_args? ? [:any] : @args
|
38
|
+
@recorder.was_called?(@method_name, @mode, *a)
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
data/lib/caricature.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
class Class
|
2
|
+
|
3
|
+
# removes all the modules from this class name
|
4
|
+
def demodulize
|
5
|
+
self.to_s.gsub(/^.*::/, '')
|
6
|
+
end
|
7
|
+
|
8
|
+
# indicates whether this type has a CLR type in its ancestors
|
9
|
+
def is_clr_type?
|
10
|
+
!self.to_clr_type.nil? ||
|
11
|
+
self.included_modules.any? {|mod| !mod.to_clr_type.nil? } ||
|
12
|
+
self.ancestors.reject {|mod| mod == Object }.any? { |mod| !mod.to_clr_type.nil? }
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/string'
|
2
|
+
require File.dirname(__FILE__) + '/system/string'
|
3
|
+
require File.dirname(__FILE__) + '/system/type'
|
4
|
+
require File.dirname(__FILE__) + '/class'
|
5
|
+
require File.dirname(__FILE__) + '/module'
|
6
|
+
require File.dirname(__FILE__) + '/object'
|
7
|
+
require File.dirname(__FILE__) + '/array'
|
8
|
+
require File.dirname(__FILE__) + '/hash'
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class Hash
|
2
|
+
|
3
|
+
# Destructively convert all keys which respond_to?(:to_sym) to symbols. Works recursively if given nested hashes.
|
4
|
+
def symbolize_keys!
|
5
|
+
each do |k,v|
|
6
|
+
sym = k.respond_to?(:to_sym) ? k.to_sym : k
|
7
|
+
self[sym] = Hash === v ? v.symbolize_keys! : v
|
8
|
+
delete(k) unless k == sym
|
9
|
+
end
|
10
|
+
self
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class Module
|
2
|
+
|
3
|
+
# Removes all but the containing modules from this module's name
|
4
|
+
def demodulize
|
5
|
+
self.to_s.gsub(/^.*::/, '')
|
6
|
+
end
|
7
|
+
|
8
|
+
# indicates whether this type has a CLR type in its ancestors
|
9
|
+
def is_clr_type?
|
10
|
+
!self.to_clr_type.nil? ||
|
11
|
+
self.included_modules.any? {|mod| !mod.to_clr_type.nil? } ||
|
12
|
+
self.ancestors.any? { |mod| !mod.to_clr_type.nil? }
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class Object
|
2
|
+
|
3
|
+
# returns whether this object is a clr_type.
|
4
|
+
# if it has a CLR type in one of its ancestors
|
5
|
+
def is_clr_type?
|
6
|
+
self.class.is_clr_type?
|
7
|
+
end
|
8
|
+
|
9
|
+
# returns the clr type of this object if any
|
10
|
+
def to_clr_type
|
11
|
+
self.class.to_clr_type
|
12
|
+
end
|
13
|
+
|
14
|
+
# defines a class method on an object
|
15
|
+
def define_cmethod(name, &blk)
|
16
|
+
(class << self; self; end).instance_eval { define_method name, &blk }
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class String
|
2
|
+
|
3
|
+
# converts a camel cased word to an underscored word
|
4
|
+
def underscore
|
5
|
+
self.gsub(/::/, '/').
|
6
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
7
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
8
|
+
tr("-", "_").
|
9
|
+
downcase
|
10
|
+
end
|
11
|
+
|
12
|
+
# Gets the constant when it is defined that corresponds to this string
|
13
|
+
def classify
|
14
|
+
Object.const_get self
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module System
|
2
|
+
|
3
|
+
class String
|
4
|
+
|
5
|
+
# converts a camel cased word to an underscored word
|
6
|
+
def underscore
|
7
|
+
self.gsub(/::/, '/').
|
8
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
9
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
10
|
+
tr("-", "_").
|
11
|
+
downcase
|
12
|
+
end
|
13
|
+
|
14
|
+
# Gets the constant when it is defined that corresponds to this string
|
15
|
+
def classify
|
16
|
+
Object.const_get self
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module System
|
2
|
+
|
3
|
+
class Type
|
4
|
+
|
5
|
+
# collects all the methods defined on an interface and its parents
|
6
|
+
def collect_interface_methods
|
7
|
+
iface_methods = []
|
8
|
+
iface_methods += self.get_interfaces.collect { |t| t.collect_interface_methods }
|
9
|
+
self.get_methods + iface_methods.flatten
|
10
|
+
end
|
11
|
+
|
12
|
+
# collects the properties defined on an interface an its parents
|
13
|
+
def collect_interface_properties
|
14
|
+
iface_properties = []
|
15
|
+
iface_properties += self.get_interfaces.collect { |t| t.collect_interface_properties }
|
16
|
+
self.get_properties + iface_properties.flatten
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
data/pkg/.gitignore
ADDED
File without changes
|
Binary file
|