posix-spawn 0.3.8 → 0.3.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a758d442978f77e9c025e5221d612e26b870bb0c
4
- data.tar.gz: 78cc34f97240c5f37ce70285f4967aa1fe15aa32
3
+ metadata.gz: 8872619c1e0a6be3084be820d80626418a206467
4
+ data.tar.gz: 638918f1cc5c4ac96318aef573e73b9b4ceae4de
5
5
  SHA512:
6
- metadata.gz: 9b30ac20c707dc810eacd8f43158f3d641952e73ac367120acf204996d235085dbbc75dec527bfe60156ec9a5486418e1d0031c732c90375f4655f39c361de1c
7
- data.tar.gz: adcaada88b55b3584cf6d671865730c12ab04069ad40833c462ae0d1db95c79c2d61113b56de126cfa3e49748ac032fb5c79193ab354ac500505d1381c655d9e
6
+ metadata.gz: fe6792783174e8d08ab89ca52d792d285167ae3e28f49899f913e8e596b0ad3c986f4c6661e482741f2c2ca85e2ceab8c934db731d524212097a6d52f946fed1
7
+ data.tar.gz: ddb22efef13631d5a7c72afa73789f87db199b31918c33f44e280459f2410168d3a194fe987f839b20b202d1d664f16e39d3991b67b5c4e8fc17f62edd9e3a9d
data/.gitignore CHANGED
@@ -3,3 +3,4 @@ ext/Makefile
3
3
  lib/posix_spawn_ext.*
4
4
  tmp
5
5
  pkg
6
+ .bundle
data/Gemfile CHANGED
@@ -1,2 +1,2 @@
1
- source :rubygems
1
+ source 'https://rubygems.org'
2
2
  gemspec
data/README.md CHANGED
@@ -156,9 +156,34 @@ after spawning:
156
156
  >> child.out
157
157
  "42\n"
158
158
 
159
- Additional options can be used to specify the maximum output size and time of
160
- execution before the child process is aborted. See the `POSIX::Spawn::Child`
161
- docs for more info.
159
+ Additional options can be used to specify the maximum output size (`:max`) and
160
+ time of execution (`:timeout`) before the child process is aborted. See the
161
+ `POSIX::Spawn::Child` docs for more info.
162
+
163
+ #### Reading Partial Results
164
+
165
+ `POSIX::Spawn::Child.new` spawns the process immediately when instantiated.
166
+ As a result, if it is interrupted by an exception (either from reaching the
167
+ maximum output size, the time limit, or another factor), it is not possible to
168
+ access the `out` or `err` results because the constructor did not complete.
169
+
170
+ If you want to get the `out` and `err` data was available when the process
171
+ was interrupted, use the `POSIX::Spawn::Child.build` alternate form to
172
+ create the child without immediately spawning the process. Call `exec!`
173
+ to run the command at a place where you can catch any exceptions:
174
+
175
+ >> child = POSIX::Spawn::Child.build('git', 'log', :max => 100)
176
+ >> begin
177
+ ?> child.exec!
178
+ ?> rescue POSIX::Spawn::MaximumOutputExceeded
179
+ ?> # limit was reached
180
+ ?> end
181
+ >> child.out
182
+ "commit fa54abe139fd045bf6dc1cc259c0f4c06a9285bb\n..."
183
+
184
+ Please note that when the `MaximumOutputExceeded` exception is raised, the
185
+ actual combined `out` and `err` data may be a bit longer than the `:max`
186
+ value due to internal buffering.
162
187
 
163
188
  ## STATUS
164
189
 
@@ -180,7 +205,7 @@ These `Process::spawn` arguments are currently supported to any of
180
205
  :unsetenv_others => true : clear environment variables except specified by env
181
206
  :unsetenv_others => false : don't clear (default)
182
207
  current directory:
183
- :chdir => str
208
+ :chdir => str : Not thread-safe when using posix_spawn (see below)
184
209
  process group:
185
210
  :pgroup => true or 0 : make a new process group
186
211
  :pgroup => pgid : join to specified process group
@@ -218,6 +243,13 @@ These options are currently NOT supported:
218
243
  :close_others => false : inherit fds (default for system and exec)
219
244
  :close_others => true : don't inherit (default for spawn and IO.popen)
