diff options
Diffstat (limited to 'spec/ruby/core/process/spawn_spec.rb')
| -rw-r--r-- | spec/ruby/core/process/spawn_spec.rb | 262 |
1 files changed, 158 insertions, 104 deletions
diff --git a/spec/ruby/core/process/spawn_spec.rb b/spec/ruby/core/process/spawn_spec.rb index aa7a7dc3aa..283a7f033d 100644 --- a/spec/ruby/core/process/spawn_spec.rb +++ b/spec/ruby/core/process/spawn_spec.rb @@ -45,13 +45,13 @@ describe "Process.spawn" do end it "executes the given command" do - lambda { Process.wait Process.spawn("echo spawn") }.should output_to_fd("spawn\n") + -> { Process.wait Process.spawn("echo spawn") }.should output_to_fd("spawn\n") end - it "returns the process ID of the new process as a Fixnum" do + 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(Fixnum) + pid.should be_an_instance_of(Integer) end it "returns immediately" do @@ -67,43 +67,43 @@ describe "Process.spawn" do describe "with a single argument" do platform_is_not :windows do it "subjects the specified command to shell expansion" do - lambda { Process.wait Process.spawn("echo *") }.should_not output_to_fd("*\n") + -> { Process.wait Process.spawn("echo *") }.should_not output_to_fd("*\n") end it "creates an argument array with shell parsing semantics for whitespace" do - lambda { Process.wait Process.spawn("echo a b c d") }.should output_to_fd("a b c d\n") + -> { Process.wait Process.spawn("echo a b c d") }.should output_to_fd("a b c d\n") end end platform_is :windows do # There is no shell expansion on Windows it "does not subject the specified command to shell expansion on Windows" do - lambda { Process.wait Process.spawn("echo *") }.should output_to_fd("*\n") + -> { Process.wait Process.spawn("echo *") }.should output_to_fd("*\n") end it "does not create an argument array with shell parsing semantics for whitespace on Windows" do - lambda { Process.wait Process.spawn("echo a b c d") }.should output_to_fd("a b c d\n") + -> { Process.wait Process.spawn("echo a b c d") }.should output_to_fd("a b c d\n") end end it "calls #to_str to convert the argument to a String" do o = mock("to_str") o.should_receive(:to_str).and_return("echo foo") - lambda { Process.wait Process.spawn(o) }.should output_to_fd("foo\n") + -> { Process.wait Process.spawn(o) }.should output_to_fd("foo\n") end it "raises an ArgumentError if the command includes a null byte" do - lambda { Process.spawn "\000" }.should raise_error(ArgumentError) + -> { Process.spawn "\000" }.should raise_error(ArgumentError) end it "raises a TypeError if the argument does not respond to #to_str" do - lambda { Process.spawn :echo }.should raise_error(TypeError) + -> { Process.spawn :echo }.should raise_error(TypeError) end end describe "with multiple arguments" do it "does not subject the arguments to shell expansion" do - lambda { Process.wait Process.spawn("echo", "*") }.should output_to_fd("*\n") + -> { Process.wait Process.spawn("echo", "*") }.should output_to_fd("*\n") end it "preserves whitespace in passed arguments" do @@ -112,36 +112,36 @@ describe "Process.spawn" do # The echo command on Windows takes quotes literally out = "\"a b c d\"\n" end - lambda { Process.wait Process.spawn("echo", "a b c d") }.should output_to_fd(out) + -> { Process.wait Process.spawn("echo", "a b c d") }.should output_to_fd(out) end it "calls #to_str to convert the arguments to Strings" do o = mock("to_str") o.should_receive(:to_str).and_return("foo") - lambda { Process.wait Process.spawn("echo", o) }.should output_to_fd("foo\n") + -> { Process.wait Process.spawn("echo", o) }.should output_to_fd("foo\n") end it "raises an ArgumentError if an argument includes a null byte" do - lambda { Process.spawn "echo", "\000" }.should raise_error(ArgumentError) + -> { Process.spawn "echo", "\000" }.should raise_error(ArgumentError) end it "raises a TypeError if an argument does not respond to #to_str" do - lambda { Process.spawn "echo", :foo }.should raise_error(TypeError) + -> { Process.spawn "echo", :foo }.should raise_error(TypeError) end end describe "with a command array" do it "uses the first element as the command name and the second as the argv[0] value" do platform_is_not :windows do - lambda { Process.wait Process.spawn(["/bin/sh", "argv_zero"], "-c", "echo $0") }.should output_to_fd("argv_zero\n") + -> { Process.wait Process.spawn(["/bin/sh", "argv_zero"], "-c", "echo $0") }.should output_to_fd("argv_zero\n") end platform_is :windows do - lambda { Process.wait Process.spawn(["cmd.exe", "/C"], "/C", "echo", "argv_zero") }.should output_to_fd("argv_zero\n") + -> { Process.wait Process.spawn(["cmd.exe", "/C"], "/C", "echo", "argv_zero") }.should output_to_fd("argv_zero\n") end end it "does not subject the arguments to shell expansion" do - lambda { Process.wait Process.spawn(["echo", "echo"], "*") }.should output_to_fd("*\n") + -> { Process.wait Process.spawn(["echo", "echo"], "*") }.should output_to_fd("*\n") end it "preserves whitespace in passed arguments" do @@ -150,47 +150,47 @@ describe "Process.spawn" do # The echo command on Windows takes quotes literally out = "\"a b c d\"\n" end - lambda { Process.wait Process.spawn(["echo", "echo"], "a b c d") }.should output_to_fd(out) + -> { Process.wait Process.spawn(["echo", "echo"], "a b c d") }.should output_to_fd(out) end it "calls #to_ary to convert the argument to an Array" do o = mock("to_ary") platform_is_not :windows do o.should_receive(:to_ary).and_return(["/bin/sh", "argv_zero"]) - lambda { Process.wait Process.spawn(o, "-c", "echo $0") }.should output_to_fd("argv_zero\n") + -> { Process.wait Process.spawn(o, "-c", "echo $0") }.should output_to_fd("argv_zero\n") end platform_is :windows do o.should_receive(:to_ary).and_return(["cmd.exe", "/C"]) - lambda { Process.wait Process.spawn(o, "/C", "echo", "argv_zero") }.should output_to_fd("argv_zero\n") + -> { Process.wait Process.spawn(o, "/C", "echo", "argv_zero") }.should output_to_fd("argv_zero\n") end end it "calls #to_str to convert the first element to a String" do o = mock("to_str") o.should_receive(:to_str).and_return("echo") - lambda { Process.wait Process.spawn([o, "echo"], "foo") }.should output_to_fd("foo\n") + -> { Process.wait Process.spawn([o, "echo"], "foo") }.should output_to_fd("foo\n") end it "calls #to_str to convert the second element to a String" do o = mock("to_str") o.should_receive(:to_str).and_return("echo") - lambda { Process.wait Process.spawn(["echo", o], "foo") }.should output_to_fd("foo\n") + -> { Process.wait Process.spawn(["echo", o], "foo") }.should output_to_fd("foo\n") end it "raises an ArgumentError if the Array does not have exactly two elements" do - lambda { Process.spawn([]) }.should raise_error(ArgumentError) - lambda { Process.spawn([:a]) }.should raise_error(ArgumentError) - lambda { Process.spawn([:a, :b, :c]) }.should raise_error(ArgumentError) + -> { 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 - lambda { Process.spawn ["\000", "echo"] }.should raise_error(ArgumentError) - lambda { Process.spawn ["echo", "\000"] }.should raise_error(ArgumentError) + -> { 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 - lambda { Process.spawn ["echo", :echo] }.should raise_error(TypeError) - lambda { Process.spawn [:echo, "echo"] }.should raise_error(TypeError) + -> { Process.spawn ["echo", :echo] }.should raise_error(TypeError) + -> { Process.spawn [:echo, "echo"] }.should raise_error(TypeError) end end @@ -207,13 +207,29 @@ describe "Process.spawn" do it "unsets environment variables whose value is nil" do ENV["FOO"] = "BAR" - Process.wait Process.spawn({"FOO" => nil}, "echo #{@var}>#{@name}") - expected = "\n" - platform_is :windows do - # Windows does not expand the variable if it is unset - expected = "#{@var}\n" + -> 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 - File.read(@name).should == expected end it "calls #to_hash to convert the environment" do @@ -238,19 +254,19 @@ describe "Process.spawn" do end it "raises an ArgumentError if an environment key includes an equals sign" do - lambda 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 - lambda 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 - lambda do + -> do Process.spawn({"FOO" => "\000"}, "echo #{@var}>#{@name}") end.should raise_error(ArgumentError) end @@ -294,25 +310,25 @@ describe "Process.spawn" do platform_is_not :windows do it "joins the current process group by default" do - lambda 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 - lambda 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 - lambda 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 = lambda do + process = -> do Process.wait Process.spawn(ruby_cmd("print Process.getpgid(Process.pid)"), pgroup: true) end @@ -321,7 +337,7 @@ describe "Process.spawn" do end it "joins a new process group if pgroup: 0" do - process = lambda do + process = -> do Process.wait Process.spawn(ruby_cmd("print Process.getpgid(Process.pid)"), pgroup: 0) end @@ -331,23 +347,33 @@ describe "Process.spawn" do it "joins the specified process group if pgroup: pgid" do pgid = Process.getpgid(Process.pid) - lambda do - Process.wait Process.spawn(ruby_cmd("print Process.getpgid(Process.pid)"), pgroup: pgid) - end.should output_to_fd(pgid.to_s) + # 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 - lambda { Process.spawn("echo", pgroup: -1) }.should raise_error(ArgumentError) + -> { Process.spawn("echo", pgroup: -1) }.should raise_error(ArgumentError) end it "raises a TypeError if given a symbol as :pgroup option" do - lambda { Process.spawn("echo", pgroup: :true) }.should raise_error(TypeError) + -> { Process.spawn("echo", pgroup: :true) }.should raise_error(TypeError) end end platform_is :windows do it "raises an ArgumentError if given :pgroup option" do - lambda { Process.spawn("echo", pgroup: false) }.should raise_error(ArgumentError) + -> { Process.spawn("echo", pgroup: false) }.should raise_error(ArgumentError) end end @@ -358,7 +384,7 @@ describe "Process.spawn" do # :chdir it "uses the current working directory as its working directory" do - lambda do + -> do Process.wait Process.spawn(ruby_cmd("print Dir.pwd")) end.should output_to_fd(Dir.pwd) end @@ -374,7 +400,7 @@ describe "Process.spawn" do end it "changes to the directory passed for :chdir" do - lambda do + -> do Process.wait Process.spawn(ruby_cmd("print Dir.pwd"), chdir: @dir) end.should output_to_fd(@dir) end @@ -383,7 +409,7 @@ describe "Process.spawn" do dir = mock("spawn_to_path") dir.should_receive(:to_path).and_return(@dir) - lambda do + -> do Process.wait Process.spawn(ruby_cmd("print Dir.pwd"), chdir: dir) end.should output_to_fd(@dir) end @@ -409,7 +435,7 @@ describe "Process.spawn" do it "kills extra chdir processes" do pid = nil - Dir.chdir("/tmp") do + Dir.chdir("/") do pid = Process.spawn("sleep 10") end @@ -424,7 +450,7 @@ describe "Process.spawn" do sleep(1) children.each do |child| - lambda do + -> do Process.kill("TERM", child) end.should raise_error(Errno::ESRCH) end @@ -436,14 +462,14 @@ describe "Process.spawn" do # :umask it "uses the current umask by default" do - lambda 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 - lambda do + -> do Process.wait Process.spawn(ruby_cmd("print File.umask"), umask: 146) end.should output_to_fd("146") end @@ -451,9 +477,19 @@ describe "Process.spawn" do # redirection - it "redirects STDOUT to the given file descriptor if out: Fixnum" do + it 'redirects to the wrapped IO using wrapped_io.to_io if out: wrapped_io' do File.open(@name, 'w') do |file| - lambda do + -> 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 @@ -461,7 +497,7 @@ describe "Process.spawn" do it "redirects STDOUT to the given file if out: IO" do File.open(@name, 'w') do |file| - lambda do + -> do Process.wait Process.spawn("echo glark", out: file) end.should output_to_fd("glark\n", file) end @@ -477,9 +513,9 @@ describe "Process.spawn" do File.read(@name).should == "glark\n" end - it "redirects STDERR to the given file descriptor if err: Fixnum" do + it "redirects STDERR to the given file descriptor if err: Integer" do File.open(@name, 'w') do |file| - lambda do + -> do Process.wait Process.spawn("echo glark>&2", err: file.fileno) end.should output_to_fd("glark\n", file) end @@ -487,7 +523,7 @@ describe "Process.spawn" do it "redirects STDERR to the given file descriptor if err: IO" do File.open(@name, 'w') do |file| - lambda do + -> do Process.wait Process.spawn("echo glark>&2", err: file) end.should output_to_fd("glark\n", file) end @@ -500,7 +536,7 @@ describe "Process.spawn" do it "redirects STDERR to child STDOUT if :err => [:child, :out]" do File.open(@name, 'w') do |file| - lambda do + -> do Process.wait Process.spawn("echo glark>&2", :out => file, :err => [:child, :out]) end.should output_to_fd("glark\n", file) end @@ -508,7 +544,7 @@ describe "Process.spawn" do it "redirects both STDERR and STDOUT to the given file descriptor" do File.open(@name, 'w') do |file| - lambda do + -> 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) @@ -517,7 +553,7 @@ describe "Process.spawn" do it "redirects both STDERR and STDOUT to the given IO" do File.open(@name, 'w') do |file| - lambda do + -> do Process.wait Process.spawn(ruby_cmd("print(:glark); STDOUT.flush; STDERR.print(:bang)"), [:out, :err] => file) end.should output_to_fd("glarkbang", file) @@ -530,31 +566,47 @@ describe "Process.spawn" do File.read(@name).should == "glarkbang" end - # :close_others + 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 - context "defaults :close_others to" do - ruby_version_is ""..."2.6" do - it "true" do - IO.pipe do |r, w| - w.close_on_exec = false - code = "begin; IO.new(#{w.fileno}).close; rescue Errno::EBADF; puts 'not inherited'; end" - Process.wait Process.spawn(ruby_cmd(code), :out => @name) - File.read(@name).should == "not inherited\n" - end - end + 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 - ruby_version_is "2.6" 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 + 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 @@ -626,35 +678,35 @@ describe "Process.spawn" do # error handling it "raises an ArgumentError if passed no command arguments" do - lambda { Process.spawn }.should raise_error(ArgumentError) + -> { Process.spawn }.should raise_error(ArgumentError) end it "raises an ArgumentError if passed env or options but no command arguments" do - lambda { Process.spawn({}) }.should raise_error(ArgumentError) + -> { Process.spawn({}) }.should raise_error(ArgumentError) end it "raises an ArgumentError if passed env and options but no command arguments" do - lambda { Process.spawn({}, {}) }.should raise_error(ArgumentError) + -> { Process.spawn({}, {}) }.should raise_error(ArgumentError) end it "raises an Errno::ENOENT for an empty string" do - lambda { Process.spawn "" }.should raise_error(Errno::ENOENT) + -> { Process.spawn "" }.should raise_error(Errno::ENOENT) end it "raises an Errno::ENOENT if the command does not exist" do - lambda { Process.spawn "nonesuch" }.should raise_error(Errno::ENOENT) + -> { Process.spawn "nonesuch" }.should raise_error(Errno::ENOENT) end unless File.executable?(__FILE__) # Some FS (e.g. vboxfs) locate all files executable platform_is_not :windows do it "raises an Errno::EACCES when the file does not have execute permissions" do - lambda { Process.spawn __FILE__ }.should raise_error(Errno::EACCES) + -> { Process.spawn __FILE__ }.should raise_error(Errno::EACCES) end end platform_is :windows do it "raises Errno::EACCES or Errno::ENOEXEC when the file is not an executable file" do - lambda { Process.spawn __FILE__ }.should raise_error(SystemCallError) { |e| + -> { Process.spawn __FILE__ }.should raise_error(SystemCallError) { |e| [Errno::EACCES, Errno::ENOEXEC].should include(e.class) } end @@ -662,17 +714,17 @@ describe "Process.spawn" do end it "raises an Errno::EACCES or Errno::EISDIR when passed a directory" do - lambda { Process.spawn File.dirname(__FILE__) }.should raise_error(SystemCallError) { |e| + -> { 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 - lambda { Process.spawn("echo", "chdir" => Dir.pwd) }.should raise_error(ArgumentError) + -> { Process.spawn("echo", "chdir" => Dir.pwd) }.should raise_error(ArgumentError) end it "raises an ArgumentError when passed an unknown option key" do - lambda { Process.spawn("echo", nonesuch: :foo) }.should raise_error(ArgumentError) + -> { Process.spawn("echo", nonesuch: :foo) }.should raise_error(ArgumentError) end platform_is_not :windows, :aix do @@ -689,13 +741,15 @@ describe "Process.spawn" do end it "maps the key to a file descriptor in the child that inherits the file descriptor from the parent specified by the value" do - child_fd = find_unused_fd - 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}" + 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 |
