childprocess 0.3.6 → 0.3.7
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.
- data/.gitignore +1 -0
- data/.travis.yml +1 -3
- data/LICENSE +1 -1
- data/README.md +93 -20
- data/Rakefile +0 -1
- data/lib/childprocess/abstract_process.rb +1 -0
- data/lib/childprocess/jruby/process.rb +26 -8
- data/lib/childprocess/unix/fork_exec_process.rb +3 -1
- data/lib/childprocess/version.rb +1 -1
- data/spec/childprocess_spec.rb +9 -0
- data/spec/io_spec.rb +29 -18
- data/spec/spec_helper.rb +37 -6
- metadata +2 -2
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
@@ -9,10 +9,8 @@ env:
|
|
9
9
|
- CHILDPROCESS_POSIX_SPAWN=true
|
10
10
|
- CHILDPROCESS_POSIX_SPAWN=false
|
11
11
|
matrix:
|
12
|
-
|
12
|
+
allow_failures:
|
13
13
|
- rvm: jruby
|
14
14
|
env: CHILDPROCESS_POSIX_SPAWN=true
|
15
15
|
- rvm: rbx
|
16
16
|
env: CHILDPROCESS_POSIX_SPAWN=true
|
17
|
-
allow_failures:
|
18
|
-
- rvm: ruby-head
|
data/LICENSE
CHANGED
data/README.md
CHANGED
@@ -1,16 +1,19 @@
|
|
1
|
-
childprocess
|
2
|
-
============
|
1
|
+
# childprocess
|
3
2
|
|
4
3
|
This gem aims at being a simple and reliable solution for controlling
|
5
4
|
external programs running in the background on any Ruby / OS combination.
|
6
5
|
|
7
|
-
The code originated in the selenium-webdriver gem, but should prove useful as
|
6
|
+
The code originated in the [selenium-webdriver](https://rubygems.org/gems/selenium-webdriver) gem, but should prove useful as
|
8
7
|
a standalone library.
|
9
8
|
|
10
9
|
[](http://travis-ci.org/jarib/childprocess)
|
11
10
|
|
12
|
-
Usage
|
13
|
-
|
11
|
+
# Usage
|
12
|
+
|
13
|
+
The object returned from `ChildProcess.build` will implement `ChildProcess::AbstractProcess`.
|
14
|
+
|
15
|
+
### Basic examples
|
16
|
+
|
14
17
|
```ruby
|
15
18
|
process = ChildProcess.build("ruby", "-e", "sleep")
|
16
19
|
|
@@ -24,6 +27,9 @@ process.io.stdout = Tempfile.new("child-output")
|
|
24
27
|
process.environment["a"] = "b"
|
25
28
|
process.environment["c"] = nil
|
26
29
|
|
30
|
+
# set the child's working directory
|
31
|
+
process.cwd = '/some/path'
|
32
|
+
|
27
33
|
# start the process
|
28
34
|
process.start
|
29
35
|
|
@@ -35,6 +41,9 @@ process.exited? #=> false
|
|
35
41
|
process.wait
|
36
42
|
process.exited? #=> true
|
37
43
|
|
44
|
+
# get the exit code
|
45
|
+
process.exit_code #=> 0
|
46
|
+
|
38
47
|
# ...or poll for exit + force quit
|
39
48
|
begin
|
40
49
|
process.poll_for_exit(10)
|
@@ -43,29 +52,93 @@ rescue ChildProcess::TimeoutError
|
|
43
52
|
end
|
44
53
|
```
|
45
54
|
|
46
|
-
|
55
|
+
### Advanced examples
|
56
|
+
|
57
|
+
#### Output to pipe
|
58
|
+
|
59
|
+
```ruby
|
60
|
+
r, w = IO.pipe
|
61
|
+
|
62
|
+
proc = ChildProcess.build("echo", "foo")
|
63
|
+
proc.io.stdout = proc.io.stderr = w
|
64
|
+
proc.start
|
65
|
+
proc.wait
|
66
|
+
|
67
|
+
w.close
|
68
|
+
r.read #=> "test\n"
|
69
|
+
```
|
70
|
+
|
71
|
+
#### Write to stdin
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
process = ChildProcess.build("cat")
|
75
|
+
|
76
|
+
out = Tempfile.new("duplex")
|
77
|
+
out.sync = true
|
78
|
+
|
79
|
+
process.io.stdout = process.io.stderr = out
|
80
|
+
process.duplex = true # sets up pipe so process.io.stdin will be available after .start
|
81
|
+
|
82
|
+
process.start
|
83
|
+
process.io.stdin.puts "hello world"
|
84
|
+
process.io.stdin.close
|
85
|
+
|
86
|
+
process.poll_for_exit(exit_timeout_in_seconds)
|
87
|
+
|
88
|
+
out.rewind
|
89
|
+
out.read #=> "hello world\n"
|
90
|
+
```
|
91
|
+
|
92
|
+
#### Pipe output to another ChildProcess
|
93
|
+
|
94
|
+
```ruby
|
95
|
+
search = ChildProcess.build("grep", '-E', %w(redis memcached).join('|'))
|
96
|
+
search.duplex = true # sets up pipe so search.io.stdin will be available after .start
|
97
|
+
search.io.stdout = $stdout
|
98
|
+
search.start
|
99
|
+
|
100
|
+
listing = ChildProcess.build("ps", "aux")
|
101
|
+
listing.io.stdout = search.io.stdin
|
102
|
+
listing.start
|
103
|
+
listing.wait
|
104
|
+
|
105
|
+
search.io.stdin.close
|
106
|
+
search.wait
|
107
|
+
```
|
108
|
+
|
109
|
+
#### Prefer posix_spawn on *nix
|
110
|
+
|
111
|
+
If the parent process is using a lot of memory, `fork+exec` can be very expensive. The `posix_spawn()` API removes this overhead.
|
112
|
+
|
113
|
+
```ruby
|
114
|
+
ChildProcess.posix_spawn = true
|
115
|
+
process = ChildProcess.build(*args)
|
116
|
+
```
|
117
|
+
|
118
|
+
#### Detach from parent
|
119
|
+
|
120
|
+
```ruby
|
121
|
+
process = ChildProcess.build("sleep", "10")
|
122
|
+
process.detach = true
|
123
|
+
process.start
|
124
|
+
```
|
47
125
|
|
48
|
-
Implementation
|
49
|
-
--------------
|
126
|
+
# Implementation
|
50
127
|
|
51
128
|
How the process is launched and killed depends on the platform:
|
52
129
|
|
53
|
-
* Unix : fork + exec (or posix_spawn if enabled)
|
54
|
-
* Windows : CreateProcess and friends
|
55
|
-
* JRuby : java.lang.{Process,ProcessBuilder}
|
130
|
+
* Unix : `fork + exec` (or `posix_spawn` if enabled)
|
131
|
+
* Windows : `CreateProcess()` and friends
|
132
|
+
* JRuby : `java.lang.{Process,ProcessBuilder}`
|
56
133
|
|
57
|
-
Note on Patches/Pull Requests
|
58
|
-
-----------------------------
|
134
|
+
# Note on Patches/Pull Requests
|
59
135
|
|
60
136
|
* Fork the project.
|
61
137
|
* Make your feature addition or bug fix.
|
62
|
-
* Add tests for it. This is important so I don't break it in a
|
63
|
-
|
64
|
-
* Commit, do not mess with rakefile, version, or history.
|
65
|
-
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
138
|
+
* Add tests for it. This is important so I don't break it in a future version unintentionally.
|
139
|
+
* Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
66
140
|
* Send me a pull request. Bonus points for topic branches.
|
67
141
|
|
68
|
-
Copyright
|
69
|
-
---------
|
142
|
+
# Copyright
|
70
143
|
|
71
|
-
Copyright (c) 2010-
|
144
|
+
Copyright (c) 2010-2013 Jari Bakken. See LICENSE for details.
|
data/Rakefile
CHANGED
@@ -11,7 +11,6 @@ require 'rspec/core/rake_task'
|
|
11
11
|
RSpec::Core::RakeTask.new(:spec) do |spec|
|
12
12
|
spec.ruby_opts = "-I lib:spec"
|
13
13
|
spec.pattern = 'spec/**/*_spec.rb'
|
14
|
-
spec.rcov_opts = %w[--exclude spec,ruby-debug,/Library/Ruby,.gem --include lib/childprocess]
|
15
14
|
end
|
16
15
|
|
17
16
|
desc 'Run specs for rcov'
|
@@ -88,14 +88,7 @@ module ChildProcess
|
|
88
88
|
end
|
89
89
|
|
90
90
|
if duplex?
|
91
|
-
|
92
|
-
stdin.sync = true
|
93
|
-
stdin.instance_variable_set(:@java_stream, @process.getOutputStream)
|
94
|
-
def stdin.__flushit; @java_stream.flush; end #The stream provided is a BufferedeOutputStream, so we have to flush it to make the bytes flow to the process
|
95
|
-
def stdin.flush; super; self.__flushit; end
|
96
|
-
def stdin.puts(*args); super(*args); self.__flushit; end
|
97
|
-
|
98
|
-
io._stdin = stdin
|
91
|
+
io._stdin = create_stdin
|
99
92
|
else
|
100
93
|
@process.getOutputStream.close
|
101
94
|
end
|
@@ -120,6 +113,31 @@ module ChildProcess
|
|
120
113
|
end
|
121
114
|
end
|
122
115
|
|
116
|
+
def create_stdin
|
117
|
+
output_stream = @process.getOutputStream
|
118
|
+
|
119
|
+
stdin = output_stream.to_io
|
120
|
+
stdin.sync = true
|
121
|
+
stdin.instance_variable_set(:@childprocess_java_stream, output_stream)
|
122
|
+
|
123
|
+
class << stdin
|
124
|
+
# The stream provided is a BufferedeOutputStream, so we
|
125
|
+
# have to flush it to make the bytes flow to the process
|
126
|
+
def __childprocess_flush__
|
127
|
+
@childprocess_java_stream.flush
|
128
|
+
end
|
129
|
+
|
130
|
+
[:flush, :print, :printf, :putc, :puts, :write, :write_nonblock].each do |m|
|
131
|
+
define_method(m) do |*args|
|
132
|
+
super(*args)
|
133
|
+
self.__childprocess_flush__
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
stdin
|
139
|
+
end
|
140
|
+
|
123
141
|
end # Process
|
124
142
|
end # JRuby
|
125
143
|
end # ChildProcess
|
data/lib/childprocess/version.rb
CHANGED
data/spec/childprocess_spec.rb
CHANGED
@@ -163,6 +163,15 @@ describe ChildProcess do
|
|
163
163
|
end
|
164
164
|
end
|
165
165
|
|
166
|
+
it 'handles whitespace in the executable name' do
|
167
|
+
path = File.expand_path('foo bar')
|
168
|
+
|
169
|
+
with_executable_at(path) do |proc|
|
170
|
+
proc.start.should == proc
|
171
|
+
proc.should be_started
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
166
175
|
it "times out when polling for exit" do
|
167
176
|
process = sleeping_ruby.start
|
168
177
|
lambda { process.poll_for_exit(0.1) }.should raise_error(ChildProcess::TimeoutError)
|
data/spec/io_spec.rb
CHANGED
@@ -57,15 +57,15 @@ describe ChildProcess do
|
|
57
57
|
it "pumps all output" do
|
58
58
|
10.times do |i|
|
59
59
|
process = echo
|
60
|
-
|
60
|
+
|
61
61
|
out = Tempfile.new("duplex")
|
62
|
-
|
62
|
+
|
63
63
|
begin
|
64
64
|
process.io.stdout = out
|
65
|
-
|
65
|
+
|
66
66
|
process.start
|
67
67
|
process.poll_for_exit(exit_timeout)
|
68
|
-
|
68
|
+
|
69
69
|
out.rewind
|
70
70
|
out.read.should == "hello\n"
|
71
71
|
ensure
|
@@ -111,21 +111,32 @@ describe ChildProcess do
|
|
111
111
|
process.duplex = true
|
112
112
|
|
113
113
|
process.start
|
114
|
-
process.io.stdin.puts "hello"
|
115
|
-
sleep 0.1
|
116
|
-
out_receiver.read.should == "hello\n"
|
117
|
-
process.io.stdin.puts "new"
|
118
|
-
sleep 0.1
|
119
|
-
out_receiver.read.should == "new\n"
|
120
|
-
process.io.stdin.write "world\n"
|
121
|
-
process.io.stdin.flush
|
122
|
-
sleep 0.1
|
123
|
-
out_receiver.read.should == "world\n"
|
124
|
-
process.io.stdin.close
|
125
|
-
process.poll_for_exit(exit_timeout)
|
126
114
|
|
127
|
-
|
128
|
-
|
115
|
+
stdin = process.io.stdin
|
116
|
+
lf = ChildProcess.windows? ? "\r\n" : "\n"
|
117
|
+
|
118
|
+
stdin.puts "hello"
|
119
|
+
stdin.flush
|
120
|
+
wait_until { rewind_and_read(out_receiver).should == "hello#{lf}" }
|
121
|
+
|
122
|
+
stdin.putc "n"
|
123
|
+
stdin.flush
|
124
|
+
wait_until { rewind_and_read(out_receiver).should == "hello#{lf}n" }
|
125
|
+
|
126
|
+
stdin.print "e"
|
127
|
+
stdin.flush
|
128
|
+
wait_until { rewind_and_read(out_receiver).should == "hello#{lf}ne" }
|
129
|
+
|
130
|
+
stdin.printf "w"
|
131
|
+
stdin.flush
|
132
|
+
wait_until { rewind_and_read(out_receiver).should == "hello#{lf}new" }
|
133
|
+
|
134
|
+
stdin.write "\nworld\n"
|
135
|
+
stdin.flush
|
136
|
+
wait_until { rewind_and_read(out_receiver).should == "hello#{lf}new#{lf}world#{lf}" }
|
137
|
+
|
138
|
+
stdin.close
|
139
|
+
process.poll_for_exit(exit_timeout)
|
129
140
|
ensure
|
130
141
|
out_receiver.close
|
131
142
|
out.close
|
data/spec/spec_helper.rb
CHANGED
@@ -97,10 +97,8 @@ module ChildProcessSpecHelper
|
|
97
97
|
def cat
|
98
98
|
if ChildProcess.os == :windows
|
99
99
|
ruby(<<-CODE)
|
100
|
-
STDIN.sync
|
101
|
-
|
102
|
-
|
103
|
-
puts STDIN.read
|
100
|
+
STDIN.sync = STDOUT.sync = true
|
101
|
+
IO.copy_stream(STDIN, STDOUT)
|
104
102
|
CODE
|
105
103
|
else
|
106
104
|
ChildProcess.build("cat")
|
@@ -124,6 +122,25 @@ module ChildProcessSpecHelper
|
|
124
122
|
ruby_process(tmp_script(code))
|
125
123
|
end
|
126
124
|
|
125
|
+
def with_executable_at(path, &blk)
|
126
|
+
if ChildProcess.os == :windows
|
127
|
+
path << ".cmd"
|
128
|
+
content = "@echo foo"
|
129
|
+
else
|
130
|
+
content = "#!/bin/sh\necho foo"
|
131
|
+
end
|
132
|
+
|
133
|
+
File.open(path, 'w', 0744) { |io| io << content }
|
134
|
+
proc = ChildProcess.build(path)
|
135
|
+
|
136
|
+
begin
|
137
|
+
yield proc
|
138
|
+
ensure
|
139
|
+
proc.stop if proc.alive?
|
140
|
+
File.delete path
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
127
144
|
def exit_timeout
|
128
145
|
10
|
129
146
|
end
|
@@ -149,13 +166,22 @@ module ChildProcessSpecHelper
|
|
149
166
|
|
150
167
|
def wait_until(timeout = 10, &blk)
|
151
168
|
end_time = Time.now + timeout
|
169
|
+
last_exception = nil
|
152
170
|
|
153
171
|
until Time.now >= end_time
|
154
|
-
|
172
|
+
begin
|
173
|
+
return if yield
|
174
|
+
rescue RSpec::Expectations::ExpectationNotMetError => ex
|
175
|
+
last_exception = ex
|
176
|
+
end
|
177
|
+
|
155
178
|
sleep 0.05
|
156
179
|
end
|
157
180
|
|
158
|
-
|
181
|
+
msg = "timed out after #{timeout} seconds"
|
182
|
+
msg << ":\n#{last_exception.message}" if last_exception
|
183
|
+
|
184
|
+
raise msg
|
159
185
|
end
|
160
186
|
|
161
187
|
def can_bind?(host, port)
|
@@ -165,6 +191,11 @@ module ChildProcessSpecHelper
|
|
165
191
|
false
|
166
192
|
end
|
167
193
|
|
194
|
+
def rewind_and_read(io)
|
195
|
+
io.rewind
|
196
|
+
io.read
|
197
|
+
end
|
198
|
+
|
168
199
|
end # ChildProcessSpecHelper
|
169
200
|
|
170
201
|
Thread.abort_on_exception = true
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: childprocess
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.7
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2013-01-23 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|