root/trunk/tests/test_spawn.rb

Revision 668, 6.8 kB (checked in by blackhedd, 1 year ago)

migrated version_0 to trunk

  • Property svn:keywords set to Id
Line 
1 # $Id$
2 #
3 # Author:: Francis Cianfrocca (gmail: blackhedd)
4 # Homepage::  http://rubyeventmachine.com
5 # Date:: 25 Aug 2007
6 #
7 # See EventMachine and EventMachine::Connection for documentation and
8 # usage examples.
9 #
10 #----------------------------------------------------------------------------
11 #
12 # Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
13 # Gmail: blackhedd
14 #
15 # This program is free software; you can redistribute it and/or modify
16 # it under the terms of either: 1) the GNU General Public License
17 # as published by the Free Software Foundation; either version 2 of the
18 # License, or (at your option) any later version; or 2) Ruby's License.
19 #
20 # See the file COPYING for complete licensing information.
21 #
22 #---------------------------------------------------------------------------
23 #
24 #
25 #
26 #
27
28
29 $:.unshift "../lib"
30 require 'eventmachine'
31 require 'test/unit'
32
33
34
35 class TestSpawn < Test::Unit::TestCase
36
37         def setup
38         end
39
40         def teardown
41         end
42
43
44         # Spawn a process that simply stops the reactor.
45         # Assert that the notification runs after the block that calls it.
46         #
47         def test_stop
48                 x = nil
49                 EM.run {
50                         s = EM.spawn {EM.stop}
51                         s.notify
52                         x = true
53                 }
54                 assert x
55         end
56
57
58         # Pass a parameter to a spawned process.
59         #
60         def test_parms
61                 val = 5
62                 EM.run {
63                         s = EM.spawn {|v| val *= v; EM.stop}
64                         s.notify 3
65                 }
66                 assert_equal( 15, val )
67         end
68
69         # Pass multiple parameters to a spawned process.
70         #
71         def test_multiparms
72                 val = 5
73                 EM.run {
74                         s = EM.spawn {|v1,v2| val *= (v1 + v2); EM.stop}
75                         s.notify 3,4
76                 }
77                 assert_equal( 35, val )
78         end
79
80
81         # This test demonstrates that a notification does not happen immediately,
82         # but rather is scheduled sometime after the current code path completes.
83         #
84         def test_race
85                 x = 0
86                 EM.run {
87                         s = EM.spawn {x *= 2; EM.stop}
88                         s.notify
89                         x = 2
90                 }
91                 assert_equal( 4, x)
92         end
93
94
95         # Spawn a process and notify it 25 times to run fibonacci
96         # on a pair of global variables.
97         #
98         def test_fibonacci
99                 x = 1
100                 y = 1
101                 EM.run {
102                         s = EM.spawn {x,y = y,x+y}
103                         25.times {s.notify}
104
105                         t = EM.spawn {EM.stop}
106                         t.notify
107                 }
108                 assert_equal( 121393, x)
109                 assert_equal( 196418, y)
110         end
111
112         # This one spawns 25 distinct processes, and notifies each one once,
113         # rather than notifying a single process 25 times.
114         #
115         def test_another_fibonacci
116                 x = 1
117                 y = 1
118                 EM.run {
119                         25.times {
120                                 s = EM.spawn {x,y = y,x+y}
121                                 s.notify
122                         }
123
124                         t = EM.spawn {EM.stop}
125                         t.notify
126                 }
127                 assert_equal( 121393, x)
128                 assert_equal( 196418, y)
129         end
130
131
132         # Make a chain of processes that notify each other in turn
133         # with intermediate fibonacci results. The final process in
134         # the chain stops the loop and returns the result.
135         #
136         def test_fibonacci_chain
137                 a,b = nil
138
139                 EM.run {
140                         nextpid = EM.spawn {|x,y|
141                                 a,b = x,y
142                                 EM.stop
143                         }
144
145                         25.times {
146                                 n = nextpid
147                                 nextpid = EM.spawn {|x,y| n.notify( y, x+y )}
148                         }
149
150                         nextpid.notify( 1, 1 )
151                 }
152
153                 assert_equal( 121393, a)
154                 assert_equal( 196418, b)
155         end
156
157
158         # EM#yield gives a spawed process to yield control to other processes
159         # (in other words, to stop running), and to specify a different code block
160         # that will run on its next notification.
161         #
162         def test_yield
163                 a = 0
164                 EM.run {
165                         n = EM.spawn {
166                                 a += 10
167                                 EM.yield {
168                                         a += 20
169                                         EM.yield {
170                                                 a += 30
171                                                 EM.stop
172                                         }
173                                 }
174                         }
175                         n.notify
176                         n.notify
177                         n.notify
178                 }
179                 assert_equal( 60, a )
180         end
181
182         # EM#yield_and_notify behaves like EM#yield, except that it also notifies the
183         # yielding process. This may sound trivial, since the yield block will run very
184         # shortly after with no action by the program, but this actually can be very useful,
185         # because it causes the reactor core to execute once before the yielding process
186         # gets control back. So it can be used to allow heavily-used network connections
187         # to clear buffers, or allow other processes to process their notifications.
188         #
189         # Notice in this test code that only a simple notify is needed at the bottom
190         # of the initial block. Even so, all of the yielded blocks will execute.
191         #
192         def test_yield_and_notify
193                 a = 0
194                 EM.run {
195                         n = EM.spawn {
196                                 a += 10
197                                 EM.yield_and_notify {
198                                         a += 20
199                                         EM.yield_and_notify {
200                                                 a += 30
201                                                 EM.stop
202                                         }
203                                 }
204                         }
205                         n.notify
206                 }
207                 assert_equal( 60, a )
208         end
209
210         # resume is an alias for notify.
211         #
212         def test_resume
213                 EM.run {
214                         n = EM.spawn {EM.stop}
215                         n.resume
216                 }
217                 assert true
218         end
219
220         # run is an idiomatic alias for notify.
221         #
222         def test_run
223                 EM.run {
224                         (EM.spawn {EM.stop}).run
225                 }
226                 assert true
227         end
228
229
230         # Clones the ping-pong example from the Erlang tutorial, in much less code.
231         # Illustrates that a spawned block executes in the context of a SpawnableObject.
232         # (Meaning, we can pass self as a parameter to another process that can then
233         # notify us.)
234         #
235         def test_ping_pong
236                 n_pongs = 0
237                 EM.run {
238                         pong = EM.spawn {|x, ping|
239                                 n_pongs += 1
240                                 ping.notify( x-1 )
241                         }
242                         ping = EM.spawn {|x|
243                                 if x > 0
244                                         pong.notify x, self
245                                 else
246                                         EM.stop
247                                 end
248                         }
249                         ping.notify 3
250                 }
251                 assert_equal( 3, n_pongs )
252         end
253
254         # Illustrates that you can call notify inside a notification, and it will cause
255         # the currently-executing process to be re-notified. Of course, the new notification
256         # won't run until sometime after the current one completes.
257         #
258         def test_self_notify
259                 n = 0
260                 EM.run {
261                         pid = EM.spawn {|x|
262                                 if x > 0
263                                         n += x
264                                         notify( x-1 )
265                                 else
266                                         EM.stop
267                                 end
268                         }
269                         pid.notify 3
270                 }
271                 assert_equal( 6, n )
272         end
273
274
275         # Illustrates that the block passed to #spawn executes in the context of a
276         # SpawnedProcess object, NOT in the local context. This can often be deceptive.
277         #
278         class BlockScopeTest
279                 attr_reader :var
280                 def run
281                         # The following line correctly raises a NameError.
282                         # The problem is that the programmer expected the spawned block to
283                         # execute in the local context, but it doesn't.
284                         #
285                         # (EM.spawn { do_something }).notify ### NO! BAD!
286
287
288
289                         # The following line correctly passes self as a parameter to the
290                         # notified process.
291                         #
292                         (EM.spawn {|obj| obj.do_something }).notify(self)
293
294
295
296                         # Here's another way to do it. This works because "myself" is bound
297                         # in the local scope, unlike "self," so the spawned block sees it.
298                         #
299                         myself = self
300                         (EM.spawn { myself.do_something }).notify
301
302
303
304                         # And we end the loop.
305                         # This is a tangential point, but observe that #notify never blocks.
306                         # It merely appends a message to the internal queue of a spawned process
307                         # and returns. As it turns out, the reactor processes notifications for ALL
308                         # spawned processes in the order that #notify is called. So there is a
309                         # reasonable expectation that the process which stops the reactor will
310                         # execute after the previous ones in this method. HOWEVER, this is NOT
311                         # a documented behavior and is subject to change.
312                         #
313                         (EM.spawn {EM.stop}).notify
314                 end
315                 def do_something
316                         @var ||= 0
317                         @var += 100
318                 end
319         end
320
321         def test_block_scope
322                 bs = BlockScopeTest.new
323                 EM.run {
324                         bs.run
325                 }
326                 assert_equal( 200, bs.var )
327         end
328
329 end
Note: See TracBrowser for help on using the browser.