1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267
|
require_relative 'concern/dereferenceable_shared'
require_relative 'concern/observable_shared'
module Concurrent
RSpec.describe TimerTask do
context :dereferenceable do
def kill_subject
@subject.kill if defined?(@subject) && @subject
rescue Exception
# prevent exceptions with mocks in tests
end
after(:each) do
kill_subject
end
def dereferenceable_subject(value, opts = {})
kill_subject
opts = opts.merge(execution_interval: 0.1, run_now: true)
@subject = TimerTask.new(opts) { value }.execute.tap { sleep(0.1) }
end
def dereferenceable_observable(opts = {})
opts = opts.merge(execution_interval: 0.1, run_now: true)
@subject = TimerTask.new(opts) { 'value' }
end
def execute_dereferenceable(subject)
subject.execute
sleep(0.1)
end
it_should_behave_like :dereferenceable
end
context :observable do
subject { TimerTask.new(execution_interval: 0.1) { nil } }
after(:each) { subject.kill }
def trigger_observable(observable)
observable.execute
sleep(0.2)
end
it_should_behave_like :observable
end
context 'created with #new' do
context '#initialize' do
it 'raises an exception if no block given' do
expect {
Concurrent::TimerTask.new
}.to raise_error(ArgumentError)
end
it 'raises an exception if :execution_interval is not greater than zero' do
expect {
Concurrent::TimerTask.new(execution_interval: 0) { nil }
}.to raise_error(ArgumentError)
end
it 'raises an exception if :execution_interval is not an integer' do
expect {
Concurrent::TimerTask.new(execution_interval: 'one') { nil }
}.to raise_error(ArgumentError)
end
it 'raises an exception if :timeout_interval is not greater than zero' do
expect {
Concurrent::TimerTask.new(timeout_interval: 0) { nil }
}.to raise_error(ArgumentError)
end
it 'raises an exception if :timeout_interval is not an integer' do
expect {
Concurrent::TimerTask.new(timeout_interval: 'one') { nil }
}.to raise_error(ArgumentError)
end
it 'uses the default execution interval when no interval is given' do
subject = TimerTask.new { nil }
expect(subject.execution_interval).to eq TimerTask::EXECUTION_INTERVAL
end
it 'uses the default timeout interval when no interval is given' do
subject = TimerTask.new { nil }
expect(subject.timeout_interval).to eq TimerTask::TIMEOUT_INTERVAL
end
it 'uses the given execution interval' do
subject = TimerTask.new(execution_interval: 5) { nil }
expect(subject.execution_interval).to eq 5
end
it 'uses the given timeout interval' do
subject = TimerTask.new(timeout_interval: 5) { nil }
expect(subject.timeout_interval).to eq 5
end
end
context '#kill' do
it 'returns true on success' do
task = TimerTask.execute(run_now: false) { nil }
sleep(0.1)
expect(task.kill).to be_truthy
end
end
context '#shutdown' do
it 'returns true on success' do
task = TimerTask.execute(run_now: false) { nil }
sleep(0.1)
expect(task.shutdown).to be_truthy
end
end
end
context 'arguments' do
it 'raises an exception if no block given' do
expect {
Concurrent::TimerTask.execute
}.to raise_error(ArgumentError)
end
specify '#execution_interval is writeable' do
latch = CountDownLatch.new(1)
subject = TimerTask.new(timeout_interval: 1,
execution_interval: 1,
run_now: true) do |task|
task.execution_interval = 3
latch.count_down
end
expect(subject.execution_interval).to eq(1)
subject.execution_interval = 0.1
expect(subject.execution_interval).to eq(0.1)
subject.execute
latch.wait(0.2)
expect(subject.execution_interval).to eq(3)
subject.kill
end
specify '#timeout_interval is writeable' do
latch = CountDownLatch.new(1)
subject = TimerTask.new(timeout_interval: 1,
execution_interval: 0.1,
run_now: true) do |task|
task.timeout_interval = 3
latch.count_down
end
expect(subject.timeout_interval).to eq(1)
subject.timeout_interval = 2
expect(subject.timeout_interval).to eq(2)
subject.execute
latch.wait(0.2)
expect(subject.timeout_interval).to eq(3)
subject.kill
end
end
context 'execution' do
it 'runs the block immediately when the :run_now option is true' do
latch = CountDownLatch.new(1)
subject = TimerTask.execute(execution: 500, now: true) { latch.count_down }
expect(latch.wait(1)).to be_truthy
subject.kill
end
it 'waits for :execution_interval seconds when the :run_now option is false' do
latch = CountDownLatch.new(1)
subject = TimerTask.execute(execution: 0.1, now: false) { latch.count_down }
expect(latch.count).to eq 1
expect(latch.wait(1)).to be_truthy
subject.kill
end
it 'waits for :execution_interval seconds when the :run_now option is not given' do
latch = CountDownLatch.new(1)
subject = TimerTask.execute(execution: 0.1, now: false) { latch.count_down }
expect(latch.count).to eq 1
expect(latch.wait(1)).to be_truthy
subject.kill
end
it 'passes a "self" reference to the block as the sole argument' do
expected = nil
latch = CountDownLatch.new(1)
subject = TimerTask.new(execution_interval: 1, run_now: true) do |task|
expected = task
latch.count_down
end
subject.execute
latch.wait(1)
expect(expected).to eq subject
expect(latch.count).to eq(0)
subject.kill
end
end
context 'observation' do
let(:observer) do
Class.new do
attr_reader :time
attr_reader :value
attr_reader :ex
attr_reader :latch
define_method(:initialize) { @latch = CountDownLatch.new(1) }
define_method(:update) do |time, value, ex|
@time = time
@value = value
@ex = ex
@latch.count_down
end
end.new
end
it 'notifies all observers on success' do
subject = TimerTask.new(execution: 0.1) { 42 }
subject.add_observer(observer)
subject.execute
observer.latch.wait(1)
expect(observer.value).to eq(42)
expect(observer.ex).to be_nil
subject.kill
end
it 'notifies all observers on timeout' do
subject = TimerTask.new(run_now: true, execution: 2, timeout: 0.1) { sleep }
subject.add_observer(observer)
subject.execute
observer.latch.wait(1)
expect(observer.value).to be_nil
expect(observer.ex).to be_a(Concurrent::TimeoutError)
subject.kill
end
it 'notifies all observers on error' do
subject = TimerTask.new(execution: 0.1) { raise ArgumentError }
subject.add_observer(observer)
subject.execute
observer.latch.wait(1)
expect(observer.value).to be_nil
expect(observer.ex).to be_a(ArgumentError)
subject.kill
end
end
end
end
|