diff options
Diffstat (limited to 'spec/ruby/core/process')
86 files changed, 2956 insertions, 0 deletions
diff --git a/spec/ruby/core/process/abort_spec.rb b/spec/ruby/core/process/abort_spec.rb new file mode 100644 index 0000000000..f7113c8b7e --- /dev/null +++ b/spec/ruby/core/process/abort_spec.rb @@ -0,0 +1,6 @@ +require File.expand_path('../../../spec_helper', __FILE__) +require File.expand_path('../../../shared/process/abort', __FILE__) + +describe "Process.abort" do + it_behaves_like :process_abort, :abort, Process +end diff --git a/spec/ruby/core/process/constants_spec.rb b/spec/ruby/core/process/constants_spec.rb new file mode 100644 index 0000000000..2fa93ad8bf --- /dev/null +++ b/spec/ruby/core/process/constants_spec.rb @@ -0,0 +1,63 @@ + +describe "Process::Constants" do + platform_is :darwin, :netbsd, :freebsd do + it "has the correct constant values on BSD-like systems" do + Process::WNOHANG.should == 1 + Process::WUNTRACED.should == 2 + Process::PRIO_PROCESS.should == 0 + Process::PRIO_PGRP.should == 1 + Process::PRIO_USER.should == 2 + Process::RLIM_INFINITY.should == 9223372036854775807 + Process::RLIMIT_CPU.should == 0 + Process::RLIMIT_FSIZE.should == 1 + Process::RLIMIT_DATA.should == 2 + Process::RLIMIT_STACK.should == 3 + Process::RLIMIT_CORE.should == 4 + Process::RLIMIT_RSS.should == 5 + Process::RLIMIT_MEMLOCK.should == 6 + Process::RLIMIT_NPROC.should == 7 + Process::RLIMIT_NOFILE.should == 8 + end + end + + platform_is :darwin do + it "has the correct constant values on Darwin" do + Process::RLIM_SAVED_MAX.should == 9223372036854775807 + Process::RLIM_SAVED_CUR.should == 9223372036854775807 + Process::RLIMIT_AS.should == 5 + end + end + + platform_is :linux do + it "has the correct constant values on Linux" do + Process::WNOHANG.should == 1 + Process::WUNTRACED.should == 2 + Process::PRIO_PROCESS.should == 0 + Process::PRIO_PGRP.should == 1 + Process::PRIO_USER.should == 2 + Process::RLIMIT_CPU.should == 0 + Process::RLIMIT_FSIZE.should == 1 + Process::RLIMIT_DATA.should == 2 + Process::RLIMIT_STACK.should == 3 + Process::RLIMIT_CORE.should == 4 + Process::RLIMIT_RSS.should == 5 + Process::RLIMIT_NPROC.should == 6 + Process::RLIMIT_NOFILE.should == 7 + Process::RLIMIT_MEMLOCK.should == 8 + Process::RLIMIT_AS.should == 9 + + # These values appear to change according to the platform. + values = [4294967295, 9223372036854775807, 18446744073709551615] + values.include?(Process::RLIM_INFINITY).should be_true + values.include?(Process::RLIM_SAVED_MAX).should be_true + values.include?(Process::RLIM_SAVED_CUR).should be_true + end + end + + platform_is :netbsd, :freebsd do + it "Process::RLIMIT_SBSIZE" do + Process::RLIMIT_SBSIZE.should == 9 # FIXME: what's it equal? + Process::RLIMIT_AS.should == 10 + end + end +end diff --git a/spec/ruby/core/process/daemon_spec.rb b/spec/ruby/core/process/daemon_spec.rb new file mode 100644 index 0000000000..f68ddfe253 --- /dev/null +++ b/spec/ruby/core/process/daemon_spec.rb @@ -0,0 +1,123 @@ +require File.expand_path('../../../spec_helper', __FILE__) +require File.expand_path('../fixtures/common', __FILE__) + +platform_is_not :windows do + describe :process_daemon_keep_stdio_open_false, shared: true do + it "redirects stdout to /dev/null" do + @daemon.invoke("keep_stdio_open_false_stdout", @object).should == "" + end + + it "redirects stderr to /dev/null" do + @daemon.invoke("keep_stdio_open_false_stderr", @object).should == "" + end + + it "redirects stdin to /dev/null" do + @daemon.invoke("keep_stdio_open_false_stdin", @object).should == "" + end + + it "does not close open files" do + @daemon.invoke("keep_stdio_open_files", @object).should == "false" + end + end + + describe :process_daemon_keep_stdio_open_true, shared: true do + it "does not redirect stdout to /dev/null" do + @daemon.invoke("keep_stdio_open_true_stdout", @object).should == "writing to stdout" + end + + it "does not redirect stderr to /dev/null" do + @daemon.invoke("keep_stdio_open_true_stderr", @object).should == "writing to stderr" + end + + it "does not redirect stdin to /dev/null" do + @daemon.invoke("keep_stdio_open_true_stdin", @object).should == "reading from stdin" + end + + it "does not close open files" do + @daemon.invoke("keep_stdio_open_files", @object).should == "false" + end + end + + describe "Process.daemon" do + before :each do + @invoke_dir = Dir.pwd + @daemon = ProcessSpecs::Daemonizer.new + end + + after :each do + rm_r @daemon.input, @daemon.data if @daemon + end + + it "returns 0" do + @daemon.invoke("return_value").should == "0" + end + + it "has a different PID after daemonizing" do + parent, daemon = @daemon.invoke("pid").split(":") + parent.should_not == daemon + end + + it "has a different process group after daemonizing" do + parent, daemon = @daemon.invoke("process_group").split(":") + parent.should_not == daemon + end + + it "does not run existing at_exit handlers when daemonizing" do + @daemon.invoke("daemonizing_at_exit").should == "not running at_exit" + end + + it "runs at_exit handlers when the daemon exits" do + @daemon.invoke("daemon_at_exit").should == "running at_exit" + end + + it "changes directory to the root directory if the first argument is not given" do + @daemon.invoke("stay_in_dir").should == "/" + end + + it "changes directory to the root directory if the first argument is false" do + @daemon.invoke("stay_in_dir", [false]).should == "/" + end + + it "changes directory to the root directory if the first argument is nil" do + @daemon.invoke("stay_in_dir", [nil]).should == "/" + end + + it "does not change to the root directory if the first argument is true" do + @daemon.invoke("stay_in_dir", [true]).should == @invoke_dir + end + + it "does not change to the root directory if the first argument is non-false" do + @daemon.invoke("stay_in_dir", [:yes]).should == @invoke_dir + end + + describe "when the second argument is not given" do + it_behaves_like :process_daemon_keep_stdio_open_false, nil, [false] + end + + describe "when the second argument is false" do + it_behaves_like :process_daemon_keep_stdio_open_false, nil, [false, false] + end + + describe "when the second argument is nil" do + it_behaves_like :process_daemon_keep_stdio_open_false, nil, [false, nil] + end + + describe "when the second argument is true" do + it_behaves_like :process_daemon_keep_stdio_open_true, nil, [false, true] + end + + describe "when the second argument is non-false" do + it_behaves_like :process_daemon_keep_stdio_open_true, nil, [false, :yes] + end + end +end + +platform_is :windows do + describe "Process.daemon" do + it "raises a NotImplementedError" do + lambda { + Process.daemon + }.should raise_error(NotImplementedError) + end + end +end diff --git a/spec/ruby/core/process/detach_spec.rb b/spec/ruby/core/process/detach_spec.rb new file mode 100644 index 0000000000..7a38e290ce --- /dev/null +++ b/spec/ruby/core/process/detach_spec.rb @@ -0,0 +1,46 @@ +require File.expand_path('../../../spec_helper', __FILE__) + +describe "Process.detach" do + platform_is_not :windows do + it "returns a thread" do + pid = Process.fork { Process.exit! } + thr = Process.detach(pid) + thr.should be_kind_of(Thread) + thr.join + end + + it "produces the exit Process::Status as the thread value" do + pid = Process.fork { Process.exit! } + thr = Process.detach(pid) + thr.join + + status = thr.value + status.should be_kind_of(Process::Status) + status.pid.should == pid + end + + platform_is_not :openbsd do + it "reaps the child process's status automatically" do + pid = Process.fork { Process.exit! } + Process.detach(pid).join + lambda { Process.waitpid(pid) }.should raise_error(Errno::ECHILD) + end + end + + it "sets the :pid thread-local to the PID" do + pid = Process.fork { Process.exit! } + thr = Process.detach(pid) + thr.join + + thr[:pid].should == pid + end + + it "provides a #pid method on the returned thread which returns the PID" do + pid = Process.fork { Process.exit! } + thr = Process.detach(pid) + thr.join + + thr.pid.should == pid + end + end +end diff --git a/spec/ruby/core/process/egid_spec.rb b/spec/ruby/core/process/egid_spec.rb new file mode 100644 index 0000000000..316343944c --- /dev/null +++ b/spec/ruby/core/process/egid_spec.rb @@ -0,0 +1,19 @@ +require File.expand_path('../../../spec_helper', __FILE__) + +describe "Process.egid" do + it "returns the effective group ID for this process" do + Process.egid.should be_kind_of(Integer) + end + + it "also goes by Process::GID.eid" do + Process::GID.eid.should == Process.egid + end + + it "also goes by Process::Sys.getegid" do + Process::Sys.getegid.should == Process.egid + end +end + +describe "Process.egid=" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/process/euid_spec.rb b/spec/ruby/core/process/euid_spec.rb new file mode 100644 index 0000000000..1855ef66f5 --- /dev/null +++ b/spec/ruby/core/process/euid_spec.rb @@ -0,0 +1,59 @@ +require File.expand_path('../../../spec_helper', __FILE__) + +describe "Process.euid" do + it "returns the effective user ID for this process" do + Process.euid.should be_kind_of(Fixnum) + end + + it "also goes by Process::UID.eid" do + Process::UID.eid.should == Process.euid + end + + it "also goes by Process::Sys.geteuid" do + Process::Sys.geteuid.should == Process.euid + end +end + +describe "Process.euid=" do + + platform_is_not :windows do + it "raises TypeError if not passed an Integer" do + lambda { Process.euid = Object.new }.should raise_error(TypeError) + end + + as_user do + it "raises Errno::ERPERM if run by a non superuser trying to set the superuser id" do + lambda { (Process.euid = 0)}.should raise_error(Errno::EPERM) + end + + it "raises Errno::ERPERM if run by a non superuser trying to set the superuser id from username" do + lambda { Process.euid = "root" }.should raise_error(Errno::EPERM) + end + end + + as_superuser do + describe "if run by a superuser" do + with_feature :fork do + it "sets the effective user id for the current process if run by a superuser" do + read, write = IO.pipe + pid = Process.fork do + begin + read.close + Process.euid = 1 + write << Process.euid + write.close + rescue Exception => e + write << e << e.backtrace + end + Process.exit! + end + write.close + euid = read.gets + euid.should == "1" + Process.wait pid + end + end + end + end + end +end diff --git a/spec/ruby/core/process/exec_spec.rb b/spec/ruby/core/process/exec_spec.rb new file mode 100644 index 0000000000..b4eddf526e --- /dev/null +++ b/spec/ruby/core/process/exec_spec.rb @@ -0,0 +1,218 @@ +require File.expand_path('../../../spec_helper', __FILE__) + +describe "Process.exec" do + it "raises Errno::ENOENT for an empty string" do + lambda { Process.exec "" }.should raise_error(Errno::ENOENT) + end + + it "raises Errno::ENOENT for a command which does not exist" do + lambda { Process.exec "bogus-noent-script.sh" }.should raise_error(Errno::ENOENT) + end + + it "raises an ArgumentError if the command includes a null byte" do + lambda { Process.exec "\000" }.should raise_error(ArgumentError) + end + + unless File.executable?(__FILE__) # Some FS (e.g. vboxfs) locate all files executable + platform_is_not :windows do + it "raises Errno::EACCES when the file does not have execute permissions" do + lambda { Process.exec __FILE__ }.should raise_error(Errno::EACCES) + end + end + + platform_is :windows do + it "raises Errno::EACCES or Errno::ENOEXEC when the file is not an executable file" do + lambda { Process.exec __FILE__ }.should raise_error(SystemCallError) { |e| + [Errno::EACCES, Errno::ENOEXEC].should include(e.class) + } + end + end + end + + platform_is_not :openbsd do + it "raises Errno::EACCES when passed a directory" do + lambda { Process.exec File.dirname(__FILE__) }.should raise_error(Errno::EACCES) + end + end + + platform_is :openbsd do + it "raises Errno::EISDIR when passed a directory" do + lambda { Process.exec File.dirname(__FILE__) }.should raise_error(Errno::EISDIR) + end + end + + it "runs the specified command, replacing current process" do + ruby_exe('Process.exec "echo hello"; puts "fail"', escape: true).should == "hello\n" + end + + it "sets the current directory when given the :chdir option" do + tmpdir = tmp("")[0..-2] + platform_is_not :windows do + ruby_exe("Process.exec(\"pwd\", chdir: #{tmpdir.inspect})", escape: true).should == "#{tmpdir}\n" + end + platform_is :windows do + ruby_exe("Process.exec(\"cd\", chdir: #{tmpdir.inspect})", escape: true).tr('\\', '/').should == "#{tmpdir}\n" + end + end + + it "flushes STDOUT upon exit when it's not set to sync" do + ruby_exe("STDOUT.sync = false; STDOUT.write 'hello'").should == "hello" + end + + it "flushes STDERR upon exit when it's not set to sync" do + ruby_exe("STDERR.sync = false; STDERR.write 'hello'", args: "2>&1").should == "hello" + end + + describe "with a single argument" do + before :each do + @dir = tmp("exec_with_dir", false) + Dir.mkdir @dir + + @name = "some_file" + @path = tmp("exec_with_dir/#{@name}", false) + touch @path + end + + after :each do + rm_r @path + rm_r @dir + end + + platform_is_not :windows do + it "subjects the specified command to shell expansion" do + result = Dir.chdir(@dir) do + ruby_exe('Process.exec "echo *"', escape: true) + end + result.chomp.should == @name + end + + it "creates an argument array with shell parsing semantics for whitespace" do + ruby_exe('Process.exec "echo a b c d"', escape: true).should == "a b c d\n" + end + end + + platform_is :windows do + # There is no shell expansion on Windows + it "does not subject the specified command to shell expansion on Windows" do + result = Dir.chdir(@dir) do + ruby_exe('Process.exec "echo *"', escape: true) + end + result.should == "*\n" + end + + it "does not create an argument array with shell parsing semantics for whitespace on Windows" do + ruby_exe('Process.exec "echo a b c d"', escape: true).should == "a b c d\n" + end + end + + end + + describe "with multiple arguments" do + it "does not subject the arguments to shell expansion" do + cmd = '"echo", "*"' + platform_is :windows do + cmd = '"cmd.exe", "/C", "echo", "*"' + end + ruby_exe("Process.exec #{cmd}", escape: true).should == "*\n" + end + end + + describe "(environment variables)" do + before :each do + ENV["FOO"] = "FOO" + end + + after :each do + ENV["FOO"] = nil + end + + var = '$FOO' + platform_is :windows do + var = '%FOO%' + end + + it "sets environment variables in the child environment" do + ruby_exe('Process.exec({"FOO" => "BAR"}, "echo ' + var + '")', escape: true).should == "BAR\n" + end + + it "unsets environment variables whose value is nil" do + platform_is_not :windows do + ruby_exe('Process.exec({"FOO" => nil}, "echo ' + var + '")', escape: true).should == "\n" + end + platform_is :windows do + # On Windows, echo-ing a non-existent env var is treated as echo-ing any other string of text + ruby_exe('Process.exec({"FOO" => nil}, "echo ' + var + '")', escape: true).should == var + "\n" + end + end + + it "coerces environment argument using to_hash" do + ruby_exe('o = Object.new; def o.to_hash; {"FOO" => "BAR"}; end; Process.exec(o, "echo ' + var + '")', escape: true).should == "BAR\n" + end + + it "unsets other environment variables when given a true :unsetenv_others option" do + platform_is_not :windows do + ruby_exe('Process.exec("echo ' + var + '", unsetenv_others: true)', escape: true).should == "\n" + end + platform_is :windows do + ruby_exe('Process.exec("' + ENV['COMSPEC'].gsub('\\', '\\\\\\') + ' /C echo ' + var + '", unsetenv_others: true)', escape: true).should == var + "\n" + end + end + end + + describe "with a command array" do + it "uses the first element as the command name and the second as the argv[0] value" do + platform_is_not :windows do + ruby_exe('Process.exec(["/bin/sh", "argv_zero"], "-c", "echo $0")', escape: true).should == "argv_zero\n" + end + platform_is :windows do + ruby_exe('Process.exec(["cmd.exe", "/C"], "/C", "echo", "argv_zero")', escape: true).should == "argv_zero\n" + end + end + + it "coerces the argument using to_ary" do + platform_is_not :windows do + ruby_exe('o = Object.new; def o.to_ary; ["/bin/sh", "argv_zero"]; end; Process.exec(o, "-c", "echo $0")', escape: true).should == "argv_zero\n" + end + platform_is :windows do + ruby_exe('o = Object.new; def o.to_ary; ["cmd.exe", "/C"]; end; Process.exec(o, "/C", "echo", "argv_zero")', escape: true).should == "argv_zero\n" + end + end + + it "raises an ArgumentError if the Array does not have exactly two elements" do + lambda { Process.exec([]) }.should raise_error(ArgumentError) + lambda { Process.exec([:a]) }.should raise_error(ArgumentError) + lambda { Process.exec([:a, :b, :c]) }.should raise_error(ArgumentError) + end + end + + platform_is_not :windows do + describe "with an options Hash" do + describe "with Integer option keys" do + before :each do + @name = tmp("exec_fd_map.txt") + @child_fd_file = tmp("child_fd_file.txt") + end + + after :each do + rm_r @name, @child_fd_file + end + + it "maps the key to a file descriptor in the child that inherits the file descriptor from the parent specified by the value" do + map_fd_fixture = fixture __FILE__, "map_fd.rb" + cmd = <<-EOC + f = File.open("#{@name}", "w+") + child_fd = f.fileno + 1 + File.open("#{@child_fd_file}", "w") { |io| io.print child_fd } + Process.exec "#{ruby_cmd(map_fd_fixture)} \#{child_fd}", { child_fd => f } + EOC + + ruby_exe(cmd, escape: true) + child_fd = IO.read(@child_fd_file).to_i + child_fd.to_i.should > STDERR.fileno + + File.read(@name).should == "writing to fd: #{child_fd}" + end + end + end + end +end diff --git a/spec/ruby/core/process/exit_spec.rb b/spec/ruby/core/process/exit_spec.rb new file mode 100644 index 0000000000..7cbd0c363e --- /dev/null +++ b/spec/ruby/core/process/exit_spec.rb @@ -0,0 +1,10 @@ +require File.expand_path('../../../spec_helper', __FILE__) +require File.expand_path('../../../shared/process/exit', __FILE__) + +describe "Process.exit" do + it_behaves_like :process_exit, :exit, Process +end + +describe "Process.exit!" do + it_behaves_like :process_exit!, :exit!, Process +end diff --git a/spec/ruby/core/process/fixtures/common.rb b/spec/ruby/core/process/fixtures/common.rb new file mode 100644 index 0000000000..bdbf1e654b --- /dev/null +++ b/spec/ruby/core/process/fixtures/common.rb @@ -0,0 +1,84 @@ +module ProcessSpecs + def self.use_system_ruby(context) + if defined?(MSpecScript::SYSTEM_RUBY) + context.send(:before, :all) do + @ruby = ::RUBY_EXE + Object.const_set(:RUBY_EXE, MSpecScript::SYSTEM_RUBY) + end + + context.send(:after, :all) do + Object.const_set(:RUBY_EXE, @ruby) + end + end + end + + class Daemonizer + attr_reader :input, :data + + def initialize + # Fast feedback for implementations without Process.daemon + raise NotImplementedError, "Process.daemon is not implemented" unless Process.respond_to? :daemon + + @script = fixture __FILE__, "daemon.rb" + @input = tmp("process_daemon_input_file") + @data = tmp("process_daemon_data_file") + @args = [] + end + + def wait_for_daemon + sleep 0.001 until File.exist?(@data) and File.size?(@data) + end + + def invoke(behavior, arguments=[]) + args = Marshal.dump(arguments).unpack("H*") + args << @input << @data << behavior + + ruby_exe @script, args: args + + wait_for_daemon + + return unless File.exist? @data + + File.open(@data, "rb") { |f| return f.read.chomp } + end + end + + class Signalizer + attr_reader :pid_file, :pid + + def initialize(scenario=nil) + platform_is :windows do + fail "not supported on windows" + end + @script = fixture __FILE__, "kill.rb" + @pid = nil + @pid_file = tmp("process_kill_signal_file") + rm_r @pid_file + + @thread = Thread.new do + Thread.current.abort_on_exception = true + args = [@pid_file] + args << scenario if scenario + @result = ruby_exe @script, args: args + end + Thread.pass while @thread.status and !File.exist?(@pid_file) + while @thread.status && (@pid.nil? || @pid == 0) + @pid = IO.read(@pid_file).chomp.to_i + end + end + + def wait_on_result + @thread.join + end + + def cleanup + wait_on_result + rm_r pid_file + end + + def result + wait_on_result + @result.chomp if @result + end + end +end diff --git a/spec/ruby/core/process/fixtures/daemon.rb b/spec/ruby/core/process/fixtures/daemon.rb new file mode 100644 index 0000000000..772df2d09e --- /dev/null +++ b/spec/ruby/core/process/fixtures/daemon.rb @@ -0,0 +1,111 @@ +module ProcessSpecs + class Daemon + def initialize(argv) + args, @input, @data, @behavior = argv + @args = Marshal.load [args].pack("H*") + @no_at_exit = false + end + + def run + send @behavior + + # Exit without running any at_exit handlers + exit!(0) if @no_at_exit + end + + def write(data) + File.open(@data, "wb") { |f| f.puts data } + end + + def daemonizing_at_exit + at_exit do + write "running at_exit" + end + + @no_at_exit = true + Process.daemon + write "not running at_exit" + end + + def return_value + write Process.daemon.to_s + end + + def pid + parent = Process.pid + Process.daemon + daemon = Process.pid + write "#{parent}:#{daemon}" + end + + def process_group + parent = Process.getpgrp + Process.daemon + daemon = Process.getpgrp + write "#{parent}:#{daemon}" + end + + def daemon_at_exit + at_exit do + write "running at_exit" + end + + Process.daemon + end + + def stay_in_dir + Process.daemon(*@args) + write Dir.pwd + end + + def keep_stdio_open_false_stdout + Process.daemon(*@args) + $stdout.write "writing to stdout" + write "" + end + + def keep_stdio_open_false_stderr + Process.daemon(*@args) + $stderr.write "writing to stderr" + write "" + end + + def keep_stdio_open_false_stdin + Process.daemon(*@args) + + # Reading from /dev/null will return right away. If STDIN were not + # /dev/null, reading would block and the spec would hang. This is not a + # perfect way to spec the behavior but it works. + write $stdin.read + end + + def keep_stdio_open_true_stdout + $stdout.reopen @data + Process.daemon(*@args) + $stdout.write "writing to stdout" + end + + def keep_stdio_open_true_stderr + $stderr.reopen @data + Process.daemon(*@args) + $stderr.write "writing to stderr" + end + + def keep_stdio_open_true_stdin + File.open(@input, "w") { |f| f.puts "reading from stdin" } + + $stdin.reopen @input, "r" + Process.daemon(*@args) + write $stdin.read + end + + def keep_stdio_open_files + file = File.open @input, "w" + + Process.daemon(*@args) + write file.closed? + end + end +end + +ProcessSpecs::Daemon.new(ARGV).run diff --git a/spec/ruby/core/process/fixtures/kill.rb b/spec/ruby/core/process/fixtures/kill.rb new file mode 100644 index 0000000000..0b88f8ee1f --- /dev/null +++ b/spec/ruby/core/process/fixtures/kill.rb @@ -0,0 +1,45 @@ +require 'thread' + +pid_file = ARGV.shift +scenario = ARGV.shift + +# We must do this first otherwise there will be a race with the process that +# creates this process and the TERM signal below could go to that process +# instead, which will likely abort the specs process. +Process.setsid if scenario + +mutex = Mutex.new + +Signal.trap(:TERM) do + if mutex.try_lock + STDOUT.puts "signaled" + STDOUT.flush + $signaled = true + end +end + +File.open(pid_file, "wb") { |f| f.puts Process.pid } + +if scenario + # We are sending a signal to the process group + process = "Process.getpgrp" + + case scenario + when "self" + signal = %["SIGTERM"] + process = "0" + when "group_numeric" + signal = %[-Signal.list["TERM"]] + when "group_short_string" + signal = %["-TERM"] + when "group_full_string" + signal = %["-SIGTERM"] + else + raise "unknown scenario: #{scenario.inspect}" + end + + code = "Process.kill(#{signal}, #{process})" + system(ENV["RUBY_EXE"], *ENV["RUBY_FLAGS"].split(' '), "-e", code) +end + +sleep 0.001 until mutex.locked? and $signaled diff --git a/spec/ruby/core/process/fixtures/map_fd.rb b/spec/ruby/core/process/fixtures/map_fd.rb new file mode 100644 index 0000000000..fc542625b0 --- /dev/null +++ b/spec/ruby/core/process/fixtures/map_fd.rb @@ -0,0 +1,8 @@ +fd = ARGV.shift.to_i + +f = File.for_fd fd +begin + f.write "writing to fd: #{fd}" +ensure + f.close +end diff --git a/spec/ruby/core/process/fixtures/setpriority.rb b/spec/ruby/core/process/fixtures/setpriority.rb new file mode 100644 index 0000000000..cf22d85b12 --- /dev/null +++ b/spec/ruby/core/process/fixtures/setpriority.rb @@ -0,0 +1,12 @@ +case ARGV[0] +when "process" + which = Process::PRIO_PROCESS +when "group" + Process.setpgrp + which = Process::PRIO_PGRP +end + +priority = Process.getpriority(which, 0) +p priority +p Process.setpriority(which, 0, priority + 1) +p Process.getpriority(which, 0) diff --git a/spec/ruby/core/process/fork_spec.rb b/spec/ruby/core/process/fork_spec.rb new file mode 100644 index 0000000000..2de2231dc5 --- /dev/null +++ b/spec/ruby/core/process/fork_spec.rb @@ -0,0 +1,6 @@ +require File.expand_path('../../../spec_helper', __FILE__) +require File.expand_path('../../../shared/process/fork', __FILE__) + +describe "Process.fork" do + it_behaves_like :process_fork, :fork, Process +end diff --git a/spec/ruby/core/process/getpgid_spec.rb b/spec/ruby/core/process/getpgid_spec.rb new file mode 100644 index 0000000000..b92fb15152 --- /dev/null +++ b/spec/ruby/core/process/getpgid_spec.rb @@ -0,0 +1,17 @@ +require File.expand_path('../../../spec_helper', __FILE__) + +describe "Process.getpgid" do + platform_is_not :windows do + it "coerces the argument to an Integer" do + Process.getpgid(mock_int(Process.pid)).should == Process.getpgrp + end + + it "returns the process group ID for the given process id" do + Process.getpgid(Process.pid).should == Process.getpgrp + end + + it "returns the process group ID for the calling process id when passed 0" do + Process.getpgid(0).should == Process.getpgrp + end + end +end diff --git a/spec/ruby/core/process/getpgrp_spec.rb b/spec/ruby/core/process/getpgrp_spec.rb new file mode 100644 index 0000000000..9643a16b2a --- /dev/null +++ b/spec/ruby/core/process/getpgrp_spec.rb @@ -0,0 +1,7 @@ +require File.expand_path('../../../spec_helper', __FILE__) + +# see setpgrp_spec.rb + +describe "Process.getpgrp" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/process/getpriority_spec.rb b/spec/ruby/core/process/getpriority_spec.rb new file mode 100644 index 0000000000..68307b9bd5 --- /dev/null +++ b/spec/ruby/core/process/getpriority_spec.rb @@ -0,0 +1,23 @@ +require File.expand_path('../../../spec_helper', __FILE__) + +describe "Process.getpriority" do + platform_is_not :windows do + + it "coerces arguments to Integers" do + ret = Process.getpriority mock_int(Process::PRIO_PROCESS), mock_int(0) + ret.should be_kind_of(Fixnum) + end + + it "gets the scheduling priority for a specified process" do + Process.getpriority(Process::PRIO_PROCESS, 0).should be_kind_of(Fixnum) + end + + it "gets the scheduling priority for a specified process group" do + Process.getpriority(Process::PRIO_PGRP, 0).should be_kind_of(Fixnum) + end + + it "gets the scheduling priority for a specified user" do + Process.getpriority(Process::PRIO_USER, 0).should be_kind_of(Fixnum) + end + end +end diff --git a/spec/ruby/core/process/getrlimit_spec.rb b/spec/ruby/core/process/getrlimit_spec.rb new file mode 100644 index 0000000000..7924d43081 --- /dev/null +++ b/spec/ruby/core/process/getrlimit_spec.rb @@ -0,0 +1,91 @@ +require File.expand_path('../../../spec_helper', __FILE__) + +platform_is :aix do + # In AIX, if getrlimit(2) is called multiple times with RLIMIT_DATA, + # the first call and the subequent calls return slightly different + # values of rlim_cur, even if the process does nothing between + # the calls. This behavior causes some of the tests in this spec + # to fail, so call Process.getrlimit(:DATA) once and discard the result. + # Subsequent calls to Process.getrlimit(:DATA) should return + # a consistent value of rlim_cur. + Process.getrlimit(:DATA) +end + +platform_is_not :windows do + describe "Process.getrlimit" do + it "returns a two-element Array of Integers" do + result = Process.getrlimit Process::RLIMIT_CORE + result.size.should == 2 + result.first.should be_kind_of(Integer) + result.last.should be_kind_of(Integer) + end + + context "when passed an Object" do + before do + @resource = Process::RLIMIT_CORE + end + + it "calls #to_int to convert to an Integer" do + obj = mock("process getrlimit integer") + obj.should_receive(:to_int).and_return(@resource) + + Process.getrlimit(obj).should == Process.getrlimit(@resource) + end + + it "raises a TypeError if #to_int does not return an Integer" do + obj = mock("process getrlimit integer") + obj.should_receive(:to_int).and_return(nil) + + lambda { Process.getrlimit(obj) }.should raise_error(TypeError) + end + end + + context "when passed a Symbol" do + Process.constants.grep(/\ARLIMIT_/) do |fullname| + short = $' + it "coerces :#{short} into #{fullname}" do + Process.getrlimit(short.to_sym).should == Process.getrlimit(Process.const_get(fullname)) + end + end + + it "raises ArgumentError when passed an unknown resource" do + lambda { Process.getrlimit(:FOO) }.should raise_error(ArgumentError) + end + end + + context "when passed a String" do + Process.constants.grep(/\ARLIMIT_/) do |fullname| + short = $' + it "coerces '#{short}' into #{fullname}" do + Process.getrlimit(short).should == Process.getrlimit(Process.const_get(fullname)) + end + end + + it "raises ArgumentError when passed an unknown resource" do + lambda { Process.getrlimit("FOO") }.should raise_error(ArgumentError) + end + end + + context "when passed on Object" do + before do + @resource = Process::RLIMIT_CORE + end + + it "calls #to_str to convert to a String" do + obj = mock("process getrlimit string") + obj.should_receive(:to_str).and_return("CORE") + obj.should_not_receive(:to_int) + + Process.getrlimit(obj).should == Process.getrlimit(@resource) + end + + it "calls #to_int if #to_str does not return a String" do + obj = mock("process getrlimit string") + obj.should_receive(:to_str).and_return(nil) + obj.should_receive(:to_int).and_return(@resource) + + Process.getrlimit(obj).should == Process.getrlimit(@resource) + end + end + end +end diff --git a/spec/ruby/core/process/gid/change_privilege_spec.rb b/spec/ruby/core/process/gid/change_privilege_spec.rb new file mode 100644 index 0000000000..9670e4970f --- /dev/null +++ b/spec/ruby/core/process/gid/change_privilege_spec.rb @@ -0,0 +1,5 @@ +require File.expand_path('../../../../spec_helper', __FILE__) + +describe "Process::GID.change_privilege" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/process/gid/eid_spec.rb b/spec/ruby/core/process/gid/eid_spec.rb new file mode 100644 index 0000000000..6d6a672fea --- /dev/null +++ b/spec/ruby/core/process/gid/eid_spec.rb @@ -0,0 +1,9 @@ +require File.expand_path('../../../../spec_helper', __FILE__) + +describe "Process::GID.eid" do + it "needs to be reviewed for spec completeness" +end + +describe "Process::GID.eid=" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/process/gid/grant_privilege_spec.rb b/spec/ruby/core/process/gid/grant_privilege_spec.rb new file mode 100644 index 0000000000..27373c7113 --- /dev/null +++ b/spec/ruby/core/process/gid/grant_privilege_spec.rb @@ -0,0 +1,5 @@ +require File.expand_path('../../../../spec_helper', __FILE__) + +describe "Process::GID.grant_privilege" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/process/gid/re_exchange_spec.rb b/spec/ruby/core/process/gid/re_exchange_spec.rb new file mode 100644 index 0000000000..4f3a242680 --- /dev/null +++ b/spec/ruby/core/process/gid/re_exchange_spec.rb @@ -0,0 +1,5 @@ +require File.expand_path('../../../../spec_helper', __FILE__) + +describe "Process::GID.re_exchange" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/process/gid/re_exchangeable_spec.rb b/spec/ruby/core/process/gid/re_exchangeable_spec.rb new file mode 100644 index 0000000000..1fc5298f26 --- /dev/null +++ b/spec/ruby/core/process/gid/re_exchangeable_spec.rb @@ -0,0 +1,5 @@ +require File.expand_path('../../../../spec_helper', __FILE__) + +describe "Process::GID.re_exchangeable?" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/process/gid/rid_spec.rb b/spec/ruby/core/process/gid/rid_spec.rb new file mode 100644 index 0000000000..fb5c51e412 --- /dev/null +++ b/spec/ruby/core/process/gid/rid_spec.rb @@ -0,0 +1,5 @@ +require File.expand_path('../../../../spec_helper', __FILE__) + +describe "Process::GID.rid" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/process/gid/sid_available_spec.rb b/spec/ruby/core/process/gid/sid_available_spec.rb new file mode 100644 index 0000000000..59b2c3ae1e --- /dev/null +++ b/spec/ruby/core/process/gid/sid_available_spec.rb @@ -0,0 +1,5 @@ +require File.expand_path('../../../../spec_helper', __FILE__) + +describe "Process::GID.sid_available?" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/process/gid/switch_spec.rb b/spec/ruby/core/process/gid/switch_spec.rb new file mode 100644 index 0000000000..7ae6dae9a7 --- /dev/null +++ b/spec/ruby/core/process/gid/switch_spec.rb @@ -0,0 +1,5 @@ +require File.expand_path('../../../../spec_helper', __FILE__) + +describe "Process::GID.switch" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/process/gid_spec.rb b/spec/ruby/core/process/gid_spec.rb new file mode 100644 index 0000000000..c39c60a95f --- /dev/null +++ b/spec/ruby/core/process/gid_spec.rb @@ -0,0 +1,22 @@ +require File.expand_path('../../../spec_helper', __FILE__) + +describe "Process.gid" do + platform_is_not :windows do + it "returns the correct gid for the user executing this process" do + current_gid_according_to_unix = `id -gr`.to_i + Process.gid.should == current_gid_according_to_unix + end + end + + it "also goes by Process::GID.rid" do + Process::GID.rid.should == Process.gid + end + + it "also goes by Process::Sys.getgid" do + Process::Sys.getgid.should == Process.gid + end +end + +describe "Process.gid=" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/process/groups_spec.rb b/spec/ruby/core/process/groups_spec.rb new file mode 100644 index 0000000000..0669258bf3 --- /dev/null +++ b/spec/ruby/core/process/groups_spec.rb @@ -0,0 +1,54 @@ +require File.expand_path('../../../spec_helper', __FILE__) + +describe "Process.groups" do + platform_is_not :windows do + it "gets an Array of the gids of groups in the supplemental group access list" do + groups = `id -G`.scan(/\d+/).map { |i| i.to_i } + gid = Process.gid + + expected = (groups.sort - [gid]).sort + actual = (Process.groups - [gid]).sort + actual.should == expected + end + + # NOTE: This is kind of sketchy. + it "sets the list of gids of groups in the supplemental group access list" do + groups = Process.groups + if Process.uid == 0 + Process.groups = [] + Process.groups.should == [] + Process.groups = groups + Process.groups.sort.should == groups.sort + else + platform_is :aix do + # setgroups() is not part of the POSIX standard, + # so its behavior varies from OS to OS. AIX allows a non-root + # process to set the supplementary group IDs, as long as + # they are presently in its supplementary group IDs. + # The order of the following tests matters. + # After this process executes "Process.groups = []" + # it should no longer be able to set any supplementary + # group IDs, even if it originally belonged to them. + # It should only be able to set its primary group ID. + Process.groups = groups + Process.groups.sort.should == groups.sort + Process.groups = [] + Process.groups.should == [] + Process.groups = [ Process.gid ] + Process.groups.should == [ Process.gid ] + supplementary = groups - [ Process.gid ] + if supplementary.length > 0 + lambda { Process.groups = supplementary }.should raise_error(Errno::EPERM) + end + end + platform_is_not :aix do + lambda { Process.groups = [] }.should raise_error(Errno::EPERM) + end + end + end + end +end + +describe "Process.groups=" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/process/initgroups_spec.rb b/spec/ruby/core/process/initgroups_spec.rb new file mode 100644 index 0000000000..084c52652c --- /dev/null +++ b/spec/ruby/core/process/initgroups_spec.rb @@ -0,0 +1,20 @@ +require File.expand_path('../../../spec_helper', __FILE__) + +describe "Process.initgroups" do + platform_is_not :windows do + it "initializes the supplemental group access list" do + name = `id -un`.strip + groups = Process.groups + gid = groups.max.to_i + 1 + augmented_groups = `id -G`.scan(/\d+/).map {|i| i.to_i} << gid + if Process.uid == 0 + Process.groups = [] + Process.initgroups(name, gid).sort.should == augmented_groups.sort + Process.groups.sort.should == augmented_groups.sort + Process.groups = groups + else + lambda { Process.initgroups(name, gid) }.should raise_error(Errno::EPERM) + end + end + end +end diff --git a/spec/ruby/core/process/kill_spec.rb b/spec/ruby/core/process/kill_spec.rb new file mode 100644 index 0000000000..4316daf374 --- /dev/null +++ b/spec/ruby/core/process/kill_spec.rb @@ -0,0 +1,128 @@ +require File.expand_path('../../../spec_helper', __FILE__) +require File.expand_path('../fixtures/common', __FILE__) + +describe "Process.kill" do + ProcessSpecs.use_system_ruby(self) + + before :each do + @pid = Process.pid + end + + it "raises an ArgumentError for unknown signals" do + lambda { Process.kill("FOO", @pid) }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError if passed a lowercase signal name" do + lambda { Process.kill("term", @pid) }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError if signal is not a Fixnum or String" do + signal = mock("process kill signal") + signal.should_not_receive(:to_int) + + lambda { Process.kill(signal, @pid) }.should raise_error(ArgumentError) + end + + it "raises Errno::ESRCH if the process does not exist" do + pid = Process.spawn(*ruby_exe, "-e", "sleep 10") + Process.kill("SIGKILL", pid) + Process.wait(pid) + lambda { + Process.kill("SIGKILL", pid) + }.should raise_error(Errno::ESRCH) + end +end + +platform_is_not :windows do + describe "Process.kill" do + ProcessSpecs.use_system_ruby(self) + + before :each do + @sp = ProcessSpecs::Signalizer.new + end + + after :each do + @sp.cleanup + end + + it "accepts a Symbol as a signal name" do + Process.kill(:SIGTERM, @sp.pid) + @sp.result.should == "signaled" + end + + it "accepts a String as signal name" do + Process.kill("SIGTERM", @sp.pid) + @sp.result.should == "signaled" + end + + it "accepts a signal name without the 'SIG' prefix" do + Process.kill("TERM", @sp.pid) + @sp.result.should == "signaled" + end + + it "accepts a signal name with the 'SIG' prefix" do + Process.kill("SIGTERM", @sp.pid) + @sp.result.should == "signaled" + end + + it "acceps an Integer as a signal value" do + Process.kill(15, @sp.pid) + @sp.result.should == "signaled" + end + + it "calls #to_int to coerce the pid to an Integer" do + Process.kill("SIGTERM", mock_int(@sp.pid)) + @sp.result.should == "signaled" + end + end + + describe "Process.kill" do + ProcessSpecs.use_system_ruby(self) + + before :each do + @sp1 = ProcessSpecs::Signalizer.new + @sp2 = ProcessSpecs::Signalizer.new + end + + after :each do + @sp1.cleanup + @sp2.cleanup + end + + it "signals multiple processes" do + Process.kill("SIGTERM", @sp1.pid, @sp2.pid) + @sp1.result.should == "signaled" + @sp2.result.should == "signaled" + end + + it "returns the number of processes signaled" do + Process.kill("SIGTERM", @sp1.pid, @sp2.pid).should == 2 + end + end + + describe "Process.kill" do + after :each do + @sp.cleanup if @sp + end + + it "signals the process group if the PID is zero" do + @sp = ProcessSpecs::Signalizer.new "self" + @sp.result.should == "signaled" + end + + it "signals the process group if the signal number is negative" do + @sp = ProcessSpecs::Signalizer.new "group_numeric" + @sp.result.should == "signaled" + end + + it "signals the process group if the short signal name starts with a minus sign" do + @sp = ProcessSpecs::Signalizer.new "group_short_string" + @sp.result.should == "signaled" + end + + it "signals the process group if the full signal name starts with a minus sign" do + @sp = ProcessSpecs::Signalizer.new "group_full_string" + @sp.result.should == "signaled" + end + end +end diff --git a/spec/ruby/core/process/maxgroups_spec.rb b/spec/ruby/core/process/maxgroups_spec.rb new file mode 100644 index 0000000000..8a40a1aba6 --- /dev/null +++ b/spec/ruby/core/process/maxgroups_spec.rb @@ -0,0 +1,19 @@ +require File.expand_path('../../../spec_helper', __FILE__) + +platform_is_not :windows do + describe "Process.maxgroups" do + it "returns the maximum number of gids allowed in the supplemental group access list" do + Process.maxgroups.should be_kind_of(Fixnum) + end + + it "sets the maximum number of gids allowed in the supplemental group access list" do + n = Process.maxgroups + begin + Process.maxgroups = n - 1 + Process.maxgroups.should == n - 1 + ensure + Process.maxgroups = n + end + end + end +end diff --git a/spec/ruby/core/process/pid_spec.rb b/spec/ruby/core/process/pid_spec.rb new file mode 100644 index 0000000000..114b20f11f --- /dev/null +++ b/spec/ruby/core/process/pid_spec.rb @@ -0,0 +1,9 @@ +require File.expand_path('../../../spec_helper', __FILE__) + +describe "Process.pid" do + it "returns the process id of this process" do + pid = Process.pid + pid.should be_kind_of(Fixnum) + Process.pid.should == pid + end +end diff --git a/spec/ruby/core/process/ppid_spec.rb b/spec/ruby/core/process/ppid_spec.rb new file mode 100644 index 0000000000..ec6ce865ee --- /dev/null +++ b/spec/ruby/core/process/ppid_spec.rb @@ -0,0 +1,23 @@ +require File.expand_path('../../../spec_helper', __FILE__) + +describe "Process.ppid" do + with_feature :fork do + it "returns the process id of the parent of this process" do + + read, write = IO.pipe + + child_pid = Process.fork { + read.close + write << "#{Process.ppid}\n" + write.close + exit! + } + + write.close + pid = read.gets + read.close + Process.wait(child_pid) + pid.to_i.should == Process.pid + end + end +end diff --git a/spec/ruby/core/process/set_proctitle_spec.rb b/spec/ruby/core/process/set_proctitle_spec.rb new file mode 100644 index 0000000000..e004f8efc9 --- /dev/null +++ b/spec/ruby/core/process/set_proctitle_spec.rb @@ -0,0 +1,23 @@ +require File.expand_path('../../../spec_helper', __FILE__) + +# Note that there's no way to get the current process title defined as a spec +# somewhere. Process.setproctitle explicitly does not change `$0` so the only +# way to get the process title is to shell out. +describe 'Process.setproctitle' do + platform_is :linux, :darwin do + before :each do + @old_title = $0 + end + + after :each do + Process.setproctitle(@old_title) + end + + it 'should set the process title' do + title = 'rubyspec-proctitle-test' + + Process.setproctitle(title).should == title + `ps -ocommand= -p#{$$}`.should include(title) + end + end +end diff --git a/spec/ruby/core/process/setpgid_spec.rb b/spec/ruby/core/process/setpgid_spec.rb new file mode 100644 index 0000000000..bd1596d0eb --- /dev/null +++ b/spec/ruby/core/process/setpgid_spec.rb @@ -0,0 +1,28 @@ +require File.expand_path('../../../spec_helper', __FILE__) + +describe "Process.setpgid" do + with_feature :fork do + it "sets the process group id of the specified process" do + rd, wr = IO.pipe + + pid = Process.fork do + wr.close + rd.read + rd.close + Process.exit! + end + + rd.close + + begin + Process.getpgid(pid).should == Process.getpgrp + Process.setpgid(mock_int(pid), mock_int(pid)).should == 0 + Process.getpgid(pid).should == pid + ensure + wr.write ' ' + wr.close + Process.wait pid + end + end + end +end diff --git a/spec/ruby/core/process/setpgrp_spec.rb b/spec/ruby/core/process/setpgrp_spec.rb new file mode 100644 index 0000000000..8e72795f96 --- /dev/null +++ b/spec/ruby/core/process/setpgrp_spec.rb @@ -0,0 +1,37 @@ +require File.expand_path('../../../spec_helper', __FILE__) + +# TODO: put these in the right files. +describe "Process.setpgrp and Process.getpgrp" do + platform_is_not :windows do + it "sets and gets the process group ID of the calling process" do + # there are two synchronization points here: + # One for the child to let the parent know that it has finished + # setting its process group; + # and another for the parent to let the child know that it's ok to die. + read1, write1 = IO.pipe + read2, write2 = IO.pipe + pid = Process.fork do + read1.close + write2.close + Process.setpgrp + write1 << Process.getpgrp + write1.close + read2.read(1) + read2.close + Process.exit! + end + write1.close + read2.close + pgid = read1.read # wait for child to change process groups + read1.close + + begin + Process.getpgid(pid).should == pgid.to_i + ensure + write2 << "!" + write2.close + Process.wait pid + end + end + end +end diff --git a/spec/ruby/core/process/setpriority_spec.rb b/spec/ruby/core/process/setpriority_spec.rb new file mode 100644 index 0000000000..7b437a547a --- /dev/null +++ b/spec/ruby/core/process/setpriority_spec.rb @@ -0,0 +1,41 @@ +require File.expand_path('../../../spec_helper', __FILE__) + +describe "Process.setpriority" do + platform_is_not :windows do + it "sets the scheduling priority for a specified process" do + priority = Process.getpriority(Process::PRIO_PROCESS, 0) + + out = ruby_exe(fixture(__FILE__, "setpriority.rb"), args: "process") + out = out.lines.map { |l| Integer(l) } + pr = out[0] + out.should == [pr, 0, pr+1] + + Process.getpriority(Process::PRIO_PROCESS, 0).should == priority + end + + # Darwin and FreeBSD don't seem to handle these at all, getting all out of + # whack with either permission errors or just the wrong value + platform_is_not :darwin, :freebsd do + it "sets the scheduling priority for a specified process group" do + priority = Process.getpriority(Process::PRIO_PGRP, 0) + + out = ruby_exe(fixture(__FILE__, "setpriority.rb"), args: "group") + out = out.lines.map { |l| Integer(l) } + pr = out[0] + out.should == [pr, 0, pr+1] + + Process.getpriority(Process::PRIO_PGRP, 0).should == priority + end + end + + as_superuser do + it "sets the scheduling priority for a specified user" do + p = Process.getpriority(Process::PRIO_USER, 0) + Process.setpriority(Process::PRIO_USER, 0, p + 1).should == 0 + Process.getpriority(Process::PRIO_USER, 0).should == (p + 1) + Process.setpriority(Process::PRIO_USER, 0, p).should == 0 + end + end + end + +end diff --git a/spec/ruby/core/process/setrlimit_spec.rb b/spec/ruby/core/process/setrlimit_spec.rb new file mode 100644 index 0000000000..5d137ca6c1 --- /dev/null +++ b/spec/ruby/core/process/setrlimit_spec.rb @@ -0,0 +1,232 @@ +require File.expand_path('../../../spec_helper', __FILE__) + +platform_is_not :windows do + describe "Process.setrlimit" do + context "when passed an Object" do + before do + @resource = Process::RLIMIT_CORE + @limit, @max = Process.getrlimit @resource + end + + it "calls #to_int to convert resource to an Integer" do + Process.setrlimit(mock_int(@resource), @limit, @max).should be_nil + end + + it "raises a TypeError if #to_int for resource does not return an Integer" do + obj = mock("process getrlimit integer") + obj.should_receive(:to_int).and_return(nil) + + lambda { Process.setrlimit(obj, @limit, @max) }.should raise_error(TypeError) + end + + it "calls #to_int to convert the soft limit to an Integer" do + Process.setrlimit(@resource, mock_int(@limit), @max).should be_nil + end + + it "raises a TypeError if #to_int for resource does not return an Integer" do + obj = mock("process getrlimit integer") + obj.should_receive(:to_int).and_return(nil) + + lambda { Process.setrlimit(@resource, obj, @max) }.should raise_error(TypeError) + end + + it "calls #to_int to convert the hard limit to an Integer" do + Process.setrlimit(@resource, @limit, mock_int(@max)).should be_nil + end + + it "raises a TypeError if #to_int for resource does not return an Integer" do + obj = mock("process getrlimit integer") + obj.should_receive(:to_int).and_return(nil) + + lambda { Process.setrlimit(@resource, @limit, obj) }.should raise_error(TypeError) + end + end + + context "when passed a Symbol" do + platform_is_not :openbsd do + it "coerces :AS into RLIMIT_AS" do + Process.setrlimit(:AS, *Process.getrlimit(Process::RLIMIT_AS)).should be_nil + end + end + + it "coerces :CORE into RLIMIT_CORE" do + Process.setrlimit(:CORE, *Process.getrlimit(Process::RLIMIT_CORE)).should be_nil + end + + it "coerces :CPU into RLIMIT_CPU" do + Process.setrlimit(:CPU, *Process.getrlimit(Process::RLIMIT_CPU)).should be_nil + end + + it "coerces :DATA into RLIMIT_DATA" do + Process.setrlimit(:DATA, *Process.getrlimit(Process::RLIMIT_DATA)).should be_nil + end + + it "coerces :FSIZE into RLIMIT_FSIZE" do + Process.setrlimit(:FSIZE, *Process.getrlimit(Process::RLIMIT_FSIZE)).should be_nil + end + + it "coerces :NOFILE into RLIMIT_NOFILE" do + Process.setrlimit(:NOFILE, *Process.getrlimit(Process::RLIMIT_NOFILE)).should be_nil + end + + it "coerces :STACK into RLIMIT_STACK" do + Process.setrlimit(:STACK, *Process.getrlimit(Process::RLIMIT_STACK)).should be_nil + end + + platform_is_not :solaris, :aix do + it "coerces :MEMLOCK into RLIMIT_MEMLOCK" do + Process.setrlimit(:MEMLOCK, *Process.getrlimit(Process::RLIMIT_MEMLOCK)).should be_nil + end + end + + platform_is_not :solaris do + it "coerces :NPROC into RLIMIT_NPROC" do + Process.setrlimit(:NPROC, *Process.getrlimit(Process::RLIMIT_NPROC)).should be_nil + end + + it "coerces :RSS into RLIMIT_RSS" do + Process.setrlimit(:RSS, *Process.getrlimit(Process::RLIMIT_RSS)).should be_nil + end + end + + platform_is :netbsd, :freebsd do + it "coerces :SBSIZE into RLIMIT_SBSIZE" do + Process.setrlimit(:SBSIZE, *Process.getrlimit(Process::RLIMIT_SBSIZE)).should be_nil + end + end + + platform_is :linux do + it "coerces :RTPRIO into RLIMIT_RTPRIO" do + Process.setrlimit(:RTPRIO, *Process.getrlimit(Process::RLIMIT_RTPRIO)).should be_nil + end + + if defined?(Process::RLIMIT_RTTIME) + it "coerces :RTTIME into RLIMIT_RTTIME" do + Process.setrlimit(:RTTIME, *Process.getrlimit(Process::RLIMIT_RTTIME)).should be_nil + end + end + + it "coerces :SIGPENDING into RLIMIT_SIGPENDING" do + Process.setrlimit(:SIGPENDING, *Process.getrlimit(Process::RLIMIT_SIGPENDING)).should be_nil + end + + it "coerces :MSGQUEUE into RLIMIT_MSGQUEUE" do + Process.setrlimit(:MSGQUEUE, *Process.getrlimit(Process::RLIMIT_MSGQUEUE)).should be_nil + end + + it "coerces :NICE into RLIMIT_NICE" do + Process.setrlimit(:NICE, *Process.getrlimit(Process::RLIMIT_NICE)).should be_nil + end + end + + it "raises ArgumentError when passed an unknown resource" do + lambda { Process.setrlimit(:FOO, 1, 1) }.should raise_error(ArgumentError) + end + end + + context "when passed a String" do + platform_is_not :openbsd do + it "coerces 'AS' into RLIMIT_AS" do + Process.setrlimit("AS", *Process.getrlimit(Process::RLIMIT_AS)).should be_nil + end + end + + it "coerces 'CORE' into RLIMIT_CORE" do + Process.setrlimit("CORE", *Process.getrlimit(Process::RLIMIT_CORE)).should be_nil + end + + it "coerces 'CPU' into RLIMIT_CPU" do + Process.setrlimit("CPU", *Process.getrlimit(Process::RLIMIT_CPU)).should be_nil + end + + it "coerces 'DATA' into RLIMIT_DATA" do + Process.setrlimit("DATA", *Process.getrlimit(Process::RLIMIT_DATA)).should be_nil + end + + it "coerces 'FSIZE' into RLIMIT_FSIZE" do + Process.setrlimit("FSIZE", *Process.getrlimit(Process::RLIMIT_FSIZE)).should be_nil + end + + it "coerces 'NOFILE' into RLIMIT_NOFILE" do + Process.setrlimit("NOFILE", *Process.getrlimit(Process::RLIMIT_NOFILE)).should be_nil + end + + it "coerces 'STACK' into RLIMIT_STACK" do + Process.setrlimit("STACK", *Process.getrlimit(Process::RLIMIT_STACK)).should be_nil + end + + platform_is_not :solaris, :aix do + it "coerces 'MEMLOCK' into RLIMIT_MEMLOCK" do + Process.setrlimit("MEMLOCK", *Process.getrlimit(Process::RLIMIT_MEMLOCK)).should be_nil + end + end + + platform_is_not :solaris do + it "coerces 'NPROC' into RLIMIT_NPROC" do + Process.setrlimit("NPROC", *Process.getrlimit(Process::RLIMIT_NPROC)).should be_nil + end + + it "coerces 'RSS' into RLIMIT_RSS" do + Process.setrlimit("RSS", *Process.getrlimit(Process::RLIMIT_RSS)).should be_nil + end + end + + platform_is :netbsd, :freebsd do + it "coerces 'SBSIZE' into RLIMIT_SBSIZE" do + Process.setrlimit("SBSIZE", *Process.getrlimit(Process::RLIMIT_SBSIZE)).should be_nil + end + end + + platform_is :linux do + it "coerces 'RTPRIO' into RLIMIT_RTPRIO" do + Process.setrlimit("RTPRIO", *Process.getrlimit(Process::RLIMIT_RTPRIO)).should be_nil + end + + if defined?(Process::RLIMIT_RTTIME) + it "coerces 'RTTIME' into RLIMIT_RTTIME" do + Process.setrlimit("RTTIME", *Process.getrlimit(Process::RLIMIT_RTTIME)).should be_nil + end + end + + it "coerces 'SIGPENDING' into RLIMIT_SIGPENDING" do + Process.setrlimit("SIGPENDING", *Process.getrlimit(Process::RLIMIT_SIGPENDING)).should be_nil + end + + it "coerces 'MSGQUEUE' into RLIMIT_MSGQUEUE" do + Process.setrlimit("MSGQUEUE", *Process.getrlimit(Process::RLIMIT_MSGQUEUE)).should be_nil + end + + it "coerces 'NICE' into RLIMIT_NICE" do + Process.setrlimit("NICE", *Process.getrlimit(Process::RLIMIT_NICE)).should be_nil + end + end + + it "raises ArgumentError when passed an unknown resource" do + lambda { Process.setrlimit("FOO", 1, 1) }.should raise_error(ArgumentError) + end + end + + context "when passed on Object" do + before do + @resource = Process::RLIMIT_CORE + @limit, @max = Process.getrlimit @resource + end + + it "calls #to_str to convert to a String" do + obj = mock("process getrlimit string") + obj.should_receive(:to_str).and_return("CORE") + obj.should_not_receive(:to_int) + + Process.setrlimit(obj, @limit, @max).should be_nil + end + + it "calls #to_int if #to_str does not return a String" do + obj = mock("process getrlimit string") + obj.should_receive(:to_str).and_return(nil) + obj.should_receive(:to_int).and_return(@resource) + + Process.setrlimit(obj, @limit, @max).should be_nil + end + end + end +end diff --git a/spec/ruby/core/process/setsid_spec.rb b/spec/ruby/core/process/setsid_spec.rb new file mode 100644 index 0000000000..abb75f8225 --- /dev/null +++ b/spec/ruby/core/process/setsid_spec.rb @@ -0,0 +1,37 @@ +require File.expand_path('../../../spec_helper', __FILE__) + +describe "Process.setsid" do + with_feature :fork do + it "establishes this process as a new session and process group leader" do + read, write = IO.pipe + read2, write2 = IO.pipe + pid = Process.fork { + begin + read.close + write2.close + pgid = Process.setsid + write << pgid + write.close + read2.gets + rescue Exception => e + write << e << e.backtrace + end + Process.exit! + } + write.close + read2.close + pgid_child = Integer(read.gets) + read.close + platform_is_not :aix do + # AIX does not allow Process.getsid(pid) + # if pid is in a different session. + pgid = Process.getsid(pid) + pgid_child.should == pgid + end + write2.close + Process.wait pid + + pgid_child.should_not == Process.getsid + end + end +end diff --git a/spec/ruby/core/process/spawn_spec.rb b/spec/ruby/core/process/spawn_spec.rb new file mode 100644 index 0000000000..330ec8fcd8 --- /dev/null +++ b/spec/ruby/core/process/spawn_spec.rb @@ -0,0 +1,634 @@ +require File.expand_path('../../../spec_helper', __FILE__) +require File.expand_path('../fixtures/common', __FILE__) + +newline = "\n" +platform_is :windows do + newline = "\r\n" +end + +describe :process_spawn_does_not_close_std_streams, shared: true do + platform_is_not :windows do + it "does not close STDIN" do + code = "STDOUT.puts STDIN.read(0).inspect" + cmd = "Process.wait Process.spawn(#{ruby_cmd(code).inspect}, #{@options.inspect})" + ruby_exe(cmd, args: "> #{@output}") + File.binread(@output).should == %[""#{newline}] + end + + it "does not close STDOUT" do + code = "STDOUT.puts 'hello'" + cmd = "Process.wait Process.spawn(#{ruby_cmd(code).inspect}, #{@options.inspect})" + ruby_exe(cmd, args: "> #{@output}") + File.binread(@output).should == "hello#{newline}" + end + + it "does not close STDERR" do + code = "STDERR.puts 'hello'" + cmd = "Process.wait Process.spawn(#{ruby_cmd(code).inspect}, #{@options.inspect})" + ruby_exe(cmd, args: "2> #{@output}") + File.binread(@output).should == "hello#{newline}" + end + end +end + +describe "Process.spawn" do + ProcessSpecs.use_system_ruby(self) + + before :each do + @name = tmp("process_spawn.txt") + @var = "$FOO" + platform_is :windows do + @var = "%FOO%" + end + end + + after :each do + rm_r @name + end + + it "executes the given command" do + lambda { Process.wait Process.spawn("echo spawn") }.should output_to_fd("spawn\n") + end + + it "returns the process ID of the new process as a Fixnum" do + pid = Process.spawn(*ruby_exe, "-e", "exit") + Process.wait pid + pid.should be_an_instance_of(Fixnum) + end + + it "returns immediately" do + start = Time.now + pid = Process.spawn(*ruby_exe, "-e", "sleep 10") + (Time.now - start).should < 5 + Process.kill :KILL, pid + Process.wait pid + end + + # argv processing + + describe "with a single argument" do + platform_is_not :windows do + it "subjects the specified command to shell expansion" do + lambda { Process.wait Process.spawn("echo *") }.should_not output_to_fd("*\n") + end + + it "creates an argument array with shell parsing semantics for whitespace" do + lambda { Process.wait Process.spawn("echo a b c d") }.should output_to_fd("a b c d\n") + end + end + + platform_is :windows do + # There is no shell expansion on Windows + it "does not subject the specified command to shell expansion on Windows" do + lambda { Process.wait Process.spawn("echo *") }.should output_to_fd("*\n") + end + + it "does not create an argument array with shell parsing semantics for whitespace on Windows" do + lambda { Process.wait Process.spawn("echo a b c d") }.should output_to_fd("a b c d\n") + end + end + + it "calls #to_str to convert the argument to a String" do + o = mock("to_str") + o.should_receive(:to_str).and_return("echo foo") + lambda { Process.wait Process.spawn(o) }.should output_to_fd("foo\n") + end + + it "raises an ArgumentError if the command includes a null byte" do + lambda { Process.spawn "\000" }.should raise_error(ArgumentError) + end + + it "raises a TypeError if the argument does not respond to #to_str" do + lambda { Process.spawn :echo }.should raise_error(TypeError) + end + end + + describe "with multiple arguments" do + it "does not subject the arguments to shell expansion" do + lambda { Process.wait Process.spawn("echo", "*") }.should output_to_fd("*\n") + end + + it "preserves whitespace in passed arguments" do + out = "a b c d\n" + platform_is :windows do + # The echo command on Windows takes quotes literally + out = "\"a b c d\"\n" + end + lambda { Process.wait Process.spawn("echo", "a b c d") }.should output_to_fd(out) + end + + it "calls #to_str to convert the arguments to Strings" do + o = mock("to_str") + o.should_receive(:to_str).and_return("foo") + lambda { Process.wait Process.spawn("echo", o) }.should output_to_fd("foo\n") + end + + it "raises an ArgumentError if an argument includes a null byte" do + lambda { Process.spawn "echo", "\000" }.should raise_error(ArgumentError) + end + + it "raises a TypeError if an argument does not respond to #to_str" do + lambda { Process.spawn "echo", :foo }.should raise_error(TypeError) + end + end + + describe "with a command array" do + it "uses the first element as the command name and the second as the argv[0] value" do + platform_is_not :windows do + lambda { Process.wait Process.spawn(["/bin/sh", "argv_zero"], "-c", "echo $0") }.should output_to_fd("argv_zero\n") + end + platform_is :windows do + lambda { Process.wait Process.spawn(["cmd.exe", "/C"], "/C", "echo", "argv_zero") }.should output_to_fd("argv_zero\n") + end + end + + it "does not subject the arguments to shell expansion" do + lambda { Process.wait Process.spawn(["echo", "echo"], "*") }.should output_to_fd("*\n") + end + + it "preserves whitespace in passed arguments" do + out = "a b c d\n" + platform_is :windows do + # The echo command on Windows takes quotes literally + out = "\"a b c d\"\n" + end + lambda { Process.wait Process.spawn(["echo", "echo"], "a b c d") }.should output_to_fd(out) + end + + it "calls #to_ary to convert the argument to an Array" do + o = mock("to_ary") + platform_is_not :windows do + o.should_receive(:to_ary).and_return(["/bin/sh", "argv_zero"]) + lambda { Process.wait Process.spawn(o, "-c", "echo $0") }.should output_to_fd("argv_zero\n") + end + platform_is :windows do + o.should_receive(:to_ary).and_return(["cmd.exe", "/C"]) + lambda { Process.wait Process.spawn(o, "/C", "echo", "argv_zero") }.should output_to_fd("argv_zero\n") + end + end + + it "calls #to_str to convert the first element to a String" do + o = mock("to_str") + o.should_receive(:to_str).and_return("echo") + lambda { Process.wait Process.spawn([o, "echo"], "foo") }.should output_to_fd("foo\n") + end + + it "calls #to_str to convert the second element to a String" do + o = mock("to_str") + o.should_receive(:to_str).and_return("echo") + lambda { Process.wait Process.spawn(["echo", o], "foo") }.should output_to_fd("foo\n") + end + + it "raises an ArgumentError if the Array does not have exactly two elements" do + lambda { Process.spawn([]) }.should raise_error(ArgumentError) + lambda { Process.spawn([:a]) }.should raise_error(ArgumentError) + lambda { Process.spawn([:a, :b, :c]) }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError if the Strings in the Array include a null byte" do + lambda { Process.spawn ["\000", "echo"] }.should raise_error(ArgumentError) + lambda { Process.spawn ["echo", "\000"] }.should raise_error(ArgumentError) + end + + it "raises a TypeError if an element in the Array does not respond to #to_str" do + lambda { Process.spawn ["echo", :echo] }.should raise_error(TypeError) + lambda { Process.spawn [:echo, "echo"] }.should raise_error(TypeError) + end + end + + # env handling + + after :each do + ENV.delete("FOO") + end + + it "sets environment variables in the child environment" do + Process.wait Process.spawn({"FOO" => "BAR"}, "echo #{@var}>#{@name}") + File.read(@name).should == "BAR\n" + end + + it "unsets environment variables whose value is nil" do + ENV["FOO"] = "BAR" + Process.wait Process.spawn({"FOO" => nil}, "echo #{@var}>#{@name}") + expected = "\n" + platform_is :windows do + # Windows does not expand the variable if it is unset + expected = "#{@var}\n" + end + File.read(@name).should == expected + end + + it "calls #to_hash to convert the environment" do + o = mock("to_hash") + o.should_receive(:to_hash).and_return({"FOO" => "BAR"}) + Process.wait Process.spawn(o, "echo #{@var}>#{@name}") + File.read(@name).should == "BAR\n" + end + + it "calls #to_str to convert the environment keys" do + o = mock("to_str") + o.should_receive(:to_str).and_return("FOO") + Process.wait Process.spawn({o => "BAR"}, "echo #{@var}>#{@name}") + File.read(@name).should == "BAR\n" + end + + it "calls #to_str to convert the environment values" do + o = mock("to_str") + o.should_receive(:to_str).and_return("BAR") + Process.wait Process.spawn({"FOO" => o}, "echo #{@var}>#{@name}") + File.read(@name).should == "BAR\n" + end + + it "raises an ArgumentError if an environment key includes an equals sign" do + lambda do + Process.spawn({"FOO=" => "BAR"}, "echo #{@var}>#{@name}") + end.should raise_error(ArgumentError) + end + + it "raises an ArgumentError if an environment key includes a null byte" do + lambda do + Process.spawn({"\000" => "BAR"}, "echo #{@var}>#{@name}") + end.should raise_error(ArgumentError) + end + + it "raises an ArgumentError if an environment value includes a null byte" do + lambda do + Process.spawn({"FOO" => "\000"}, "echo #{@var}>#{@name}") + end.should raise_error(ArgumentError) + end + + # :unsetenv_others + + before :each do + @minimal_env = { + "PATH" => ENV["PATH"], + "HOME" => ENV["HOME"] + } + @common_env_spawn_args = [@minimal_env, "echo #{@var}>#{@name}"] + end + + platform_is_not :windows do + it "unsets other environment variables when given a true :unsetenv_others option" do + ENV["FOO"] = "BAR" + Process.wait Process.spawn(*@common_env_spawn_args, unsetenv_others: true) + $?.success?.should be_true + File.read(@name).should == "\n" + end + end + + it "does not unset other environment variables when given a false :unsetenv_others option" do + ENV["FOO"] = "BAR" + Process.wait Process.spawn(*@common_env_spawn_args, unsetenv_others: false) + $?.success?.should be_true + File.read(@name).should == "BAR\n" + end + + platform_is_not :windows do + it "does not unset environment variables included in the environment hash" do + env = @minimal_env.merge({"FOO" => "BAR"}) + Process.wait Process.spawn(env, "echo #{@var}>#{@name}", unsetenv_others: true) + $?.success?.should be_true + File.read(@name).should == "BAR\n" + end + end + + # :pgroup + + platform_is_not :windows do + it "joins the current process group by default" do + lambda do + Process.wait Process.spawn(ruby_cmd("print Process.getpgid(Process.pid)")) + end.should output_to_fd(Process.getpgid(Process.pid).to_s) + end + + it "joins the current process if pgroup: false" do + lambda do + Process.wait Process.spawn(ruby_cmd("print Process.getpgid(Process.pid)"), pgroup: false) + end.should output_to_fd(Process.getpgid(Process.pid).to_s) + end + + it "joins the current process if pgroup: nil" do + lambda do + Process.wait Process.spawn(ruby_cmd("print Process.getpgid(Process.pid)"), pgroup: nil) + end.should output_to_fd(Process.getpgid(Process.pid).to_s) + end + + it "joins a new process group if pgroup: true" do + process = lambda do + Process.wait Process.spawn(ruby_cmd("print Process.getpgid(Process.pid)"), pgroup: true) + end + + process.should_not output_to_fd(Process.getpgid(Process.pid).to_s) + process.should output_to_fd(/\d+/) + end + + it "joins a new process group if pgroup: 0" do + process = lambda do + Process.wait Process.spawn(ruby_cmd("print Process.getpgid(Process.pid)"), pgroup: 0) + end + + process.should_not output_to_fd(Process.getpgid(Process.pid).to_s) + process.should output_to_fd(/\d+/) + end + + it "joins the specified process group if pgroup: pgid" do + pgid = Process.getpgid(Process.pid) + lambda do + Process.wait Process.spawn(ruby_cmd("print Process.getpgid(Process.pid)"), pgroup: pgid) + end.should output_to_fd(pgid.to_s) + end + + it "raises an ArgumentError if given a negative :pgroup option" do + lambda { Process.spawn("echo", pgroup: -1) }.should raise_error(ArgumentError) + end + + it "raises a TypeError if given a symbol as :pgroup option" do + lambda { Process.spawn("echo", pgroup: :true) }.should raise_error(TypeError) + end + end + + platform_is :windows do + it "raises an ArgumentError if given :pgroup option" do + lambda { Process.spawn("echo", pgroup: false) }.should raise_error(ArgumentError) + end + end + + # :rlimit_core + # :rlimit_cpu + # :rlimit_data + + # :chdir + + it "uses the current working directory as its working directory" do + lambda do + Process.wait Process.spawn(ruby_cmd("print Dir.pwd")) + end.should output_to_fd(Dir.pwd) + end + + describe "when passed :chdir" do + before do + @dir = tmp("spawn_chdir", false) + Dir.mkdir @dir + end + + after do + rm_r @dir + end + + it "changes to the directory passed for :chdir" do + lambda do + Process.wait Process.spawn(ruby_cmd("print Dir.pwd"), chdir: @dir) + end.should output_to_fd(@dir) + end + + it "calls #to_path to convert the :chdir value" do + dir = mock("spawn_to_path") + dir.should_receive(:to_path).and_return(@dir) + + lambda do + Process.wait Process.spawn(ruby_cmd("print Dir.pwd"), chdir: dir) + end.should output_to_fd(@dir) + end + end + + # :umask + + it "uses the current umask by default" do + lambda do + Process.wait Process.spawn(ruby_cmd("print File.umask")) + end.should output_to_fd(File.umask.to_s) + end + + platform_is_not :windows do + it "sets the umask if given the :umask option" do + lambda do + Process.wait Process.spawn(ruby_cmd("print File.umask"), umask: 146) + end.should output_to_fd("146") + end + end + + # redirection + + it "redirects STDOUT to the given file descriptior if out: Fixnum" do + File.open(@name, 'w') do |file| + lambda do + Process.wait Process.spawn("echo glark", out: file.fileno) + end.should output_to_fd("glark\n", file) + end + end + + it "redirects STDOUT to the given file if out: IO" do + File.open(@name, 'w') do |file| + lambda do + Process.wait Process.spawn("echo glark", out: file) + end.should output_to_fd("glark\n", file) + end + end + + it "redirects STDOUT to the given file if out: String" do + Process.wait Process.spawn("echo glark", out: @name) + File.read(@name).should == "glark\n" + end + + it "redirects STDOUT to the given file if out: [String name, String mode]" do + Process.wait Process.spawn("echo glark", out: [@name, 'w']) + File.read(@name).should == "glark\n" + end + + it "redirects STDERR to the given file descriptior if err: Fixnum" do + File.open(@name, 'w') do |file| + lambda do + Process.wait Process.spawn("echo glark>&2", err: file.fileno) + end.should output_to_fd("glark\n", file) + end + end + + it "redirects STDERR to the given file descriptor if err: IO" do + File.open(@name, 'w') do |file| + lambda do + Process.wait Process.spawn("echo glark>&2", err: file) + end.should output_to_fd("glark\n", file) + end + end + + it "redirects STDERR to the given file if err: String" do + Process.wait Process.spawn("echo glark>&2", err: @name) + File.read(@name).should == "glark\n" + end + + it "redirects STDERR to child STDOUT if :err => [:child, :out]" do + File.open(@name, 'w') do |file| + lambda do + Process.wait Process.spawn("echo glark>&2", :out => file, :err => [:child, :out]) + end.should output_to_fd("glark\n", file) + end + end + + it "redirects both STDERR and STDOUT to the given file descriptior" do + File.open(@name, 'w') do |file| + lambda do + Process.wait Process.spawn(ruby_cmd("print(:glark); STDOUT.flush; STDERR.print(:bang)"), + [:out, :err] => file.fileno) + end.should output_to_fd("glarkbang", file) + end + end + + it "redirects both STDERR and STDOUT to the given IO" do + File.open(@name, 'w') do |file| + lambda do + Process.wait Process.spawn(ruby_cmd("print(:glark); STDOUT.flush; STDERR.print(:bang)"), + [:out, :err] => file) + end.should output_to_fd("glarkbang", file) + end + end + + it "redirects both STDERR and STDOUT at the time to the given name" do + touch @name + Process.wait Process.spawn(ruby_cmd("print(:glark); STDOUT.flush; STDERR.print(:bang)"), [:out, :err] => @name) + File.read(@name).should == "glarkbang" + end + + context "when passed close_others: true" do + before :each do + @output = tmp("spawn_close_others_true") + @options = { close_others: true } + end + + after :each do + rm_r @output + end + + it "closes file descriptors >= 3 in the child process" do + IO.pipe do |r, w| + begin + pid = Process.spawn(ruby_cmd("while File.exist? '#{@name}'; sleep 0.1; end"), @options) + w.close + lambda { r.read_nonblock(1) }.should raise_error(EOFError) + ensure + rm_r @name + Process.wait(pid) if pid + end + end + end + + it_should_behave_like :process_spawn_does_not_close_std_streams + end + + context "when passed close_others: false" do + before :each do + @output = tmp("spawn_close_others_false") + @options = { close_others: false } + end + + after :each do + rm_r @output + end + + it "closes file descriptors >= 3 in the child process because they are set close_on_exec by default" do + IO.pipe do |r, w| + begin + pid = Process.spawn(ruby_cmd("while File.exist? '#{@name}'; sleep 0.1; end"), @options) + w.close + lambda { r.read_nonblock(1) }.should raise_error(EOFError) + ensure + rm_r @name + Process.wait(pid) if pid + end + end + end + + platform_is_not :windows do + it "does not close file descriptors >= 3 in the child process if fds are set close_on_exec=false" do + IO.pipe do |r, w| + r.close_on_exec = false + w.close_on_exec = false + begin + pid = Process.spawn(ruby_cmd("while File.exist? '#{@name}'; sleep 0.1; end"), @options) + w.close + lambda { r.read_nonblock(1) }.should raise_error(Errno::EAGAIN) + ensure + rm_r @name + Process.wait(pid) if pid + end + end + end + end + + it_should_behave_like :process_spawn_does_not_close_std_streams + end + + # error handling + + it "raises an ArgumentError if passed no command arguments" do + lambda { Process.spawn }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError if passed env or options but no command arguments" do + lambda { Process.spawn({}) }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError if passed env and options but no command arguments" do + lambda { Process.spawn({}, {}) }.should raise_error(ArgumentError) + end + + it "raises an Errno::ENOENT for an empty string" do + lambda { Process.spawn "" }.should raise_error(Errno::ENOENT) + end + + it "raises an Errno::ENOENT if the command does not exist" do + lambda { Process.spawn "nonesuch" }.should raise_error(Errno::ENOENT) + end + + unless File.executable?(__FILE__) # Some FS (e.g. vboxfs) locate all files executable + platform_is_not :windows do + it "raises an Errno::EACCES when the file does not have execute permissions" do + lambda { Process.spawn __FILE__ }.should raise_error(Errno::EACCES) + end + end + + platform_is :windows do + it "raises Errno::EACCES or Errno::ENOEXEC when the file is not an executable file" do + lambda { Process.spawn __FILE__ }.should raise_error(SystemCallError) { |e| + [Errno::EACCES, Errno::ENOEXEC].should include(e.class) + } + end + end + end + + it "raises an Errno::EACCES when passed a directory" do + lambda { Process.spawn File.dirname(__FILE__) }.should raise_error(Errno::EACCES) + end + + it "raises an ArgumentError when passed a string key in options" do + lambda { Process.spawn("echo", "chdir" => Dir.pwd) }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError when passed an unknown option key" do + lambda { Process.spawn("echo", nonesuch: :foo) }.should raise_error(ArgumentError) + end + + platform_is_not :windows do + describe "with Integer option keys" do + before :each do + @name = tmp("spawn_fd_map.txt") + @io = new_io @name, "w+" + @io.sync = true + end + + after :each do + @io.close unless @io.closed? + rm_r @name + end + + it "maps the key to a file descriptor in the child that inherits the file descriptor from the parent specified by the value" do + child_fd = @io.fileno + 1 + args = ruby_cmd(fixture(__FILE__, "map_fd.rb"), args: [child_fd.to_s]) + pid = Process.spawn(*args, { child_fd => @io }) + Process.waitpid pid + @io.rewind + + @io.read.should == "writing to fd: #{child_fd}" + end + end + end +end diff --git a/spec/ruby/core/process/status/bit_and_spec.rb b/spec/ruby/core/process/status/bit_and_spec.rb new file mode 100644 index 0000000000..963d2c6c26 --- /dev/null +++ b/spec/ruby/core/process/status/bit_and_spec.rb @@ -0,0 +1,5 @@ +require File.expand_path('../../../../spec_helper', __FILE__) + +describe "Process::Status#&" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/process/status/coredump_spec.rb b/spec/ruby/core/process/status/coredump_spec.rb new file mode 100644 index 0000000000..8988cff9e1 --- /dev/null +++ b/spec/ruby/core/process/status/coredump_spec.rb @@ -0,0 +1,5 @@ +require File.expand_path('../../../../spec_helper', __FILE__) + +describe "Process::Status#coredump?" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/process/status/equal_value_spec.rb b/spec/ruby/core/process/status/equal_value_spec.rb new file mode 100644 index 0000000000..1eaaf82273 --- /dev/null +++ b/spec/ruby/core/process/status/equal_value_spec.rb @@ -0,0 +1,5 @@ +require File.expand_path('../../../../spec_helper', __FILE__) + +describe "Process::Status#==" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/process/status/exited_spec.rb b/spec/ruby/core/process/status/exited_spec.rb new file mode 100644 index 0000000000..79863360c5 --- /dev/null +++ b/spec/ruby/core/process/status/exited_spec.rb @@ -0,0 +1,37 @@ +require File.expand_path('../../../../spec_helper', __FILE__) + +describe "Process::Status#exited?" do + + describe "for a child that exited normally" do + + before :each do + ruby_exe("exit(0)") + end + + it "returns true" do + $?.exited?.should be_true + end + end + + + describe "for a terminated child" do + + before :each do + ruby_exe("Process.kill(:KILL, $$); exit(42)") + end + + platform_is_not :windows do + it "returns false" do + $?.exited?.should be_false + end + end + + platform_is :windows do + it "always returns true" do + $?.exited?.should be_true + end + end + + end + +end diff --git a/spec/ruby/core/process/status/exitstatus_spec.rb b/spec/ruby/core/process/status/exitstatus_spec.rb new file mode 100644 index 0000000000..57baf77724 --- /dev/null +++ b/spec/ruby/core/process/status/exitstatus_spec.rb @@ -0,0 +1,13 @@ +require File.expand_path('../../../../spec_helper', __FILE__) + +describe "Process::Status#exitstatus" do + + before :each do + ruby_exe("exit(42)") + end + + it "returns the process exit code" do + $?.exitstatus.should == 42 + end + +end diff --git a/spec/ruby/core/process/status/inspect_spec.rb b/spec/ruby/core/process/status/inspect_spec.rb new file mode 100644 index 0000000000..f3e7d8c9ab --- /dev/null +++ b/spec/ruby/core/process/status/inspect_spec.rb @@ -0,0 +1,5 @@ +require File.expand_path('../../../../spec_helper', __FILE__) + +describe "Process::Status#inspect" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/process/status/pid_spec.rb b/spec/ruby/core/process/status/pid_spec.rb new file mode 100644 index 0000000000..3389c242ff --- /dev/null +++ b/spec/ruby/core/process/status/pid_spec.rb @@ -0,0 +1,15 @@ +require File.expand_path('../../../../spec_helper', __FILE__) + +platform_is_not :windows do + describe "Process::Status#pid" do + + before :each do + @pid = ruby_exe("print $$").to_i + end + + it "returns the pid of the process" do + $?.pid.should == @pid + end + + end +end diff --git a/spec/ruby/core/process/status/right_shift_spec.rb b/spec/ruby/core/process/status/right_shift_spec.rb new file mode 100644 index 0000000000..5786d4163c --- /dev/null +++ b/spec/ruby/core/process/status/right_shift_spec.rb @@ -0,0 +1,5 @@ +require File.expand_path('../../../../spec_helper', __FILE__) + +describe "Process::Status#>>" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/process/status/signaled_spec.rb b/spec/ruby/core/process/status/signaled_spec.rb new file mode 100644 index 0000000000..0f80a525c9 --- /dev/null +++ b/spec/ruby/core/process/status/signaled_spec.rb @@ -0,0 +1,35 @@ +require File.expand_path('../../../../spec_helper', __FILE__) + +describe "Process::Status#signaled?" do + + describe "for a cleanly exited child" do + + before :each do + ruby_exe("exit(0)") + end + + it "returns false" do + $?.signaled?.should be_false + end + end + + describe "for a terminated child" do + + before :each do + ruby_exe("Process.kill(:KILL, $$); exit(42)") + end + + platform_is_not :windows do + it "returns true" do + $?.signaled?.should be_true + end + end + + platform_is :windows do + it "always returns false" do + $?.signaled?.should be_false + end + end + + end +end diff --git a/spec/ruby/core/process/status/stopped_spec.rb b/spec/ruby/core/process/status/stopped_spec.rb new file mode 100644 index 0000000000..e984cefaf7 --- /dev/null +++ b/spec/ruby/core/process/status/stopped_spec.rb @@ -0,0 +1,5 @@ +require File.expand_path('../../../../spec_helper', __FILE__) + +describe "Process::Status#stopped?" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/process/status/stopsig_spec.rb b/spec/ruby/core/process/status/stopsig_spec.rb new file mode 100644 index 0000000000..95fc5b0e77 --- /dev/null +++ b/spec/ruby/core/process/status/stopsig_spec.rb @@ -0,0 +1,5 @@ +require File.expand_path('../../../../spec_helper', __FILE__) + +describe "Process::Status#stopsig" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/process/status/success_spec.rb b/spec/ruby/core/process/status/success_spec.rb new file mode 100644 index 0000000000..e589d3f819 --- /dev/null +++ b/spec/ruby/core/process/status/success_spec.rb @@ -0,0 +1,51 @@ +require File.expand_path('../../../../spec_helper', __FILE__) + +describe "Process::Status#success?" do + + describe "for a child that exited normally" do + + before :each do + ruby_exe("exit(0)") + end + + it "returns true" do + $?.success?.should be_true + end + end + + describe "for a child that exited with a non zero status" do + + before :each do + ruby_exe("exit(42)") + end + + it "returns false" do + $?.success?.should be_false + end + end + + describe "for a child that was terminated" do + + before :each do + ruby_exe("Process.kill(:KILL, $$); exit(42)") + end + + platform_is_not :windows do + + it "returns nil" do + $?.success?.should be_nil + end + + end + + platform_is :windows do + + it "always returns true" do + $?.success?.should be_true + end + + end + + end + +end diff --git a/spec/ruby/core/process/status/termsig_spec.rb b/spec/ruby/core/process/status/termsig_spec.rb new file mode 100644 index 0000000000..d4f55e2521 --- /dev/null +++ b/spec/ruby/core/process/status/termsig_spec.rb @@ -0,0 +1,39 @@ +require File.expand_path('../../../../spec_helper', __FILE__) + +describe "Process::Status#termsig" do + + describe "for a child that exited normally" do + + before :each do + ruby_exe("exit(0)") + end + + it "returns true" do + $?.termsig.should be_nil + end + end + + describe "for a child that was sent a signal" do + + before :each do + ruby_exe("Process.kill(:KILL, $$); exit(42)") + end + + platform_is_not :windows do + + it "returns the signal" do + $?.termsig.should == Signal.list["KILL"] + end + + end + + platform_is :windows do + + it "always returns nil" do + $?.termsig.should be_nil + end + + end + + end +end diff --git a/spec/ruby/core/process/status/to_i_spec.rb b/spec/ruby/core/process/status/to_i_spec.rb new file mode 100644 index 0000000000..c45724552e --- /dev/null +++ b/spec/ruby/core/process/status/to_i_spec.rb @@ -0,0 +1,5 @@ +require File.expand_path('../../../../spec_helper', __FILE__) + +describe "Process::Status#to_i" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/process/status/to_int_spec.rb b/spec/ruby/core/process/status/to_int_spec.rb new file mode 100644 index 0000000000..8c988d7e19 --- /dev/null +++ b/spec/ruby/core/process/status/to_int_spec.rb @@ -0,0 +1,5 @@ +require File.expand_path('../../../../spec_helper', __FILE__) + +describe "Process::Status#to_int" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/process/status/to_s_spec.rb b/spec/ruby/core/process/status/to_s_spec.rb new file mode 100644 index 0000000000..ac87f4712a --- /dev/null +++ b/spec/ruby/core/process/status/to_s_spec.rb @@ -0,0 +1,5 @@ +require File.expand_path('../../../../spec_helper', __FILE__) + +describe "Process::Status#to_s" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/process/sys/getegid_spec.rb b/spec/ruby/core/process/sys/getegid_spec.rb new file mode 100644 index 0000000000..c21b890519 --- /dev/null +++ b/spec/ruby/core/process/sys/getegid_spec.rb @@ -0,0 +1,5 @@ +require File.expand_path('../../../../spec_helper', __FILE__) + +describe "Process::Sys.getegid" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/process/sys/geteuid_spec.rb b/spec/ruby/core/process/sys/geteuid_spec.rb new file mode 100644 index 0000000000..85c8d6e1bb --- /dev/null +++ b/spec/ruby/core/process/sys/geteuid_spec.rb @@ -0,0 +1,5 @@ +require File.expand_path('../../../../spec_helper', __FILE__) + +describe "Process::Sys.geteuid" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/process/sys/getgid_spec.rb b/spec/ruby/core/process/sys/getgid_spec.rb new file mode 100644 index 0000000000..945d3340f7 --- /dev/null +++ b/spec/ruby/core/process/sys/getgid_spec.rb @@ -0,0 +1,5 @@ +require File.expand_path('../../../../spec_helper', __FILE__) + +describe "Process::Sys.getgid" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/process/sys/getuid_spec.rb b/spec/ruby/core/process/sys/getuid_spec.rb new file mode 100644 index 0000000000..ead6e3044f --- /dev/null +++ b/spec/ruby/core/process/sys/getuid_spec.rb @@ -0,0 +1,5 @@ +require File.expand_path('../../../../spec_helper', __FILE__) + +describe "Process::Sys.getuid" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/process/sys/issetugid_spec.rb b/spec/ruby/core/process/sys/issetugid_spec.rb new file mode 100644 index 0000000000..2919c351a7 --- /dev/null +++ b/spec/ruby/core/process/sys/issetugid_spec.rb @@ -0,0 +1,5 @@ +require File.expand_path('../../../../spec_helper', __FILE__) + +describe "Process::Sys.issetugid" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/process/sys/setegid_spec.rb b/spec/ruby/core/process/sys/setegid_spec.rb new file mode 100644 index 0000000000..edc0d59da4 --- /dev/null +++ b/spec/ruby/core/process/sys/setegid_spec.rb @@ -0,0 +1,5 @@ +require File.expand_path('../../../../spec_helper', __FILE__) + +describe "Process::Sys.setegid" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/process/sys/seteuid_spec.rb b/spec/ruby/core/process/sys/seteuid_spec.rb new file mode 100644 index 0000000000..70cc78bec4 --- /dev/null +++ b/spec/ruby/core/process/sys/seteuid_spec.rb @@ -0,0 +1,5 @@ +require File.expand_path('../../../../spec_helper', __FILE__) + +describe "Process::Sys.seteuid" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/process/sys/setgid_spec.rb b/spec/ruby/core/process/sys/setgid_spec.rb new file mode 100644 index 0000000000..25272b1eec --- /dev/null +++ b/spec/ruby/core/process/sys/setgid_spec.rb @@ -0,0 +1,5 @@ +require File.expand_path('../../../../spec_helper', __FILE__) + +describe "Process::Sys.setgid" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/process/sys/setregid_spec.rb b/spec/ruby/core/process/sys/setregid_spec.rb new file mode 100644 index 0000000000..18a5834c80 --- /dev/null +++ b/spec/ruby/core/process/sys/setregid_spec.rb @@ -0,0 +1,5 @@ +require File.expand_path('../../../../spec_helper', __FILE__) + +describe "Process::Sys.setregid" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/process/sys/setresgid_spec.rb b/spec/ruby/core/process/sys/setresgid_spec.rb new file mode 100644 index 0000000000..9f1736b460 --- /dev/null +++ b/spec/ruby/core/process/sys/setresgid_spec.rb @@ -0,0 +1,5 @@ +require File.expand_path('../../../../spec_helper', __FILE__) + +describe "Process::Sys.setresgid" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/process/sys/setresuid_spec.rb b/spec/ruby/core/process/sys/setresuid_spec.rb new file mode 100644 index 0000000000..94c892bfdf --- /dev/null +++ b/spec/ruby/core/process/sys/setresuid_spec.rb @@ -0,0 +1,5 @@ +require File.expand_path('../../../../spec_helper', __FILE__) + +describe "Process::Sys.setresuid" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/process/sys/setreuid_spec.rb b/spec/ruby/core/process/sys/setreuid_spec.rb new file mode 100644 index 0000000000..72a8e61e7a --- /dev/null +++ b/spec/ruby/core/process/sys/setreuid_spec.rb @@ -0,0 +1,5 @@ +require File.expand_path('../../../../spec_helper', __FILE__) + +describe "Process::Sys.setreuid" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/process/sys/setrgid_spec.rb b/spec/ruby/core/process/sys/setrgid_spec.rb new file mode 100644 index 0000000000..ae820c98b8 --- /dev/null +++ b/spec/ruby/core/process/sys/setrgid_spec.rb @@ -0,0 +1,5 @@ +require File.expand_path('../../../../spec_helper', __FILE__) + +describe "Process::Sys.setrgid" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/process/sys/setruid_spec.rb b/spec/ruby/core/process/sys/setruid_spec.rb new file mode 100644 index 0000000000..4f40f6666a --- /dev/null +++ b/spec/ruby/core/process/sys/setruid_spec.rb @@ -0,0 +1,5 @@ +require File.expand_path('../../../../spec_helper', __FILE__) + +describe "Process::Sys.setruid" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/process/sys/setuid_spec.rb b/spec/ruby/core/process/sys/setuid_spec.rb new file mode 100644 index 0000000000..13bf072ad1 --- /dev/null +++ b/spec/ruby/core/process/sys/setuid_spec.rb @@ -0,0 +1,5 @@ +require File.expand_path('../../../../spec_helper', __FILE__) + +describe "Process::Sys.setuid" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/process/times_spec.rb b/spec/ruby/core/process/times_spec.rb new file mode 100644 index 0000000000..01a5595ef9 --- /dev/null +++ b/spec/ruby/core/process/times_spec.rb @@ -0,0 +1,27 @@ +require File.expand_path('../../../spec_helper', __FILE__) + +describe "Process.times" do + it "returns a Struct::Tms" do + Process.times.should be_kind_of(Struct::Tms) + end + + it "returns current cpu times" do + t = Process.times + + # Do busy work for a wall-clock interval. + start = Time.now + 1 until (Time.now - start) > 0.5 + + # Ensure times is larger. NOTE that there is no + # guarantee of an upper bound since anything may be + # happening at the OS level, so we ONLY check that at + # least an interval has elapsed. Also, we are assuming + # there is a correlation between wall clock time and + # process time. In practice, there is an observed + # discrepancy often 10% or greater. In other words, + # this is a very fuzzy test. + t2 = Process.times + diff = (t2.utime + t2.stime) - (t.utime + t.stime) + diff.should > 0 + end +end diff --git a/spec/ruby/core/process/uid/change_privilege_spec.rb b/spec/ruby/core/process/uid/change_privilege_spec.rb new file mode 100644 index 0000000000..91a38bfcdf --- /dev/null +++ b/spec/ruby/core/process/uid/change_privilege_spec.rb @@ -0,0 +1,5 @@ +require File.expand_path('../../../../spec_helper', __FILE__) + +describe "Process::UID.change_privilege" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/process/uid/eid_spec.rb b/spec/ruby/core/process/uid/eid_spec.rb new file mode 100644 index 0000000000..39fcd13d93 --- /dev/null +++ b/spec/ruby/core/process/uid/eid_spec.rb @@ -0,0 +1,9 @@ +require File.expand_path('../../../../spec_helper', __FILE__) + +describe "Process::UID.eid" do + it "needs to be reviewed for spec completeness" +end + +describe "Process::UID.eid=" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/process/uid/grant_privilege_spec.rb b/spec/ruby/core/process/uid/grant_privilege_spec.rb new file mode 100644 index 0000000000..c4d3443de8 --- /dev/null +++ b/spec/ruby/core/process/uid/grant_privilege_spec.rb @@ -0,0 +1,5 @@ +require File.expand_path('../../../../spec_helper', __FILE__) + +describe "Process::UID.grant_privilege" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/process/uid/re_exchange_spec.rb b/spec/ruby/core/process/uid/re_exchange_spec.rb new file mode 100644 index 0000000000..2f9b0d6a87 --- /dev/null +++ b/spec/ruby/core/process/uid/re_exchange_spec.rb @@ -0,0 +1,5 @@ +require File.expand_path('../../../../spec_helper', __FILE__) + +describe "Process::UID.re_exchange" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/process/uid/re_exchangeable_spec.rb b/spec/ruby/core/process/uid/re_exchangeable_spec.rb new file mode 100644 index 0000000000..63f45fa662 --- /dev/null +++ b/spec/ruby/core/process/uid/re_exchangeable_spec.rb @@ -0,0 +1,5 @@ +require File.expand_path('../../../../spec_helper', __FILE__) + +describe "Process::UID.re_exchangeable?" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/process/uid/rid_spec.rb b/spec/ruby/core/process/uid/rid_spec.rb new file mode 100644 index 0000000000..cdfe08e3be --- /dev/null +++ b/spec/ruby/core/process/uid/rid_spec.rb @@ -0,0 +1,5 @@ +require File.expand_path('../../../../spec_helper', __FILE__) + +describe "Process::UID.rid" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/process/uid/sid_available_spec.rb b/spec/ruby/core/process/uid/sid_available_spec.rb new file mode 100644 index 0000000000..5d51366dd4 --- /dev/null +++ b/spec/ruby/core/process/uid/sid_available_spec.rb @@ -0,0 +1,5 @@ +require File.expand_path('../../../../spec_helper', __FILE__) + +describe "Process::UID.sid_available?" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/process/uid/switch_spec.rb b/spec/ruby/core/process/uid/switch_spec.rb new file mode 100644 index 0000000000..6747ee4f43 --- /dev/null +++ b/spec/ruby/core/process/uid/switch_spec.rb @@ -0,0 +1,5 @@ +require File.expand_path('../../../../spec_helper', __FILE__) + +describe "Process::UID.switch" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/process/uid_spec.rb b/spec/ruby/core/process/uid_spec.rb new file mode 100644 index 0000000000..1b561f47e0 --- /dev/null +++ b/spec/ruby/core/process/uid_spec.rb @@ -0,0 +1,84 @@ +require File.expand_path('../../../spec_helper', __FILE__) + +describe "Process.uid" do + platform_is_not :windows do + it "returns the correct uid for the user executing this process" do + current_uid_according_to_unix = `id -ur`.to_i + Process.uid.should == current_uid_according_to_unix + end + end + + it "also goes by Process::UID.rid" do + Process::UID.rid.should == Process.uid + end + + it "also goes by Process::Sys.getuid" do + Process::Sys.getuid.should == Process.uid + end +end + +describe "Process.uid=" do + + platform_is_not :windows do + it "raises TypeError if not passed an Integer" do + lambda { Process.uid = Object.new }.should raise_error(TypeError) + end + + as_user do + it "raises Errno::ERPERM if run by a non privileged user trying to set the superuser id" do + lambda { (Process.uid = 0)}.should raise_error(Errno::EPERM) + end + + it "raises Errno::ERPERM if run by a non privileged user trying to set the superuser id from username" do + lambda { Process.uid = "root" }.should raise_error(Errno::EPERM) + end + end + + as_superuser do + describe "if run by a superuser" do + with_feature :fork do + it "sets the real user id for the current process" do + read, write = IO.pipe + pid = Process.fork do + begin + read.close + Process.uid = 1 + write << Process.uid + write.close + rescue Exception => e + write << e << e.backtrace + end + Process.exit! + end + write.close + uid = read.gets + uid.should == "1" + Process.wait pid + end + + it "sets the real user id if preceded by Process.euid=id" do + read, write = IO.pipe + pid = Process.fork do + begin + read.close + Process.euid = 1 + Process.uid = 1 + write << Process.uid + write.close + rescue Exception => e + write << e << e.backtrace + end + Process.exit! + end + write.close + uid = read.gets + uid.should == "1" + Process.wait pid + end + end + end + end + end + + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/process/wait2_spec.rb b/spec/ruby/core/process/wait2_spec.rb new file mode 100644 index 0000000000..8e1ac763f1 --- /dev/null +++ b/spec/ruby/core/process/wait2_spec.rb @@ -0,0 +1,29 @@ +require File.expand_path('../../../spec_helper', __FILE__) + +describe "Process.wait2" do + before :all do + # HACK: this kludge is temporarily necessary because some + # misbehaving spec somewhere else does not clear processes + begin + leaked = Process.waitall + puts "leaked before wait2 specs: #{leaked}" unless leaked.empty? + rescue NotImplementedError + end + end + + platform_is_not :windows do + it "returns the pid and status of child process" do + pidf = Process.fork { Process.exit! 99 } + results = Process.wait2 + results.size.should == 2 + pidw, status = results + pidf.should == pidw + status.exitstatus.should == 99 + end + end + + it "raises a StandardError if no child processes exist" do + lambda { Process.wait2 }.should raise_error(Errno::ECHILD) + lambda { Process.wait2 }.should raise_error(StandardError) + end +end diff --git a/spec/ruby/core/process/wait_spec.rb b/spec/ruby/core/process/wait_spec.rb new file mode 100644 index 0000000000..8111621695 --- /dev/null +++ b/spec/ruby/core/process/wait_spec.rb @@ -0,0 +1,90 @@ +require File.expand_path('../../../spec_helper', __FILE__) +require File.expand_path('../fixtures/common', __FILE__) + +describe "Process.wait" do + ProcessSpecs.use_system_ruby(self) + + before :all do + begin + leaked = Process.waitall + puts "leaked before wait specs: #{leaked}" unless leaked.empty? + rescue NotImplementedError + end + end + + it "raises an Errno::ECHILD if there are no child processes" do + lambda { Process.wait }.should raise_error(Errno::ECHILD) + end + + platform_is_not :windows do + it "returns its childs pid" do + pid = Process.spawn(ruby_cmd('exit')) + Process.wait.should == pid + end + + it "sets $? to a Process::Status" do + pid = Process.spawn(ruby_cmd('exit')) + Process.wait + $?.should be_kind_of(Process::Status) + $?.pid.should == pid + end + + it "waits for any child process if no pid is given" do + pid = Process.spawn(ruby_cmd('exit')) + Process.wait.should == pid + lambda { Process.kill(0, pid) }.should raise_error(Errno::ESRCH) + end + + it "waits for a specific child if a pid is given" do + pid1 = Process.spawn(ruby_cmd('exit')) + pid2 = Process.spawn(ruby_cmd('exit')) + Process.wait(pid2).should == pid2 + Process.wait(pid1).should == pid1 + lambda { Process.kill(0, pid1) }.should raise_error(Errno::ESRCH) + lambda { Process.kill(0, pid2) }.should raise_error(Errno::ESRCH) + end + + it "coerces the pid to an Integer" do + pid1 = Process.spawn(ruby_cmd('exit')) + Process.wait(mock_int(pid1)).should == pid1 + lambda { Process.kill(0, pid1) }.should raise_error(Errno::ESRCH) + end + + # This spec is probably system-dependent. + it "waits for a child whose process group ID is that of the calling process" do + pid1 = Process.spawn(ruby_cmd('exit'), pgroup: true) + pid2 = Process.spawn(ruby_cmd('exit')) + + Process.wait(0).should == pid2 + Process.wait.should == pid1 + end + + # This spec is probably system-dependent. + it "doesn't block if no child is available when WNOHANG is used" do + read, write = IO.pipe + pid = Process.fork do + read.close + Signal.trap("TERM") { Process.exit! } + write << 1 + write.close + sleep + end + + Process.wait(pid, Process::WNOHANG).should be_nil + + # wait for the child to setup its TERM handler + write.close + read.read(1) + read.close + + Process.kill("TERM", pid) + Process.wait.should == pid + end + + it "always accepts flags=0" do + pid = Process.spawn(ruby_cmd('exit')) + Process.wait(-1, 0).should == pid + lambda { Process.kill(0, pid) }.should raise_error(Errno::ESRCH) + end + end +end diff --git a/spec/ruby/core/process/waitall_spec.rb b/spec/ruby/core/process/waitall_spec.rb new file mode 100644 index 0000000000..e1fc38d2bc --- /dev/null +++ b/spec/ruby/core/process/waitall_spec.rb @@ -0,0 +1,48 @@ +require File.expand_path('../../../spec_helper', __FILE__) + +describe "Process.waitall" do + before :all do + begin + Process.waitall + rescue NotImplementedError + end + end + + it "returns an empty array when there are no children" do + Process.waitall.should == [] + end + + it "takes no arguments" do + lambda { Process.waitall(0) }.should raise_error(ArgumentError) + end + + platform_is_not :windows do + it "waits for all children" do + pids = [] + pids << Process.fork { Process.exit! 2 } + pids << Process.fork { Process.exit! 1 } + pids << Process.fork { Process.exit! 0 } + Process.waitall + pids.each { |pid| + lambda { Process.kill(0, pid) }.should raise_error(Errno::ESRCH) + } + end + + it "returns an array of pid/status pairs" do + pids = [] + pids << Process.fork { Process.exit! 2 } + pids << Process.fork { Process.exit! 1 } + pids << Process.fork { Process.exit! 0 } + a = Process.waitall + a.should be_kind_of(Array) + a.size.should == 3 + pids.each { |pid| + pid_status = a.assoc(pid) + pid_status.should be_kind_of(Array) + pid_status.size.should == 2 + pid_status.first.should == pid + pid_status.last.should be_kind_of(Process::Status) + } + end + end +end diff --git a/spec/ruby/core/process/waitpid2_spec.rb b/spec/ruby/core/process/waitpid2_spec.rb new file mode 100644 index 0000000000..303c59fda3 --- /dev/null +++ b/spec/ruby/core/process/waitpid2_spec.rb @@ -0,0 +1,5 @@ +require File.expand_path('../../../spec_helper', __FILE__) + +describe "Process.waitpid2" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/process/waitpid_spec.rb b/spec/ruby/core/process/waitpid_spec.rb new file mode 100644 index 0000000000..c95e8d59dd --- /dev/null +++ b/spec/ruby/core/process/waitpid_spec.rb @@ -0,0 +1,15 @@ +require File.expand_path('../../../spec_helper', __FILE__) + +describe "Process.waitpid" do + it "needs to be reviewed for spec completeness" + + it "returns nil when the process has not yet completed and WNOHANG is specified" do + pid = spawn("sleep 5") + begin + Process.waitpid(pid, Process::WNOHANG).should == nil + Process.kill("KILL", pid) + ensure + Process.wait(pid) + end + end +end |