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 CHANGED
@@ -1 +1 @@
1
- 0.1.0
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.0"
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/unix_process_spec.rb"
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.6}
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/unix_process_spec.rb"
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::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
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
 
@@ -3,4 +3,7 @@ module ChildProcess
3
3
  end
4
4
  end
5
5
 
6
+ require "java"
7
+ require "childprocess/jruby/redirector"
8
+ require "childprocess/jruby/io"
6
9
  require "childprocess/jruby/process"
@@ -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 background_args!
49
- case ChildProcess.os
50
- when :windows
51
- args = %w[start /wait /b]
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
- @args.push "&" unless @args.last == "&"
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
@@ -3,4 +3,5 @@ module ChildProcess
3
3
  end
4
4
  end
5
5
 
6
+ require "childprocess/unix/io"
6
7
  require "childprocess/unix/process"
@@ -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
- unless $DEBUG
68
- [STDOUT, STDERR].each { |io| io.reopen("/dev/null") }
69
- end
76
+ STDOUT.reopen(stdout || "/dev/null")
77
+ STDERR.reopen(stderr || "/dev/null")
70
78
 
71
79
  exec(*@args)
72
80
  }
@@ -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
- ffi_lib "kernel32"
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
- end
11
- end
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/process"
33
+ require "childprocess/windows/io"
34
+ require "childprocess/windows/process"
@@ -19,6 +19,10 @@ module ChildProcess::Windows
19
19
 
20
20
  DETACHED_PROCESS = 0x00000008
21
21
 
22
+ STARTF_USESTDHANDLES = 0x00000100
23
+ INVALID_HANDLE_VALUE = 0xFFFFFFFF
24
+
25
+
22
26
  module Lib
