[DRE-commits] [ruby-posix-spawn] 01/04: Imported Upstream version 0.3.9

Youhei SASAKI uwabami-guest at moszumanska.debian.org
Wed Oct 1 06:15:32 UTC 2014


This is an automated email from the git hooks/post-receive script.

uwabami-guest pushed a commit to annotated tag debian/0.3.9-1
in repository ruby-posix-spawn.

commit 3aa2a350c51eb600744142c3338f3728d8ab71b2
Author: Youhei SASAKI <uwabami at gfd-dennou.org>
Date:   Wed Oct 1 15:01:03 2014 +0900

    Imported Upstream version 0.3.9
---
 .gitignore                 |   1 +
 Gemfile                    |   2 +-
 README.md                  |  40 +++++++++++++++++++++++++---
 checksums.yaml.gz          | Bin 267 -> 269 bytes
 ext/posix-spawn.c          |  50 +++++++++++++++++++++++++---------
 lib/posix/spawn.rb         |  38 +++++++++++++++++++-------
 lib/posix/spawn/child.rb   |  65 ++++++++++++++++++++++++++++++++++++---------
 lib/posix/spawn/version.rb |   2 +-
 metadata.yml               |  15 ++++++-----
 posix-spawn.gemspec        |   1 +
 test/test_backtick.rb      |   3 ++-
 test/test_child.rb         |  43 ++++++++++++++++++++++++++++++
 test/test_spawn.rb         |  26 +++++++++++-------
 13 files changed, 228 insertions(+), 58 deletions(-)

diff --git a/.gitignore b/.gitignore
index e7b4dd5..4633a7f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,4 @@ ext/Makefile
 lib/posix_spawn_ext.*
 tmp
 pkg
+.bundle
diff --git a/Gemfile b/Gemfile
index e45e65f..851fabc 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,2 +1,2 @@
-source :rubygems
+source 'https://rubygems.org'
 gemspec
diff --git a/README.md b/README.md
index a0a9495..cabf1ac 100644
--- a/README.md
+++ b/README.md
@@ -156,9 +156,34 @@ after spawning:
     >> child.out
     "42\n"
 
-Additional options can be used to specify the maximum output size and time of
-execution before the child process is aborted. See the `POSIX::Spawn::Child`
-docs for more info.
+Additional options can be used to specify the maximum output size (`:max`) and
+time of execution (`:timeout`) before the child process is aborted. See the
+`POSIX::Spawn::Child` docs for more info.
+
+#### Reading Partial Results
+
+`POSIX::Spawn::Child.new` spawns the process immediately when instantiated.
+As a result, if it is interrupted by an exception (either from reaching the
+maximum output size, the time limit, or another factor), it is not possible to
+access the `out` or `err` results because the constructor did not complete.
+
+If you want to get the `out` and `err` data was available when the process
+was interrupted, use the `POSIX::Spawn::Child.build` alternate form to
+create the child without immediately spawning the process.  Call `exec!`
+to run the command at a place where you can catch any exceptions:
+
+    >> child = POSIX::Spawn::Child.build('git', 'log', :max => 100)
+    >> begin
+    ?>   child.exec!
+    ?> rescue POSIX::Spawn::MaximumOutputExceeded
+    ?>   # limit was reached
+    ?> end
+    >> child.out
+    "commit fa54abe139fd045bf6dc1cc259c0f4c06a9285bb\n..."
+
+Please note that when the `MaximumOutputExceeded` exception is raised, the
+actual combined `out` and `err` data may be a bit longer than the `:max`
+value due to internal buffering.
 
 ## STATUS
 
@@ -180,7 +205,7 @@ These `Process::spawn` arguments are currently supported to any of
         :unsetenv_others => true   : clear environment variables except specified by env
         :unsetenv_others => false  : don't clear (default)
       current directory:
-        :chdir => str
+        :chdir => str : Not thread-safe when using posix_spawn (see below)
       process group:
         :pgroup => true or 0 : make a new process group
         :pgroup => pgid      : join to specified process group
