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 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 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
|