dat-worker-pool 0.5.0 → 0.6.0

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.
@@ -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