minilab 1.0.0-mswin32
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/.document +2 -0
- data/CHANGES +2 -0
- data/LICENSE +19 -0
- data/README +107 -0
- data/Rakefile +145 -0
- data/config/environment.rb +15 -0
- data/config/objects.yml +22 -0
- data/lib/analog_io.rb +30 -0
- data/lib/digital_auxport_io.rb +49 -0
- data/lib/digital_configuration.rb +66 -0
- data/lib/digital_port_io.rb +68 -0
- data/lib/extension/extconf.rb +4 -0
- data/lib/extension/minilab_hardware.c +235 -0
- data/lib/extension/minilab_hardware.so +0 -0
- data/lib/library_translator.rb +48 -0
- data/lib/minilab.rb +149 -0
- data/lib/minilab_context.rb +39 -0
- data/lib/result_verifier.rb +14 -0
- data/test/integration/analog_input_output_test.rb +43 -0
- data/test/integration/connect_to_hardware_test.rb +13 -0
- data/test/integration/digital_input_output_test.rb +114 -0
- data/test/integration/integration_test.rb +53 -0
- data/test/integration/require_minilab_test.rb +9 -0
- data/test/system/analog_input.test +3 -0
- data/test/system/analog_output.test +37 -0
- data/test/system/digital_port_input.test +5 -0
- data/test/system/digital_port_output.test +39 -0
- data/test/system/digital_port_read_byte.test +26 -0
- data/test/system/digital_screw_terminals_input.test +2 -0
- data/test/system/digital_screw_terminals_output.test +11 -0
- data/test/system/minilab_driver.rb +85 -0
- data/test/test_helper.rb +11 -0
- data/test/unit/analog_io_test.rb +87 -0
- data/test/unit/digital_auxport_io_test.rb +114 -0
- data/test/unit/digital_configuration_test.rb +136 -0
- data/test/unit/digital_port_io_test.rb +117 -0
- data/test/unit/library_translator_test.rb +100 -0
- data/test/unit/minilab_context_test.rb +82 -0
- data/test/unit/minilab_hardware_test.rb +83 -0
- data/test/unit/minilab_test.rb +131 -0
- data/test/unit/result_verifier_test.rb +33 -0
- data/vendor/behaviors/lib/behaviors.rb +50 -0
- data/vendor/behaviors/tasks/behaviors_tasks.rake +140 -0
- data/vendor/behaviors/test/behaviors_tasks_test.rb +71 -0
- data/vendor/behaviors/test/behaviors_test.rb +50 -0
- data/vendor/behaviors/test/tasks_test/Rakefile +16 -0
- data/vendor/behaviors/test/tasks_test/doc/behaviors.html +55 -0
- data/vendor/behaviors/test/tasks_test/lib/user.rb +2 -0
- data/vendor/behaviors/test/tasks_test/test/user_test.rb +17 -0
- data/vendor/constructor/Rakefile +44 -0
- data/vendor/constructor/config/environment.rb +12 -0
- data/vendor/constructor/lib/constructor.rb +132 -0
- data/vendor/constructor/test/constructor_test.rb +366 -0
- data/vendor/constructor/test/helper.rb +3 -0
- data/vendor/diy/README +26 -0
- data/vendor/diy/Rakefile +18 -0
- data/vendor/diy/lib/constructor.rb +114 -0
- data/vendor/diy/lib/diy.rb +329 -0
- data/vendor/diy/proto/context.rb +117 -0
- data/vendor/diy/proto/context.yml +20 -0
- data/vendor/diy/test/diy_test.rb +370 -0
- data/vendor/diy/test/files/broken_construction.yml +7 -0
- data/vendor/diy/test/files/cat/cat.rb +4 -0
- data/vendor/diy/test/files/cat/extra_conflict.yml +5 -0
- data/vendor/diy/test/files/cat/heritage.rb +2 -0
- data/vendor/diy/test/files/cat/needs_input.yml +3 -0
- data/vendor/diy/test/files/cat/the_cat_lineage.rb +1 -0
- data/vendor/diy/test/files/dog/dog_model.rb +4 -0
- data/vendor/diy/test/files/dog/dog_presenter.rb +4 -0
- data/vendor/diy/test/files/dog/dog_view.rb +2 -0
- data/vendor/diy/test/files/dog/file_resolver.rb +2 -0
- data/vendor/diy/test/files/dog/other_thing.rb +2 -0
- data/vendor/diy/test/files/dog/simple.yml +11 -0
- data/vendor/diy/test/files/donkey/foo.rb +8 -0
- data/vendor/diy/test/files/donkey/foo/bar/qux.rb +7 -0
- data/vendor/diy/test/files/fud/objects.yml +13 -0
- data/vendor/diy/test/files/fud/toy.rb +15 -0
- data/vendor/diy/test/files/gnu/objects.yml +14 -0
- data/vendor/diy/test/files/gnu/thinger.rb +8 -0
- data/vendor/diy/test/files/goat/base.rb +8 -0
- data/vendor/diy/test/files/goat/can.rb +6 -0
- data/vendor/diy/test/files/goat/goat.rb +6 -0
- data/vendor/diy/test/files/goat/objects.yml +12 -0
- data/vendor/diy/test/files/goat/paper.rb +6 -0
- data/vendor/diy/test/files/goat/plane.rb +8 -0
- data/vendor/diy/test/files/goat/shirt.rb +6 -0
- data/vendor/diy/test/files/goat/wings.rb +8 -0
- data/vendor/diy/test/files/horse/holder_thing.rb +4 -0
- data/vendor/diy/test/files/horse/objects.yml +7 -0
- data/vendor/diy/test/files/yak/core_model.rb +4 -0
- data/vendor/diy/test/files/yak/core_presenter.rb +4 -0
- data/vendor/diy/test/files/yak/core_view.rb +1 -0
- data/vendor/diy/test/files/yak/data_source.rb +1 -0
- data/vendor/diy/test/files/yak/fringe_model.rb +4 -0
- data/vendor/diy/test/files/yak/fringe_presenter.rb +4 -0
- data/vendor/diy/test/files/yak/fringe_view.rb +1 -0
- data/vendor/diy/test/files/yak/my_objects.yml +21 -0
- data/vendor/diy/test/test_helper.rb +40 -0
- data/vendor/hardmock/CHANGES +8 -0
- data/vendor/hardmock/LICENSE +7 -0
- data/vendor/hardmock/README +48 -0
- data/vendor/hardmock/Rakefile +100 -0
- data/vendor/hardmock/TODO +7 -0
- data/vendor/hardmock/config/environment.rb +12 -0
- data/vendor/hardmock/homepage/demo.rb +21 -0
- data/vendor/hardmock/homepage/hardmock_sample.png +0 -0
- data/vendor/hardmock/homepage/index.html +65 -0
- data/vendor/hardmock/init.rb +3 -0
- data/vendor/hardmock/lib/hardmock.rb +634 -0
- data/vendor/hardmock/lib/method_cleanout.rb +14 -0
- data/vendor/hardmock/rcov.rake +18 -0
- data/vendor/hardmock/test/functional/assert_error_test.rb +52 -0
- data/vendor/hardmock/test/functional/auto_verify_test.rb +192 -0
- data/vendor/hardmock/test/functional/direct_mock_usage_test.rb +396 -0
- data/vendor/hardmock/test/functional/hardmock_test.rb +380 -0
- data/vendor/hardmock/test/test_helper.rb +23 -0
- data/vendor/hardmock/test/unit/expectation_builder_test.rb +18 -0
- data/vendor/hardmock/test/unit/expector_test.rb +56 -0
- data/vendor/hardmock/test/unit/method_cleanout_test.rb +35 -0
- data/vendor/hardmock/test/unit/mock_control_test.rb +172 -0
- data/vendor/hardmock/test/unit/mock_test.rb +273 -0
- data/vendor/hardmock/test/unit/simple_expectation_test.rb +345 -0
- data/vendor/hardmock/test/unit/trapper_test.rb +60 -0
- data/vendor/hardmock/test/unit/verify_error_test.rb +34 -0
- data/vendor/systir/systir.rb +403 -0
- data/vendor/systir/test/unit/ui/xml/testrunner.rb +192 -0
- data/vendor/systir/test/unit/ui/xml/xmltestrunner.xslt +109 -0
- metadata +235 -0
Binary file
|
@@ -0,0 +1,65 @@
|
|
1
|
+
<html>
|
2
|
+
<head>
|
3
|
+
<title>Hardmock</title>
|
4
|
+
<style>
|
5
|
+
html
|
6
|
+
{
|
7
|
+
text-align: center;
|
8
|
+
}
|
9
|
+
|
10
|
+
body
|
11
|
+
{
|
12
|
+
margin: 10px auto;
|
13
|
+
width: 750px;
|
14
|
+
text-align: left;
|
15
|
+
font-family: "Lucida Grande", verdana, arial, helvetica, sans-serif;
|
16
|
+
font-size: 14px;
|
17
|
+
}
|
18
|
+
|
19
|
+
.title
|
20
|
+
{
|
21
|
+
background-color: #99FFB3;
|
22
|
+
border: 1px solid;
|
23
|
+
border-color: #ccc;
|
24
|
+
text-align: center;
|
25
|
+
padding: 10px;
|
26
|
+
}
|
27
|
+
|
28
|
+
.big
|
29
|
+
{
|
30
|
+
font-size: 170%;
|
31
|
+
font-weight: bold;
|
32
|
+
}
|
33
|
+
|
34
|
+
pre
|
35
|
+
{
|
36
|
+
border: 1px solid #ddd;
|
37
|
+
padding: 5px;
|
38
|
+
}
|
39
|
+
|
40
|
+
img
|
41
|
+
{
|
42
|
+
border: none;
|
43
|
+
}
|
44
|
+
</style>
|
45
|
+
</head>
|
46
|
+
<body>
|
47
|
+
<div id="content">
|
48
|
+
<div class="title"><span class="big">Hardmock =></span> Strict, ordered mock objects.</div>
|
49
|
+
|
50
|
+
<h3>Install</h3>
|
51
|
+
<p>
|
52
|
+
<pre>$ script/plugin install svn://rubyforge.org/var/svn/hardmock/tags/hardmock</pre>
|
53
|
+
</p>
|
54
|
+
<p>(Also available on <a href="http://rubyforge.org/frs/?group_id=2742">Rubyforge</a> as Gem, Tgz and Zip)</p>
|
55
|
+
|
56
|
+
<h3>Use</h3>
|
57
|
+
<p align="center">
|
58
|
+
<a class="codelink" href="/doc/">
|
59
|
+
<img src="hardmock_sample.png"/>
|
60
|
+
</a>
|
61
|
+
</p>
|
62
|
+
|
63
|
+
</div>
|
64
|
+
</body>
|
65
|
+
</html>
|
@@ -0,0 +1,634 @@
|
|
1
|
+
require 'method_cleanout'
|
2
|
+
|
3
|
+
module Hardmock
|
4
|
+
|
5
|
+
# Setup auto mock verification on teardown, being careful not to interfere
|
6
|
+
# with inherited, pre-mixed or post-added user teardowns.
|
7
|
+
def self.included(base) #:nodoc:#
|
8
|
+
base.class_eval do
|
9
|
+
# Core of our actual teardown behavior
|
10
|
+
def hardmock_teardown
|
11
|
+
verify_mocks
|
12
|
+
end
|
13
|
+
|
14
|
+
# disable until later:
|
15
|
+
def self.method_added(symbol) #:nodoc:
|
16
|
+
end
|
17
|
+
|
18
|
+
if method_defined?(:teardown) then
|
19
|
+
# Wrap existing teardown
|
20
|
+
alias_method :old_teardown, :teardown
|
21
|
+
define_method(:new_teardown) do
|
22
|
+
begin
|
23
|
+
hardmock_teardown
|
24
|
+
ensure
|
25
|
+
old_teardown
|
26
|
+
end
|
27
|
+
end
|
28
|
+
else
|
29
|
+
# We don't need to account for previous teardown
|
30
|
+
define_method(:new_teardown) do
|
31
|
+
hardmock_teardown
|
32
|
+
end
|
33
|
+
end
|
34
|
+
alias_method :teardown, :new_teardown
|
35
|
+
|
36
|
+
def self.method_added(method) #:nodoc:
|
37
|
+
case method
|
38
|
+
when :teardown
|
39
|
+
unless method_defined?(:user_teardown)
|
40
|
+
alias_method :user_teardown, :teardown
|
41
|
+
define_method(:teardown) do
|
42
|
+
begin
|
43
|
+
new_teardown
|
44
|
+
ensure
|
45
|
+
user_teardown
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Create one or more new Mock instances in your test suite.
|
55
|
+
# Once created, the Mocks are accessible as instance variables in your test.
|
56
|
+
# Newly built Mocks are added to the full set of Mocks for this test, which will
|
57
|
+
# be verified when you call verify_mocks.
|
58
|
+
#
|
59
|
+
# create_mocks :donkey, :cat # Your test now has @donkey and @cat
|
60
|
+
# create_mock :dog # Test now has @donkey, @cat and @dog
|
61
|
+
#
|
62
|
+
# The first call returned a hash { :donkey => @donkey, :cat => @cat }
|
63
|
+
# and the second call returned { :dog => @dog }
|
64
|
+
#
|
65
|
+
# For more info on how to use your mocks, see Mock and SimpleExpectation
|
66
|
+
#
|
67
|
+
def create_mocks(*mock_names)
|
68
|
+
@main_mock_control ||= MockControl.new
|
69
|
+
|
70
|
+
mocks = {}
|
71
|
+
mock_names.each do |mock_name|
|
72
|
+
mock_name = mock_name.to_s
|
73
|
+
mock_object = Mock.new(mock_name, @main_mock_control)
|
74
|
+
mocks[mock_name.to_sym] = mock_object
|
75
|
+
self.instance_variable_set "@#{mock_name}", mock_object
|
76
|
+
end
|
77
|
+
@all_mocks ||= {}
|
78
|
+
@all_mocks.merge! mocks
|
79
|
+
|
80
|
+
return mocks.clone
|
81
|
+
end
|
82
|
+
|
83
|
+
alias :create_mock :create_mocks
|
84
|
+
|
85
|
+
# Ensures that all expectations have been met. If not, VerifyException is
|
86
|
+
# raised.
|
87
|
+
#
|
88
|
+
# <b>You normally won't need to call this yourself.</b> Within Test::Unit::TestCase, this will be done automatically at teardown time.
|
89
|
+
#
|
90
|
+
# * +force+ -- if +false+, and a VerifyError or ExpectationError has already occurred, this method will not raise. This is to help you suppress repeated errors when if you're calling #verify_mocks in the teardown method of your test suite. BE WARNED - only use this if you're sure you aren't obscuring useful information. Eg, if your code handles exceptions internally, and an ExpectationError gets gobbled up by your +rescue+ block, the cause of failure for your test may be hidden from you. For this reason, #verify_mocks defaults to force=true as of Hardmock 1.0.1
|
91
|
+
def verify_mocks(force=true)
|
92
|
+
return unless @main_mock_control
|
93
|
+
return if @main_mock_control.disappointed? and !force
|
94
|
+
@main_mock_control.verify
|
95
|
+
end
|
96
|
+
|
97
|
+
module Utils #:nodoc:
|
98
|
+
def fmt_call(mock,mname,args)
|
99
|
+
arg_string = args.map { |a| a.inspect }.join(', ')
|
100
|
+
call_text = "#{mock._name}.#{mname}(#{arg_string})"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# Mock is used to set expectations in your test. Most of the time you'll use
|
105
|
+
# <tt>#expects</tt> to create expectations.
|
106
|
+
#
|
107
|
+
# Aside from the scant few control methods (like +expects+, +trap+ and +_verify+)
|
108
|
+
# all calls made on a Mock instance will be immediately applied to the internal
|
109
|
+
# expectation mechanism.
|
110
|
+
#
|
111
|
+
# * If the method call was expected and all the parameters match properly, execution continues
|
112
|
+
# * If the expectation was configured with an expectation block, the block is invoked
|
113
|
+
# * If the expectation was set up to raise an error, the error is raised now
|
114
|
+
# * If the expectation was set up to return a value, it is returned
|
115
|
+
# * If the method call was _not_ expected, or the parameter values are wrong, an ExpectationError is raised.
|
116
|
+
class Mock
|
117
|
+
include MethodCleanout
|
118
|
+
|
119
|
+
# Create a new Mock instance with a name and a MockControl to support it.
|
120
|
+
# If not given, a MockControl is made implicitly for this Mock alone; this means
|
121
|
+
# expectations for this mock are not tied to other expectations in your test.
|
122
|
+
#
|
123
|
+
# It's not recommended to use a Mock directly; see Hardmock and
|
124
|
+
# Hardmock#create_mocks for the more wholistic approach.
|
125
|
+
def initialize(name, mock_control=nil)
|
126
|
+
@name = name
|
127
|
+
@control = mock_control || MockControl.new
|
128
|
+
@expectation_builder = ExpectationBuilder.new
|
129
|
+
end
|
130
|
+
|
131
|
+
# Begin declaring an expectation for this Mock.
|
132
|
+
#
|
133
|
+
# == Simple Examples
|
134
|
+
# Expect the +customer+ to be queried for +account+, and return <tt>"The
|
135
|
+
# Account"</tt>:
|
136
|
+
# @customer.expects.account.returns "The Account"
|
137
|
+
#
|
138
|
+
# Expect the +withdraw+ method to be called, and raise an exception when it
|
139
|
+
# is (see SimpleExpectation#raises for more info):
|
140
|
+
# @cash_machine.expects.withdraw(20,:dollars).raises("not enough money")
|
141
|
+
#
|
142
|
+
# Expect +customer+ to have its +user_name+ set
|
143
|
+
# @customer.expects.user_name = 'Big Boss'
|
144
|
+
#
|
145
|
+
# Expect +customer+ to have its +user_name+ set, and raise a RuntimeException when
|
146
|
+
# that happens:
|
147
|
+
# @customer.expects('user_name=', "Big Boss").raises "lost connection"
|
148
|
+
#
|
149
|
+
# Expect +evaluate+ to be passed a block, and when that happens, pass a value
|
150
|
+
# to the block (see SimpleExpectation#yields for more info):
|
151
|
+
# @cruncher.expects.evaluate.yields("some data").returns("some results")
|
152
|
+
#
|
153
|
+
#
|
154
|
+
# == Expectation Blocks
|
155
|
+
# To do special handling of expected method calls when they occur, you
|
156
|
+
# may pass a block to your expectation, like:
|
157
|
+
# @page_scraper.expects.handle_content do |address,request,status|
|
158
|
+
# assert_not_nil address, "Can't abide nil addresses"
|
159
|
+
# assert_equal "http-get", request.method, "Can only handle GET"
|
160
|
+
# assert status > 200 and status < 300, status, "Failed status"
|
161
|
+
# "Simulated results #{request.content.downcase}"
|
162
|
+
# end
|
163
|
+
# In this example, when <tt>page_scraper.handle_content</tt> is called, its
|
164
|
+
# three arguments are passed to the <i>expectation block</i> and evaluated
|
165
|
+
# using the above assertions. The last value in the block will be used
|
166
|
+
# as the return value for +handle_content+
|
167
|
+
#
|
168
|
+
# You may specify arguments to the expected method call, just like any normal
|
169
|
+
# expectation, and those arguments will be pre-validated before being passed
|
170
|
+
# to the expectation block. This is useful when you know all of the
|
171
|
+
# expected values but still need to do something programmatic.
|
172
|
+
#
|
173
|
+
# If the method being invoked on the mock accepts a block, that block will be
|
174
|
+
# passed to your expectation block as the last (or only) argument. Eg, the
|
175
|
+
# convenience method +yields+ can be replaced with the more explicit:
|
176
|
+
# @cruncher.expects.evaluate do |block|
|
177
|
+
# block.call "some data"
|
178
|
+
# "some results"
|
179
|
+
# end
|
180
|
+
#
|
181
|
+
# The result value of the expectation block becomes the return value for the
|
182
|
+
# expected method call. This can be overidden by using the +returns+ method:
|
183
|
+
# @cruncher.expects.evaluate do |block|
|
184
|
+
# block.call "some data"
|
185
|
+
# "some results"
|
186
|
+
# end.returns("the actual value")
|
187
|
+
#
|
188
|
+
# <b>Additionally</b>, the resulting value of the expectation block is stored
|
189
|
+
# in the +block_value+ field on the expectation. If you've saved a reference
|
190
|
+
# to your expectation, you may retrieve the block value once the expectation
|
191
|
+
# has been met.
|
192
|
+
#
|
193
|
+
# evaluation_event = @cruncher.expects.evaluate do |block|
|
194
|
+
# block.call "some data"
|
195
|
+
# "some results"
|
196
|
+
# end.returns("the actual value")
|
197
|
+
#
|
198
|
+
# result = @cruncher.evaluate do |input|
|
199
|
+
# puts input # => 'some data'
|
200
|
+
# end
|
201
|
+
# # result is 'the actual value'
|
202
|
+
#
|
203
|
+
# evaluation_event.block_value # => 'some results'
|
204
|
+
#
|
205
|
+
def expects(*args, &block)
|
206
|
+
expector = Expector.new(self,@control,@expectation_builder)
|
207
|
+
# If there are no args, we return the Expector
|
208
|
+
return expector if args.empty?
|
209
|
+
# If there ARE args, we set up the expectation right here and return it
|
210
|
+
expector.send(args.shift.to_sym, *args, &block)
|
211
|
+
end
|
212
|
+
alias_method :expect, :expects
|
213
|
+
# def expect(*args, &block) #:nodoc:
|
214
|
+
# raise DeprecationError.new("Please use 'expects' instead of 'expect'. Sorry about the inconvenience.")
|
215
|
+
# end
|
216
|
+
|
217
|
+
# Special-case convenience: #trap sets up an expectation for a method
|
218
|
+
# that will take a block. That block, when sent to the expected method, will
|
219
|
+
# be trapped and stored in the expectation's +block_value+ field.
|
220
|
+
# The SimpleExpectation#trigger method may then be used to invoke that block.
|
221
|
+
#
|
222
|
+
# Like +expects+, the +trap+ mechanism can be followed by +raises+ or +returns+.
|
223
|
+
#
|
224
|
+
# _Unlike_ +expects+, you may not use an expectation block with +trap+. If
|
225
|
+
# the expected method takes arguments in addition to the block, they must
|
226
|
+
# be specified in the arguments to the +trap+ call itself.
|
227
|
+
#
|
228
|
+
# == Example
|
229
|
+
#
|
230
|
+
# create_mocks :address_book, :editor_form
|
231
|
+
#
|
232
|
+
# # Expect a subscription on the :person_added event for @address_book:
|
233
|
+
# person_event = @address_book.trap.subscribe(:person_added)
|
234
|
+
#
|
235
|
+
# # The runtime code would look like:
|
236
|
+
# @address_book.subscribe :person_added do |person_name|
|
237
|
+
# @editor_form.name = person_name
|
238
|
+
# end
|
239
|
+
#
|
240
|
+
# # At this point, the expectation for 'subscribe' is met and the
|
241
|
+
# # block has been captured. But we're not done:
|
242
|
+
# @editor_form.expects.name = "David"
|
243
|
+
#
|
244
|
+
# # Now invoke the block we trapped earlier:
|
245
|
+
# person_event.trigger "David"
|
246
|
+
#
|
247
|
+
# verify_mocks
|
248
|
+
def trap(*args)
|
249
|
+
Trapper.new(self,@control,ExpectationBuilder.new)
|
250
|
+
end
|
251
|
+
|
252
|
+
def method_missing(mname,*args) #:nodoc:
|
253
|
+
block = nil
|
254
|
+
block = Proc.new if block_given?
|
255
|
+
@control.apply_method_call(self,mname,args,block)
|
256
|
+
end
|
257
|
+
|
258
|
+
|
259
|
+
def _control #:nodoc:
|
260
|
+
@control
|
261
|
+
end
|
262
|
+
|
263
|
+
def _name #:nodoc:
|
264
|
+
@name
|
265
|
+
end
|
266
|
+
|
267
|
+
# Verify that all expectations are fulfilled. NOTE: this method triggers
|
268
|
+
# validation on the _control_ for this mock, so all Mocks that share the
|
269
|
+
# MockControl with this instance will be included in the verification.
|
270
|
+
#
|
271
|
+
# <b>Only use this method if you are managing your own Mocks and their controls.</b>
|
272
|
+
#
|
273
|
+
# Normal usage of Hardmock doesn't require you to call this; let
|
274
|
+
# Hardmock#verify_mocks do it for you.
|
275
|
+
def _verify
|
276
|
+
@control.verify
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
class Expector #:nodoc:
|
281
|
+
include MethodCleanout
|
282
|
+
|
283
|
+
def initialize(mock,mock_control,expectation_builder)
|
284
|
+
@mock = mock
|
285
|
+
@mock_control = mock_control
|
286
|
+
@expectation_builder = expectation_builder
|
287
|
+
end
|
288
|
+
|
289
|
+
def method_missing(mname, *args, &block)
|
290
|
+
expectation = @expectation_builder.build_expectation(
|
291
|
+
:mock => @mock,
|
292
|
+
:method => mname,
|
293
|
+
:arguments => args,
|
294
|
+
:block => block)
|
295
|
+
|
296
|
+
@mock_control.add_expectation expectation
|
297
|
+
expectation
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
class Trapper #:nodoc:
|
302
|
+
include MethodCleanout
|
303
|
+
|
304
|
+
def initialize(mock,mock_control,expectation_builder)
|
305
|
+
@mock = mock
|
306
|
+
@mock_control = mock_control
|
307
|
+
@expectation_builder = expectation_builder
|
308
|
+
end
|
309
|
+
|
310
|
+
def method_missing(mname, *args)
|
311
|
+
if block_given?
|
312
|
+
raise ExpectationError.new("Don't pass blocks when using 'trap' (setting exepectations for '#{mname}')")
|
313
|
+
end
|
314
|
+
|
315
|
+
the_block = lambda { |target_block| target_block }
|
316
|
+
expectation = @expectation_builder.build_expectation(
|
317
|
+
:mock => @mock,
|
318
|
+
:method => mname,
|
319
|
+
:arguments => args,
|
320
|
+
:suppress_arguments_to_block => true,
|
321
|
+
:block => the_block)
|
322
|
+
|
323
|
+
@mock_control.add_expectation expectation
|
324
|
+
expectation
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
class ExpectationBuilder #:nodoc:
|
329
|
+
def build_expectation(options)
|
330
|
+
SimpleExpectation.new(options)
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
class SimpleExpectation
|
335
|
+
include Utils
|
336
|
+
attr_reader :block_value
|
337
|
+
|
338
|
+
def initialize(options) #:nodoc:
|
339
|
+
@options = options
|
340
|
+
end
|
341
|
+
|
342
|
+
def apply_method_call(mock,mname,args,block) #:nodoc:
|
343
|
+
unless @options[:mock].equal?(mock)
|
344
|
+
raise anger("Wrong object", mock,mname,args)
|
345
|
+
end
|
346
|
+
unless @options[:method] == mname
|
347
|
+
raise anger("Wrong method",mock,mname,args)
|
348
|
+
end
|
349
|
+
|
350
|
+
# Tester-defined block to invoke at method-call-time:
|
351
|
+
expectation_block = @options[:block]
|
352
|
+
|
353
|
+
expected_args = @options[:arguments]
|
354
|
+
# if we have a block, we can skip the argument check if none were specified
|
355
|
+
unless (expected_args.nil? || expected_args.empty?) && expectation_block && !@options[:suppress_arguments_to_block]
|
356
|
+
unless expected_args == args
|
357
|
+
raise anger("Wrong arguments",mock,mname,args)
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
relayed_args = args.dup
|
362
|
+
if block
|
363
|
+
if expectation_block.nil?
|
364
|
+
# Can't handle a runtime block without an expectation block
|
365
|
+
raise ExpectationError.new("Unexpected block provided to #{to_s}")
|
366
|
+
else
|
367
|
+
# Runtime blocks are passed as final argument to the expectation block
|
368
|
+
unless @options[:suppress_arguments_to_block]
|
369
|
+
relayed_args << block
|
370
|
+
else
|
371
|
+
# Arguments suppressed; send only the block
|
372
|
+
relayed_args = [block]
|
373
|
+
end
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
# Run the expectation block:
|
378
|
+
@block_value = expectation_block.call(*relayed_args) if expectation_block
|
379
|
+
|
380
|
+
raise @options[:raises] unless @options[:raises].nil?
|
381
|
+
|
382
|
+
return_value = @options[:returns]
|
383
|
+
if return_value.nil?
|
384
|
+
return @block_value
|
385
|
+
else
|
386
|
+
return return_value
|
387
|
+
end
|
388
|
+
end
|
389
|
+
|
390
|
+
# Set the return value for an expected method call.
|
391
|
+
# Eg,
|
392
|
+
# @cash_machine.expects.withdraw(20,:dollars).returns(20.00)
|
393
|
+
def returns(val)
|
394
|
+
@options[:returns] = val
|
395
|
+
self
|
396
|
+
end
|
397
|
+
|
398
|
+
# Rig an expected method to raise an exception when the mock is invoked.
|
399
|
+
#
|
400
|
+
# Eg,
|
401
|
+
# @cash_machine.expects.withdraw(20,:dollars).raises "Insufficient funds"
|
402
|
+
#
|
403
|
+
# The argument can be:
|
404
|
+
# * an Exception -- will be used directly
|
405
|
+
# * a String -- will be used as the message for a RuntimeError
|
406
|
+
# * nothing -- RuntimeError.new("An Error") will be raised
|
407
|
+
def raises(err=nil)
|
408
|
+
case err
|
409
|
+
when Exception
|
410
|
+
@options[:raises] = err
|
411
|
+
when String
|
412
|
+
@options[:raises] = RuntimeError.new(err)
|
413
|
+
else
|
414
|
+
@options[:raises] = RuntimeError.new("An Error")
|
415
|
+
end
|
416
|
+
self
|
417
|
+
end
|
418
|
+
|
419
|
+
# Convenience method: assumes +block_value+ is set, and is set to a Proc
|
420
|
+
# (or anything that responds to 'call')
|
421
|
+
#
|
422
|
+
# light_event = @traffic_light.trap.subscribe(:light_changes)
|
423
|
+
#
|
424
|
+
# # This code will meet the expectation:
|
425
|
+
# @traffic_light.subscribe :light_changes do |color|
|
426
|
+
# puts color
|
427
|
+
# end
|
428
|
+
#
|
429
|
+
# The color-handling block is now stored in <tt>light_event.block_value</tt>
|
430
|
+
#
|
431
|
+
# The block can be invoked like this:
|
432
|
+
#
|
433
|
+
# light_event.trigger :red
|
434
|
+
#
|
435
|
+
# See Mock#trap and Mock#expects for information on using expectation objects
|
436
|
+
# after they are set.
|
437
|
+
#
|
438
|
+
def trigger(*block_arguments)
|
439
|
+
unless block_value
|
440
|
+
raise ExpectationError.new("No block value is currently set for expectation #{to_s}")
|
441
|
+
end
|
442
|
+
unless block_value.respond_to?(:call)
|
443
|
+
raise ExpectationError.new("Can't apply trigger to #{block_value} for expectation #{to_s}")
|
444
|
+
end
|
445
|
+
block_value.call *block_arguments
|
446
|
+
end
|
447
|
+
|
448
|
+
# Used when an expected method accepts a block at runtime.
|
449
|
+
# When the expected method is invoked, the block passed to
|
450
|
+
# that method will be invoked as well.
|
451
|
+
#
|
452
|
+
# NOTE: ExpectationError will be thrown upon running the expected method
|
453
|
+
# if the arguments you set up in +yields+ do not properly match up with
|
454
|
+
# the actual block that ends up getting passed.
|
455
|
+
#
|
456
|
+
# == Examples
|
457
|
+
# <b>Single invocation</b>: The block passed to +lock_down+ gets invoked
|
458
|
+
# once with no arguments:
|
459
|
+
#
|
460
|
+
# @safe_zone.expects.lock_down.yields
|
461
|
+
#
|
462
|
+
# # (works on code that looks like:)
|
463
|
+
# @safe_zone.lock_down do
|
464
|
+
# # ... this block invoked once
|
465
|
+
# end
|
466
|
+
#
|
467
|
+
# <b>Multi-parameter blocks:</b> The block passed to +each_item+ gets
|
468
|
+
# invoked twice, with <tt>:item1</tt> the first time, and with
|
469
|
+
# <tt>:item2</tt> the second time:
|
470
|
+
#
|
471
|
+
# @fruit_basket.expects.each_with_index.yields [:apple,1], [:orange,2]
|
472
|
+
#
|
473
|
+
# # (works on code that looks like:)
|
474
|
+
# @fruit_basket.each_with_index do |fruit,index|
|
475
|
+
# # ... this block invoked with fruit=:apple, index=1,
|
476
|
+
# # ... and then with fruit=:orange, index=2
|
477
|
+
# end
|
478
|
+
#
|
479
|
+
# <b>Arrays can be passed as arguments too</b>... if the block
|
480
|
+
# takes a single argument and you want to pass a series of arrays into it,
|
481
|
+
# that will work as well:
|
482
|
+
#
|
483
|
+
# @list_provider.expects.each_list.yields [1,2,3], [4,5,6]
|
484
|
+
#
|
485
|
+
# # (works on code that looks like:)
|
486
|
+
# @list_provider.each_list do |list|
|
487
|
+
# # ... list is [1,2,3] the first time
|
488
|
+
# # ... list is [4,5,6] the second time
|
489
|
+
# end
|
490
|
+
#
|
491
|
+
# <b>Return value</b>: You can set the return value for the method that
|
492
|
+
# accepts the block like so:
|
493
|
+
#
|
494
|
+
# @cruncher.expects.do_things.yields(:bean1,:bean2).returns("The Results")
|
495
|
+
#
|
496
|
+
# <b>Raising errors</b>: You can set the raised exception for the method that
|
497
|
+
# accepts the block. NOTE: the error will be raised _after_ the block has
|
498
|
+
# been invoked.
|
499
|
+
#
|
500
|
+
# # :bean1 and :bean2 will be passed to the block, then an error is raised:
|
501
|
+
# @cruncher.expects.do_things.yields(:bean1,:bean2).raises("Too crunchy")
|
502
|
+
#
|
503
|
+
def yields(*items)
|
504
|
+
@options[:suppress_arguments_to_block] = true
|
505
|
+
if items.empty?
|
506
|
+
# Yield once
|
507
|
+
@options[:block] = lambda do |block|
|
508
|
+
if block.arity != 0 and block.arity != -1
|
509
|
+
raise ExpectationError.new("Can't pass #{item.inspect} to block with arity #{block.arity} to <#{to_s}>")
|
510
|
+
end
|
511
|
+
block.call
|
512
|
+
end
|
513
|
+
else
|
514
|
+
# Yield one or more specific items
|
515
|
+
@options[:block] = lambda do |block|
|
516
|
+
items.each do |item|
|
517
|
+
if item.kind_of?(Array)
|
518
|
+
if block.arity == item.size
|
519
|
+
# Unfold the array into the block's arguments:
|
520
|
+
block.call *item
|
521
|
+
elsif block.arity == 1
|
522
|
+
# Just pass the array in
|
523
|
+
block.call item
|
524
|
+
else
|
525
|
+
# Size mismatch
|
526
|
+
raise ExpectationError.new("Can't pass #{item.inspect} to block with arity #{block.arity} to <#{to_s}>")
|
527
|
+
end
|
528
|
+
else
|
529
|
+
if block.arity != 1
|
530
|
+
# Size mismatch
|
531
|
+
raise ExpectationError.new("Can't pass #{item.inspect} to block with arity #{block.arity} to <#{to_s}>")
|
532
|
+
end
|
533
|
+
block.call item
|
534
|
+
end
|
535
|
+
end
|
536
|
+
end
|
537
|
+
end
|
538
|
+
self
|
539
|
+
end
|
540
|
+
|
541
|
+
def to_s # :nodoc:
|
542
|
+
fmt_call(@options[:mock],@options[:method],@options[:arguments])
|
543
|
+
end
|
544
|
+
|
545
|
+
private
|
546
|
+
def anger(msg, mock,mname,args)
|
547
|
+
ExpectationError.new("#{msg}: expected call <#{to_s}> but was <#{fmt_call(mock,mname,args)}>")
|
548
|
+
end
|
549
|
+
end
|
550
|
+
|
551
|
+
class MockControl #:nodoc:
|
552
|
+
include Utils
|
553
|
+
attr_accessor :name
|
554
|
+
|
555
|
+
def initialize
|
556
|
+
@expectations = []
|
557
|
+
@disappointed = false
|
558
|
+
end
|
559
|
+
|
560
|
+
def happy?
|
561
|
+
@expectations.empty?
|
562
|
+
end
|
563
|
+
|
564
|
+
def disappointed?
|
565
|
+
@disappointed
|
566
|
+
end
|
567
|
+
|
568
|
+
def add_expectation(expectation)
|
569
|
+
@expectations << expectation
|
570
|
+
end
|
571
|
+
|
572
|
+
def apply_method_call(mock,mname,args,block)
|
573
|
+
# Are we even expecting any sort of call?
|
574
|
+
if happy?
|
575
|
+
@disappointed = true
|
576
|
+
raise ExpectationError.new("Surprise call to #{fmt_call(mock,mname,args)}")
|
577
|
+
end
|
578
|
+
|
579
|
+
begin
|
580
|
+
@expectations.shift.apply_method_call(mock,mname,args,block)
|
581
|
+
rescue Exception => ouch
|
582
|
+
@disappointed = true
|
583
|
+
raise ouch
|
584
|
+
end
|
585
|
+
end
|
586
|
+
|
587
|
+
def verify
|
588
|
+
@disappointed = !happy?
|
589
|
+
raise VerifyError.new("Unmet expectations", @expectations) unless happy?
|
590
|
+
end
|
591
|
+
end
|
592
|
+
|
593
|
+
# Raised when:
|
594
|
+
# * Unexpected method is called on a mock object
|
595
|
+
# * Bad arguments passed to an expected call
|
596
|
+
class ExpectationError < StandardError; end
|
597
|
+
|
598
|
+
# Raised for methods that should no longer be called. Hopefully, the exception message contains helpful alternatives.
|
599
|
+
class DeprecationError < StandardError; end
|
600
|
+
|
601
|
+
# Raised when it is discovered that an expected method call was never made.
|
602
|
+
class VerifyError < StandardError
|
603
|
+
def initialize(msg,unmet_expectations)
|
604
|
+
super("#{msg}:" + unmet_expectations.map { |ex| "\n * #{ex.to_s}" }.join)
|
605
|
+
end
|
606
|
+
end
|
607
|
+
|
608
|
+
# A better 'assert_raise'. +patterns+ can be one or more Regexps, or a literal String that
|
609
|
+
# must match the entire error message.
|
610
|
+
def assert_error(err_type,*patterns,&block)
|
611
|
+
assert_not_nil block, "assert_error requires a block"
|
612
|
+
assert((err_type and err_type.kind_of?(Class)), "First argument to assert_error has to be an error type")
|
613
|
+
err = assert_raise(err_type) do
|
614
|
+
block.call
|
615
|
+
end
|
616
|
+
patterns.each do |pattern|
|
617
|
+
case pattern
|
618
|
+
when Regexp
|
619
|
+
assert_match(pattern, err.message)
|
620
|
+
else
|
621
|
+
assert_equal pattern, err.message
|
622
|
+
end
|
623
|
+
end
|
624
|
+
end
|
625
|
+
|
626
|
+
end
|
627
|
+
|
628
|
+
# Insert Hardmock functionality into the TestCase base class
|
629
|
+
require 'test/unit/testcase'
|
630
|
+
unless Test::Unit::TestCase.instance_methods.include?('hardmock_teardown')
|
631
|
+
class Test::Unit::TestCase
|
632
|
+
include Hardmock
|
633
|
+
end
|
634
|
+
end
|