dat-worker-pool 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,142 +1,135 @@
1
1
  require 'assert'
2
2
  require 'dat-worker-pool/worker_pool_spy'
3
3
 
4
+ require 'dat-worker-pool'
5
+ require 'dat-worker-pool/default_queue'
6
+ require 'dat-worker-pool/worker'
7
+
4
8
  class DatWorkerPool::WorkerPoolSpy
5
9
 
6
10
  class UnitTests < Assert::Context
7
11
  desc "DatWorkerPool::WorkerPoolSpy"
8
12
  setup do
9
- @work_proc = proc{ 'work' }
10
- @worker_pool_spy = DatWorkerPool::WorkerPoolSpy.new(&@work_proc)
13
+ @spy_class = DatWorkerPool::WorkerPoolSpy
14
+ end
15
+ subject{ @spy_class }
16
+
17
+ end
18
+
19
+ class InitTests < UnitTests
20
+ desc "when init"
21
+ setup do
22
+ @worker_class = Class.new{ include DatWorkerPool::Worker }
23
+ @options = {
24
+ :num_workers => Factory.integer,
25
+ :logger => TEST_LOGGER,
26
+ :queue => DatWorkerPool::DefaultQueue.new,
27
+ :worker_params => { Factory.string => Factory.string }
28
+ }
29
+
30
+ @worker_pool_spy = @spy_class.new(@worker_class, @options)
11
31
  end
12
32
  subject{ @worker_pool_spy }
13
33
 
14
- should have_readers :work_proc, :work_items
34
+ should have_readers :logger, :queue
35
+ should have_readers :options, :num_workers, :worker_class, :worker_params
15
36
  should have_readers :start_called, :shutdown_called, :shutdown_timeout
16
- should have_readers :on_queue_pop_callbacks, :on_queue_push_callbacks
17
- should have_readers :on_worker_error_callbacks
18
- should have_readers :on_worker_start_callbacks, :on_worker_shutdown_callbacks
19
- should have_readers :on_worker_sleep_callbacks, :on_worker_wakeup_callbacks
20
- should have_readers :before_work_callbacks, :after_work_callbacks
21
- should have_accessors :worker_available
22
- should have_imeths :worker_available?, :queue_empty?
23
- should have_imeths :add_work, :start, :shutdown
24
- should have_imeths :on_queue_pop, :on_queue_push
25
- should have_imeths :on_worker_error
26
- should have_imeths :on_worker_start, :on_worker_shutdown
27
- should have_imeths :on_worker_sleep, :on_worker_wakeup
28
- should have_imeths :before_work, :after_work
29
-
30
- should "know its work proc" do
31
- assert_equal @work_proc, subject.work_proc
37
+ should have_accessors :available_worker_count, :worker_available
38
+ should have_imeths :start, :shutdown
39
+ should have_imeths :add_work, :push, :work_items
40
+ should have_imeths :worker_available?
41
+
42
+ should "know its attributes" do
43
+ assert_equal @worker_class, subject.worker_class
44
+ assert_equal @options, subject.options
45
+ assert_equal @options[:num_workers], subject.num_workers
46
+ assert_equal @options[:logger], subject.logger
47
+ assert_equal @options[:queue], subject.queue
48
+ assert_equal @options[:worker_params], subject.worker_params
49
+ assert_equal 0, subject.available_worker_count
50
+
51
+ assert_false subject.worker_available?
52
+ assert_false subject.start_called
53
+ assert_false subject.shutdown_called
54
+ assert_nil subject.shutdown_timeout
32
55
  end
33
56
 
34
- should "have nothing in it's work items by default" do
35
- assert subject.work_items.empty?
57
+ should "default its attributes" do
58
+ worker_pool_spy = @spy_class.new(@worker_class)
59
+ assert_instance_of DatWorkerPool::DefaultQueue, worker_pool_spy.queue
60
+ assert_equal DatWorkerPool::DEFAULT_NUM_WORKERS, worker_pool_spy.num_workers
36
61
  end
37
62
 
38
- should "not have a worker available by default" do
39
- assert_equal false, subject.worker_available
40
- assert_not subject.worker_available?
63
+ should "allow setting its available worker count" do
64
+ integer = Factory.integer
65
+ subject.available_worker_count = integer
66
+ assert_equal integer, subject.available_worker_count
41
67
  end
42
68
 
43
- should "return false for start called by default" do
44
- assert_equal false, subject.start_called
69
+ should "allow setting whether a worker is available" do
70
+ subject.worker_available = true
71
+ assert_true subject.worker_available?
72
+ subject.worker_available = false
73
+ assert_false subject.worker_available?
45
74
  end
46
75
 
47
- should "return false for shutdown called by default" do
48
- assert_equal false, subject.shutdown_called
76
+ should "know if it's been started" do
77
+ assert_false subject.start_called
78
+ subject.start
79
+ assert_true subject.start_called
49
80
  end
50
81
 
51
- should "return `nil` for shutdown timeout by default" do
52
- assert_nil subject.shutdown_timeout
82
+ should "start its queue when it's been started" do
83
+ assert_false subject.queue.running?
84
+ subject.start
85
+ assert_true subject.queue.running?
53
86
  end
54
87
 
55
- should "allow setting whether a worker is available" do
56
- subject.worker_available = true
57
- assert_equal true, subject.worker_available
58
- assert subject.worker_available?
88
+ should "know if it's been shutdown" do
89
+ assert_false subject.shutdown_called
90
+ subject.shutdown
91
+ assert_true subject.shutdown_called
92
+ assert_nil subject.shutdown_timeout
59
93
  end
60
94
 
61
- should "allow adding work to the work items with #add_work" do
62
- subject.add_work 'work'
63
- assert_equal 1, subject.work_items.size
64
- assert_includes 'work', subject.work_items
65
- end
95
+ end
66
96
 
67
- should "not add `nil` work to the work items with #add_work" do
68
- subject.add_work nil
69
- assert_equal 0, subject.work_items.size
97
+ class StartedTests < InitTests
98
+ desc "and started"
99
+ setup do
100
+ @worker_pool_spy.start
70
101
  end
71
102
 
72
- should "return whether the work items is empty with #queue_empty?" do
73
- assert_equal true, subject.queue_empty?
74
- subject.add_work 'work'
75
- assert_equal false, subject.queue_empty?
103
+ should "shutdown its queue when it's been shutdown" do
104
+ assert_false subject.queue.shutdown?
105
+ subject.shutdown
106
+ assert_true subject.queue.shutdown?
76
107
  end
77
108
 
