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 +4 -4
- data/.gitignore +1 -0
- data/Gemfile +1 -1
- data/README.md +36 -4
- data/ext/posix-spawn.c +37 -13
- data/lib/posix/spawn.rb +29 -9
- data/lib/posix/spawn/child.rb +52 -13
- data/lib/posix/spawn/version.rb +1 -1
- data/posix-spawn.gemspec +1 -0
- data/test/test_backtick.rb +2 -1
- data/test/test_child.rb +43 -0
- data/test/test_spawn.rb +17 -9
- metadata +8 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8872619c1e0a6be3084be820d80626418a206467
|
4
|
+
data.tar.gz: 638918f1cc5c4ac96318aef573e73b9b4ceae4de
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fe6792783174e8d08ab89ca52d792d285167ae3e28f49899f913e8e596b0ad3c986f4c6661e482741f2c2ca85e2ceab8c934db731d524212097a6d52f946fed1
|
7
|
+
data.tar.gz: ddb22efef13631d5a7c72afa73789f87db199b31918c33f44e280459f2410168d3a194fe987f839b20b202d1d664f16e39d3991b67b5c4e8fc17f62edd9e3a9d
|
data/.gitignore
CHANGED
data/Gemfile
CHANGED
@@ -1,2 +1,2 @@
|
|
1
|
-
source
|
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
|
160
|
-
execution before the child process is aborted. See the
|
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
|
data/ext/posix-spawn.c
CHANGED
@@ -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
|
-
|
400
|
-
|
401
|
-
|
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 (
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
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) {
|
data/lib/posix/spawn.rb
CHANGED
@@ -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
|
-
|
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
|
-
[
|
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]]
|
data/lib/posix/spawn/child.rb
CHANGED
@@ -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
|
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
|
-
|
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
|
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.
|
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
|
data/lib/posix/spawn/version.rb
CHANGED
data/posix-spawn.gemspec
CHANGED
data/test/test_backtick.rb
CHANGED
@@ -24,7 +24,8 @@ class BacktickTest < Test::Unit::TestCase
|
|
24
24
|
|
25
25
|
def test_backtick_redirect
|
26
26
|
out = `nosuchcmd 2>&1`
|
27
|
-
|
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
|
|
data/test/test_child.rb
CHANGED
@@ -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
|
data/test/test_spawn.rb
CHANGED
@@ -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
|
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('
|
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('
|
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('
|
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('
|
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("
|
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("
|
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("
|
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
|
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.
|
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:
|
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.
|
80
|
+
rubygems_version: 2.2.2
|
80
81
|
signing_key:
|
81
82
|
specification_version: 4
|
82
83
|
summary: posix_spawnp(2) for ruby
|