minilab 1.0.0-mswin32

Sign up to get free protection for your applications and to get access to all the features.
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