78
- should "know when it's been started" do
79
- subject.start
80
- assert_true subject.start_called
109
+ should "know if it's been shutdown with a timeout" do
110
+ timeout = Factory.integer
111
+ subject.shutdown(timeout)
112
+ assert_equal timeout, subject.shutdown_timeout
81
113
  end
82
114
 
83
- should "know when it's been shutdown and with what timeout" do
84
- subject.shutdown(10)
85
- assert_true subject.shutdown_called
86
- assert_equal 10, subject.shutdown_timeout
115
+ should "allow adding work to its queue" do
116
+ work_item = Factory.string
117
+ subject.add_work(work_item)
118
+ assert_equal work_item, subject.queue.work_items.last
119
+
120
+ work_item = Factory.string
121
+ subject.push(work_item)
122
+ assert_equal work_item, subject.queue.work_items.last
87
123
  end
88
124
 
89
- should "allow calling shutdown with no timeout" do
90
- subject.shutdown
91
- assert_true subject.shutdown_called
92
- assert_nil subject.shutdown_timeout
125
+ should "not allow adding `nil` work" do
126
+ subject.add_work(nil)
127
+ assert_equal 0, subject.queue.work_items.size
93
128
  end
94
129
 
95
- should "know its queue and worker callbacks" do
96
- assert_equal [], subject.on_queue_pop_callbacks
97
- callback = proc{ }
98
- subject.on_queue_pop(&callback)
99
- assert_equal [callback], subject.on_queue_pop_callbacks
100
-
101
- assert_equal [], subject.on_queue_push_callbacks
102
- callback = proc{ }
103
- subject.on_queue_push(&callback)
104
- assert_equal [callback], subject.on_queue_push_callbacks
105
-
106
- assert_equal [], subject.on_worker_error_callbacks
107
- callback = proc{ }
108
- subject.on_worker_error(&callback)
109
- assert_equal [callback], subject.on_worker_error_callbacks
110
-
111
- assert_equal [], subject.on_worker_start_callbacks
112
- callback = proc{ }
113
- subject.on_worker_start(&callback)
114
- assert_equal [callback], subject.on_worker_start_callbacks
115
-
116
- assert_equal [], subject.on_worker_shutdown_callbacks
117
- callback = proc{ }
118
- subject.on_worker_shutdown(&callback)
119
- assert_equal [callback], subject.on_worker_shutdown_callbacks
120
-
121
- assert_equal [], subject.on_worker_sleep_callbacks
122
- callback = proc{ }
123
- subject.on_worker_sleep(&callback)
124
- assert_equal [callback], subject.on_worker_sleep_callbacks
125
-
126
- assert_equal [], subject.on_worker_wakeup_callbacks
127
- callback = proc{ }
128
- subject.on_worker_wakeup(&callback)
129
- assert_equal [callback], subject.on_worker_wakeup_callbacks
130
-
131
- assert_equal [], subject.before_work_callbacks
132
- callback = proc{ }
133
- subject.before_work(&callback)
134
- assert_equal [callback], subject.before_work_callbacks
135
-
136
- assert_equal [], subject.after_work_callbacks
137
- callback = proc{ }
138
- subject.after_work(&callback)
139
- assert_equal [callback], subject.after_work_callbacks
130
+ should "know its queues work items" do
131
+ Factory.integer(3).times{ subject.queue.dwp_push(Factory.string) }
132
+ assert_equal subject.queue.work_items, subject.work_items
140
133
  end
141
134
 
142
135
  end
@@ -1,199 +1,865 @@
1
1
  require 'assert'
2
2
  require 'dat-worker-pool/worker'
3
3
 
4
- require 'dat-worker-pool'
5
- require 'dat-worker-pool/queue'
4
+ require 'system_timer'
5
+ require 'dat-worker-pool/default_queue'
6
+ require 'dat-worker-pool/runner'
7
+ require 'test/support/thread_spies'
6
8
 
7
- class DatWorkerPool::Worker
9
+ module DatWorkerPool::Worker
8
10
 
9
11
  class UnitTests < Assert::Context
10
12
  desc "DatWorkerPool::Worker"
11
13
  setup do
12
- @queue = DatWorkerPool::Queue.new
13
- @work_done = []
14
- @worker = DatWorkerPool::Worker.new(@queue).tap do |w|
15
- w.on_work = proc{ |worker, work| @work_done << work }
14
+ @worker_class = Class.new do
15
+ include DatWorkerPool::Worker
16
16
  end
17
17
  end
18
+ subject{ @worker_class }
19
+
20
+ should have_imeths :on_start_callbacks, :on_shutdown_callbacks
21
+ should have_imeths :on_available_callbacks, :on_unavailable_callbacks
22
+ should have_imeths :on_error_callbacks
23
+ should have_imeths :before_work_callbacks, :after_work_callbacks
24
+ should have_imeths :on_start, :on_shutdown
25
+ should have_imeths :on_available, :on_unavailable
26
+ should have_imeths :on_error
27
+ should have_imeths :before_work, :after_work
28
+ should have_imeths :prepend_on_start, :prepend_on_shutdown
29
+ should have_imeths :prepend_on_available, :prepend_on_unavailable
30
+ should have_imeths :prepend_on_error
31
+ should have_imeths :prepend_before_work, :prepend_after_work
32
+
33
+ should "not have any callbacks by default" do
34
+ assert_equal [], subject.on_start_callbacks
35
+ assert_equal [], subject.on_shutdown_callbacks
36
+ assert_equal [], subject.on_available_callbacks
37
+ assert_equal [], subject.on_unavailable_callbacks
38
+ assert_equal [], subject.on_error_callbacks
39
+ assert_equal [], subject.before_work_callbacks
40
+ assert_equal [], subject.after_work_callbacks
41
+ end
42
+
43
+ should "allow appending callbacks" do
44
+ callback = proc{ Factory.string }
45
+ # add a callback to each type to show we are appending
46
+ subject.on_start_callbacks << proc{ Factory.string }
47
+ subject.on_shutdown_callbacks << proc{ Factory.string }
48
+ subject.on_available_callbacks << proc{ Factory.string }
49
+ subject.on_unavailable_callbacks << proc{ Factory.string }
50
+ subject.on_error_callbacks << proc{ Factory.string }
51
+ subject.before_work_callbacks << proc{ Factory.string }
52
+ subject.after_work_callbacks << proc{ Factory.string }
53
+
54
+ subject.on_start(&callback)
55
+ assert_equal callback, subject.on_start_callbacks.last
56
+
57
+ subject.on_shutdown(&callback)
58
+ assert_equal callback, subject.on_shutdown_callbacks.last
59
+
60
+ subject.on_available(&callback)
61
+ assert_equal callback, subject.on_available_callbacks.last
62
+
63
+ subject.on_unavailable(&callback)
64
+ assert_equal callback, subject.on_unavailable_callbacks.last
65
+
66
+ subject.on_error(&callback)
67
+ assert_equal callback, subject.on_error_callbacks.last
68
+
69
+ subject.before_work(&callback)
70
+ assert_equal callback, subject.before_work_callbacks.last
71
+
72
+ subject.after_work(&callback)
73
+ assert_equal callback, subject.after_work_callbacks.last
74
+ end
75
+
76
+ should "allow prepending callbacks" do
77
+ callback = proc{ Factory.string }
78
+ # add a callback to each type to show we are appending
79
+ subject.on_start_callbacks << proc{ Factory.string }
80
+ subject.on_shutdown_callbacks << proc{ Factory.string }
81
+ subject.on_available_callbacks << proc{ Factory.string }
82
+ subject.on_unavailable_callbacks << proc{ Factory.string }
83
+ subject.on_error_callbacks << proc{ Factory.string }
84
+ subject.before_work_callbacks << proc{ Factory.string }
85
+ subject.after_work_callbacks << proc{ Factory.string }
86
+
87
+ subject.prepend_on_start(&callback)
88
+ assert_equal callback, subject.on_start_callbacks.first
89
+
90
+ subject.prepend_on_shutdown(&callback)
91
+ assert_equal callback, subject.on_shutdown_callbacks.first
92
+
93
+ subject.prepend_on_available(&callback)
94
+ assert_equal callback, subject.on_available_callbacks.first
95
+
96
+ subject.prepend_on_unavailable(&callback)
97
+ assert_equal callback, subject.on_unavailable_callbacks.first
98
+
99
+ subject.prepend_on_error(&callback)
100
+ assert_equal callback, subject.on_error_callbacks.first
101
+
102
+ subject.prepend_before_work(&callback)
103
+ assert_equal callback, subject.before_work_callbacks.first
104
+
105
+ subject.prepend_after_work(&callback)
106
+ assert_equal callback, subject.after_work_callbacks.first
107
+ end
108
+
109
+ end
110
+
111
+ class InitTests < UnitTests
112
+ desc "when init"
113
+ setup do
114
+ @mutex = Mutex.new
115
+ @cond_var = ConditionVariable.new
116
+
117
+ @queue = DatWorkerPool::DefaultQueue.new.tap(&:dwp_start)
118
+ @runner = DatWorkerPool::Runner.new({
119
+ :logger => TEST_LOGGER,
120
+ :queue => @queue,
121
+ :worker_params => {
122
+ :mutex => @mutex,
123
+ :cond_var => @cond_var
124
+ }
125
+ })
126
+ @number = Factory.integer(10)
127
+
128
+ @thread_spy = ThreadSpy.new
129
+ Assert.stub(Thread, :new) do |&block|
130
+ @thread_spy.tap{ |s| s.block = block }
131
+ end
132
+
133
+ @available_worker = nil
134
+ Assert.stub(@runner, :make_worker_available){ |w| @available_worker = w }
135
+ @unavailable_worker = nil
136
+ Assert.stub(@runner, :make_worker_unavailable){ |w| @unavailable_worker = w }
137
+
138
+ @worker = TestWorker.new(@runner, @queue, @number)
139
+ end
18
140
  teardown do
19
- @worker.shutdown
20
- @queue.shutdown
21
- @worker.join
141
+ shutdown_worker_queue_and_wait_for_thread_to_stop
22
142
  end
23
143
  subject{ @worker }
24
144
 
25
- should have_accessors :on_work, :on_error_callbacks
26
- should have_accessors :on_start_callbacks, :on_shutdown_callbacks
27
- should have_accessors :on_sleep_callbacks, :on_wakeup_callbacks
28
- should have_accessors :before_work_callbacks, :after_work_callbacks
29
- should have_imeths :start, :shutdown, :join, :raise, :running?
145
+ should have_readers :dwp_number
146
+ should have_imeths :dwp_start, :dwp_signal_shutdown
147
+ should have_imeths :dwp_running?, :dwp_shutdown?
148
+ should have_imeths :dwp_thread_alive?, :dwp_join, :dwp_raise
30
149
 
31
- should "default its callbacks" do
32
- worker = DatWorkerPool::Worker.new(@queue)
33
- assert_equal [], worker.on_error_callbacks
34
- assert_equal [], worker.on_start_callbacks
35
- assert_equal [], worker.on_shutdown_callbacks
36
- assert_equal [], worker.on_sleep_callbacks
37
- assert_equal [], worker.on_wakeup_callbacks
38
- assert_equal [], worker.before_work_callbacks
39
- assert_equal [], worker.after_work_callbacks
150
+ should "know its queue and params" do
151
+ assert_equal @number, subject.instance_eval{ number }
152
+ assert_equal @runner.worker_params, subject.instance_eval{ params }
153
+ assert_equal @queue, subject.instance_eval{ queue }
40
154
  end
41
155
 
42
- should "start a thread with it's work loop using `start`" do
43
- thread = nil
44
- assert_nothing_raised{ thread = subject.start }
45
- assert_instance_of Thread, thread
46
- assert thread.alive?
47
- assert subject.running?
156
+ should "start a thread when its started" do
157
+ thread = subject.dwp_start
158
+ wait_for_worker_to_be_available
159
+
160
+ assert_same @thread_spy, thread
161
+ assert_true thread.alive?
48
162
  end
49
163
 
50
- should "call the block it's passed when it get's work from the queue" do
51
- subject.start
52
- @queue.push 'one'
53
- subject.join 0.1 # trigger the worker's thread to run
54
- @queue.push 'two'
55
- subject.join 0.1 # trigger the worker's thread to run
56
- assert_equal [ 'one', 'two' ], @work_done
57
- end
58
-
59
- should "flag itself for exiting it's work loop using `shutdown` and " \
60
- "end it's thread once it's queue is shutdown" do
61
- thread = subject.start
62
- subject.join 0.1 # trigger the worker's thread to run, allow it to get into it's
63
- # work loop
64
- assert_nothing_raised{ subject.shutdown }
65
- @queue.shutdown
66
-
67
- subject.join 0.1 # trigger the worker's thread to run, should exit
68
- assert_not thread.alive?
69
- assert_not subject.running?
70
- end
71
-
72
- should "raise an error on the thread using `raise`" do
73
- subject.on_work = proc do |worker, work|
74
- begin
75
- sleep 1
76
- rescue RuntimeError => error
77
- @work_done << error
78
- raise error
79
- end
164
+ should "make itself available when started" do
165
+ subject.dwp_start
166
+ wait_for_worker_to_be_available
167
+
168
+ assert_same subject, @available_worker
169
+ assert_not_nil subject.first_on_available_call_order
170
+ assert_not_nil subject.second_on_available_call_order
171
+ end
172
+
173
+ should "know if its running and if its thread is alive or not" do
174
+ assert_false subject.dwp_running?
175
+ assert_false subject.dwp_thread_alive?
176
+
177
+ subject.dwp_start
178
+ wait_for_worker_to_be_available
179
+
180
+ assert_true subject.dwp_running?
181
+ assert_true subject.dwp_thread_alive?
182
+
183
+ subject.dwp_signal_shutdown
184
+ assert_false subject.dwp_running?
185
+ assert_true subject.dwp_thread_alive?
186
+
187
+ shutdown_worker_queue_and_wait_for_thread_to_stop
188
+ assert_false subject.dwp_running?
189
+ assert_false subject.dwp_thread_alive?
190
+ end
191
+
192
+ should "make itself unavailable when its thread stops" do
193
+ subject.dwp_start
194
+ wait_for_worker_to_be_available
195
+
196
+ shutdown_worker_queue_and_wait_for_thread_to_stop
197
+ assert_same subject, @unavailable_worker
198
+ assert_not_nil subject.first_on_unavailable_call_order
199
+ assert_not_nil subject.second_on_unavailable_call_order
200
+ end
201
+
202
+ should "allow joining and raising on its thread" do
203
+ subject.dwp_start
204
+ wait_for_worker_to_be_available
205
+
206
+ subject.dwp_join(0.1)
207
+ assert_equal 0.1, @thread_spy.join_seconds
208
+ assert_true @thread_spy.join_called
209
+
210
+ exception = Factory.exception
211
+ subject.dwp_raise(exception)
212
+ assert_equal exception, @thread_spy.raised_exception
213
+ end
214
+
215
+ should "not allow joining or raising if it hasn't been started" do
216
+ assert_nothing_raised{ subject.dwp_join(Factory.integer) }
217
+ assert_false @thread_spy.join_called
218
+ assert_nothing_raised{ subject.dwp_raise(Factory.exception) }
219
+ assert_nil @thread_spy.raised_exception
220
+ end
221
+
222
+ should "make itself available and unavailable while running" do
223
+ subject.dwp_start
224
+ wait_for_worker_to_be_available
225
+
226
+ @queue.dwp_push(Factory.string)
227
+ wait_for_worker_to_work_and_then_be_available
228
+
229
+ assert_same subject, @unavailable_worker
230
+ assert_same subject, @available_worker
231
+ end
232
+
233
+ should "call its on-available and on-unavailable callbacks while running" do
234
+ subject.dwp_start
235
+ wait_for_worker_to_be_available
236
+
237
+ @queue.dwp_push(Factory.string)
238
+ wait_for_worker_to_work_and_then_be_available
239
+
240
+ assert_not_nil subject.first_on_unavailable_call_order
241
+ assert_not_nil subject.second_on_unavailable_call_order
242
+ assert_not_nil subject.first_on_available_call_order
243
+ assert_not_nil subject.second_on_available_call_order
244
+ end
245
+
246
+ should "make itself available/unavailable and run callbacks if it errors" do
247
+ # these are the only errors that could interfere with it
248
+ error_method = [:on_unavailable_error, :work_error].choice
249
+ exception = Factory.exception
250
+ subject.send("#{error_method}=", exception)
251
+
252
+ subject.dwp_start
253
+ wait_for_worker_to_be_available
254
+
255
+ @queue.dwp_push(Factory.string)
256
+ wait_for_worker_to_error_and_then_be_available
257
+
258
+ assert_equal exception, subject.on_error_exception
259
+ assert_same subject, @unavailable_worker
260
+ assert_not_nil subject.first_on_unavailable_call_order
261
+ assert_same subject, @available_worker
262
+ assert_not_nil subject.first_on_available_call_order
263
+ end
264
+
265
+ should "call its `work` method on any pushed items while running" do
266
+ assert_nil subject.item_worked_on
267
+ subject.dwp_start
268
+ wait_for_worker_to_be_available
269
+ assert_nil subject.item_worked_on
270
+
271
+ work_item = Factory.string
272
+ @queue.dwp_push(work_item)
273
+ wait_for_worker_to_work_and_then_be_available
274
+
275
+ assert_same work_item, subject.before_work_item_worked_on
276
+ assert_same work_item, subject.item_worked_on
277
+ assert_same work_item, subject.after_work_item_worked_on
278
+ end
279
+
280
+ should "not call its `work` method if it pops a `nil` work item" do
281
+ subject.dwp_start
282
+ wait_for_worker_to_be_available
283
+
284
+ @queue.dwp_push(nil)
285
+ # we don't have any event to listen for because it should ignore `nil`
286
+ # work items
287
+ @thread_spy.join(JOIN_SECONDS)
288
+ assert_false subject.work_called
289
+
290
+ # when the queue is shutdown it returns `nil`, so we shouldn't call `work`
291
+ # when shutting down
292
+ shutdown_worker_queue_and_wait_for_thread_to_stop
293
+ assert_false subject.work_called
294
+ end
295
+
296
+ should "run its on-error callbacks if it errors while starting" do
297
+ exception = Factory.exception
298
+ error_method = [:on_start_error, :on_available_error].choice
299
+ subject.send("#{error_method}=", exception)
300
+
301
+ subject.dwp_start
302
+ wait_for_worker_thread_to_stop_and_rescue_if_expected_error(exception)
303
+
304
+ assert_equal exception, subject.on_error_exception
305
+ assert_nil subject.on_error_work_item
306
+ end
307
+
308
+ should "stop its thread if it errors while starting" do
309
+ exception = Factory.exception
310
+ error_method = [:on_start_error, :on_available_error].choice
311
+ subject.send("#{error_method}=", exception)
312
+
313
+ subject.dwp_start
314
+ wait_for_worker_thread_to_stop_and_rescue_if_expected_error(exception)
315
+
316
+ assert_false subject.dwp_thread_alive?
317
+ assert_false subject.dwp_running?
318
+ end
319
+
320
+ should "run its on-error callbacks if it errors while shutting down" do
321
+ exception = Factory.exception
322
+ error_method = [:on_shutdown_error, :on_unavailable_error].choice
323
+ subject.send("#{error_method}=", exception)
324
+
325
+ subject.dwp_start
326
+ shutdown_worker_and_queue
327
+ wait_for_worker_thread_to_stop_and_rescue_if_expected_error(exception)
328
+
329
+ assert_equal exception, subject.on_error_exception
330
+ assert_nil subject.on_error_work_item
331
+ end
332
+
333
+ should "not stop its thread when an error occurs while running" do
334
+ subject.dwp_start
335
+ wait_for_worker_to_be_available
336
+
337
+ exception = Factory.exception
338
+ setup_work_loop_to_raise_exception(exception)
339
+
340
+ @queue.dwp_push(Factory.string)
341
+ wait_for_worker_to_error_and_then_be_available
342
+
343
+ assert_true subject.dwp_thread_alive?
344
+ end
345
+
346
+ should "run its on-error callbacks if an error occurs while running" do
347
+ exception = Factory.exception
348
+ subject.dwp_start
349
+ wait_for_worker_to_be_available
350
+
351
+ setup_work_loop_to_raise_exception(exception)
352
+ work_item = Factory.string
353
+ @queue.dwp_push(work_item)
354
+ wait_for_worker_to_error_and_then_be_available
355
+
356
+ assert_equal exception, subject.on_error_exception
357
+ assert_equal work_item, subject.on_error_work_item
358
+ end
359
+
360
+ should "not stop its thread if an error occurs in an on-error callback" do
361
+ exception = Factory.exception
362
+ subject.dwp_start
363
+ wait_for_worker_to_be_available
364
+
365
+ setup_work_loop_to_raise_exception(Factory.exception)
366
+ subject.on_error_error = exception
367
+ work_item = Factory.string
368
+ @queue.dwp_push(work_item)
369
+ wait_for_worker_to_error_and_then_be_available
370
+
371
+ assert_true subject.dwp_thread_alive?
372
+ end
373
+
374
+ should "stop its thread when a shutdown error is raised while running" do
375
+ exception = Factory.exception(DatWorkerPool::ShutdownError)
376
+ subject.dwp_start
377
+ wait_for_worker_to_be_available
378
+
379
+ setup_work_loop_to_raise_exception(exception)
380
+ @queue.dwp_push(Factory.string)
381
+
382
+ wait_for_worker_thread_to_stop_and_rescue_if_expected_error(exception)
383
+ assert_false subject.dwp_thread_alive?
384
+ end
385
+
386
+ should "stop its thread when a shutdown error is raised in an on-error callback" do
387
+ exception = Factory.exception(DatWorkerPool::ShutdownError)
388
+ subject.dwp_start
389
+ wait_for_worker_to_be_available
390
+
391
+ setup_work_loop_to_raise_exception(Factory.exception)
392
+ subject.on_error_error = exception
393
+ @queue.dwp_push(Factory.string)
394
+
395
+ wait_for_worker_thread_to_stop_and_rescue_if_expected_error(exception)
396
+ assert_false subject.dwp_thread_alive?
397
+ end
398
+
399
+ should "only run its on-error callbacks when shutdown error is raised with a work item" do
400
+ exception = Factory.exception(DatWorkerPool::ShutdownError)
401
+ subject.dwp_start
402
+ wait_for_worker_to_be_available
403
+
404
+ error_method = ERROR_METHODS.reject{ |e| e == :on_available_error }.choice
405
+ subject.send("#{error_method}=", exception)
406
+ work_item = Factory.string
407
+ @queue.dwp_push(work_item)
408
+ wait_for_worker_thread_to_stop_and_rescue_if_expected_error(exception)
409
+
410
+ assert_equal exception, subject.on_error_exception
411
+ assert_equal work_item, subject.on_error_work_item
412
+ end
413
+
414
+ should "run callbacks when its started" do
415
+ assert_nil subject.first_on_start_call_order
416
+ assert_nil subject.second_on_start_call_order
417
+ assert_nil subject.first_on_available_call_order
418
+ assert_nil subject.second_on_available_call_order
419
+
420
+ subject.dwp_start
421
+ wait_for_worker_to_be_available
422
+
423
+ assert_equal 1, subject.first_on_start_call_order
424
+ assert_equal 2, subject.second_on_start_call_order
425
+ assert_equal 3, subject.first_on_available_call_order
426
+ assert_equal 4, subject.second_on_available_call_order
427
+ end
428
+
429
+ should "run its callbacks when work is pushed" do
430
+ subject.dwp_start
431
+ wait_for_worker_to_be_available
432
+ subject.reset_call_order
433
+
434
+ assert_nil subject.first_on_unavailable_call_order
435
+ assert_nil subject.second_on_unavailable_call_order
436
+ assert_nil subject.first_before_work_call_order
437
+ assert_nil subject.second_before_work_call_order
438
+ assert_nil subject.work_call_order
439
+ assert_nil subject.first_after_work_call_order
440
+ assert_nil subject.second_after_work_call_order
441
+ assert_nil subject.first_on_available_call_order
442
+ assert_nil subject.second_on_available_call_order
443
+
444
+ @queue.dwp_push(Factory.string)
445
+ wait_for_worker_to_work_and_then_be_available
446
+
447
+ assert_equal 1, subject.first_on_unavailable_call_order
448
+ assert_equal 2, subject.second_on_unavailable_call_order
449
+ assert_equal 3, subject.first_before_work_call_order
450
+ assert_equal 4, subject.second_before_work_call_order
451
+ assert_equal 5, subject.work_call_order
452
+ assert_equal 6, subject.first_after_work_call_order
453
+ assert_equal 7, subject.second_after_work_call_order
454
+ assert_equal 8, subject.first_on_available_call_order
455
+ assert_equal 9, subject.second_on_available_call_order
456
+ end
457
+
458
+ should "run callbacks when its shutdown" do
459
+ subject.dwp_start
460
+ wait_for_worker_to_be_available
461
+ subject.reset_call_order
462
+
463
+ assert_nil subject.first_on_unavailable_call_order
464
+ assert_nil subject.second_on_unavailable_call_order
465
+ assert_nil subject.first_on_shutdown_call_order
466
+ assert_nil subject.second_on_shutdown_call_order
467
+
468
+ shutdown_worker_queue_and_wait_for_thread_to_stop
469
+
470
+ assert_equal 1, subject.first_on_unavailable_call_order
471
+ assert_equal 2, subject.second_on_unavailable_call_order
472
+ assert_equal 3, subject.first_on_shutdown_call_order
473
+ assert_equal 4, subject.second_on_shutdown_call_order
474
+ end
475
+
476
+ should "run its callbacks when an error occurs while making itself unavailable" do
477
+ subject.dwp_start
478
+ wait_for_worker_to_be_available
479
+ subject.reset_call_order
480
+
481
+ subject.on_unavailable_error = Factory.exception
482
+ @queue.dwp_push(Factory.string)
483
+ wait_for_worker_to_error_and_then_be_available
484
+
485
+ assert_equal 1, subject.first_on_unavailable_call_order
486
+ assert_equal 2, subject.on_error_call_order
487
+ assert_equal 3, subject.first_on_available_call_order
488
+ assert_equal 4, subject.second_on_available_call_order
489
+ assert_nil subject.second_on_unavailable_call_order
490
+ assert_nil subject.first_before_work_call_order
491
+ assert_nil subject.second_before_work_call_order
492
+ assert_nil subject.work_call_order
493
+ assert_nil subject.first_after_work_call_order
494
+ assert_nil subject.second_after_work_call_order
495
+ end
496
+
497
+ should "run its callbacks when an error occurs while working" do
498
+ subject.dwp_start
499
+ wait_for_worker_to_be_available
500
+ subject.reset_call_order
501
+
502
+ subject.work_error = Factory.exception
503
+ @queue.dwp_push(Factory.string)
504
+ wait_for_worker_to_error_and_then_be_available
505
+
506
+ assert_equal 1, subject.first_on_unavailable_call_order
507
+ assert_equal 2, subject.second_on_unavailable_call_order
508
+ assert_equal 3, subject.first_before_work_call_order
509
+ assert_equal 4, subject.second_before_work_call_order
510
+ assert_equal 5, subject.on_error_call_order
511
+ assert_equal 6, subject.first_on_available_call_order
512
+ assert_equal 7, subject.second_on_available_call_order
513
+ assert_nil subject.work_call_order
514
+ assert_nil subject.first_after_work_call_order
515
+ assert_nil subject.second_after_work_call_order
516
+ end
517
+
518
+ should "run its callbacks when an error occurs while making itself available" do
519
+ subject.dwp_start
520
+ wait_for_worker_to_be_available
521
+ subject.reset_call_order
522
+
523
+ subject.on_available_error = Factory.exception
524
+ @queue.dwp_push(Factory.string)
525
+ wait_for_worker_to_error_and_then_be_available
526
+
527
+ assert_equal 1, subject.first_on_unavailable_call_order
528
+ assert_equal 2, subject.second_on_unavailable_call_order
529
+ assert_equal 3, subject.first_before_work_call_order
530
+ assert_equal 4, subject.second_before_work_call_order
531
+ assert_equal 5, subject.work_call_order
532
+ assert_equal 6, subject.first_after_work_call_order
533
+ assert_equal 7, subject.second_after_work_call_order
534
+ assert_equal 8, subject.first_on_available_call_order
535
+ assert_equal 9, subject.on_error_call_order
536
+ assert_nil subject.second_on_available_call_order
537
+ end
538
+
539
+ ERROR_METHODS = [
540
+ :on_available_error,
541
+ :on_unavailable_error,
542
+ :before_work_error,
543
+ :work_error,
544
+ :after_work_error
545
+ ].freeze
546
+ def setup_work_loop_to_raise_exception(exception)
547
+ error_method = ERROR_METHODS.choice
548
+ @worker.send("#{error_method}=", exception)
549
+ error_method
550
+ end
551
+
552
+ # this could loop forever so ensure it doesn't by using a timeout; use
553
+ # timeout instead of system timer because system timer is paranoid about a
554
+ # deadlock even though its intended to prevent the deadlock because it times
555
+ # out the block
556
+ def wait_for_worker(&block)
557
+ Timeout.timeout(1) do
558
+ @mutex.synchronize{ @cond_var.wait(@mutex) } while !block.call
80
559
  end
