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 CHANGED
@@ -18,5 +18,6 @@ coverage
18
18
  rdoc
19
19
  pkg
20
20
  .rbx
21
+ Gemfile.lock
21
22
 
22
23
  ## PROJECT::SPECIFIC
@@ -9,10 +9,8 @@ env:
9
9
  - CHILDPROCESS_POSIX_SPAWN=true
10
10
  - CHILDPROCESS_POSIX_SPAWN=false
11
11
  matrix:
12
- exclude:
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
@@ -1,4 +1,4 @@
1
- Copyright (c) 2010-2012 Jari Bakken
1
+ Copyright (c) 2010-2013 Jari Bakken
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
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
  [![Build Status](https://secure.travis-ci.org/jarib/childprocess.png)](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
- The object returned from ChildProcess.build will implement ChildProcess::AbstractProcess.
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
- future version unintentionally.
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-2012 Jari Bakken. See LICENSE for details.
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'
@@ -36,6 +36,7 @@ module ChildProcess
36
36
  @started = false
37
37
  @exit_code = nil
38
38
  @io = nil
39
+ @cwd = nil
39
40
  @detach = false
40
41
  @duplex = false
41
42
  @environment = {}
@@ -88,14 +88,7 @@ module ChildProcess
88
88
  end
89
89
 
90
90
  if duplex?
91
- stdin = @process.getOutputStream.to_io
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
@@ -33,8 +33,10 @@ module ChildProcess
33
33
  writer.close
34
34
  end
35
35
 
36
+ executable, *args = @args
37
+
36
38
  begin
37
- exec(*@args)
39
+ exec([executable, executable], *args)
38
40
  rescue SystemCallError => ex
39
41
  exec_w << ex.message
40
42
  end
@@ -1,3 +1,3 @@
1
1
  module ChildProcess
2
- VERSION = "0.3.6"
2
+ VERSION = "0.3.7"
3
3
  end
@@ -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)
@@ -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
- out_receiver.rewind
128
- out_receiver.read.should == "hello\nnew\nworld\n"
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
@@ -97,10 +97,8 @@ module ChildProcessSpecHelper
97
97
  def cat
98
98
  if ChildProcess.os == :windows
99
99
  ruby(<<-CODE)
100
- STDIN.sync = true
101
- STDOUT.sync = true
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
- return if yield
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
- raise "timed out"
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.6
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: 2012-10-17 00:00:00.000000000 Z
12
+ date: 2013-01-23 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec