summaryrefslogtreecommitdiff
path: root/spec/ruby/core/process
diff options
context:
space:
mode:
Diffstat (limited to 'spec/ruby/core/process')
-rw-r--r--spec/ruby/core/process/_fork_spec.rb24
-rw-r--r--spec/ruby/core/process/abort_spec.rb6
-rw-r--r--spec/ruby/core/process/argv0_spec.rb25
-rw-r--r--spec/ruby/core/process/clock_getres_spec.rb33
-rw-r--r--spec/ruby/core/process/clock_gettime_spec.rb152
-rw-r--r--spec/ruby/core/process/constants_spec.rb114
-rw-r--r--spec/ruby/core/process/daemon_spec.rb118
-rw-r--r--spec/ruby/core/process/detach_spec.rb81
-rw-r--r--spec/ruby/core/process/egid_spec.rb58
-rw-r--r--spec/ruby/core/process/euid_spec.rb56
-rw-r--r--spec/ruby/core/process/exec_spec.rb241
-rw-r--r--spec/ruby/core/process/exit_spec.rb10
-rw-r--r--spec/ruby/core/process/fixtures/argv0.rb6
-rw-r--r--spec/ruby/core/process/fixtures/clocks.rb18
-rw-r--r--spec/ruby/core/process/fixtures/common.rb88
-rw-r--r--spec/ruby/core/process/fixtures/daemon.rb111
-rw-r--r--spec/ruby/core/process/fixtures/in.txt1
-rw-r--r--spec/ruby/core/process/fixtures/kill.rb43
-rw-r--r--spec/ruby/core/process/fixtures/map_fd.rb9
-rw-r--r--spec/ruby/core/process/fixtures/setpriority.rb12
-rw-r--r--spec/ruby/core/process/fork_spec.rb6
-rw-r--r--spec/ruby/core/process/getpgid_spec.rb17
-rw-r--r--spec/ruby/core/process/getpgrp_spec.rb7
-rw-r--r--spec/ruby/core/process/getpriority_spec.rb23
-rw-r--r--spec/ruby/core/process/getrlimit_spec.rb100
-rw-r--r--spec/ruby/core/process/gid/change_privilege_spec.rb5
-rw-r--r--spec/ruby/core/process/gid/eid_spec.rb9
-rw-r--r--spec/ruby/core/process/gid/grant_privilege_spec.rb5
-rw-r--r--spec/ruby/core/process/gid/re_exchange_spec.rb5
-rw-r--r--spec/ruby/core/process/gid/re_exchangeable_spec.rb5
-rw-r--r--spec/ruby/core/process/gid/rid_spec.rb5
-rw-r--r--spec/ruby/core/process/gid/sid_available_spec.rb5
-rw-r--r--spec/ruby/core/process/gid/switch_spec.rb5
-rw-r--r--spec/ruby/core/process/gid_spec.rb22
-rw-r--r--spec/ruby/core/process/groups_spec.rb67
-rw-r--r--spec/ruby/core/process/initgroups_spec.rb22
-rw-r--r--spec/ruby/core/process/kill_spec.rb132
-rw-r--r--spec/ruby/core/process/last_status_spec.rb18
-rw-r--r--spec/ruby/core/process/maxgroups_spec.rb19
-rw-r--r--spec/ruby/core/process/pid_spec.rb9
-rw-r--r--spec/ruby/core/process/ppid_spec.rb9
-rw-r--r--spec/ruby/core/process/set_proctitle_spec.rb23
-rw-r--r--spec/ruby/core/process/setpgid_spec.rb29
-rw-r--r--spec/ruby/core/process/setpgrp_spec.rb37
-rw-r--r--spec/ruby/core/process/setpriority_spec.rb60
-rw-r--r--spec/ruby/core/process/setrlimit_spec.rb237
-rw-r--r--spec/ruby/core/process/setsid_spec.rb16
-rw-r--r--spec/ruby/core/process/spawn_spec.rb756
-rw-r--r--spec/ruby/core/process/status/bit_and_spec.rb38
-rw-r--r--spec/ruby/core/process/status/coredump_spec.rb5
-rw-r--r--spec/ruby/core/process/status/equal_value_spec.rb15
-rw-r--r--spec/ruby/core/process/status/exited_spec.rb32
-rw-r--r--spec/ruby/core/process/status/exitstatus_spec.rb25
-rw-r--r--spec/ruby/core/process/status/inspect_spec.rb5
-rw-r--r--spec/ruby/core/process/status/pid_spec.rb15
-rw-r--r--spec/ruby/core/process/status/right_shift_spec.rb37
-rw-r--r--spec/ruby/core/process/status/signaled_spec.rb31
-rw-r--r--spec/ruby/core/process/status/stopped_spec.rb5
-rw-r--r--spec/ruby/core/process/status/stopsig_spec.rb5
-rw-r--r--spec/ruby/core/process/status/success_spec.rb41
-rw-r--r--spec/ruby/core/process/status/termsig_spec.rb43
-rw-r--r--spec/ruby/core/process/status/to_i_spec.rb13
-rw-r--r--spec/ruby/core/process/status/to_int_spec.rb5
-rw-r--r--spec/ruby/core/process/status/to_s_spec.rb5
-rw-r--r--spec/ruby/core/process/status/wait_spec.rb100
-rw-r--r--spec/ruby/core/process/sys/getegid_spec.rb5
-rw-r--r--spec/ruby/core/process/sys/geteuid_spec.rb5
-rw-r--r--spec/ruby/core/process/sys/getgid_spec.rb5
-rw-r--r--spec/ruby/core/process/sys/getuid_spec.rb5
-rw-r--r--spec/ruby/core/process/sys/issetugid_spec.rb5
-rw-r--r--spec/ruby/core/process/sys/setegid_spec.rb5
-rw-r--r--spec/ruby/core/process/sys/seteuid_spec.rb5
-rw-r--r--spec/ruby/core/process/sys/setgid_spec.rb5
-rw-r--r--spec/ruby/core/process/sys/setregid_spec.rb5
-rw-r--r--spec/ruby/core/process/sys/setresgid_spec.rb5
-rw-r--r--spec/ruby/core/process/sys/setresuid_spec.rb5
-rw-r--r--spec/ruby/core/process/sys/setreuid_spec.rb5
-rw-r--r--spec/ruby/core/process/sys/setrgid_spec.rb5
-rw-r--r--spec/ruby/core/process/sys/setruid_spec.rb5
-rw-r--r--spec/ruby/core/process/sys/setuid_spec.rb5
-rw-r--r--spec/ruby/core/process/times_spec.rb19
-rw-r--r--spec/ruby/core/process/tms/cstime_spec.rb17
-rw-r--r--spec/ruby/core/process/tms/cutime_spec.rb17
-rw-r--r--spec/ruby/core/process/tms/stime_spec.rb17
-rw-r--r--spec/ruby/core/process/tms/utime_spec.rb17
-rw-r--r--spec/ruby/core/process/uid/change_privilege_spec.rb5
-rw-r--r--spec/ruby/core/process/uid/eid_spec.rb9
-rw-r--r--spec/ruby/core/process/uid/grant_privilege_spec.rb5
-rw-r--r--spec/ruby/core/process/uid/re_exchange_spec.rb5
-rw-r--r--spec/ruby/core/process/uid/re_exchangeable_spec.rb5
-rw-r--r--spec/ruby/core/process/uid/rid_spec.rb5
-rw-r--r--spec/ruby/core/process/uid/sid_available_spec.rb5
-rw-r--r--spec/ruby/core/process/uid/switch_spec.rb5
-rw-r--r--spec/ruby/core/process/uid_spec.rb57
-rw-r--r--spec/ruby/core/process/wait2_spec.rb45
-rw-r--r--spec/ruby/core/process/wait_spec.rb91
-rw-r--r--spec/ruby/core/process/waitall_spec.rb48
-rw-r--r--spec/ruby/core/process/waitpid2_spec.rb5
-rw-r--r--spec/ruby/core/process/waitpid_spec.rb14
-rw-r--r--spec/ruby/core/process/warmup_spec.rb11
100 files changed, 3756 insertions, 0 deletions
diff --git a/spec/ruby/core/process/_fork_spec.rb b/spec/ruby/core/process/_fork_spec.rb
new file mode 100644
index 0000000000..e1f45e2656
--- /dev/null
+++ b/spec/ruby/core/process/_fork_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../spec_helper'
+
+describe "Process._fork" do
+ it "for #respond_to? returns the same as Process.respond_to?(:fork)" do
+ Process.respond_to?(:_fork).should == Process.respond_to?(:fork)
+ end
+
+ # Using respond_to? in a guard here is OK because the correct semantics
+ # are that _fork is implemented if and only if fork is (see above).
+ guard_not -> { Process.respond_to?(:fork) } do
+ it "raises a NotImplementedError when called" do
+ -> { Process._fork }.should raise_error(NotImplementedError)
+ end
+ end
+
+ guard -> { Process.respond_to?(:fork) } do
+ it "is called by Process#fork" do
+ Process.should_receive(:_fork).once.and_return(42)
+
+ pid = Process.fork {}
+ pid.should equal(42)
+ end
+ end
+end
diff --git a/spec/ruby/core/process/abort_spec.rb b/spec/ruby/core/process/abort_spec.rb
new file mode 100644
index 0000000000..1b6ad1da43
--- /dev/null
+++ b/spec/ruby/core/process/abort_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/process/abort'
+
+describe "Process.abort" do
+ it_behaves_like :process_abort, :abort, Process
+end
diff --git a/spec/ruby/core/process/argv0_spec.rb b/spec/ruby/core/process/argv0_spec.rb
new file mode 100644
index 0000000000..f5aba719e9
--- /dev/null
+++ b/spec/ruby/core/process/argv0_spec.rb
@@ -0,0 +1,25 @@
+require_relative '../../spec_helper'
+
+describe "Process.argv0" do
+ it "returns a String" do
+ Process.argv0.should be_kind_of(String)
+ end
+
+ it "is the path given as the main script and the same as __FILE__" do
+ script = "fixtures/argv0.rb"
+
+ Dir.chdir(__dir__) do
+ ruby_exe(script).should == "#{script}\n#{script}\nOK"
+ end
+ end
+
+ ruby_bug "#19597", ""..."3.3" do
+ it "returns a frozen object" do
+ Process.argv0.should.frozen?
+ end
+ end
+
+ it "returns every time the same object" do
+ Process.argv0.should.equal?(Process.argv0)
+ end
+end
diff --git a/spec/ruby/core/process/clock_getres_spec.rb b/spec/ruby/core/process/clock_getres_spec.rb
new file mode 100644
index 0000000000..85aa2b25f1
--- /dev/null
+++ b/spec/ruby/core/process/clock_getres_spec.rb
@@ -0,0 +1,33 @@
+require_relative '../../spec_helper'
+
+describe "Process.clock_getres" do
+ # These are documented
+
+ it "with :GETTIMEOFDAY_BASED_CLOCK_REALTIME reports 1 microsecond" do
+ Process.clock_getres(:GETTIMEOFDAY_BASED_CLOCK_REALTIME, :nanosecond).should == 1_000
+ end
+
+ it "with :TIME_BASED_CLOCK_REALTIME reports 1 second" do
+ Process.clock_getres(:TIME_BASED_CLOCK_REALTIME, :nanosecond).should == 1_000_000_000
+ end
+
+ platform_is_not :windows do
+ it "with :GETRUSAGE_BASED_CLOCK_PROCESS_CPUTIME_ID reports 1 microsecond" do
+ Process.clock_getres(:GETRUSAGE_BASED_CLOCK_PROCESS_CPUTIME_ID, :nanosecond).should == 1_000
+ end
+ end
+
+ # These are observed
+
+ platform_is :linux, :darwin, :windows do
+ it "with Process::CLOCK_REALTIME reports at least 10 millisecond" do
+ Process.clock_getres(Process::CLOCK_REALTIME, :nanosecond).should <= 10_000_000
+ end
+ end
+
+ platform_is :linux, :darwin, :windows do
+ it "with Process::CLOCK_MONOTONIC reports at least 10 millisecond" do
+ Process.clock_getres(Process::CLOCK_MONOTONIC, :nanosecond).should <= 10_000_000
+ end
+ end
+end
diff --git a/spec/ruby/core/process/clock_gettime_spec.rb b/spec/ruby/core/process/clock_gettime_spec.rb
new file mode 100644
index 0000000000..6c1a52f21e
--- /dev/null
+++ b/spec/ruby/core/process/clock_gettime_spec.rb
@@ -0,0 +1,152 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/clocks'
+
+describe "Process.clock_gettime" do
+ ProcessSpecs.clock_constants.each do |name, value|
+ it "can be called with Process::#{name}" do
+ Process.clock_gettime(value).should be_an_instance_of(Float)
+ end
+ end
+
+ describe 'time units' do
+ it 'handles a fixed set of time units' do
+ [:nanosecond, :microsecond, :millisecond, :second].each do |unit|
+ Process.clock_gettime(Process::CLOCK_MONOTONIC, unit).should be_kind_of(Integer)
+ end
+
+ [:float_microsecond, :float_millisecond, :float_second].each do |unit|
+ Process.clock_gettime(Process::CLOCK_MONOTONIC, unit).should be_an_instance_of(Float)
+ end
+ end
+
+ it 'raises an ArgumentError for an invalid time unit' do
+ -> { Process.clock_gettime(Process::CLOCK_MONOTONIC, :bad) }.should raise_error(ArgumentError)
+ end
+
+ it 'defaults to :float_second' do
+ t1 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
+ t2 = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_second)
+
+ t1.should be_an_instance_of(Float)
+ t2.should be_an_instance_of(Float)
+ t2.should be_close(t1, TIME_TOLERANCE)
+ end
+
+ it 'uses the default time unit (:float_second) when passed nil' do
+ t1 = Process.clock_gettime(Process::CLOCK_MONOTONIC, nil)
+ t2 = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_second)
+
+ t1.should be_an_instance_of(Float)
+ t2.should be_an_instance_of(Float)
+ t2.should be_close(t1, TIME_TOLERANCE)
+ end
+ end
+
+ describe "supports the platform clocks mentioned in the documentation" do
+ it "CLOCK_REALTIME" do
+ Process.clock_gettime(Process::CLOCK_REALTIME).should be_an_instance_of(Float)
+ end
+
+ it "CLOCK_MONOTONIC" do
+ Process.clock_gettime(Process::CLOCK_MONOTONIC).should be_an_instance_of(Float)
+ end
+
+ # These specs need macOS 10.12+ / darwin 16+
+ guard -> { platform_is_not(:darwin) or kernel_version_is '16' } do
+ platform_is :linux, :openbsd, :darwin do
+ it "CLOCK_PROCESS_CPUTIME_ID" do
+ Process.clock_gettime(Process::CLOCK_PROCESS_CPUTIME_ID).should be_an_instance_of(Float)
+ end
+ end
+
+ platform_is :linux, :freebsd, :openbsd, :darwin do
+ it "CLOCK_THREAD_CPUTIME_ID" do
+ Process.clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID).should be_an_instance_of(Float)
+ end
+ end
+
+ platform_is :linux, :darwin do
+ it "CLOCK_MONOTONIC_RAW" do
+ Process.clock_gettime(Process::CLOCK_MONOTONIC_RAW).should be_an_instance_of(Float)
+ end
+ end
+
+ platform_is :darwin do
+ it "CLOCK_MONOTONIC_RAW_APPROX" do
+ Process.clock_gettime(Process::CLOCK_MONOTONIC_RAW_APPROX).should be_an_instance_of(Float)
+ end
+
+ it "CLOCK_UPTIME_RAW and CLOCK_UPTIME_RAW_APPROX" do
+ Process.clock_gettime(Process::CLOCK_UPTIME_RAW).should be_an_instance_of(Float)
+ Process.clock_gettime(Process::CLOCK_UPTIME_RAW_APPROX).should be_an_instance_of(Float)
+ end
+ end
+ end
+
+ platform_is :freebsd do
+ it "CLOCK_VIRTUAL" do
+ Process.clock_gettime(Process::CLOCK_VIRTUAL).should be_an_instance_of(Float)
+ end
+
+ it "CLOCK_PROF" do
+ Process.clock_gettime(Process::CLOCK_PROF).should be_an_instance_of(Float)
+ end
+ end
+
+ platform_is :freebsd, :openbsd do
+ it "CLOCK_UPTIME" do
+ Process.clock_gettime(Process::CLOCK_UPTIME).should be_an_instance_of(Float)
+ end
+ end
+
+ platform_is :freebsd do
+ it "CLOCK_REALTIME_FAST and CLOCK_REALTIME_PRECISE" do
+ Process.clock_gettime(Process::CLOCK_REALTIME_FAST).should be_an_instance_of(Float)
+ Process.clock_gettime(Process::CLOCK_REALTIME_PRECISE).should be_an_instance_of(Float)
+ end
+
+ it "CLOCK_MONOTONIC_FAST and CLOCK_MONOTONIC_PRECISE" do
+ Process.clock_gettime(Process::CLOCK_MONOTONIC_FAST).should be_an_instance_of(Float)
+ Process.clock_gettime(Process::CLOCK_MONOTONIC_PRECISE).should be_an_instance_of(Float)
+ end
+
+ it "CLOCK_UPTIME_FAST and CLOCK_UPTIME_PRECISE" do
+ Process.clock_gettime(Process::CLOCK_UPTIME_FAST).should be_an_instance_of(Float)
+ Process.clock_gettime(Process::CLOCK_UPTIME_PRECISE).should be_an_instance_of(Float)
+ end
+
+ it "CLOCK_SECOND" do
+ Process.clock_gettime(Process::CLOCK_SECOND).should be_an_instance_of(Float)
+ end
+ end
+
+ guard -> { platform_is :linux and kernel_version_is '2.6.32' } do
+ it "CLOCK_REALTIME_COARSE" do
+ Process.clock_gettime(Process::CLOCK_REALTIME_COARSE).should be_an_instance_of(Float)
+ end
+
+ it "CLOCK_MONOTONIC_COARSE" do
+ Process.clock_gettime(Process::CLOCK_MONOTONIC_COARSE).should be_an_instance_of(Float)
+ end
+ end
+
+ guard -> { platform_is :linux and kernel_version_is '2.6.39' } do
+ it "CLOCK_BOOTTIME" do
+ skip "No Process::CLOCK_BOOTTIME" unless defined?(Process::CLOCK_BOOTTIME)
+ Process.clock_gettime(Process::CLOCK_BOOTTIME).should be_an_instance_of(Float)
+ end
+ end
+
+ guard -> { platform_is "x86_64-linux" and kernel_version_is '3.0' } do
+ it "CLOCK_REALTIME_ALARM" do
+ skip "No Process::CLOCK_REALTIME_ALARM" unless defined?(Process::CLOCK_REALTIME_ALARM)
+ Process.clock_gettime(Process::CLOCK_REALTIME_ALARM).should be_an_instance_of(Float)
+ end
+
+ it "CLOCK_BOOTTIME_ALARM" do
+ skip "No Process::CLOCK_BOOTTIME_ALARM" unless defined?(Process::CLOCK_BOOTTIME_ALARM)
+ Process.clock_gettime(Process::CLOCK_BOOTTIME_ALARM).should be_an_instance_of(Float)
+ end
+ end
+ end
+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..57cacadef2
--- /dev/null
+++ b/spec/ruby/core/process/constants_spec.rb
@@ -0,0 +1,114 @@
+require_relative '../../spec_helper'
+
+describe "Process::Constants" do
+ platform_is :darwin, :netbsd, :freebsd do
+ it "are all present on BSD-like systems" do
+ %i[
+ WNOHANG
+ WUNTRACED
+ PRIO_PROCESS
+ PRIO_PGRP
+ PRIO_USER
+ RLIM_INFINITY
+ RLIMIT_CPU
+ RLIMIT_FSIZE
+ RLIMIT_DATA
+ RLIMIT_STACK
+ RLIMIT_CORE
+ RLIMIT_RSS
+ RLIMIT_MEMLOCK
+ RLIMIT_NPROC
+ RLIMIT_NOFILE
+ ].each do |const|
+ Process.const_defined?(const).should be_true
+ Process.const_get(const).should be_an_instance_of(Integer)
+ end
+ end
+ end
+
+ platform_is :darwin do
+ it "are all present on Darwin" do
+ %i[
+ RLIM_SAVED_MAX
+ RLIM_SAVED_CUR
+ RLIMIT_AS
+ ].each do |const|
+ Process.const_defined?(const).should be_true
+ Process.const_get(const).should be_an_instance_of(Integer)
+ end
+ end
+ end
+
+ platform_is :linux do
+ it "are all present on Linux" do
+ %i[
+ WNOHANG
+ WUNTRACED
+ PRIO_PROCESS
+ PRIO_PGRP
+ PRIO_USER
+ RLIMIT_CPU
+ RLIMIT_FSIZE
+ RLIMIT_DATA
+ RLIMIT_STACK
+ RLIMIT_CORE
+ RLIMIT_RSS
+ RLIMIT_NPROC
+ RLIMIT_NOFILE
+ RLIMIT_MEMLOCK
+ RLIMIT_AS
+ RLIM_INFINITY
+ RLIM_SAVED_MAX
+ RLIM_SAVED_CUR
+ ].each do |const|
+ Process.const_defined?(const).should be_true
+ Process.const_get(const).should be_an_instance_of(Integer)
+ end
+ end
+ end
+
+ platform_is :netbsd, :freebsd do
+ it "are all present on NetBSD and FreeBSD" do
+ %i[
+ RLIMIT_SBSIZE
+ RLIMIT_AS
+ ].each do |const|
+ Process.const_defined?(const).should be_true
+ Process.const_get(const).should be_an_instance_of(Integer)
+ end
+ end
+ end
+
+ platform_is :freebsd do
+ it "are all present on FreeBSD" do
+ %i[
+ RLIMIT_NPTS
+ ].each do |const|
+ Process.const_defined?(const).should be_true
+ Process.const_get(const).should be_an_instance_of(Integer)
+ end
+ end
+ end
+
+ platform_is :windows do
+ it "does not define RLIMIT constants" do
+ %i[
+ RLIMIT_CPU
+ RLIMIT_FSIZE
+ RLIMIT_DATA
+ RLIMIT_STACK
+ RLIMIT_CORE
+ RLIMIT_RSS
+ RLIMIT_NPROC
+ RLIMIT_NOFILE
+ RLIMIT_MEMLOCK
+ RLIMIT_AS
+ RLIM_INFINITY
+ RLIM_SAVED_MAX
+ RLIM_SAVED_CUR
+ ].each do |const|
+ Process.const_defined?(const).should be_false
+ end
+ 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..20b0d743b9
--- /dev/null
+++ b/spec/ruby/core/process/daemon_spec.rb
@@ -0,0 +1,118 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+platform_is_not :windows do
+ # macOS 15 is not working this examples
+ return if /darwin/ =~ RUBY_PLATFORM && /15/ =~ `sw_vers -productVersion`
+
+ 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
+
+ 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
+ end
+end
+
+platform_is :windows do
+ describe "Process.daemon" do
+ it "raises a NotImplementedError" do
+ -> {
+ 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..f13bda1f5d
--- /dev/null
+++ b/spec/ruby/core/process/detach_spec.rb
@@ -0,0 +1,81 @@
+require_relative '../../spec_helper'
+
+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
+ -> { 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
+
+ it "tolerates not existing child process pid" do
+ # Use a value that is close to the INT_MAX (pid usually is signed int).
+ # It should (at least) be greater than allowed pid limit value that depends on OS.
+ pid_not_existing = 2.pow(30)
+
+ # Check that there is no a child process with this hardcoded pid.
+ # Command `kill 0 pid`:
+ # - returns "1" if a process exists and
+ # - raises Errno::ESRCH otherwise
+ -> { Process.kill(0, pid_not_existing) }.should raise_error(Errno::ESRCH)
+
+ thr = Process.detach(pid_not_existing)
+ thr.join
+
+ thr.should be_kind_of(Thread)
+ end
+
+ it "calls #to_int to implicitly convert non-Integer pid to Integer" do
+ pid = MockObject.new('mock-enumerable')
+ pid.should_receive(:to_int).and_return(100500)
+
+ Process.detach(pid).join
+ end
+
+ it "raises TypeError when pid argument does not have #to_int method" do
+ -> { Process.detach(Object.new) }.should raise_error(TypeError, "no implicit conversion of Object into Integer")
+ end
+
+ it "raises TypeError when #to_int returns non-Integer value" do
+ pid = MockObject.new('mock-enumerable')
+ pid.should_receive(:to_int).and_return(:symbol)
+
+ -> { Process.detach(pid) }.should raise_error(TypeError, "can't convert MockObject to Integer (MockObject#to_int gives Symbol)")
+ 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..a67b623d5c
--- /dev/null
+++ b/spec/ruby/core/process/egid_spec.rb
@@ -0,0 +1,58 @@
+require_relative '../../spec_helper'
+
+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
+
+ platform_is_not :windows do
+ it "raises TypeError if not passed an Integer or String" do
+ -> { Process.egid = Object.new }.should raise_error(TypeError)
+ end
+
+ it "sets the effective group id to its own gid if given the username corresponding to its own gid" do
+ raise unless Process.gid == Process.egid
+
+ require "etc"
+ group = Etc.getgrgid(Process.gid).name
+
+ Process.egid = group
+ Process.egid.should == Process.gid
+ end
+
+ as_user do
+ it "raises Errno::ERPERM if run by a non superuser trying to set the root group id" do
+ -> { Process.egid = 0 }.should raise_error(Errno::EPERM)
+ end
+
+ platform_is :linux do
+ it "raises Errno::ERPERM if run by a non superuser trying to set the group id from group name" do
+ -> { Process.egid = "root" }.should raise_error(Errno::EPERM)
+ end
+ end
+ end
+
+ as_superuser do
+ context "when ran by a superuser" do
+ it "sets the effective group id for the current process if run by a superuser" do
+ code = <<-RUBY
+ Process.egid = 1
+ puts Process.egid
+ RUBY
+ ruby_exe(code).should == "1\n"
+ end
+ end
+ end
+ end
+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..c1ec4171d0
--- /dev/null
+++ b/spec/ruby/core/process/euid_spec.rb
@@ -0,0 +1,56 @@
+require_relative '../../spec_helper'
+
+describe "Process.euid" do
+ it "returns the effective user ID for this process" do
+ Process.euid.should be_kind_of(Integer)
+ 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
+ -> { Process.euid = Object.new }.should raise_error(TypeError)
+ end
+
+ it "sets the effective user id to its own uid if given the username corresponding to its own uid" do
+ raise unless Process.uid == Process.euid
+
+ require "etc"
+ user = Etc.getpwuid(Process.uid).name
+
+ Process.euid = user
+ Process.euid.should == Process.uid
+ end
+
+ as_user do
+ it "raises Errno::ERPERM if run by a non superuser trying to set the superuser id" do
+ -> { 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
+ -> { Process.euid = "root" }.should raise_error(Errno::EPERM)
+ end
+ end
+
+ as_superuser do
+ describe "if run by a superuser" do
+ it "sets the effective user id for the current process if run by a superuser" do
+ code = <<-RUBY
+ Process.euid = 1
+ puts Process.euid
+ RUBY
+ ruby_exe(code).should == "1\n"
+ 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..0f371b39c8
--- /dev/null
+++ b/spec/ruby/core/process/exec_spec.rb
@@ -0,0 +1,241 @@
+require_relative '../../spec_helper'
+
+describe "Process.exec" do
+ it "raises Errno::ENOENT for an empty string" do
+ -> { Process.exec "" }.should raise_error(Errno::ENOENT)
+ end
+
+ it "raises Errno::ENOENT for a command which does not exist" do
+ -> { Process.exec "bogus-noent-script.sh" }.should raise_error(Errno::ENOENT)
+ end
+
+ it "raises an ArgumentError if the command includes a null byte" do
+ -> { 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
+ -> { 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
+ -> { Process.exec __FILE__ }.should raise_error(SystemCallError) { |e|
+ [Errno::EACCES, Errno::ENOEXEC].should include(e.class)
+ }
+ end
+ end
+ end
+
+ it "raises Errno::EACCES when passed a directory" do
+ -> { Process.exec __dir__ }.should raise_error(Errno::EACCES)
+ end
+
+ it "runs the specified command, replacing current process" do
+ ruby_exe('Process.exec "echo hello"; puts "fail"').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})").should == "#{tmpdir}\n"
+ end
+ platform_is :windows do
+ ruby_exe("Process.exec(\"cd\", chdir: #{tmpdir.inspect})").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 *"')
+ 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"').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 *"')
+ 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"').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}").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 + '")').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 + '")').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 + '")').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 + '")').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)').should == "\n"
+ end
+ platform_is :windows do
+ ruby_exe('Process.exec("' + ENV['COMSPEC'].gsub('\\', '\\\\\\') + ' /C echo ' + var + '", unsetenv_others: 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")').should == "argv_zero\n"
+ end
+ platform_is :windows do
+ ruby_exe('Process.exec(["cmd.exe", "/C"], "/C", "echo", "argv_zero")').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")').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")').should == "argv_zero\n"
+ end
+ end
+
+ it "raises an ArgumentError if the Array does not have exactly two elements" do
+ -> { Process.exec([]) }.should raise_error(ArgumentError)
+ -> { Process.exec([:a]) }.should raise_error(ArgumentError)
+ -> { 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.inspect}, "w+")
+ File.open(#{__FILE__.inspect}, "r") do |io|
+ child_fd = io.fileno
+ File.open(#{@child_fd_file.inspect}, "w") { |io| io.print child_fd }
+ Process.exec "#{ruby_cmd(map_fd_fixture)} \#{child_fd}", { child_fd => f }
+ end
+ EOC
+
+ ruby_exe(cmd)
+ 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
+
+ it "lets the process after exec have specified file descriptor despite close_on_exec" do
+ map_fd_fixture = fixture __FILE__, "map_fd.rb"
+ cmd = <<-EOC
+ f = File.open(#{@name.inspect}, 'w+')
+ puts(f.fileno, f.close_on_exec?)
+ STDOUT.flush
+ Process.exec("#{ruby_cmd(map_fd_fixture)} \#{f.fileno}", f.fileno => f.fileno)
+ EOC
+
+ output = ruby_exe(cmd)
+ child_fd, close_on_exec = output.split
+
+ child_fd.to_i.should > STDERR.fileno
+ close_on_exec.should == 'true'
+ File.read(@name).should == "writing to fd: #{child_fd}"
+ end
+
+ it "sets close_on_exec to false on specified fd even when it fails" do
+ cmd = <<-EOC
+ f = File.open(#{__FILE__.inspect}, 'r')
+ puts(f.close_on_exec?)
+ Process.exec('/', f.fileno => f.fileno) rescue nil
+ puts(f.close_on_exec?)
+ EOC
+
+ output = ruby_exe(cmd)
+ output.split.should == ['true', 'false']
+ 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..4f7dc94407
--- /dev/null
+++ b/spec/ruby/core/process/exit_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/process/exit'
+
+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/argv0.rb b/spec/ruby/core/process/fixtures/argv0.rb
new file mode 100644
index 0000000000..847a3e903e
--- /dev/null
+++ b/spec/ruby/core/process/fixtures/argv0.rb
@@ -0,0 +1,6 @@
+puts Process.argv0
+puts __FILE__
+
+if Process.argv0 == __FILE__
+ print "OK"
+end
diff --git a/spec/ruby/core/process/fixtures/clocks.rb b/spec/ruby/core/process/fixtures/clocks.rb
new file mode 100644
index 0000000000..5757e280be
--- /dev/null
+++ b/spec/ruby/core/process/fixtures/clocks.rb
@@ -0,0 +1,18 @@
+module ProcessSpecs
+ def self.clock_constants
+ clocks = []
+
+ platform_is_not :windows do
+ clocks += Process.constants.select { |c| c.to_s.start_with?('CLOCK_') }
+
+ # These require CAP_WAKE_ALARM and are not documented in
+ # Process#clock_gettime. They return EINVAL if the permission
+ # is not granted.
+ clocks -= [:CLOCK_BOOTTIME_ALARM, :CLOCK_REALTIME_ALARM]
+ end
+
+ clocks.sort.map { |c|
+ [c, Process.const_get(c)]
+ }
+ end
+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..f49513d262
--- /dev/null
+++ b/spec/ruby/core/process/fixtures/common.rb
@@ -0,0 +1,88 @@
+module ProcessSpecs
+ def self.use_system_ruby(context)
+ if defined?(MSpecScript::SYSTEM_RUBY)
+ context.send(:before, :all) do
+ @ruby = ::RUBY_EXE
+ suppress_warning {
+ Object.const_set(:RUBY_EXE, MSpecScript::SYSTEM_RUBY)
+ }
+ end
+
+ context.send(:after, :all) do
+ suppress_warning {
+ 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/in.txt b/spec/ruby/core/process/fixtures/in.txt
new file mode 100644
index 0000000000..cf52303bdc
--- /dev/null
+++ b/spec/ruby/core/process/fixtures/in.txt
@@ -0,0 +1 @@
+stdin
diff --git a/spec/ruby/core/process/fixtures/kill.rb b/spec/ruby/core/process/fixtures/kill.rb
new file mode 100644
index 0000000000..b922a043f1
--- /dev/null
+++ b/spec/ruby/core/process/fixtures/kill.rb
@@ -0,0 +1,43 @@
+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..3ed887486b
--- /dev/null
+++ b/spec/ruby/core/process/fixtures/map_fd.rb
@@ -0,0 +1,9 @@
+fd = ARGV.shift.to_i
+
+f = File.for_fd(fd)
+f.autoclose = false
+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..a4f765247d
--- /dev/null
+++ b/spec/ruby/core/process/fork_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/process/fork'
+
+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..c1dd007b16
--- /dev/null
+++ b/spec/ruby/core/process/getpgid_spec.rb
@@ -0,0 +1,17 @@
+require_relative '../../spec_helper'
+
+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..e1d1c5f92d
--- /dev/null
+++ b/spec/ruby/core/process/getpgrp_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+# 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..a35e5956a5
--- /dev/null
+++ b/spec/ruby/core/process/getpriority_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../spec_helper'
+
+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(Integer)
+ end
+
+ it "gets the scheduling priority for a specified process" do
+ Process.getpriority(Process::PRIO_PROCESS, 0).should be_kind_of(Integer)
+ end
+
+ it "gets the scheduling priority for a specified process group" do
+ Process.getpriority(Process::PRIO_PGRP, 0).should be_kind_of(Integer)
+ end
+
+ it "gets the scheduling priority for a specified user" do
+ Process.getpriority(Process::PRIO_USER, 0).should be_kind_of(Integer)
+ 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..883b8fac48
--- /dev/null
+++ b/spec/ruby/core/process/getrlimit_spec.rb
@@ -0,0 +1,100 @@
+require_relative '../../spec_helper'
+
+platform_is :aix do
+ # In AIX, if getrlimit(2) is called multiple times with RLIMIT_DATA,
+ # the first call and the subsequent 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
+
+describe "Process.getrlimit" do
+ platform_is_not :windows 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)
+
+ -> { Process.getrlimit(obj) }.should raise_error(TypeError)
+ end
+ end
+
+ context "when passed a Symbol" do
+ it "coerces the short name into the full RLIMIT_ prefixed name" do
+ Process.constants.grep(/\ARLIMIT_/) do |fullname|
+ short = fullname[/\ARLIMIT_(.+)/, 1]
+ Process.getrlimit(short.to_sym).should == Process.getrlimit(Process.const_get(fullname))
+ end
+ end
+
+ it "raises ArgumentError when passed an unknown resource" do
+ -> { Process.getrlimit(:FOO) }.should raise_error(ArgumentError)
+ end
+ end
+
+ context "when passed a String" do
+ it "coerces the short name into the full RLIMIT_ prefixed name" do
+ Process.constants.grep(/\ARLIMIT_/) do |fullname|
+ short = fullname[/\ARLIMIT_(.+)/, 1]
+ Process.getrlimit(short).should == Process.getrlimit(Process.const_get(fullname))
+ end
+ end
+
+ it "raises ArgumentError when passed an unknown resource" do
+ -> { 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
+
+ platform_is :windows do
+ it "is not implemented" do
+ Process.respond_to?(:getrlimit).should be_false
+ -> do
+ Process.getrlimit(nil)
+ end.should raise_error NotImplementedError
+ 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..4ada277514
--- /dev/null
+++ b/spec/ruby/core/process/gid/change_privilege_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+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..3f2186bb6a
--- /dev/null
+++ b/spec/ruby/core/process/gid/eid_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../../spec_helper'
+
+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..10da7f8a26
--- /dev/null
+++ b/spec/ruby/core/process/gid/grant_privilege_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+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..9642c81c5b
--- /dev/null
+++ b/spec/ruby/core/process/gid/re_exchange_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+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..1c38f903d5
--- /dev/null
+++ b/spec/ruby/core/process/gid/re_exchangeable_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+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..ad66c94e72
--- /dev/null
+++ b/spec/ruby/core/process/gid/rid_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+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..8d86b3e9d2
--- /dev/null
+++ b/spec/ruby/core/process/gid/sid_available_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+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..b162f1e782
--- /dev/null
+++ b/spec/ruby/core/process/gid/switch_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+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..ca935ed520
--- /dev/null
+++ b/spec/ruby/core/process/gid_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../spec_helper'
+
+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..33e0f9d7b3
--- /dev/null
+++ b/spec/ruby/core/process/groups_spec.rb
@@ -0,0 +1,67 @@
+require_relative '../../spec_helper'
+
+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 }
+ # Include the standard `id` command output. On macOS, GNU
+ # coreutils `id` is limited to NGROUPS_MAX groups, because of
+ # the backward compatibility of getgroups(2).
+ (groups |= `/usr/bin/id -G`.scan(/\d+/).map { |i| i.to_i }) rescue nil
+ gid = Process.gid
+
+ expected = (groups.sort - [gid]).uniq.sort
+ actual = (Process.groups - [gid]).uniq.sort
+ actual.should == expected
+ end
+ end
+end
+
+describe "Process.groups=" do
+ platform_is_not :windows, :android do
+ as_superuser do
+ it "sets the list of gids of groups in the supplemental group access list" do
+ groups = Process.groups
+ Process.groups = []
+ Process.groups.should == []
+ Process.groups = groups
+ Process.groups.sort.should == groups.sort
+ end
+ end
+
+ as_user do
+ platform_is :aix do
+ it "sets the list of gids of groups in the supplemental group access list" 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.
+ groups = Process.groups
+ 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
+ -> { Process.groups = supplementary }.should raise_error(Errno::EPERM)
+ end
+ end
+ end
+
+ platform_is_not :aix do
+ it "raises Errno::EPERM" do
+ -> {
+ Process.groups = [0]
+ }.should raise_error(Errno::EPERM)
+ end
+ end
+ end
+ end
+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..ffc7f282b6
--- /dev/null
+++ b/spec/ruby/core/process/initgroups_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../spec_helper'
+
+describe "Process.initgroups" do
+ platform_is_not :windows, :android do
+ as_user 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
+ -> { Process.initgroups(name, gid) }.should raise_error(Errno::EPERM)
+ end
+ 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..af9c9e6deb
--- /dev/null
+++ b/spec/ruby/core/process/kill_spec.rb
@@ -0,0 +1,132 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "Process.kill" do
+ ProcessSpecs.use_system_ruby(self)
+
+ before :each do
+ @pid = Process.pid
+ end
+
+ it "raises an ArgumentError for unknown signals" do
+ -> { Process.kill("FOO", @pid) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError if passed a lowercase signal name" do
+ -> { Process.kill("term", @pid) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError if signal is not an Integer or String" do
+ signal = mock("process kill signal")
+ signal.should_not_receive(:to_int)
+
+ -> { 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)
+ -> {
+ Process.kill("SIGKILL", pid)
+ }.should raise_error(Errno::ESRCH)
+ end
+
+ it "checks for existence and permissions to signal a process, but does not actually signal it, when using signal 0" do
+ Process.kill(0, @pid).should == 1
+ 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 "accepts 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/last_status_spec.rb b/spec/ruby/core/process/last_status_spec.rb
new file mode 100644
index 0000000000..2372f2aae3
--- /dev/null
+++ b/spec/ruby/core/process/last_status_spec.rb
@@ -0,0 +1,18 @@
+require_relative '../../spec_helper'
+
+describe 'Process#last_status' do
+ it 'returns the status of the last executed child process in the current thread' do
+ pid = Process.wait Process.spawn("exit 0")
+ Process.last_status.pid.should == pid
+ end
+
+ it 'returns nil if no child process has been ever executed in the current thread' do
+ Thread.new do
+ Process.last_status.should == nil
+ end.join
+ end
+
+ it 'raises an ArgumentError if any arguments are provided' do
+ -> { Process.last_status(1) }.should raise_error(ArgumentError)
+ 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..2549a7a971
--- /dev/null
+++ b/spec/ruby/core/process/maxgroups_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+
+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(Integer)
+ 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..2d008bc4b7
--- /dev/null
+++ b/spec/ruby/core/process/pid_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+
+describe "Process.pid" do
+ it "returns the process id of this process" do
+ pid = Process.pid
+ pid.should be_kind_of(Integer)
+ 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..47c32a8591
--- /dev/null
+++ b/spec/ruby/core/process/ppid_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+
+describe "Process.ppid" do
+ platform_is_not :windows do
+ it "returns the process id of the parent of this process" do
+ ruby_exe("puts Process.ppid").should == "#{Process.pid}\n"
+ 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..d022f2021c
--- /dev/null
+++ b/spec/ruby/core/process/set_proctitle_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../spec_helper'
+
+# 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..be724e9007
--- /dev/null
+++ b/spec/ruby/core/process/setpgid_spec.rb
@@ -0,0 +1,29 @@
+require_relative '../../spec_helper'
+
+describe "Process.setpgid" do
+ platform_is_not :windows do
+ # Must use fork as setpgid(2) gives EACCESS after execve()
+ 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..800668008d
--- /dev/null
+++ b/spec/ruby/core/process/setpgrp_spec.rb
@@ -0,0 +1,37 @@
+require_relative '../../spec_helper'
+
+# 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..4d60973429
--- /dev/null
+++ b/spec/ruby/core/process/setpriority_spec.rb
@@ -0,0 +1,60 @@
+require_relative '../../spec_helper'
+
+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
+ guard -> {
+ prio = Process.getpriority(Process::PRIO_USER, 0)
+ # The nice value is a value in the range -20 to 19.
+ # This test tries to change the nice value to +-1, so it cannot run if prio == -20 || prio == 19.
+ if -20 < prio && prio < 19
+ begin
+ # Check if we can lower the nice value or not.
+ #
+ # We are not always able to do it even as a root.
+ # Docker container is not always able to do it depending upon the configuration,
+ # which cannot know from the container itself.
+ Process.setpriority(Process::PRIO_USER, 0, prio - 1)
+ Process.setpriority(Process::PRIO_USER, 0, prio)
+ true
+ rescue Errno::EACCES
+ false
+ end
+ end
+ } do
+ it "sets the scheduling priority for a specified user" do
+ prio = Process.getpriority(Process::PRIO_USER, 0)
+ Process.setpriority(Process::PRIO_USER, 0, prio + 1).should == 0
+ Process.getpriority(Process::PRIO_USER, 0).should == (prio + 1)
+ Process.setpriority(Process::PRIO_USER, 0, prio).should == 0
+ end
+ 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..ba8d1e04ca
--- /dev/null
+++ b/spec/ruby/core/process/setrlimit_spec.rb
@@ -0,0 +1,237 @@
+require_relative '../../spec_helper'
+
+describe "Process.setrlimit" do
+ platform_is_not :windows 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)
+
+ -> { 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)
+
+ -> { 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)
+
+ -> { 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 :aix do
+ it "coerces :MEMLOCK into RLIMIT_MEMLOCK" do
+ Process.setrlimit(:MEMLOCK, *Process.getrlimit(Process::RLIMIT_MEMLOCK)).should be_nil
+ end
+ end
+
+ 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
+
+ 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
+
+ guard -> { defined?(Process::RLIMIT_RTTIME) } do
+ 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
+ -> { 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 :aix do
+ it "coerces 'MEMLOCK' into RLIMIT_MEMLOCK" do
+ Process.setrlimit("MEMLOCK", *Process.getrlimit(Process::RLIMIT_MEMLOCK)).should be_nil
+ end
+ end
+
+ 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
+
+ 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
+
+ guard -> { defined?(Process::RLIMIT_RTTIME) } do
+ 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
+ -> { 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
+
+ platform_is :windows do
+ it "is not implemented" do
+ Process.respond_to?(:setrlimit).should be_false
+ -> do
+ Process.setrlimit(nil, nil)
+ end.should raise_error NotImplementedError
+ 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..c83f912066
--- /dev/null
+++ b/spec/ruby/core/process/setsid_spec.rb
@@ -0,0 +1,16 @@
+require_relative '../../spec_helper'
+
+describe "Process.setsid" do
+ platform_is_not :windows do
+ it "establishes this process as a new session and process group leader" do
+ sid = Process.getsid
+
+ out = ruby_exe("p Process.getsid; p Process.setsid; p Process.getsid").lines
+ out[0].should == "#{sid}\n"
+ out[1].should == out[2]
+ out[2].should_not == "#{sid}\n"
+
+ sid.should == 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..283a7f033d
--- /dev/null
+++ b/spec/ruby/core/process/spawn_spec.rb
@@ -0,0 +1,756 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+newline = "\n"
+platform_is :windows do
+ newline = "\r\n"
+end
+
+describe :process_spawn_does_not_close_std_streams, shared: true do
+ it "does not close STDIN" do
+ code = "puts STDIN.read"
+ cmd = "Process.wait Process.spawn(#{ruby_cmd(code).inspect}, #{@options.inspect})"
+ ruby_exe(cmd, args: "< #{fixture(__FILE__, "in.txt")} > #{@name}")
+ File.binread(@name).should == %[stdin#{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: "> #{@name}")
+ File.binread(@name).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> #{@name}")
+ File.binread(@name).should =~ /hello#{newline}/
+ 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
+ -> { Process.wait Process.spawn("echo spawn") }.should output_to_fd("spawn\n")
+ end
+
+ it "returns the process ID of the new process as an Integer" do
+ pid = Process.spawn(*ruby_exe, "-e", "exit")
+ Process.wait pid
+ pid.should be_an_instance_of(Integer)
+ 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
+ -> { Process.wait Process.spawn("echo *") }.should_not output_to_fd("*\n")
+ end
+
+ it "creates an argument array with shell parsing semantics for whitespace" do
+ -> { 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
+ -> { 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
+ -> { 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")
+ -> { Process.wait Process.spawn(o) }.should output_to_fd("foo\n")
+ end
+
+ it "raises an ArgumentError if the command includes a null byte" do
+ -> { Process.spawn "\000" }.should raise_error(ArgumentError)
+ end
+
+ it "raises a TypeError if the argument does not respond to #to_str" do
+ -> { Process.spawn :echo }.should raise_error(TypeError)
+ end
+ end
+
+ describe "with multiple arguments" do
+ it "does not subject the arguments to shell expansion" do
+ -> { 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
+ -> { 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")
+ -> { 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
+ -> { Process.spawn "echo", "\000" }.should raise_error(ArgumentError)
+ end
+
+ it "raises a TypeError if an argument does not respond to #to_str" do
+ -> { 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
+ -> { Process.wait Process.spawn(["/bin/sh", "argv_zero"], "-c", "echo $0") }.should output_to_fd("argv_zero\n")
+ end
+ platform_is :windows do
+ -> { 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
+ -> { 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
+ -> { 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"])
+ -> { 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"])
+ -> { 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")
+ -> { 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")
+ -> { 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
+ -> { Process.spawn([]) }.should raise_error(ArgumentError)
+ -> { Process.spawn([:a]) }.should raise_error(ArgumentError)
+ -> { 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
+ -> { Process.spawn ["\000", "echo"] }.should raise_error(ArgumentError)
+ -> { 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
+ -> { Process.spawn ["echo", :echo] }.should raise_error(TypeError)
+ -> { 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"
+ -> do
+ Process.wait Process.spawn({"FOO" => nil}, ruby_cmd("p ENV['FOO']"))
+ end.should output_to_fd("nil\n")
+ end
+
+ platform_is_not :windows do
+ it "uses the passed env['PATH'] to search the executable" do
+ dir = tmp("spawn_path_dir")
+ mkdir_p dir
+ begin
+ exe = 'process-spawn-executable-in-path'
+ path = "#{dir}/#{exe}"
+ File.write(path, "#!/bin/sh\necho $1")
+ File.chmod(0755, path)
+
+ env = { "PATH" => "#{dir}#{File::PATH_SEPARATOR}#{ENV['PATH']}" }
+ Process.wait Process.spawn(env, exe, 'OK', out: @name)
+ $?.should.success?
+ File.read(@name).should == "OK\n"
+ ensure
+ rm_r dir
+ end
+ end
+ 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
+ -> 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
+ -> 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
+ -> 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
+ -> 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
+ -> 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
+ -> 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 = -> 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 = -> 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)
+ # The process group is not available on all platforms.
+ # See "man proc" - /proc/[pid]/stat - (5) pgrp
+ # In Travis aarch64 environment, the value is 0.
+ #
+ # $ cat /proc/[pid]/stat
+ # 19179 (ruby) S 19160 0 0 ...
+ unless pgid.zero?
+ -> do
+ Process.wait Process.spawn(ruby_cmd("print Process.getpgid(Process.pid)"), pgroup: pgid)
+ end.should output_to_fd(pgid.to_s)
+ else
+ skip "The process group is not available."
+ end
+ end
+
+ it "raises an ArgumentError if given a negative :pgroup option" do
+ -> { Process.spawn("echo", pgroup: -1) }.should raise_error(ArgumentError)
+ end
+
+ it "raises a TypeError if given a symbol as :pgroup option" do
+ -> { Process.spawn("echo", pgroup: :true) }.should raise_error(TypeError)
+ end
+ end
+
+ platform_is :windows do
+ it "raises an ArgumentError if given :pgroup option" do
+ -> { 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
+ -> 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
+ -> 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)
+
+ -> do
+ Process.wait Process.spawn(ruby_cmd("print Dir.pwd"), chdir: dir)
+ end.should output_to_fd(@dir)
+ end
+ end
+
+ # chdir
+
+ platform_is :linux do
+ describe "inside Dir.chdir" do
+ def child_pids(pid)
+ `pgrep -P #{pid}`.lines.map { |child| Integer(child) }
+ end
+
+ it "does not create extra process without chdir" do
+ pid = Process.spawn("sleep 10")
+ begin
+ child_pids(pid).size.should == 0
+ ensure
+ Process.kill("TERM", pid)
+ Process.wait(pid)
+ end
+ end
+
+ it "kills extra chdir processes" do
+ pid = nil
+ Dir.chdir("/") do
+ pid = Process.spawn("sleep 10")
+ end
+
+ children = child_pids(pid)
+ children.size.should <= 1
+
+ Process.kill("TERM", pid)
+ Process.wait(pid)
+
+ if children.size > 0
+ # wait a bit for children to die
+ sleep(1)
+
+ children.each do |child|
+ -> do
+ Process.kill("TERM", child)
+ end.should raise_error(Errno::ESRCH)
+ end
+ end
+ end
+ end
+ end
+
+ # :umask
+
+ it "uses the current umask by default" do
+ -> 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
+ -> do
+ Process.wait Process.spawn(ruby_cmd("print File.umask"), umask: 146)
+ end.should output_to_fd("146")
+ end
+ end
+
+ # redirection
+
+ it 'redirects to the wrapped IO using wrapped_io.to_io if out: wrapped_io' do
+ File.open(@name, 'w') do |file|
+ -> do
+ wrapped_io = mock('wrapped IO')
+ wrapped_io.should_receive(:to_io).and_return(file)
+ Process.wait Process.spawn('echo Hello World', out: wrapped_io)
+ end.should output_to_fd("Hello World\n", file)
+ end
+ end
+
+ it "redirects STDOUT to the given file descriptor if out: Integer" do
+ File.open(@name, 'w') do |file|
+ -> 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|
+ -> 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 descriptor if err: Integer" do
+ File.open(@name, 'w') do |file|
+ -> 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|
+ -> 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|
+ -> 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 descriptor" do
+ File.open(@name, 'w') do |file|
+ -> 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|
+ -> 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
+
+ platform_is_not :windows, :android do
+ it "closes STDERR in the child if :err => :close" do
+ File.open(@name, 'w') do |file|
+ -> do
+ code = "begin; STDOUT.puts 'out'; STDERR.puts 'hello'; rescue => e; puts 'rescued'; end"
+ Process.wait Process.spawn(ruby_cmd(code), :out => file, :err => :close)
+ end.should output_to_fd("out\nrescued\n", file)
+ end
+ end
+ end
+
+ platform_is_not :windows do
+ it "redirects non-default file descriptor to itself" do
+ File.open(@name, 'w') do |file|
+ -> do
+ Process.wait Process.spawn(
+ ruby_cmd("f = IO.new(#{file.fileno}, 'w'); f.print(:bang); f.flush"), file.fileno => file.fileno)
+ end.should output_to_fd("bang", file)
+ end
+ end
+ end
+
+ it "redirects default file descriptor to itself" do
+ -> do
+ Process.wait Process.spawn(
+ ruby_cmd("f = IO.new(#{STDOUT.fileno}, 'w'); f.print(:bang); f.flush"), STDOUT.fileno => STDOUT.fileno)
+ end.should output_to_fd("bang", STDOUT)
+ end
+
+ # :close_others
+
+ platform_is_not :windows do
+ context "defaults :close_others to" do
+ it "false" do
+ IO.pipe do |r, w|
+ w.close_on_exec = false
+ code = "io = IO.new(#{w.fileno}); io.puts('inherited'); io.close"
+ pid = Process.spawn(ruby_cmd(code))
+ w.close
+ Process.wait(pid)
+ r.read.should == "inherited\n"
+ end
+ end
+ end
+
+ context "when passed close_others: true" do
+ before :each do
+ @options = { close_others: true }
+ end
+
+ it "closes file descriptors >= 3 in the child process even if fds are set close_on_exec=false" do
+ touch @name
+ 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
+ r.read(1).should == nil
+ 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
+ @options = { close_others: false }
+ end
+
+ it "closes file descriptors >= 3 in the child process because they are set close_on_exec by default" do
+ touch @name
+ IO.pipe do |r, w|
+ begin
+ pid = Process.spawn(ruby_cmd("while File.exist? '#{@name}'; sleep 0.1; end"), @options)
+ w.close
+ r.read(1).should == nil
+ ensure
+ rm_r @name
+ Process.wait(pid) if pid
+ end
+ end
+ end
+
+ 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
+
+ code = "fd = IO.for_fd(#{w.fileno}); fd.autoclose = false; fd.write 'abc'; fd.close"
+ pid = Process.spawn(ruby_cmd(code), @options)
+ begin
+ w.close
+ r.read.should == 'abc'
+ ensure
+ Process.wait(pid)
+ end
+ end
+ end
+
+ it_should_behave_like :process_spawn_does_not_close_std_streams
+ end
+ end
+
+ # error handling
+
+ it "raises an ArgumentError if passed no command arguments" do
+ -> { Process.spawn }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError if passed env or options but no command arguments" do
+ -> { Process.spawn({}) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError if passed env and options but no command arguments" do
+ -> { Process.spawn({}, {}) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an Errno::ENOENT for an empty string" do
+ -> { Process.spawn "" }.should raise_error(Errno::ENOENT)
+ end
+
+ it "raises an Errno::ENOENT if the command does not exist" do
+ -> { 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
+ -> { 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
+ -> { Process.spawn __FILE__ }.should raise_error(SystemCallError) { |e|
+ [Errno::EACCES, Errno::ENOEXEC].should include(e.class)
+ }
+ end
+ end
+ end
+
+ it "raises an Errno::EACCES or Errno::EISDIR when passed a directory" do
+ -> { Process.spawn __dir__ }.should raise_error(SystemCallError) { |e|
+ [Errno::EACCES, Errno::EISDIR].should include(e.class)
+ }
+ end
+
+ it "raises an ArgumentError when passed a string key in options" do
+ -> { Process.spawn("echo", "chdir" => Dir.pwd) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when passed an unknown option key" do
+ -> { Process.spawn("echo", nonesuch: :foo) }.should raise_error(ArgumentError)
+ end
+
+ platform_is_not :windows, :aix 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
+ File.open(__FILE__, "r") do |f|
+ child_fd = f.fileno
+ 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
+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..a805364629
--- /dev/null
+++ b/spec/ruby/core/process/status/bit_and_spec.rb
@@ -0,0 +1,38 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."4.0" do
+
+ describe "Process::Status#&" do
+ it "returns a bitwise and of the integer status of an exited child" do
+ suppress_warning do
+ ruby_exe("exit(29)", exit_status: 29)
+ ($? & 0).should == 0
+ ($? & $?.to_i).should == $?.to_i
+
+ # Actual value is implementation specific
+ platform_is :linux do
+ # 29 == 0b11101
+ ($? & 0b1011100000000).should == 0b1010100000000
+ end
+ end
+ end
+
+ ruby_version_is "3.3"..."4.0" do
+ it "raises an ArgumentError if mask is negative" do
+ suppress_warning do
+ ruby_exe("exit(0)")
+ -> {
+ $? & -1
+ }.should raise_error(ArgumentError, 'negative mask value: -1')
+ end
+ end
+
+ it "shows a deprecation warning" do
+ ruby_exe("exit(0)")
+ -> {
+ $? & 0
+ }.should complain(/warning: Process::Status#& is deprecated and will be removed .*use other Process::Status predicates instead/)
+ end
+ end
+ end
+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..fbbaf926f7
--- /dev/null
+++ b/spec/ruby/core/process/status/coredump_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+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..d8a2be26b8
--- /dev/null
+++ b/spec/ruby/core/process/status/equal_value_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../../spec_helper'
+
+describe "Process::Status#==" do
+ it "returns true when compared to the integer status of an exited child" do
+ ruby_exe("exit(29)", exit_status: 29)
+ $?.to_i.should == $?
+ $?.should == $?.to_i
+ end
+
+ it "returns true when compared to the integer status of a terminated child" do
+ ruby_exe("Process.kill(:KILL, $$); exit(29)", exit_status: platform_is(:windows) ? 0 : :SIGKILL)
+ $?.to_i.should == $?
+ $?.should == $?.to_i
+ end
+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..a61292b146
--- /dev/null
+++ b/spec/ruby/core/process/status/exited_spec.rb
@@ -0,0 +1,32 @@
+require_relative '../../../spec_helper'
+
+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)", exit_status: platform_is(:windows) ? 0 : :SIGKILL)
+ 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..5c86c2b3c8
--- /dev/null
+++ b/spec/ruby/core/process/status/exitstatus_spec.rb
@@ -0,0 +1,25 @@
+require_relative '../../../spec_helper'
+
+describe "Process::Status#exitstatus" do
+ before :each do
+ ruby_exe("exit(42)", exit_status: 42)
+ end
+
+ it "returns the process exit code" do
+ $?.exitstatus.should == 42
+ end
+
+ describe "for a child that raised SignalException" do
+ before :each do
+ ruby_exe("Process.kill(:KILL, $$); exit(42)", exit_status: platform_is(:windows) ? 0 : :SIGKILL)
+ end
+
+ platform_is_not :windows do
+ # The exitstatus is not set in these cases. See the termsig_spec
+ # for info on where the signal number (SIGTERM) is available.
+ it "returns nil" do
+ $?.exitstatus.should == nil
+ end
+ end
+ 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..03f0479f2c
--- /dev/null
+++ b/spec/ruby/core/process/status/inspect_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+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..9965fc3bdf
--- /dev/null
+++ b/spec/ruby/core/process/status/pid_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../../spec_helper'
+
+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..355aaf4c95
--- /dev/null
+++ b/spec/ruby/core/process/status/right_shift_spec.rb
@@ -0,0 +1,37 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."4.0" do
+
+ describe "Process::Status#>>" do
+ it "returns a right shift of the integer status of an exited child" do
+ suppress_warning do
+ ruby_exe("exit(29)", exit_status: 29)
+ ($? >> 0).should == $?.to_i
+ ($? >> 1).should == $?.to_i >> 1
+
+ # Actual value is implementation specific
+ platform_is :linux do
+ ($? >> 8).should == 29
+ end
+ end
+ end
+
+ ruby_version_is "3.3"..."4.0" do
+ it "raises an ArgumentError if shift value is negative" do
+ suppress_warning do
+ ruby_exe("exit(0)")
+ -> {
+ $? >> -1
+ }.should raise_error(ArgumentError, 'negative shift value: -1')
+ end
+ end
+
+ it "shows a deprecation warning" do
+ ruby_exe("exit(0)")
+ -> {
+ $? >> 0
+ }.should complain(/warning: Process::Status#>> is deprecated and will be removed .*use other Process::Status attributes instead/)
+ end
+ end
+ end
+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..c0de7b8006
--- /dev/null
+++ b/spec/ruby/core/process/status/signaled_spec.rb
@@ -0,0 +1,31 @@
+require_relative '../../../spec_helper'
+
+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)", exit_status: platform_is(:windows) ? 0 : :SIGKILL)
+ 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..bebd441d6f
--- /dev/null
+++ b/spec/ruby/core/process/status/stopped_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+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..b2a7c5d9e2
--- /dev/null
+++ b/spec/ruby/core/process/status/stopsig_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+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..3589cc611f
--- /dev/null
+++ b/spec/ruby/core/process/status/success_spec.rb
@@ -0,0 +1,41 @@
+require_relative '../../../spec_helper'
+
+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)", exit_status: 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)", exit_status: platform_is(:windows) ? 0 : :SIGKILL)
+ 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..9a22dbea71
--- /dev/null
+++ b/spec/ruby/core/process/status/termsig_spec.rb
@@ -0,0 +1,43 @@
+require_relative '../../../spec_helper'
+
+describe "Process::Status#termsig" do
+ describe "for a child that exited normally" do
+ before :each do
+ ruby_exe("exit(0)")
+ end
+
+ it "returns nil" do
+ $?.termsig.should be_nil
+ end
+ end
+
+ describe "for a child that raised SignalException" do
+ before :each do
+ ruby_exe("raise SignalException, 'SIGTERM'", exit_status: :SIGTERM)
+ end
+
+ platform_is_not :windows do
+ it "returns the signal" do
+ $?.termsig.should == Signal.list["TERM"]
+ end
+ end
+ end
+
+ describe "for a child that was sent a signal" do
+ before :each do
+ ruby_exe("Process.kill(:KILL, $$); exit(42)", exit_status: platform_is(:windows) ? 0 : :SIGKILL)
+ 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..39f8e2d84c
--- /dev/null
+++ b/spec/ruby/core/process/status/to_i_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../../spec_helper'
+
+describe "Process::Status#to_i" do
+ it "returns an integer when the child exits" do
+ ruby_exe('exit 48', exit_status: 48)
+ $?.to_i.should be_an_instance_of(Integer)
+ end
+
+ it "returns an integer when the child is signaled" do
+ ruby_exe('raise SignalException, "TERM"', exit_status: platform_is(:windows) ? 3 : :SIGTERM)
+ $?.to_i.should be_an_instance_of(Integer)
+ end
+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..fb596c1bfb
--- /dev/null
+++ b/spec/ruby/core/process/status/to_int_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+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..5c0d4cd30c
--- /dev/null
+++ b/spec/ruby/core/process/status/to_s_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+describe "Process::Status#to_s" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/process/status/wait_spec.rb b/spec/ruby/core/process/status/wait_spec.rb
new file mode 100644
index 0000000000..57d56209a9
--- /dev/null
+++ b/spec/ruby/core/process/status/wait_spec.rb
@@ -0,0 +1,100 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/common'
+
+describe "Process::Status.wait" do
+ ProcessSpecs.use_system_ruby(self)
+
+ before :all do
+ begin
+ leaked = Process.waitall
+ # Ruby-space should not see PIDs used by rjit
+ raise "subprocesses leaked before wait specs: #{leaked}" unless leaked.empty?
+ rescue NotImplementedError
+ end
+ end
+
+ it "returns a status with pid -1 if there are no child processes" do
+ Process::Status.wait.pid.should == -1
+ end
+
+ platform_is_not :windows do
+ it "returns a status with its child pid" do
+ pid = Process.spawn(ruby_cmd('exit'))
+ status = Process::Status.wait
+ status.should be_an_instance_of(Process::Status)
+ status.pid.should == pid
+ end
+
+ it "should not set $? to the Process::Status" do
+ pid = Process.spawn(ruby_cmd('exit'))
+ status = Process::Status.wait
+ $?.should_not equal(status)
+ end
+
+ it "should not change the value of $?" do
+ pid = Process.spawn(ruby_cmd('exit'))
+ Process.wait
+ status = $?
+ Process::Status.wait
+ status.should equal($?)
+ end
+
+ it "waits for any child process if no pid is given" do
+ pid = Process.spawn(ruby_cmd('exit'))
+ Process::Status.wait.pid.should == pid
+ -> { 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::Status.wait(pid2).pid.should == pid2
+ Process::Status.wait(pid1).pid.should == pid1
+ -> { Process.kill(0, pid1) }.should raise_error(Errno::ESRCH)
+ -> { 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::Status.wait(mock_int(pid1)).pid.should == pid1
+ -> { 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::Status.wait(0).pid.should == pid2
+ Process::Status.wait.pid.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::Status.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::Status.wait.pid.should == pid
+ end
+
+ it "always accepts flags=0" do
+ pid = Process.spawn(ruby_cmd('exit'))
+ Process::Status.wait(-1, 0).pid.should == pid
+ -> { Process.kill(0, pid) }.should raise_error(Errno::ESRCH)
+ end
+ end
+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..5b8588f147
--- /dev/null
+++ b/spec/ruby/core/process/sys/getegid_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+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..0ce7fc5459
--- /dev/null
+++ b/spec/ruby/core/process/sys/geteuid_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+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..05c0fd4c0b
--- /dev/null
+++ b/spec/ruby/core/process/sys/getgid_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+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..56bcef01cc
--- /dev/null
+++ b/spec/ruby/core/process/sys/getuid_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+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..4fc7dc3bc6
--- /dev/null
+++ b/spec/ruby/core/process/sys/issetugid_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+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..8f20330b94
--- /dev/null
+++ b/spec/ruby/core/process/sys/setegid_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+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..57d3a4800d
--- /dev/null
+++ b/spec/ruby/core/process/sys/seteuid_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+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..cc712e0102
--- /dev/null
+++ b/spec/ruby/core/process/sys/setgid_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+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..57d491a707
--- /dev/null
+++ b/spec/ruby/core/process/sys/setregid_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+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..9be06612c1
--- /dev/null
+++ b/spec/ruby/core/process/sys/setresgid_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+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..1092b349cb
--- /dev/null
+++ b/spec/ruby/core/process/sys/setresuid_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+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..f3451c63c8
--- /dev/null
+++ b/spec/ruby/core/process/sys/setreuid_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+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..27eea2ed86
--- /dev/null
+++ b/spec/ruby/core/process/sys/setrgid_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+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..9dbd84bf68
--- /dev/null
+++ b/spec/ruby/core/process/sys/setruid_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+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..e06c3588a5
--- /dev/null
+++ b/spec/ruby/core/process/sys/setuid_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+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..d3bff2cda9
--- /dev/null
+++ b/spec/ruby/core/process/times_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+
+describe "Process.times" do
+ it "returns a Process::Tms" do
+ Process.times.should be_kind_of(Process::Tms)
+ end
+
+ # TODO: Intel C Compiler does not work this example
+ # http://rubyci.s3.amazonaws.com/icc-x64/ruby-master/log/20221013T030005Z.fail.html.gz
+ unless RbConfig::CONFIG['CC']&.include?("icx")
+ it "returns current cpu times" do
+ t = Process.times
+ user = t.utime
+
+ 1 until Process.times.utime > user
+ Process.times.utime.should > user
+ end
+ end
+end
diff --git a/spec/ruby/core/process/tms/cstime_spec.rb b/spec/ruby/core/process/tms/cstime_spec.rb
new file mode 100644
index 0000000000..9c2d9e8632
--- /dev/null
+++ b/spec/ruby/core/process/tms/cstime_spec.rb
@@ -0,0 +1,17 @@
+require_relative '../../../spec_helper'
+
+describe "Process::Tms#cstime" do
+ it "returns cstime attribute" do
+ cstime = Object.new
+ Process::Tms.new(nil, nil, nil, cstime).cstime.should == cstime
+ end
+end
+
+describe "Process::Tms#cstime=" do
+ it "assigns a value to the cstime attribute" do
+ cstime = Object.new
+ tms = Process::Tms.new
+ tms.cstime = cstime
+ tms.cstime.should == cstime
+ end
+end
diff --git a/spec/ruby/core/process/tms/cutime_spec.rb b/spec/ruby/core/process/tms/cutime_spec.rb
new file mode 100644
index 0000000000..0ac3ff1964
--- /dev/null
+++ b/spec/ruby/core/process/tms/cutime_spec.rb
@@ -0,0 +1,17 @@
+require_relative '../../../spec_helper'
+
+describe "Process::Tms#cutime" do
+ it "returns cutime attribute" do
+ cutime = Object.new
+ Process::Tms.new(nil, nil, cutime, nil).cutime.should == cutime
+ end
+end
+
+describe "Process::Tms#cutime=" do
+ it "assigns a value to the cutime attribute" do
+ cutime = Object.new
+ tms = Process::Tms.new
+ tms.cutime = cutime
+ tms.cutime.should == cutime
+ end
+end
diff --git a/spec/ruby/core/process/tms/stime_spec.rb b/spec/ruby/core/process/tms/stime_spec.rb
new file mode 100644
index 0000000000..1e8371475f
--- /dev/null
+++ b/spec/ruby/core/process/tms/stime_spec.rb
@@ -0,0 +1,17 @@
+require_relative '../../../spec_helper'
+
+describe "Process::Tms#stime" do
+ it "returns stime attribute" do
+ stime = Object.new
+ Process::Tms.new(nil, stime, nil, nil).stime.should == stime
+ end
+end
+
+describe "Process::Tms#stime=" do
+ it "assigns a value to the stime attribute" do
+ stime = Object.new
+ tms = Process::Tms.new
+ tms.stime = stime
+ tms.stime.should == stime
+ end
+end
diff --git a/spec/ruby/core/process/tms/utime_spec.rb b/spec/ruby/core/process/tms/utime_spec.rb
new file mode 100644
index 0000000000..403a31e2e6
--- /dev/null
+++ b/spec/ruby/core/process/tms/utime_spec.rb
@@ -0,0 +1,17 @@
+require_relative '../../../spec_helper'
+
+describe "Process::Tms#utime" do
+ it "returns utime attribute" do
+ utime = Object.new
+ Process::Tms.new(utime, nil, nil, nil).utime.should == utime
+ end
+end
+
+describe "Process::Tms#utime=" do
+ it "assigns a value to the ctime attribute" do
+ utime = Object.new
+ tms = Process::Tms.new
+ tms.utime = utime
+ tms.utime.should == utime
+ 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..e4b552dd94
--- /dev/null
+++ b/spec/ruby/core/process/uid/change_privilege_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+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..f0bb9ce762
--- /dev/null
+++ b/spec/ruby/core/process/uid/eid_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../../spec_helper'
+
+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..2b8a5c9102
--- /dev/null
+++ b/spec/ruby/core/process/uid/grant_privilege_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+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..c0f10f33c4
--- /dev/null
+++ b/spec/ruby/core/process/uid/re_exchange_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+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..8200d7ecb7
--- /dev/null
+++ b/spec/ruby/core/process/uid/re_exchangeable_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+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..e865cbfef6
--- /dev/null
+++ b/spec/ruby/core/process/uid/rid_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+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..be7912eb68
--- /dev/null
+++ b/spec/ruby/core/process/uid/sid_available_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+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..4191b97db4
--- /dev/null
+++ b/spec/ruby/core/process/uid/switch_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+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..a068b1a2c1
--- /dev/null
+++ b/spec/ruby/core/process/uid_spec.rb
@@ -0,0 +1,57 @@
+require_relative '../../spec_helper'
+
+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
+ -> { 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
+ -> { (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
+ -> { Process.uid = "root" }.should raise_error(Errno::EPERM)
+ end
+ end
+
+ as_superuser do
+ describe "if run by a superuser" do
+ it "sets the real user id for the current process" do
+ code = <<-RUBY
+ Process.uid = 1
+ puts Process.uid
+ RUBY
+ ruby_exe(code).should == "1\n"
+ end
+
+ it "sets the real user id if preceded by Process.euid=id" do
+ code = <<-RUBY
+ Process.euid = 1
+ Process.uid = 1
+ puts Process.uid
+ RUBY
+ ruby_exe(code).should == "1\n"
+ end
+ end
+ end
+ end
+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..8ba429dc96
--- /dev/null
+++ b/spec/ruby/core/process/wait2_spec.rb
@@ -0,0 +1,45 @@
+require_relative '../../spec_helper'
+
+describe "Process.wait2" do
+ before :all do
+ # HACK: this kludge is temporarily necessary because some
+ # misbehaving spec somewhere else does not clear processes
+ # Note: background processes are unavoidable with RJIT,
+ # but we shouldn't reap them from Ruby-space
+ begin
+ Process.wait(-1, Process::WNOHANG)
+ $stderr.puts "Leaked process before wait2 specs! Waiting for it"
+ leaked = Process.waitall
+ $stderr.puts "leaked before wait2 specs: #{leaked}" unless leaked.empty?
+ # Ruby-space should not see PIDs used by rjit
+ leaked.should be_empty
+ rescue Errno::ECHILD # No child processes
+ 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
+ -> { Process.wait2 }.should raise_error(Errno::ECHILD)
+ -> { Process.wait2 }.should raise_error(StandardError)
+ end
+
+ it "returns nil if the child process is still running when given the WNOHANG flag" do
+ IO.popen(ruby_cmd('STDIN.getbyte'), "w") do |io|
+ pid, status = Process.wait2(io.pid, Process::WNOHANG)
+ pid.should be_nil
+ status.should be_nil
+ io.write('a')
+ end
+ 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..385acc9928
--- /dev/null
+++ b/spec/ruby/core/process/wait_spec.rb
@@ -0,0 +1,91 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "Process.wait" do
+ ProcessSpecs.use_system_ruby(self)
+
+ before :all do
+ begin
+ leaked = Process.waitall
+ # Ruby-space should not see PIDs used by rjit
+ raise "subprocesses leaked before wait specs: #{leaked}" unless leaked.empty?
+ rescue NotImplementedError
+ end
+ end
+
+ it "raises an Errno::ECHILD if there are no child processes" do
+ -> { Process.wait }.should raise_error(Errno::ECHILD)
+ end
+
+ platform_is_not :windows do
+ it "returns its child 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
+ -> { 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
+ -> { Process.kill(0, pid1) }.should raise_error(Errno::ESRCH)
+ -> { 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
+ -> { 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
+ -> { 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..6cf4e32bc9
--- /dev/null
+++ b/spec/ruby/core/process/waitall_spec.rb
@@ -0,0 +1,48 @@
+require_relative '../../spec_helper'
+
+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
+ -> { 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|
+ -> { 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..45513af667
--- /dev/null
+++ b/spec/ruby/core/process/waitpid2_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../spec_helper'
+
+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..a02147b663
--- /dev/null
+++ b/spec/ruby/core/process/waitpid_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../spec_helper'
+
+describe "Process.waitpid" do
+ it "returns nil when the process has not yet completed and WNOHANG is specified" do
+ cmd = platform_is(:windows) ? "timeout" : "sleep"
+ pid = spawn("#{cmd} 5")
+ begin
+ Process.waitpid(pid, Process::WNOHANG).should == nil
+ Process.kill("KILL", pid)
+ ensure
+ Process.wait(pid)
+ end
+ end
+end
diff --git a/spec/ruby/core/process/warmup_spec.rb b/spec/ruby/core/process/warmup_spec.rb
new file mode 100644
index 0000000000..b562d52d22
--- /dev/null
+++ b/spec/ruby/core/process/warmup_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+
+describe "Process.warmup" do
+ ruby_version_is "3.3" do
+ # The behavior is entirely implementation specific.
+ # Other implementations are free to just make it a noop
+ it "is implemented" do
+ Process.warmup.should == true
+ end
+ end
+end