@@ -218,6 +243,13 @@ These options are currently NOT supported:
         :close_others => false : inherit fds (default for system and exec)
         :close_others => true  : don't inherit (default for spawn and IO.popen)
 
+The `:chdir` option provided by Posix::Spawn::Child, Posix::Spawn#spawn,
+Posix::Spawn#system and Posix::Spawn#popen4 is not thread-safe because
+processes spawned with the posix_spawn(2) system call inherit the working
+directory of the calling process. The posix-spawn gem works around this
+limitation in the system call by changing the working directory of the calling
+process immediately before and after spawning the child process.
+
 ## ACKNOWLEDGEMENTS
 
 Copyright (c) by
diff --git a/checksums.yaml.gz b/checksums.yaml.gz
index 97dc27c..cb2abe4 100644
Binary files a/checksums.yaml.gz and b/checksums.yaml.gz differ
diff --git a/ext/posix-spawn.c b/ext/posix-spawn.c
index 501423d..b6f6fe2 100644
--- a/ext/posix-spawn.c
+++ b/ext/posix-spawn.c
@@ -1,6 +1,6 @@
 /* we want GNU extensions like POSIX_SPAWN_USEVFORK */
 #ifndef _GNU_SOURCE
-#define _GNU_SOURCE
+#define _GNU_SOURCE 1
 #endif
 
 #include <errno.h>
@@ -110,6 +110,13 @@ posixspawn_file_actions_addclose(VALUE key, VALUE val, posix_spawn_file_actions_
 
 	fd  = posixspawn_obj_to_fd(key);
 	if (fd >= 0) {
+		/* raise an exception if 'fd' is invalid */
+		if (fcntl(fd, F_GETFD) == -1) {
+			char error_context[32];
+			snprintf(error_context, sizeof(error_context), "when closing fd %d", fd);
+			rb_sys_fail(error_context);
+			return ST_DELETE;
+		}
 		posix_spawn_file_actions_addclose(fops, fd);
 		return ST_DELETE;
 	} else {
@@ -138,6 +145,8 @@ posixspawn_file_actions_adddup2(VALUE key, VALUE val, posix_spawn_file_actions_t
 	if (fd < 0)
 		return ST_CONTINUE;
 
+	fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) & ~FD_CLOEXEC);
+	fcntl(newfd, F_SETFD, fcntl(newfd, F_GETFD) & ~FD_CLOEXEC);
 	posix_spawn_file_actions_adddup2(fops, fd, newfd);
 	return ST_DELETE;
 }
