childprocess 0.5.9 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.document +6 -6
  3. data/.gitignore +25 -25
  4. data/.rspec +1 -1
  5. data/.travis.yml +20 -18
  6. data/CHANGELOG.md +8 -0
  7. data/Gemfile +11 -4
  8. data/LICENSE +20 -20
  9. data/README.md +178 -178
  10. data/Rakefile +61 -61
  11. data/childprocess.gemspec +30 -29
  12. data/lib/childprocess.rb +184 -177
  13. data/lib/childprocess/abstract_io.rb +36 -36
  14. data/lib/childprocess/abstract_process.rb +187 -187
  15. data/lib/childprocess/errors.rb +26 -26
  16. data/lib/childprocess/jruby.rb +56 -56
  17. data/lib/childprocess/jruby/io.rb +16 -16
  18. data/lib/childprocess/jruby/process.rb +159 -159
  19. data/lib/childprocess/jruby/pump.rb +52 -52
  20. data/lib/childprocess/tools/generator.rb +145 -145
  21. data/lib/childprocess/unix.rb +9 -9
  22. data/lib/childprocess/unix/fork_exec_process.rb +70 -70
  23. data/lib/childprocess/unix/io.rb +21 -21
  24. data/lib/childprocess/unix/lib.rb +186 -186
  25. data/lib/childprocess/unix/platform/i386-linux.rb +12 -12
  26. data/lib/childprocess/unix/platform/i386-solaris.rb +11 -11
  27. data/lib/childprocess/unix/platform/x86_64-linux.rb +12 -12
  28. data/lib/childprocess/unix/platform/x86_64-macosx.rb +11 -11
  29. data/lib/childprocess/unix/posix_spawn_process.rb +134 -134
  30. data/lib/childprocess/unix/process.rb +89 -89
  31. data/lib/childprocess/version.rb +3 -3
  32. data/lib/childprocess/windows.rb +33 -33
  33. data/lib/childprocess/windows/handle.rb +91 -91
  34. data/lib/childprocess/windows/io.rb +25 -25
  35. data/lib/childprocess/windows/lib.rb +415 -415
  36. data/lib/childprocess/windows/process.rb +129 -129
  37. data/lib/childprocess/windows/process_builder.rb +174 -174
  38. data/lib/childprocess/windows/structs.rb +148 -148
  39. data/spec/abstract_io_spec.rb +12 -12
  40. data/spec/childprocess_spec.rb +291 -256
  41. data/spec/io_spec.rb +228 -228
  42. data/spec/jruby_spec.rb +24 -24
  43. data/spec/pid_behavior.rb +12 -12
  44. data/spec/spec_helper.rb +253 -253
  45. data/spec/unix_spec.rb +57 -57
  46. data/spec/windows_spec.rb +23 -23
  47. metadata +47 -45
