Ruby posix_mq 2.4.1
[ruby_posix_mq.git] / test / test_posix_mq.rb
blob843b65e08a4891c1f12aa9fa59b6cf31348f5c0b
1 # -*- encoding: binary -*-
2 # frozen_string_literal: true
3 require 'test/unit'
4 require 'thread'
5 require 'fcntl'
6 $stderr.sync = $stdout.sync = true
7 $-w = true
8 require 'posix_mq'
10 class Test_POSIX_MQ < Test::Unit::TestCase
12   POSIX_MQ.method_defined?(:to_io) or
13     warn "POSIX_MQ#to_io not supported on this platform: #{RUBY_PLATFORM}"
14   POSIX_MQ.method_defined?(:notify) or
15     warn "POSIX_MQ#notify not supported on this platform: #{RUBY_PLATFORM}"
16   POSIX_MQ.respond_to?(:for_fd) or
17     warn "POSIX_MQ::for_fd not supported on this platform: #{RUBY_PLATFORM}"
19   def setup
20     @mq = nil
21     @path = "/posix_mq.rb.#{Time.now.to_i}.#$$.#{rand}"
22   end
24   def teardown
25     @mq or return
26     assert_equal @mq, @mq.unlink
27     assert ! @mq.closed?
28     assert_nil @mq.close
29     assert @mq.closed?
30   end
32   def test_open_with_null_byte
33     assert_raises(ArgumentError) { POSIX_MQ.open("/hello\0world", :rw) }
34   end
36   def test_unlink_with_null_byte
37     assert_raises(ArgumentError) { POSIX_MQ.open("/hello\0world", :rw) }
38   end
40   def test_gc
41     assert_nothing_raised do
42       2025.times { POSIX_MQ.new(@path, :rw) }
43       2025.times {
44         @mq = POSIX_MQ.new(@path, :rw)
45         @mq.to_io if @mq.respond_to?(:to_io)
46       }
47     end
48   end unless defined?RUBY_ENGINE && RUBY_ENGINE == "rbx"
50   def test_name_clobber_proof
51     @mq = POSIX_MQ.new(@path, :rw)
52     tmp = @mq.name
53     tmp.freeze
54     assert_nothing_raised { @mq.name.gsub!(/\A/, "foo") }
55     assert_equal tmp, @mq.name
56     assert tmp.object_id != @mq.name.object_id
57   end
59   def test_dup_clone
60     @mq = POSIX_MQ.new(@path, :rw)
61     dup = @mq.dup
62     assert_equal @mq.object_id, dup.object_id
63     clone = @mq.clone
64     assert_equal @mq.object_id, clone.object_id
65   end
67   def test_timed_receive_float
68     interval = 0.01
69     @mq = POSIX_MQ.new(@path, :rw)
70     assert ! @mq.nonblock?
71     t0 = Time.now
72     maybe_timeout { @mq.receive nil, interval } or return
73     elapsed = Time.now - t0
74     assert_operator elapsed, :>, interval, elapsed.inspect
75     assert_operator elapsed, :<, 0.04, elapsed.inspect
76   end
78   def test_timed_receive_divmod
79     interval = Object.new
80     def interval.divmod(num)
81       num == 1 ? [ 0, 0.01 ] : nil
82     end
83     @mq = POSIX_MQ.new(@path, :rw)
84     assert ! @mq.nonblock?
85     t0 = Time.now
86     maybe_timeout { @mq.receive nil, interval } or return
87     elapsed = Time.now - t0
88     assert_operator elapsed, :>=, 0.01, elapsed.inspect
89     assert_operator elapsed, :<=, 0.04, elapsed.inspect
90   end
92   def test_timed_receive_fixnum
93     interval = 1
94     @mq = POSIX_MQ.new(@path, :rw)
95     assert ! @mq.nonblock?
96     t0 = Time.now
97     maybe_timeout { @mq.receive nil, interval } or return
98     elapsed = Time.now - t0
99     assert elapsed >= interval, elapsed.inspect
100     assert elapsed < 1.10, elapsed.inspect
101   end
103   def test_signal_safe
104     alarm = lambda do |x|
105       Thread.new(x) do |time|
106         sleep(time)
107         Process.kill(:USR1, $$)
108       end
109     end
110     alarms = 0
111     sig = trap(:USR1) do
112       alarms += 1
113       Thread.new { @mq.send("HI") }
114     end
115     interval = 1
116     alarm.call interval
117     @mq = POSIX_MQ.new(@path, :rw)
118     assert ! @mq.nonblock?
119     t0 = Time.now
120     a = @mq.receive
121     elapsed = Time.now - t0
122     assert_equal(["HI", 0], a)
123     assert elapsed >= interval, elapsed.inspect
124     assert elapsed < 1.10, elapsed.inspect
125     assert_equal 1, alarms
126   ensure
127     trap(:USR1, sig) if sig
128   end
130   def test_timed_send
131     interval = 0.01
132     @mq = POSIX_MQ.new(@path, :rw, 0666, POSIX_MQ::Attr[0, 1, 1, 0])
133     assert ! @mq.nonblock?
134     assert_nothing_raised {
135       begin
136         @mq.send "A", 1, interval
137       rescue NotImplementedError
138         return
139       end
140     }
141     t0 = Time.now
142     maybe_timeout { @mq.send "B", 1, interval } or return
143     elapsed = Time.now - t0
144     assert elapsed > interval
145   end
147   def test_open
148     POSIX_MQ.open(@path, IO::CREAT|IO::WRONLY, 0666) do |mq|
149       @mq = mq
150       assert mq.kind_of?(POSIX_MQ)
151       assert_equal @path, mq.name
152       assert_equal true, mq.send("HI", 0)
153       assert_equal 1, mq.attr.curmsgs
154       assert_nil mq.close
156       r, w = IO.pipe
157       w.close
158       r.close
159       idempotent_close = begin
160         r.close
161         true
162       rescue IOError
163         false
164       end
165       if idempotent_close
166         2.times { assert_nil mq.close }
167       else
168         assert_raises(IOError) { mq.close }
169       end
170     end
171     assert @mq.closed?
172     @mq = nil
173     POSIX_MQ.unlink(@path)
174   end
176   def test_name
177     path = @path.dup
178     path.freeze
179     @mq = POSIX_MQ.new @path, IO::CREAT|IO::WRONLY, 0666
180     assert_equal path, @mq.name
181   end
183   def test_new_readonly
184     @mq = POSIX_MQ.new @path, IO::CREAT|IO::WRONLY, 0666
185     rd = POSIX_MQ.new @path, IO::RDONLY
186     assert_equal @mq.name, rd.name
187     assert_nil rd.close
188   end
190   def test_send0_receive
191     @mq = POSIX_MQ.new @path, IO::CREAT|IO::RDWR, 0666
192     assert_equal(@mq, @mq << "hello")
193     assert_equal [ "hello", 0 ], @mq.receive
194   end
196   def test_send0_chain
197     @mq = POSIX_MQ.new @path, IO::CREAT|IO::RDWR, 0666
198     @mq << "hello" << "world"
199     assert_equal [ "hello", 0 ], @mq.receive
200     assert_equal [ "world", 0 ], @mq.receive
201   end
203   def test_shift
204     @mq = POSIX_MQ.new @path, IO::CREAT|IO::RDWR, 0666
205     @mq << "hello"
206     assert_equal "hello", @mq.shift
207   end
209   def test_shift_buf
210     buf = "".dup
211     @mq = POSIX_MQ.new @path, IO::CREAT|IO::RDWR, 0666
212     @mq << "hello"
213     assert_equal "hello", @mq.shift(buf)
214     assert_equal "hello", buf
215   end
217   def test_send_receive
218     @mq = POSIX_MQ.new @path, IO::CREAT|IO::RDWR, 0666
219     assert_equal true, @mq.send("hello", 0)
220     assert_equal [ "hello", 0 ], @mq.receive
221   end
223   def test_send_receive_buf
224     buf = "".dup
225     @mq = POSIX_MQ.new @path, IO::CREAT|IO::RDWR, 0666
226     assert_equal true, @mq.send("hello", 0)
227     assert_equal [ "hello", 0 ], @mq.receive(buf)
228     assert_equal "hello", buf
229   end
231   def test_send_receive_prio
232     @mq = POSIX_MQ.new @path, IO::CREAT|IO::RDWR, 0666
233     assert_equal true, @mq.send("hello", 2)
234     assert_equal [ "hello", 2 ], @mq.receive
235   end
237   def test_getattr
238     @mq = POSIX_MQ.new @path, IO::CREAT|IO::WRONLY, 0666
239     mq_attr = @mq.attr
240     assert_equal POSIX_MQ::Attr, mq_attr.class
241     assert mq_attr.flags.kind_of?(Integer)
242     assert mq_attr.maxmsg.kind_of?(Integer)
243     assert mq_attr.msgsize.kind_of?(Integer)
244     assert mq_attr.curmsgs.kind_of?(Integer)
245   end
247   def test_to_io
248     @mq = POSIX_MQ.new @path, IO::CREAT|IO::RDWR, 0666
249     assert @mq.to_io.kind_of?(IO)
250     assert_nothing_raised { IO.select([@mq], nil, nil, 0) }
251   end if POSIX_MQ.method_defined?(:to_io)
253   def test_for_fd
254     buf = "".dup
255     @mq = POSIX_MQ.new @path, IO::CREAT|IO::RDWR, 0666
256     @alt = POSIX_MQ.for_fd(@mq.to_io.to_i)
257     assert_equal true, @mq.send("hello", 0)
258     assert_equal [ "hello", 0 ], @alt.receive(buf)
259     assert_equal "hello", buf
260     assert_equal @mq.to_io.to_i, @alt.to_io.to_i
261     assert_raises(ArgumentError) { @alt.name }
262     assert_raises(Errno::EBADF) { POSIX_MQ.for_fd(1) }
263     @alt.autoclose = false
264     assert_equal false, @alt.autoclose?
266     # iterate a bunch and hope GC kicks in
267     fd = @mq.to_io.fileno
268     10_000.times do
269       mq = POSIX_MQ.for_fd(fd)
270       assert_equal true, mq.autoclose?
271       mq.autoclose = false
272       assert_equal false, mq.autoclose?
273     end
274   end if POSIX_MQ.respond_to?(:for_fd) && POSIX_MQ.method_defined?(:to_io)
276   def test_autoclose_propagates_to_io
277     @mq = POSIX_MQ.new @path, IO::CREAT|IO::RDWR, 0666
278     @mq.autoclose = false
279     assert_equal false, @mq.to_io.autoclose?
280     @mq.autoclose = true
281     assert_equal true, @mq.to_io.autoclose?
282   end if POSIX_MQ.method_defined?(:to_io)
284   def test_notify
285     rd, wr = IO.pipe
286     orig = trap(:USR1) { wr.syswrite('.') }
287     @mq = POSIX_MQ.new @path, IO::CREAT|IO::RDWR, 0666
288     assert_nothing_raised { @mq.notify = :SIGUSR1 }
289     assert_nothing_raised { @mq.send("hello", 0) }
290     assert_equal [[rd], [], []], IO.select([rd], nil, nil, 10)
291     assert_equal '.', rd.sysread(1)
292     assert_nil(@mq.notify = nil)
293     assert_nothing_raised { @mq.send("hello", 0) }
294     assert_nil IO.select([rd], nil, nil, 0.1)
295     ensure
296       trap(:USR1, orig)
297   end if POSIX_MQ.method_defined?(:to_io)
299   def test_notify_none
300     @mq = POSIX_MQ.new @path, IO::CREAT|IO::RDWR, 0666
301     assert_nothing_raised { @mq.notify = false }
302     pid = fork do
303       begin
304         @mq.notify = :USR1
305       rescue Errno::EBUSY
306         exit!(0)
307       rescue => e
308         exit!(0) if Errno::EBADF === e && RUBY_PLATFORM =~ /freebsd/
309         warn "#{e.message} (#{e.class})\n"
310       end
311       exit! 1
312     end
313     _, status = Process.waitpid2(pid)
314     assert status.success?, status.inspect
315   end
317   def test_setattr
318     @mq = POSIX_MQ.new @path, IO::CREAT|IO::WRONLY, 0666
319     mq_attr = POSIX_MQ::Attr.new(IO::NONBLOCK)
320     @mq.attr = mq_attr
321     assert_equal IO::NONBLOCK, @mq.attr.flags
322     assert mq_attr.flags.kind_of?(Integer)
324     mq_attr.flags = 0
325     @mq.attr = mq_attr
326     assert_equal 0, @mq.attr.flags
327   end
329   def test_setattr_fork
330     return if RUBY_PLATFORM !~ /freebsd/
331     @mq = POSIX_MQ.new @path, IO::CREAT|IO::WRONLY, 0666
332     mq_attr = POSIX_MQ::Attr.new(IO::NONBLOCK)
333     @mq.attr = mq_attr
334     assert @mq.nonblock?
336     pid = fork do
337       begin
338         @mq.nonblock = false
339       rescue => e
340         exit!(2) if Errno::EBADF === e && RUBY_PLATFORM =~ /freebsd/
341         warn "#{e.message} (#{e.class})\n"
342         exit!(1)
343       end
344       exit!(0)
345     end
346     _, status = Process.waitpid2(pid)
347     if status.success?
348       assert ! @mq.nonblock?
349     else
350       assert_equal 2, status.exitstatus
351     end
352   end
354   def test_new_nonblocking
355     @mq = POSIX_MQ.new @path, IO::CREAT|IO::WRONLY|IO::NONBLOCK, 0666
356     assert @mq.nonblock?
357   end
359   def test_new_blocking
360     @mq = POSIX_MQ.new @path, IO::CREAT|IO::WRONLY, 0666
361     assert ! @mq.nonblock?
362   end
364   def test_nonblock_toggle
365     @mq = POSIX_MQ.new @path, IO::CREAT|IO::WRONLY, 0666
366     assert ! @mq.nonblock?
367     @mq.nonblock = true
368     assert @mq.nonblock?
369     @mq.nonblock = false
370     assert ! @mq.nonblock?
371     assert_raises(ArgumentError) { @mq.nonblock = nil }
372   end
374   def test_new_sym_w
375     @mq = POSIX_MQ.new @path, :w
376     assert_equal IO::WRONLY, @mq.to_io.fcntl(Fcntl::F_GETFL)
377   end if POSIX_MQ.method_defined?(:to_io)
379   def test_new_sym_r
380     @mq = POSIX_MQ.new @path, :w
381     mq = nil
382     assert_nothing_raised { mq = POSIX_MQ.new @path, :r }
383     assert_equal IO::RDONLY, mq.to_io.fcntl(Fcntl::F_GETFL)
384     assert_nil mq.close
385   end if POSIX_MQ.method_defined?(:to_io)
387   def test_new_path_only
388     @mq = POSIX_MQ.new @path, :w
389     mq = nil
390     assert_nothing_raised { mq = POSIX_MQ.new @path }
391     assert_equal IO::RDONLY, mq.to_io.fcntl(Fcntl::F_GETFL)
392     assert_nil mq.close
393   end if POSIX_MQ.method_defined?(:to_io)
395   def test_new_sym_wr
396     @mq = POSIX_MQ.new @path, :rw
397     assert_equal IO::RDWR, @mq.to_io.fcntl(Fcntl::F_GETFL)
398   end if POSIX_MQ.method_defined?(:to_io)
400   def test_new_attr
401     mq_attr = POSIX_MQ::Attr.new(IO::NONBLOCK, 1, 1, 0)
402     @mq = POSIX_MQ.new @path, IO::CREAT|IO::RDWR, 0666, mq_attr
403     assert @mq.nonblock?
404     assert_equal mq_attr, @mq.attr
406     assert_raises(Errno::EAGAIN) { @mq.receive }
407     assert_raises(Errno::EMSGSIZE) { @mq << '..' }
408     assert_nothing_raised { @mq << '.' }
409     assert_equal [ '.', 0 ], @mq.receive
410     assert_nothing_raised { @mq << '.' }
411     assert_raises(Errno::EAGAIN) { @mq << '.' }
412   end
414   def test_try
415     mq_attr = POSIX_MQ::Attr.new(IO::NONBLOCK, 1, 1, 0)
416     @mq = POSIX_MQ.new @path, IO::CREAT|IO::RDWR, 0666, mq_attr
418     assert_nil @mq.tryreceive
419     assert_nil @mq.tryshift
420     assert_equal true, @mq.trysend("a")
421     assert_equal [ "a", 0 ], @mq.tryreceive
422     assert_equal true, @mq.trysend("b")
423     assert_equal "b", @mq.tryshift
424     assert_equal true, @mq.trysend("c")
425     assert_equal false, @mq.trysend("d")
426   end
428   def test_prio_max
429     min_posix_mq_prio_max = 31 # defined by POSIX
430     assert POSIX_MQ::PRIO_MAX >= min_posix_mq_prio_max
431   end
433   def test_open_max
434     assert POSIX_MQ::OPEN_MAX.kind_of?(Integer)
435   end
437   def test_notify_block_replace
438     q = Queue.new
439     @mq = POSIX_MQ.new(@path, :rw)
440     assert_nothing_raised { @mq.notify { |mq| q << mq } }
441     assert_nothing_raised { Process.waitpid2(fork { @mq << "hi" }) }
442     assert_equal @mq.object_id, q.pop.object_id
443     assert_equal "hi", @mq.receive.first
444     assert_nothing_raised { @mq.notify { |mq| q << "hi" } }
445     assert_nothing_raised { Process.waitpid2(fork { @mq << "bye" }) }
446     assert_equal "hi", q.pop
447   end if POSIX_MQ.method_defined?(:notify)
449   def test_notify_thread
450     q = Queue.new
451     @mq = POSIX_MQ.new(@path, :rw)
452     @mq.notify { |mq| q << Thread.current }
453     @mq << "."
454     x = q.pop
455     assert x.instance_of?(Thread)
456     assert Thread.current != x
457   end if POSIX_MQ.method_defined?(:notify)
459   def test_bad_open_mode
460     assert_raises(ArgumentError) { POSIX_MQ.new(@path, "rw") }
461   end
463   def test_bad_open_attr
464     assert_raises(TypeError) { POSIX_MQ.new(@path, :rw, 0666, [0, 1, 1, 0]) }
465   end
467   def test_bad_setattr
468     @mq = POSIX_MQ.new @path, IO::CREAT|IO::WRONLY, 0666
469     assert_raises(TypeError) { @mq.attr = {} }
470     assert_raises(TypeError) { @mq.attr = Struct.new(:a,:b,:c,:d).new }
471   end
473   def maybe_timeout
474     yield
475     assert_raises(exc) { } # FAIL
476     return true
477     rescue Errno::ETIMEDOUT => e
478       return true
479     rescue NotImplementedError => e
480       warn "E: #{e}"
481       return false
482   end