childprocess 0.8.0 → 3.0.0
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.
- checksums.yaml +5 -5
- data/.document +6 -6
- data/.gitignore +28 -28
- data/.rspec +1 -1
- data/.travis.yml +40 -36
- data/CHANGELOG.md +73 -44
- data/Gemfile +21 -15
- data/LICENSE +20 -20
- data/README.md +218 -192
- data/Rakefile +61 -61
- data/appveyor.yml +42 -43
- data/childprocess.gemspec +26 -30
- data/lib/childprocess.rb +210 -205
- data/lib/childprocess/abstract_io.rb +36 -36
- data/lib/childprocess/abstract_process.rb +192 -192
- data/lib/childprocess/errors.rb +37 -26
- data/lib/childprocess/jruby.rb +56 -56
- data/lib/childprocess/jruby/io.rb +16 -16
- data/lib/childprocess/jruby/process.rb +184 -159
- data/lib/childprocess/jruby/pump.rb +53 -53
- data/lib/childprocess/tools/generator.rb +145 -145
- data/lib/childprocess/unix.rb +9 -9
- data/lib/childprocess/unix/fork_exec_process.rb +78 -70
- data/lib/childprocess/unix/io.rb +21 -21
- data/lib/childprocess/unix/lib.rb +186 -186
- data/lib/childprocess/unix/platform/i386-linux.rb +12 -12
- data/lib/childprocess/unix/platform/i386-solaris.rb +11 -11
- data/lib/childprocess/unix/platform/x86_64-linux.rb +12 -12
- data/lib/childprocess/unix/platform/x86_64-macosx.rb +11 -11
- data/lib/childprocess/unix/posix_spawn_process.rb +134 -134
- data/lib/childprocess/unix/process.rb +90 -89
- data/lib/childprocess/version.rb +3 -3
- data/lib/childprocess/windows.rb +38 -33
- data/lib/childprocess/windows/handle.rb +91 -91
- data/lib/childprocess/windows/io.rb +25 -25
- data/lib/childprocess/windows/lib.rb +416 -416
- data/lib/childprocess/windows/process.rb +130 -130
- data/lib/childprocess/windows/process_builder.rb +178 -175
- data/lib/childprocess/windows/structs.rb +148 -148
- data/spec/abstract_io_spec.rb +12 -12
- data/spec/childprocess_spec.rb +447 -391
- data/spec/get_env.ps1 +13 -0
- data/spec/io_spec.rb +228 -228
- data/spec/jruby_spec.rb +24 -24
- data/spec/pid_behavior.rb +12 -12
- data/spec/platform_detection_spec.rb +86 -86
- data/spec/spec_helper.rb +270 -261
- data/spec/unix_spec.rb +57 -57
- data/spec/windows_spec.rb +23 -23
- metadata +8 -39
@@ -1,149 +1,149 @@
|
|
1
|
-
module ChildProcess
|
2
|
-
module Windows
|
3
|
-
# typedef struct _STARTUPINFO {
|
4
|
-
# DWORD cb;
|
5
|
-
# LPTSTR lpReserved;
|
6
|
-
# LPTSTR lpDesktop;
|
7
|
-
# LPTSTR lpTitle;
|
8
|
-
# DWORD dwX;
|
9
|
-
# DWORD dwY;
|
10
|
-
# DWORD dwXSize;
|
11
|
-
# DWORD dwYSize;
|
12
|
-
# DWORD dwXCountChars;
|
13
|
-
# DWORD dwYCountChars;
|
14
|
-
# DWORD dwFillAttribute;
|
15
|
-
# DWORD dwFlags;
|
16
|
-
# WORD wShowWindow;
|
17
|
-
# WORD cbReserved2;
|
18
|
-
# LPBYTE lpReserved2;
|
19
|
-
# HANDLE hStdInput;
|
20
|
-
# HANDLE hStdOutput;
|
21
|
-
# HANDLE hStdError;
|
22
|
-
# } STARTUPINFO, *LPSTARTUPINFO;
|
23
|
-
|
24
|
-
class StartupInfo < FFI::Struct
|
25
|
-
layout :cb, :ulong,
|
26
|
-
:lpReserved, :pointer,
|
27
|
-
:lpDesktop, :pointer,
|
28
|
-
:lpTitle, :pointer,
|
29
|
-
:dwX, :ulong,
|
30
|
-
:dwY, :ulong,
|
31
|
-
:dwXSize, :ulong,
|
32
|
-
:dwYSize, :ulong,
|
33
|
-
:dwXCountChars, :ulong,
|
34
|
-
:dwYCountChars, :ulong,
|
35
|
-
:dwFillAttribute, :ulong,
|
36
|
-
:dwFlags, :ulong,
|
37
|
-
:wShowWindow, :ushort,
|
38
|
-
:cbReserved2, :ushort,
|
39
|
-
:lpReserved2, :pointer,
|
40
|
-
:hStdInput, :pointer, # void ptr
|
41
|
-
:hStdOutput, :pointer, # void ptr
|
42
|
-
:hStdError, :pointer # void ptr
|
43
|
-
end
|
44
|
-
|
45
|
-
#
|
46
|
-
# typedef struct _PROCESS_INFORMATION {
|
47
|
-
# HANDLE hProcess;
|
48
|
-
# HANDLE hThread;
|
49
|
-
# DWORD dwProcessId;
|
50
|
-
# DWORD dwThreadId;
|
51
|
-
# } PROCESS_INFORMATION, *LPPROCESS_INFORMATION;
|
52
|
-
#
|
53
|
-
|
54
|
-
class ProcessInfo < FFI::Struct
|
55
|
-
layout :hProcess, :pointer, # void ptr
|
56
|
-
:hThread, :pointer, # void ptr
|
57
|
-
:dwProcessId, :ulong,
|
58
|
-
:dwThreadId, :ulong
|
59
|
-
end
|
60
|
-
|
61
|
-
#
|
62
|
-
# typedef struct _SECURITY_ATTRIBUTES {
|
63
|
-
# DWORD nLength;
|
64
|
-
# LPVOID lpSecurityDescriptor;
|
65
|
-
# BOOL bInheritHandle;
|
66
|
-
# } SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES;
|
67
|
-
#
|
68
|
-
|
69
|
-
class SecurityAttributes < FFI::Struct
|
70
|
-
layout :nLength, :ulong,
|
71
|
-
:lpSecurityDescriptor, :pointer, # void ptr
|
72
|
-
:bInheritHandle, :int
|
73
|
-
|
74
|
-
def initialize(opts = {})
|
75
|
-
super()
|
76
|
-
|
77
|
-
self[:nLength] = self.class.size
|
78
|
-
self[:lpSecurityDescriptor] = nil
|
79
|
-
self[:bInheritHandle] = opts[:inherit] ? 1 : 0
|
80
|
-
end
|
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
|
-
|
148
|
-
end # Windows
|
1
|
+
module ChildProcess
|
2
|
+
module Windows
|
3
|
+
# typedef struct _STARTUPINFO {
|
4
|
+
# DWORD cb;
|
5
|
+
# LPTSTR lpReserved;
|
6
|
+
# LPTSTR lpDesktop;
|
7
|
+
# LPTSTR lpTitle;
|
8
|
+
# DWORD dwX;
|
9
|
+
# DWORD dwY;
|
10
|
+
# DWORD dwXSize;
|
11
|
+
# DWORD dwYSize;
|
12
|
+
# DWORD dwXCountChars;
|
13
|
+
# DWORD dwYCountChars;
|
14
|
+
# DWORD dwFillAttribute;
|
15
|
+
# DWORD dwFlags;
|
16
|
+
# WORD wShowWindow;
|
17
|
+
# WORD cbReserved2;
|
18
|
+
# LPBYTE lpReserved2;
|
19
|
+
# HANDLE hStdInput;
|
20
|
+
# HANDLE hStdOutput;
|
21
|
+
# HANDLE hStdError;
|
22
|
+
# } STARTUPINFO, *LPSTARTUPINFO;
|
23
|
+
|
24
|
+
class StartupInfo < FFI::Struct
|
25
|
+
layout :cb, :ulong,
|
26
|
+
:lpReserved, :pointer,
|
27
|
+
:lpDesktop, :pointer,
|
28
|
+
:lpTitle, :pointer,
|
29
|
+
:dwX, :ulong,
|
30
|
+
:dwY, :ulong,
|
31
|
+
:dwXSize, :ulong,
|
32
|
+
:dwYSize, :ulong,
|
33
|
+
:dwXCountChars, :ulong,
|
34
|
+
:dwYCountChars, :ulong,
|
35
|
+
:dwFillAttribute, :ulong,
|
36
|
+
:dwFlags, :ulong,
|
37
|
+
:wShowWindow, :ushort,
|
38
|
+
:cbReserved2, :ushort,
|
39
|
+
:lpReserved2, :pointer,
|
40
|
+
:hStdInput, :pointer, # void ptr
|
41
|
+
:hStdOutput, :pointer, # void ptr
|
42
|
+
:hStdError, :pointer # void ptr
|
43
|
+
end
|
44
|
+
|
45
|
+
#
|
46
|
+
# typedef struct _PROCESS_INFORMATION {
|
47
|
+
# HANDLE hProcess;
|
48
|
+
# HANDLE hThread;
|
49
|
+
# DWORD dwProcessId;
|
50
|
+
# DWORD dwThreadId;
|
51
|
+
# } PROCESS_INFORMATION, *LPPROCESS_INFORMATION;
|
52
|
+
#
|
53
|
+
|
54
|
+
class ProcessInfo < FFI::Struct
|
55
|
+
layout :hProcess, :pointer, # void ptr
|
56
|
+
:hThread, :pointer, # void ptr
|
57
|
+
:dwProcessId, :ulong,
|
58
|
+
:dwThreadId, :ulong
|
59
|
+
end
|
60
|
+
|
61
|
+
#
|
62
|
+
# typedef struct _SECURITY_ATTRIBUTES {
|
63
|
+
# DWORD nLength;
|
64
|
+
# LPVOID lpSecurityDescriptor;
|
65
|
+
# BOOL bInheritHandle;
|
66
|
+
# } SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES;
|
67
|
+
#
|
68
|
+
|
69
|
+
class SecurityAttributes < FFI::Struct
|
70
|
+
layout :nLength, :ulong,
|
71
|
+
:lpSecurityDescriptor, :pointer, # void ptr
|
72
|
+
:bInheritHandle, :int
|
73
|
+
|
74
|
+
def initialize(opts = {})
|
75
|
+
super()
|
76
|
+
|
77
|
+
self[:nLength] = self.class.size
|
78
|
+
self[:lpSecurityDescriptor] = nil
|
79
|
+
self[:bInheritHandle] = opts[:inherit] ? 1 : 0
|
80
|
+
end
|
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
|
+
|
148
|
+
end # Windows
|
149
149
|
end # ChildProcess
|
data/spec/abstract_io_spec.rb
CHANGED
@@ -1,12 +1,12 @@
|
|
1
|
-
require File.expand_path('../spec_helper', __FILE__)
|
2
|
-
|
3
|
-
describe ChildProcess::AbstractIO do
|
4
|
-
let(:io) { ChildProcess::AbstractIO.new }
|
5
|
-
|
6
|
-
it "inherits the parent's IO streams" do
|
7
|
-
io.inherit!
|
8
|
-
|
9
|
-
expect(io.stdout).to eq STDOUT
|
10
|
-
expect(io.stderr).to eq STDERR
|
11
|
-
end
|
12
|
-
end
|
1
|
+
require File.expand_path('../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
describe ChildProcess::AbstractIO do
|
4
|
+
let(:io) { ChildProcess::AbstractIO.new }
|
5
|
+
|
6
|
+
it "inherits the parent's IO streams" do
|
7
|
+
io.inherit!
|
8
|
+
|
9
|
+
expect(io.stdout).to eq STDOUT
|
10
|
+
expect(io.stderr).to eq STDERR
|
11
|
+
end
|
12
|
+
end
|
data/spec/childprocess_spec.rb
CHANGED
@@ -1,391 +1,447 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
require File.expand_path('../spec_helper', __FILE__)
|
4
|
-
require 'rubygems/mock_gem_ui'
|
5
|
-
|
6
|
-
|
7
|
-
describe ChildProcess do
|
8
|
-
|
9
|
-
here = File.dirname(__FILE__)
|
10
|
-
|
11
|
-
let(:gemspec) { eval(File.read "#{here}/../childprocess.gemspec") }
|
12
|
-
|
13
|
-
it 'validates cleanly' do
|
14
|
-
mock_ui = Gem::MockGemUi.new
|
15
|
-
Gem::DefaultUserInteraction.use_ui(mock_ui) { gemspec.validate }
|
16
|
-
|
17
|
-
expect(mock_ui.error).to_not match(/warn/i)
|
18
|
-
end
|
19
|
-
|
20
|
-
|
21
|
-
it "returns self when started" do
|
22
|
-
process = sleeping_ruby
|
23
|
-
|
24
|
-
expect(process.start).to eq process
|
25
|
-
expect(process).to be_alive
|
26
|
-
end
|
27
|
-
|
28
|
-
# We can't detect failure to execve() when using posix_spawn() on Linux
|
29
|
-
# without waiting for the child to exit with code 127.
|
30
|
-
#
|
31
|
-
# See e.g. http://repo.or.cz/w/glibc.git/blob/669704fd:/sysdeps/posix/spawni.c#l34
|
32
|
-
#
|
33
|
-
# We could work around this by doing the PATH search ourselves, but not sure
|
34
|
-
# it's worth it.
|
35
|
-
it "raises ChildProcess::LaunchError if the process can't be started", :posix_spawn_on_linux => false do
|
36
|
-
expect { invalid_process.start }.to raise_error(ChildProcess::LaunchError)
|
37
|
-
end
|
38
|
-
|
39
|
-
it 'raises ArgumentError if given a non-string argument' do
|
40
|
-
expect { ChildProcess.build(nil, "unlikelytoexist") }.to raise_error(ArgumentError)
|
41
|
-
expect { ChildProcess.build("foo", 1) }.to raise_error(ArgumentError)
|
42
|
-
end
|
43
|
-
|
44
|
-
it "knows if the process crashed" do
|
45
|
-
process = exit_with(1).start
|
46
|
-
process.wait
|
47
|
-
|
48
|
-
expect(process).to be_crashed
|
49
|
-
end
|
50
|
-
|
51
|
-
it "knows if the process didn't crash" do
|
52
|
-
process = exit_with(0).start
|
53
|
-
process.wait
|
54
|
-
|
55
|
-
expect(process).to_not be_crashed
|
56
|
-
end
|
57
|
-
|
58
|
-
it "can wait for a process to finish" do
|
59
|
-
process = exit_with(0).start
|
60
|
-
return_value = process.wait
|
61
|
-
|
62
|
-
expect(process).to_not be_alive
|
63
|
-
expect(return_value).to eq 0
|
64
|
-
end
|
65
|
-
|
66
|
-
it 'ignores #wait if process already finished' do
|
67
|
-
process = exit_with(0).start
|
68
|
-
sleep 0.01 until process.exited?
|
69
|
-
|
70
|
-
expect(process.wait).to eql 0
|
71
|
-
end
|
72
|
-
|
73
|
-
it "escalates if TERM is ignored" do
|
74
|
-
process = ignored('TERM').start
|
75
|
-
process.stop
|
76
|
-
expect(process).to be_exited
|
77
|
-
end
|
78
|
-
|
79
|
-
it "accepts a timeout argument to #stop" do
|
80
|
-
process = sleeping_ruby.start
|
81
|
-
process.stop(exit_timeout)
|
82
|
-
end
|
83
|
-
|
84
|
-
it "lets child process inherit the environment of the current process" do
|
85
|
-
Tempfile.open("env-spec") do |file|
|
86
|
-
|
87
|
-
|
88
|
-
process.
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
end
|
191
|
-
|
192
|
-
it "
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
end
|
233
|
-
|
234
|
-
it "can
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
process
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
end
|
279
|
-
|
280
|
-
it '
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
end
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
describe '
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
it
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require File.expand_path('../spec_helper', __FILE__)
|
4
|
+
require 'rubygems/mock_gem_ui'
|
5
|
+
|
6
|
+
|
7
|
+
describe ChildProcess do
|
8
|
+
|
9
|
+
here = File.dirname(__FILE__)
|
10
|
+
|
11
|
+
let(:gemspec) { eval(File.read "#{here}/../childprocess.gemspec") }
|
12
|
+
|
13
|
+
it 'validates cleanly' do
|
14
|
+
mock_ui = Gem::MockGemUi.new
|
15
|
+
Gem::DefaultUserInteraction.use_ui(mock_ui) { gemspec.validate }
|
16
|
+
|
17
|
+
expect(mock_ui.error).to_not match(/warn/i)
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
it "returns self when started" do
|
22
|
+
process = sleeping_ruby
|
23
|
+
|
24
|
+
expect(process.start).to eq process
|
25
|
+
expect(process).to be_alive
|
26
|
+
end
|
27
|
+
|
28
|
+
# We can't detect failure to execve() when using posix_spawn() on Linux
|
29
|
+
# without waiting for the child to exit with code 127.
|
30
|
+
#
|
31
|
+
# See e.g. http://repo.or.cz/w/glibc.git/blob/669704fd:/sysdeps/posix/spawni.c#l34
|
32
|
+
#
|
33
|
+
# We could work around this by doing the PATH search ourselves, but not sure
|
34
|
+
# it's worth it.
|
35
|
+
it "raises ChildProcess::LaunchError if the process can't be started", :posix_spawn_on_linux => false do
|
36
|
+
expect { invalid_process.start }.to raise_error(ChildProcess::LaunchError)
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'raises ArgumentError if given a non-string argument' do
|
40
|
+
expect { ChildProcess.build(nil, "unlikelytoexist") }.to raise_error(ArgumentError)
|
41
|
+
expect { ChildProcess.build("foo", 1) }.to raise_error(ArgumentError)
|
42
|
+
end
|
43
|
+
|
44
|
+
it "knows if the process crashed" do
|
45
|
+
process = exit_with(1).start
|
46
|
+
process.wait
|
47
|
+
|
48
|
+
expect(process).to be_crashed
|
49
|
+
end
|
50
|
+
|
51
|
+
it "knows if the process didn't crash" do
|
52
|
+
process = exit_with(0).start
|
53
|
+
process.wait
|
54
|
+
|
55
|
+
expect(process).to_not be_crashed
|
56
|
+
end
|
57
|
+
|
58
|
+
it "can wait for a process to finish" do
|
59
|
+
process = exit_with(0).start
|
60
|
+
return_value = process.wait
|
61
|
+
|
62
|
+
expect(process).to_not be_alive
|
63
|
+
expect(return_value).to eq 0
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'ignores #wait if process already finished' do
|
67
|
+
process = exit_with(0).start
|
68
|
+
sleep 0.01 until process.exited?
|
69
|
+
|
70
|
+
expect(process.wait).to eql 0
|
71
|
+
end
|
72
|
+
|
73
|
+
it "escalates if TERM is ignored" do
|
74
|
+
process = ignored('TERM').start
|
75
|
+
process.stop
|
76
|
+
expect(process).to be_exited
|
77
|
+
end
|
78
|
+
|
79
|
+
it "accepts a timeout argument to #stop" do
|
80
|
+
process = sleeping_ruby.start
|
81
|
+
process.stop(exit_timeout)
|
82
|
+
end
|
83
|
+
|
84
|
+
it "lets child process inherit the environment of the current process" do
|
85
|
+
Tempfile.open("env-spec") do |file|
|
86
|
+
file.close
|
87
|
+
with_env('INHERITED' => 'yes') do
|
88
|
+
process = write_env(file.path).start
|
89
|
+
process.wait
|
90
|
+
end
|
91
|
+
|
92
|
+
file.open
|
93
|
+
child_env = eval rewind_and_read(file)
|
94
|
+
expect(child_env['INHERITED']).to eql 'yes'
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
it "can override env vars only for the current process" do
|
99
|
+
Tempfile.open("env-spec") do |file|
|
100
|
+
file.close
|
101
|
+
process = write_env(file.path)
|
102
|
+
process.environment['CHILD_ONLY'] = '1'
|
103
|
+
process.start
|
104
|
+
|
105
|
+
expect(ENV['CHILD_ONLY']).to be_nil
|
106
|
+
|
107
|
+
process.wait
|
108
|
+
|
109
|
+
file.open
|
110
|
+
child_env = eval rewind_and_read(file)
|
111
|
+
expect(child_env['CHILD_ONLY']).to eql '1'
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'allows unicode characters in the environment' do
|
116
|
+
Tempfile.open("env-spec") do |file|
|
117
|
+
file.close
|
118
|
+
process = write_env(file.path)
|
119
|
+
process.environment['FOö'] = 'baör'
|
120
|
+
process.start
|
121
|
+
process.wait
|
122
|
+
|
123
|
+
file.open
|
124
|
+
child_env = eval rewind_and_read(file)
|
125
|
+
|
126
|
+
expect(child_env['FOö']).to eql 'baör'
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
it "inherits the parent's env vars also when some are overridden" do
|
131
|
+
Tempfile.open("env-spec") do |file|
|
132
|
+
file.close
|
133
|
+
with_env('INHERITED' => 'yes', 'CHILD_ONLY' => 'no') do
|
134
|
+
process = write_env(file.path)
|
135
|
+
process.environment['CHILD_ONLY'] = 'yes'
|
136
|
+
|
137
|
+
process.start
|
138
|
+
process.wait
|
139
|
+
|
140
|
+
file.open
|
141
|
+
child_env = eval rewind_and_read(file)
|
142
|
+
|
143
|
+
expect(child_env['INHERITED']).to eq 'yes'
|
144
|
+
expect(child_env['CHILD_ONLY']).to eq 'yes'
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
it "can unset env vars" do
|
150
|
+
Tempfile.open("env-spec") do |file|
|
151
|
+
file.close
|
152
|
+
ENV['CHILDPROCESS_UNSET'] = '1'
|
153
|
+
process = write_env(file.path)
|
154
|
+
process.environment['CHILDPROCESS_UNSET'] = nil
|
155
|
+
process.start
|
156
|
+
|
157
|
+
process.wait
|
158
|
+
|
159
|
+
file.open
|
160
|
+
child_env = eval rewind_and_read(file)
|
161
|
+
expect(child_env).to_not have_key('CHILDPROCESS_UNSET')
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
it 'does not see env vars unset in parent' do
|
166
|
+
Tempfile.open('env-spec') do |file|
|
167
|
+
file.close
|
168
|
+
ENV['CHILDPROCESS_UNSET'] = nil
|
169
|
+
process = write_env(file.path)
|
170
|
+
process.start
|
171
|
+
|
172
|
+
process.wait
|
173
|
+
|
174
|
+
file.open
|
175
|
+
child_env = eval rewind_and_read(file)
|
176
|
+
expect(child_env).to_not have_key('CHILDPROCESS_UNSET')
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
|
181
|
+
it "passes arguments to the child" do
|
182
|
+
args = ["foo", "bar"]
|
183
|
+
|
184
|
+
Tempfile.open("argv-spec") do |file|
|
185
|
+
process = write_argv(file.path, *args).start
|
186
|
+
process.wait
|
187
|
+
|
188
|
+
expect(rewind_and_read(file)).to eql args.inspect
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
it "lets a detached child live on" do
|
193
|
+
p_pid = nil
|
194
|
+
c_pid = nil
|
195
|
+
|
196
|
+
Tempfile.open('grandparent_out') do |gp_file|
|
197
|
+
# Create a parent and detached child process that will spit out their PID. Make sure that the child process lasts longer than the parent.
|
198
|
+
p_process = ruby("require 'childprocess' ; c_process = ChildProcess.build('ruby', '-e', 'puts \\\"Child PID: \#{Process.pid}\\\" ; sleep 5') ; c_process.io.inherit! ; c_process.detach = true ; c_process.start ; puts \"Child PID: \#{c_process.pid}\" ; puts \"Parent PID: \#{Process.pid}\"")
|
199
|
+
p_process.io.stdout = p_process.io.stderr = gp_file
|
200
|
+
|
201
|
+
# Let the parent process die
|
202
|
+
p_process.start
|
203
|
+
p_process.wait
|
204
|
+
|
205
|
+
|
206
|
+
# Gather parent and child PIDs
|
207
|
+
pids = rewind_and_read(gp_file).split("\n")
|
208
|
+
pids.collect! { |pid| pid[/\d+/].to_i }
|
209
|
+
c_pid, p_pid = pids
|
210
|
+
end
|
211
|
+
|
212
|
+
# Check that the parent process has dies but the child process is still alive
|
213
|
+
expect(alive?(p_pid)).to_not be true
|
214
|
+
expect(alive?(c_pid)).to be true
|
215
|
+
end
|
216
|
+
|
217
|
+
it "preserves Dir.pwd in the child" do
|
218
|
+
Tempfile.open("dir-spec-out") do |file|
|
219
|
+
process = ruby("print Dir.pwd")
|
220
|
+
process.io.stdout = process.io.stderr = file
|
221
|
+
|
222
|
+
expected_dir = nil
|
223
|
+
Dir.chdir(Dir.tmpdir) do
|
224
|
+
expected_dir = Dir.pwd
|
225
|
+
process.start
|
226
|
+
end
|
227
|
+
|
228
|
+
process.wait
|
229
|
+
|
230
|
+
expect(rewind_and_read(file)).to eq expected_dir
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
it "can handle whitespace, special characters and quotes in arguments" do
|
235
|
+
args = ["foo bar", 'foo\bar', "'i-am-quoted'", '"i am double quoted"']
|
236
|
+
|
237
|
+
Tempfile.open("argv-spec") do |file|
|
238
|
+
process = write_argv(file.path, *args).start
|
239
|
+
process.wait
|
240
|
+
|
241
|
+
expect(rewind_and_read(file)).to eq args.inspect
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
it 'handles whitespace in the executable name' do
|
246
|
+
path = File.expand_path('foo bar')
|
247
|
+
|
248
|
+
with_executable_at(path) do |proc|
|
249
|
+
expect(proc.start).to eq proc
|
250
|
+
expect(proc).to be_alive
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
it "times out when polling for exit" do
|
255
|
+
process = sleeping_ruby.start
|
256
|
+
expect { process.poll_for_exit(0.1) }.to raise_error(ChildProcess::TimeoutError)
|
257
|
+
end
|
258
|
+
|
259
|
+
it "can change working directory" do
|
260
|
+
process = ruby "print Dir.pwd"
|
261
|
+
|
262
|
+
with_tmpdir { |dir|
|
263
|
+
process.cwd = dir
|
264
|
+
|
265
|
+
orig_pwd = Dir.pwd
|
266
|
+
|
267
|
+
Tempfile.open('cwd') do |file|
|
268
|
+
process.io.stdout = file
|
269
|
+
|
270
|
+
process.start
|
271
|
+
process.wait
|
272
|
+
|
273
|
+
expect(rewind_and_read(file)).to eq dir
|
274
|
+
end
|
275
|
+
|
276
|
+
expect(Dir.pwd).to eq orig_pwd
|
277
|
+
}
|
278
|
+
end
|
279
|
+
|
280
|
+
it 'kills the full process tree', :process_builder => false do
|
281
|
+
Tempfile.open('kill-process-tree') do |file|
|
282
|
+
process = write_pid_in_sleepy_grand_child(file.path)
|
283
|
+
process.leader = true
|
284
|
+
process.start
|
285
|
+
|
286
|
+
pid = wait_until(30) do
|
287
|
+
Integer(rewind_and_read(file)) rescue nil
|
288
|
+
end
|
289
|
+
|
290
|
+
process.stop
|
291
|
+
wait_until(3) { expect(alive?(pid)).to eql(false) }
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
it 'releases the GIL while waiting for the process' do
|
296
|
+
time = Time.now
|
297
|
+
threads = []
|
298
|
+
|
299
|
+
threads << Thread.new { sleeping_ruby(1).start.wait }
|
300
|
+
threads << Thread.new(time) { expect(Time.now - time).to be < 0.5 }
|
301
|
+
|
302
|
+
threads.each { |t| t.join }
|
303
|
+
end
|
304
|
+
|
305
|
+
it 'can check if a detached child is alive' do
|
306
|
+
proc = ruby_process("-e", "sleep")
|
307
|
+
proc.detach = true
|
308
|
+
|
309
|
+
proc.start
|
310
|
+
|
311
|
+
expect(proc).to be_alive
|
312
|
+
proc.stop(0)
|
313
|
+
|
314
|
+
expect(proc).to be_exited
|
315
|
+
end
|
316
|
+
|
317
|
+
describe 'OS detection' do
|
318
|
+
|
319
|
+
before(:all) do
|
320
|
+
# Save off original OS so that it can be restored later
|
321
|
+
@original_host_os = RbConfig::CONFIG['host_os']
|
322
|
+
end
|
323
|
+
|
324
|
+
after(:each) do
|
325
|
+
# Restore things to the real OS instead of the fake test OS
|
326
|
+
RbConfig::CONFIG['host_os'] = @original_host_os
|
327
|
+
ChildProcess.instance_variable_set(:@os, nil)
|
328
|
+
end
|
329
|
+
|
330
|
+
|
331
|
+
# TODO: add tests for other OSs
|
332
|
+
context 'on a BSD system' do
|
333
|
+
|
334
|
+
let(:bsd_patterns) { ['bsd', 'dragonfly'] }
|
335
|
+
|
336
|
+
it 'correctly identifies BSD systems' do
|
337
|
+
bsd_patterns.each do |pattern|
|
338
|
+
RbConfig::CONFIG['host_os'] = pattern
|
339
|
+
ChildProcess.instance_variable_set(:@os, nil)
|
340
|
+
|
341
|
+
expect(ChildProcess.os).to eq(:bsd)
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
end
|
346
|
+
|
347
|
+
end
|
348
|
+
|
349
|
+
it 'has a logger' do
|
350
|
+
expect(ChildProcess).to respond_to(:logger)
|
351
|
+
end
|
352
|
+
|
353
|
+
it 'can change its logger' do
|
354
|
+
expect(ChildProcess).to respond_to(:logger=)
|
355
|
+
|
356
|
+
original_logger = ChildProcess.logger
|
357
|
+
begin
|
358
|
+
ChildProcess.logger = :some_other_logger
|
359
|
+
expect(ChildProcess.logger).to eq(:some_other_logger)
|
360
|
+
ensure
|
361
|
+
ChildProcess.logger = original_logger
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
|
366
|
+
describe 'logger' do
|
367
|
+
|
368
|
+
before(:each) do
|
369
|
+
ChildProcess.logger = logger
|
370
|
+
end
|
371
|
+
|
372
|
+
after(:all) do
|
373
|
+
ChildProcess.logger = nil
|
374
|
+
end
|
375
|
+
|
376
|
+
|
377
|
+
context 'with the default logger' do
|
378
|
+
|
379
|
+
let(:logger) { nil }
|
380
|
+
|
381
|
+
|
382
|
+
it 'logs at INFO level by default' do
|
383
|
+
expect(ChildProcess.logger.level).to eq(Logger::INFO)
|
384
|
+
end
|
385
|
+
|
386
|
+
it 'logs at DEBUG level by default if $DEBUG is on' do
|
387
|
+
original_debug = $DEBUG
|
388
|
+
|
389
|
+
begin
|
390
|
+
$DEBUG = true
|
391
|
+
|
392
|
+
expect(ChildProcess.logger.level).to eq(Logger::DEBUG)
|
393
|
+
ensure
|
394
|
+
$DEBUG = original_debug
|
395
|
+
end
|
396
|
+
end
|
397
|
+
|
398
|
+
it "logs to stderr by default" do
|
399
|
+
cap = capture_std { generate_log_messages }
|
400
|
+
|
401
|
+
expect(cap.stdout).to be_empty
|
402
|
+
expect(cap.stderr).to_not be_empty
|
403
|
+
end
|
404
|
+
|
405
|
+
end
|
406
|
+
|
407
|
+
context 'with a custom logger' do
|
408
|
+
|
409
|
+
let(:logger) { Logger.new($stdout) }
|
410
|
+
|
411
|
+
it "logs to configured logger" do
|
412
|
+
cap = capture_std { generate_log_messages }
|
413
|
+
|
414
|
+
expect(cap.stdout).to_not be_empty
|
415
|
+
expect(cap.stderr).to be_empty
|
416
|
+
end
|
417
|
+
|
418
|
+
end
|
419
|
+
|
420
|
+
end
|
421
|
+
|
422
|
+
describe '#started?' do
|
423
|
+
subject { process.started? }
|
424
|
+
|
425
|
+
context 'when not started' do
|
426
|
+
let(:process) { sleeping_ruby(1) }
|
427
|
+
|
428
|
+
it { is_expected.to be false }
|
429
|
+
end
|
430
|
+
|
431
|
+
context 'when started' do
|
432
|
+
let(:process) { sleeping_ruby(1).start }
|
433
|
+
|
434
|
+
it { is_expected.to be true }
|
435
|
+
end
|
436
|
+
|
437
|
+
context 'when finished' do
|
438
|
+
before(:each) { process.wait }
|
439
|
+
|
440
|
+
let(:process) { sleeping_ruby(0).start }
|
441
|
+
|
442
|
+
it { is_expected.to be true }
|
443
|
+
end
|
444
|
+
|
445
|
+
end
|
446
|
+
|
447
|
+
end
|