Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions lib/open4.rb
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ def Open4.description

class Error < ::StandardError; end

def pfork4(fun, &b)
Open4.do_popen(b, :block) do |ps_read, _|
def pfork4(fun, encoding = nil, &b)
Open4.do_popen(b, :block, false, encoding) do |ps_read, _|
ps_read.close
begin
fun.call
Expand All @@ -30,8 +30,8 @@ def pfork4(fun, &b)
end
module_function :pfork4

def popen4(*cmd, &b)
Open4.do_popen(b, :init) do |ps_read, ps_write|
def popen4(cmd, encoding = nil, &b)
Open4.do_popen(b, :init, false, encoding) do |ps_read, ps_write|
ps_read.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
ps_write.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
exec(*cmd)
Expand All @@ -42,8 +42,8 @@ def popen4(*cmd, &b)
module_function :popen4
module_function :open4

def popen4ext(closefds=false, *cmd, &b)
Open4.do_popen(b, :init, closefds) do |ps_read, ps_write|
def popen4ext(cmd, closefds = false, encoding = nil, &b)
Open4.do_popen(b, :init, closefds, encoding) do |ps_read, ps_write|
ps_read.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
ps_write.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
exec(*cmd)
Expand All @@ -52,8 +52,8 @@ def popen4ext(closefds=false, *cmd, &b)
end
module_function :popen4ext

def self.do_popen(b = nil, exception_propagation_at = nil, closefds=false, &cmd)
pw, pr, pe, ps = IO.pipe, IO.pipe, IO.pipe, IO.pipe
def self.do_popen(b = nil, exception_propagation_at = nil, closefds=false, encoding=nil, &cmd)
pw, pr, pe, ps = IO.pipe(encoding), IO.pipe(encoding), IO.pipe(encoding), IO.pipe(encoding)

verbose = $VERBOSE
begin
Expand Down Expand Up @@ -333,7 +333,7 @@ def spawn arg, *argv
begin
chdir(cwd) do
Timeout::timeout(timeout) do
popen4ext(closefds, *argv) do |c, i, o, e|
popen4ext(*argv, closefds) do |c, i, o, e|
started = true

%w( replace pid= << push update ).each do |msg|
Expand Down
24 changes: 24 additions & 0 deletions test/popen4_test.rb
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,30 @@ def test_io_pipes_without_block
assert_equal err_msg, err_actual
assert_equal 0, wait_status(cid)
end

def test_io_pipes_encoding
via_msg = "I'm so \xF0\x9F\x98\x82"
via_msg.force_encoding('ASCII-8BIT')
err_msg = "Errors make me \xF0\x9F\x98\xA0"
err_msg.force_encoding('ASCII-8BIT')

cmd = <<-END
ruby -e "
STDOUT.write STDIN.read
STDERR.write '#{err_msg}'
"
END
cid, stdin, stdout, stderr = popen4 cmd, "ASCII-8BIT"
stdin.write via_msg
stdin.close
out_actual = stdout.read
err_actual = stderr.read
assert_equal via_msg, out_actual
assert_equal err_msg, err_actual
assert_equal 0, wait_status(cid)
assert_equal out_actual.encoding, Encoding.find("ASCII-8BIT")
end


def test_io_pipes_with_block
via_msg = 'foo'
Expand Down
16 changes: 8 additions & 8 deletions test/popen4ext_test.rb
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -8,31 +8,31 @@ class POpen4Test < TestCase
UNKNOWN_CMD_ERRORS = [Errno::ENOENT, Errno::EINVAL]

def test_unknown_command_propagates_exception
err = assert_raises(*UNKNOWN_CMD_ERRORS) { popen4ext true, UNKNOWN_CMD }
err = assert_raises(*UNKNOWN_CMD_ERRORS) { popen4ext UNKNOWN_CMD, true }
assert_match(/#{UNKNOWN_CMD}/, err.to_s) if on_mri?
end

def test_exception_propagation_avoids_zombie_child_process
assert_raises(*UNKNOWN_CMD_ERRORS) { popen4ext true, UNKNOWN_CMD }
assert_raises(*UNKNOWN_CMD_ERRORS) { popen4ext UNKNOWN_CMD, true }
assert_empty Process.waitall
end

def test_exit_failure
code = 43
cid, _ = popen4ext true, %{ruby -e "exit #{43}"}
cid, _ = popen4ext %{ruby -e "exit #{43}"}, true
assert_equal code, wait_status(cid)
end

def test_exit_success
cid, _ = popen4ext true, %{ruby -e "exit"}
cid, _ = popen4ext %{ruby -e "exit"}, true
assert_equal 0, wait_status(cid)
end

def test_passes_child_pid_to_block
cmd = %{ruby -e "STDOUT.print Process.pid"}
cid_in_block = nil
cid_in_fun = nil
popen4ext(true, cmd) do |cid, _, stdout, _|
popen4ext(cmd, true) do |cid, _, stdout, _|
cid_in_block = cid
cid_in_fun = stdout.read.to_i
end
Expand All @@ -48,7 +48,7 @@ def test_io_pipes_without_block
STDERR.write '#{err_msg}'
"
END
cid, stdin, stdout, stderr = popen4ext true, cmd
cid, stdin, stdout, stderr = popen4ext cmd, true
stdin.write via_msg
stdin.close
out_actual = stdout.read
Expand All @@ -68,7 +68,7 @@ def test_io_pipes_with_block
STDERR.write '#{err_msg}'
"
END
status = popen4ext(true, cmd) do |_, stdin, stdout, stderr|
status = popen4ext(cmd, true) do |_, stdin, stdout, stderr|
stdin.write via_msg
stdin.close
out_actual = stdout.read
Expand All @@ -81,7 +81,7 @@ def test_io_pipes_with_block

def test_close_ignores_errors
TCPSocket.new('localhost', 59367).close rescue nil
cid, _ = popen4ext true, %{ruby -e "exit"}
cid, _ = popen4ext %{ruby -e "exit"}, true
assert_equal 0, wait_status(cid)
end
end
Expand Down