childprocess 0.0.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/.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