childprocess 0.5.9 → 0.6.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.
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