@@ -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
@@ -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
@@ -1,256 +1,291 @@
1
- # encoding: utf-8
2
-
3
- require File.expand_path('../spec_helper', __FILE__)
4
-
5
- describe ChildProcess do
6
- it "returns self when started" do
7
- process = sleeping_ruby
8
-
9
- expect(process.start).to eq process
10
- expect(process).to be_alive
11
- end
12
-
13
- # We can't detect failure to execve() when using posix_spawn() on Linux
14
- # without waiting for the child to exit with code 127.
15
- #
16
- # See e.g. http://repo.or.cz/w/glibc.git/blob/669704fd:/sysdeps/posix/spawni.c#l34
17
- #
18
- # We could work around this by doing the PATH search ourselves, but not sure
19
- # it's worth it.
20
- it "raises ChildProcess::LaunchError if the process can't be started", :posix_spawn_on_linux => false do
21
- expect { invalid_process.start }.to raise_error(ChildProcess::LaunchError)
22
- end
23
-
24
- it 'raises ArgumentError if given a non-string argument' do
25
- expect { ChildProcess.build(nil, "unlikelytoexist") }.to raise_error(ArgumentError)
26
- expect { ChildProcess.build("foo", 1) }.to raise_error(ArgumentError)
27
- end
28
-
29
- it "knows if the process crashed" do
30
- process = exit_with(1).start
31
- process.wait
32
-
33
- expect(process).to be_crashed
34
- end
35
-
36
- it "knows if the process didn't crash" do
37
- process = exit_with(0).start
38
- process.wait
39
-
40
- expect(process).to_not be_crashed
41
- end
42
-
43
- it "can wait for a process to finish" do
44
- process = exit_with(0).start
45
- return_value = process.wait
46
-
47
- expect(process).to_not be_alive
48
- expect(return_value).to eq 0
49
- end
50
-
51
- it 'ignores #wait if process already finished' do
52
- process = exit_with(0).start
53
- sleep 0.01 until process.exited?
54
-
55
- expect(process.wait).to eql 0
56
- end
57
-
58
- it "escalates if TERM is ignored" do
59
- process = ignored('TERM').start
60
- process.stop
61
- expect(process).to be_exited
62
- end
63
-
64
- it "accepts a timeout argument to #stop" do
65
- process = sleeping_ruby.start
66
- process.stop(exit_timeout)
67
- end
68
-
69
- it "lets child process inherit the environment of the current process" do
70
- Tempfile.open("env-spec") do |file|
71
- with_env('INHERITED' => 'yes') do
72
- process = write_env(file.path).start
73
- process.wait
74
- end
75
-
76
- child_env = eval rewind_and_read(file)
77
- expect(child_env['INHERITED']).to eql 'yes'
78
- end
79
- end
80
-
81
- it "can override env vars only for the current process" do
82
- Tempfile.open("env-spec") do |file|
83
- process = write_env(file.path)
84
- process.environment['CHILD_ONLY'] = '1'
85
- process.start
86
-
87
- expect(ENV['CHILD_ONLY']).to be_nil
88
-
89
- process.wait
90
-
91
- child_env = eval rewind_and_read(file)
92
- expect(child_env['CHILD_ONLY']).to eql '1'
93
- end
94
- end
95
-
96
- it "inherits the parent's env vars also when some are overridden" do
97
- Tempfile.open("env-spec") do |file|
98
- with_env('INHERITED' => 'yes', 'CHILD_ONLY' => 'no') do
99
- process = write_env(file.path)
100
- process.environment['CHILD_ONLY'] = 'yes'
101
-
102
- process.start
103
- process.wait
104
-
105
- child_env = eval rewind_and_read(file)
106
-
107
- expect(child_env['INHERITED']).to eq 'yes'
108
- expect(child_env['CHILD_ONLY']).to eq 'yes'
109
- end
110
- end
111
- end
112
-
113
- it "can unset env vars" do
114
- Tempfile.open("env-spec") do |file|
115
- ENV['CHILDPROCESS_UNSET'] = '1'
116
- process = write_env(file.path)
117
- process.environment['CHILDPROCESS_UNSET'] = nil
118
- process.start
119
-
120
- process.wait
121
-
122
- child_env = eval rewind_and_read(file)
123
- expect(child_env).to_not have_key('CHILDPROCESS_UNSET')
124
- end
125
- end
126
-
127
- it 'does not see env vars unset in parent' do
128
- Tempfile.open('env-spec') do |file|
129
- ENV['CHILDPROCESS_UNSET'] = nil
130
- process = write_env(file.path)
131
- process.start
132
-
133
- process.wait
134
-
135
- child_env = eval rewind_and_read(file)
136
- expect(child_env).to_not have_key('CHILDPROCESS_UNSET')
137
- end
138
- end
139
-
140
-
141
- it "passes arguments to the child" do
142
- args = ["foo", "bar"]
143
-
144
- Tempfile.open("argv-spec") do |file|
145
- process = write_argv(file.path, *args).start
146
- process.wait
147
-
148
- expect(rewind_and_read(file)).to eql args.inspect
149
- end
150
- end
151
-
152
- it "lets a detached child live on" do
153
- pending "how do we spec this?"
154
- fail
155
- end
156
-
157
- it "preserves Dir.pwd in the child" do
158
- Tempfile.open("dir-spec-out") do |file|
159
- process = ruby("print Dir.pwd")
160
- process.io.stdout = process.io.stderr = file
161
-
162
- expected_dir = nil
163
- Dir.chdir(Dir.tmpdir) do
164
- expected_dir = Dir.pwd
165
- process.start
166
- end
167
-
168
- process.wait
169
-
170
- expect(rewind_and_read(file)).to eq expected_dir
171
- end
172
- end
173
-
174
- it "can handle whitespace, special characters and quotes in arguments" do
175
- args = ["foo bar", 'foo\bar', "'i-am-quoted'", '"i am double quoted"']
176
-
177
- Tempfile.open("argv-spec") do |file|
178
- process = write_argv(file.path, *args).start
179
- process.wait
180
-
181
- expect(rewind_and_read(file)).to eq args.inspect
182
- end
183
- end
184
-
185
- it 'handles whitespace in the executable name' do
186
- path = File.expand_path('foo bar')
187
-
188
- with_executable_at(path) do |proc|
189
- expect(proc.start).to eq proc
190
- expect(proc).to be_alive
191
- end
192
- end
193
-
194
- it "times out when polling for exit" do
195
- process = sleeping_ruby.start
196
- expect { process.poll_for_exit(0.1) }.to raise_error(ChildProcess::TimeoutError)
197
- end
198
-
199
- it "can change working directory" do
200
- process = ruby "print Dir.pwd"
201
-
202
- with_tmpdir { |dir|
203
- process.cwd = dir
204
-
205
- orig_pwd = Dir.pwd
206
-
207
- Tempfile.open('cwd') do |file|
208
- process.io.stdout = file
209
-
210
- process.start
211
- process.wait
212
-
213
- expect(rewind_and_read(file)).to eq dir
214
- end
215
-
216
- expect(Dir.pwd).to eq orig_pwd
217
- }
218
- end
219
-
220
- it 'kills the full process tree', :process_builder => false do
221
- Tempfile.open('kill-process-tree') do |file|
222
- process = write_pid_in_sleepy_grand_child(file.path)
223
- process.leader = true
224
- process.start
225
-
226
- pid = wait_until(30) do
227
- Integer(rewind_and_read(file)) rescue nil
228
- end
229
-
230
- process.stop
231
- wait_until(3) { expect(alive?(pid)).to eql(false) }
232
- end
233
- end
234
-
235
- it 'releases the GIL while waiting for the process' do
236
- time = Time.now
237
- threads = []
238
-
239
- threads << Thread.new { sleeping_ruby(1).start.wait }
240
- threads << Thread.new(time) { expect(Time.now - time).to be < 0.5 }
241
-
242
- threads.each { |t| t.join }
243
- end
244
-
245
- it 'can check if a detached child is alive' do
246
- proc = ruby_process("-e", "sleep")
247
- proc.detach = true
248
-
249
- proc.start
250
-
251
- expect(proc).to be_alive
252
- proc.stop(0)
253
-
254
- expect(proc).to be_exited
255
- end
256
- end
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
+ with_env('INHERITED' => 'yes') do
87
+ process = write_env(file.path).start
88
+ process.wait
89
+ end
90
+
91
+ child_env = eval rewind_and_read(file)
92
+ expect(child_env['INHERITED']).to eql 'yes'
93
+ end
94
+ end
95
+
96
+ it "can override env vars only for the current process" do
97
+ Tempfile.open("env-spec") do |file|
98
+ process = write_env(file.path)
99
+ process.environment['CHILD_ONLY'] = '1'
100
+ process.start
101
+
102
+ expect(ENV['CHILD_ONLY']).to be_nil
103
+
104
+ process.wait
105
+
106
+ child_env = eval rewind_and_read(file)
107
+ expect(child_env['CHILD_ONLY']).to eql '1'
108
+ end
109
+ end
110
+
111
+ it "inherits the parent's env vars also when some are overridden" do
112
+ Tempfile.open("env-spec") do |file|
113
+ with_env('INHERITED' => 'yes', 'CHILD_ONLY' => 'no') do
114
+ process = write_env(file.path)
115
+ process.environment['CHILD_ONLY'] = 'yes'
116
+
117
+ process.start
118
+ process.wait
119
+
120
+ child_env = eval rewind_and_read(file)
121
+
122
+ expect(child_env['INHERITED']).to eq 'yes'
123
+ expect(child_env['CHILD_ONLY']).to eq 'yes'
124
+ end
125
+ end
126
+ end
127
+
128
+ it "can unset env vars" do
129
+ Tempfile.open("env-spec") do |file|
130
+ ENV['CHILDPROCESS_UNSET'] = '1'
131
+ process = write_env(file.path)
132
+ process.environment['CHILDPROCESS_UNSET'] = nil
133
+ process.start
134
+
135
+ process.wait
136
+
137
+ child_env = eval rewind_and_read(file)
138
+ expect(child_env).to_not have_key('CHILDPROCESS_UNSET')
139
+ end
140
+ end
141
+
142
+ it 'does not see env vars unset in parent' do
143
+ Tempfile.open('env-spec') do |file|
144
+ ENV['CHILDPROCESS_UNSET'] = nil
145
+ process = write_env(file.path)
146
+ process.start
147
+
148
+ process.wait
149
+
150
+ child_env = eval rewind_and_read(file)
151
+ expect(child_env).to_not have_key('CHILDPROCESS_UNSET')
152
+ end
153
+ end
154
+
155
+
156
+ it "passes arguments to the child" do
157
+ args = ["foo", "bar"]
158
+
159
+ Tempfile.open("argv-spec") do |file|
160
+ process = write_argv(file.path, *args).start
161
+ process.wait
162
+
163
+ expect(rewind_and_read(file)).to eql args.inspect
164
+ end
165
+ end
166
+
167
+ it "lets a detached child live on" do
168
+ p_pid = nil
169
+ c_pid = nil
170
+
171
+ Tempfile.open('grandparent_out') do |gp_file|
172
+ # Create a parent and detached child process that will spit out their PID. Make sure that the child process lasts longer than the parent.
173
+ 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}\"")
174
+ p_process.io.stdout = p_process.io.stderr = gp_file
175
+
176
+ # Let the parent process die
177
+ p_process.start
178
+ p_process.wait
179
+
180
+
181
+ # Gather parent and child PIDs
182
+ pids = rewind_and_read(gp_file).split("\n")
183
+ pids.collect! { |pid| pid[/\d+/].to_i }
184
+ c_pid, p_pid = pids
185
+ end
186
+
187
+ # Check that the parent process has dies but the child process is still alive
188
+ expect(alive?(p_pid)).to_not be true
189
+ expect(alive?(c_pid)).to be true
190
+ end
191
+
192
+ it "preserves Dir.pwd in the child" do
193
+ Tempfile.open("dir-spec-out") do |file|
194
+ process = ruby("print Dir.pwd")
195
+ process.io.stdout = process.io.stderr = file
196
+
197
+ expected_dir = nil
198
+ Dir.chdir(Dir.tmpdir) do
199
+ expected_dir = Dir.pwd
200
+ process.start
201
+ end
202
+
203
+ process.wait
204
+
205
+ expect(rewind_and_read(file)).to eq expected_dir
206
+ end
207
+ end
208
+
209
+ it "can handle whitespace, special characters and quotes in arguments" do
210
+ args = ["foo bar", 'foo\bar', "'i-am-quoted'", '"i am double quoted"']
211
+
212
+ Tempfile.open("argv-spec") do |file|
213
+ process = write_argv(file.path, *args).start
214
+ process.wait
215
+
216
+ expect(rewind_and_read(file)).to eq args.inspect
217
+ end
218
+ end
219
+
220
+ it 'handles whitespace in the executable name' do
221
+ path = File.expand_path('foo bar')
222
+
223
+ with_executable_at(path) do |proc|
224
+ expect(proc.start).to eq proc
225
+ expect(proc).to be_alive
226
+ end
227
+ end
228
+
229
+ it "times out when polling for exit" do
230
+ process = sleeping_ruby.start
231
+ expect { process.poll_for_exit(0.1) }.to raise_error(ChildProcess::TimeoutError)
232
+ end
233
+
234
+ it "can change working directory" do
235
+ process = ruby "print Dir.pwd"
236
+
237
+ with_tmpdir { |dir|
238
+ process.cwd = dir
239
+
240
+ orig_pwd = Dir.pwd
241
+
242
+ Tempfile.open('cwd') do |file|
243
+ process.io.stdout = file
244
+
245
+ process.start
246
+ process.wait
247
+
248
+ expect(rewind_and_read(file)).to eq dir
249
+ end
250
+
251
+ expect(Dir.pwd).to eq orig_pwd
252
+ }
253
+ end
254
+
255
+ it 'kills the full process tree', :process_builder => false do
256
+ Tempfile.open('kill-process-tree') do |file|
257
+ process = write_pid_in_sleepy_grand_child(file.path)
258
+ process.leader = true
259
+ process.start
260
+
261
+ pid = wait_until(30) do
262
+ Integer(rewind_and_read(file)) rescue nil
263
+ end
264
+
265
+ process.stop
266
+ wait_until(3) { expect(alive?(pid)).to eql(false) }
267
+ end
268
+ end
269
+
270
+ it 'releases the GIL while waiting for the process' do
271
+ time = Time.now
272
+ threads = []
273
+
274
+ threads << Thread.new { sleeping_ruby(1).start.wait }
275
+ threads << Thread.new(time) { expect(Time.now - time).to be < 0.5 }
276
+
277
+ threads.each { |t| t.join }
278
+ end
279
+
280
+ it 'can check if a detached child is alive' do
281
+ proc = ruby_process("-e", "sleep")
282
+ proc.detach = true
283
+
284
+ proc.start
285
+
286
+ expect(proc).to be_alive
287
+ proc.stop(0)
288
+
289
+ expect(proc).to be_exited
290
+ end
291
+ end