8d6b01455a764a68f83a50743d4a16bacedbe33c
[ruby_posix_mq.git] / test / test_posix_mq.rb
blob8d6b01455a764a68f83a50743d4a16bacedbe33c
1 # -*- encoding: binary -*-
2 require 'test/unit'
3 require 'thread'
4 require 'fcntl'
5 $stderr.sync = $stdout.sync = true
6 require "dl"
7 begin
8   require "dl/func"
9 rescue LoadError
10 end
11 $-w = true
12 require 'posix_mq'
14 class Test_POSIX_MQ < Test::Unit::TestCase
16   POSIX_MQ.method_defined?(:to_io) or
17     warn "POSIX_MQ#to_io not supported on this platform: #{RUBY_PLATFORM}"
18   POSIX_MQ.method_defined?(:notify) or
19     warn "POSIX_MQ#notify not supported on this platform: #{RUBY_PLATFORM}"
21   def setup
22     @mq = nil
23     @path = "/posix_mq.rb.#{Time.now.to_i}.#$$.#{rand}"
24   end
26   def teardown
27     @mq or return
28     assert_equal @mq, @mq.unlink
29     assert ! @mq.closed?
30     assert_nil @mq.close
31     assert @mq.closed?
32   end
34   def test_open_with_null_byte
35     assert_raises(ArgumentError) { POSIX_MQ.open("/hello\0world", :rw) }
36   end
38   def test_unlink_with_null_byte
39     assert_raises(ArgumentError) { POSIX_MQ.open("/hello\0world", :rw) }
40   end
42   def test_gc
43     assert_nothing_raised do
44       2025.times { POSIX_MQ.new(@path, :rw) }
45       2025.times {
46         @mq = POSIX_MQ.new(@path, :rw)
47         @mq.to_io if @mq.respond_to?(:to_io)
48       }
49     end
50   end unless defined?RUBY_ENGINE && RUBY_ENGINE == "rbx"
52   def test_name_clobber_proof
53     @mq = POSIX_MQ.new(@path, :rw)
54     tmp = @mq.name
55     tmp.freeze
56     assert_nothing_raised { @mq.name.gsub!(/\A/, "foo") }
57     assert_equal tmp, @mq.name
58     assert tmp.object_id != @mq.name.object_id
59   end
61   def test_dup_clone
62     @mq = POSIX_MQ.new(@path, :rw)
63     dup = @mq.dup
64     assert_equal @mq.object_id, dup.object_id
65     clone = @mq.clone
66     assert_equal @mq.object_id, clone.object_id
67   end
69   def test_timed_receive_float
70     interval = 0.01
71     @mq = POSIX_MQ.new(@path, :rw)
72     assert ! @mq.nonblock?
73     t0 = Time.now
74     maybe_timeout { @mq.receive "", interval } or return
75     elapsed = Time.now - t0
76     assert elapsed > interval, elapsed.inspect
77     assert elapsed < 0.02, elapsed.inspect
78   end
80   def test_timed_receive_divmod
81     interval = Object.new
82     def interval.divmod(num)
83       num == 1 ? [ 0, 0.01 ] : nil
84     end
85     @mq = POSIX_MQ.new(@path, :rw)
86     assert ! @mq.nonblock?
87     t0 = Time.now
88     maybe_timeout { @mq.receive "", interval } or return
89     elapsed = Time.now - t0
90     assert elapsed >= 0.01, elapsed.inspect
91     assert elapsed <= 0.02, elapsed.inspect
92   end
94   def test_timed_receive_fixnum
95     interval = 1
96     @mq = POSIX_MQ.new(@path, :rw)
97     assert ! @mq.nonblock?
98     t0 = Time.now
99     maybe_timeout { @mq.receive "", interval } or return
100     elapsed = Time.now - t0
101     assert elapsed >= interval, elapsed.inspect
102     assert elapsed < 1.10, elapsed.inspect
103   end
105   def test_alarm_signal_safe
106     libc = alarm = nil
107     libcs = %w(libc.so.6 libc.so.0.1 libc.so.7 /usr/lib/libc.sl)
108     libcs.each do |name|
109       libc = DL::Handle.new(name) rescue next
110       if defined?(DL::Function)
111         alarm = libc["alarm"]
112         alarm = DL::CFunc.new(alarm, DL::TYPE_INT, "alarm")
113         alarm = DL::Function.new(alarm, [DL::TYPE_INT])
114       else
115         alarm = libc["alarm", "II"]
116       end
117       break
118     end
119     alarm or return warn "alarm() not found in #{libcs.inspect}"
120     alarms = 0
121     trap("ALRM") do
122       alarms += 1
123       Thread.new { @mq.send("HI") }
124     end
125     interval = 1
126     alarm.call interval
127     @mq = POSIX_MQ.new(@path, :rw)
128     assert ! @mq.nonblock?
129     t0 = Time.now
130     a = @mq.receive
131     elapsed = Time.now - t0
132     assert_equal(["HI", 0], a)
133     assert elapsed >= interval, elapsed.inspect
134     assert elapsed < 1.10, elapsed.inspect
135     assert_equal 1, alarms
136   end
138   def test_timed_send
139     interval = 0.01
140     @mq = POSIX_MQ.new(@path, :rw, 0666, POSIX_MQ::Attr[0, 1, 1, 0])
141     assert ! @mq.nonblock?
142     assert_nothing_raised {
143       begin
144         @mq.send "A", 1, interval
145       rescue NotImplementedError
146         return
147       end
148     }
149     t0 = Time.now
150     maybe_timeout { @mq.send "B", 1, interval } or return
151     elapsed = Time.now - t0
152     assert elapsed > interval
153   end
155   def test_open
156     POSIX_MQ.open(@path, IO::CREAT|IO::WRONLY, 0666) do |mq|
157       @mq = mq
158       assert mq.kind_of?(POSIX_MQ)
159       assert_equal @path, mq.name
160       assert_equal true, mq.send("HI", 0)
161       assert_equal 1, mq.attr.curmsgs
162       assert_nil mq.close
163       assert_raises(IOError) { mq.close }
164     end
165     assert @mq.closed?
166     @mq = nil
167     POSIX_MQ.unlink(@path)
168   end
170   def test_name
171     path = "" << @path.dup
172     path.freeze
173     @mq = POSIX_MQ.new @path, IO::CREAT|IO::WRONLY, 0666
174     assert_equal path, @mq.name
175   end
177   def test_new_readonly
178     @mq = POSIX_MQ.new @path, IO::CREAT|IO::WRONLY, 0666
179     rd = POSIX_MQ.new @path, IO::RDONLY
180     assert_equal @mq.name, rd.name
181     assert_nil rd.close
182   end
184   def test_send0_receive
185     @mq = POSIX_MQ.new @path, IO::CREAT|IO::RDWR, 0666
186     assert_equal(@mq, @mq << "hello")
187     assert_equal [ "hello", 0 ], @mq.receive
188   end
190   def test_send0_chain
191     @mq = POSIX_MQ.new @path, IO::CREAT|IO::RDWR, 0666
192     @mq << "hello" << "world"
193     assert_equal [ "hello", 0 ], @mq.receive
194     assert_equal [ "world", 0 ], @mq.receive
195   end
197   def test_shift
198     @mq = POSIX_MQ.new @path, IO::CREAT|IO::RDWR, 0666
199     @mq << "hello"
200     assert_equal "hello", @mq.shift
201   end
203   def test_shift_buf
204     buf = ""
205     @mq = POSIX_MQ.new @path, IO::CREAT|IO::RDWR, 0666
206     @mq << "hello"
207     assert_equal "hello", @mq.shift(buf)
208     assert_equal "hello", buf
209   end
211   def test_send_receive
212     @mq = POSIX_MQ.new @path, IO::CREAT|IO::RDWR, 0666
213     assert_equal true, @mq.send("hello", 0)
214     assert_equal [ "hello", 0 ], @mq.receive
215   end
217   def test_send_receive_buf
218     buf = ""
219     @mq = POSIX_MQ.new @path, IO::CREAT|IO::RDWR, 0666
220     assert_equal true, @mq.send("hello", 0)
221     assert_equal [ "hello", 0 ], @mq.receive(buf)
222     assert_equal "hello", buf
223   end
225   def test_send_receive_prio
226     @mq = POSIX_MQ.new @path, IO::CREAT|IO::RDWR, 0666
227     assert_equal true, @mq.send("hello", 2)
228     assert_equal [ "hello", 2 ], @mq.receive
229   end
231   def test_getattr
232     @mq = POSIX_MQ.new @path, IO::CREAT|IO::WRONLY, 0666
233     mq_attr = @mq.attr
234     assert_equal POSIX_MQ::Attr, mq_attr.class
235     assert mq_attr.flags.kind_of?(Integer)
236     assert mq_attr.maxmsg.kind_of?(Integer)
237     assert mq_attr.msgsize.kind_of?(Integer)
238     assert mq_attr.curmsgs.kind_of?(Integer)
239   end
241   def test_to_io
242     @mq = POSIX_MQ.new @path, IO::CREAT|IO::RDWR, 0666
243     assert @mq.to_io.kind_of?(IO)
244     assert_nothing_raised { IO.select([@mq], nil, nil, 0) }
245   end if POSIX_MQ.method_defined?(:to_io)
247   def test_notify
248     rd, wr = IO.pipe
249     orig = trap(:USR1) { wr.syswrite('.') }
250     @mq = POSIX_MQ.new @path, IO::CREAT|IO::RDWR, 0666
251     assert_nothing_raised { @mq.notify = :SIGUSR1 }
252     assert_nothing_raised { @mq.send("hello", 0) }
253     assert_equal [[rd], [], []], IO.select([rd], nil, nil, 10)
254     assert_equal '.', rd.sysread(1)
255     assert_nil(@mq.notify = nil)
256     assert_nothing_raised { @mq.send("hello", 0) }
257     assert_nil IO.select([rd], nil, nil, 0.1)
258     ensure
259       trap(:USR1, orig)
260   end
262   def test_notify_none
263     @mq = POSIX_MQ.new @path, IO::CREAT|IO::RDWR, 0666
264     assert_nothing_raised { @mq.notify = false }
265     pid = fork do
266       begin
267         @mq.notify = :USR1
268       rescue Errno::EBUSY
269         exit 0
270       rescue => e
271         p e
272       end
273       exit! 1
274     end
275     _, status = Process.waitpid2(pid)
276     assert status.success?, status.inspect
277   end
279   def test_setattr
280     @mq = POSIX_MQ.new @path, IO::CREAT|IO::WRONLY, 0666
281     mq_attr = POSIX_MQ::Attr.new(IO::NONBLOCK)
282     @mq.attr = mq_attr
283     assert_equal IO::NONBLOCK, @mq.attr.flags
284     assert mq_attr.flags.kind_of?(Integer)
286     mq_attr.flags = 0
287     @mq.attr = mq_attr
288     assert_equal 0, @mq.attr.flags
289   end
291   def test_setattr_fork
292     @mq = POSIX_MQ.new @path, IO::CREAT|IO::WRONLY, 0666
293     mq_attr = POSIX_MQ::Attr.new(IO::NONBLOCK)
294     @mq.attr = mq_attr
295     assert @mq.nonblock?
297     pid = fork { @mq.nonblock = false }
298     assert Process.waitpid2(pid)[1].success?
299     assert ! @mq.nonblock?
300   end
302   def test_new_nonblocking
303     @mq = POSIX_MQ.new @path, IO::CREAT|IO::WRONLY|IO::NONBLOCK, 0666
304     assert @mq.nonblock?
305   end
307   def test_new_blocking
308     @mq = POSIX_MQ.new @path, IO::CREAT|IO::WRONLY, 0666
309     assert ! @mq.nonblock?
310   end
312   def test_nonblock_toggle
313     @mq = POSIX_MQ.new @path, IO::CREAT|IO::WRONLY, 0666
314     assert ! @mq.nonblock?
315     @mq.nonblock = true
316     assert @mq.nonblock?
317     @mq.nonblock = false
318     assert ! @mq.nonblock?
319     assert_raises(ArgumentError) { @mq.nonblock = nil }
320   end
322   def test_new_sym_w
323     @mq = POSIX_MQ.new @path, :w
324     assert_equal IO::WRONLY, @mq.to_io.fcntl(Fcntl::F_GETFL)
325   end if POSIX_MQ.method_defined?(:to_io)
327   def test_new_sym_r
328     @mq = POSIX_MQ.new @path, :w
329     mq = nil
330     assert_nothing_raised { mq = POSIX_MQ.new @path, :r }
331     assert_equal IO::RDONLY, mq.to_io.fcntl(Fcntl::F_GETFL)
332     assert_nil mq.close
333   end if POSIX_MQ.method_defined?(:to_io)
335   def test_new_path_only
336     @mq = POSIX_MQ.new @path, :w
337     mq = nil
338     assert_nothing_raised { mq = POSIX_MQ.new @path }
339     assert_equal IO::RDONLY, mq.to_io.fcntl(Fcntl::F_GETFL)
340     assert_nil mq.close
341   end if POSIX_MQ.method_defined?(:to_io)
343   def test_new_sym_wr
344     @mq = POSIX_MQ.new @path, :rw
345     assert_equal IO::RDWR, @mq.to_io.fcntl(Fcntl::F_GETFL)
346   end if POSIX_MQ.method_defined?(:to_io)
348   def test_new_attr
349     mq_attr = POSIX_MQ::Attr.new(IO::NONBLOCK, 1, 1, 0)
350     @mq = POSIX_MQ.new @path, IO::CREAT|IO::RDWR, 0666, mq_attr
351     assert @mq.nonblock?
352     assert_equal mq_attr, @mq.attr
354     assert_raises(Errno::EAGAIN) { @mq.receive }
355     assert_raises(Errno::EMSGSIZE) { @mq << '..' }
356     assert_nothing_raised { @mq << '.' }
357     assert_equal [ '.', 0 ], @mq.receive
358     assert_nothing_raised { @mq << '.' }
359     assert_raises(Errno::EAGAIN) { @mq << '.' }
360   end
362   def test_try
363     mq_attr = POSIX_MQ::Attr.new(IO::NONBLOCK, 1, 1, 0)
364     @mq = POSIX_MQ.new @path, IO::CREAT|IO::RDWR, 0666, mq_attr
366     assert_nil @mq.tryreceive
367     assert_nil @mq.tryshift
368     assert_equal true, @mq.trysend("a")
369     assert_equal [ "a", 0 ], @mq.tryreceive
370     assert_equal true, @mq.trysend("b")
371     assert_equal "b", @mq.tryshift
372     assert_equal true, @mq.trysend("c")
373     assert_equal false, @mq.trysend("d")
374   end
376   def test_prio_max
377     min_posix_mq_prio_max = 31 # defined by POSIX
378     assert POSIX_MQ::PRIO_MAX >= min_posix_mq_prio_max
379   end
381   def test_open_max
382     assert POSIX_MQ::OPEN_MAX.kind_of?(Integer)
383   end
385   def test_notify_block_replace
386     q = Queue.new
387     @mq = POSIX_MQ.new(@path, :rw)
388     assert_nothing_raised { @mq.notify { |mq| q << mq } }
389     assert_nothing_raised { Process.waitpid2(fork { @mq << "hi" }) }
390     assert_equal @mq.object_id, q.pop.object_id
391     assert_equal "hi", @mq.receive.first
392     assert_nothing_raised { @mq.notify { |mq| q << "hi" } }
393     assert_nothing_raised { Process.waitpid2(fork { @mq << "bye" }) }
394     assert_equal "hi", q.pop
395   end if POSIX_MQ.method_defined?(:notify)
397   def test_notify_thread
398     q = Queue.new
399     @mq = POSIX_MQ.new(@path, :rw)
400     @mq.notify { |mq| q << Thread.current }
401     @mq << "."
402     x = q.pop
403     assert x.instance_of?(Thread)
404     assert Thread.current != x
405   end if POSIX_MQ.method_defined?(:notify)
407   def test_bad_open_mode
408     assert_raises(ArgumentError) { POSIX_MQ.new(@path, "rw") }
409   end
411   def test_bad_open_attr
412     assert_raises(TypeError) { POSIX_MQ.new(@path, :rw, 0666, [0, 1, 1, 0]) }
413   end
415   def test_bad_setattr
416     @mq = POSIX_MQ.new @path, IO::CREAT|IO::WRONLY, 0666
417     assert_raises(TypeError) { @mq.attr = {} }
418     assert_raises(TypeError) { @mq.attr = Struct.new(:a,:b,:c,:d).new }
419   end
421   def maybe_timeout
422     yield
423     assert_raises(exc) { } # FAIL
424     return true
425     rescue Errno::ETIMEDOUT => e
426       return true
427     rescue NotImplementedError => e
428       warn "E: #{e}"
429       return false
430   end