childprocess 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Jari Bakken
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,43 @@
1
+ = childprocess
2
+
3
+ This gem aims at being a simple and reliable solution for controlling
4
+ external programs running in the background on any Ruby / OS combination.
5
+
6
+ The code originated in the selenium-webdriver gem, but should prove useful as
7
+ a standalone library.
8
+
9
+ == Usage
10
+
11
+ process = ChildProcess.build("ruby", "-e", "sleep").start
12
+
13
+ process.started? #=> true
14
+ process.alive? #=> true
15
+ process.exited? #=> false
16
+
17
+ process.stop
18
+
19
+ The object returned from ChildProcess.build will implement ChildProcess::AbstractProcess.
20
+
21
+ == Implementation
22
+
23
+ How the process is launched and killed depends on the platform:
24
+
25
+ * Unix : fork + exec
26
+ * Windows : CreateProcess and friends
27
+ * JRuby : java.lang.{Process,ProcessBuilder}
28
+ * IronRuby : System.Diagnostics.Process
29
+
30
+
31
+ == Note on Patches/Pull Requests
32
+
33
+ * Fork the project.
34
+ * Make your feature addition or bug fix.
35
+ * Add tests for it. This is important so I don't break it in a
36
+ future version unintentionally.
37
+ * Commit, do not mess with rakefile, version, or history.
38
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
39
+ * Send me a pull request. Bonus points for topic branches.
40
+
41
+ == Copyright
42
+
43
+ Copyright (c) 2010 Jari Bakken. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,47 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "childprocess"
8
+ gem.summary = %Q{Cross-platform ruby library for managing child processes.}
9
+ gem.description = %Q{This gem aims at being a simple and reliable solution for controlling external programs running in the background on any Ruby / OS combination.}
10
+ gem.email = "jari.bakken@gmail.com"
11
+ gem.homepage = "http://github.com/jarib/childprocess"
12
+ gem.authors = ["Jari Bakken"]
13
+ gem.add_development_dependency "rspec", ">= 1.2.9"
14
+ gem.add_development_dependency "yard", ">= 0"
15
+
16
+ gem.add_dependency "ffi", "~> 0.6.3"
17
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
18
+ end
19
+ Jeweler::GemcutterTasks.new
20
+ rescue LoadError
21
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
22
+ end
23
+
24
+ require 'spec/rake/spectask'
25
+ Spec::Rake::SpecTask.new(:spec) do |spec|
26
+ spec.libs << 'lib' << 'spec'
27
+ spec.spec_files = FileList['spec/**/*_spec.rb']
28
+ end
29
+
30
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
31
+ spec.libs << 'lib' << 'spec'
32
+ spec.pattern = 'spec/**/*_spec.rb'
33
+ spec.rcov = true
34
+ end
35
+
36
+ task :spec => :check_dependencies
37
+
38
+ task :default => :spec
39
+
40
+ begin
41
+ require 'yard'
42
+ YARD::Rake::YardocTask.new
43
+ rescue LoadError
44
+ task :yardoc do
45
+ abort "YARD is not available. In order to run yardoc, you must: sudo gem install yard"
46
+ end
47
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
@@ -0,0 +1,33 @@
1
+ require 'childprocess/abstract_process'
2
+ require 'childprocess/errors'
3
+
4
+ module ChildProcess
5
+ autoload :Unix, 'childprocess/unix'
6
+ autoload :Windows, 'childprocess/windows'
7
+ autoload :JRuby, 'childprocess/jruby'
8
+ autoload :IronRuby, 'childprocess/ironruby'
9
+
10
+ def self.build(*args)
11
+ case platform
12
+ when :jruby
13
+ JRuby::Process.new(args)
14
+ when :ironruby
15
+ IronRuby::Process.new(args)
16
+ when :windows
17
+ Windows::Process.new(args)
18
+ else
19
+ Unix::Process.new(args)
20
+ end
21
+ end
22
+
23
+ def self.platform
24
+ if RUBY_PLATFORM == "java"
25
+ :jruby
26
+ elsif defined?(RUBY_ENGINE) && RUBY_ENGINE == "ironruby"
27
+ :ironruby
28
+ elsif RUBY_PLATFORM =~ /mswin|msys|mingw32/
29
+ :windows
30
+ end
31
+ end
32
+
33
+ end
@@ -0,0 +1,6 @@
1
+ module ChildProcess
2
+ module IronRuby
3
+ end
4
+ end
5
+
6
+ require "childprocess/ironruby/process"
@@ -0,0 +1,7 @@
1
+ module ChildProcess
2
+ module IronRuby
3
+ class Process < AbstractProcess
4
+
5
+ end # Process
6
+ end # IronRuby
7
+ end # ChildProcess
@@ -0,0 +1,6 @@
1
+ module ChildProcess
2
+ module JRuby
3
+ end
4
+ end
5
+
6
+ require "childprocess/jruby/process"
@@ -0,0 +1,47 @@
1
+ require "java"
2
+
3
+ module ChildProcess
4
+ module JRuby
5
+ class Process < AbstractProcess
6
+
7
+ def exited?
8
+ return true if @exit_code
9
+
10
+ assert_started
11
+ @exit_code = @process.exitValue
12
+ rescue java.lang.IllegalThreadStateException
13
+ false
14
+ end
15
+
16
+ def stop
17
+ assert_started
18
+
19
+ @process.destroy
20
+ @process.waitFor
21
+
22
+ @exit_code = @process.exitValue
23
+ end
24
+
25
+ private
26
+
27
+ def launch_process
28
+ pb = java.lang.ProcessBuilder.new(@args)
29
+
30
+ # not sure why this is necessary
31
+ env = pb.environment
32
+ ENV.each { |k,v| env.put(k, v) }
33
+
34
+ @process = pb.start
35
+
36
+ # Firefox 3.6 on Snow Leopard has a lot output on stderr, which makes
37
+ # the launch act funny if we don't do something to the streams
38
+ # Closing the streams solves the problem for now, but on other platforms
39
+ # we might need to actually read them.
40
+
41
+ @process.getErrorStream.close
42
+ @process.getInputStream.close
43
+ end
44
+
45
+ end # Process
46
+ end # JRuby
47
+ end # ChildProcess
@@ -0,0 +1,6 @@
1
+ module ChildProcess
2
+ module Unix
3
+ end
4
+ end
5
+
6
+ require "childprocess/unix/process"
@@ -0,0 +1,77 @@
1
+ module ChildProcess
2
+ module Unix
3
+ class Process < AbstractProcess
4
+
5
+ def stop(timeout = 3)
6
+ assert_started
7
+ send_term
8
+
9
+ begin
10
+ return poll_for_exit(timeout)
11
+ rescue TimeoutError
12
+ # try next
13
+ end
14
+
15
+ send_kill
16
+ wait
17
+ rescue Errno::ECHILD
18
+ # that'll do
19
+ true
20
+ end
21
+
22
+ #
23
+ # Did the process exit?
24
+ #
25
+ # @return [Boolean]
26
+ #
27
+
28
+ def exited?
29
+ return true if @exit_code
30
+
31
+
32
+ assert_started
33
+ pid, status = ::Process.waitpid2(@pid, ::Process::WNOHANG)
34
+
35
+ log(pid, status)
36
+
37
+ if pid
38
+ @exit_code = status.exitstatus || status.termsig
39
+ end
40
+
41
+ !!pid
42
+ end
43
+
44
+ private
45
+
46
+ def wait
47
+ @exit_code = ::Process.waitpid @pid
48
+ end
49
+
50
+ def send_term
51
+ send_signal 'TERM'
52
+ end
53
+
54
+ def send_kill
55
+ send_signal 'KILL'
56
+ end
57
+
58
+ def send_signal(sig)
59
+ assert_started
60
+
61
+ log "sending #{sig}"
62
+ ::Process.kill sig, @pid
63
+ end
64
+
65
+ def launch_process
66
+ @pid = fork {
67
+ unless $DEBUG
68
+ [STDOUT, STDERR].each { |io| io.reopen("/dev/null") }
69
+ end
70
+
71
+ exec(*@args)
72
+ }
73
+ end
74
+
75
+ end # Process
76
+ end # Unix
77
+ end # ChildProcess
@@ -0,0 +1,19 @@
1
+ require "ffi"
2
+
3
+ module ChildProcess
4
+ module Windows
5
+ module Lib
6
+ extend FFI::Library
7
+
8
+ ffi_lib "kernel32"
9
+ ffi_convention :stdcall
10
+ end
11
+ end
12
+ end
13
+
14
+ require "childprocess/windows/constants"
15
+ require "childprocess/windows/structs"
16
+ require "childprocess/windows/functions"
17
+ require "childprocess/windows/handle"
18
+ require "childprocess/windows/api"
19
+ require "childprocess/windows/process"
@@ -0,0 +1,48 @@
1
+ module ChildProcess
2
+ module Windows
3
+ class << self
4
+ def kill(signal, *pids)
5
+ case signal
6
+ when 'SIGINT', 'INT', :SIGINT, :INT
7
+ signal = WIN_SIGINT
8
+ when 'SIGBRK', 'BRK', :SIGBREAK, :BRK
9
+ signal = WIN_SIGBREAK
10
+ when 'SIGKILL', 'KILL', :SIGKILL, :KILL
11
+ signal = WIN_SIGKILL
12
+ when 0..9
13
+ # Do nothing
14
+ else
15
+ raise Error, "invalid signal #{signal.inspect}"
16
+ end
17
+
18
+ pids.map { |pid| pid if send_signal(signal, pid) }.compact
19
+ end
20
+
21
+ def waitpid(pid, flags = 0)
22
+ wait_for_pid(pid, no_hang?(flags))
23
+ end
24
+
25
+ def waitpid2(pid, flags = 0)
26
+ code = wait_for_pid(pid, no_hang?(flags))
27
+
28
+ [pid, code] if code
29
+ end
30
+
31
+ private
32
+
33
+ def no_hang?(flags)
34
+ (flags & Process::WNOHANG) == Process::WNOHANG
35
+ end
36
+
37
+ def wait_for_pid(pid, no_hang)
38
+ code = Handle.open(pid) { |handle|
39
+ handle.wait unless no_hang
40
+ handle.exit_code
41
+ }
42
+
43
+ code if code != PROCESS_STILL_ACTIVE
44
+ end
45
+
46
+ end # class << self
47
+ end # Windows
48
+ end # ChildProcess
@@ -0,0 +1,27 @@
1
+ module ChildProcess::Windows
2
+
3
+ FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000
4
+ FORMAT_MESSAGE_ARGUMENT_ARRAY = 0x00002000
5
+
6
+ PROCESS_ALL_ACCESS = 0x1F0FFF
7
+ PROCESS_QUERY_INFORMATION = 0x0400
8
+ PROCESS_VM_READ = 0x0010
9
+ PROCESS_STILL_ACTIVE = 259
10
+
11
+ INFINITE = 0xFFFFFFFF
12
+
13
+ WIN_SIGINT = 2
14
+ WIN_SIGBREAK = 3
15
+ WIN_SIGKILL = 9
16
+
17
+ CTRL_C_EVENT = 0
18
+ CTRL_BREAK_EVENT = 1
19
+
20
+
21
+ module Lib
22
+ enum :wait_status, [ :wait_object_0, 0,
23
+ :wait_timeout, 0x102,
24
+ :wait_abandoned, 0x80,
25
+ :wait_failed, 0xFFFFFFFF ]
26
+ end # Lib
27
+ end # ChildProcess::Windows
@@ -0,0 +1,139 @@
1
+ module ChildProcess
2
+ module Windows
3
+ module Lib
4
+
5
+ def self.last_error_message
6
+ errnum = get_last_error
7
+ buf = FFI::MemoryPointer.new :char, 512
8
+
9
+ size = format_message(
10
+ FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ARGUMENT_ARRAY,
11
+ nil, errnum, 0, buf, buf.size, nil
12
+ )
13
+
14
+ buf.read_string(size).strip
15
+ end
16
+
17
+ def self.create_proc(cmd, opts = {})
18
+ cmd_ptr = FFI::MemoryPointer.from_string cmd
19
+
20
+ si = StartupInfo.new
21
+ pi = ProcessInfo.new
22
+
23
+ ok = create_process(nil, cmd_ptr, nil, nil, !!opts[:inherit], 0, nil, nil, si, pi)
24
+ ok or raise Error, last_error_message
25
+
26
+ close_handle pi[:hProcess]
27
+ close_handle pi[:hThread]
28
+
29
+ pi[:dwProcessId]
30
+ end
31
+
32
+ #
33
+ # BOOL WINAPI CreateProcess(
34
+ # __in_opt LPCTSTR lpApplicationName,
35
+ # __inout_opt LPTSTR lpCommandLine,
36
+ # __in_opt LPSECURITY_ATTRIBUTES lpProcessAttributes,
37
+ # __in_opt LPSECURITY_ATTRIBUTES lpThreadAttributes,
38
+ # __in BOOL bInheritHandles,
39
+ # __in DWORD dwCreationFlags,
40
+ # __in_opt LPVOID lpEnvironment,
41
+ # __in_opt LPCTSTR lpCurrentDirectory,
42
+ # __in LPSTARTUPINFO lpStartupInfo,
43
+ # __out LPPROCESS_INFORMATION lpProcessInformation
44
+ # );
45
+ #
46
+
47
+ attach_function :create_process, :CreateProcessA, [
48
+ :pointer,
49
+ :pointer,
50
+ :pointer,
51
+ :pointer,
52
+ :bool,
53
+ :ulong,
54
+ :pointer,
55
+ :pointer,
56
+ :pointer,
57
+ :pointer],
58
+ :bool
59
+
60
+ #
61
+ # DWORD WINAPI GetLastError(void);
62
+ #
63
+
64
+ attach_function :get_last_error, :GetLastError, [], :ulong
65
+
66
+ #
67
+ # DWORD WINAPI FormatMessage(
68
+ # __in DWORD dwFlags,
69
+ # __in_opt LPCVOID lpSource,
70
+ # __in DWORD dwMessageId,
71
+ # __in DWORD dwLanguageId,
72
+ # __out LPTSTR lpBuffer,
73
+ # __in DWORD nSize,
74
+ # __in_opt va_list *Arguments
75
+ # );
76
+ #
77
+
78
+ attach_function :format_message, :FormatMessageA, [
79
+ :ulong,
80
+ :pointer,
81
+ :ulong,
82
+ :ulong,
83
+ :pointer,
84
+ :ulong,
85
+ :pointer],
86
+ :ulong
87
+
88
+
89
+ attach_function :close_handle, :CloseHandle, [:pointer], :bool
90
+
91
+ #
92
+ # HANDLE WINAPI OpenProcess(
93
+ # __in DWORD dwDesiredAccess,
94
+ # __in BOOL bInheritHandle,
95
+ # __in DWORD dwProcessId
96
+ # );
97
+ #
98
+
99
+ attach_function :open_process, :OpenProcess, [:ulong, :bool, :ulong], :pointer
100
+
101
+ #
102
+ # DWORD WINAPI WaitForSingleObject(
103
+ # __in HANDLE hHandle,
104
+ # __in DWORD dwMilliseconds
105
+ # );
106
+ #
107
+
108
+ attach_function :wait_for_single_object, :WaitForSingleObject, [:pointer, :ulong], :wait_status
109
+
110
+ #
111
+ # BOOL WINAPI GetExitCodeProcess(
112
+ # __in HANDLE hProcess,
113
+ # __out LPDWORD lpExitCode
114
+ # );
115
+ #
116
+
117
+ attach_function :get_exit_code, :GetExitCodeProcess, [:pointer, :pointer], :bool
118
+
119
+ #
120
+ # BOOL WINAPI GenerateConsoleCtrlEvent(
121
+ # __in DWORD dwCtrlEvent,
122
+ # __in DWORD dwProcessGroupId
123
+ # );
124
+ #
125
+
126
+ attach_function :generate_console_ctrl_event, :GenerateConsoleCtrlEvent, [:ulong, :ulong], :bool
127
+
128
+ #
129
+ # BOOL WINAPI TerminateProcess(
130
+ # __in HANDLE hProcess,
131
+ # __in UINT uExitCode
132
+ # );
133
+ #
134
+
135
+ attach_function :terminate_process, :TerminateProcess, [:pointer, :uint], :bool
136
+
137
+ end # Lib
138
+ end # Windows
139
+ end # ChildProcess
@@ -0,0 +1,87 @@
1
+ module ChildProcess::Windows
2
+ class Handle
3
+
4
+ class << self
5
+ private :new
6
+
7
+ def open(pid, access = PROCESS_ALL_ACCESS)
8
+ handle = Lib.open_process(access, false, pid)
9
+
10
+ if handle.null?
11
+ raise Error, Lib.last_error_message
12
+ end
13
+
14
+ h = new(handle, pid)
15
+ return h unless block_given?
16
+
17
+ begin
18
+ yield h
19
+ ensure
20
+ h.close
21
+ end
22
+ end
23
+ end
24
+
25
+ def initialize(handle, pid)
26
+ unless handle.kind_of?(FFI::Pointer)
27
+ raise TypeError, "invalid handle: #{handle.inspect}"
28
+ end
29
+
30
+ if handle.null?
31
+ raise ArgumentError, "handle is null: #{handle.inspect}"
32
+ end
33
+
34
+ @pid = pid
35
+ @handle = handle
36
+ @closed = false
37
+ end
38
+
39
+ def exit_code
40
+ code_pointer = FFI::MemoryPointer.new :ulong
41
+ ok = Lib.get_exit_code(@handle, code_pointer)
42
+
43
+ if ok
44
+ code_pointer.get_ulong(0)
45
+ else
46
+ close
47
+ raise Error, Lib.last_error_message
48
+ 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
72
+ end
73
+ end
74
+
75
+ def close
76
+ return if @closed
77
+
78
+ Lib.close_handle(@handle)
79
+ @closed = true
80
+ end
81
+
82
+ def wait(milliseconds = nil)
83
+ Lib.wait_for_single_object(@handle, milliseconds || INFINITE)
84
+ end
85
+
86
+ end # Handle
87
+ end # WindowsProcess
@@ -0,0 +1,42 @@
1
+ module ChildProcess
2
+ module Windows
3
+ class Process < AbstractProcess
4
+
5
+ def stop(timeout = 3)
6
+ assert_started
7
+
8
+ # just kill right away on windows.
9
+ log "sending KILL"
10
+ @handle.send(WIN_SIGKILL)
11
+
12
+ poll_for_exit(timeout)
13
+ ensure
14
+ @handle.close
15
+ end
16
+
17
+ def exited?
18
+ return true if @exit_code
19
+
20
+ assert_started
21
+ code = @handle.exit_code
22
+ exited = code != PROCESS_STILL_ACTIVE
23
+
24
+ if exited
25
+ @exit_code = code
26
+ end
27
+
28
+ exited
29
+ end
30
+
31
+ private
32
+
33
+ def launch_process
34
+ @pid = Lib.create_proc(@args.join(' '), :inherit => false)
35
+ @handle = Handle.open(@pid)
36
+
37
+ self
38
+ end
39
+
40
+ end # Process
41
+ end # Windows
42
+ end # ChildProcess
@@ -0,0 +1,59 @@
1
+ module ChildProcess::Windows
2
+ # typedef struct _STARTUPINFO {
3
+ # DWORD cb;
4
+ # LPTSTR lpReserved;
5
+ # LPTSTR lpDesktop;
6
+ # LPTSTR lpTitle;
7
+ # DWORD dwX;
8
+ # DWORD dwY;
9
+ # DWORD dwXSize;
10
+ # DWORD dwYSize;
11
+ # DWORD dwXCountChars;
12
+ # DWORD dwYCountChars;
13
+ # DWORD dwFillAttribute;
14
+ # DWORD dwFlags;
15
+ # WORD wShowWindow;
16
+ # WORD cbReserved2;
17
+ # LPBYTE lpReserved2;
18
+ # HANDLE hStdInput;
19
+ # HANDLE hStdOutput;
20
+ # HANDLE hStdError;
21
+ # } STARTUPINFO, *LPSTARTUPINFO;
22
+
23
+ class StartupInfo < FFI::Struct
24
+ layout :cb, :ulong,
25
+ :lpReserved, :pointer,
26
+ :lpDesktop, :pointer,
27
+ :lpTitle, :pointer,
28
+ :dwX, :ulong,
29
+ :dwY, :ulong,
30
+ :dwXSize, :ulong,
31
+ :dwYSize, :ulong,
32
+ :dwXCountChars, :ulong,
33
+ :dwYCountChars, :ulong,
34
+ :dwFillAttribute, :ulong,
35
+ :wShowWindow, :ushort,
36
+ :cbReserved2, :ushort,
37
+ :lpReserved2, :pointer,
38
+ :hStdInput, :pointer, # void ptr
39
+ :hStdOutput, :pointer, # void ptr
40
+ :hStdError, :pointer # void ptr
41
+ end
42
+
43
+ #
44
+ # typedef struct _PROCESS_INFORMATION {
45
+ # HANDLE hProcess;
46
+ # HANDLE hThread;
47
+ # DWORD dwProcessId;
48
+ # DWORD dwThreadId;
49
+ # } PROCESS_INFORMATION, *LPPROCESS_INFORMATION;
50
+ #
51
+
52
+ class ProcessInfo < FFI::Struct
53
+ layout :hProcess, :pointer, # void ptr
54
+ :hThread, :pointer, # void ptr
55
+ :dwProcessId, :ulong,
56
+ :dwThreadId, :ulong
57
+ end
58
+
59
+ end
@@ -0,0 +1,35 @@
1
+ require File.expand_path('../spec_helper', __FILE__)
2
+
3
+ describe ChildProcess do
4
+
5
+ EXIT_TIMEOUT = ChildProcess.platform == :jruby ? 2 : 1
6
+
7
+ it "returns self when started" do
8
+ process = sleeping_ruby
9
+ process.start.should == process
10
+ process.should be_started
11
+ end
12
+
13
+ it "should know if the process crashed" do
14
+ process = exit_with(1).start
15
+ process.poll_for_exit(EXIT_TIMEOUT)
16
+
17
+ process.should be_exited
18
+ process.should be_crashed
19
+ end
20
+
21
+ it "should know if the process didn't crash" do
22
+ process = exit_with(0).start
23
+ process.poll_for_exit(EXIT_TIMEOUT)
24
+
25
+ process.should be_exited
26
+ process.should_not be_crashed
27
+ end
28
+
29
+ it "should escalate if TERM is ignored" do
30
+ process = ignored('TERM').start
31
+ process.stop
32
+ process.should be_exited
33
+ end
34
+
35
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1 @@
1
+ --color
@@ -0,0 +1,48 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ require 'childprocess'
4
+ require 'spec'
5
+ require 'spec/autorun'
6
+ require 'tempfile'
7
+
8
+ module ChildProcessSpecHelper
9
+
10
+ def ruby_process(*args)
11
+ @process = ChildProcess.build("ruby" , *args)
12
+ end
13
+
14
+ def sleeping_ruby
15
+ ruby_process("-e", "sleep")
16
+ end
17
+
18
+ def ignored(signal)
19
+ code = <<-RUBY
20
+ trap(#{signal.inspect}, "IGNORE")
21
+ sleep
22
+ RUBY
23
+
24
+ ruby_process tmp_script(code)
25
+ end
26
+
27
+ def exit_with(exit_code)
28
+ ruby_process(tmp_script("exit(#{exit_code})"))
29
+ end
30
+
31
+ def tmp_script(code)
32
+ tf = Tempfile.new("childprocess-temp")
33
+ tf << code
34
+ tf.close
35
+
36
+ puts code if $DEBUG
37
+
38
+ tf.path
39
+ end
40
+ end
41
+
42
+
43
+ Spec::Runner.configure do |config|
44
+ config.include(ChildProcessSpecHelper)
45
+ config.after(:each) {
46
+ @process && @process.alive? && @process.stop
47
+ }
48
+ end
metadata ADDED
@@ -0,0 +1,125 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: childprocess
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 1
9
+ version: 0.0.1
10
+ platform: ruby
11
+ authors:
12
+ - Jari Bakken
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-10-08 00:00:00 +02:00
18
+ default_executable:
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
60
+ 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
+ email: jari.bakken@gmail.com
62
+ executables: []
63
+
64
+ extensions: []
65
+
66
+ extra_rdoc_files:
67
+ - LICENSE
68
+ - README.rdoc
69
+ files:
70
+ - .document
71
+ - .gitignore
72
+ - LICENSE
73
+ - README.rdoc
74
+ - Rakefile
75
+ - VERSION
76
+ - lib/childprocess.rb
77
+ - lib/childprocess/ironruby.rb
78
+ - lib/childprocess/ironruby/process.rb
79
+ - lib/childprocess/jruby.rb
80
+ - lib/childprocess/jruby/process.rb
81
+ - lib/childprocess/unix.rb
82
+ - lib/childprocess/unix/process.rb
83
+ - lib/childprocess/windows.rb
84
+ - lib/childprocess/windows/api.rb
85
+ - lib/childprocess/windows/constants.rb
86
+ - lib/childprocess/windows/functions.rb
87
+ - lib/childprocess/windows/handle.rb
88
+ - lib/childprocess/windows/process.rb
89
+ - lib/childprocess/windows/structs.rb
90
+ - spec/childprocess_spec.rb
91
+ - spec/spec.opts
92
+ - spec/spec_helper.rb
93
+ has_rdoc: true
94
+ homepage: http://github.com/jarib/childprocess
95
+ licenses: []
96
+
97
+ post_install_message:
98
+ rdoc_options:
99
+ - --charset=UTF-8
100
+ require_paths:
101
+ - lib
102
+ required_ruby_version: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ segments:
107
+ - 0
108
+ version: "0"
109
+ required_rubygems_version: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ segments:
114
+ - 0
115
+ version: "0"
116
+ requirements: []
117
+
118
+ rubyforge_project:
119
+ rubygems_version: 1.3.6
120
+ signing_key:
121
+ specification_version: 3
122
+ summary: Cross-platform ruby library for managing child processes.
123
+ test_files:
124
+ - spec/childprocess_spec.rb
125
+ - spec/spec_helper.rb