81
- subject.start
82
- @queue.push 'a'
83
- subject.join 0.1 # trigger the worker's thread to run
560
+ end
84
561
 
85
- exception = RuntimeError.new
86
- subject.raise exception
87
- assert_equal [exception], @work_done
562
+ def wait_for_worker_to_be_available
563
+ wait_for_worker{ @worker.first_on_available_call_order }
564
+ end
565
+
566
+ def wait_for_worker_to_work_and_then_be_available
567
+ wait_for_worker do
568
+ @worker.work_called && @worker.first_on_available_call_order
569
+ end
570
+ end
571
+
572
+ def wait_for_worker_to_error_and_then_be_available
573
+ wait_for_worker do
574
+ @worker.on_error_exception && @worker.first_on_available_call_order
575
+ end
576
+ end
577
+
578
+ def shutdown_worker_queue_and_wait_for_thread_to_stop
579
+ shutdown_worker_and_queue
580
+ wait_for_worker_thread_to_stop
581
+ end
582
+
583
+ def shutdown_worker_and_queue
584
+ @worker.dwp_signal_shutdown
585
+ @queue.dwp_shutdown
586
+ end
587
+
588
+ def wait_for_worker_thread_to_stop
589
+ return unless @worker.dwp_thread_alive?
590
+ Timeout.timeout(1){ @worker.dwp_join }
591
+ end
592
+
593
+ # this is needed because errors will be re-raised when the thread is joined
594
+ # and in some cases we expect these errors because we are manually raising
595
+ # them, this checks if they are the expected exception and won't re-raise
596
+ # them; to check if they are the expected exception, we have to use the
597
+ # class and message because when the thread raises an error on join it is a
598
+ # different instance with a different backtrace (so we can't use `==`)
599
+ def wait_for_worker_thread_to_stop_and_rescue_if_expected_error(exception)
600
+ begin
601
+ wait_for_worker_thread_to_stop
602
+ rescue exception.class => caught_exception
603
+ unless caught_exception.class == exception.class &&
604
+ caught_exception.message == exception.message
605
+ raise(caught_exception)
606
+ end
607
+ end
88
608
  end
