mac_bacon 1.2.1 → 1.3

Sign up to get free protection for your applications and to get access to all the features.
data/ChangeLog CHANGED
@@ -1,3 +1,21 @@
1
+ Sat Mar 12 15:47:27 2011 +0100 Eloy Duran <eloy.de.enige@gmail.com>
2
+ * Filter the backtraces.
3
+
4
+ Sat Mar 12 15:34:41 2011 +0100 Eloy Duran <eloy.de.enige@gmail.com>
5
+ * Postponing blocks until a KVO message is received should work from before and after filters as well.
6
+
7
+ Sat Mar 12 15:06:26 2011 +0100 Eloy Duran <eloy.de.enige@gmail.com>
8
+ * Postponing blocks until Context#resume is called should work from before and after filters as well.
9
+
10
+ Sat Mar 12 14:52:41 2011 +0100 Eloy Duran <eloy.de.enige@gmail.com>
11
+ * Postponing blocks for a fixed period of time should work from before and after filters as well.
12
+
13
+ Sat Mar 12 13:57:01 2011 +0100 Eloy Duran <eloy.de.enige@gmail.com>
14
+ * Add Context#wait_for_change(object_to_observe, key_path, timeout = 1), which resumes the postponed block until a KVO message is received.
15
+
16
+ Sat Mar 12 12:41:10 2011 +0100 Eloy Duran <eloy.de.enige@gmail.com>
17
+ * Postpone a block until either Context#resume is called or the timeout has exceeded. The default timeout is 1s, or can be given by using Context#wait_max instead of Context#wait.
18
+
1
19
  Mon Feb 28 12:34:55 2011 +0100 Klaas Speller <klaasspeller@gmail.com>
2
20
  * Exit with other status than 0 on fails or errors
3
21
 
data/RDOX CHANGED
@@ -55,8 +55,36 @@ describe arguments
55
55
  - should work with multiple arguments
56
56
 
57
57
  NSRunloop aware Bacon
58
+
59
+ concerning `wait' with a fixed time
58
60
  - allows the user to postpone execution of a block for n seconds, which will halt any further execution of specs
59
61
 
62
+ concerning `wait' without a fixed time
63
+ - allows the user to postpone execution of a block until Context#resume is called, from for instance a delegate callback
64
+ - has a default timeout of 1 second after which the spec will fail and further scheduled calls to the Context are cancelled [FAILED]
65
+ - takes an explicit timeout [FAILED]
66
+
67
+ concerning `wait_for_change'
68
+ - resumes the postponed block once an observed value changes
69
+ - has a default timeout of 1 second [FAILED]
70
+ - takes an explicit timeout [FAILED]
71
+
72
+ postponing blocks should work from before/after filters as well
73
+
74
+ with `wait'
75
+
76
+ and an explicit time
77
+ - starts later because of postponed blocks in the before filter
78
+ - starts even later because of the postponed blocks in the after filter
79
+
80
+ and without explicit time
81
+ - starts later because of postponed blocks in the before filter
82
+ - starts even later because of the postponed blocks in the after filter
83
+
84
+ with `wait_for_change'
85
+ - starts later because of postponed blocks in the before filter
86
+ - starts even later because of the postponed blocks in the after filter
87
+
60
88
  Nib helper
61
89
  - takes a NIB path and instantiates the NIB with the given `owner' object
62
90
  - also returns an array or other top level objects
@@ -84,18 +112,18 @@ Bacon::Context
84
112
 
85
113
  Bacon::Context empty
86
114
 
