1require 'test/unit'
2
3require 'drb/drb'
4require 'drb/eq'
5require 'rinda/tuplespace'
6
7require 'singleton'
8
9module Rinda
10
11class MockClock
12  include Singleton
13
14  class MyTS < Rinda::TupleSpace
15    def keeper_thread
16      nil
17    end
18  end
19
20  def initialize
21    @now = 2
22    @reso = 1
23    @ts = MyTS.new
24    @ts.write([2, :now])
25    @inf = 2**31 - 1
26  end
27
28  def now
29    @now.to_f
30  end
31
32  def at(n)
33    n
34  end
35
36  def _forward(n=nil)
37    now ,= @ts.take([nil, :now])
38    @now = now + n
39    n = @reso if n.nil?
40    @ts.write([@now, :now])
41  end
42
43  def forward(n)
44    while n > 0
45      _forward(@reso)
46      n -= @reso
47      Thread.pass
48    end
49  end
50
51  def rewind
52    now ,= @ts.take([nil, :now])
53    @ts.write([@inf, :now])
54    @ts.take([nil, :now])
55    @now = 2
56    @ts.write([2, :now])
57  end
58
59  def sleep(n=nil)
60    now ,= @ts.read([nil, :now])
61    @ts.read([(now + n)..@inf, :now])
62    0
63  end
64end
65
66module Time
67  def sleep(n)
68    @m.sleep(n)
69  end
70  module_function :sleep
71
72  def at(n)
73    n
74  end
75  module_function :at
76
77  def now
78    @m ? @m.now : 2
79  end
80  module_function :now
81
82  def rewind
83    @m.rewind
84  end
85  module_function :rewind
86
87  def forward(n)
88    @m.forward(n)
89  end
90  module_function :forward
91
92  @m = MockClock.instance
93end
94
95class TupleSpace
96  def sleep(n)
97    Kernel.sleep(n * 0.01)
98  end
99end
100
101module TupleSpaceTestModule
102  def sleep(n)
103    if Thread.current == Thread.main
104      Time.forward(n)
105    else
106      Time.sleep(n)
107    end
108  end
109
110  def thread_join(th)
111    while th.alive?
112      Kernel.sleep(0.1)
113      sleep(1)
114    end
115    th.value
116  end
117
118  def test_00_tuple
119    tuple = Rinda::TupleEntry.new([1,2,3])
120    assert(!tuple.canceled?)
121    assert(!tuple.expired?)
122    assert(tuple.alive?)
123  end
124
125  def test_00_template
126    tmpl = Rinda::Template.new([1,2,3])
127    assert_equal(3, tmpl.size)
128    assert_equal(3, tmpl[2])
129    assert(tmpl.match([1,2,3]))
130    assert(!tmpl.match([1,nil,3]))
131
132    tmpl = Rinda::Template.new([/^rinda/i, nil, :hello])
133    assert_equal(3, tmpl.size)
134    assert(tmpl.match(['Rinda', 2, :hello]))
135    assert(!tmpl.match(['Rinda', 2, Symbol]))
136    assert(!tmpl.match([1, 2, :hello]))
137    assert(tmpl.match([/^rinda/i, 2, :hello]))
138
139    tmpl = Rinda::Template.new([Symbol])
140    assert_equal(1, tmpl.size)
141    assert(tmpl.match([:hello]))
142    assert(tmpl.match([Symbol]))
143    assert(!tmpl.match(['Symbol']))
144
145    tmpl = Rinda::Template.new({"message"=>String, "name"=>String})
146    assert_equal(2, tmpl.size)
147    assert(tmpl.match({"message"=>"Hello", "name"=>"Foo"}))
148    assert(!tmpl.match({"message"=>"Hello", "name"=>"Foo", "1"=>2}))
149    assert(!tmpl.match({"message"=>"Hi", "name"=>"Foo", "age"=>1}))
150    assert(!tmpl.match({"message"=>"Hello", "no_name"=>"Foo"}))
151
152    assert_raise(Rinda::InvalidHashTupleKey) do
153      tmpl = Rinda::Template.new({:message=>String, "name"=>String})
154    end
155    tmpl = Rinda::Template.new({"name"=>String})
156    assert_equal(1, tmpl.size)
157    assert(tmpl.match({"name"=>"Foo"}))
158    assert(!tmpl.match({"message"=>"Hello", "name"=>"Foo"}))
159    assert(!tmpl.match({"message"=>:symbol, "name"=>"Foo", "1"=>2}))
160    assert(!tmpl.match({"message"=>"Hi", "name"=>"Foo", "age"=>1}))
161    assert(!tmpl.match({"message"=>"Hello", "no_name"=>"Foo"}))
162
163    tmpl = Rinda::Template.new({"message"=>String, "name"=>String})
164    assert_equal(2, tmpl.size)
165    assert(tmpl.match({"message"=>"Hello", "name"=>"Foo"}))
166    assert(!tmpl.match({"message"=>"Hello", "name"=>"Foo", "1"=>2}))
167    assert(!tmpl.match({"message"=>"Hi", "name"=>"Foo", "age"=>1}))
168    assert(!tmpl.match({"message"=>"Hello", "no_name"=>"Foo"}))
169
170    tmpl = Rinda::Template.new({"message"=>String})
171    assert_equal(1, tmpl.size)
172    assert(tmpl.match({"message"=>"Hello"}))
173    assert(!tmpl.match({"message"=>"Hello", "name"=>"Foo"}))
174    assert(!tmpl.match({"message"=>"Hello", "name"=>"Foo", "1"=>2}))
175    assert(!tmpl.match({"message"=>"Hi", "name"=>"Foo", "age"=>1}))
176    assert(!tmpl.match({"message"=>"Hello", "no_name"=>"Foo"}))
177
178    tmpl = Rinda::Template.new({"message"=>String, "name"=>nil})
179    assert_equal(2, tmpl.size)
180    assert(tmpl.match({"message"=>"Hello", "name"=>"Foo"}))
181    assert(!tmpl.match({"message"=>"Hello", "name"=>"Foo", "1"=>2}))
182    assert(!tmpl.match({"message"=>"Hi", "name"=>"Foo", "age"=>1}))
183    assert(!tmpl.match({"message"=>"Hello", "no_name"=>"Foo"}))
184
185    assert_raise(Rinda::InvalidHashTupleKey) do
186      @ts.write({:message=>String, "name"=>String})
187    end
188
189    @ts.write([1, 2, 3])
190    assert_equal([1, 2, 3], @ts.take([1, 2, 3]))
191
192    @ts.write({'1'=>1, '2'=>2, '3'=>3})
193    assert_equal({'1'=>1, '2'=>2, '3'=>3}, @ts.take({'1'=>1, '2'=>2, '3'=>3}))
194
195    entry = @ts.write(['1'=>1, '2'=>2, '3'=>3])
196    assert_raise(Rinda::RequestExpiredError) do
197      assert_equal({'1'=>1, '2'=>2, '3'=>3}, @ts.read({'1'=>1}, 0))
198    end
199    entry.cancel
200  end
201
202  def test_00_DRbObject
203    ro = DRbObject.new(nil, "druby://host:1234")
204    tmpl = Rinda::DRbObjectTemplate.new
205    assert(tmpl === ro)
206
207    tmpl = Rinda::DRbObjectTemplate.new("druby://host:1234")
208    assert(tmpl === ro)
209
210    tmpl = Rinda::DRbObjectTemplate.new("druby://host:12345")
211    assert(!(tmpl === ro))
212
213    tmpl = Rinda::DRbObjectTemplate.new(/^druby:\/\/host:/)
214    assert(tmpl === ro)
215
216    ro = DRbObject.new_with(12345, 1234)
217    assert(!(tmpl === ro))
218
219    ro = DRbObject.new_with("druby://foo:12345", 1234)
220    assert(!(tmpl === ro))
221
222    tmpl = Rinda::DRbObjectTemplate.new(/^druby:\/\/(foo|bar):/)
223    assert(tmpl === ro)
224
225    ro = DRbObject.new_with("druby://bar:12345", 1234)
226    assert(tmpl === ro)
227
228    ro = DRbObject.new_with("druby://baz:12345", 1234)
229    assert(!(tmpl === ro))
230  end
231
232  def test_inp_rdp
233    assert_raise(Rinda::RequestExpiredError) do
234      @ts.take([:empty], 0)
235    end
236
237    assert_raise(Rinda::RequestExpiredError) do
238      @ts.read([:empty], 0)
239    end
240  end
241
242  def test_ruby_talk_264062
243    th = Thread.new { @ts.take([:empty], 1) }
244    sleep(10)
245    assert_raise(Rinda::RequestExpiredError) do
246      thread_join(th)
247    end
248
249    th = Thread.new { @ts.read([:empty], 1) }
250    sleep(10)
251    assert_raise(Rinda::RequestExpiredError) do
252      thread_join(th)
253    end
254  end
255
256  def test_symbol_tuple
257    @ts.write([:symbol, :symbol])
258    @ts.write(['string', :string])
259    assert_equal([[:symbol, :symbol]], @ts.read_all([:symbol, nil]))
260    assert_equal([[:symbol, :symbol]], @ts.read_all([Symbol, nil]))
261    assert_equal([], @ts.read_all([:nil, nil]))
262  end
263
264  def test_core_01
265    5.times do |n|
266      @ts.write([:req, 2])
267    end
268
269    assert_equal([[:req, 2], [:req, 2], [:req, 2], [:req, 2], [:req, 2]],
270		 @ts.read_all([nil, nil]))
271
272    taker = Thread.new(5) do |count|
273      s = 0
274      count.times do
275        tuple = @ts.take([:req, Integer])
276        assert_equal(2, tuple[1])
277        s += tuple[1]
278      end
279      @ts.write([:ans, s])
280      s
281    end
282
283    assert_equal(10, thread_join(taker))
284    assert_equal([:ans, 10], @ts.take([:ans, 10]))
285    assert_equal([], @ts.read_all([nil, nil]))
286  end
287
288  def test_core_02
289    taker = Thread.new(5) do |count|
290      s = 0
291      count.times do
292        tuple = @ts.take([:req, Integer])
293        assert_equal(2, tuple[1])
294        s += tuple[1]
295      end
296      @ts.write([:ans, s])
297      s
298    end
299
300    5.times do |n|
301      @ts.write([:req, 2])
302    end
303
304    assert_equal(10, thread_join(taker))
305    assert_equal([:ans, 10], @ts.take([:ans, 10]))
306    assert_equal([], @ts.read_all([nil, nil]))
307  end
308
309  def test_core_03_notify
310    notify1 = @ts.notify(nil, [:req, Integer])
311    notify2 = @ts.notify(nil, {"message"=>String, "name"=>String})
312
313    5.times do |n|
314      @ts.write([:req, 2])
315    end
316
317    5.times do
318      tuple = @ts.take([:req, Integer])
319      assert_equal(2, tuple[1])
320    end
321
322    5.times do
323      assert_equal(['write', [:req, 2]], notify1.pop)
324    end
325    5.times do
326      assert_equal(['take', [:req, 2]], notify1.pop)
327    end
328
329    @ts.write({"message"=>"first", "name"=>"3"})
330    @ts.write({"message"=>"second", "name"=>"1"})
331    @ts.write({"message"=>"third", "name"=>"0"})
332    @ts.take({"message"=>"third", "name"=>"0"})
333    @ts.take({"message"=>"first", "name"=>"3"})
334
335    assert_equal(["write", {"message"=>"first", "name"=>"3"}], notify2.pop)
336    assert_equal(["write", {"message"=>"second", "name"=>"1"}], notify2.pop)
337    assert_equal(["write", {"message"=>"third", "name"=>"0"}], notify2.pop)
338    assert_equal(["take", {"message"=>"third", "name"=>"0"}], notify2.pop)
339    assert_equal(["take", {"message"=>"first", "name"=>"3"}], notify2.pop)
340  end
341
342  def test_cancel_01
343    entry = @ts.write([:removeme, 1])
344    assert_equal([[:removeme, 1]], @ts.read_all([nil, nil]))
345    entry.cancel
346    assert_equal([], @ts.read_all([nil, nil]))
347
348    template = nil
349    taker = Thread.new do
350      @ts.take([:take, nil], 10) do |t|
351        template = t
352	Thread.new do
353	  template.cancel
354	end
355      end
356    end
357
358    sleep(2)
359
360    assert_raise(Rinda::RequestCanceledError) do
361      assert_nil(thread_join(taker))
362    end
363
364    assert(template.canceled?)
365
366    @ts.write([:take, 1])
367
368    assert_equal([[:take, 1]], @ts.read_all([nil, nil]))
369  end
370
371  def test_cancel_02
372    entry = @ts.write([:removeme, 1])
373    assert_equal([[:removeme, 1]], @ts.read_all([nil, nil]))
374    entry.cancel
375    assert_equal([], @ts.read_all([nil, nil]))
376
377    template = nil
378    reader = Thread.new do
379      @ts.read([:take, nil], 10) do |t|
380        template = t
381	Thread.new do
382	  template.cancel
383	end
384      end
385    end
386
387    sleep(2)
388
389    assert_raise(Rinda::RequestCanceledError) do
390      assert_nil(thread_join(reader))
391    end
392
393    assert(template.canceled?)
394
395    @ts.write([:take, 1])
396
397    assert_equal([[:take, 1]], @ts.read_all([nil, nil]))
398  end
399
400  class SimpleRenewer
401    def initialize(sec, n = 1)
402      @sec = sec
403      @n = n
404    end
405
406    def renew
407      return -1 if @n <= 0
408      @n -= 1
409      return @sec
410    end
411  end
412
413  def test_00_renewer
414    tuple = Rinda::TupleEntry.new([1,2,3], true)
415    assert(!tuple.canceled?)
416    assert(tuple.expired?)
417    assert(!tuple.alive?)
418
419    tuple = Rinda::TupleEntry.new([1,2,3], 1)
420    assert(!tuple.canceled?)
421    assert(!tuple.expired?)
422    assert(tuple.alive?)
423    sleep(2)
424    assert(tuple.expired?)
425    assert(!tuple.alive?)
426
427    @renewer = SimpleRenewer.new(1,2)
428    tuple = Rinda::TupleEntry.new([1,2,3], @renewer)
429    assert(!tuple.canceled?)
430    assert(!tuple.expired?)
431    assert(tuple.alive?)
432    sleep(1)
433    assert(!tuple.canceled?)
434    assert(!tuple.expired?)
435    assert(tuple.alive?)
436    sleep(2)
437    assert(tuple.expired?)
438    assert(!tuple.alive?)
439  end
440end
441
442class TupleSpaceTest < Test::Unit::TestCase
443  include TupleSpaceTestModule
444
445  def setup
446    ThreadGroup.new.add(Thread.current)
447    @ts = Rinda::TupleSpace.new(1)
448  end
449  def teardown
450    # implementation-dependent
451    @ts.instance_eval{@keeper.kill if @keeper}
452  end
453end
454
455class TupleSpaceProxyTest < Test::Unit::TestCase
456  include TupleSpaceTestModule
457
458  def setup
459    ThreadGroup.new.add(Thread.current)
460    @ts_base = Rinda::TupleSpace.new(1)
461    @ts = Rinda::TupleSpaceProxy.new(@ts_base)
462  end
463  def teardown
464    # implementation-dependent
465    @ts_base.instance_eval{@keeper.kill if @keeper}
466  end
467
468  def test_remote_array_and_hash
469    # Don't remove ary/hsh local variables.
470    # These are necessary to protect objects from GC.
471    ary = [1, 2, 3]
472    @ts.write(DRbObject.new(ary))
473    assert_equal([1, 2, 3], @ts.take([1, 2, 3], 0))
474    hsh = {'head' => 1, 'tail' => 2}
475    @ts.write(DRbObject.new(hsh))
476    assert_equal({'head' => 1, 'tail' => 2},
477                 @ts.take({'head' => 1, 'tail' => 2}, 0))
478  end
479
480  @server = DRb.primary_server || DRb.start_service
481end
482
483end
484
485