rf-web/vendor/bundle/gems/eventmachine-1.2.7/tests/test_spawn.rb

294 lines
6.5 KiB
Ruby
Raw Normal View History

2019-10-21 08:18:17 +00:00
require 'em_test_helper'
class TestSpawn < Test::Unit::TestCase
# Spawn a process that simply stops the reactor.
# Assert that the notification runs after the block that calls it.
#
def test_stop
x = nil
EM.run {
s = EM.spawn {EM.stop}
s.notify
x = true
}
assert x
end
# Pass a parameter to a spawned process.
#
def test_parms
val = 5
EM.run {
s = EM.spawn {|v| val *= v; EM.stop}
s.notify 3
}
assert_equal( 15, val )
end
# Pass multiple parameters to a spawned process.
#
def test_multiparms
val = 5
EM.run {
s = EM.spawn {|v1,v2| val *= (v1 + v2); EM.stop}
s.notify 3,4
}
assert_equal( 35, val )
end
# This test demonstrates that a notification does not happen immediately,
# but rather is scheduled sometime after the current code path completes.
#
def test_race
x = 0
EM.run {
s = EM.spawn {x *= 2; EM.stop}
s.notify
x = 2
}
assert_equal( 4, x)
end
# Spawn a process and notify it 25 times to run fibonacci
# on a pair of global variables.
#
def test_fibonacci
x = 1
y = 1
EM.run {
s = EM.spawn {x,y = y,x+y}
25.times {s.notify}
t = EM.spawn {EM.stop}
t.notify
}
assert_equal( 121393, x)
assert_equal( 196418, y)
end
# This one spawns 25 distinct processes, and notifies each one once,
# rather than notifying a single process 25 times.
#
def test_another_fibonacci
x = 1
y = 1
EM.run {
25.times {
s = EM.spawn {x,y = y,x+y}
s.notify
}
t = EM.spawn {EM.stop}
t.notify
}
assert_equal( 121393, x)
assert_equal( 196418, y)
end
# Make a chain of processes that notify each other in turn
# with intermediate fibonacci results. The final process in
# the chain stops the loop and returns the result.
#
def test_fibonacci_chain
a,b = nil
EM.run {
nextpid = EM.spawn {|x,y|
a,b = x,y
EM.stop
}
25.times {
n = nextpid
nextpid = EM.spawn {|x,y| n.notify( y, x+y )}
}
nextpid.notify( 1, 1 )
}
assert_equal( 121393, a)
assert_equal( 196418, b)
end
# EM#yield gives a spawed process to yield control to other processes
# (in other words, to stop running), and to specify a different code block
# that will run on its next notification.
#
def test_yield
a = 0
EM.run {
n = EM.spawn {
a += 10
EM.yield {
a += 20
EM.yield {
a += 30
EM.stop
}
}
}
n.notify
n.notify
n.notify
}
assert_equal( 60, a )
end
# EM#yield_and_notify behaves like EM#yield, except that it also notifies the
# yielding process. This may sound trivial, since the yield block will run very
# shortly after with no action by the program, but this actually can be very useful,
# because it causes the reactor core to execute once before the yielding process
# gets control back. So it can be used to allow heavily-used network connections
# to clear buffers, or allow other processes to process their notifications.
#
# Notice in this test code that only a simple notify is needed at the bottom
# of the initial block. Even so, all of the yielded blocks will execute.
#
def test_yield_and_notify
a = 0
EM.run {
n = EM.spawn {
a += 10
EM.yield_and_notify {
a += 20
EM.yield_and_notify {
a += 30
EM.stop
}
}
}
n.notify
}
assert_equal( 60, a )
end
# resume is an alias for notify.
#
def test_resume
EM.run {
n = EM.spawn {EM.stop}
n.resume
}
assert true
end
# run is an idiomatic alias for notify.
#
def test_run
EM.run {
(EM.spawn {EM.stop}).run
}
assert true
end
# Clones the ping-pong example from the Erlang tutorial, in much less code.
# Illustrates that a spawned block executes in the context of a SpawnableObject.
# (Meaning, we can pass self as a parameter to another process that can then
# notify us.)
#
def test_ping_pong
n_pongs = 0
EM.run {
pong = EM.spawn {|x, ping|
n_pongs += 1
ping.notify( x-1 )
}
ping = EM.spawn {|x|
if x > 0
pong.notify x, self
else
EM.stop
end
}
ping.notify 3
}
assert_equal( 3, n_pongs )
end
# Illustrates that you can call notify inside a notification, and it will cause
# the currently-executing process to be re-notified. Of course, the new notification
# won't run until sometime after the current one completes.
#
def test_self_notify
n = 0
EM.run {
pid = EM.spawn {|x|
if x > 0
n += x
notify( x-1 )
else
EM.stop
end
}
pid.notify 3
}
assert_equal( 6, n )
end
# Illustrates that the block passed to #spawn executes in the context of a
# SpawnedProcess object, NOT in the local context. This can often be deceptive.
#
class BlockScopeTest
attr_reader :var
def run
# The following line correctly raises a NameError.
# The problem is that the programmer expected the spawned block to
# execute in the local context, but it doesn't.
#
# (EM.spawn { do_something }).notify ### NO! BAD!
# The following line correctly passes self as a parameter to the
# notified process.
#
(EM.spawn {|obj| obj.do_something }).notify(self)
# Here's another way to do it. This works because "myself" is bound
# in the local scope, unlike "self," so the spawned block sees it.
#
myself = self
(EM.spawn { myself.do_something }).notify
# And we end the loop.
# This is a tangential point, but observe that #notify never blocks.
# It merely appends a message to the internal queue of a spawned process
# and returns. As it turns out, the reactor processes notifications for ALL
# spawned processes in the order that #notify is called. So there is a
# reasonable expectation that the process which stops the reactor will
# execute after the previous ones in this method. HOWEVER, this is NOT
# a documented behavior and is subject to change.
#
(EM.spawn {EM.stop}).notify
end
def do_something
@var ||= 0
@var += 100
end
end
def test_block_scope
bs = BlockScopeTest.new
EM.run {
bs.run
}
assert_equal( 200, bs.var )
end
end