87
- Bacon::Error: not #<Proc:0x200b9d840 (lambda)>.raise?(Bacon::Error) failed
88
- /Users/eloy/Documents/DEVELOPMENT/MacRuby/MacBacon/lib/mac_bacon.rb:426:in `satisfy': Bacon - should have should.be.close
89
- /Users/eloy/Documents/DEVELOPMENT/MacRuby/MacBacon/lib/mac_bacon.rb:440:in `method_missing:'
90
- /Users/eloy/Documents/DEVELOPMENT/MacRuby/MacBacon/test/spec_bacon.rb:9:in `block'
91
- /Users/eloy/Documents/DEVELOPMENT/MacRuby/MacBacon/lib/mac_bacon.rb:423:in `satisfy'
92
- /Users/eloy/Documents/DEVELOPMENT/MacRuby/MacBacon/lib/mac_bacon.rb:409:in `an:'
93
- /Users/eloy/Documents/DEVELOPMENT/MacRuby/MacBacon/lib/mac_bacon.rb:375:in `should:'
94
- /Users/eloy/Documents/DEVELOPMENT/MacRuby/MacBacon/test/spec_bacon.rb:182:in `block'
95
- /Users/eloy/Documents/DEVELOPMENT/MacRuby/MacBacon/lib/mac_bacon.rb:158:in `block'
96
- /Users/eloy/Documents/DEVELOPMENT/MacRuby/MacBacon/lib/mac_bacon.rb:195:in `execute_block'
97
- /Users/eloy/Documents/DEVELOPMENT/MacRuby/MacBacon/lib/mac_bacon.rb:154:in `run'
98
- /Users/eloy/Documents/DEVELOPMENT/MacRuby/MacBacon/lib/mac_bacon.rb:234:in `run'
99
- /Users/eloy/Documents/DEVELOPMENT/MacRuby/MacBacon/bin/macbacon:118:in `<main>'
100
-
101
- 52 specifications (387 requirements), 1 failures, 0 errors
115
+ Bacon::Error: not #<Proc:0x200485980 (lambda)>.raise?(Bacon::Error) failed
116
+ /Users/eloy/code/MacRuby/MacBacon/test/spec_bacon.rb:9:in `block': Bacon - should have should.be.close
117
+ /Users/eloy/code/MacRuby/MacBacon/test/spec_bacon.rb:182:in `block'
118
+
119
+ Bacon::Error: timeout exceeded: concerning `wait' without a fixed time - has a default timeout of 1 second after which the spec will fail and further scheduled calls to the Context are cancelled
120
+
121
+ Bacon::Error: timeout exceeded: concerning `wait' without a fixed time - takes an explicit timeout
122
+
123
+ Bacon::Error: timeout exceeded: concerning `wait_for_change' - has a default timeout of 1 second
124
+ /Users/eloy/code/MacRuby/MacBacon/test/spec_helper.rb:23:in `finish_spec': concerning `wait_for_change' - has a default timeout of 1 second
125
+
126
+ Bacon::Error: timeout exceeded: concerning `wait_for_change' - takes an explicit timeout
127
+ /Users/eloy/code/MacRuby/MacBacon/test/spec_helper.rb:23:in `finish_spec': concerning `wait_for_change' - takes an explicit timeout
128
+
129
+ 64 specifications (411 requirements), 1 failures, 0 errors
@@ -1,6 +1,7 @@
1
- = MacBacon -- small RSpec clone.
1
+ MacBacon -- small RSpec clone.
2
+ ------------------------------
2
3
 
3
- "Truth will sooner come out from error than from confusion."
4
+ "Truth will sooner come out from error than from confusion."
4
5
  ---Francis Bacon
5
6
 
6
7
  Bacon is a small RSpec clone weighing less than 350 LoC but
@@ -9,9 +10,10 @@ nevertheless providing all essential features.
9
10
  This MacBacon fork is created and maintained by Eloy Durán (@alloy).
10
11
  It differs with regular Bacon in that it operates properly in a
11
12
  NSRunloop based environment. I.e. MacRuby/Objective-C. See the
12
- paragraph about the `wait` command for more info.
13
+ Objective-C runloop macros section for more info.
13
14
 
14
- == Whirl-wind tour
15
+ Whirl-wind tour
16
+ ===============
15
17
 
16
18
  require 'mac_bacon'
17
19
 
