Do not trust locally cached mq_flags
[ruby_posix_mq.git] / test / test_posix_mq.rb
blob97b633765c8bbf5a42d2ec946a12511b2fe61f17
1 # -*- encoding: binary -*-
2 require 'test/unit'
3 require 'posix_mq'
4 require 'thread'
5 require 'fcntl'
6 $stderr.sync = $stdout.sync = true
7 require "dl"
9 class Test_POSIX_MQ < Test::Unit::TestCase
11   POSIX_MQ.method_defined?(:to_io) or
12     warn "POSIX_MQ#to_io not supported on this platform: #{RUBY_PLATFORM}"
13   POSIX_MQ.method_defined?(:notify) or
14     warn "POSIX_MQ#notify not supported on this platform: #{RUBY_PLATFORM}"
16   def setup
17     @mq = nil
18     @path = "/posix_mq.rb.#{Time.now.to_i}.#$$.#{rand}"
19   end
21   def teardown
22     @mq or return
23     assert_equal @mq, @mq.unlink
24     assert ! @mq.closed?
25     assert_nil @mq.close
26     assert @mq.closed?
27   end
29   def test_open_with_null_byte
30     assert_raises(ArgumentError) { POSIX_MQ.open("/hello\0world", :rw) }
31   end
33   def test_unlink_with_null_byte
34     assert_raises(ArgumentError) { POSIX_MQ.open("/hello\0world", :rw) }
35   end
37   def test_gc
38     assert_nothing_raised do
39       2025.times { POSIX_MQ.new(@path, :rw) }
40       2025.times {
41         @mq = POSIX_MQ.new(@path, :rw)
42         @mq.to_io if @mq.respond_to?(:to_io)
43       }
44     end
45   end unless defined?RUBY_ENGINE && RUBY_ENGINE == "rbx"
47   def test_name_clobber_proof
48     @mq = POSIX_MQ.new(@path, :rw)
49     tmp = @mq.name
50     tmp.freeze
51     assert_nothing_raised { @mq.name.gsub!(/\A/, "foo") }
52     assert_equal tmp, @mq.name
53     assert tmp.object_id != @mq.name.object_id
54   end
56   def test_dup_clone
57     @mq = POSIX_MQ.new(@path, :rw)
58     dup = @mq.dup
59     assert_equal @mq.object_id, dup.object_id
60     clone = @mq.clone
61     assert_equal @mq.object_id, clone.object_id
62   end
64   def test_timed_receive_float
65     interval = 0.01
66     @mq = POSIX_MQ.new(@path, :rw)
67     assert ! @mq.nonblock?
68     t0 = Time.now
69     maybe_timeout { @mq.receive "", interval } or return
70     elapsed = Time.now - t0
71     assert elapsed > interval, elapsed.inspect
72     assert elapsed < 0.02, elapsed.inspect
73   end
75   def test_timed_receive_divmod
76     interval = Object.new
77     def interval.divmod(num)
78       num == 1 ? [ 0, 0.01 ] : nil
79     end
80     @mq = POSIX_MQ.new(@path, :rw)
81     assert ! @mq.nonblock?
82     t0 = Time.now
83     maybe_timeout { @mq.receive "", interval } or return
84     elapsed = Time.now - t0
85     assert elapsed >= 0.01, elapsed.inspect
86     assert elapsed <= 0.02, elapsed.inspect
87   end
89   def test_timed_receive_fixnum
90     interval = 1
91     @mq = POSIX_MQ.new(@path, :rw)
92     assert ! @mq.nonblock?
93     t0 = Time.now
94     maybe_timeout { @mq.receive "", interval } or return
95     elapsed = Time.now - t0
96     assert elapsed >= interval, elapsed.inspect
97     assert elapsed < 1.10, elapsed.inspect
98   end
100   def test_alarm_signal_safe
101     libc = alarm = nil
102     libcs = %w(libc.so.6 /usr/lib/libc.sl)
103     libcs.each do |name|
104       libc = DL::Handle.new(name) rescue next
105       if defined?(Fiddle)
106         alarm = libc["alarm"]
107         alarm = Fiddle::Function.new(alarm, [DL::TYPE_INT], DL::TYPE_INT)
108       else
109         alarm = libc["alarm", "II"]
110       end
111       break
112     end
113     alarm or return warn "alarm() not found in #{libcs.inspect}"
114     alarms = 0
115     trap("ALRM") { alarms += 1 }
116     interval = 1
117     alarm.call interval
118     @mq = POSIX_MQ.new(@path, :rw)
119     assert ! @mq.nonblock?
120     t0 = Time.now
121     a = nil
122     assert_raises(Errno::EINTR) { a = @mq.receive }
123     elapsed = Time.now - t0
124     assert_nil a
125     assert elapsed >= interval, elapsed.inspect
126     assert elapsed < 1.10, elapsed.inspect
127     assert_equal 1, alarms
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_nil mq.send("HI", 0)
153       assert_equal 1, mq.attr.curmsgs
154       assert_nil mq.close
155       assert_raises(IOError) { mq.close }
156     end
157     assert @mq.closed?
158     @mq = nil
159     POSIX_MQ.unlink(@path)
160   end
162   def test_name
163     path = "" << @path.dup
164     path.freeze
165     @mq = POSIX_MQ.new @path, IO::CREAT|IO::WRONLY, 0666
166     assert_equal path, @mq.name
167   end
169   def test_new_readonly
170     @mq = POSIX_MQ.new @path, IO::CREAT|IO::WRONLY, 0666
171     rd = POSIX_MQ.new @path, IO::RDONLY
172     assert_equal @mq.name, rd.name
173     assert_nil rd.close
174   end
176   def test_send0_receive
177     @mq = POSIX_MQ.new @path, IO::CREAT|IO::RDWR, 0666
178     assert_equal(@mq, @mq << "hello")
179     assert_equal [ "hello", 0 ], @mq.receive
180   end
182   def test_send0_chain
183     @mq = POSIX_MQ.new @path, IO::CREAT|IO::RDWR, 0666
184     @mq << "hello" << "world"
185     assert_equal [ "hello", 0 ], @mq.receive
186     assert_equal [ "world", 0 ], @mq.receive
187   end
189   def test_shift
190     @mq = POSIX_MQ.new @path, IO::CREAT|IO::RDWR, 0666
191     @mq << "hello"
192     assert_equal "hello", @mq.shift
193   end
195   def test_shift_buf
196     buf = ""
197     @mq = POSIX_MQ.new @path, IO::CREAT|IO::RDWR, 0666
198     @mq << "hello"
199     assert_equal "hello", @mq.shift(buf)
200     assert_equal "hello", buf
201   end
203   def test_send_receive
204     @mq = POSIX_MQ.new @path, IO::CREAT|IO::RDWR, 0666
205     assert_nil @mq.send("hello", 0)
206     assert_equal [ "hello", 0 ], @mq.receive
207   end
209   def test_send_receive_buf
210     buf = ""
211     @mq = POSIX_MQ.new @path, IO::CREAT|IO::RDWR, 0666
212     assert_nil @mq.send("hello", 0)
213     assert_equal [ "hello", 0 ], @mq.receive(buf)
214     assert_equal "hello", buf
215   end
217   def test_send_receive_prio
218     @mq = POSIX_MQ.new @path, IO::CREAT|IO::RDWR, 0666
219     assert_nil @mq.send("hello", 2)
220     assert_equal [ "hello", 2 ], @mq.receive
221   end
223   def test_getattr
224     @mq = POSIX_MQ.new @path, IO::CREAT|IO::WRONLY, 0666
225     mq_attr = @mq.attr
226     assert_equal POSIX_MQ::Attr, mq_attr.class
227     assert mq_attr.flags.kind_of?(Integer)
228     assert mq_attr.maxmsg.kind_of?(Integer)
229     assert mq_attr.msgsize.kind_of?(Integer)
230     assert mq_attr.curmsgs.kind_of?(Integer)
231   end
233   def test_to_io
234     @mq = POSIX_MQ.new @path, IO::CREAT|IO::RDWR, 0666
235     assert @mq.to_io.kind_of?(IO)
236     assert_nothing_raised { IO.select([@mq], nil, nil, 0) }
237   end if POSIX_MQ.method_defined?(:to_io)
239   def test_notify
240     rd, wr = IO.pipe
241     orig = trap(:USR1) { wr.syswrite('.') }
242     @mq = POSIX_MQ.new @path, IO::CREAT|IO::RDWR, 0666
243     assert_nothing_raised { @mq.notify = :SIGUSR1 }
244     assert_nothing_raised { @mq.send("hello", 0) }
245     assert_equal [[rd], [], []], IO.select([rd], nil, nil, 10)
246     assert_equal '.', rd.sysread(1)
247     assert_nil(@mq.notify = nil)
248     assert_nothing_raised { @mq.send("hello", 0) }
249     assert_nil IO.select([rd], nil, nil, 0.1)
250     ensure
251       trap(:USR1, orig)
252   end
254   def test_notify_none
255     @mq = POSIX_MQ.new @path, IO::CREAT|IO::RDWR, 0666
256     assert_nothing_raised { @mq.notify = false }
257     pid = fork do
258       begin
259         @mq.notify = :USR1
260       rescue Errno::EBUSY
261         exit 0
262       rescue => e
263         p e
264       end
265       exit! 1
266     end
267     _, status = Process.waitpid2(pid)
268     assert status.success?, status.inspect
269   end
271   def test_setattr
272     @mq = POSIX_MQ.new @path, IO::CREAT|IO::WRONLY, 0666
273     mq_attr = POSIX_MQ::Attr.new(IO::NONBLOCK)
274     @mq.attr = mq_attr
275     assert_equal IO::NONBLOCK, @mq.attr.flags
276     assert mq_attr.flags.kind_of?(Integer)
278     mq_attr.flags = 0
279     @mq.attr = mq_attr
280     assert_equal 0, @mq.attr.flags
281   end
283   def test_setattr_fork
284     @mq = POSIX_MQ.new @path, IO::CREAT|IO::WRONLY, 0666
285     mq_attr = POSIX_MQ::Attr.new(IO::NONBLOCK)
286     @mq.attr = mq_attr
287     assert @mq.nonblock?
289     pid = fork { @mq.nonblock = false }
290     assert Process.waitpid2(pid)[1].success?
291     assert ! @mq.nonblock?
292   end
294   def test_new_nonblocking
295     @mq = POSIX_MQ.new @path, IO::CREAT|IO::WRONLY|IO::NONBLOCK, 0666
296     assert @mq.nonblock?
297   end
299   def test_new_blocking
300     @mq = POSIX_MQ.new @path, IO::CREAT|IO::WRONLY, 0666
301     assert ! @mq.nonblock?
302   end
304   def test_nonblock_toggle
305     @mq = POSIX_MQ.new @path, IO::CREAT|IO::WRONLY, 0666
306     assert ! @mq.nonblock?
307     @mq.nonblock = true
308     assert @mq.nonblock?
309     @mq.nonblock = false
310     assert ! @mq.nonblock?
311     assert_raises(ArgumentError) { @mq.nonblock = nil }
312   end
314   def test_new_sym_w
315     @mq = POSIX_MQ.new @path, :w
316     assert_equal IO::WRONLY, @mq.to_io.fcntl(Fcntl::F_GETFL)
317   end if POSIX_MQ.method_defined?(:to_io)
319   def test_new_sym_r
320     @mq = POSIX_MQ.new @path, :w
321     mq = nil
322     assert_nothing_raised { mq = POSIX_MQ.new @path, :r }
323     assert_equal IO::RDONLY, mq.to_io.fcntl(Fcntl::F_GETFL)
324     assert_nil mq.close
325   end if POSIX_MQ.method_defined?(:to_io)
327   def test_new_path_only
328     @mq = POSIX_MQ.new @path, :w
329     mq = nil
330     assert_nothing_raised { mq = POSIX_MQ.new @path }
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_sym_wr
336     @mq = POSIX_MQ.new @path, :rw
337     assert_equal IO::RDWR, @mq.to_io.fcntl(Fcntl::F_GETFL)
338   end if POSIX_MQ.method_defined?(:to_io)
340   def test_new_attr
341     mq_attr = POSIX_MQ::Attr.new(IO::NONBLOCK, 1, 1, 0)
342     @mq = POSIX_MQ.new @path, IO::CREAT|IO::RDWR, 0666, mq_attr
343     assert @mq.nonblock?
344     assert_equal mq_attr, @mq.attr
346     assert_raises(Errno::EAGAIN) { @mq.receive }
347     assert_raises(Errno::EMSGSIZE) { @mq << '..' }
348     assert_nothing_raised { @mq << '.' }
349     assert_equal [ '.', 0 ], @mq.receive
350     assert_nothing_raised { @mq << '.' }
351     assert_raises(Errno::EAGAIN) { @mq << '.' }
352   end
354   def test_prio_max
355     min_posix_mq_prio_max = 31 # defined by POSIX
356     assert POSIX_MQ::PRIO_MAX >= min_posix_mq_prio_max
357   end
359   def test_open_max
360     assert POSIX_MQ::OPEN_MAX.kind_of?(Integer)
361   end
363   def test_notify_block_replace
364     q = Queue.new
365     @mq = POSIX_MQ.new(@path, :rw)
366     assert_nothing_raised { @mq.notify { |mq| q << mq } }
367     assert_nothing_raised { Process.waitpid2(fork { @mq << "hi" }) }
368     assert_equal @mq.object_id, q.pop.object_id
369     assert_equal "hi", @mq.receive.first
370     assert_nothing_raised { @mq.notify { |mq| q << "hi" } }
371     assert_nothing_raised { Process.waitpid2(fork { @mq << "bye" }) }
372     assert_equal "hi", q.pop
373   end if POSIX_MQ.method_defined?(:notify)
375   def test_notify_thread
376     q = Queue.new
377     @mq = POSIX_MQ.new(@path, :rw)
378     @mq.notify { |mq| q << Thread.current }
379     @mq << "."
380     x = q.pop
381     assert x.instance_of?(Thread)
382     assert Thread.current != x
383   end if POSIX_MQ.method_defined?(:notify)
385   def test_bad_open_mode
386     assert_raises(ArgumentError) { mq = POSIX_MQ.new(@path, "rw") }
387   end
389   def test_bad_open_attr
390     assert_raises(TypeError) { POSIX_MQ.new(@path, :rw, 0666, [0, 1, 1, 0]) }
391   end
393   def test_bad_setattr
394     @mq = POSIX_MQ.new @path, IO::CREAT|IO::WRONLY, 0666
395     assert_raises(TypeError) { @mq.attr = {} }
396     assert_raises(TypeError) { @mq.attr = Struct.new(:a,:b,:c,:d).new }
397   end
399   def maybe_timeout
400     yield
401     assert_raises(exc) { } # FAIL
402     return true
403     rescue Errno::ETIMEDOUT => e
404       return true
405     rescue NotImplementedError => e
406       warn "E: #{e}"
407       return false
408   end