220
245
 
246
+ The `:chdir` option provided by Posix::Spawn::Child, Posix::Spawn#spawn,
247
+ Posix::Spawn#system and Posix::Spawn#popen4 is not thread-safe because
248
+ processes spawned with the posix_spawn(2) system call inherit the working
249
+ directory of the calling process. The posix-spawn gem works around this
250
+ limitation in the system call by changing the working directory of the calling
251
+ process immediately before and after spawning the child process.
252
+
221
253
  ## ACKNOWLEDGEMENTS
222
254
 
223
255
  Copyright (c) by
@@ -1,6 +1,6 @@
1
1
  /* we want GNU extensions like POSIX_SPAWN_USEVFORK */
2
2
  #ifndef _GNU_SOURCE
3
- #define _GNU_SOURCE
3
+ #define _GNU_SOURCE 1
4
4
  #endif
5
5
 
6
6
  #include <errno.h>
@@ -110,6 +110,13 @@ posixspawn_file_actions_addclose(VALUE key, VALUE val, posix_spawn_file_actions_
110
110
 
111
111
  fd = posixspawn_obj_to_fd(key);
112
112
  if (fd >= 0) {
113
+ /* raise an exception if 'fd' is invalid */
114
+ if (fcntl(fd, F_GETFD) == -1) {
115
+ char error_context[32];
116
+ snprintf(error_context, sizeof(error_context), "when closing fd %d", fd);
117
+ rb_sys_fail(error_context);
118
+ return ST_DELETE;
119
+ }
113
120
  posix_spawn_file_actions_addclose(fops, fd);
114
121
  return ST_DELETE;
115
122
  } else {
@@ -138,6 +145,8 @@ posixspawn_file_actions_adddup2(VALUE key, VALUE val, posix_spawn_file_actions_t
138
145
  if (fd < 0)
139
146
  return ST_CONTINUE;
140
147
 
148
+ fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) & ~FD_CLOEXEC);
149
+ fcntl(newfd, F_SETFD, fcntl(newfd, F_GETFD) & ~FD_CLOEXEC);
141
150
  posix_spawn_file_actions_adddup2(fops, fd, newfd);
142
151
  return ST_DELETE;
143
152
  }
@@ -319,7 +328,7 @@ each_env_i(VALUE key, VALUE val, VALUE arg)
319
328
  static VALUE
320
329
  rb_posixspawn_pspawn(VALUE self, VALUE env, VALUE argv, VALUE options)
