childprocess 0.2.5 → 0.2.6

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.
@@ -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