childprocess 0.2.5 → 0.2.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,170 @@
1
+ module ChildProcess
2
+ module Windows
3
+ class ProcessBuilder
4
+ attr_accessor :inherit, :detach, :duplex, :environment, :stdout, :stderr
5
+ attr_reader :stdin
6
+
7
+ def initialize(args)
8
+ @args = args
9
+
10
+ @inherit = false
11
+ @detach = false
12
+ @duplex = false
13
+ @environment = nil
14
+
15
+ @stdout = nil
16
+ @stderr = nil
17
+ @stdin = nil
18
+
19
+ @flags = 0
20
+ @cmd_ptr = nil
21
+ @env_ptr = nil
22
+ end
23
+
24
+ def start
25
+ create_command_pointer
26
+ create_environment_pointer
27
+
28
+ setup_detach
29
+ setup_io
30
+
31
+ pid = create_process
32
+ close_handles
33
+
34
+ pid
35
+ end
36
+
37
+ private
38
+
39
+ def create_command_pointer
40
+ string = @args.map { |arg| quote_if_necessary(arg.to_s) }.join ' '
41
+ @cmd_ptr = FFI::MemoryPointer.from_string string
42
+ end
43
+
44
+ def create_environment_pointer
45
+ return unless @environment.kind_of?(Hash) && @environment.any?
46
+
47
+ # inherited env
48
+ strings = ENV.map { |k,v| "#{k}=#{v}\0" }
49
+
50
+ # extras
51
+ @environment.each do |key, value|
52
+ if key.include?("=")
53
+ raise InvalidEnvironmentVariableName, key
54
+ end
55
+
56
+ strings << "#{key}=#{value}\0"
57
+ end
58
+
59
+ strings << "\0" # terminate the env block
60
+ env_str = strings.join
61
+
62
+ @env_ptr = FFI::MemoryPointer.new(:long, env_str.bytesize)
63
+ @env_ptr.write_bytes env_str, 0, env_str.bytesize
64
+ end
65
+
66
+ def create_process
67
+ ok = Lib.create_process(
68
+ nil, # application name
69
+ @cmd_ptr, # command line
70
+ nil, # process attributes
71
+ nil, # thread attributes
72
+ @inherit, # inherit handles
73
+ @flags, # creation flags
74
+ @env_ptr, # environment
75
+ nil, # current directory
76
+ startup_info, # startup info
77
+ process_info # process info
78
+ )
79
+
80
+ ok or raise LaunchError, Lib.last_error_message
81
+
82
+ process_info[:dwProcessId]
83
+ end
84
+
85
+ def startup_info
86
+ @startup_info ||= StartupInfo.new
87
+ end
88
+
89
+ def process_info
90
+ @process_info ||= ProcessInfo.new
91
+ end
92
+
93
+ def setup_detach
94
+ @flags |= DETACHED_PROCESS if @detach
95
+ end
96
+
97
+ def setup_io
98
+ if @stdout || @stderr
99
+ startup_info[:dwFlags] ||= 0
100
+ startup_info[:dwFlags] |= STARTF_USESTDHANDLES
101
+
102
+ @inherit = true
103
+
104
+ if @stdout
105
+ startup_info[:hStdOutput] = std_stream_handle_for(@stdout)
106
+ end
107
+
108
+ if @stderr
109
+ startup_info[:hStdError] = std_stream_handle_for(@stderr)
110
+ end
111
+ end
112
+
113
+ setup_stdin if @duplex
114
+ end
115
+
116
+ def setup_stdin
117
+ read_pipe_ptr = FFI::MemoryPointer.new(:pointer)
118
+ write_pipe_ptr = FFI::MemoryPointer.new(:pointer)
119
+ sa = SecurityAttributes.new(:inherit => true)
120
+
121
+ ok = Lib.create_pipe(read_pipe_ptr, write_pipe_ptr, sa, 0)
122
+ Lib.check_error ok
123
+
124
+ @read_pipe = read_pipe_ptr.read_pointer
125
+ @write_pipe = write_pipe_ptr.read_pointer
126
+
127
+ Lib.set_handle_inheritance @write_pipe.address, false
128
+
129
+ startup_info[:hStdInput] = @read_pipe
130
+ end
131
+
132
+ def close_handles
133
+ Lib.close_handle process_info[:hProcess]
134
+ Lib.close_handle process_info[:hThread]
135
+
136
+ if @duplex
137
+ @stdin = Lib.io_for(Lib.duplicate_handle(@write_pipe), File::WRONLY)
138
+ Lib.close_handle @read_pipe
139
+ Lib.close_handle @write_pipe
140
+ end
141
+ end
142
+
143
+ def std_stream_handle_for(io)
144
+ handle = Lib.handle_for(io.fileno)
145
+
146
+ begin
147
+ Lib.set_handle_inheritance handle, true
148
+ rescue ChildProcess::Error
149
+ # If the IO was set to close on exec previously, this call will fail.
150
+ # That's probably OK, since the user explicitly asked for it to be
151
+ # closed (at least I have yet to find other cases where this will
152
+ # happen...)
153
+ end
154
+
155
+ handle
156
+ end
157
+
158
+ def quote_if_necessary(str)
159
+ quote = str.start_with?('"') ? "'" : '"'
160
+
161
+ case str
162
+ when /[\s\\'"]/
163
+ [quote, str, quote].join
164
+ else
165
+ str
166
+ end
167
+ end
168
+ end # ProcessBuilder
169
+ end # Windows
170
+ end # ChildProcess
@@ -1,81 +1,83 @@
1
- module ChildProcess::Windows
2
- # typedef struct _STARTUPINFO {
3
- # DWORD cb;
4
- # LPTSTR lpReserved;
5
- # LPTSTR lpDesktop;
6
- # LPTSTR lpTitle;
7
- # DWORD dwX;
8
- # DWORD dwY;
9
- # DWORD dwXSize;
10
- # DWORD dwYSize;
11
- # DWORD dwXCountChars;
12
- # DWORD dwYCountChars;
13
- # DWORD dwFillAttribute;
14
- # DWORD dwFlags;
15
- # WORD wShowWindow;
16
- # WORD cbReserved2;
17
- # LPBYTE lpReserved2;
18
- # HANDLE hStdInput;
19
- # HANDLE hStdOutput;
20
- # HANDLE hStdError;
21
- # } STARTUPINFO, *LPSTARTUPINFO;
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;
22
23
 
23
- class StartupInfo < FFI::Struct
24
- layout :cb, :ulong,
25
- :lpReserved, :pointer,
26
- :lpDesktop, :pointer,
27
- :lpTitle, :pointer,
28
- :dwX, :ulong,
29
- :dwY, :ulong,
30
- :dwXSize, :ulong,
31
- :dwYSize, :ulong,
32
- :dwXCountChars, :ulong,
33
- :dwYCountChars, :ulong,
34
- :dwFillAttribute, :ulong,
35
- :dwFlags, :ulong,
36
- :wShowWindow, :ushort,
37
- :cbReserved2, :ushort,
38
- :lpReserved2, :pointer,
39
- :hStdInput, :pointer, # void ptr
40
- :hStdOutput, :pointer, # void ptr
41
- :hStdError, :pointer # void ptr
42
- end
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
43
44
 
44
- #
45
- # typedef struct _PROCESS_INFORMATION {
46
- # HANDLE hProcess;
47
- # HANDLE hThread;
48
- # DWORD dwProcessId;
49
- # DWORD dwThreadId;
50
- # } PROCESS_INFORMATION, *LPPROCESS_INFORMATION;
51
- #
45
+ #
46
+ # typedef struct _PROCESS_INFORMATION {
47
+ # HANDLE hProcess;
48
+ # HANDLE hThread;
49
+ # DWORD dwProcessId;
50
+ # DWORD dwThreadId;
51
+ # } PROCESS_INFORMATION, *LPPROCESS_INFORMATION;
52
+ #
52
53
 
53
- class ProcessInfo < FFI::Struct
54
- layout :hProcess, :pointer, # void ptr
55
- :hThread, :pointer, # void ptr
56
- :dwProcessId, :ulong,
57
- :dwThreadId, :ulong
58
- end
54
+ class ProcessInfo < FFI::Struct
55
+ layout :hProcess, :pointer, # void ptr
56
+ :hThread, :pointer, # void ptr
57
+ :dwProcessId, :ulong,
58
+ :dwThreadId, :ulong
59
+ end
59
60
 
60
- #
61
- # typedef struct _SECURITY_ATTRIBUTES {
62
- # DWORD nLength;
63
- # LPVOID lpSecurityDescriptor;
64
- # BOOL bInheritHandle;
65
- # } SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES;
66
- #
61
+ #
62
+ # typedef struct _SECURITY_ATTRIBUTES {
63
+ # DWORD nLength;
64
+ # LPVOID lpSecurityDescriptor;
65
+ # BOOL bInheritHandle;
66
+ # } SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES;
67
+ #
67
68
 
68
- class SecurityAttributes < FFI::Struct
69
- layout :nLength, :ulong,
70
- :lpSecurityDescriptor, :pointer, # void ptr
71
- :bInheritHandle, :int
69
+ class SecurityAttributes < FFI::Struct
70
+ layout :nLength, :ulong,
71
+ :lpSecurityDescriptor, :pointer, # void ptr
72
+ :bInheritHandle, :int
72
73
 
73
- def initialize(opts = {})
74
- super()
74
+ def initialize(opts = {})
75
+ super()
75
76
 
76
- self[:nLength] = self.class.size
77
- self[:lpSecurityDescriptor] = nil
78
- self[:bInheritHandle] = opts[:inherit] ? 1 : 0
77
+ self[:nLength] = self.class.size
78
+ self[:lpSecurityDescriptor] = nil
79
+ self[:bInheritHandle] = opts[:inherit] ? 1 : 0
80
+ end
79
81
  end
80
- end
81
- end
82
+ end # Windows
83
+ end # ChildProcess
@@ -3,8 +3,6 @@
3
3
  require File.expand_path('../spec_helper', __FILE__)
4
4
 
5
5
  describe ChildProcess do
6
- EXIT_TIMEOUT = 10
7
-
8
6
  it "returns self when started" do
9
7
  process = sleeping_ruby
10
8
 
@@ -46,7 +44,7 @@ describe ChildProcess do
46
44
 
47
45
  it "accepts a timeout argument to #stop" do
48
46
  process = sleeping_ruby.start
49
- process.stop(EXIT_TIMEOUT)
47
+ process.stop(exit_timeout)
50
48
  end
51
49
 
52
50
  it "lets child process inherit the environment of the current process" do
@@ -112,102 +110,6 @@ describe ChildProcess do
112
110
  pending "how do we spec this?"
113
111
  end
114
112
 
115
- it "can redirect stdout, stderr" do
116
- process = ruby(<<-CODE)
117
- [STDOUT, STDERR].each_with_index do |io, idx|
118
- io.sync = true
119
- io.puts idx
120
- end
121
-
122
- sleep 0.2
123
- CODE
124
-
125
- out = Tempfile.new("stdout-spec")
126
- err = Tempfile.new("stderr-spec")
127
-
128
- begin
129
- process.io.stdout = out
130
- process.io.stderr = err
131
-
132
- process.start
133
- process.io.stdin.should be_nil
134
- process.wait
135
-
136
- out.rewind
137
- err.rewind
138
-
139
- out.read.should == "0\n"
140
- err.read.should == "1\n"
141
- ensure
142
- out.close
143
- err.close
144
- end
145
- end
146
-
147
- it "can redirect stdout only" do
148
- process = ruby(<<-CODE)
149
- [STDOUT, STDERR].each_with_index do |io, idx|
150
- io.sync = true
151
- io.puts idx
152
- end
153
-
154
- sleep 0.2
155
- CODE
156
-
157
- out = Tempfile.new("stdout-spec")
158
-
159
- begin
160
- process.io.stdout = out
161
-
162
- process.start
163
- process.wait
164
-
165
- out.rewind
166
-
167
- out.read.should == "0\n"
168
- ensure
169
- out.close
170
- end
171
- end
172
-
173
- it "can write to stdin if duplex = true" do
174
- process = cat
175
-
176
- out = Tempfile.new("duplex")
177
- out.sync = true
178
-
179
- begin
180
- process.io.stdout = out
181
- process.io.stderr = out
182
- process.duplex = true
183
-
184
- process.start
185
- process.io.stdin.puts "hello world"
186
- process.io.stdin.close
187
-
188
- process.poll_for_exit(EXIT_TIMEOUT)
189
-
190
- out.rewind
191
- out.read.should == "hello world\n"
192
- ensure
193
- out.close
194
- end
195
- end
196
-
197
- it "can set close-on-exec when IO is inherited" do
198
- server = TCPServer.new("localhost", 4433)
199
- ChildProcess.close_on_exec server
200
-
201
- process = sleeping_ruby
202
- process.io.inherit!
203
-
204
- process.start
205
- sleep 0.5 # give the forked process a chance to exec() (which closes the fd)
206
-
207
- server.close
208
- lambda { TCPServer.new("localhost", 4433).close }.should_not raise_error
209
- end
210
-
211
113
  it "preserves Dir.pwd in the child" do
212
114
  Tempfile.open("dir-spec-out") do |file|
213
115
  process = ruby("print Dir.pwd")
data/spec/io_spec.rb ADDED
@@ -0,0 +1,128 @@
1
+ require File.expand_path('../spec_helper', __FILE__)
2
+
3
+ describe ChildProcess do
4
+ it "can redirect stdout, stderr" do
5
+ process = ruby(<<-CODE)
6
+ [STDOUT, STDERR].each_with_index do |io, idx|
7
+ io.sync = true
8
+ io.puts idx
9
+ end
10
+ CODE
11
+
12
+ out = Tempfile.new("stdout-spec")
13
+ err = Tempfile.new("stderr-spec")
14
+
15
+ begin
16
+ process.io.stdout = out
17
+ process.io.stderr = err
18
+
19
+ process.start
20
+ process.io.stdin.should be_nil
21
+ process.wait
22
+
23
+ out.rewind
24
+ err.rewind
25
+
26
+ out.read.should == "0\n"
27
+ err.read.should == "1\n"
28
+ ensure
29
+ out.close
30
+ err.close
31
+ end
32
+ end
33
+
34
+ it "can redirect stdout only" do
35
+ process = ruby(<<-CODE)
36
+ [STDOUT, STDERR].each_with_index do |io, idx|
37
+ io.sync = true
38
+ io.puts idx
39
+ end
40
+ CODE
41
+
42
+ out = Tempfile.new("stdout-spec")
43
+
44
+ begin
45
+ process.io.stdout = out
46
+
47
+ process.start
48
+ process.wait
49
+
50
+ out.rewind
51
+ out.read.should == "0\n"
52
+ ensure
53
+ out.close
54
+ end
55
+ end
56
+
57
+ it "can write to stdin if duplex = true" do
58
+ process = cat
59
+
60
+ out = Tempfile.new("duplex")
61
+ out.sync = true
62
+
63
+ begin
64
+ process.io.stdout = out
65
+ process.io.stderr = out
66
+ process.duplex = true
67
+
68
+ process.start
69
+ process.io.stdin.puts "hello world"
70
+ process.io.stdin.close
71
+
72
+ process.poll_for_exit(exit_timeout)
73
+
74
+ out.rewind
75
+ out.read.should == "hello world\n"
76
+ ensure
77
+ out.close
78
+ end
79
+ end
80
+
81
+ #
82
+ # this works on JRuby 1.6.5 on my Mac, but for some reason
83
+ # hangs on Travis (running 1.6.5.1 + OpenJDK).
84
+ #
85
+ # http://travis-ci.org/#!/jarib/childprocess/jobs/487331
86
+ #
87
+
88
+ it "works with pipes", :jruby => false do
89
+ process = ruby(<<-CODE)
90
+ STDOUT.puts "stdout"
91
+ STDERR.puts "stderr"
92
+ CODE
93
+
94
+ stdout, stdout_w = IO.pipe
95
+ stderr, stderr_w = IO.pipe
96
+
97
+ process.io.stdout = stdout_w
98
+ process.io.stderr = stderr_w
99
+
100
+ process.duplex = true
101
+
102
+ process.start
103
+ process.wait
104
+
105
+ # write streams are closed *after* the process
106
+ # has exited - otherwise it won't work on JRuby
107
+ # with the current Process implementation
108
+
109
+ stdout_w.close
110
+ stderr_w.close
111
+
112
+ stdout.read.should == "stdout\n"
113
+ stderr.read.should == "stderr\n"
114
+ end
115
+
116
+ it "can set close-on-exec when IO is inherited" do
117
+ server = TCPServer.new("localhost", 4433)
118
+ ChildProcess.close_on_exec server
119
+
120
+ process = sleeping_ruby
121
+ process.io.inherit!
122
+
123
+ process.start
124
+ server.close
125
+
126
+ lambda { TCPServer.new("localhost", 4433).close }.should_not raise_error
127
+ end
128
+ end