@@ -135,7 +137,8 @@ As of Bacon 1.1, it also supports Knock:
135
137
  (knock is available from http://github.com/chneukirchen/knock/)
136
138
 
137
139
 
138
- == Implemented assertions
140
+ Implemented assertions
141
+ ======================
139
142
 
140
143
  * should.<predicate> and should.be.<predicate>
141
144
  * should.equal
@@ -147,7 +150,8 @@ As of Bacon 1.1, it also supports Knock:
147
150
  * should.satisfy { |object| }
148
151
 
149
152
 
150
- == Added core predicates
153
+ Added core predicates
154
+ =====================
151
155
 
152
156
  * Object#true?
153
157
  * Object#false?
@@ -157,7 +161,8 @@ As of Bacon 1.1, it also supports Knock:
157
161
  * Numeric#close?
158
162
 
159
163
 
160
- == before/after
164
+ before/after
165
+ ============
161
166
 
162
167
  before and after need to be defined before the first specification in
163
168
  a context and are run before and after each specification.
@@ -165,7 +170,8 @@ a context and are run before and after each specification.
165
170
  As of Bacon 1.1, before and after do nest in nested contexts.
166
171
 
167
172
 
168
- == Shared contexts
173
+ Shared contexts
174
+ ===============
169
175
 
170
176
  You can define shared contexts in Bacon like this:
171
177
 
@@ -186,59 +192,132 @@ behaves_like in other contexts. You can use shared contexts to
186
192
  structure suites with many recurring specifications.
187
193
 
188
194
 
189
- == Matchers
195
+ Matchers
196
+ ========
190
197
 
191
198
  Custom matchers are simply lambdas returning a boolean value, for
192
199
  example:
193
200
 
194
- def shorter_than(max_size)
195
- lambda { |obj| obj.size < max_size }
196
- end
197
-
198
- [1,2,3].should.be shorter_than(5)
201
+ def shorter_than(max_size)
202
+ lambda { |obj| obj.size < max_size }
203
+ end
204
+
205
+ [1,2,3].should.be shorter_than(5)
199
206
 
200
207
  You can use modules and extend to group matchers for use in multiple
201
208
  contexts.
202
209
 
203
210
 
204
- == wait
211
+ Objective-C runloop macros
212
+ ==========================
213
+
205
214
 
206
215
  Often in Objective-C apps, code will *not* execute immediately, but
207
216
  scheduled on a runloop for later execution. Therefor a mechanism is
208
- needed that will postpone execution of some assertions for a period of
209
- time. This is where the `wait` macro comes in:
217
+ provided that will postpone execution of blocks for a period of time.
210
218
 
211
- it 'should perform a long running operation' do
212
- # Here a method call is scheduled to be performed ~0.5 seconds in the future
213
- @ary.performSelector("addObject:", withObject:"soup", afterDelay:0.5)
214
- wait 0.6 do
215
- # This block is executed ~0.6 seconds in the future
216
- @ary.size.should.be 1
217
- end
219
+ You can event nest these blocks. However, with the exception of `wait`
220
+ with an explicit time, you can *not* have multiple at the same time.
221
+
222
+ All these macros may be used in before and after filters as well.
223
+
224
+
225
+ ### `wait` with fixed period of time
226
+
227
+ it 'should perform a long running operation' do
228
+ # Here a method call is scheduled to be performed ~0.5 seconds in the future
229
+ @ary.performSelector("addObject:", withObject:"soup", afterDelay:0.5)
230
+ wait 0.6 do
231
+ # This block is executed ~0.6 seconds in the future
232
+ @ary.size.should.be 1
233
+ end
234
+ end
235
+
236
+
237
+ ### `wait` without fixed period of time, until `resume` is called
238
+
239
+ By default this usage of `wait` will wait for 1 second. If `resume`
240
+ has not been called by that time, the spec fails. If you want to
241
+ specify the timeout use `wait_max(timeout, &block)` instead.
242
+
243
+ def aDelegateCallbackMethod(sender)
244
+ @delegateCallbackMethodCalled = true
245
+ resume
246
+ end
247
+
248
+ it 'should wait until notified' do
249
+ # Here a method is called that in the near future will result in the object calling back the delegate
250
+ @object.delegate = self
251
+ @object.startLongRunningMethod
252
+ wait do
253
+ # This block is executed once aDelegateCallbackMethod is called
254
+ @delegateCallbackMethodCalled.should == true
218
255
  end
256
+ end
257
+
219
258
 
220
- The postponed block does *not* halt the thread, but is scheduled on
221
- the runloop as well. This means that your runloop based code will have
222
- a chance to perform its job before the assertions in the block are
223
- executed.
259
+ ### `wait_for_change` (Key-Value Observing)
224
260
 
225
- You can schedule as many blocks as you’d want and even nest them.
261
+ This macro makes the specification an observer of the key path of the
262
+ given object for the duration of the specification.
226
263
 
264
+ By default this usage of `wait_for_change` will wait for 1 second. If
265
+ the KVO message has not arrived by that time, the spec fails. If you
266
+ want to specify the timeout use
267
+ `wait_for_change(observable, key_path, timeout)` instead.
227
268
 
228
- == bacon standalone runner
269
+ class AKeyValueObservableClass
270
+ attr_accessor :an_attribute
229
271
 
230
- -s, --specdox do AgileDox-like output (default)
231
- -q, --quiet do Test::Unit-like non-verbose output
232
- -p, --tap do TAP (Test Anything Protocol) output
233
- -k, --knock do Knock output
234
- -o, --output FORMAT do FORMAT (SpecDox/TestUnit/Tap) output
235
- -Q, --no-backtrace don't print backtraces
236
- -a, --automatic gather tests from ./test/, include ./lib/
237
- -n, --name NAME runs tests matching regexp NAME
238
- -t, --testcase TESTCASE runs tests in TestCases matching regexp TESTCASE
272
+ def compute_an_attribute
273
+ # trust me, this takes a few ms
274
+ end
275
+ end
239
276
 
277
+ it 'should wait until AKeyValueObservableClass#an_attribute changes' do
278
+ # Here a method is called that in the near future will update the 'an_attribute' value of the object
279
+ observable.compute_an_attribute
280
+ wait_for_change observable, 'an_attribute' do
281
+ # This block is executed once 'an_attribute' has changed value
282
+ observable.an_attribute.should == 'changed'
283
+ end
284
+ end
240
285
 
241
- == Object#should
286
+
287
+ Load NIBs
288
+ =========
289
+
290
+ In case you have a NIB that defines the UI for the controller you're testing,
291
+ then you can use the `load_nib` method to easily do so:
292
+
293
+ describe "PreferencesController" do
294
+ before do
295
+ @controller = PreferencesController.new
296
+ nib_path = File.join(SRC_ROOT, 'app/views/PreferencesWindow.xib')
297
+ @top_level_objects = load_nib(nib_path, @controller)
298
+ end
299
+
300
+ # tests...
301
+
302
+ end
303
+
304
+
305
+ bacon standalone runner
306
+ =======================
307
+
308
+ -s, --specdox do AgileDox-like output (default)
309
+ -q, --quiet do Test::Unit-like non-verbose output
310
+ -p, --tap do TAP (Test Anything Protocol) output
311
+ -k, --knock do Knock output
312
+ -o, --output FORMAT do FORMAT (SpecDox/TestUnit/Tap) output
313
+ -Q, --no-backtrace don't print backtraces
314
+ -a, --automatic gather tests from ./test/, include ./lib/
315
+ -n, --name NAME runs tests matching regexp NAME
316
+ -t, --testcase TESTCASE runs tests in TestCases matching regexp TESTCASE
317
+
318
+
319
+ Object#should
320
+ =============
242
321
 
243
322
  You can use Object#should outside of contexts, where the result of
244
323
  assertion will be returned as a boolean. This is nice for
@@ -251,7 +330,8 @@ demonstrations, quick checks and doctest tests.
251
330
  => false
252
331
 
253
332
 
254
- == Converting specs
333
+ Converting specs
334
+ ================
255
335
 
256
336
  spec-converter is a simple tool to convert test-unit or dust style
257
337
  tests to test/spec specs.
@@ -259,7 +339,8 @@ tests to test/spec specs.
259
339
  It can be found at http://opensource.thinkrelevance.com/wiki/spec_converter.
260
340
 
261
341
 
262
- == Thanks to
342
+ Thanks to
343
+ =========
263
344
 
264
345
  * Michael Fellinger, for fixing Bacon for 1.9 and various improvements.
265
346
  * Gabriele Renzi, for implementing Context#should.
@@ -268,7 +349,8 @@ It can be found at http://opensource.thinkrelevance.com/wiki/spec_converter.
268
349
  * everyone contributing bug fixes.
269
350
 
270
351
 
271
- == History
352
+ History
353
+ =======
272
354
 
273
355
  * January 7, 2008: First public release 0.9.
274
356
 
@@ -288,10 +370,17 @@ It can be found at http://opensource.thinkrelevance.com/wiki/spec_converter.
288
370
 
289
371
  * January 10th, 2011: MacBacon fork release 1.1
290
372
  * Make it work in a NSRunloop environment
291
- * Add `wait'
373
+ * Add `wait`
292
374
  * Remove extras, for now
293
375
 
294
- == Contact
376
+ * March 12th, 2011: MacBacon fork release 1.3
377
+ * Add NIB helper
378
+ * exit with non-zero status when there were failures/errors
379
+ * Add `wait` without explicit time
380
+ * Add `wait_for_change`
381
+
382
+ Contact
383
+ =======
295
384
 
296
385
  Please mail bugs, suggestions and patches for Bacon to
297
386
  <mailto:chneukirchen@gmail.com>
@@ -307,18 +396,23 @@ And repository location:
307
396
  https://github.com/alloy/MacBacon
308
397
  git://github.com/alloy/MacBacon.git
309
398
 
310
- == Copying
399
+ Copying
400
+ =======
311
401
 
312
- Copyright (C) 2007, 2008 Christian Neukirchen <purl.org/net/chneukirchen>
402
+ Copyright (C) 2007 - 2011 Christian Neukirchen <purl.org/net/chneukirchen>
403
+ Copyright (C) 2011 Eloy Durán <eloy.de.enige@gmail.com>
313
404
 
314
405
  Bacon is freely distributable under the terms of an MIT-style license.
315
406
  See COPYING or http://www.opensource.org/licenses/mit-license.php.
316
407
 
317
408
 
318
- == Links
409
+ Links
410
+ =====
319
411
 
320
412
  Behavior-Driven Development:: <http://behaviour-driven.org/>
321
413
  RSpec:: <http://rspec.rubyforge.org/>
322
414
  test/spec:: <http://test-spec.rubyforge.org/>
323
415
 
324
416
  Christian Neukirchen:: <http://chneukirchen.org/>
417
+ Eloy Durán:: <http://soup.superalloy.nl/>
418
+
data/Rakefile CHANGED
@@ -27,7 +27,7 @@ def git_tree_version
27
27
  #$: << "lib"
28
28
  #require 'mac_bacon'
29
29
  #@tree_version = Bacon::VERSION
30
- @tree_version = "1.2.1"
30
+ @tree_version = "1.3"
31
31
  end
32
32
  @tree_version
33
33
  end
@@ -116,7 +116,7 @@ https://github.com/alloy/MacBacon
116
116
  s.executables << 'macbacon'
117
117
  s.require_path = 'lib'
118
118
  s.has_rdoc = true
119
- s.extra_rdoc_files = ['README', 'RDOX']
119
+ s.extra_rdoc_files = ['README.md', 'RDOX']
120
120
  s.test_files = []
121
121
 
122
122
  s.author = 'Eloy Durán'
@@ -137,11 +137,11 @@ end
137
137
  desc "Generate RDoc documentation"
138
138
  Rake::RDocTask.new(:rdoc) do |rdoc|
139
139
  rdoc.options << '--line-numbers' << '--inline-source' <<
140
- '--main' << 'README' <<
140
+ '--main' << 'README.md' <<
141
141
  '--title' << 'Bacon Documentation' <<
142
142
  '--charset' << 'utf-8'
143
143
  rdoc.rdoc_dir = "doc"
144
- rdoc.rdoc_files.include 'README'
144
+ rdoc.rdoc_files.include 'README.md'
145
145
  rdoc.rdoc_files.include 'COPYING'
146
146
  rdoc.rdoc_files.include 'RDOX'
147
147
  rdoc.rdoc_files.include('lib/mac_bacon.rb')
data/TODO ADDED
@@ -0,0 +1,2 @@
1
+ * refactor
2
+ * cleanup README.md
@@ -11,7 +11,7 @@ framework "Cocoa"
11
11
  require "mac_bacon/helpers"
12
12
 
13
13
  module Bacon
14
- VERSION = "1.2.1"
14
+ VERSION = "1.3"
15
15
 
16
16
  Counter = Hash.new(0)
17
17
  ErrorLog = ""
@@ -137,31 +137,43 @@ module Bacon
137
137
  @before_filters, @after_filters = before_filters.dup, after_filters.dup
138
138
 
139
139
  @postponed_blocks_count = 0
140
+ @ran_spec_block = false
141
+ @ran_after_filters = false
140
142
  @exception_occurred = false
141
143
  @error = ""
142
144
  end
143
145
 
146
+ def postponed?
147
+ @postponed_blocks_count != 0
148
+ end
149
+
144
150
  def run_before_filters
145
- @before_filters.each { |f| @context.instance_eval(&f) }
151
+ execute_block { @before_filters.each { |f| @context.instance_eval(&f) } }
152
+ end
153
+
154
+ def run_spec_block
155
+ @ran_spec_block = true
156
+ # If an exception occurred, we definitely don't need to perform the actual spec anymore
157
+ unless @exception_occurred
158
+ execute_block { @context.instance_eval(&@block) }
159
+ end
160
+ finish_spec unless postponed?
146
161
  end
147
162
 
148
163
  def run_after_filters
149
- @after_filters.each { |f| @context.instance_eval(&f) }
164
+ @ran_after_filters = true
165
+ execute_block { @after_filters.each { |f| @context.instance_eval(&f) } }
150
166
  end
151
167
 
152
168
  def run
153
169
  Bacon.handle_requirement_begin(@description)
154
- execute_block do
155
- Counter[:depth] += 1
156
- run_before_filters
157
- @number_of_requirements_before = Counter[:requirements]
158
- @context.instance_eval(&@block)
159
- end
160
-
161
- finalize if @postponed_blocks_count == 0
170
+ Counter[:depth] += 1
171
+ run_before_filters
172
+ @number_of_requirements_before = Counter[:requirements]
173
+ run_spec_block unless postponed?
162
174
  end
163
175
 
164
- def postpone_block(seconds, &block)
176
+ def schedule_block(seconds, &block)
165
177
  # If an exception occurred, we definitely don't need to schedule any more blocks
166
178
  unless @exception_occurred
167
179
  @postponed_blocks_count += 1
@@ -169,22 +181,97 @@ module Bacon
169
181
  end
170
182
  end
171
183
 
184
+ def postpone_block(timeout = 1, &block)
185
+ # If an exception occurred, we definitely don't need to schedule any more blocks
186
+ unless @exception_occurred
187
+ if @postponed_block
188
+ raise "Only one indefinite `wait' block at the same time is allowed!"
189
+ else
190
+ @postponed_blocks_count += 1
191
+ @postponed_block = block
192
+ performSelector("postponed_block_timeout_exceeded", withObject:nil, afterDelay:timeout)
193
+ end
194
+ end
195
+ end
196
+
197
+ def postpone_block_until_change(object_to_observe, key_path, timeout = 1, &block)
198
+ # If an exception occurred, we definitely don't need to schedule any more blocks
199
+ unless @exception_occurred
200
+ if @postponed_block
201
+ raise "Only one indefinite `wait' block at the same time is allowed!"
202
+ else
203
+ @postponed_blocks_count += 1
204
+ @postponed_block = block
205
+ @observed_object_and_key_path = [object_to_observe, key_path]
206
+ object_to_observe.addObserver(self, forKeyPath:key_path, options:0, context:nil)
207
+ performSelector("postponed_change_block_timeout_exceeded", withObject:nil, afterDelay:timeout)
208
+ end
209
+ end
210
+ end
211
+
212
+ def observeValueForKeyPath(key_path, ofObject:object, change:_, context:__)
213
+ resume
214
+ end
215
+
216
+ def postponed_change_block_timeout_exceeded
217
+ remove_observer!
218
+ postponed_block_timeout_exceeded
219
+ end
220
+
221
+ def remove_observer!
222
+ if @observed_object_and_key_path
223
+ object, key_path = @observed_object_and_key_path
224
+ object.removeObserver(self, forKeyPath:key_path)
225
+ @observed_object_and_key_path = nil
226
+ end
227
+ end
228
+
229
+ def postponed_block_timeout_exceeded
230
+ cancel_scheduled_requests!
231
+ execute_block { raise Error.new(:failed, "timeout exceeded: #{@context.name} - #{@description}") }
232
+ @postponed_blocks_count = 0
233
+ finish_spec
234
+ end
235
+
236
+ def resume
237
+ NSObject.cancelPreviousPerformRequestsWithTarget(self, selector:'postponed_block_timeout_exceeded', object:nil)
238
+ NSObject.cancelPreviousPerformRequestsWithTarget(self, selector:'postponed_change_block_timeout_exceeded', object:nil)
239
+ remove_observer!
240
+ block, @postponed_block = @postponed_block, nil
241
+ run_postponed_block(block)
242
+ end
243
+
172
244
  def run_postponed_block(block)
173
245
  # If an exception occurred, we definitely don't need execute any more blocks
174
246
  execute_block(&block) unless @exception_occurred
175
247
  @postponed_blocks_count -= 1
176
- finalize if @postponed_blocks_count == 0
248
+ unless postponed?
249
+ if @ran_after_filters
250
+ exit_spec
251
+ elsif @ran_spec_block
252
+ finish_spec
253
+ else
254
+ run_spec_block
255
+ end
256
+ end
177
257
  end
178
258
 
179
- def finalize
180
- if Counter[:requirements] == @number_of_requirements_before
259
+ def finish_spec
260
+ if !@exception_occurred && Counter[:requirements] == @number_of_requirements_before
181
261
  # the specification did not contain any requirements, so it flunked
182
- # TODO ugh, exceptions for control flow, need to clean this up
183
262
  execute_block { raise Error.new(:missing, "empty specification: #{@context.name} #{@description}") }
184
263
  end
264
+ run_after_filters
265
+ exit_spec unless postponed?
266
+ end
185
267
 
186
- execute_block { run_after_filters }
268
+ def cancel_scheduled_requests!
269
+ NSObject.cancelPreviousPerformRequestsWithTarget(@context)
270
+ NSObject.cancelPreviousPerformRequestsWithTarget(self)
271
+ end
187
272
 
273
+ def exit_spec
274
+ cancel_scheduled_requests!
188
275
  Counter[:depth] -= 1
189
276
  Bacon.handle_requirement_end(@error)
190
277
  @context.specification_did_finish(self)
@@ -197,8 +284,8 @@ module Bacon
197
284
  @exception_occurred = true
198
285
 
199
286
  ErrorLog << "#{e.class}: #{e.message}\n"
200
- e.backtrace.find_all { |line| line !~ /bin\/bacon|\/bacon\.rb:\d+/ }.
201
- each_with_index { |line, i|
287
+ lines = $DEBUG ? e.backtrace : e.backtrace.find_all { |line| line !~ /bin\/macbacon|\/mac_bacon\.rb:\d+/ }
288
+ lines.each_with_index { |line, i|
202
289
  ErrorLog << "\t#{line}#{i==0 ? ": #{@context.name} - #{@description}" : ""}\n"
203
290
  }
204
291
  ErrorLog << "\n"
@@ -315,8 +402,24 @@ module Bacon
315
402
  context
316
403
  end
317
404
 
318
- def wait(seconds, &block)
319
- current_specification.postpone_block(seconds, &block)
405
+ def wait(seconds = nil, &block)
406
+ if seconds
407
+ current_specification.schedule_block(seconds, &block)
408
+ else
409
+ current_specification.postpone_block(&block)
410
+ end
411
+ end
412
+
413
+ def wait_max(timeout, &block)
414
+ current_specification.postpone_block(timeout, &block)
415
+ end
416
+
417
+ def wait_for_change(object_to_observe, key_path, timeout = 1, &block)
418
+ current_specification.postpone_block_until_change(object_to_observe, key_path, timeout, &block)
419
+ end
420
+
421
+ def resume
422
+ current_specification.resume
320
423
  end
321
424
 
322
425
  def raise?(*args, &block); block.raise?(*args); end
@@ -1,2 +1,28 @@
1
1
  $:.unshift File.expand_path('../../lib', __FILE__)
2
2
  require 'mac_bacon'
3
+
4
+ module Bacon
5
+ class Specification
6
+ alias_method :_real_finish_spec, :finish_spec
7
+ end
8
+
9
+ class Context
10
+ def failures_before
11
+ @failures_before
12
+ end
13
+
14
+ def expect_spec_to_fail!
15
+ @failures_before = Bacon::Counter[:failed]
16
+ Bacon::Specification.class_eval do
17
+ def finish_spec
18
+ @exception_occurred.should == true
19
+ @exception_occurred = nil
20
+ Bacon::Counter[:failed].should == @context.failures_before + 1
21
+ Bacon::Counter[:failed] = @context.failures_before
22
+ self.class.class_eval { alias_method :finish_spec, :_real_finish_spec }
23
+ _real_finish_spec
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -1,19 +1,204 @@
1
1
  require File.expand_path('../spec_helper', __FILE__)
2
2
 
3
+ class MockObservable
4
+ attr_accessor :an_attribute
5
+ end
6
+
3
7
  describe "NSRunloop aware Bacon" do
4
- it "allows the user to postpone execution of a block for n seconds, which will halt any further execution of specs" do
5
- started_at_1 = started_at_2 = started_at_3 = Time.now
6
- number_of_specs_before = Bacon::Counter[:specifications]
8
+ describe "concerning `wait' with a fixed time" do
9
+ it "allows the user to postpone execution of a block for n seconds, which will halt any further execution of specs" do
10
+ started_at_1 = started_at_2 = started_at_3 = Time.now
11
+ number_of_specs_before = Bacon::Counter[:specifications]
12
+
13
+ wait 0.5 do
14
+ (Time.now - started_at_1).should.be.close(0.5, 0.5)
15
+ end
16
+ wait 1 do
17
+ (Time.now - started_at_2).should.be.close(1, 0.5)
18
+ wait 1.5 do
19
+ (Time.now - started_at_3).should.be.close(2.5, 0.5)
20
+ Bacon::Counter[:specifications].should == number_of_specs_before
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ describe "concerning `wait' without a fixed time" do
27
+ def delegateCallbackMethod
28
+ @delegateCallbackCalled = true
29
+ resume
30
+ end
31
+
32
+ it "allows the user to postpone execution of a block until Context#resume is called, from for instance a delegate callback" do
33
+ performSelector('delegateCallbackMethod', withObject:nil, afterDelay:0.1)
34
+ @delegateCallbackCalled.should == nil
35
+ wait do
36
+ @delegateCallbackCalled.should == true
37
+ end
38
+ end
39
+
40
+ def delegateCallbackTookTooLongMethod
41
+ raise "Oh noes, I must never be called!"
42
+ end
43
+
44
+ # This spec adds a failure to the ErrorLog!
45
+ it "has a default timeout of 1 second after which the spec will fail and further scheduled calls to the Context are cancelled" do
46
+ expect_spec_to_fail!
47
+ performSelector('delegateCallbackTookTooLongMethod', withObject:nil, afterDelay:1.2)
48
+ wait do
49
+ # we must never arrive here, because the default timeout of 1 second will have passed
50
+ raise "Oh noes, we shouldn't have arrived in this postponed block!"
51
+ end
52
+ end
53
+
54
+ # This spec adds a failure to the ErrorLog!
55
+ it "takes an explicit timeout" do
56
+ expect_spec_to_fail!
57
+ performSelector('delegateCallbackTookTooLongMethod', withObject:nil, afterDelay:0.8)
58
+ wait_max 0.3 do
59
+ # we must never arrive here, because the default timeout of 1 second will have passed
60
+ raise "Oh noes, we shouldn't have arrived in this postponed block!"
61
+ end
62
+ end
63
+ end
64
+
65
+ describe "concerning `wait_for_change'" do
66
+ before do
67
+ @observable = MockObservable.new
68
+ end
69
+
70
+ def triggerChange
71
+ @observable.an_attribute = 'changed'
72
+ end
73
+
74
+ it "resumes the postponed block once an observed value changes" do
75
+ wait_for_change @observable, 'an_attribute' do
76
+ @value = @observable.an_attribute
77
+ end
78
+ @value.should == nil
79
+ performSelector('triggerChange', withObject:nil, afterDelay:0.1)
80
+ wait 0.2 do
81
+ @value.should == 'changed'
82
+ end
83
+ end
84
+
85
+ # This spec adds a failure to the ErrorLog!
86
+ it "has a default timeout of 1 second" do
87
+ expect_spec_to_fail!
88
+ wait_for_change(@observable, 'an_attribute') do
89
+ raise "Oh noes, I must never be called!"
90
+ end
91
+ performSelector('triggerChange', withObject:nil, afterDelay:1.1)
92
+ wait 1.2 do
93
+ # we must never arrive here, because the default timeout of 1 second will have passed
94
+ raise "Oh noes, we shouldn't have arrived in this postponed block!"
95
+ end
96
+ end
97
+
98
+ # This spec adds a failure to the ErrorLog!
99
+ it "takes an explicit timeout" do
100
+ expect_spec_to_fail!
101
+ wait_for_change(@observable, 'an_attribute', 0.3) do
102
+ raise "Oh noes, I must never be called!"
103
+ end
104
+ performSelector('triggerChange', withObject:nil, afterDelay:0.8)
105
+ wait 0.9 do
106
+ # we must never arrive here, because the default timeout of 1 second will have passed
107
+ raise "Oh noes, we shouldn't have arrived in this postponed block!"
108
+ end
109
+ end
110
+ end
111
+
112
+ describe "postponing blocks should work from before/after filters as well" do
113
+ shared "waiting in before/after filters" do
114
+ it "starts later because of postponed blocks in the before filter" do
115
+ (Time.now - @started_at).should.be.close(1, 0.5)
116
+ end
117
+
118
+ it "starts even later because of the postponed blocks in the after filter" do
119
+ (Time.now - @started_at).should.be.close(3, 0.5)
120
+ end
121
+ end
122
+
123
+ describe "with `wait'" do
124
+ describe "and an explicit time" do
125
+ before do
126
+ @started_at ||= Time.now
127
+ wait 0.5 do
128
+ wait 0.5 do
129
+ end
130
+ end
131
+ end
132
+
133
+ after do
134
+ wait 0.5 do
135
+ wait 0.5 do
136
+ @time ||= 0
137
+ @time += 2
138
+ (Time.now - @started_at).should.be.close(@time, 0.2)
139
+ end
140
+ end
141
+ end
142
+
143
+ behaves_like "waiting in before/after filters"
144
+ end
145
+
146
+ describe "and without explicit time" do
147
+ before do
148
+ @started_at ||= Time.now
149
+ performSelector('resume', withObject:nil, afterDelay:0.5)
150
+ wait do
151
+ performSelector('resume', withObject:nil, afterDelay:0.5)
152
+ wait do
153
+ end
154
+ end
155
+ end
156
+
157
+ after do
158
+ performSelector('resume', withObject:nil, afterDelay:0.5)
159
+ wait do
160
+ performSelector('resume', withObject:nil, afterDelay:0.5)
161
+ wait do
162
+ @time ||= 0
163
+ @time += 2
164
+ (Time.now - @started_at).should.be.close(@time, 0.2)
165
+ end
166
+ end
167
+ end
7
168
 
8
- wait 0.5 do
9
- (Time.now - started_at_1).should.be.close(0.5, 0.5)
169
+ behaves_like "waiting in before/after filters"
170
+ end
10
171
  end
11
- wait 1 do
12
- (Time.now - started_at_2).should.be.close(1, 0.5)
13
- wait 1.5 do
14
- (Time.now - started_at_3).should.be.close(2.5, 0.5)
15
- Bacon::Counter[:specifications].should == number_of_specs_before
172
+
173
+ describe "with `wait_for_change'" do
174
+ before do
175
+ @observable = MockObservable.new
176
+ @started_at ||= Time.now
177
+ performSelector('triggerChange', withObject:nil, afterDelay:0.5)
178
+ wait_for_change @observable, 'an_attribute' do
179
+ performSelector('triggerChange', withObject:nil, afterDelay:0.5)
180
+ wait_for_change @observable, 'an_attribute' do
181
+ end
182
+ end
183
+ end
184
+
185
+ after do
186
+ performSelector('triggerChange', withObject:nil, afterDelay:0.5)
187
+ wait_for_change @observable, 'an_attribute' do
188
+ performSelector('triggerChange', withObject:nil, afterDelay:0.5)
189
+ wait_for_change @observable, 'an_attribute' do
190
+ @time ||= 0
191
+ @time += 2
192
+ (Time.now - @started_at).should.be.close(@time, 1)
193
+ end
194
+ end
16
195
  end
196
+
197
+ def triggerChange
198
+ @observable.an_attribute = 'changed'
199
+ end
200
+
201
+ behaves_like "waiting in before/after filters"
17
202
  end
18
203
  end
19
204
  end
metadata CHANGED
@@ -1,13 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mac_bacon
3
3
  version: !ruby/object:Gem::Version
4
- hash: 29
5
- prerelease: false
6
- segments:
7
- - 1
8
- - 2
9
- - 1
10
- version: 1.2.1
4
+ version: "1.3"
11
5
  platform: ruby
12
6
  authors:
13
7
  - "Eloy Dur\xC3\xA1n"
@@ -15,7 +9,7 @@ autorequire:
15
9
  bindir: bin
16
10
  cert_chain: []
17
11
 
18
- date: 2011-03-01 00:00:00 +01:00
12
+ date: 2011-03-12 00:00:00 +01:00
19
13
  default_executable:
20
14
  dependencies: []
21
15
 
@@ -34,12 +28,13 @@ executables:
34
28
  extensions: []
35
29
 
36
30
  extra_rdoc_files:
37
- - README
31
+ - README.md
38
32
  - RDOX
39
33
  files:
40
34
  - COPYING
41
- - README
35
+ - README.md
42
36
  - Rakefile
37
+ - TODO
43
38
  - bin/macbacon
44
39
  - lib/mac_bacon.rb
45
40
  - lib/mac_bacon/helpers.rb
@@ -63,27 +58,21 @@ rdoc_options: []
63
58
  require_paths:
64
59
  - lib
65
60
  required_ruby_version: !ruby/object:Gem::Requirement
66
- none: false
67
61
  requirements:
68
62
  - - ">="
69
63
  - !ruby/object:Gem::Version
70
- hash: 3
71
- segments:
72
- - 0
73
64
  version: "0"
65
+ version:
74
66
  required_rubygems_version: !ruby/object:Gem::Requirement
75
- none: false
76
67
  requirements:
77
68
  - - ">="
78
69
  - !ruby/object:Gem::Version
79
- hash: 3
80
- segments:
81
- - 0
82
70
  version: "0"
71
+ version:
83
72
  requirements: []
84
73
 
85
74
  rubyforge_project:
86
- rubygems_version: 1.3.7
75
+ rubygems_version: 1.3.5
87
76
  signing_key:
88
77
  specification_version: 3
89
78
  summary: a small RSpec clone for MacRuby