childprocess 0.1.0 → 0.1.1
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.
- data/VERSION +1 -1
- data/childprocess.gemspec +12 -5
- data/lib/childprocess.rb +10 -1
- data/lib/childprocess/abstract_io.rb +22 -0
- data/lib/childprocess/abstract_process.rb +21 -0
- data/lib/childprocess/jruby.rb +3 -0
- data/lib/childprocess/jruby/io.rb +16 -0
- data/lib/childprocess/jruby/process.rb +22 -17
- data/lib/childprocess/jruby/redirector.rb +29 -0
- data/lib/childprocess/unix.rb +1 -0
- data/lib/childprocess/unix/io.rb +21 -0
- data/lib/childprocess/unix/process.rb +11 -3
- data/lib/childprocess/windows.rb +20 -5
- data/lib/childprocess/windows/constants.rb +4 -0
- data/lib/childprocess/windows/functions.rb +44 -0
- data/lib/childprocess/windows/handle.rb +71 -69
- data/lib/childprocess/windows/io.rb +25 -0
- data/lib/childprocess/windows/process.rb +16 -4
- data/lib/childprocess/windows/structs.rb +1 -0
- data/spec/childprocess_spec.rb +31 -2
- data/spec/jruby_spec.rb +12 -0
- data/spec/spec_helper.rb +1 -2
- data/spec/unix_spec.rb +49 -0
- metadata +102 -90
- data/spec/unix_process_spec.rb +0 -29
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.1
|
data/childprocess.gemspec
CHANGED
@@ -5,7 +5,7 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{childprocess}
|
8
|
-
s.version = "0.1.
|
8
|
+
s.version = "0.1.1"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Jari Bakken"]
|
@@ -25,42 +25,49 @@ Gem::Specification.new do |s|
|
|
25
25
|
"VERSION",
|
26
26
|
"childprocess.gemspec",
|
27
27
|
"lib/childprocess.rb",
|
28
|
+
"lib/childprocess/abstract_io.rb",
|
28
29
|
"lib/childprocess/abstract_process.rb",
|
29
30
|
"lib/childprocess/errors.rb",
|
30
31
|
"lib/childprocess/ironruby.rb",
|
31
32
|
"lib/childprocess/ironruby/process.rb",
|
32
33
|
"lib/childprocess/jruby.rb",
|
34
|
+
"lib/childprocess/jruby/io.rb",
|
33
35
|
"lib/childprocess/jruby/process.rb",
|
36
|
+
"lib/childprocess/jruby/redirector.rb",
|
34
37
|
"lib/childprocess/unix.rb",
|
38
|
+
"lib/childprocess/unix/io.rb",
|
35
39
|
"lib/childprocess/unix/process.rb",
|
36
40
|
"lib/childprocess/windows.rb",
|
37
41
|
"lib/childprocess/windows/api.rb",
|
38
42
|
"lib/childprocess/windows/constants.rb",
|
39
43
|
"lib/childprocess/windows/functions.rb",
|
40
44
|
"lib/childprocess/windows/handle.rb",
|
45
|
+
"lib/childprocess/windows/io.rb",
|
41
46
|
"lib/childprocess/windows/process.rb",
|
42
47
|
"lib/childprocess/windows/structs.rb",
|
43
48
|
"spec/childprocess_spec.rb",
|
49
|
+
"spec/jruby_spec.rb",
|
44
50
|
"spec/spec.opts",
|
45
51
|
"spec/spec_helper.rb",
|
46
|
-
"spec/
|
52
|
+
"spec/unix_spec.rb"
|
47
53
|
]
|
48
54
|
s.homepage = %q{http://github.com/jarib/childprocess}
|
49
55
|
s.rdoc_options = ["--charset=UTF-8"]
|
50
56
|
s.require_paths = ["lib"]
|
51
|
-
s.rubygems_version = %q{1.3.
|
57
|
+
s.rubygems_version = %q{1.3.7}
|
52
58
|
s.summary = %q{Cross-platform ruby library for managing child processes.}
|
53
59
|
s.test_files = [
|
54
60
|
"spec/childprocess_spec.rb",
|
61
|
+
"spec/jruby_spec.rb",
|
55
62
|
"spec/spec_helper.rb",
|
56
|
-
"spec/
|
63
|
+
"spec/unix_spec.rb"
|
57
64
|
]
|
58
65
|
|
59
66
|
if s.respond_to? :specification_version then
|
60
67
|
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
61
68
|
s.specification_version = 3
|
62
69
|
|
63
|
-
if Gem::Version.new(Gem::
|
70
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
64
71
|
s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
|
65
72
|
s.add_development_dependency(%q<yard>, [">= 0"])
|
66
73
|
s.add_runtime_dependency(%q<ffi>, ["~> 0.6.3"])
|
data/lib/childprocess.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
|
-
require 'childprocess/abstract_process'
|
2
1
|
require 'childprocess/errors'
|
2
|
+
require 'childprocess/abstract_process'
|
3
|
+
require 'childprocess/abstract_io'
|
3
4
|
|
4
5
|
module ChildProcess
|
5
6
|
autoload :Unix, 'childprocess/unix'
|
@@ -35,6 +36,14 @@ module ChildProcess
|
|
35
36
|
end
|
36
37
|
end
|
37
38
|
|
39
|
+
def unix?
|
40
|
+
!jruby? && [:macosx, :linux, :unix].include?(os)
|
41
|
+
end
|
42
|
+
|
43
|
+
def jruby?
|
44
|
+
platform == :jruby
|
45
|
+
end
|
46
|
+
|
38
47
|
def os
|
39
48
|
@os ||= (
|
40
49
|
require "rbconfig"
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module ChildProcess
|
2
|
+
class AbstractIO
|
3
|
+
attr_reader :stderr, :stdout
|
4
|
+
|
5
|
+
def stderr=(io)
|
6
|
+
check_type io
|
7
|
+
@stderr = io
|
8
|
+
end
|
9
|
+
|
10
|
+
def stdout=(io)
|
11
|
+
check_type io
|
12
|
+
@stdout = io
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def check_type(io)
|
18
|
+
raise SubclassResponsibility, "check_type"
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
@@ -21,6 +21,14 @@ module ChildProcess
|
|
21
21
|
@exit_code = nil
|
22
22
|
end
|
23
23
|
|
24
|
+
#
|
25
|
+
# Returns a ChildProcess::AbstractIO subclass to configure the child's IO streams.
|
26
|
+
#
|
27
|
+
|
28
|
+
def io
|
29
|
+
raise SubclassResponsibility, "io"
|
30
|
+
end
|
31
|
+
|
24
32
|
#
|
25
33
|
# Launch the child process
|
26
34
|
#
|
@@ -57,15 +65,28 @@ module ChildProcess
|
|
57
65
|
#
|
58
66
|
# Is this process running?
|
59
67
|
#
|
68
|
+
# @return [Boolean]
|
69
|
+
#
|
60
70
|
|
61
71
|
def alive?
|
62
72
|
started? && !exited?
|
63
73
|
end
|
64
74
|
|
75
|
+
#
|
76
|
+
# Returns true if the process has exited and the exit code was not 0.
|
77
|
+
#
|
78
|
+
# @return [Boolean]
|
79
|
+
#
|
80
|
+
|
65
81
|
def crashed?
|
66
82
|
exited? && @exit_code != 0
|
67
83
|
end
|
68
84
|
|
85
|
+
#
|
86
|
+
# Wait for the process to exit, raising a ChildProcess::TimeoutError if
|
87
|
+
# the timeout expires.
|
88
|
+
#
|
89
|
+
|
69
90
|
def poll_for_exit(timeout)
|
70
91
|
log "polling #{timeout} seconds for exit"
|
71
92
|
|
data/lib/childprocess/jruby.rb
CHANGED
@@ -0,0 +1,16 @@
|
|
1
|
+
module ChildProcess
|
2
|
+
module JRuby
|
3
|
+
class IO < AbstractIO
|
4
|
+
private
|
5
|
+
|
6
|
+
def check_type(output)
|
7
|
+
unless output.respond_to?(:to_outputstream) && output.respond_to?(:write)
|
8
|
+
raise ArgumentError, "expected #{output.inspect} to respond to :to_outputstream"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
end # IO
|
13
|
+
end # Unix
|
14
|
+
end # ChildProcess
|
15
|
+
|
16
|
+
|
@@ -3,11 +3,16 @@ require "java"
|
|
3
3
|
module ChildProcess
|
4
4
|
module JRuby
|
5
5
|
class Process < AbstractProcess
|
6
|
+
def io
|
7
|
+
@io ||= JRuby::IO.new
|
8
|
+
end
|
9
|
+
|
6
10
|
def exited?
|
7
11
|
return true if @exit_code
|
8
12
|
|
9
13
|
assert_started
|
10
14
|
@exit_code = @process.exitValue
|
15
|
+
true
|
11
16
|
rescue java.lang.IllegalThreadStateException
|
12
17
|
false
|
13
18
|
ensure
|
@@ -26,35 +31,35 @@ module ChildProcess
|
|
26
31
|
private
|
27
32
|
|
28
33
|
def launch_process
|
29
|
-
# background_args! if @detach
|
30
|
-
|
31
34
|
pb = java.lang.ProcessBuilder.new(@args)
|
32
35
|
|
33
|
-
# not sure why this is necessary
|
34
36
|
env = pb.environment
|
35
37
|
ENV.each { |k,v| env.put(k, v) }
|
36
38
|
|
37
39
|
@process = pb.start
|
38
|
-
|
39
|
-
# Firefox 3.6 on Snow Leopard has a lot output on stderr, which makes
|
40
|
-
# the launch act funny if we don't do something to the streams
|
41
|
-
# Closing the streams solves the problem for now, but on other platforms
|
42
|
-
# we might need to actually read them.
|
43
|
-
|
44
|
-
@process.getErrorStream.close
|
45
|
-
@process.getInputStream.close
|
40
|
+
setup_io
|
46
41
|
end
|
47
42
|
|
48
|
-
def
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
@args.unshift(*args) unless @args[0] == start
|
43
|
+
def setup_io
|
44
|
+
if @io
|
45
|
+
redirect @process.getErrorStream, @io.stderr
|
46
|
+
redirect @process.getInputStream, @io.stdout
|
53
47
|
else
|
54
|
-
@
|
48
|
+
@process.getErrorStream.close
|
49
|
+
@process.getInputStream.close
|
55
50
|
end
|
56
51
|
end
|
57
52
|
|
53
|
+
def redirect(input, output)
|
54
|
+
if output.nil?
|
55
|
+
input.close
|
56
|
+
return
|
57
|
+
end
|
58
|
+
|
59
|
+
output = output.to_outputstream
|
60
|
+
Thread.new { Redirector.new(input, output).run }
|
61
|
+
end
|
62
|
+
|
58
63
|
end # Process
|
59
64
|
end # JRuby
|
60
65
|
end # ChildProcess
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module ChildProcess
|
2
|
+
module JRuby
|
3
|
+
class Redirector
|
4
|
+
BUFFER_SIZE = 2048
|
5
|
+
|
6
|
+
def initialize(input, output)
|
7
|
+
@input = input
|
8
|
+
@output = output
|
9
|
+
@buffer = Java.byte[BUFFER_SIZE].new
|
10
|
+
end
|
11
|
+
|
12
|
+
def run
|
13
|
+
read, avail = 0, 0
|
14
|
+
|
15
|
+
while(read != -1)
|
16
|
+
avail = [@input.available, 1].max
|
17
|
+
read = @input.read(@buffer, 0, avail)
|
18
|
+
|
19
|
+
if read > 0
|
20
|
+
@output.write(@buffer, 0, read)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
rescue java.io.IOException => ex
|
24
|
+
$stderr.puts ex.message, ex.backtrace if $DEBUG
|
25
|
+
end
|
26
|
+
|
27
|
+
end # Redirector
|
28
|
+
end # JRuby
|
29
|
+
end # ChildProcess
|
data/lib/childprocess/unix.rb
CHANGED
@@ -0,0 +1,21 @@
|
|
1
|
+
module ChildProcess
|
2
|
+
module Unix
|
3
|
+
class IO < AbstractIO
|
4
|
+
private
|
5
|
+
|
6
|
+
def check_type(io)
|
7
|
+
unless io.respond_to? :to_io
|
8
|
+
raise ArgumentError, "expected #{io.inspect} to respond to :to_io"
|
9
|
+
end
|
10
|
+
|
11
|
+
result = io.to_io
|
12
|
+
unless result && result.kind_of?(::IO)
|
13
|
+
raise TypeError, "expected IO, got #{result.inspect}:#{result.class}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
end # IO
|
18
|
+
end # Unix
|
19
|
+
end # ChildProcess
|
20
|
+
|
21
|
+
|
@@ -2,6 +2,10 @@ module ChildProcess
|
|
2
2
|
module Unix
|
3
3
|
class Process < AbstractProcess
|
4
4
|
|
5
|
+
def io
|
6
|
+
@io ||= Unix::IO.new
|
7
|
+
end
|
8
|
+
|
5
9
|
def stop(timeout = 3)
|
6
10
|
assert_started
|
7
11
|
send_term
|
@@ -63,10 +67,14 @@ module ChildProcess
|
|
63
67
|
end
|
64
68
|
|
65
69
|
def launch_process
|
70
|
+
if @io
|
71
|
+
stdout = @io.stdout
|
72
|
+
stderr = @io.stderr
|
73
|
+
end
|
74
|
+
|
66
75
|
@pid = fork {
|
67
|
-
|
68
|
-
|
69
|
-
end
|
76
|
+
STDOUT.reopen(stdout || "/dev/null")
|
77
|
+
STDERR.reopen(stderr || "/dev/null")
|
70
78
|
|
71
79
|
exec(*@args)
|
72
80
|
}
|
data/lib/childprocess/windows.rb
CHANGED
@@ -1,19 +1,34 @@
|
|
1
1
|
require "ffi"
|
2
|
+
require "rbconfig"
|
2
3
|
|
3
4
|
module ChildProcess
|
4
5
|
module Windows
|
5
6
|
module Lib
|
6
7
|
extend FFI::Library
|
7
8
|
|
8
|
-
|
9
|
+
def self.msvcrt_name
|
10
|
+
host_part = RbConfig::CONFIG['host_os'].split("_")[1]
|
11
|
+
manifest = File.join(RbConfig::CONFIG['bindir'], 'ruby.exe.manifest')
|
12
|
+
|
13
|
+
if host_part && host_part.to_i > 80 && File.exists?(manifest)
|
14
|
+
"msvcr#{host_part}"
|
15
|
+
else
|
16
|
+
"msvcrt"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
ffi_lib "kernel32", msvcrt_name
|
9
21
|
ffi_convention :stdcall
|
10
|
-
|
11
|
-
|
12
|
-
end
|
22
|
+
|
23
|
+
|
24
|
+
end # Library
|
25
|
+
end # Windows
|
26
|
+
end # ChildProcess
|
13
27
|
|
14
28
|
require "childprocess/windows/constants"
|
15
29
|
require "childprocess/windows/structs"
|
16
30
|
require "childprocess/windows/functions"
|
17
31
|
require "childprocess/windows/handle"
|
18
32
|
require "childprocess/windows/api"
|
19
|
-
require "childprocess/windows/
|
33
|
+
require "childprocess/windows/io"
|
34
|
+
require "childprocess/windows/process"
|
@@ -13,6 +13,15 @@ module ChildProcess
|
|
13
13
|
si = StartupInfo.new
|
14
14
|
pi = ProcessInfo.new
|
15
15
|
|
16
|
+
if opts[:stdout] || opts[:stderr]
|
17
|
+
si[:dwFlags] ||= 0
|
18
|
+
si[:dwFlags] |= STARTF_USESTDHANDLES
|
19
|
+
inherit = true
|
20
|
+
|
21
|
+
si[:hStdOutput] = get_os_file_handle(opts[:stdout].fileno) if opts[:stdout]
|
22
|
+
si[:hStdError] = get_os_file_handle(opts[:stderr].fileno) if opts[:stderr]
|
23
|
+
end
|
24
|
+
|
16
25
|
ok = create_process(nil, cmd_ptr, nil, nil, inherit, flags, nil, nil, si, pi)
|
17
26
|
ok or raise Error, last_error_message
|
18
27
|
|
@@ -34,6 +43,33 @@ module ChildProcess
|
|
34
43
|
buf.read_string(size).strip
|
35
44
|
end
|
36
45
|
|
46
|
+
def self.get_os_file_handle(fd_or_io)
|
47
|
+
case fd_or_io
|
48
|
+
when IO
|
49
|
+
handle = _get_osfhandle(fd.fileno)
|
50
|
+
when Fixnum
|
51
|
+
handle = _get_osfhandle(fd_or_io)
|
52
|
+
else
|
53
|
+
if fd_or_io.respond_to?(:to_io)
|
54
|
+
io = fd_or_io.to_io
|
55
|
+
|
56
|
+
unless io.kind_of?(IO)
|
57
|
+
raise TypeError, "expected #to_io to return an instance of IO"
|
58
|
+
end
|
59
|
+
|
60
|
+
handle = _get_osfhandle(io.fileno)
|
61
|
+
else
|
62
|
+
raise TypeError, "invalid type: #{fd_or_io.inspect}"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
if handle == INVALID_HANDLE_VALUE
|
67
|
+
raise Error, Lib.last_error_message
|
68
|
+
end
|
69
|
+
|
70
|
+
handle
|
71
|
+
end
|
72
|
+
|
37
73
|
#
|
38
74
|
# BOOL WINAPI CreateProcess(
|
39
75
|
# __in_opt LPCTSTR lpApplicationName,
|
@@ -139,6 +175,14 @@ module ChildProcess
|
|
139
175
|
|
140
176
|
attach_function :terminate_process, :TerminateProcess, [:pointer, :uint], :bool
|
141
177
|
|
178
|
+
#
|
179
|
+
# long _get_osfhandle(
|
180
|
+
# int fd
|
181
|
+
# );
|
182
|
+
#
|
183
|
+
|
184
|
+
attach_function :_get_osfhandle, :_get_osfhandle, [:int], :long
|
185
|
+
|
142
186
|
end # Lib
|
143
187
|
end # Windows
|
144
188
|
end # ChildProcess
|
@@ -1,87 +1,89 @@
|
|
1
|
-
module ChildProcess
|
2
|
-
|
1
|
+
module ChildProcess
|
2
|
+
module Windows
|
3
|
+
class Handle
|
3
4
|
|
4
|
-
|
5
|
-
|
5
|
+
class << self
|
6
|
+
private :new
|
6
7
|
|
7
|
-
|
8
|
-
|
8
|
+
def open(pid, access = PROCESS_ALL_ACCESS)
|
9
|
+
handle = Lib.open_process(access, false, pid)
|
9
10
|
|
10
|
-
|
11
|
-
|
12
|
-
|
11
|
+
if handle.null?
|
12
|
+
raise Error, Lib.last_error_message
|
13
|
+
end
|
13
14
|
|
14
|
-
|
15
|
-
|
15
|
+
h = new(handle, pid)
|
16
|
+
return h unless block_given?
|
16
17
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
18
|
+
begin
|
19
|
+
yield h
|
20
|
+
ensure
|
21
|
+
h.close
|
22
|
+
end
|
21
23
|
end
|
22
24
|
end
|
23
|
-
end
|
24
25
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
26
|
+
def initialize(handle, pid)
|
27
|
+
unless handle.kind_of?(FFI::Pointer)
|
28
|
+
raise TypeError, "invalid handle: #{handle.inspect}"
|
29
|
+
end
|
29
30
|
|
30
|
-
|
31
|
-
|
32
|
-
|
31
|
+
if handle.null?
|
32
|
+
raise ArgumentError, "handle is null: #{handle.inspect}"
|
33
|
+
end
|
33
34
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
35
|
+
@pid = pid
|
36
|
+
@handle = handle
|
37
|
+
@closed = false
|
38
|
+
end
|
38
39
|
|
39
|
-
|
40
|
-
|
41
|
-
|
40
|
+
def exit_code
|
41
|
+
code_pointer = FFI::MemoryPointer.new :ulong
|
42
|
+
ok = Lib.get_exit_code(@handle, code_pointer)
|
42
43
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
44
|
+
if ok
|
45
|
+
code_pointer.get_ulong(0)
|
46
|
+
else
|
47
|
+
close
|
48
|
+
raise Error, Lib.last_error_message
|
49
|
+
end
|
48
50
|
end
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
51
|
+
|
52
|
+
def send(signal)
|
53
|
+
case signal
|
54
|
+
when 0
|
55
|
+
exit_code == PROCESS_STILL_ALIVE
|
56
|
+
when WIN_SIGINT
|
57
|
+
Lib.generate_console_ctrl_event(CTRL_C_EVENT, @pid)
|
58
|
+
when WIN_SIGBREAK
|
59
|
+
Lib.generate_console_ctrl_event(CTRL_BREAK_EVENT, @pid)
|
60
|
+
when WIN_SIGKILL
|
61
|
+
ok = Lib.terminate_process(@handle, @pid)
|
62
|
+
ok or raise Error, Lib.last_error_message
|
63
|
+
else
|
64
|
+
thread_id = FFI::MemoryPointer.new(:ulong)
|
65
|
+
module_handle = Lib.get_module_handle("kernel32")
|
66
|
+
proc_address = Lib.get_proc_address(module_handle, "ExitProcess")
|
67
|
+
|
68
|
+
thread = Lib.create_remote_thread(@handle, 0, 0, proc_address, 0, 0, thread_id)
|
69
|
+
thread or raise Error, Lib.last_error_message
|
70
|
+
|
71
|
+
Lib.wait_for_single_object(thread, 5)
|
72
|
+
true
|
73
|
+
end
|
72
74
|
end
|
73
|
-
end
|
74
75
|
|
75
|
-
|
76
|
-
|
76
|
+
def close
|
77
|
+
return if @closed
|
77
78
|
|
78
|
-
|
79
|
-
|
80
|
-
|
79
|
+
Lib.close_handle(@handle)
|
80
|
+
@closed = true
|
81
|
+
end
|
81
82
|
|
82
|
-
|
83
|
-
|
84
|
-
|
83
|
+
def wait(milliseconds = nil)
|
84
|
+
Lib.wait_for_single_object(@handle, milliseconds || INFINITE)
|
85
|
+
end
|
85
86
|
|
86
|
-
|
87
|
-
end #
|
87
|
+
end # Handle
|
88
|
+
end # Windows
|
89
|
+
end # ChildProcess
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module ChildProcess
|
2
|
+
module Windows
|
3
|
+
class IO < AbstractIO
|
4
|
+
private
|
5
|
+
|
6
|
+
def check_type(io)
|
7
|
+
return if has_fileno?(io)
|
8
|
+
return if has_to_io?(io)
|
9
|
+
|
10
|
+
raise ArgumentError, "#{io.inspect}:#{io.class} must have :fileno or :to_io"
|
11
|
+
end
|
12
|
+
|
13
|
+
def has_fileno?(io)
|
14
|
+
io.respond_to?(:fileno) && io.fileno
|
15
|
+
end
|
16
|
+
|
17
|
+
def has_to_io?
|
18
|
+
io.respond_to?(:to_io) && io.to_io.kind_of?(::IO)
|
19
|
+
end
|
20
|
+
|
21
|
+
end # IO
|
22
|
+
end # Windows
|
23
|
+
end # ChildProcess
|
24
|
+
|
25
|
+
|
@@ -2,6 +2,10 @@ module ChildProcess
|
|
2
2
|
module Windows
|
3
3
|
class Process < AbstractProcess
|
4
4
|
|
5
|
+
def io
|
6
|
+
@io ||= Windows::IO.new
|
7
|
+
end
|
8
|
+
|
5
9
|
def stop(timeout = 3)
|
6
10
|
assert_started
|
7
11
|
|
@@ -33,11 +37,19 @@ module ChildProcess
|
|
33
37
|
private
|
34
38
|
|
35
39
|
def launch_process
|
36
|
-
|
37
|
-
@args.join(' '),
|
40
|
+
opts = {
|
38
41
|
:inherit => false,
|
39
|
-
:detach => @detach
|
40
|
-
|
42
|
+
:detach => @detach,
|
43
|
+
}
|
44
|
+
|
45
|
+
if @io
|
46
|
+
opts[:stdout] = @io.stdout
|
47
|
+
opts[:stderr] = @io.stderr
|
48
|
+
end
|
49
|
+
|
50
|
+
command = @args.map { |e| e.inspect }.join(' ')
|
51
|
+
|
52
|
+
@pid = Lib.create_proc(command, opts)
|
41
53
|
@handle = Handle.open(@pid)
|
42
54
|
|
43
55
|
self
|
data/spec/childprocess_spec.rb
CHANGED
@@ -63,8 +63,37 @@ describe ChildProcess do
|
|
63
63
|
end
|
64
64
|
|
65
65
|
it "lets a detached child live on" do
|
66
|
-
|
66
|
+
pending "how do we spec this?"
|
67
|
+
end
|
68
|
+
|
69
|
+
it "can redirect stdout and stderr" do
|
70
|
+
process = ruby(<<-CODE)
|
71
|
+
[STDOUT, STDERR].each_with_index do |io, idx|
|
72
|
+
io.sync = true
|
73
|
+
io.puts idx
|
74
|
+
end
|
75
|
+
sleep 0.2
|
76
|
+
CODE
|
77
|
+
|
78
|
+
out = Tempfile.new("stdout-spec")
|
79
|
+
err = Tempfile.new("stderr-spec")
|
80
|
+
|
81
|
+
begin
|
82
|
+
process.io.stdout = out
|
83
|
+
process.io.stderr = err
|
84
|
+
|
85
|
+
process.start
|
86
|
+
process.poll_for_exit(EXIT_TIMEOUT)
|
87
|
+
|
88
|
+
out.rewind
|
89
|
+
err.rewind
|
90
|
+
|
91
|
+
out.read.should == "0\n"
|
92
|
+
err.read.should == "1\n"
|
93
|
+
ensure
|
94
|
+
out.close
|
95
|
+
err.close
|
96
|
+
end
|
67
97
|
end
|
68
98
|
|
69
|
-
it_should_behave_like "unix process" if ChildProcess.platform == :unix
|
70
99
|
end
|
data/spec/jruby_spec.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require File.expand_path('../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
if ChildProcess.jruby?
|
4
|
+
describe ChildProcess::JRuby::IO do
|
5
|
+
let(:io) { ChildProcess::JRuby::IO.new }
|
6
|
+
|
7
|
+
it "raises an ArgumentError if given IO does not respond to :to_outputstream" do
|
8
|
+
lambda { io.stdout = nil }.should raise_error(ArgumentError)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -85,6 +85,7 @@ module ChildProcessSpecHelper
|
|
85
85
|
|
86
86
|
end # ChildProcessSpecHelper
|
87
87
|
|
88
|
+
Thread.abort_on_exception = true
|
88
89
|
|
89
90
|
Spec::Runner.configure do |config|
|
90
91
|
config.include(ChildProcessSpecHelper)
|
@@ -92,5 +93,3 @@ Spec::Runner.configure do |config|
|
|
92
93
|
@process && @process.alive? && @process.stop
|
93
94
|
}
|
94
95
|
end
|
95
|
-
|
96
|
-
require "unix_process_spec"
|
data/spec/unix_spec.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
require File.expand_path('../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
if ChildProcess.unix?
|
4
|
+
describe ChildProcess::Unix::Process do
|
5
|
+
it "handles ECHILD race condition where process dies between timeout and KILL" do
|
6
|
+
process = sleeping_ruby
|
7
|
+
|
8
|
+
process.stub!(:fork).and_return('fakepid')
|
9
|
+
process.stub!(:send_term)
|
10
|
+
process.stub!(:poll_for_exit).and_raise(ChildProcess::TimeoutError)
|
11
|
+
process.stub!(:send_kill).and_raise(Errno::ECHILD)
|
12
|
+
|
13
|
+
process.start
|
14
|
+
lambda { process.stop }.should_not raise_error
|
15
|
+
|
16
|
+
process.stub(:alive?).and_return(false)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "handles ESRCH race condition where process dies between timeout and KILL" do
|
20
|
+
process = sleeping_ruby
|
21
|
+
|
22
|
+
process.stub!(:fork).and_return('fakepid')
|
23
|
+
process.stub!(:send_term)
|
24
|
+
process.stub!(:poll_for_exit).and_raise(ChildProcess::TimeoutError)
|
25
|
+
process.stub!(:send_kill).and_raise(Errno::ESRCH)
|
26
|
+
|
27
|
+
process.start
|
28
|
+
lambda { process.stop }.should_not raise_error
|
29
|
+
|
30
|
+
process.stub(:alive?).and_return(false)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe ChildProcess::Unix::IO do
|
35
|
+
let(:io) { ChildProcess::Unix::IO.new }
|
36
|
+
|
37
|
+
it "raises an ArgumentError if given IO does not respond to :to_io" do
|
38
|
+
lambda { io.stdout = nil }.should raise_error(ArgumentError, /to respond to :to_io/)
|
39
|
+
end
|
40
|
+
|
41
|
+
it "raises an ArgumentError if the IO's fileno is nil" do
|
42
|
+
fake_io = Object.new
|
43
|
+
def fake_io.to_io() StringIO.new end
|
44
|
+
|
45
|
+
lambda { io.stdout = fake_io }.should raise_error(TypeError, /expected IO, got/)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
metadata
CHANGED
@@ -3,13 +3,13 @@ name: childprocess
|
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
version: 0.1.
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
- 1
|
9
|
+
version: 0.1.1
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
|
-
|
12
|
+
- Jari Bakken
|
13
13
|
autorequire:
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
@@ -17,46 +17,49 @@ cert_chain: []
|
|
17
17
|
date: 2010-10-17 00:00:00 +02:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: rspec
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
segments:
|
29
|
+
- 1
|
30
|
+
- 2
|
31
|
+
- 9
|
32
|
+
version: 1.2.9
|
33
|
+
type: :development
|
34
|
+
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: yard
|
37
|
+
prerelease: false
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
segments:
|
44
|
+
- 0
|
45
|
+
version: "0"
|
46
|
+
type: :development
|
47
|
+
version_requirements: *id002
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: ffi
|
50
|
+
prerelease: false
|
51
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ~>
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
segments:
|
57
|
+
- 0
|
58
|
+
- 6
|
59
|
+
- 3
|
60
|
+
version: 0.6.3
|
61
|
+
type: :runtime
|
62
|
+
version_requirements: *id003
|
60
63
|
description: This gem aims at being a simple and reliable solution for controlling external programs running in the background on any Ruby / OS combination.
|
61
64
|
email: jari.bakken@gmail.com
|
62
65
|
executables: []
|
@@ -64,67 +67,76 @@ executables: []
|
|
64
67
|
extensions: []
|
65
68
|
|
66
69
|
extra_rdoc_files:
|
67
|
-
|
68
|
-
|
70
|
+
- LICENSE
|
71
|
+
- README.rdoc
|
69
72
|
files:
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
73
|
+
- .document
|
74
|
+
- .gitignore
|
75
|
+
- LICENSE
|
76
|
+
- README.rdoc
|
77
|
+
- Rakefile
|
78
|
+
- VERSION
|
79
|
+
- childprocess.gemspec
|
80
|
+
- lib/childprocess.rb
|
81
|
+
- lib/childprocess/abstract_io.rb
|
82
|
+
- lib/childprocess/abstract_process.rb
|
83
|
+
- lib/childprocess/errors.rb
|
84
|
+
- lib/childprocess/ironruby.rb
|
85
|
+
- lib/childprocess/ironruby/process.rb
|
86
|
+
- lib/childprocess/jruby.rb
|
87
|
+
- lib/childprocess/jruby/io.rb
|
88
|
+
- lib/childprocess/jruby/process.rb
|
89
|
+
- lib/childprocess/jruby/redirector.rb
|
90
|
+
- lib/childprocess/unix.rb
|
91
|
+
- lib/childprocess/unix/io.rb
|
92
|
+
- lib/childprocess/unix/process.rb
|
93
|
+
- lib/childprocess/windows.rb
|
94
|
+
- lib/childprocess/windows/api.rb
|
95
|
+
- lib/childprocess/windows/constants.rb
|
96
|
+
- lib/childprocess/windows/functions.rb
|
97
|
+
- lib/childprocess/windows/handle.rb
|
98
|
+
- lib/childprocess/windows/io.rb
|
99
|
+
- lib/childprocess/windows/process.rb
|
100
|
+
- lib/childprocess/windows/structs.rb
|
101
|
+
- spec/childprocess_spec.rb
|
102
|
+
- spec/jruby_spec.rb
|
103
|
+
- spec/spec.opts
|
104
|
+
- spec/spec_helper.rb
|
105
|
+
- spec/unix_spec.rb
|
97
106
|
has_rdoc: true
|
98
107
|
homepage: http://github.com/jarib/childprocess
|
99
108
|
licenses: []
|
100
109
|
|
101
110
|
post_install_message:
|
102
111
|
rdoc_options:
|
103
|
-
|
112
|
+
- --charset=UTF-8
|
104
113
|
require_paths:
|
105
|
-
|
114
|
+
- lib
|
106
115
|
required_ruby_version: !ruby/object:Gem::Requirement
|
116
|
+
none: false
|
107
117
|
requirements:
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
118
|
+
- - ">="
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
segments:
|
121
|
+
- 0
|
122
|
+
version: "0"
|
113
123
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
124
|
+
none: false
|
114
125
|
requirements:
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
126
|
+
- - ">="
|
127
|
+
- !ruby/object:Gem::Version
|
128
|
+
segments:
|
129
|
+
- 0
|
130
|
+
version: "0"
|
120
131
|
requirements: []
|
121
132
|
|
122
133
|
rubyforge_project:
|
123
|
-
rubygems_version: 1.3.
|
134
|
+
rubygems_version: 1.3.7
|
124
135
|
signing_key:
|
125
136
|
specification_version: 3
|
126
137
|
summary: Cross-platform ruby library for managing child processes.
|
127
138
|
test_files:
|
128
|
-
|
129
|
-
|
130
|
-
|
139
|
+
- spec/childprocess_spec.rb
|
140
|
+
- spec/jruby_spec.rb
|
141
|
+
- spec/spec_helper.rb
|
142
|
+
- spec/unix_spec.rb
|
data/spec/unix_process_spec.rb
DELETED
@@ -1,29 +0,0 @@
|
|
1
|
-
shared_examples_for "unix process" do
|
2
|
-
it "handles ECHILD race condition where process dies between timeout and KILL" do
|
3
|
-
process = sleeping_ruby
|
4
|
-
|
5
|
-
process.stub!(:fork).and_return('fakepid')
|
6
|
-
process.stub!(:send_term)
|
7
|
-
process.stub!(:poll_for_exit).and_raise(ChildProcess::TimeoutError)
|
8
|
-
process.stub!(:send_kill).and_raise(Errno::ECHILD)
|
9
|
-
|
10
|
-
process.start
|
11
|
-
lambda { process.stop }.should_not raise_error
|
12
|
-
|
13
|
-
process.stub(:alive?).and_return(false)
|
14
|
-
end
|
15
|
-
|
16
|
-
it "handles ESRCH race condition where process dies between timeout and KILL" do
|
17
|
-
process = sleeping_ruby
|
18
|
-
|
19
|
-
process.stub!(:fork).and_return('fakepid')
|
20
|
-
process.stub!(:send_term)
|
21
|
-
process.stub!(:poll_for_exit).and_raise(ChildProcess::TimeoutError)
|
22
|
-
process.stub!(:send_kill).and_raise(Errno::ESRCH)
|
23
|
-
|
24
|
-
process.start
|
25
|
-
lambda { process.stop }.should_not raise_error
|
26
|
-
|
27
|
-
process.stub(:alive?).and_return(false)
|
28
|
-
end
|
29
|
-
end
|