89
609
 
90
610
  end
91
611
 
92
- class CallbacksTests < UnitTests
93
- desc "callbacks"
612
+ class TestHelperTests < UnitTests
613
+ desc "TestHelpers"
94
614
  setup do
95
- @call_counter = 0
96
- @on_error_called_with = nil
97
- @on_start_called_with = nil
98
- @on_start_called_at = nil
99
- @on_shutdown_called_with = nil
100
- @on_shutdown_called_at = nil
101
- @on_sleep_called_with = nil
102
- @on_sleep_called_at = nil
103
- @on_wakeup_called_with = nil
104
- @on_wakeup_called_at = nil
105
- @before_work_called_with = nil
106
- @before_work_called_at = nil
107
- @after_work_called_with = nil
108
- @after_work_called_at = nil
109
- @worker = DatWorkerPool::Worker.new(@queue).tap do |w|
110
- w.on_error_callbacks << proc do |*args|
111
- @on_error_called_with = args
112
- end
113
- w.on_start_callbacks << proc do |*args|
114
- @on_start_called_with = args
115
- @on_start_called_at = (@call_counter += 1)
116
- end
117
- w.on_shutdown_callbacks << proc do |*args|
118
- @on_shutdown_called_with = args
119
- @on_shutdown_called_at = (@call_counter += 1)
120
- end
121
- w.on_sleep_callbacks << proc do |*args|
122
- @on_sleep_called_with = args
123
- @on_sleep_called_at = (@call_counter += 1)
124
- end
125
- w.on_wakeup_callbacks << proc do |*args|
126
- @on_wakeup_called_with = args
127
- @on_wakeup_called_at = (@call_counter += 1)
128
- end
129
- w.before_work_callbacks << proc do |*args|
130
- @before_work_called_with = args
131
- @before_work_called_at = (@call_counter += 1)
132
- end
133
- w.after_work_callbacks << proc do |*args|
134
- @after_work_called_with = args
135
- @after_work_called_at = (@call_counter += 1)
136
- end
615
+ @mutex = Mutex.new
616
+ @cond_var = ConditionVariable.new
617
+
618
+ @worker_class = TestWorker
619
+ @options = {
620
+ :logger => TEST_LOGGER || Logger.new("/dev/null"),
621
+ :queue => DatWorkerPool::DefaultQueue.new,
622
+ :params => {
623
+ :mutex => @mutex,
624
+ :cond_var => @cond_var
625
+ }
626
+ }
627
+
628
+ @context_class = Class.new do
629
+ include DatWorkerPool::Worker::TestHelpers
137
630
  end