321
330
  {
322
- int i, ret;
331
+ int i, ret = 0;
323
332
  char **envp = NULL;
324
333
  VALUE dirname;
325
334
  VALUE cmdname;
@@ -396,9 +405,14 @@ rb_posixspawn_pspawn(VALUE self, VALUE env, VALUE argv, VALUE options)
396
405
  sigemptyset(&mask);
397
406
  posix_spawnattr_setsigmask(&attr, &mask);
398
407
 
399
- #if defined(POSIX_SPAWN_USEVFORK) || defined(__linux__)
400
- /* Force USEVFORK on linux. If this is undefined, it's probably because
401
- * you forgot to define _GNU_SOURCE at the top of this file.
408
+ /* Child reverts SIGPIPE handler to the default. */
409
+ flags |= POSIX_SPAWN_SETSIGDEF;
410
+ sigaddset(&mask, SIGPIPE);
411
+ posix_spawnattr_setsigdefault(&attr, &mask);
412
+
413
+ #if defined(POSIX_SPAWN_USEVFORK) || defined(__GLIBC__)
414
+ /* Force USEVFORK on GNU libc. If this is undefined, it's probably
415
+ * because you forgot to define _GNU_SOURCE at the top of this file.
402
416
  */
403
417
  flags |= POSIX_SPAWN_USEVFORK;
404
418
  #endif
@@ -411,19 +425,29 @@ rb_posixspawn_pspawn(VALUE self, VALUE env, VALUE argv, VALUE options)
411
425
  if (RTEST(dirname = rb_hash_delete(options, ID2SYM(rb_intern("chdir"))))) {
412
426
  char *new_cwd = StringValuePtr(dirname);
413
427
  cwd = getcwd(NULL, 0);
414
- chdir(new_cwd);
428
+ if (chdir(new_cwd) == -1) {
429
+ free(cwd);
430
+ cwd = NULL;
431
+ ret = errno;
432
+ }
415
433
  }
416
434
 
417
- if (RHASH_SIZE(options) == 0) {
418
- ret = posix_spawnp(&pid, file, &fops, &attr, cargv, envp ? envp : environ);
419
- if (cwd) {
420
- chdir(cwd);
421
- free(cwd);
435
+ if (ret == 0) {
436
+ if (RHASH_SIZE(options) == 0) {
437
+ ret = posix_spawnp(&pid, file, &fops, &attr, cargv, envp ? envp : environ);
438
+ if (cwd) {
439
+ /* Ignore chdir failures here. There's already a child running, so
440
+ * raising an exception here would do more harm than good. */
441
+ if (chdir(cwd) == -1) {}
442
+ }
443
+ } else {
444
+ ret = -1;
422
445
  }
423
- } else {
424
- ret = -1;
425
446
  }
426
447
 
448
+ if (cwd)
449
+ free(cwd);
450
+
427
451
  posix_spawn_file_actions_destroy(&fops);
428
452
  posix_spawnattr_destroy(&attr);
429
453
  if (envp) {
@@ -79,7 +79,9 @@ module POSIX
79
79
  # When a hash is given in the last argument (options), it specifies a
80
80
  # current directory and zero or more fd redirects for the child process.
81
81
  #
82
- # The :chdir option specifies the current directory:
82
+ # The :chdir option specifies the current directory. Note that :chdir is not
83
+ # thread-safe on systems that provide posix_spawn(2), because it forces a
84
+ # temporary change of the working directory of the calling process.
83
85
  #
84
86
  # spawn(command, :chdir => "/var/tmp")
85
87
  #
@@ -210,6 +212,8 @@ module POSIX
210
212
  if fd?(val)
211
213
  val = fd_to_io(val)
212
214
  key.reopen(val)
215
+ key.close_on_exec = false
216
+ val.close_on_exec = false
213
217
  elsif val == :close
214
218
  if key.respond_to?(:close_on_exec=)
215
219
  key.close_on_exec = true
@@ -236,7 +240,7 @@ module POSIX
236
240
  Process::setpgid(0, pgroup) if pgroup
237
241
 
238
242
  # do the deed
239
- ::Kernel::exec(*argv)
243
+ ::Kernel::exec(*argv, :close_others=>false)
240
244
  ensure
241
245
  exit!(127)
242
246
  end
@@ -267,12 +271,7 @@ module POSIX
267
271
  # Returns the String output of the command.
268
272
  def `(cmd)
269
273
  r, w = IO.pipe
270
- if RUBY_PLATFORM =~ /(mswin|mingw|cygwin|bccwin)/
271
- sh = ENV['COMSPEC'] || 'cmd.exe'
272
- pid = spawn([sh, sh], '/c', cmd, :out => w, r => :close)
273
- else
274
- pid = spawn(['/bin/sh', '/bin/sh'], '-c', cmd, :out => w, r => :close)
275
- end
274
+ pid = spawn(*system_command_prefixes, cmd, :out => w, r => :close)
276
275
 
277
276
  if pid > 0
278
277
  w.close
@@ -488,6 +487,27 @@ module POSIX
488
487
  end
489
488
  end
490
489
 
490
+ # Derives the shell command to use when running the spawn.
491
+ #
492
+ # On a Windows machine, this will yield:
493
+ # [['cmd.exe', 'cmd.exe'], '/c']
494
+ # Note: 'cmd.exe' is used if the COMSPEC environment variable
495
+ # is not specified. If you would like to use something other
496
+ # than 'cmd.exe', specify its path in ENV['COMSPEC']
497
+ #
498
+ # On all other systems, this will yield:
499
+ # [['/bin/sh', '/bin/sh'], '-c']
500
+ #
501
+ # Returns a platform-specific [[<shell>, <shell>], <command-switch>] array.
502
+ def system_command_prefixes
503
+ if RUBY_PLATFORM =~ /(mswin|mingw|cygwin|bccwin)/
504
+ sh = ENV['COMSPEC'] || 'cmd.exe'
505
+ [[sh, sh], '/c']
506
+ else
507
+ [['/bin/sh', '/bin/sh'], '-c']
508
+ end
509
+ end
510
+
491
511
  # Converts the various supported command argument variations into a
492
512
  # standard argv suitable for use with exec. This includes detecting commands
493
513
  # to be run through the shell (single argument strings with spaces).
@@ -503,7 +523,7 @@ module POSIX
503
523
  def adjust_process_spawn_argv(args)
504
524
  if args.size == 1 && args[0] =~ /[ |>]/
505
525
  # single string with these characters means run it through the shell
506
- [['/bin/sh', '/bin/sh'], '-c', args[0]]
526
+ [*system_command_prefixes, args[0]]
507
527
  elsif !args[0].respond_to?(:to_ary)
508
528
  # [argv0, argv1, ...]
509
529
  [[args[0], args[0]], *args[1..-1]]
@@ -30,6 +30,17 @@ module POSIX
30
30
  # >> child.out
31
31
  # "42\n"
32
32
  #
33
+ # To access output from the process even if an exception was raised:
34
+ #
35
+ # >> child = POSIX::Spawn::Child.build('git', 'log', :max => 1000)
36
+ # >> begin
37
+ # ?> child.exec!
38
+ # ?> rescue POSIX::Spawn::MaximumOutputExceeded
39
+ # ?> # just so you know
40
+ # ?> end
41
+ # >> child.out
42
+ # "... first 1000 characters of log output ..."
43
+ #
33
44
  # Q: Why use POSIX::Spawn::Child instead of popen3, hand rolled fork/exec
34
45
  # code, or Process::spawn?
35
46
  #
@@ -77,7 +88,32 @@ module POSIX
77
88
  @timeout = @options.delete(:timeout)
78
89
  @max = @options.delete(:max)
79
90
  @options.delete(:chdir) if @options[:chdir].nil?
80
- exec!
91
+ exec! if !@options.delete(:noexec)
92
+ end
93
+
94
+ # Set up a new process to spawn, but do not actually spawn it.
95
+ #
96
+ # Invoke this just like the normal constructor to set up a process
97
+ # to be run. Call `exec!` to actually run the child process, send
98
+ # the input, read the output, and wait for completion. Use this
99
+ # alternative way of constructing a POSIX::Spawn::Child if you want
100
+ # to read any partial output from the child process even after an
101
+ # exception.
102
+ #
103
+ # child = POSIX::Spawn::Child.build(... arguments ...)
104
+ # child.exec!
105
+ #
106
+ # The arguments are the same as the regular constructor.
107
+ #
108
+ # Returns a new Child instance but does not run the underlying process.
109
+ def self.build(*args)
110
+ options =
111
+ if args[-1].respond_to?(:to_hash)
112
+ args.pop.to_hash
113
+ else
114
+ {}
115
+ end
116
+ new(*args, { :noexec => true }.merge(options))
81
117
  end
82
118
 
83
119
  # All data written to the child process's stdout stream as a String.
@@ -97,15 +133,15 @@ module POSIX
97
133
  @status && @status.success?
98
134
  end
99
135
 
100
- private
101
136
  # Execute command, write input, and read output. This is called
102
- # immediately when a new instance of this object is initialized.
137
+ # immediately when a new instance of this object is created, or
138
+ # can be called explicitly when creating the Child via `build`.
103
139
  def exec!
104
140
  # spawn the process and hook up the pipes
105
141
  pid, stdin, stdout, stderr = popen4(@env, *(@argv + [@options]))
106
142
 
107
143
  # async read from all streams into buffers
108
- @out, @err = read_and_write(@input, stdin, stdout, stderr, @timeout, @max)
144
+ read_and_write(@input, stdin, stdout, stderr, @timeout, @max)
109
145
 
110
146
  # grab exit status
111
147
  @status = waitpid(pid)
@@ -121,6 +157,7 @@ module POSIX
121
157
  [stdin, stdout, stderr].each { |fd| fd.close rescue nil }
122
158
  end
123
159
 
160
+ private
124
161
  # Maximum buffer size for reading
125
162
  BUFSIZE = (32 * 1024)
126
163
 
@@ -142,16 +179,16 @@ module POSIX
142
179
  # exceeds the amount specified by the max argument.
143
180
  def read_and_write(input, stdin, stdout, stderr, timeout=nil, max=nil)
144
181
  max = nil if max && max <= 0
145
- out, err = '', ''
182
+ @out, @err = '', ''
146
183
  offset = 0
147
184
 
148
185
  # force all string and IO encodings to BINARY under 1.9 for now
149
- if out.respond_to?(:force_encoding) and stdin.respond_to?(:set_encoding)
186
+ if @out.respond_to?(:force_encoding) and stdin.respond_to?(:set_encoding)
150
187
  [stdin, stdout, stderr].each do |fd|
151
188
  fd.set_encoding('BINARY', 'BINARY')
152
189
  end
153
- out.force_encoding('BINARY')
154
- err.force_encoding('BINARY')
190
+ @out.force_encoding('BINARY')
191
+ @err.force_encoding('BINARY')
155
192
  input = input.dup.force_encoding('BINARY') if input
156
193
  end
157
194
 
@@ -167,7 +204,9 @@ module POSIX
167
204
  stdin.close
168
205
  []
169
206
  end
207
+ slice_method = input.respond_to?(:byteslice) ? :byteslice : :slice
170
208
  t = timeout
209
+
171
210
  while readers.any? || writers.any?
172
211
  ready = IO.select(readers, writers, readers + writers, t)
173
212
  raise TimeoutExceeded if ready.nil?
@@ -177,11 +216,11 @@ module POSIX
177
216
  begin
178
217
  boom = nil
179
218
  size = fd.write_nonblock(input)
180
- input = input[size, input.size]
219
+ input = input.send(slice_method, size..-1)
181
220
  rescue Errno::EPIPE => boom
182
221
  rescue Errno::EAGAIN, Errno::EINTR
183
222
  end
184
- if boom || input.size == 0
223
+ if boom || input.bytesize == 0
185
224
  stdin.close
186
225
  writers.delete(stdin)
187
226
  end
@@ -189,7 +228,7 @@ module POSIX
189
228
 
190
229
  # read from stdout and stderr streams
191
230
  ready[0].each do |fd|
192
- buf = (fd == stdout) ? out : err
231
+ buf = (fd == stdout) ? @out : @err
193
232
  begin
194
233
  buf << fd.readpartial(BUFSIZE)
195
234
  rescue Errno::EAGAIN, Errno::EINTR
@@ -207,12 +246,12 @@ module POSIX
207
246
  end
208
247
 
209
248
  # maybe we've hit our max output
210
- if max && ready[0].any? && (out.size + err.size) > max
249
+ if max && ready[0].any? && (@out.size + @err.size) > max
211
250
  raise MaximumOutputExceeded
212
251
  end
213
252
  end
214
253
 
215
- [out, err]
254
+ [@out, @err]
216
255
  end
217
256
 
218
257
  # Wait for the child process to exit
@@ -1,5 +1,5 @@
1
1
  module POSIX
2
2
  module Spawn
3
- VERSION = '0.3.8'
3
+ VERSION = '0.3.9'
4
4
  end
5
5
  end
@@ -11,6 +11,7 @@ Gem::Specification.new do |s|
11
11
 
12
12
  s.authors = ['Ryan Tomayko', 'Aman Gupta']
13
13
  s.email = ['r@tomayko.com', 'aman@tmm1.net']
14
+ s.license = 'MIT'
14
15
 
15
16
  s.add_development_dependency 'rake-compiler', '0.7.6'
16
17
 
@@ -24,7 +24,8 @@ class BacktickTest < Test::Unit::TestCase
24
24
 
25
25
  def test_backtick_redirect
26
26
  out = `nosuchcmd 2>&1`
27
- assert_equal "/bin/sh: nosuchcmd: command not found\n", out
27
+ regex = %r{/bin/sh: (1: )?nosuchcmd: (command )?not found}
28
+ assert regex.match(out), "Got #{out.inspect}, expected match of pattern #{regex.inspect}"
28
29
  assert_equal 127, $?.exitstatus, 127
29
30
  end
30
31
 
@@ -74,6 +74,23 @@ class ChildTest < Test::Unit::TestCase
74
74
  end
75
75
  end
76
76
 
77
+ def test_max_with_partial_output
78
+ p = Child.build('yes', :max => 100_000)
79
+ assert_nil p.out
80
+ assert_raise MaximumOutputExceeded do
81
+ p.exec!
82
+ end
83
+ assert_output_exceeds_repeated_string("y\n", 100_000, p.out)
84
+ end
85
+
86
+ def test_max_with_partial_output_long_lines
87
+ p = Child.build('yes', "nice to meet you", :max => 10_000)
88
+ assert_raise MaximumOutputExceeded do
89
+ p.exec!
90
+ end
91
+ assert_output_exceeds_repeated_string("nice to meet you\n", 10_000, p.out)
92
+ end
93
+
77
94
  def test_timeout
78
95
  start = Time.now
79
96
  assert_raise TimeoutExceeded do
@@ -88,6 +105,16 @@ class ChildTest < Test::Unit::TestCase
88
105
  end
89
106
  end
90
107
 
108
+ def test_timeout_with_partial_output
109
+ start = Time.now
110
+ p = Child.build('echo Hello; sleep 1', :timeout => 0.05)
111
+ assert_raise TimeoutExceeded do
112
+ p.exec!
113
+ end
114
+ assert (Time.now-start) <= 0.2
115
+ assert_equal "Hello\n", p.out
116
+ end
117
+
91
118
  def test_lots_of_input_and_lots_of_output_at_the_same_time
92
119
  input = "stuff on stdin \n" * 1_000
93
120
  command = "
@@ -114,4 +141,20 @@ class ChildTest < Test::Unit::TestCase
114
141
  p = Child.new('cat', :input => input)
115
142
  assert p.success?
116
143
  end
144
+
145
+ def test_utf8_input_long
146
+ input = "hålø" * 10_000
147
+ p = Child.new('cat', :input => input)
148
+ assert p.success?
149
+ end
150
+
151
+ ##
152
+ # Assertion Helpers
153
+
154
+ def assert_output_exceeds_repeated_string(str, len, actual)
155
+ assert_operator actual.length, :>=, len
156
+
157
+ expected = (str * (len / str.length + 1)).slice(0, len)
158
+ assert_equal expected, actual.slice(0, len)
159
+ end
117
160
  end
@@ -68,33 +68,33 @@ module SpawnImplementationTests
68
68
 
69
69
  def test_sanity_of_checking_clone_with_sh
70
70
  rd, wr = IO.pipe
71
- pid = _spawn("exec 2>/dev/null 100<&#{rd.posix_fileno} || exit 1", rd => rd)
71
+ pid = _spawn("exec 2>/dev/null 9<&#{rd.posix_fileno} || exit 1", rd => rd)
72
72
  assert_process_exit_status pid, 0
73
73
  ensure
74
74
  [rd, wr].each { |fd| fd.close rescue nil }
75
75
  end
76
76
 
77
77
  def test_spawn_close_option_with_symbolic_standard_stream_names
78
- pid = _spawn('exec 2>/dev/null 100<&0 || exit 1', :in => :close)
78
+ pid = _spawn('true 2>/dev/null 9<&0 || exit 1', :in => :close)
79
79
  assert_process_exit_status pid, 1
80
80
 
81
- pid = _spawn('exec 2>/dev/null 101>&1 102>&2 || exit 1',
81
+ pid = _spawn('true 2>/dev/null 9>&1 8>&2 || exit 1',
82
82
  :out => :close, :err => :close)
83
83
  assert_process_exit_status pid, 1
84
84
  end
85
85
 
86
86
  def test_spawn_close_on_standard_stream_io_object
87
- pid = _spawn('exec 2>/dev/null 100<&0 || exit 1', STDIN => :close)
87
+ pid = _spawn('true 2>/dev/null 9<&0 || exit 1', STDIN => :close)
88
88
  assert_process_exit_status pid, 1
89
89
 
90
- pid = _spawn('exec 2>/dev/null 101>&1 102>&2 || exit 1',
90
+ pid = _spawn('true 2>/dev/null 9>&1 8>&2 || exit 1',
91
91
  STDOUT => :close, STDOUT => :close)
92
92
  assert_process_exit_status pid, 1
93
93
  end
94
94
 
95
95
  def test_spawn_close_option_with_fd_number
96
96
  rd, wr = IO.pipe
97
- pid = _spawn("exec 2>/dev/null 100<&#{rd.posix_fileno} || exit 1", rd.posix_fileno => :close)
97
+ pid = _spawn("true 2>/dev/null 9<&#{rd.posix_fileno} || exit 1", rd.posix_fileno => :close)
98
98
  assert_process_exit_status pid, 1
99
99
 
100
100
  assert !rd.closed?
@@ -105,7 +105,7 @@ module SpawnImplementationTests
105
105
 
106
106
  def test_spawn_close_option_with_io_object
107
107
  rd, wr = IO.pipe
108
- pid = _spawn("exec 2>/dev/null 100<&#{rd.posix_fileno} || exit 1", rd => :close)
108
+ pid = _spawn("true 2>/dev/null 9<&#{rd.posix_fileno} || exit 1", rd => :close)
109
109
  assert_process_exit_status pid, 1
110
110
 
111
111
  assert !rd.closed?
@@ -121,9 +121,17 @@ module SpawnImplementationTests
121
121
  # this happens on darwin only. GNU does spawn and exits 127.
122
122
  end
123
123
 
124
+ def test_spawn_invalid_chdir_raises_exception
125
+ pid = _spawn("echo", "hiya", :chdir => "/this/does/not/exist")
126
+ # fspawn does chdir in child, so it exits with 127
127
+ assert_process_exit_status pid, 127
128
+ rescue Errno::ENOENT
129
+ # pspawn and native spawn do chdir in parent, so they throw an exception
130
+ end
131
+
124
132
  def test_spawn_closing_multiple_fds_with_array_keys
125
133
  rd, wr = IO.pipe
126
- pid = _spawn("exec 2>/dev/null 101>&#{wr.posix_fileno} || exit 1", [rd, wr, :out] => :close)
134
+ pid = _spawn("true 2>/dev/null 9>&#{wr.posix_fileno} || exit 1", [rd, wr, :out] => :close)
127
135
  assert_process_exit_status pid, 1
128
136
  ensure
129
137
  [rd, wr].each { |fd| fd.close rescue nil }
@@ -181,7 +189,7 @@ module SpawnImplementationTests
181
189
  # have to pass it explicitly as fd => fd.
182
190
  def test_explicitly_passing_an_fd_as_open
183
191
  rd, wr = IO.pipe
184
- pid = _spawn("exec 101>&#{wr.posix_fileno} || exit 1", wr => wr)
192
+ pid = _spawn("exec 9>&#{wr.posix_fileno} || exit 1", wr => wr)
185
193
  assert_process_exit_ok pid
186
194
  ensure
187
195
  [rd, wr].each { |fd| fd.close rescue nil }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: posix-spawn
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.8
4
+ version: 0.3.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryan Tomayko
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-12-06 00:00:00.000000000 Z
12
+ date: 2014-08-01 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake-compiler
@@ -37,7 +37,7 @@ extra_rdoc_files:
37
37
  - COPYING
38
38
  - HACKING
39
39
  files:
40
- - .gitignore
40
+ - ".gitignore"
41
41
  - COPYING
42
42
  - Gemfile
43
43
  - HACKING
@@ -58,7 +58,8 @@ files:
58
58
  - test/test_spawn.rb
59
59
  - test/test_system.rb
60
60
  homepage: http://github.com/rtomayko/posix-spawn
61
- licenses: []
61
+ licenses:
62
+ - MIT
62
63
  metadata: {}
63
64
  post_install_message:
64
65
  rdoc_options: []
@@ -66,17 +67,17 @@ require_paths:
66
67
  - lib
67
68
  required_ruby_version: !ruby/object:Gem::Requirement
68
69
  requirements:
69
- - - '>='
70
+ - - ">="
70
71
  - !ruby/object:Gem::Version
71
72
  version: '0'
72
73
  required_rubygems_version: !ruby/object:Gem::Requirement
73
74
  requirements:
74
- - - '>='
75
+ - - ">="
75
76
  - !ruby/object:Gem::Version
76
77
  version: '0'
77
78
  requirements: []
78
79
  rubyforge_project:
79
- rubygems_version: 2.0.3
80
+ rubygems_version: 2.2.2
80
81
  signing_key:
81
82
  specification_version: 4
82
83
  summary: posix_spawnp(2) for ruby