Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactored Process.run #382

Closed
Closed
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
62 changes: 62 additions & 0 deletions spec/std/open3_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
require "spec"
require "open3"

def nullify_io(std)
original = std
File.open("/dev/null") do |null|
null.reopen(std)
yield
end
ensure
original.reopen(std) if original
end

describe "Open3" do
describe "capture2" do
it "captures STDOUT only" do
nullify_io(STDERR) do
output, status = Open3.capture2({"ls", ".", "*"}, env: { LANG: "C" })
output.should match(/src/)
output.should_not match(/No such file or directory/)
status.should_not eq(0)
end
end

it "collects child process status" do
output, status = Open3.capture2("ls")
output.should match(/spec/)
status.should eq(0)
end
end

describe("capture2e") do
it "merges and captures STDOUT and STDERR" do
output, status = Open3.capture2e({"ls", ".", "*"}, env: { LANG: "C" })
output.should match(/src/)
output.should match(/No such file or directory/)
status.should_not eq(0)
end

it "collects child process status" do
output, status = Open3.capture2e("ls")
output.should match(/spec/)
status.should eq(0)
end
end

describe "capture3" do
it "capture3 captures STDOUT and STDERR" do
output, error, status = Open3.capture3({"ls", ".", "*"}, env: { LANG: "C" })
output.should match(/spec/)
error.should match(/No such file or directory/)
status.should_not eq(0)
end

it "collects child process status" do
output, error, status = Open3.capture3("ls")
output.should match(/spec/)
error.should eq("")
status.should eq(0)
end
end
end
52 changes: 0 additions & 52 deletions spec/std/process/run_spec.cr

This file was deleted.

89 changes: 89 additions & 0 deletions spec/std/process/spawn_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
require "spec"

describe "Process" do
describe "spawn" do
it "executes a shell command and nullifies STDOUT" do
pid = Process.spawn("ls *", output: "/dev/null")
Process.waitpid(pid).should eq(0)
end

it "executes a command as tuple and nullifies STDERR" do
pid = Process.spawn({"ls", "*"}, error: "/dev/null")
Process.waitpid(pid).should_not eq(0)
end

it "executes a command as array" do
pid = Process.spawn(["ls", "/"], output: "/dev/null")
Process.waitpid(pid).should eq(0)
end

it "returns status 127 if command couldn't be executed" do
pid = Process.spawn("foobarbaz", error: false)
Process.waitpid(pid).should eq(127)
end

it "exports environment variables and redirects STDOUT" do
IO.pipe do |r, w|
pid = Process.spawn("echo $MY_ENV_VAR", env: { MY_ENV_VAR: "MY_ENV_VALUE" }, output: w)
w.close
r.gets_to_end.should eq("MY_ENV_VALUE\n")
Process.waitpid(pid).should eq(0)
end
end

it "writes String to STDIN" do
IO.pipe do |r, w|
pid = Process.spawn("cat -", input: "my input\nmessage\n", output: w)
w.close
r.gets_to_end.should eq("my input\nmessage\n")
Process.waitpid(pid).should eq(0)
end
end

it "pipes to STDIN" do
IO.pipe do |in_r, in_w|
IO.pipe do |out_r, out_w|
in_w.print("my input message\n")
in_w.close

pid = Process.spawn({"cat"}, input: in_r, output: out_w)
in_r.close
out_w.close

out_r.gets_to_end.should eq("my input message\n")
Process.waitpid(pid).should eq(0)
end
end
end

# FIXME: subject to raise conditions (fork may run setsid later)
it "changes sid of child process" do
pid = Process.spawn("sleep 0.02", pgroup: true)
sleep(0.01)
Process.getsid(pid).should_not eq(Process.getsid)
Process.waitpid(pid).should eq(0)
end
end

describe "system" do
it "executes a shell command" do
system("/bin/ls", output: false).should eq(true)
system("foobarbaz", error: false).should eq(false)
end

it "executes a command as tuple" do
system({"/bin/ls", "."}, output: false).should eq(true)
system({"/bin/ls", "*"}, error: false).should eq(false)
end

it "sends file to input and reads ouput" do
File.open(__FILE__, "r") do |file|
IO.pipe do |r, w|
system("/bin/cat", input: file, output: w).should eq(true)
w.close
r.gets_to_end.should eq(File.read(__FILE__))
end
end
end
end
end
23 changes: 23 additions & 0 deletions spec/std/process_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
require "spec"

describe "Process" do
describe "kill" do
it "terminates a child process" do
pid = Process.spawn("sleep 2")
Process.kill(pid).should eq(true)
Process.waitpid(pid).should eq(0)
end

it "kills a child process" do
pid = Process.spawn("sleep 2")
Process.kill(pid, Signal::KILL).should eq(true)
Process.waitpid(pid).should eq(0)
end

