mac_bacon 1.2.1 → 1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/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