This commit is contained in:
@@ -0,0 +1,293 @@
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user