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.
Files changed (128) hide show
  1. data/.document +2 -0
  2. data/CHANGES +2 -0
  3. data/LICENSE +19 -0
  4. data/README +107 -0
  5. data/Rakefile +145 -0
  6. data/config/environment.rb +15 -0
  7. data/config/objects.yml +22 -0
  8. data/lib/analog_io.rb +30 -0
  9. data/lib/digital_auxport_io.rb +49 -0
  10. data/lib/digital_configuration.rb +66 -0
  11. data/lib/digital_port_io.rb +68 -0
  12. data/lib/extension/extconf.rb +4 -0
  13. data/lib/extension/minilab_hardware.c +235 -0
  14. data/lib/extension/minilab_hardware.so +0 -0
  15. data/lib/library_translator.rb +48 -0
  16. data/lib/minilab.rb +149 -0
  17. data/lib/minilab_context.rb +39 -0
  18. data/lib/result_verifier.rb +14 -0
  19. data/test/integration/analog_input_output_test.rb +43 -0
  20. data/test/integration/connect_to_hardware_test.rb +13 -0
  21. data/test/integration/digital_input_output_test.rb +114 -0
  22. data/test/integration/integration_test.rb +53 -0
  23. data/test/integration/require_minilab_test.rb +9 -0
  24. data/test/system/analog_input.test +3 -0
  25. data/test/system/analog_output.test +37 -0
  26. data/test/system/digital_port_input.test +5 -0
  27. data/test/system/digital_port_output.test +39 -0
  28. data/test/system/digital_port_read_byte.test +26 -0
  29. data/test/system/digital_screw_terminals_input.test +2 -0
  30. data/test/system/digital_screw_terminals_output.test +11 -0
  31. data/test/system/minilab_driver.rb +85 -0
  32. data/test/test_helper.rb +11 -0
  33. data/test/unit/analog_io_test.rb +87 -0
  34. data/test/unit/digital_auxport_io_test.rb +114 -0
  35. data/test/unit/digital_configuration_test.rb +136 -0
  36. data/test/unit/digital_port_io_test.rb +117 -0
  37. data/test/unit/library_translator_test.rb +100 -0
  38. data/test/unit/minilab_context_test.rb +82 -0
  39. data/test/unit/minilab_hardware_test.rb +83 -0
  40. data/test/unit/minilab_test.rb +131 -0
  41. data/test/unit/result_verifier_test.rb +33 -0
  42. data/vendor/behaviors/lib/behaviors.rb +50 -0
  43. data/vendor/behaviors/tasks/behaviors_tasks.rake +140 -0
  44. data/vendor/behaviors/test/behaviors_tasks_test.rb +71 -0
  45. data/vendor/behaviors/test/behaviors_test.rb +50 -0
  46. data/vendor/behaviors/test/tasks_test/Rakefile +16 -0
  47. data/vendor/behaviors/test/tasks_test/doc/behaviors.html +55 -0
  48. data/vendor/behaviors/test/tasks_test/lib/user.rb +2 -0
  49. data/vendor/behaviors/test/tasks_test/test/user_test.rb +17 -0
  50. data/vendor/constructor/Rakefile +44 -0
  51. data/vendor/constructor/config/environment.rb +12 -0
  52. data/vendor/constructor/lib/constructor.rb +132 -0
  53. data/vendor/constructor/test/constructor_test.rb +366 -0
  54. data/vendor/constructor/test/helper.rb +3 -0
  55. data/vendor/diy/README +26 -0
  56. data/vendor/diy/Rakefile +18 -0
  57. data/vendor/diy/lib/constructor.rb +114 -0
  58. data/vendor/diy/lib/diy.rb +329 -0
  59. data/vendor/diy/proto/context.rb +117 -0
  60. data/vendor/diy/proto/context.yml +20 -0
  61. data/vendor/diy/test/diy_test.rb +370 -0
  62. data/vendor/diy/test/files/broken_construction.yml +7 -0
  63. data/vendor/diy/test/files/cat/cat.rb +4 -0
  64. data/vendor/diy/test/files/cat/extra_conflict.yml +5 -0
  65. data/vendor/diy/test/files/cat/heritage.rb +2 -0
  66. data/vendor/diy/test/files/cat/needs_input.yml +3 -0
  67. data/vendor/diy/test/files/cat/the_cat_lineage.rb +1 -0
  68. data/vendor/diy/test/files/dog/dog_model.rb +4 -0
  69. data/vendor/diy/test/files/dog/dog_presenter.rb +4 -0
  70. data/vendor/diy/test/files/dog/dog_view.rb +2 -0
  71. data/vendor/diy/test/files/dog/file_resolver.rb +2 -0
  72. data/vendor/diy/test/files/dog/other_thing.rb +2 -0
  73. data/vendor/diy/test/files/dog/simple.yml +11 -0
  74. data/vendor/diy/test/files/donkey/foo.rb +8 -0
  75. data/vendor/diy/test/files/donkey/foo/bar/qux.rb +7 -0
  76. data/vendor/diy/test/files/fud/objects.yml +13 -0
  77. data/vendor/diy/test/files/fud/toy.rb +15 -0
  78. data/vendor/diy/test/files/gnu/objects.yml +14 -0
  79. data/vendor/diy/test/files/gnu/thinger.rb +8 -0
  80. data/vendor/diy/test/files/goat/base.rb +8 -0
  81. data/vendor/diy/test/files/goat/can.rb +6 -0
  82. data/vendor/diy/test/files/goat/goat.rb +6 -0
  83. data/vendor/diy/test/files/goat/objects.yml +12 -0
  84. data/vendor/diy/test/files/goat/paper.rb +6 -0
  85. data/vendor/diy/test/files/goat/plane.rb +8 -0
  86. data/vendor/diy/test/files/goat/shirt.rb +6 -0
  87. data/vendor/diy/test/files/goat/wings.rb +8 -0
  88. data/vendor/diy/test/files/horse/holder_thing.rb +4 -0
  89. data/vendor/diy/test/files/horse/objects.yml +7 -0
  90. data/vendor/diy/test/files/yak/core_model.rb +4 -0
  91. data/vendor/diy/test/files/yak/core_presenter.rb +4 -0
  92. data/vendor/diy/test/files/yak/core_view.rb +1 -0
  93. data/vendor/diy/test/files/yak/data_source.rb +1 -0
  94. data/vendor/diy/test/files/yak/fringe_model.rb +4 -0
  95. data/vendor/diy/test/files/yak/fringe_presenter.rb +4 -0
  96. data/vendor/diy/test/files/yak/fringe_view.rb +1 -0
  97. data/vendor/diy/test/files/yak/my_objects.yml +21 -0
  98. data/vendor/diy/test/test_helper.rb +40 -0
  99. data/vendor/hardmock/CHANGES +8 -0
  100. data/vendor/hardmock/LICENSE +7 -0
  101. data/vendor/hardmock/README +48 -0
  102. data/vendor/hardmock/Rakefile +100 -0
  103. data/vendor/hardmock/TODO +7 -0
  104. data/vendor/hardmock/config/environment.rb +12 -0
  105. data/vendor/hardmock/homepage/demo.rb +21 -0
  106. data/vendor/hardmock/homepage/hardmock_sample.png +0 -0
  107. data/vendor/hardmock/homepage/index.html +65 -0
  108. data/vendor/hardmock/init.rb +3 -0
  109. data/vendor/hardmock/lib/hardmock.rb +634 -0
  110. data/vendor/hardmock/lib/method_cleanout.rb +14 -0
  111. data/vendor/hardmock/rcov.rake +18 -0
  112. data/vendor/hardmock/test/functional/assert_error_test.rb +52 -0
  113. data/vendor/hardmock/test/functional/auto_verify_test.rb +192 -0
  114. data/vendor/hardmock/test/functional/direct_mock_usage_test.rb +396 -0
  115. data/vendor/hardmock/test/functional/hardmock_test.rb +380 -0
  116. data/vendor/hardmock/test/test_helper.rb +23 -0
  117. data/vendor/hardmock/test/unit/expectation_builder_test.rb +18 -0
  118. data/vendor/hardmock/test/unit/expector_test.rb +56 -0
  119. data/vendor/hardmock/test/unit/method_cleanout_test.rb +35 -0
  120. data/vendor/hardmock/test/unit/mock_control_test.rb +172 -0
  121. data/vendor/hardmock/test/unit/mock_test.rb +273 -0
  122. data/vendor/hardmock/test/unit/simple_expectation_test.rb +345 -0
  123. data/vendor/hardmock/test/unit/trapper_test.rb +60 -0
  124. data/vendor/hardmock/test/unit/verify_error_test.rb +34 -0
  125. data/vendor/systir/systir.rb +403 -0
  126. data/vendor/systir/test/unit/ui/xml/testrunner.rb +192 -0
  127. data/vendor/systir/test/unit/ui/xml/xmltestrunner.xslt +109 -0
  128. metadata +235 -0
@@ -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,3 @@
1
+ if RAILS_ENV == 'test'
2
+ require 'hardmock'
3
+ end
@@ -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