23
27
  enum :wait_status, [ :wait_object_0, 0,
24
28
  :wait_timeout, 0x102,
@@ -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::Windows
2
- class Handle
1
+ module ChildProcess
2
+ module Windows
3
+ class Handle
3
4
 
4
- class << self
5
- private :new
5
+ class << self
6
+ private :new
6
7
 
7
- def open(pid, access = PROCESS_ALL_ACCESS)
8
- handle = Lib.open_process(access, false, pid)
8
+ def open(pid, access = PROCESS_ALL_ACCESS)
9
+ handle = Lib.open_process(access, false, pid)
9
10
 
10
- if handle.null?
11
- raise Error, Lib.last_error_message
12
- end
11
+ if handle.null?
12
+ raise Error, Lib.last_error_message
13
+ end
13
14
 
14
- h = new(handle, pid)
15
- return h unless block_given?
15
+ h = new(handle, pid)
16
+ return h unless block_given?
16
17
 
17
- begin
18
- yield h
19
- ensure
20
- h.close
18
+ begin
19
+ yield h
20
+ ensure
21
+ h.close
22
+ end
21
23
  end
22
24
  end
23
- end
24
25
 
25
- def initialize(handle, pid)
26
- unless handle.kind_of?(FFI::Pointer)
27
- raise TypeError, "invalid handle: #{handle.inspect}"
28
- end
26
+ def initialize(handle, pid)
27
+ unless handle.kind_of?(FFI::Pointer)
28
+ raise TypeError, "invalid handle: #{handle.inspect}"
29
+ end
29
30
 
30
- if handle.null?
31
- raise ArgumentError, "handle is null: #{handle.inspect}"
32
- end
31
+ if handle.null?
32
+ raise ArgumentError, "handle is null: #{handle.inspect}"
33
+ end
33
34
 
34
- @pid = pid
35
- @handle = handle
36
- @closed = false
37
- end
35
+ @pid = pid
36
+ @handle = handle
37
+ @closed = false
38
+ end
38
39
 
39
- def exit_code
40
- code_pointer = FFI::MemoryPointer.new :ulong
41
- ok = Lib.get_exit_code(@handle, code_pointer)
40
+ def exit_code
41
+ code_pointer = FFI::MemoryPointer.new :ulong
42
+ ok = Lib.get_exit_code(@handle, code_pointer)
42
43
 
43
- if ok
44
- code_pointer.get_ulong(0)
45
- else
46
- close
47
- raise Error, Lib.last_error_message
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
- end
50
-
51
- def send(signal)
52
- case signal
53
- when 0
54
- exit_code == PROCESS_STILL_ALIVE
55
- when WIN_SIGINT
56
- Lib.generate_console_ctrl_event(CTRL_C_EVENT, @pid)
57
- when WIN_SIGBREAK
58
- Lib.generate_console_ctrl_event(CTRL_BREAK_EVENT, @pid)
59
- when WIN_SIGKILL
60
- ok = Lib.terminate_process(@handle, @pid)
61
- ok or raise Error, Lib.last_error_message
62
- else
63
- thread_id = FFI::MemoryPointer.new(:ulong)
64
- module_handle = Lib.get_module_handle("kernel32")
65
- proc_address = Lib.get_proc_address(module_handle, "ExitProcess")
66
-
67
- thread = Lib.create_remote_thread(@handle, 0, 0, proc_address, 0, 0, thread_id)
68
- thread or raise Error, Lib.last_error_message
69
-
70
- Lib.wait_for_single_object(thread, 5)
71
- true
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
- def close
76
- return if @closed
76
+ def close
77
+ return if @closed
77
78
 
78
- Lib.close_handle(@handle)
79
- @closed = true
80
- end
79
+ Lib.close_handle(@handle)
80
+ @closed = true
81
+ end
81
82
 
82
- def wait(milliseconds = nil)
83
- Lib.wait_for_single_object(@handle, milliseconds || INFINITE)
84
- end
83
+ def wait(milliseconds = nil)
84
+ Lib.wait_for_single_object(@handle, milliseconds || INFINITE)
85
+ end
85
86
 
86
- end # Handle
87
- end # WindowsProcess
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
- @pid = Lib.create_proc(
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
@@ -32,6 +32,7 @@ module ChildProcess::Windows
32
32
  :dwXCountChars, :ulong,
33
33
  :dwYCountChars, :ulong,
34
34
  :dwFillAttribute, :ulong,
35
+ :dwFlags, :ulong,
35
36
  :wShowWindow, :ushort,
36
37
  :cbReserved2, :ushort,
37
38
  :lpReserved2, :pointer,
@@ -63,8 +63,37 @@ describe ChildProcess do
63
63
  end
64
64
 
65
65
  it "lets a detached child live on" do
66
- # hmm. how do we spec this?
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
@@ -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
- - 0
7
- - 1
8
- - 0
9
- version: 0.1.0
6
+ - 0
7
+ - 1
8
+ - 1
9
+ version: 0.1.1
10
10
  platform: ruby
11
11
  authors:
12
- - Jari Bakken
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
- - !ruby/object:Gem::Dependency
21
- name: rspec
22
- prerelease: false
23
- requirement: &id001 !ruby/object:Gem::Requirement
24
- requirements:
25
- - - ">="
26
- - !ruby/object:Gem::Version
27
- segments:
28
- - 1
29
- - 2
30
- - 9
31
- version: 1.2.9
32
- type: :development
33
- version_requirements: *id001
34
- - !ruby/object:Gem::Dependency
35
- name: yard
36
- prerelease: false
37
- requirement: &id002 !ruby/object:Gem::Requirement
38
- requirements:
39
- - - ">="
40
- - !ruby/object:Gem::Version
41
- segments:
42
- - 0
43
- version: "0"
44
- type: :development
45
- version_requirements: *id002
46
- - !ruby/object:Gem::Dependency
47
- name: ffi
48
- prerelease: false
49
- requirement: &id003 !ruby/object:Gem::Requirement
50
- requirements:
51
- - - ~>
52
- - !ruby/object:Gem::Version
53
- segments:
54
- - 0
55
- - 6
56
- - 3
57
- version: 0.6.3
58
- type: :runtime
59
- version_requirements: *id003
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
- - LICENSE
68
- - README.rdoc
70
+ - LICENSE
71
+ - README.rdoc
69
72
  files:
70
- - .document
71
- - .gitignore
72
- - LICENSE
73
- - README.rdoc
74
- - Rakefile
75
- - VERSION
76
- - childprocess.gemspec
77
- - lib/childprocess.rb
78
- - lib/childprocess/abstract_process.rb
79
- - lib/childprocess/errors.rb
80
- - lib/childprocess/ironruby.rb
81
- - lib/childprocess/ironruby/process.rb
82
- - lib/childprocess/jruby.rb
83
- - lib/childprocess/jruby/process.rb
84
- - lib/childprocess/unix.rb
85
- - lib/childprocess/unix/process.rb
86
- - lib/childprocess/windows.rb
87
- - lib/childprocess/windows/api.rb
88
- - lib/childprocess/windows/constants.rb
89
- - lib/childprocess/windows/functions.rb
90
- - lib/childprocess/windows/handle.rb
91
- - lib/childprocess/windows/process.rb
92
- - lib/childprocess/windows/structs.rb
93
- - spec/childprocess_spec.rb
94
- - spec/spec.opts
95
- - spec/spec_helper.rb
96
- - spec/unix_process_spec.rb
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
- - --charset=UTF-8
112
+ - --charset=UTF-8
104
113
  require_paths:
105
- - lib
114
+ - lib
106
115
  required_ruby_version: !ruby/object:Gem::Requirement
116
+ none: false
107
117
  requirements:
108
- - - ">="
109
- - !ruby/object:Gem::Version
110
- segments:
111
- - 0
112
- version: "0"
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
- - !ruby/object:Gem::Version
117
- segments:
118
- - 0
119
- version: "0"
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.6
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
- - spec/childprocess_spec.rb
129
- - spec/spec_helper.rb
130
- - spec/unix_process_spec.rb
139
+ - spec/childprocess_spec.rb
140
+ - spec/jruby_spec.rb
141
+ - spec/spec_helper.rb
142
+ - spec/unix_spec.rb
@@ -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