it "raises exception when process can't be killed" do
pid = Process.spawn({"/bin/ls"}, output: false)
Process.waitpid(pid)
expect_raises(Errno) { Process.kill(pid).should eq(false) }
end
end
end
2 changes: 1 addition & 1 deletion src/compiler/crystal/codegen/link.cr
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ module Crystal

if libname = attr.lib
if has_pkg_config.nil?
has_pkg_config = Process.run("which", {"pkg-config"}, output: false).success?
has_pkg_config = system({"which", "pkg-config"}, output: false)
end

if has_pkg_config && (libflags = pkg_config_flags(libname, attr.static?, library_path))
Expand Down
10 changes: 10 additions & 0 deletions src/io.cr
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,16 @@ module IO
{FileDescriptorIO.new(pipe_fds[0]), FileDescriptorIO.new(pipe_fds[1])}
end

def self.pipe
read, write = pipe
begin
yield read, write
ensure
read.close rescue nil
write.close rescue nil
end
end

def reopen(other)
if LibC.dup2(self.fd, other.fd) == -1
raise Errno.new("Could not reopen file descriptor")
Expand Down
110 changes: 110 additions & 0 deletions src/open3.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
require "process"

module Open3
def self.popen2(command, env = nil, pgroup = nil, umask = nil, &block)
in_r, in_w = input = IO.pipe
out_r, out_w = IO.pipe

#popen_run(command, {in_r, out_w}, {in_w, out_r}, env, pgroup, umask, &block)
popen_run(command, {in_r, out_w}, {in_w, out_r}, env, pgroup, umask) do |pid|
yield in_w, out_r, pid
end
end

def self.popen2e(command, env = nil, pgroup = nil, umask = nil, &block)
in_r, in_w = IO.pipe
out_r, out_w = IO.pipe

#popen_run(command, {in_r, out_w, out_w}, {in_w, out_r}, env, pgroup, umask, &block)
popen_run(command, {in_r, out_w, out_w}, {in_w, out_r}, env, pgroup, umask) do |pid|
yield in_w, out_r, pid
end
end

def self.popen3(command, env = nil, pgroup = nil, umask = nil, &block)
in_r, in_w = IO.pipe
out_r, out_w = IO.pipe
err_r, err_w = IO.pipe

#popen_run(command, {in_r, out_w, err_w}, {in_w, out_r, err_r}, env, pgroup, umask, &block)
popen_run(command, {in_r, out_w, err_w}, {in_w, out_r, err_r}, env, pgroup, umask) do |wait_thr|
yield in_w, out_r, err_r, wait_thr
end
end

def self.capture2(command, stdin_data = "", env = nil, pgroup = nil, umask = nil)
popen2(command, env, pgroup, umask) do |stdin, stdout, wait_thr|
output, _error, status = capture(stdin_data, stdin, stdout, nil, wait_thr)
{output.to_s, status}
end
end

def self.capture2e(command, stdin_data = "", env = nil, pgroup = nil, umask = nil)
popen2e(command, env, pgroup, umask) do |stdin, stdout_stderr, wait_thr|
output, _error, status = capture(stdin_data, stdin, stdout_stderr, nil, wait_thr)
{output.to_s, status}
end
end

def self.capture3(command, stdin_data = "", env = nil, pgroup = nil, umask = nil)
popen3(command, env, pgroup, umask) do |stdin, stdout, stderr, wait_thr|
output, error, status = capture(stdin_data, stdin, stdout, stderr, wait_thr)
{output.to_s, error.to_s, status}
end
end

private def self.popen_run(command, child_io, parent_io, env = nil, pgroup = nil, umask = nil)
pid = Process.spawn(command, env, *child_io, pgroup: pgroup, umask: umask)
wait_thr = Thread.new { Process.waitpid(pid) }
child_io.each { |io| io.close rescue nil }

begin
#yield(*parent_io, pid)
yield wait_thr
ensure
parent_io.each { |io| io.close rescue nil }
end
end

private def self.capture(stdin_data, stdin, stdout, stderr, wait_thr)
output, error = StringIO.new, StringIO.new
read = stderr ? {stdout, stderr} : {stdout}
write = {stdin}

buffer :: UInt8[2048]

loop do
ios = IO.select(read, write)

if stdin && ios.includes?(stdin)
stdin.print(stdin_data)
stdin.close
write = stdin = nil
end

if stdout && ios.includes?(stdout)
bytes = stdout.read(buffer.to_slice)
if bytes == 0
stdout.close
stdout = nil
read = stderr ? {stderr} : nil
end
output.write(buffer.to_slice, bytes)
end

if stderr && ios.includes?(stderr)
bytes = stderr.read(buffer.to_slice)
if bytes == 0
stderr.close
stderr = nil
read = stdout ? {stdout} : nil
end
error.write(buffer.to_slice, bytes)
end

break unless read || write
end

{output, error, wait_thr.join}
end
end
Loading