631
+ @context = @context_class.new
138
632
  end
633
+ subject{ @context }
139
634
 
140
- should "pass its self to its start, shutdown, sleep and wakeup callbacks" do
141
- subject.start
142
- @queue.push('work')
143
- subject.shutdown
144
- @queue.shutdown
635
+ should have_imeths :test_runner
145
636
 
146
- assert_equal [subject], @on_start_called_with
147
- assert_equal [subject], @on_shutdown_called_with
148
- assert_equal [subject], @on_sleep_called_with
149
- assert_equal [subject], @on_wakeup_called_with
637
+ should "build a test runner using `test_runner`" do
638
+ test_runner = subject.test_runner(@worker_class, @options)
639
+
640
+ assert_instance_of TestHelpers::TestRunner, test_runner
641
+ assert_equal @worker_class, test_runner.worker_class
642
+ assert_equal @options[:queue], test_runner.queue
643
+ assert_equal @options[:params], test_runner.dwp_runner.worker_params
150
644
  end
151
645
 
152
- should "pass its self and work to its before and after work callbacks" do
153
- subject.start
154
- @queue.push('work')
155
- subject.shutdown
156
- @queue.shutdown
646
+ end
647
+
648
+ class TestRunnerTests < TestHelperTests
649
+ desc "TestRunner"
650
+ setup do
651
+ @test_runner = TestHelpers::TestRunner.new(@worker_class, @options)
652
+
653
+ dwp_runner = @test_runner.dwp_runner
654
+ @unavailable_worker = nil
655
+ Assert.stub(dwp_runner, :make_worker_unavailable){ |w| @unavailable_worker = w }
656
+ @available_worker = nil
657
+ Assert.stub(dwp_runner, :make_worker_available){ |w| @available_worker = w }
658
+ end
659
+ subject{ @test_runner }
660
+
661
+ should have_readers :worker_class, :worker
662
+ should have_readers :queue, :dwp_runner
663
+ should have_imeths :run, :work, :error
664
+ should have_imeths :start, :shutdown
665
+ should have_imeths :make_unavailable, :make_available
666
+
667
+ should "know its attributes" do
668
+ assert_equal @worker_class, subject.worker_class
669
+ assert_equal @options[:queue], subject.queue
670
+ end
157
671
 
158
- assert_equal [subject, 'work'], @before_work_called_with
159
- assert_equal [subject, 'work'], @after_work_called_with
672
+ should "build a dat-worker-pool runner" do
673
+ dwp_runner = subject.dwp_runner
674
+ assert_instance_of DatWorkerPool::Runner, dwp_runner
675
+ assert_equal DatWorkerPool::MIN_WORKERS, dwp_runner.num_workers
676
+ assert_equal subject.queue, dwp_runner.queue
677
+ assert_equal @options[:logger], dwp_runner.logger_proxy.logger
678
+ assert_equal subject.worker_class, dwp_runner.worker_class
679
+ assert_equal @options[:params], dwp_runner.worker_params
160
680
  end
161
681
 
162
- should "call its callbacks throughout its lifecycle" do
682
+ should "build a worker" do
683
+ assert_instance_of @worker_class, subject.worker
684
+ assert_equal 1, subject.worker.dwp_number
685
+ end
686
+
687
+ should "run a workers life-cycle using `run`" do
688
+ work_item = Factory.string
689
+ subject.run(work_item)
690
+
691
+ worker = subject.worker
692
+ assert_not_nil worker.first_on_start_call_order
693
+ assert_same worker, @unavailable_worker
694
+ assert_not_nil worker.first_on_unavailable_call_order
695
+ assert_equal work_item, worker.before_work_item_worked_on
696
+ assert_equal work_item, worker.item_worked_on
697
+ assert_equal work_item, worker.after_work_item_worked_on
698
+ assert_same worker, @available_worker
699
+ assert_not_nil worker.first_on_available_call_order
700
+ assert_not_nil worker.first_on_shutdown_call_order
701
+ end
702
+
703
+ should "call its workers work method using `work`" do
704
+ work_item = Factory.string
705
+ subject.work(work_item)
706
+
707
+ worker = subject.worker
708
+ assert_equal work_item, worker.before_work_item_worked_on
709
+ assert_equal work_item, worker.item_worked_on
710
+ assert_equal work_item, worker.after_work_item_worked_on
711
+ end
712
+
713
+ should "call its workers on-error callbacks using `error`" do
714
+ exception = Factory.exception
715
+ subject.error(exception)
716
+
717
+ worker = subject.worker
718
+ assert_equal exception, worker.on_error_exception
719
+ assert_nil worker.on_error_work_item
720
+
721
+ work_item = Factory.string
722
+ subject.error(exception, work_item)
723
+ assert_equal work_item, worker.on_error_work_item
724
+ end
725
+
726
+ should "call its workers on-start callbacks using `start`" do
163
727
  subject.start
