posix-spawn 0.3.8 → 0.3.9

Sign up to get free protection for your applications and to get access to all the features.
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