2 # -*- encoding: binary -*-
3 # Copyright (C) 2012-2020 all contributors <cmogstored-public@yhbt.net>
4 # License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt>
5 require 'test/test_helper'
8 class TestInherit < Test::Unit::TestCase
10 @tmpdir = Dir.mktmpdir('cmogstored-inherit-test')
13 @srv = TCPServer.new(@host, 0)
16 @err = Tempfile.new("stderr")
21 # Ruby 1.8 did not have the close_on_exec= method, but it
22 # also defaulted to close_on_exec=false (same as POSIX)
23 # 2.0.0 will be the first release with close_on_exec=true
24 # by default, but 1.9 already added the close_on_exec= method
25 def maybe_cloexec(io, val)
26 if io.respond_to?(:close_on_exec=)
27 io.close_on_exec = val
28 @exec_fds[io.fileno] = io
32 # FD inheritance is explicit in Ruby 2.0.0
34 cmd << @exec_fds if @exec_fds.size > 0
40 Process.kill(:QUIT, @pid) rescue nil
41 _, status = Process.waitpid2(@pid)
43 $stderr.write(@err.read)
44 assert status.success?, status.inspect
46 @to_close.each { |io| io.close unless io.closed? }
47 FileUtils.rm_rf(@tmpdir)
51 cmd = [ "cmogstored", "--docroot=#@tmpdir", "--httplisten=#@host:#@port",
55 maybe_cloexec(r, false)
56 maybe_cloexec(w, false)
58 ENV["CMOGSTORED_FD"] = "#{r.fileno}"
61 _, status = Process.waitpid2(pid)
62 assert ! status.success?, status.inspect
64 assert_match(/getsockname.*failed/, @err.read)
68 cmd = [ "cmogstored", "--docroot=#@tmpdir", "--httplisten=#@host:#@port",
70 vg = ENV["VALGRIND"] and cmd = vg.split(/\s+/).concat(cmd)
71 drop = TCPServer.new(@host, 0)
75 maybe_cloexec(drop, false)
76 maybe_cloexec(@srv, false)
77 ENV["CMOGSTORED_FD"] = "#{@srv.fileno},#{drop.fileno}"
81 # just ensure HTTP works after being inherited
82 Net::HTTP.start(@host, @port) do |http|
83 [ Net::HTTP::Get, Net::HTTP::Head ].each do |meth|
84 resp = http.request(meth.new("/"))
85 assert_kind_of Net::HTTPOK, resp
89 # ensure inherited FDs get closed if they're unused
90 drop_port = drop.addr[1]
92 # still works since drop is open in _this_ process
93 c = TCPSocket.new(@host, drop_port)
96 # drop is no longer valid after this:
98 assert_raises(Errno::ECONNREFUSED) { TCPSocket.new(@host, drop_port) }
101 def test_inherit_fake
102 cmd = [ "cmogstored", "--docroot=#@tmpdir", "--httplisten=#@host:#@port",
106 ENV["CMOGSTORED_FD"] = "666"
110 _, status = Process.waitpid2(pid)
111 assert ! status.success?, status.inspect
114 def test_inherit_bogus
115 cmd = [ "cmogstored", "--docroot=#@tmpdir", "--httplisten=#@host:#@port",
118 maybe_cloexec(@srv, false)
119 ENV["CMOGSTORED_FD"] = "#{@srv.fileno};"
123 _, status = Process.waitpid2(pid)
125 assert_match(/invalid byte: 59$/, @err.read)
126 assert ! status.success?, status.inspect
129 def test_inherit_high
130 cmd = [ "cmogstored", "--docroot=#@tmpdir", "--httplisten=#@host:#@port",
133 maybe_cloexec(@srv, false)
134 fd = 0xffffffffffffffffffffffffff.to_s
135 ENV["CMOGSTORED_FD"] = "#{@srv.fileno},#{fd}"
139 _, status = Process.waitpid2(pid)
140 assert ! status.success?, status.inspect
142 assert_match(/failed to parse/, @err.read)
145 def test_inherit_systemd
146 # disabled test on old Rubies: https://bugs.ruby-lang.org/issues/11336
147 # [ruby-core:69895] [Bug #11336] fixed by r51576
148 return unless RUBY_VERSION.to_f >= 2.3
150 mgmt = TCPServer.new(@host, 0)
153 cmd = %W(cmogstored --docroot=#@tmpdir --httplisten=#@host:#@port
154 --mgmtlisten=#@host:#{mport} --maxconns=100)
155 cmd << { 3 => mgmt.fileno, 4 => @srv.fileno }
157 ENV['LISTEN_PID'] = "#$$"
158 ENV['LISTEN_FDS'] = '2'
162 # just ensure HTTP works after being inherited
163 Net::HTTP.start(@host, @port) do |http|
164 [ Net::HTTP::Get, Net::HTTP::Head ].each do |meth|
165 resp = http.request(meth.new("/"))
166 assert_kind_of Net::HTTPOK, resp
170 # still works since drop is open in _this_ process
171 c = TCPSocket.new(@host, mport)
174 assert_match(/ERROR: unknown command/, c.gets)