164
- assert_equal 1, @on_start_called_at
165
- assert_equal 2, @on_sleep_called_at
166
- @queue.push('work')
167
- assert_equal 3, @on_wakeup_called_at
168
- assert_equal 4, @before_work_called_at
169
- assert_equal 5, @after_work_called_at
170
- assert_equal 6, @on_sleep_called_at
728
+ assert_not_nil subject.worker.first_on_start_call_order
729
+ end
730
+
731
+ should "call its workers on-shutdown callbacks using `shutdown`" do
171
732
  subject.shutdown
172
- @queue.shutdown
173
- assert_equal 7, @on_wakeup_called_at
174
- assert_equal 8, @on_shutdown_called_at
733
+ assert_not_nil subject.worker.first_on_shutdown_call_order
175
734
  end
176
735
 
177
- should "call its error callbacks when an exception occurs" do
178
- exception = RuntimeError.new
179
- subject.on_work = proc{ raise exception }
180
- thread = subject.start
181
- @queue.push('work')
182
- assert_equal [subject, exception, 'work'], @on_error_called_with
183
- assert_true thread.alive?
736
+ should "call its workers make unavailable method using `make_unavailable`" do
737
+ subject.make_unavailable
738
+ assert_same subject.worker, @unavailable_worker
739
+ assert_not_nil subject.worker.first_on_unavailable_call_order
184
740
  end
185
741
 
186
- should "call its error callbacks when an shutdown error occurs and reraise" do
187
- exception = DatWorkerPool::ShutdownError.new
188
- subject.on_work = proc{ raise exception }
189
- thread = subject.start
190
- @queue.push('work')
191
- assert_equal [subject, exception, 'work'], @on_error_called_with
192
- assert_false thread.alive?
193
- # ensure the shutdown error is handled and isn't thrown when we join
194
- assert_nothing_raised{ thread.join }
742
+ should "call its workers make available method using `make_available`" do
743
+ subject.make_available
744
+ assert_same subject.worker, @available_worker
745
+ assert_not_nil subject.worker.first_on_available_call_order
195
746
  end
196
747
 
197
748
  end
198
749
 
750
+ class TestWorker
751
+ include DatWorkerPool::Worker
752
+
753
+ attr_reader :first_on_start_call_order, :second_on_start_call_order
754
+ attr_reader :first_on_shutdown_call_order, :second_on_shutdown_call_order
755
+ attr_reader :first_on_available_call_order, :second_on_available_call_order
756
+ attr_reader :first_on_unavailable_call_order, :second_on_unavailable_call_order
757
+ attr_reader :first_before_work_call_order, :second_before_work_call_order
758
+ attr_reader :first_after_work_call_order, :second_after_work_call_order
759
+ attr_reader :work_call_order, :on_error_call_order
760
+
761
+ attr_reader :before_work_item_worked_on, :after_work_item_worked_on
762
+ attr_reader :item_worked_on
763
+
764
+ attr_accessor :on_start_error, :on_shutdown_error
765
+ attr_accessor :on_available_error, :on_unavailable_error
766
+ attr_accessor :before_work_error, :after_work_error
767
+ attr_accessor :work_error, :on_error_error
768
+
769
+ attr_reader :on_error_exception, :on_error_work_item
770
+
771
+ on_start{ @first_on_start_call_order = next_call_order }
772
+ on_start do
773
+ raise_error_if_set(:on_start)
774
+ @second_on_start_call_order = next_call_order
775
+ end
776
+
777
+ on_shutdown{ @first_on_shutdown_call_order = next_call_order }
778
+ on_shutdown do
779
+ raise_error_if_set(:on_shutdown)
780
+ @second_on_shutdown_call_order = next_call_order
781
+ end
782
+
783
+ on_available{ @first_on_available_call_order = next_call_order }
784
+ on_available{ signal_test_suite_thread }
785
+ on_available do
786
+ raise_error_if_set(:on_available)
787
+ @second_on_available_call_order = next_call_order
788
+ end
789
+
790
+ on_unavailable{ @first_on_unavailable_call_order = next_call_order }
791
+ on_unavailable do
792
+ raise_error_if_set(:on_unavailable)
793
+ @second_on_unavailable_call_order = next_call_order
794
+ end
795
+
796
+ before_work{ |work_item| @first_before_work_call_order = next_call_order }
797
+ before_work do |work_item|
798
+ raise_error_if_set(:before_work)
799
+ @before_work_item_worked_on = work_item
800
+ @second_before_work_call_order = next_call_order
801
+ end
802
+
803
+ after_work{ |work_item| @first_after_work_call_order = next_call_order }
804
+ after_work do |work_item|
805
+ raise_error_if_set(:after_work)
806
+ @after_work_item_worked_on = work_item
807
+ @second_after_work_call_order = next_call_order
808
+ end
809
+
810
+ on_error do |exception, work_item|
811
+ @on_error_exception = exception
812
+ @on_error_work_item = work_item
813
+ end
814
+ on_error{ signal_test_suite_thread }
815
+ on_error do
816
+ raise_error_if_set(:on_error)
817
+ @on_error_call_order = next_call_order
818
+ end
819
+
820
+
821
+ def work_called; !!@work_called; end
822
+
823
+ def reset_call_order
824
+ @order = 0
825
+ @first_on_start_call_order = nil
826
+ @second_on_start_call_order = nil
827
+ @first_on_shutdown_call_order = nil
828
+ @second_on_shutdown_call_order = nil
829
+ @first_on_available_call_order = nil
830
+ @second_on_available_call_order = nil
831
+ @first_on_unavailable_call_order = nil
832
+ @second_on_unavailable_call_order = nil
833
+ @first_before_work_call_order = nil
834
+ @second_before_work_call_order = nil
835
+ @first_after_work_call_order = nil
836
+ @second_after_work_call_order = nil
837
+ @work_call_order = nil
838
+ end
839
+
840
+ private
841
+
842
+ def work!(work_item)
843
+ raise_error_if_set(:work)
844
+ @work_called = true
845
+ @item_worked_on = work_item
846
+ @work_call_order = next_call_order
847
+ end
848
+
849
+ def next_call_order; @order = (@order || 0) + 1; end
850
+
851
+ # we want to unset the error method if its set to avoid the thread looping
852
+ # very quickly because it doesn't available on `queue.pop`
853
+ def raise_error_if_set(type)
854
+ if (error = self.send("#{type}_error"))
855
+ self.send("#{type}_error=", nil)
856
+ Thread.current.raise error
857
+ end
858
+ end
859
+
860
+ def signal_test_suite_thread
861
+ params[:mutex].synchronize{ params[:cond_var].signal }
862
+ end
863
+ end
864
+
199
865
  end