ione 1.2.4 → 1.3.0.pre0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/ione/future.rb +3 -5
- data/lib/ione/io/acceptor.rb +3 -1
- data/lib/ione/io/base_connection.rb +18 -4
- data/lib/ione/io/connection.rb +0 -1
- data/lib/ione/io/io_reactor.rb +26 -5
- data/lib/ione/io/ssl_connection.rb +0 -1
- data/lib/ione/version.rb +1 -1
- data/spec/integration/io_spec.rb +3 -3
- data/spec/integration/ssl_spec.rb +2 -7
- data/spec/ione/byte_buffer_spec.rb +38 -38
- data/spec/ione/future_spec.rb +65 -64
- data/spec/ione/heap_spec.rb +18 -18
- data/spec/ione/io/acceptor_spec.rb +5 -5
- data/spec/ione/io/connection_common.rb +15 -2
- data/spec/ione/io/io_reactor_spec.rb +194 -52
- data/spec/ione/io/ssl_acceptor_spec.rb +3 -3
- data/spec/ione/io/ssl_connection_spec.rb +3 -3
- data/spec/spec_helper.rb +1 -4
- data/spec/support/fake_server.rb +0 -1
- data/spec/support/server_helper.rb +15 -0
- metadata +7 -5
data/spec/ione/heap_spec.rb
CHANGED
@@ -17,9 +17,9 @@ module Ione
|
|
17
17
|
it 'returns the number of items in the heap' do
|
18
18
|
heap.push(13)
|
19
19
|
heap.push(100)
|
20
|
-
heap.size.should
|
20
|
+
heap.size.should == 2
|
21
21
|
heap.push(101)
|
22
|
-
heap.size.should
|
22
|
+
heap.size.should == 3
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
@@ -40,7 +40,7 @@ module Ione
|
|
40
40
|
heap.push(3)
|
41
41
|
heap.push(6)
|
42
42
|
heap.push(5)
|
43
|
-
heap.size.should
|
43
|
+
heap.size.should == 4
|
44
44
|
end
|
45
45
|
|
46
46
|
it 'is aliased as #<<' do
|
@@ -48,14 +48,14 @@ module Ione
|
|
48
48
|
heap << 3
|
49
49
|
heap << 6
|
50
50
|
heap << 5
|
51
|
-
heap.size.should
|
51
|
+
heap.size.should == 4
|
52
52
|
end
|
53
53
|
|
54
54
|
it 'does not add duplicates' do
|
55
55
|
heap << 3
|
56
56
|
heap << 3
|
57
57
|
heap << 3
|
58
|
-
heap.size.should
|
58
|
+
heap.size.should == 1
|
59
59
|
end
|
60
60
|
end
|
61
61
|
|
@@ -66,21 +66,21 @@ module Ione
|
|
66
66
|
|
67
67
|
it 'returns the only item when there is only one' do
|
68
68
|
heap.push(3)
|
69
|
-
heap.peek.should
|
69
|
+
heap.peek.should == 3
|
70
70
|
end
|
71
71
|
|
72
72
|
it 'returns the smallest item' do
|
73
73
|
heap.push(10)
|
74
74
|
heap.push(3)
|
75
75
|
heap.push(7)
|
76
|
-
heap.peek.should
|
76
|
+
heap.peek.should == 3
|
77
77
|
end
|
78
78
|
|
79
79
|
it 'does not remove the item from the heap' do
|
80
80
|
heap.push(3)
|
81
|
-
heap.peek.should
|
82
|
-
heap.peek.should
|
83
|
-
heap.peek.should
|
81
|
+
heap.peek.should == 3
|
82
|
+
heap.peek.should == 3
|
83
|
+
heap.peek.should == 3
|
84
84
|
end
|
85
85
|
end
|
86
86
|
|
@@ -91,7 +91,7 @@ module Ione
|
|
91
91
|
|
92
92
|
it 'returns and removes the only item when there is only one' do
|
93
93
|
heap.push(3)
|
94
|
-
heap.pop.should
|
94
|
+
heap.pop.should == 3
|
95
95
|
heap.should be_empty
|
96
96
|
end
|
97
97
|
|
@@ -99,14 +99,14 @@ module Ione
|
|
99
99
|
heap.push(10)
|
100
100
|
heap.push(3)
|
101
101
|
heap.push(7)
|
102
|
-
heap.pop.should
|
103
|
-
heap.pop.should
|
104
|
-
heap.size.should
|
102
|
+
heap.pop.should == 3
|
103
|
+
heap.pop.should == 7
|
104
|
+
heap.size.should == 1
|
105
105
|
end
|
106
106
|
|
107
107
|
it 'removes the item from the heap' do
|
108
108
|
heap.push(3)
|
109
|
-
heap.pop.should
|
109
|
+
heap.pop.should == 3
|
110
110
|
heap.pop.should be_nil
|
111
111
|
end
|
112
112
|
end
|
@@ -125,8 +125,8 @@ module Ione
|
|
125
125
|
heap.push(101)
|
126
126
|
heap.delete(4)
|
127
127
|
heap.pop
|
128
|
-
heap.peek.should
|
129
|
-
heap.size.should
|
128
|
+
heap.peek.should == 100
|
129
|
+
heap.size.should == 2
|
130
130
|
end
|
131
131
|
|
132
132
|
it 'removes the last item from the heap' do
|
@@ -155,7 +155,7 @@ module Ione
|
|
155
155
|
heap.push(3)
|
156
156
|
heap.push(4)
|
157
157
|
heap.push(5)
|
158
|
-
heap.delete(4).should
|
158
|
+
heap.delete(4).should == 4
|
159
159
|
end
|
160
160
|
|
161
161
|
it 'returns nil when the item is not found' do
|
@@ -174,8 +174,8 @@ module Ione
|
|
174
174
|
acceptor.bind
|
175
175
|
acceptor.read
|
176
176
|
accepted_handlers.should have(1).item
|
177
|
-
accepted_handlers.first.host.should
|
178
|
-
accepted_handlers.first.port.should
|
177
|
+
accepted_handlers.first.host.should == 'example.com'
|
178
|
+
accepted_handlers.first.port.should == 3333
|
179
179
|
end
|
180
180
|
|
181
181
|
it 'passes the unblocker along to the connection handler' do
|
@@ -197,9 +197,9 @@ module Ione
|
|
197
197
|
acceptor.on_accept { |c| received_connection2 = c }
|
198
198
|
acceptor.bind
|
199
199
|
acceptor.read
|
200
|
-
received_connection1.host.should
|
201
|
-
received_connection2.host.should
|
202
|
-
received_connection2.port.should
|
200
|
+
received_connection1.host.should == 'example.com'
|
201
|
+
received_connection2.host.should == 'example.com'
|
202
|
+
received_connection2.port.should == 3333
|
203
203
|
end
|
204
204
|
|
205
205
|
it 'ignores exceptions raised by the connection callback' do
|
@@ -40,7 +40,7 @@ shared_examples_for 'a connection' do |options|
|
|
40
40
|
handler.on_closed { calls += 1 }
|
41
41
|
handler.close
|
42
42
|
handler.close
|
43
|
-
calls.should
|
43
|
+
calls.should == 1
|
44
44
|
end
|
45
45
|
|
46
46
|
it 'returns false if it did nothing' do
|
@@ -83,6 +83,19 @@ shared_examples_for 'a connection' do |options|
|
|
83
83
|
handler.should be_closed
|
84
84
|
end
|
85
85
|
|
86
|
+
it 'closes the socket for reading if possible' do
|
87
|
+
socket.stub(:close_read)
|
88
|
+
handler.write('hello world')
|
89
|
+
handler.drain
|
90
|
+
socket.should have_received(:close_read)
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'ignores IO errors when closing for reading' do
|
94
|
+
socket.stub(:close_read).and_raise(IOError, 'Boork')
|
95
|
+
handler.write('hello world')
|
96
|
+
expect { handler.drain }.to_not raise_error
|
97
|
+
end
|
98
|
+
|
86
99
|
it 'returns a future that completes when the socket has closed' do
|
87
100
|
handler.write('hello world')
|
88
101
|
f = handler.drain
|
@@ -238,7 +251,7 @@ shared_examples_for 'a connection' do |options|
|
|
238
251
|
data = nil
|
239
252
|
handler.on_data { |d| data = d }
|
240
253
|
handler.read
|
241
|
-
data.should
|
254
|
+
data.should == 'foo bar'
|
242
255
|
end
|
243
256
|
|
244
257
|
context 'when #read_nonblock raises an error' do
|
@@ -7,7 +7,11 @@ module Ione
|
|
7
7
|
module Io
|
8
8
|
describe IoReactor do
|
9
9
|
let :reactor do
|
10
|
-
described_class.new(
|
10
|
+
described_class.new(options)
|
11
|
+
end
|
12
|
+
|
13
|
+
let :options do
|
14
|
+
{selector: selector, clock: clock, drain_timeout: 3}
|
11
15
|
end
|
12
16
|
|
13
17
|
let! :selector do
|
@@ -37,7 +41,7 @@ module Ione
|
|
37
41
|
end
|
38
42
|
|
39
43
|
after do
|
40
|
-
reactor.stop if reactor.running?
|
44
|
+
reactor.stop.value if reactor.running?
|
41
45
|
end
|
42
46
|
end
|
43
47
|
|
@@ -77,15 +81,16 @@ module Ione
|
|
77
81
|
end
|
78
82
|
reactor.start.value
|
79
83
|
stopped_future = reactor.stop
|
80
|
-
restarted_future = reactor.start
|
81
84
|
sequence = []
|
82
85
|
stopped_future.on_complete { sequence << :stopped }
|
86
|
+
restarted_future = reactor.start
|
83
87
|
restarted_future.on_complete { sequence << :restarted }
|
84
88
|
barrier.push(nil)
|
85
89
|
stopped_future.value
|
86
90
|
restarted_future.value
|
91
|
+
await { sequence.size >= 2 }
|
87
92
|
begin
|
88
|
-
sequence.should
|
93
|
+
sequence.should == [:stopped, :restarted]
|
89
94
|
ensure
|
90
95
|
reactor.stop
|
91
96
|
barrier.push(nil) while reactor.running?
|
@@ -93,7 +98,6 @@ module Ione
|
|
93
98
|
end
|
94
99
|
|
95
100
|
it 'restarts the reactor even when restarted before a failed stop' do
|
96
|
-
pending 'This test is broken in JRuby' if RUBY_ENGINE == 'jruby'
|
97
101
|
barrier = Queue.new
|
98
102
|
selector.handler do
|
99
103
|
if barrier.pop == :fail
|
@@ -112,6 +116,7 @@ module Ione
|
|
112
116
|
barrier.push(:fail)
|
113
117
|
stopped_future.value rescue nil
|
114
118
|
restarted_future.value
|
119
|
+
await { crashed && restarted }
|
115
120
|
begin
|
116
121
|
crashed.should be_true
|
117
122
|
restarted.should be_true
|
@@ -136,6 +141,8 @@ module Ione
|
|
136
141
|
|
137
142
|
context 'when already started' do
|
138
143
|
it 'is not started again' do
|
144
|
+
calls = 0
|
145
|
+
lock = Mutex.new
|
139
146
|
ticks = Queue.new
|
140
147
|
barrier = Queue.new
|
141
148
|
selector.handler do
|
@@ -176,7 +183,7 @@ module Ione
|
|
176
183
|
reactor.should_not be_running
|
177
184
|
end
|
178
185
|
|
179
|
-
it 'keeps running until stop
|
186
|
+
it 'keeps running until stop completes' do
|
180
187
|
running_barrier = Queue.new
|
181
188
|
stop_barrier = Queue.new
|
182
189
|
selector.handler do
|
@@ -191,11 +198,125 @@ module Ione
|
|
191
198
|
stop_barrier.push(nil) until future.completed?
|
192
199
|
end
|
193
200
|
|
194
|
-
it '
|
201
|
+
it 'unblocks the reactor' do
|
202
|
+
running_barrier = Queue.new
|
203
|
+
selector.handler do |readables, writables, _, _|
|
204
|
+
running_barrier.push(nil)
|
205
|
+
IO.select(readables, writables, nil, 5)
|
206
|
+
end
|
195
207
|
reactor.start.value
|
196
|
-
|
197
|
-
reactor.stop
|
198
|
-
|
208
|
+
running_barrier.pop
|
209
|
+
stopped_future = reactor.stop
|
210
|
+
await { stopped_future.completed? }
|
211
|
+
stopped_future.should be_completed
|
212
|
+
stopped_future.value
|
213
|
+
end
|
214
|
+
|
215
|
+
it 'drains all sockets' do
|
216
|
+
reactor.start.value
|
217
|
+
TCPServer.open(0) do |server|
|
218
|
+
lazy_socket = Thread.start { server.accept }
|
219
|
+
connection = reactor.connect(server.addr[3], server.addr[1], 5).value
|
220
|
+
writable = false
|
221
|
+
connection.stub(:stub_writable?) { writable }
|
222
|
+
class <<connection; alias_method :writable?, :stub_writable?; end
|
223
|
+
connection.write('12345678')
|
224
|
+
release_barrier = barrier = Queue.new
|
225
|
+
selector.handler do |readables, writables, _, _|
|
226
|
+
barrier.pop
|
227
|
+
[[], writables, []]
|
228
|
+
end
|
229
|
+
await { barrier.num_waiting > 0 }
|
230
|
+
barrier = []
|
231
|
+
release_barrier.push(nil)
|
232
|
+
connection.stub(:flush) do
|
233
|
+
writable = false
|
234
|
+
end
|
235
|
+
writable = true
|
236
|
+
reactor.stop.value
|
237
|
+
connection.should have_received(:flush)
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
it 'waits on drain to complete upto the specified drain timeout' do
|
242
|
+
time = time_increment = next_increment = 0
|
243
|
+
mutex = Mutex.new
|
244
|
+
clock.stub(:now) { mutex.synchronize { time } }
|
245
|
+
selector.handler do |_, writables, _, _|
|
246
|
+
mutex.synchronize do
|
247
|
+
time += time_increment
|
248
|
+
time_increment = next_increment
|
249
|
+
end
|
250
|
+
[[], writables, []]
|
251
|
+
end
|
252
|
+
reactor.start.value
|
253
|
+
TCPServer.open(0) do |server|
|
254
|
+
lazy_socket = Thread.start { server.accept }
|
255
|
+
connection = reactor.connect(server.addr[3], server.addr[1], 5).value
|
256
|
+
stopped_future = nil
|
257
|
+
mutex.synchronize do
|
258
|
+
connection.stub(:writable?).and_return(true)
|
259
|
+
connection.stub(:flush)
|
260
|
+
next_increment = 1
|
261
|
+
stopped_future = reactor.stop
|
262
|
+
end
|
263
|
+
expect { stopped_future.value }.to raise_error(ReactorError, /timeout/)
|
264
|
+
(time).should eq(3)
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
it 'waits on drain to complete upto five seconds by default' do
|
269
|
+
with_server do |host, port|
|
270
|
+
options.delete(:drain_timeout)
|
271
|
+
time = time_increment = next_increment = 0
|
272
|
+
mutex = Mutex.new
|
273
|
+
clock.stub(:now) { mutex.synchronize { time } }
|
274
|
+
selector.handler do |_, writables, _, _|
|
275
|
+
mutex.synchronize do
|
276
|
+
time += time_increment
|
277
|
+
time_increment = next_increment
|
278
|
+
end
|
279
|
+
[[], writables, []]
|
280
|
+
end
|
281
|
+
reactor.start.value
|
282
|
+
connection = reactor.connect(host, port, 5).value
|
283
|
+
stopped_future = nil
|
284
|
+
mutex.synchronize do
|
285
|
+
connection.stub(:writable?).and_return(true)
|
286
|
+
connection.stub(:flush)
|
287
|
+
next_increment = 1
|
288
|
+
stopped_future = reactor.stop
|
289
|
+
end
|
290
|
+
expect { stopped_future.value }.to raise_error(ReactorError, /timeout/)
|
291
|
+
time.should eq(5)
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
it 'closes all sockets' do
|
296
|
+
with_server do |host, port|
|
297
|
+
reactor.start.value
|
298
|
+
connection = reactor.connect(host, port, 5).value
|
299
|
+
reactor.stop.value
|
300
|
+
connection.should be_closed
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
it 'closes all sockets even if drain fails' do
|
305
|
+
with_server do |host, port|
|
306
|
+
reactor.start.value
|
307
|
+
connection = reactor.connect(host, port, 5).value
|
308
|
+
Thread.pass
|
309
|
+
writable = false
|
310
|
+
connection.stub(:stub_writable?) { writable }
|
311
|
+
class <<connection; alias_method :writable?, :stub_writable?; end
|
312
|
+
connection.write('12345678')
|
313
|
+
Thread.pass
|
314
|
+
connection.stub(:flush).and_raise(StandardError, 'Boork')
|
315
|
+
writable = true
|
316
|
+
f = reactor.stop
|
317
|
+
expect { f.value }.to raise_error(StandardError, 'Boork')
|
318
|
+
connection.should be_closed
|
319
|
+
end
|
199
320
|
end
|
200
321
|
|
201
322
|
it 'cancels all active timers' do
|
@@ -237,7 +358,7 @@ module Ione
|
|
237
358
|
reactor.on_error { |e| error = e }
|
238
359
|
reactor.start
|
239
360
|
await { error }
|
240
|
-
error.message.should
|
361
|
+
error.message.should == 'Blurgh'
|
241
362
|
end
|
242
363
|
|
243
364
|
it 'calls the listener immediately when the reactor has already crashed' do
|
@@ -266,17 +387,18 @@ module Ione
|
|
266
387
|
reactor.on_error { calls << :post_started }
|
267
388
|
barrier.push(nil)
|
268
389
|
await { !reactor.running? }
|
390
|
+
await { calls.size >= 2 }
|
269
391
|
reactor.on_error { calls << :pre_restarted }
|
270
|
-
calls.should
|
392
|
+
calls.should == [
|
271
393
|
:pre_started,
|
272
394
|
:post_started,
|
273
395
|
:pre_restarted,
|
274
|
-
]
|
396
|
+
]
|
275
397
|
reactor.start
|
276
398
|
reactor.on_error { calls << :post_restarted }
|
277
399
|
barrier.push(nil)
|
278
400
|
await { !reactor.running? }
|
279
|
-
calls.should
|
401
|
+
calls.should == [
|
280
402
|
:pre_started,
|
281
403
|
:post_started,
|
282
404
|
:pre_restarted,
|
@@ -284,7 +406,7 @@ module Ione
|
|
284
406
|
:post_started,
|
285
407
|
:pre_restarted,
|
286
408
|
:post_restarted,
|
287
|
-
]
|
409
|
+
]
|
288
410
|
end
|
289
411
|
end
|
290
412
|
|
@@ -292,70 +414,90 @@ module Ione
|
|
292
414
|
include_context 'running_reactor'
|
293
415
|
|
294
416
|
it 'calls the given block with a connection' do
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
417
|
+
with_server do |host, port|
|
418
|
+
connection = nil
|
419
|
+
reactor.start.value
|
420
|
+
reactor.connect(host, port, 5) { |c| connection = c }.value
|
421
|
+
connection.should_not be_nil
|
422
|
+
end
|
299
423
|
end
|
300
424
|
|
301
425
|
it 'returns a future that resolves to what the given block returns' do
|
302
|
-
|
303
|
-
|
304
|
-
|
426
|
+
with_server do |host, port|
|
427
|
+
reactor.start.value
|
428
|
+
x = reactor.connect(host, port, 5) { :foo }.value
|
429
|
+
x.should == :foo
|
430
|
+
end
|
305
431
|
end
|
306
432
|
|
307
433
|
it 'defaults to 5 as the connection timeout' do
|
308
|
-
|
309
|
-
|
310
|
-
|
434
|
+
with_server do |host, port|
|
435
|
+
reactor.start.value
|
436
|
+
connection = reactor.connect(host, port).value
|
437
|
+
connection.connection_timeout.should == 5
|
438
|
+
end
|
311
439
|
end
|
312
440
|
|
313
441
|
it 'takes the connection timeout from the :timeout option' do
|
314
|
-
|
315
|
-
|
316
|
-
|
442
|
+
with_server do |host, port|
|
443
|
+
reactor.start.value
|
444
|
+
connection = reactor.connect(host, port, timeout: 9).value
|
445
|
+
connection.connection_timeout.should == 9
|
446
|
+
end
|
317
447
|
end
|
318
448
|
|
319
449
|
it 'returns the connection when no block is given' do
|
320
|
-
|
321
|
-
|
450
|
+
with_server do |host, port|
|
451
|
+
reactor.start.value
|
452
|
+
reactor.connect(host, port, 5).value.should be_a(Connection)
|
453
|
+
end
|
322
454
|
end
|
323
455
|
|
324
456
|
it 'creates a connection and passes it to the selector as a readable' do
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
457
|
+
with_server do |host, port|
|
458
|
+
reactor.start.value
|
459
|
+
connection = reactor.connect(host, port, 5).value
|
460
|
+
await { selector.last_arguments[0].length > 1 }
|
461
|
+
selector.last_arguments[0].should include(connection)
|
462
|
+
end
|
329
463
|
end
|
330
464
|
|
331
465
|
it 'upgrades the connection to SSL' do
|
332
|
-
|
333
|
-
|
334
|
-
|
466
|
+
with_server do |host, port|
|
467
|
+
reactor.start.value
|
468
|
+
connection = reactor.connect(host, port, ssl: true).value
|
469
|
+
connection.should be_a(SslConnection)
|
470
|
+
end
|
335
471
|
end
|
336
472
|
|
337
473
|
it 'passes an SSL context to the SSL connection' do
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
474
|
+
with_server do |host, port|
|
475
|
+
ssl_context = double(:ssl_context)
|
476
|
+
reactor.start.value
|
477
|
+
f = reactor.connect(host, port, ssl: ssl_context)
|
478
|
+
expect { f.value }.to raise_error
|
479
|
+
end
|
342
480
|
end
|
343
481
|
|
344
482
|
context 'when called before the reactor is started' do
|
345
483
|
it 'waits for the reactor to start' do
|
346
|
-
|
347
|
-
|
348
|
-
|
484
|
+
with_server do |host, port|
|
485
|
+
f = reactor.connect(host, port)
|
486
|
+
reactor.start.value
|
487
|
+
f.value
|
488
|
+
end
|
349
489
|
end
|
350
490
|
end
|
351
491
|
|
352
492
|
context 'when called after the reactor has stopped' do
|
353
493
|
it 'waits for the reactor to be restarted' do
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
494
|
+
with_server do |host, port|
|
495
|
+
reactor.start.value
|
496
|
+
reactor.stop.value
|
497
|
+
f = reactor.connect(host, port)
|
498
|
+
reactor.start.value
|
499
|
+
f.value
|
500
|
+
end
|
359
501
|
end
|
360
502
|
end
|
361
503
|
end
|
@@ -377,19 +519,19 @@ module Ione
|
|
377
519
|
it 'returns a future that resolves to what the given block returns' do
|
378
520
|
reactor.start.value
|
379
521
|
x = reactor.bind(ENV['SERVER_HOST'], port, 5) { |acceptor| :foo }.value
|
380
|
-
x.should
|
522
|
+
x.should == :foo
|
381
523
|
end
|
382
524
|
|
383
525
|
it 'defaults to a backlog of 5' do
|
384
526
|
reactor.start.value
|
385
527
|
acceptor = reactor.bind(ENV['SERVER_HOST'], port).value
|
386
|
-
acceptor.backlog.should
|
528
|
+
acceptor.backlog.should == 5
|
387
529
|
end
|
388
530
|
|
389
531
|
it 'takes the backlog from the :backlog option' do
|
390
532
|
reactor.start.value
|
391
533
|
acceptor = reactor.bind(ENV['SERVER_HOST'], port, backlog: 9).value
|
392
|
-
acceptor.backlog.should
|
534
|
+
acceptor.backlog.should == 9
|
393
535
|
end
|
394
536
|
|
395
537
|
it 'returns the acceptor when no block is given' do
|