childprocess 0.4.0 → 0.4.1.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +0 -2
- data/lib/childprocess/unix/fork_exec_process.rb +2 -0
- data/lib/childprocess/unix/posix_spawn_process.rb +2 -6
- data/lib/childprocess/unix/process.rb +1 -1
- data/lib/childprocess/version.rb +1 -1
- data/lib/childprocess/windows/handle.rb +15 -13
- data/lib/childprocess/windows/lib.rb +76 -25
- data/lib/childprocess/windows/process.rb +38 -1
- data/lib/childprocess/windows/process_builder.rb +1 -0
- data/lib/childprocess/windows/structs.rb +66 -0
- data/spec/childprocess_spec.rb +21 -16
- data/spec/io_spec.rb +6 -13
- data/spec/pid_behavior.rb +1 -2
- data/spec/spec_helper.rb +23 -0
- metadata +22 -22
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3f15d02987e0f6fb61ee1de1d2a7a58463ed533e
|
4
|
+
data.tar.gz: 00bae3b277c92838151774688d3d69c691dc74f2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dccbf2db7db79bb2dcaed2726ddb1b71e9c6620bc80dd22bf4b45c6c1d9142e56731961294df701296972efada62bed9db15a12bf2af0b35073f4469fef80b47
|
7
|
+
data.tar.gz: ba1b3c25f5a7141b5632960054d9617af5fbf40b360dba30d0c73a34aac7f0f42cfdfe909066cc7e4408a18c6340901aaa433a5b3d729e469d465e3e6162fd23
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
@@ -12,7 +12,6 @@ module ChildProcess
|
|
12
12
|
pid_ptr = FFI::MemoryPointer.new(:pid_t)
|
13
13
|
actions = Lib::FileActions.new
|
14
14
|
attrs = Lib::Attrs.new
|
15
|
-
flags = 0
|
16
15
|
|
17
16
|
if @io
|
18
17
|
if @io.stdout
|
@@ -34,11 +33,8 @@ module ChildProcess
|
|
34
33
|
actions.add_close fileno_for(writer)
|
35
34
|
end
|
36
35
|
|
37
|
-
|
38
|
-
|
39
|
-
end
|
40
|
-
|
41
|
-
attrs.flags = flags
|
36
|
+
attrs.flags |= Platform::POSIX_SPAWN_SETPGROUP
|
37
|
+
attrs.flags |= Platform::POSIX_SPAWN_USEVFORK if defined? Platform::POSIX_SPAWN_USEVFORK
|
42
38
|
|
43
39
|
# wrap in helper classes in order to avoid GC'ed pointers
|
44
40
|
argv = Argv.new(@args)
|
data/lib/childprocess/version.rb
CHANGED
@@ -23,23 +23,25 @@ module ChildProcess
|
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
26
|
-
|
27
|
-
|
28
|
-
|
26
|
+
attr_reader :pointer
|
27
|
+
|
28
|
+
def initialize(pointer, pid)
|
29
|
+
unless pointer.kind_of?(FFI::Pointer)
|
30
|
+
raise TypeError, "invalid handle: #{pointer.inspect}"
|
29
31
|
end
|
30
32
|
|
31
|
-
if
|
32
|
-
raise ArgumentError, "handle is null: #{
|
33
|
+
if pointer.null?
|
34
|
+
raise ArgumentError, "handle is null: #{pointer.inspect}"
|
33
35
|
end
|
34
36
|
|
35
|
-
@pid
|
36
|
-
@
|
37
|
-
@closed
|
37
|
+
@pid = pid
|
38
|
+
@pointer = pointer
|
39
|
+
@closed = false
|
38
40
|
end
|
39
41
|
|
40
42
|
def exit_code
|
41
43
|
code_pointer = FFI::MemoryPointer.new :ulong
|
42
|
-
ok = Lib.get_exit_code(@
|
44
|
+
ok = Lib.get_exit_code(@pointer, code_pointer)
|
43
45
|
|
44
46
|
if ok
|
45
47
|
code_pointer.get_ulong(0)
|
@@ -58,14 +60,14 @@ module ChildProcess
|
|
58
60
|
when WIN_SIGBREAK
|
59
61
|
Lib.generate_console_ctrl_event(CTRL_BREAK_EVENT, @pid)
|
60
62
|
when WIN_SIGKILL
|
61
|
-
ok = Lib.terminate_process(@
|
63
|
+
ok = Lib.terminate_process(@pointer, @pid)
|
62
64
|
Lib.check_error ok
|
63
65
|
else
|
64
66
|
thread_id = FFI::MemoryPointer.new(:ulong)
|
65
67
|
module_handle = Lib.get_module_handle("kernel32")
|
66
68
|
proc_address = Lib.get_proc_address(module_handle, "ExitProcess")
|
67
69
|
|
68
|
-
thread = Lib.create_remote_thread(@
|
70
|
+
thread = Lib.create_remote_thread(@pointer, 0, 0, proc_address, 0, 0, thread_id)
|
69
71
|
check_error thread
|
70
72
|
|
71
73
|
Lib.wait_for_single_object(thread, 5)
|
@@ -76,12 +78,12 @@ module ChildProcess
|
|
76
78
|
def close
|
77
79
|
return if @closed
|
78
80
|
|
79
|
-
Lib.close_handle(@
|
81
|
+
Lib.close_handle(@pointer)
|
80
82
|
@closed = true
|
81
83
|
end
|
82
84
|
|
83
85
|
def wait(milliseconds = nil)
|
84
|
-
Lib.wait_for_single_object(@
|
86
|
+
Lib.wait_for_single_object(@pointer, milliseconds || INFINITE)
|
85
87
|
end
|
86
88
|
|
87
89
|
end # Handle
|
@@ -1,30 +1,34 @@
|
|
1
1
|
module ChildProcess
|
2
2
|
module Windows
|
3
|
-
FORMAT_MESSAGE_FROM_SYSTEM
|
4
|
-
FORMAT_MESSAGE_ARGUMENT_ARRAY
|
3
|
+
FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000
|
4
|
+
FORMAT_MESSAGE_ARGUMENT_ARRAY = 0x00002000
|
5
5
|
|
6
|
-
PROCESS_ALL_ACCESS
|
7
|
-
PROCESS_QUERY_INFORMATION
|
8
|
-
PROCESS_VM_READ
|
9
|
-
PROCESS_STILL_ACTIVE
|
6
|
+
PROCESS_ALL_ACCESS = 0x1F0FFF
|
7
|
+
PROCESS_QUERY_INFORMATION = 0x0400
|
8
|
+
PROCESS_VM_READ = 0x0010
|
9
|
+
PROCESS_STILL_ACTIVE = 259
|
10
10
|
|
11
|
-
INFINITE
|
11
|
+
INFINITE = 0xFFFFFFFF
|
12
12
|
|
13
|
-
WIN_SIGINT
|
14
|
-
WIN_SIGBREAK
|
15
|
-
WIN_SIGKILL
|
13
|
+
WIN_SIGINT = 2
|
14
|
+
WIN_SIGBREAK = 3
|
15
|
+
WIN_SIGKILL = 9
|
16
16
|
|
17
|
-
CTRL_C_EVENT
|
18
|
-
CTRL_BREAK_EVENT
|
17
|
+
CTRL_C_EVENT = 0
|
18
|
+
CTRL_BREAK_EVENT = 1
|
19
19
|
|
20
|
-
DETACHED_PROCESS
|
20
|
+
DETACHED_PROCESS = 0x00000008
|
21
21
|
|
22
|
-
STARTF_USESTDHANDLES
|
23
|
-
INVALID_HANDLE_VALUE
|
24
|
-
HANDLE_FLAG_INHERIT
|
22
|
+
STARTF_USESTDHANDLES = 0x00000100
|
23
|
+
INVALID_HANDLE_VALUE = -1
|
24
|
+
HANDLE_FLAG_INHERIT = 0x00000001
|
25
25
|
|
26
|
-
DUPLICATE_SAME_ACCESS
|
27
|
-
CREATE_UNICODE_ENVIRONMENT
|
26
|
+
DUPLICATE_SAME_ACCESS = 0x00000002
|
27
|
+
CREATE_UNICODE_ENVIRONMENT = 0x00000400
|
28
|
+
|
29
|
+
JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x2000
|
30
|
+
JOB_OBJECT_EXTENDED_LIMIT_INFORMATION = 9
|
31
|
+
JOB_OBJECT_BASIC_LIMIT_INFORMATION = 2
|
28
32
|
|
29
33
|
module Lib
|
30
34
|
enum :wait_status, [
|
@@ -61,12 +65,6 @@ module ChildProcess
|
|
61
65
|
:pointer,
|
62
66
|
:pointer], :bool
|
63
67
|
|
64
|
-
#
|
65
|
-
# DWORD WINAPI GetLastError(void);
|
66
|
-
#
|
67
|
-
|
68
|
-
attach_function :get_last_error, :GetLastError, [], :ulong
|
69
|
-
|
70
68
|
#
|
71
69
|
# DWORD WINAPI FormatMessage(
|
72
70
|
# __in DWORD dwFlags,
|
@@ -101,6 +99,35 @@ module ChildProcess
|
|
101
99
|
|
102
100
|
attach_function :open_process, :OpenProcess, [:ulong, :bool, :ulong], :pointer
|
103
101
|
|
102
|
+
#
|
103
|
+
# HANDLE WINAPI CreateJobObject(
|
104
|
+
# _In_opt_ LPSECURITY_ATTRIBUTES lpJobAttributes,
|
105
|
+
# _In_opt_ LPCTSTR lpName
|
106
|
+
# );
|
107
|
+
#
|
108
|
+
|
109
|
+
attach_function :create_job_object, :CreateJobObjectA, [:pointer, :pointer], :pointer
|
110
|
+
|
111
|
+
#
|
112
|
+
# BOOL WINAPI AssignProcessToJobObject(
|
113
|
+
# _In_ HANDLE hJob,
|
114
|
+
# _In_ HANDLE hProcess
|
115
|
+
# );
|
116
|
+
|
117
|
+
attach_function :assign_process_to_job_object, :AssignProcessToJobObject, [:pointer, :pointer], :bool
|
118
|
+
|
119
|
+
#
|
120
|
+
# BOOL WINAPI SetInformationJobObject(
|
121
|
+
# _In_ HANDLE hJob,
|
122
|
+
# _In_ JOBOBJECTINFOCLASS JobObjectInfoClass,
|
123
|
+
# _In_ LPVOID lpJobObjectInfo,
|
124
|
+
# _In_ DWORD cbJobObjectInfoLength
|
125
|
+
# );
|
126
|
+
#
|
127
|
+
|
128
|
+
attach_function :set_information_job_object, :SetInformationJobObject, [:pointer, :int, :pointer, :ulong], :bool
|
129
|
+
|
130
|
+
#
|
104
131
|
#
|
105
132
|
# DWORD WINAPI WaitForSingleObject(
|
106
133
|
# __in HANDLE hHandle,
|
@@ -243,7 +270,8 @@ module ChildProcess
|
|
243
270
|
end
|
244
271
|
|
245
272
|
def last_error_message
|
246
|
-
errnum =
|
273
|
+
errnum = FFI.errno
|
274
|
+
|
247
275
|
buf = FFI::MemoryPointer.new :char, 512
|
248
276
|
|
249
277
|
size = format_message(
|
@@ -259,6 +287,18 @@ module ChildProcess
|
|
259
287
|
end
|
260
288
|
end
|
261
289
|
|
290
|
+
def each_child_of(pid, &blk)
|
291
|
+
raise NotImplementedError
|
292
|
+
|
293
|
+
# http://stackoverflow.com/questions/1173342/terminate-a-process-tree-c-for-windows?rq=1
|
294
|
+
|
295
|
+
# for each process entry
|
296
|
+
# if pe.th32ParentProcessID == pid
|
297
|
+
# Handle.open(pe.pe.th32ProcessId, &blk)
|
298
|
+
# end
|
299
|
+
#
|
300
|
+
end
|
301
|
+
|
262
302
|
def handle_for(fd_or_io)
|
263
303
|
if fd_or_io.kind_of?(IO) || fd_or_io.respond_to?(:fileno)
|
264
304
|
if ChildProcess.jruby?
|
@@ -344,6 +384,17 @@ module ChildProcess
|
|
344
384
|
bool or raise Error, last_error_message
|
345
385
|
end
|
346
386
|
|
387
|
+
def alive?(pid)
|
388
|
+
handle = Lib.open_process(PROCESS_ALL_ACCESS, false, pid)
|
389
|
+
if handle.null?
|
390
|
+
false
|
391
|
+
else
|
392
|
+
ptr = FFI::MemoryPointer.new :ulong
|
393
|
+
Lib.check_error Lib.get_exit_code(handle, ptr)
|
394
|
+
ptr.read_ulong == PROCESS_STILL_ACTIVE
|
395
|
+
end
|
396
|
+
end
|
397
|
+
|
347
398
|
def no_hang?(flags)
|
348
399
|
(flags & Process::WNOHANG) == Process::WNOHANG
|
349
400
|
end
|
@@ -11,13 +11,13 @@ module ChildProcess
|
|
11
11
|
def stop(timeout = 3)
|
12
12
|
assert_started
|
13
13
|
|
14
|
-
# just kill right away on windows.
|
15
14
|
log "sending KILL"
|
16
15
|
@handle.send(WIN_SIGKILL)
|
17
16
|
|
18
17
|
poll_for_exit(timeout)
|
19
18
|
ensure
|
20
19
|
@handle.close
|
20
|
+
@job.close
|
21
21
|
end
|
22
22
|
|
23
23
|
def wait
|
@@ -27,6 +27,7 @@ module ChildProcess
|
|
27
27
|
@handle.wait
|
28
28
|
@exit_code = @handle.exit_code
|
29
29
|
@handle.close
|
30
|
+
@job.close
|
30
31
|
|
31
32
|
@exit_code
|
32
33
|
end
|
@@ -63,9 +64,12 @@ module ChildProcess
|
|
63
64
|
builder.stderr = @io.stderr
|
64
65
|
end
|
65
66
|
|
67
|
+
@job = Job.new
|
66
68
|
@pid = builder.start
|
67
69
|
@handle = Handle.open @pid
|
68
70
|
|
71
|
+
@job << @handle
|
72
|
+
|
69
73
|
if duplex?
|
70
74
|
raise Error, "no stdin stream" unless builder.stdin
|
71
75
|
io._stdin = builder.stdin
|
@@ -74,6 +78,39 @@ module ChildProcess
|
|
74
78
|
self
|
75
79
|
end
|
76
80
|
|
81
|
+
class Job
|
82
|
+
def initialize
|
83
|
+
@pointer = Lib.create_job_object(nil, nil)
|
84
|
+
|
85
|
+
if @pointer.nil? || @pointer.null?
|
86
|
+
raise Error, "unable to create job object"
|
87
|
+
end
|
88
|
+
|
89
|
+
basic = JobObjectBasicLimitInformation.new
|
90
|
+
basic[:LimitFlags] = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
|
91
|
+
|
92
|
+
extended = JobObjectExtendedLimitInformation.new
|
93
|
+
extended[:BasicLimitInformation] = basic
|
94
|
+
|
95
|
+
ret = Lib.set_information_job_object(
|
96
|
+
@pointer,
|
97
|
+
JOB_OBJECT_EXTENDED_LIMIT_INFORMATION,
|
98
|
+
extended,
|
99
|
+
extended.size
|
100
|
+
)
|
101
|
+
|
102
|
+
Lib.check_error ret
|
103
|
+
end
|
104
|
+
|
105
|
+
def <<(handle)
|
106
|
+
Lib.check_error Lib.assign_process_to_job_object(@pointer, handle.pointer)
|
107
|
+
end
|
108
|
+
|
109
|
+
def close
|
110
|
+
Lib.close_handle @pointer
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
77
114
|
end # Process
|
78
115
|
end # Windows
|
79
116
|
end # ChildProcess
|
@@ -79,5 +79,71 @@ module ChildProcess
|
|
79
79
|
self[:bInheritHandle] = opts[:inherit] ? 1 : 0
|
80
80
|
end
|
81
81
|
end
|
82
|
+
|
83
|
+
#
|
84
|
+
# typedef struct _JOBOBJECT_BASIC_LIMIT_INFORMATION {
|
85
|
+
# LARGE_INTEGER PerProcessUserTimeLimit;
|
86
|
+
# LARGE_INTEGER PerJobUserTimeLimit;
|
87
|
+
# DWORD LimitFlags;
|
88
|
+
# SIZE_T MinimumWorkingSetSize;
|
89
|
+
# SIZE_T MaximumWorkingSetSize;
|
90
|
+
# DWORD ActiveProcessLimit;
|
91
|
+
# ULONG_PTR Affinity;
|
92
|
+
# DWORD PriorityClass;
|
93
|
+
# DWORD SchedulingClass;
|
94
|
+
# } JOBOBJECT_BASIC_LIMIT_INFORMATION, *PJOBOBJECT_BASIC_LIMIT_INFORMATION;
|
95
|
+
#
|
96
|
+
class JobObjectBasicLimitInformation < FFI::Struct
|
97
|
+
layout :PerProcessUserTimeLimit, :int64,
|
98
|
+
:PerJobUserTimeLimit, :int64,
|
99
|
+
:LimitFlags, :ulong,
|
100
|
+
:MinimumWorkingSetSize, :size_t,
|
101
|
+
:MaximumWorkingSetSize, :size_t,
|
102
|
+
:ActiveProcessLimit, :ulong,
|
103
|
+
:Affinity, :pointer,
|
104
|
+
:PriorityClass, :ulong,
|
105
|
+
:SchedulingClass, :ulong
|
106
|
+
end
|
107
|
+
|
108
|
+
#
|
109
|
+
# typedef struct _IO_COUNTERS {
|
110
|
+
# ULONGLONG ReadOperationCount;
|
111
|
+
# ULONGLONG WriteOperationCount;
|
112
|
+
# ULONGLONG OtherOperationCount;
|
113
|
+
# ULONGLONG ReadTransferCount;
|
114
|
+
# ULONGLONG WriteTransferCount;
|
115
|
+
# ULONGLONG OtherTransferCount;
|
116
|
+
# } IO_COUNTERS, *PIO_COUNTERS;
|
117
|
+
#
|
118
|
+
|
119
|
+
class IoCounters < FFI::Struct
|
120
|
+
layout :ReadOperationCount, :ulong_long,
|
121
|
+
:WriteOperationCount, :ulong_long,
|
122
|
+
:OtherOperationCount, :ulong_long,
|
123
|
+
:ReadTransferCount, :ulong_long,
|
124
|
+
:WriteTransferCount, :ulong_long,
|
125
|
+
:OtherTransferCount, :ulong_long
|
126
|
+
end
|
127
|
+
#
|
128
|
+
# typedef struct _JOBOBJECT_EXTENDED_LIMIT_INFORMATION {
|
129
|
+
# JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
|
130
|
+
# IO_COUNTERS IoInfo;
|
131
|
+
# SIZE_T ProcessMemoryLimit;
|
132
|
+
# SIZE_T JobMemoryLimit;
|
133
|
+
# SIZE_T PeakProcessMemoryUsed;
|
134
|
+
# SIZE_T PeakJobMemoryUsed;
|
135
|
+
# } JOBOBJECT_EXTENDED_LIMIT_INFORMATION, *PJOBOBJECT_EXTENDED_LIMIT_INFORMATION;
|
136
|
+
#
|
137
|
+
|
138
|
+
class JobObjectExtendedLimitInformation < FFI::Struct
|
139
|
+
layout :BasicLimitInformation, JobObjectBasicLimitInformation,
|
140
|
+
:IoInfo, IoCounters,
|
141
|
+
:ProcessMemoryLimit, :size_t,
|
142
|
+
:JobMemoryLimit, :size_t,
|
143
|
+
:PeakProcessMemoryUsed, :size_t,
|
144
|
+
:PeakJobMemoryUsed, :size_t
|
145
|
+
end
|
146
|
+
|
147
|
+
|
82
148
|
end # Windows
|
83
149
|
end # ChildProcess
|
data/spec/childprocess_spec.rb
CHANGED
@@ -73,8 +73,7 @@ describe ChildProcess do
|
|
73
73
|
process.wait
|
74
74
|
end
|
75
75
|
|
76
|
-
file
|
77
|
-
child_env = eval(file.read)
|
76
|
+
child_env = eval rewind_and_read(file)
|
78
77
|
child_env['INHERITED'].should == 'yes'
|
79
78
|
end
|
80
79
|
end
|
@@ -88,9 +87,8 @@ describe ChildProcess do
|
|
88
87
|
ENV['CHILD_ONLY'].should be_nil
|
89
88
|
|
90
89
|
process.wait
|
91
|
-
file.rewind
|
92
90
|
|
93
|
-
child_env = eval(file
|
91
|
+
child_env = eval rewind_and_read(file)
|
94
92
|
child_env['CHILD_ONLY'].should == '1'
|
95
93
|
end
|
96
94
|
end
|
@@ -104,8 +102,7 @@ describe ChildProcess do
|
|
104
102
|
process.start
|
105
103
|
process.wait
|
106
104
|
|
107
|
-
file
|
108
|
-
child_env = eval(file.read)
|
105
|
+
child_env = eval rewind_and_read(file)
|
109
106
|
|
110
107
|
child_env['INHERITED'].should eq 'yes'
|
111
108
|
child_env['CHILD_ONLY'].should eq 'yes'
|
@@ -121,9 +118,8 @@ describe ChildProcess do
|
|
121
118
|
process.start
|
122
119
|
|
123
120
|
process.wait
|
124
|
-
file.rewind
|
125
121
|
|
126
|
-
child_env = eval(file
|
122
|
+
child_env = eval rewind_and_read(file)
|
127
123
|
child_env.should_not have_key('CHILDPROCESS_UNSET')
|
128
124
|
end
|
129
125
|
end
|
@@ -136,8 +132,7 @@ describe ChildProcess do
|
|
136
132
|
process = write_argv(file.path, *args).start
|
137
133
|
process.wait
|
138
134
|
|
139
|
-
file.
|
140
|
-
file.read.should == args.inspect
|
135
|
+
rewind_and_read(file).should == args.inspect
|
141
136
|
end
|
142
137
|
end
|
143
138
|
|
@@ -158,8 +153,7 @@ describe ChildProcess do
|
|
158
153
|
|
159
154
|
process.wait
|
160
155
|
|
161
|
-
file.
|
162
|
-
file.read.should == expected_dir
|
156
|
+
rewind_and_read(file).should == expected_dir
|
163
157
|
end
|
164
158
|
end
|
165
159
|
|
@@ -170,8 +164,7 @@ describe ChildProcess do
|
|
170
164
|
process = write_argv(file.path, *args).start
|
171
165
|
process.wait
|
172
166
|
|
173
|
-
file.
|
174
|
-
file.read.should == args.inspect
|
167
|
+
rewind_and_read(file).should == args.inspect
|
175
168
|
end
|
176
169
|
end
|
177
170
|
|
@@ -203,11 +196,23 @@ describe ChildProcess do
|
|
203
196
|
process.start
|
204
197
|
process.wait
|
205
198
|
|
206
|
-
file.
|
207
|
-
file.read.should == dir
|
199
|
+
rewind_and_read(file).should == dir
|
208
200
|
end
|
209
201
|
|
210
202
|
Dir.pwd.should == orig_pwd
|
211
203
|
}
|
212
204
|
end
|
205
|
+
|
206
|
+
it 'kills the full process tree' do
|
207
|
+
Tempfile.open('kill-process-tree') do |file|
|
208
|
+
process = write_pid_in_sleepy_grand_child(file.path).start
|
209
|
+
|
210
|
+
pid = within(5) do
|
211
|
+
Integer(rewind_and_read(file)) rescue nil
|
212
|
+
end
|
213
|
+
|
214
|
+
process.stop
|
215
|
+
within(3) { alive?(pid).should be_false }
|
216
|
+
end
|
217
|
+
end
|
213
218
|
end
|
data/spec/io_spec.rb
CHANGED
@@ -20,11 +20,8 @@ describe ChildProcess do
|
|
20
20
|
process.io.stdin.should be_nil
|
21
21
|
process.wait
|
22
22
|
|
23
|
-
out.
|
24
|
-
err.
|
25
|
-
|
26
|
-
out.read.should eq "0\n"
|
27
|
-
err.read.should eq "1\n"
|
23
|
+
rewind_and_read(out).should eq "0\n"
|
24
|
+
rewind_and_read(err).should eq "1\n"
|
28
25
|
ensure
|
29
26
|
out.close
|
30
27
|
err.close
|
@@ -47,8 +44,7 @@ describe ChildProcess do
|
|
47
44
|
process.start
|
48
45
|
process.wait
|
49
46
|
|
50
|
-
out.
|
51
|
-
out.read.should == "0\n"
|
47
|
+
rewind_and_read(out).should == "0\n"
|
52
48
|
ensure
|
53
49
|
out.close
|
54
50
|
end
|
@@ -66,8 +62,7 @@ describe ChildProcess do
|
|
66
62
|
process.start
|
67
63
|
process.poll_for_exit(exit_timeout)
|
68
64
|
|
69
|
-
out.
|
70
|
-
out.read.should == "hello\n"
|
65
|
+
rewind_and_read(out).should == "hello\n"
|
71
66
|
ensure
|
72
67
|
out.close
|
73
68
|
end
|
@@ -91,8 +86,7 @@ describe ChildProcess do
|
|
91
86
|
|
92
87
|
process.poll_for_exit(exit_timeout)
|
93
88
|
|
94
|
-
out.
|
95
|
-
out.read.should == "hello world\n"
|
89
|
+
rewind_and_read(out).should == "hello world\n"
|
96
90
|
ensure
|
97
91
|
out.close
|
98
92
|
end
|
@@ -207,8 +201,7 @@ describe ChildProcess do
|
|
207
201
|
process.start
|
208
202
|
process.wait
|
209
203
|
|
210
|
-
out.
|
211
|
-
out.read.size.should == 3000
|
204
|
+
rewind_and_read(out).size.should == 3000
|
212
205
|
ensure
|
213
206
|
out.close
|
214
207
|
end
|
data/spec/pid_behavior.rb
CHANGED
@@ -5,9 +5,8 @@ shared_examples_for "a platform that provides the child's pid" do
|
|
5
5
|
Tempfile.open("pid-spec") do |file|
|
6
6
|
process = write_pid(file.path).start
|
7
7
|
process.wait
|
8
|
-
file.rewind
|
9
8
|
|
10
|
-
process.pid.should == file.
|
9
|
+
process.pid.should == rewind_and_read(file).chomp.to_i
|
11
10
|
end
|
12
11
|
end
|
13
12
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -60,6 +60,14 @@ module ChildProcessSpecHelper
|
|
60
60
|
ruby_process tmp_script(code)
|
61
61
|
end
|
62
62
|
|
63
|
+
def write_pid_in_sleepy_grand_child(path)
|
64
|
+
code = <<-RUBY
|
65
|
+
system "ruby", "-e", 'File.open(#{path.inspect}, "w") { |f| f << Process.pid }; sleep'
|
66
|
+
RUBY
|
67
|
+
|
68
|
+
ruby_process tmp_script(code)
|
69
|
+
end
|
70
|
+
|
63
71
|
def exit_with(exit_code)
|
64
72
|
ruby_process(tmp_script("exit(#{exit_code})"))
|
65
73
|
end
|
@@ -97,6 +105,8 @@ module ChildProcessSpecHelper
|
|
97
105
|
end
|
98
106
|
|
99
107
|
raise last_error unless ok
|
108
|
+
|
109
|
+
ok
|
100
110
|
end
|
101
111
|
|
102
112
|
def cat
|
@@ -201,6 +211,19 @@ module ChildProcessSpecHelper
|
|
201
211
|
io.read
|
202
212
|
end
|
203
213
|
|
214
|
+
def alive?(pid)
|
215
|
+
if ChildProcess.windows?
|
216
|
+
ChildProcess::Windows::Lib.alive?(pid)
|
217
|
+
else
|
218
|
+
begin
|
219
|
+
Process.getpgid pid
|
220
|
+
true
|
221
|
+
rescue Errno::ESRCH
|
222
|
+
false
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
204
227
|
end # ChildProcessSpecHelper
|
205
228
|
|
206
229
|
Thread.abort_on_exception = true
|
metadata
CHANGED
@@ -1,89 +1,89 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: childprocess
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.1.rc1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jari Bakken
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-01-
|
11
|
+
date: 2014-01-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- -
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: 2.0.0
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- -
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: 2.0.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: yard
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- -
|
31
|
+
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: '0'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- -
|
38
|
+
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: rake
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- - ~>
|
45
|
+
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
47
|
version: 0.9.2
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- - ~>
|
52
|
+
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: 0.9.2
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: coveralls
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- -
|
59
|
+
- - ">="
|
60
60
|
- !ruby/object:Gem::Version
|
61
61
|
version: '0'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
|
-
- -
|
66
|
+
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: ffi
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
|
-
- - ~>
|
73
|
+
- - "~>"
|
74
74
|
- !ruby/object:Gem::Version
|
75
75
|
version: '1.0'
|
76
|
-
- -
|
76
|
+
- - ">="
|
77
77
|
- !ruby/object:Gem::Version
|
78
78
|
version: 1.0.11
|
79
79
|
type: :runtime
|
80
80
|
prerelease: false
|
81
81
|
version_requirements: !ruby/object:Gem::Requirement
|
82
82
|
requirements:
|
83
|
-
- - ~>
|
83
|
+
- - "~>"
|
84
84
|
- !ruby/object:Gem::Version
|
85
85
|
version: '1.0'
|
86
|
-
- -
|
86
|
+
- - ">="
|
87
87
|
- !ruby/object:Gem::Version
|
88
88
|
version: 1.0.11
|
89
89
|
description: This gem aims at being a simple and reliable solution for controlling
|
@@ -94,10 +94,10 @@ executables: []
|
|
94
94
|
extensions: []
|
95
95
|
extra_rdoc_files: []
|
96
96
|
files:
|
97
|
-
- .document
|
98
|
-
- .gitignore
|
99
|
-
- .rspec
|
100
|
-
- .travis.yml
|
97
|
+
- ".document"
|
98
|
+
- ".gitignore"
|
99
|
+
- ".rspec"
|
100
|
+
- ".travis.yml"
|
101
101
|
- Gemfile
|
102
102
|
- LICENSE
|
103
103
|
- README.md
|
@@ -148,17 +148,17 @@ require_paths:
|
|
148
148
|
- lib
|
149
149
|
required_ruby_version: !ruby/object:Gem::Requirement
|
150
150
|
requirements:
|
151
|
-
- -
|
151
|
+
- - ">="
|
152
152
|
- !ruby/object:Gem::Version
|
153
153
|
version: '0'
|
154
154
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
155
155
|
requirements:
|
156
|
-
- -
|
156
|
+
- - ">"
|
157
157
|
- !ruby/object:Gem::Version
|
158
|
-
version:
|
158
|
+
version: 1.3.1
|
159
159
|
requirements: []
|
160
160
|
rubyforge_project: childprocess
|
161
|
-
rubygems_version: 2.0
|
161
|
+
rubygems_version: 2.2.0
|
162
162
|
signing_key:
|
163
163
|
specification_version: 4
|
164
164
|
summary: This gem aims at being a simple and reliable solution for controlling external
|