@@ -319,7 +328,7 @@ each_env_i(VALUE key, VALUE val, VALUE arg)
 static VALUE
 rb_posixspawn_pspawn(VALUE self, VALUE env, VALUE argv, VALUE options)
 {
-	int i, ret;
+	int i, ret = 0;
 	char **envp = NULL;
 	VALUE dirname;
 	VALUE cmdname;
@@ -396,9 +405,14 @@ rb_posixspawn_pspawn(VALUE self, VALUE env, VALUE argv, VALUE options)
 	sigemptyset(&mask);
 	posix_spawnattr_setsigmask(&attr, &mask);
 
-#if defined(POSIX_SPAWN_USEVFORK) || defined(__linux__)
-	/* Force USEVFORK on linux. If this is undefined, it's probably because
-	 * you forgot to define _GNU_SOURCE at the top of this file.
+	/* Child reverts SIGPIPE handler to the default. */
+	flags |= POSIX_SPAWN_SETSIGDEF;
+	sigaddset(&mask, SIGPIPE);
+	posix_spawnattr_setsigdefault(&attr, &mask);
+
+#if defined(POSIX_SPAWN_USEVFORK) || defined(__GLIBC__)
+	/* Force USEVFORK on GNU libc. If this is undefined, it's probably
+	 * because you forgot to define _GNU_SOURCE at the top of this file.
 	 */
 	flags |= POSIX_SPAWN_USEVFORK;
 #endif
@@ -411,19 +425,29 @@ rb_posixspawn_pspawn(VALUE self, VALUE env, VALUE argv, VALUE options)
 	if (RTEST(dirname = rb_hash_delete(options, ID2SYM(rb_intern("chdir"))))) {
 		char *new_cwd = StringValuePtr(dirname);
 		cwd = getcwd(NULL, 0);
-		chdir(new_cwd);
+		if (chdir(new_cwd) == -1) {
+			free(cwd);
+			cwd = NULL;
+			ret = errno;
+		}
 	}
 
-	if (RHASH_SIZE(options) == 0) {
-		ret = posix_spawnp(&pid, file, &fops, &attr, cargv, envp ? envp : environ);
-		if (cwd) {
-			chdir(cwd);
-			free(cwd);
+	if (ret == 0) {
+		if (RHASH_SIZE(options) == 0) {
+			ret = posix_spawnp(&pid, file, &fops, &attr, cargv, envp ? envp : environ);
+			if (cwd) {
+				/* Ignore chdir failures here.  There's already a child running, so
+				 * raising an exception here would do more harm than good. */
+				if (chdir(cwd) == -1) {}
+			}
+		} else {
+			ret = -1;
 		}
-	} else {
-		ret = -1;
 	}
 
+	if (cwd)
+		free(cwd);
+
 	posix_spawn_file_actions_destroy(&fops);
 	posix_spawnattr_destroy(&attr);
 	if (envp) {
diff --git a/lib/posix/spawn.rb b/lib/posix/spawn.rb
index ec19989..dfe0b51 100644
--- a/lib/posix/spawn.rb
+++ b/lib/posix/spawn.rb
@@ -79,7 +79,9 @@ module POSIX
   # When a hash is given in the last argument (options), it specifies a
   # current directory and zero or more fd redirects for the child process.
   #
-  # The :chdir option specifies the current directory:
+  # The :chdir option specifies the current directory. Note that :chdir is not
+  # thread-safe on systems that provide posix_spawn(2), because it forces a
+  # temporary change of the working directory of the calling process.
   #
   #     spawn(command, :chdir => "/var/tmp")
   #
@@ -210,6 +212,8 @@ module POSIX
               if fd?(val)
                 val = fd_to_io(val)
                 key.reopen(val)
+                key.close_on_exec = false
+                val.close_on_exec = false
               elsif val == :close
                 if key.respond_to?(:close_on_exec=)
                   key.close_on_exec = true
@@ -236,7 +240,7 @@ module POSIX
           Process::setpgid(0, pgroup) if pgroup
 
           # do the deed
-          ::Kernel::exec(*argv)
+          ::Kernel::exec(*argv, :close_others=>false)
         ensure
           exit!(127)
         end
@@ -267,12 +271,7 @@ module POSIX
     # Returns the String output of the command.
     def `(cmd)
       r, w = IO.pipe
-      if RUBY_PLATFORM =~ /(mswin|mingw|cygwin|bccwin)/
-        sh = ENV['COMSPEC'] || 'cmd.exe'
-        pid = spawn([sh, sh], '/c', cmd, :out => w, r => :close)
-      else
-        pid = spawn(['/bin/sh', '/bin/sh'], '-c', cmd, :out => w, r => :close)
-      end
+      pid = spawn(*system_command_prefixes, cmd, :out => w, r => :close)
 
       if pid > 0
         w.close
@@ -488,6 +487,27 @@ module POSIX
       end
     end
 
+    # Derives the shell command to use when running the spawn.
+    #
+    # On a Windows machine, this will yield:
+    #   [['cmd.exe', 'cmd.exe'], '/c']
+    # Note: 'cmd.exe' is used if the COMSPEC environment variable
+    #   is not specified. If you would like to use something other
+    #   than 'cmd.exe', specify its path in ENV['COMSPEC']
+    #
+    # On all other systems, this will yield:
+    #   [['/bin/sh', '/bin/sh'], '-c']
+    #
+    # Returns a platform-specific [[<shell>, <shell>], <command-switch>] array.
+    def system_command_prefixes
+      if RUBY_PLATFORM =~ /(mswin|mingw|cygwin|bccwin)/
+        sh = ENV['COMSPEC'] || 'cmd.exe'
+        [[sh, sh], '/c']
+      else
+        [['/bin/sh', '/bin/sh'], '-c']
+      end
+    end
+
     # Converts the various supported command argument variations into a
     # standard argv suitable for use with exec. This includes detecting commands
     # to be run through the shell (single argument strings with spaces).
@@ -503,7 +523,7 @@ module POSIX
     def adjust_process_spawn_argv(args)
       if args.size == 1 && args[0] =~ /[ |>]/
         # single string with these characters means run it through the shell
-        [['/bin/sh', '/bin/sh'], '-c', args[0]]
+        [*system_command_prefixes, args[0]]
       elsif !args[0].respond_to?(:to_ary)
         # [argv0, argv1, ...]
         [[args[0], args[0]], *args[1..-1]]
diff --git a/lib/posix/spawn/child.rb b/lib/posix/spawn/child.rb
index 54fa78c..f310a02 100644
--- a/lib/posix/spawn/child.rb
+++ b/lib/posix/spawn/child.rb
@@ -30,6 +30,17 @@ module POSIX
     #   >> child.out
     #   "42\n"
     #
+    # To access output from the process even if an exception was raised:
+    #
+    #   >> child = POSIX::Spawn::Child.build('git', 'log', :max => 1000)
+    #   >> begin
+    #   ?>   child.exec!
+    #   ?> rescue POSIX::Spawn::MaximumOutputExceeded
+    #   ?>   # just so you know
+    #   ?> end
+    #   >> child.out
+    #   "... first 1000 characters of log output ..."
+    #
     # Q: Why use POSIX::Spawn::Child instead of popen3, hand rolled fork/exec
     # code, or Process::spawn?
     #
@@ -77,7 +88,32 @@ module POSIX
         @timeout = @options.delete(:timeout)
         @max = @options.delete(:max)
         @options.delete(:chdir) if @options[:chdir].nil?
-        exec!
+        exec! if !@options.delete(:noexec)
+      end
+
+      # Set up a new process to spawn, but do not actually spawn it.
+      #
+      # Invoke this just like the normal constructor to set up a process
+      # to be run.  Call `exec!` to actually run the child process, send
+      # the input, read the output, and wait for completion.  Use this
+      # alternative way of constructing a POSIX::Spawn::Child if you want
+      # to read any partial output from the child process even after an
+      # exception.
+      #
+      #   child = POSIX::Spawn::Child.build(... arguments ...)
+      #   child.exec!
+      #
+      # The arguments are the same as the regular constructor.
+      #
+      # Returns a new Child instance but does not run the underlying process.
+      def self.build(*args)
+        options =
+          if args[-1].respond_to?(:to_hash)
+            args.pop.to_hash
+          else
+            {}
+          end
+        new(*args, { :noexec => true }.merge(options))
       end
 
       # All data written to the child process's stdout stream as a String.
@@ -97,15 +133,15 @@ module POSIX
         @status && @status.success?
       end
 
-    private
       # Execute command, write input, and read output. This is called
-      # immediately when a new instance of this object is initialized.
+      # immediately when a new instance of this object is created, or
+      # can be called explicitly when creating the Child via `build`.
       def exec!
         # spawn the process and hook up the pipes
         pid, stdin, stdout, stderr = popen4(@env, *(@argv + [@options]))
 
         # async read from all streams into buffers
-        @out, @err = read_and_write(@input, stdin, stdout, stderr, @timeout, @max)
+        read_and_write(@input, stdin, stdout, stderr, @timeout, @max)
 
         # grab exit status
         @status = waitpid(pid)
@@ -121,6 +157,7 @@ module POSIX
         [stdin, stdout, stderr].each { |fd| fd.close rescue nil }
       end
 
+    private
       # Maximum buffer size for reading
       BUFSIZE = (32 * 1024)
 
@@ -142,16 +179,16 @@ module POSIX
       #   exceeds the amount specified by the max argument.
       def read_and_write(input, stdin, stdout, stderr, timeout=nil, max=nil)
         max = nil if max && max <= 0
-        out, err = '', ''
+        @out, @err = '', ''
         offset = 0
 
         # force all string and IO encodings to BINARY under 1.9 for now
-        if out.respond_to?(:force_encoding) and stdin.respond_to?(:set_encoding)
+        if @out.respond_to?(:force_encoding) and stdin.respond_to?(:set_encoding)
           [stdin, stdout, stderr].each do |fd|
             fd.set_encoding('BINARY', 'BINARY')
           end
-          out.force_encoding('BINARY')
-          err.force_encoding('BINARY')
+          @out.force_encoding('BINARY')
+          @err.force_encoding('BINARY')
           input = input.dup.force_encoding('BINARY') if input
         end
 
@@ -167,7 +204,9 @@ module POSIX
             stdin.close
             []
           end
+        slice_method = input.respond_to?(:byteslice) ? :byteslice : :slice
         t = timeout
+
         while readers.any? || writers.any?
           ready = IO.select(readers, writers, readers + writers, t)
           raise TimeoutExceeded if ready.nil?
@@ -177,11 +216,11 @@ module POSIX
             begin
               boom = nil
               size = fd.write_nonblock(input)
-              input = input[size, input.size]
+              input = input.send(slice_method, size..-1)
             rescue Errno::EPIPE => boom
             rescue Errno::EAGAIN, Errno::EINTR
             end
-            if boom || input.size == 0
+            if boom || input.bytesize == 0
               stdin.close
               writers.delete(stdin)
             end
@@ -189,7 +228,7 @@ module POSIX
 
           # read from stdout and stderr streams
           ready[0].each do |fd|
-            buf = (fd == stdout) ? out : err
+            buf = (fd == stdout) ? @out : @err
             begin
               buf << fd.readpartial(BUFSIZE)
             rescue Errno::EAGAIN, Errno::EINTR
@@ -207,12 +246,12 @@ module POSIX
           end
 
           # maybe we've hit our max output
-          if max && ready[0].any? && (out.size + err.size) > max
+          if max && ready[0].any? && (@out.size + @err.size) > max
             raise MaximumOutputExceeded
           end
         end
 
-        [out, err]
+        [@out, @err]
       end
 
       # Wait for the child process to exit
diff --git a/lib/posix/spawn/version.rb b/lib/posix/spawn/version.rb
index c93fb55..c477be8 100644
--- a/lib/posix/spawn/version.rb
+++ b/lib/posix/spawn/version.rb
@@ -1,5 +1,5 @@
 module POSIX
   module Spawn
-    VERSION = '0.3.8'
+    VERSION = '0.3.9'
   end
 end
diff --git a/metadata.yml b/metadata.yml
index 829ae26..110f18e 100644
--- a/metadata.yml
+++ b/metadata.yml
@@ -1,7 +1,7 @@
 --- !ruby/object:Gem::Specification
 name: posix-spawn
 version: !ruby/object:Gem::Version
-  version: 0.3.8
+  version: 0.3.9
 platform: ruby
 authors:
 - Ryan Tomayko
@@ -9,7 +9,7 @@ authors:
 autorequire: 
 bindir: bin
 cert_chain: []
-date: 2013-12-06 00:00:00.000000000 Z
+date: 2014-08-01 00:00:00.000000000 Z
 dependencies:
 - !ruby/object:Gem::Dependency
   name: rake-compiler
@@ -37,7 +37,7 @@ extra_rdoc_files:
 - COPYING
 - HACKING
 files:
-- .gitignore
+- ".gitignore"
 - COPYING
 - Gemfile
 - HACKING
@@ -58,7 +58,8 @@ files:
 - test/test_spawn.rb
 - test/test_system.rb
 homepage: http://github.com/rtomayko/posix-spawn
-licenses: []
+licenses:
+- MIT
 metadata: {}
 post_install_message: 
 rdoc_options: []
@@ -66,17 +67,17 @@ require_paths:
 - lib
 required_ruby_version: !ruby/object:Gem::Requirement
   requirements:
-  - - '>='
+  - - ">="
     - !ruby/object:Gem::Version
       version: '0'
 required_rubygems_version: !ruby/object:Gem::Requirement
   requirements:
-  - - '>='
+  - - ">="
     - !ruby/object:Gem::Version
       version: '0'
 requirements: []
 rubyforge_project: 
-rubygems_version: 2.0.3
+rubygems_version: 2.2.2
 signing_key: 
 specification_version: 4
 summary: posix_spawnp(2) for ruby
diff --git a/posix-spawn.gemspec b/posix-spawn.gemspec
index c42433f..f2e4fbe 100644
--- a/posix-spawn.gemspec
+++ b/posix-spawn.gemspec
@@ -11,6 +11,7 @@ Gem::Specification.new do |s|
 
   s.authors = ['Ryan Tomayko', 'Aman Gupta']
   s.email = ['r at tomayko.com', 'aman at tmm1.net']
+  s.license = 'MIT'
 
   s.add_development_dependency 'rake-compiler', '0.7.6'
 
diff --git a/test/test_backtick.rb b/test/test_backtick.rb
index 93e9559..708b73d 100644
--- a/test/test_backtick.rb
+++ b/test/test_backtick.rb
@@ -24,7 +24,8 @@ class BacktickTest < Test::Unit::TestCase
 
   def test_backtick_redirect
     out = `nosuchcmd 2>&1`
-    assert_equal "/bin/sh: nosuchcmd: command not found\n", out
+    regex = %r{/bin/sh: (1: )?nosuchcmd: (command )?not found}
+    assert regex.match(out), "Got #{out.inspect}, expected match of pattern #{regex.inspect}"
     assert_equal 127, $?.exitstatus, 127
   end
 
diff --git a/test/test_child.rb b/test/test_child.rb
index 912cb0a..f96b1a4 100644
--- a/test/test_child.rb
+++ b/test/test_child.rb
@@ -74,6 +74,23 @@ class ChildTest < Test::Unit::TestCase
     end
   end
 
+  def test_max_with_partial_output
+    p = Child.build('yes', :max => 100_000)
+    assert_nil p.out
+    assert_raise MaximumOutputExceeded do
+      p.exec!
+    end
+    assert_output_exceeds_repeated_string("y\n", 100_000, p.out)
+  end
+
+  def test_max_with_partial_output_long_lines
+    p = Child.build('yes', "nice to meet you", :max => 10_000)
+    assert_raise MaximumOutputExceeded do
+      p.exec!
+    end
+    assert_output_exceeds_repeated_string("nice to meet you\n", 10_000, p.out)
+  end
+
   def test_timeout
     start = Time.now
     assert_raise TimeoutExceeded do
@@ -88,6 +105,16 @@ class ChildTest < Test::Unit::TestCase
     end
   end
 
+  def test_timeout_with_partial_output
+    start = Time.now
+    p = Child.build('echo Hello; sleep 1', :timeout => 0.05)
+    assert_raise TimeoutExceeded do
+      p.exec!
+    end
+    assert (Time.now-start) <= 0.2
+    assert_equal "Hello\n", p.out
+  end
+
   def test_lots_of_input_and_lots_of_output_at_the_same_time
     input = "stuff on stdin \n" * 1_000
     command = "
@@ -114,4 +141,20 @@ class ChildTest < Test::Unit::TestCase
     p = Child.new('cat', :input => input)
     assert p.success?
   end
+
+  def test_utf8_input_long
+    input = "hålø" * 10_000
+    p = Child.new('cat', :input => input)
+    assert p.success?
+  end
+
+  ##
+  # Assertion Helpers
+
+  def assert_output_exceeds_repeated_string(str, len, actual)
+    assert_operator actual.length, :>=, len
+
+    expected = (str * (len / str.length + 1)).slice(0, len)
+    assert_equal expected, actual.slice(0, len)
+  end
 end
diff --git a/test/test_spawn.rb b/test/test_spawn.rb
index ac5b9fb..38bfb3d 100644
--- a/test/test_spawn.rb
+++ b/test/test_spawn.rb
@@ -68,33 +68,33 @@ module SpawnImplementationTests
 
   def test_sanity_of_checking_clone_with_sh
     rd, wr = IO.pipe
-    pid = _spawn("exec 2>/dev/null 100<&#{rd.posix_fileno} || exit 1", rd => rd)
+    pid = _spawn("exec 2>/dev/null 9<&#{rd.posix_fileno} || exit 1", rd => rd)
     assert_process_exit_status pid, 0
   ensure
     [rd, wr].each { |fd| fd.close rescue nil }
   end
 
   def test_spawn_close_option_with_symbolic_standard_stream_names
-    pid = _spawn('exec 2>/dev/null 100<&0 || exit 1', :in => :close)
+    pid = _spawn('true 2>/dev/null 9<&0 || exit 1', :in => :close)
     assert_process_exit_status pid, 1
 
-    pid = _spawn('exec 2>/dev/null 101>&1 102>&2 || exit 1',
+    pid = _spawn('true 2>/dev/null 9>&1 8>&2 || exit 1',
                  :out => :close, :err => :close)
     assert_process_exit_status pid, 1
   end
 
   def test_spawn_close_on_standard_stream_io_object
-    pid = _spawn('exec 2>/dev/null 100<&0 || exit 1', STDIN => :close)
+    pid = _spawn('true 2>/dev/null 9<&0 || exit 1', STDIN => :close)
     assert_process_exit_status pid, 1
 
-    pid = _spawn('exec 2>/dev/null 101>&1 102>&2 || exit 1',
+    pid = _spawn('true 2>/dev/null 9>&1 8>&2 || exit 1',
                  STDOUT => :close, STDOUT => :close)
     assert_process_exit_status pid, 1
   end
 
   def test_spawn_close_option_with_fd_number
     rd, wr = IO.pipe
-    pid = _spawn("exec 2>/dev/null 100<&#{rd.posix_fileno} || exit 1", rd.posix_fileno => :close)
+    pid = _spawn("true 2>/dev/null 9<&#{rd.posix_fileno} || exit 1", rd.posix_fileno => :close)
     assert_process_exit_status pid, 1
 
     assert !rd.closed?
@@ -105,7 +105,7 @@ module SpawnImplementationTests
 
   def test_spawn_close_option_with_io_object
     rd, wr = IO.pipe
-    pid = _spawn("exec 2>/dev/null 100<&#{rd.posix_fileno} || exit 1", rd => :close)
+    pid = _spawn("true 2>/dev/null 9<&#{rd.posix_fileno} || exit 1", rd => :close)
     assert_process_exit_status pid, 1
 
     assert !rd.closed?
@@ -121,9 +121,17 @@ module SpawnImplementationTests
     # this happens on darwin only. GNU does spawn and exits 127.
   end
 
+  def test_spawn_invalid_chdir_raises_exception
+    pid = _spawn("echo", "hiya", :chdir => "/this/does/not/exist")
+    # fspawn does chdir in child, so it exits with 127
+    assert_process_exit_status pid, 127
+  rescue Errno::ENOENT
+    # pspawn and native spawn do chdir in parent, so they throw an exception
+  end
+
   def test_spawn_closing_multiple_fds_with_array_keys
     rd, wr = IO.pipe
-    pid = _spawn("exec 2>/dev/null 101>&#{wr.posix_fileno} || exit 1", [rd, wr, :out] => :close)
+    pid = _spawn("true 2>/dev/null 9>&#{wr.posix_fileno} || exit 1", [rd, wr, :out] => :close)
     assert_process_exit_status pid, 1
   ensure
     [rd, wr].each { |fd| fd.close rescue nil }
@@ -181,7 +189,7 @@ module SpawnImplementationTests
   # have to pass it explicitly as fd => fd.
   def test_explicitly_passing_an_fd_as_open
     rd, wr = IO.pipe
-    pid = _spawn("exec 101>&#{wr.posix_fileno} || exit 1", wr => wr)
+    pid = _spawn("exec 9>&#{wr.posix_fileno} || exit 1", wr => wr)
     assert_process_exit_ok pid
   ensure
     [rd, wr].each { |fd| fd.close rescue nil }

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-ruby-extras/ruby-posix-spawn.git



More information about the Pkg-ruby-extras-commits mailing list