childprocess 0.8.0 → 2.0.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.
- checksums.yaml +5 -5
- data/.document +6 -6
- data/.gitignore +28 -28
- data/.rspec +1 -1
- data/.travis.yml +42 -36
- data/CHANGELOG.md +67 -44
- data/Gemfile +18 -15
- data/LICENSE +20 -20
- data/README.md +216 -192
- data/Rakefile +61 -61
- data/appveyor.yml +42 -43
- data/childprocess.gemspec +32 -30
- data/ext/mkrf_conf.rb +24 -0
- data/lib/childprocess/abstract_io.rb +36 -36
- data/lib/childprocess/abstract_process.rb +192 -192
- data/lib/childprocess/errors.rb +37 -26
- data/lib/childprocess/jruby/io.rb +16 -16
- data/lib/childprocess/jruby/process.rb +184 -159
- data/lib/childprocess/jruby/pump.rb +53 -53
- data/lib/childprocess/jruby.rb +56 -56
- data/lib/childprocess/tools/generator.rb +145 -145
- data/lib/childprocess/unix/fork_exec_process.rb +78 -70
- data/lib/childprocess/unix/io.rb +21 -21
- data/lib/childprocess/unix/lib.rb +186 -186
- data/lib/childprocess/unix/platform/i386-linux.rb +12 -12
- data/lib/childprocess/unix/platform/i386-solaris.rb +11 -11
- data/lib/childprocess/unix/platform/x86_64-linux.rb +12 -12
- data/lib/childprocess/unix/platform/x86_64-macosx.rb +11 -11
- data/lib/childprocess/unix/posix_spawn_process.rb +134 -134
- data/lib/childprocess/unix/process.rb +90 -89
- data/lib/childprocess/unix.rb +9 -9
- data/lib/childprocess/version.rb +3 -3
- data/lib/childprocess/windows/handle.rb +91 -91
- data/lib/childprocess/windows/io.rb +25 -25
- data/lib/childprocess/windows/lib.rb +416 -416
- data/lib/childprocess/windows/process.rb +130 -130
- data/lib/childprocess/windows/process_builder.rb +178 -175
- data/lib/childprocess/windows/structs.rb +148 -148
- data/lib/childprocess/windows.rb +33 -33
- data/lib/childprocess.rb +210 -205
- data/spec/abstract_io_spec.rb +12 -12
- data/spec/childprocess_spec.rb +447 -391
- data/spec/get_env.ps1 +13 -0
- data/spec/io_spec.rb +228 -228
- data/spec/jruby_spec.rb +24 -24
- data/spec/pid_behavior.rb +12 -12
- data/spec/platform_detection_spec.rb +86 -86
- data/spec/spec_helper.rb +270 -261
- data/spec/unix_spec.rb +57 -57
- data/spec/windows_spec.rb +23 -23
- metadata +18 -33
data/spec/spec_helper.rb
CHANGED
@@ -1,261 +1,270 @@
|
|
1
|
-
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
2
|
-
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
3
|
-
|
4
|
-
unless defined?(JRUBY_VERSION)
|
5
|
-
require 'coveralls'
|
6
|
-
Coveralls.wear!
|
7
|
-
end
|
8
|
-
|
9
|
-
require 'childprocess'
|
10
|
-
require 'rspec'
|
11
|
-
require 'tempfile'
|
12
|
-
require 'socket'
|
13
|
-
require 'stringio'
|
14
|
-
require 'ostruct'
|
15
|
-
|
16
|
-
module ChildProcessSpecHelper
|
17
|
-
RUBY = defined?(Gem) ? Gem.ruby : 'ruby'
|
18
|
-
|
19
|
-
def ruby_process(*args)
|
20
|
-
@process = ChildProcess.build(RUBY , *args)
|
21
|
-
end
|
22
|
-
|
23
|
-
def
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
end
|
34
|
-
|
35
|
-
def
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
code
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
end
|
88
|
-
|
89
|
-
def
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
1
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
2
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
3
|
+
|
4
|
+
unless defined?(JRUBY_VERSION)
|
5
|
+
require 'coveralls'
|
6
|
+
Coveralls.wear!
|
7
|
+
end
|
8
|
+
|
9
|
+
require 'childprocess'
|
10
|
+
require 'rspec'
|
11
|
+
require 'tempfile'
|
12
|
+
require 'socket'
|
13
|
+
require 'stringio'
|
14
|
+
require 'ostruct'
|
15
|
+
|
16
|
+
module ChildProcessSpecHelper
|
17
|
+
RUBY = defined?(Gem) ? Gem.ruby : 'ruby'
|
18
|
+
|
19
|
+
def ruby_process(*args)
|
20
|
+
@process = ChildProcess.build(RUBY , *args)
|
21
|
+
end
|
22
|
+
|
23
|
+
def windows_process(*args)
|
24
|
+
@process = ChildProcess.build("powershell", *args)
|
25
|
+
end
|
26
|
+
|
27
|
+
def sleeping_ruby(seconds = nil)
|
28
|
+
if seconds
|
29
|
+
ruby_process("-e", "sleep #{seconds}")
|
30
|
+
else
|
31
|
+
ruby_process("-e", "sleep")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def invalid_process
|
36
|
+
@process = ChildProcess.build("unlikelytoexist")
|
37
|
+
end
|
38
|
+
|
39
|
+
def ignored(signal)
|
40
|
+
code = <<-RUBY
|
41
|
+
trap(#{signal.inspect}, "IGNORE")
|
42
|
+
sleep
|
43
|
+
RUBY
|
44
|
+
|
45
|
+
ruby_process tmp_script(code)
|
46
|
+
end
|
47
|
+
|
48
|
+
def write_env(path)
|
49
|
+
if ChildProcess.os == :windows
|
50
|
+
ps_env_file_path = File.expand_path(File.dirname(__FILE__))
|
51
|
+
args = ['-File', "#{ps_env_file_path}/get_env.ps1", path]
|
52
|
+
windows_process(*args)
|
53
|
+
else
|
54
|
+
code = <<-RUBY
|
55
|
+
File.open(#{path.inspect}, "w") { |f| f << ENV.inspect }
|
56
|
+
RUBY
|
57
|
+
ruby_process tmp_script(code)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def write_argv(path, *args)
|
62
|
+
code = <<-RUBY
|
63
|
+
File.open(#{path.inspect}, "w") { |f| f << ARGV.inspect }
|
64
|
+
RUBY
|
65
|
+
|
66
|
+
ruby_process(tmp_script(code), *args)
|
67
|
+
end
|
68
|
+
|
69
|
+
def write_pid(path)
|
70
|
+
code = <<-RUBY
|
71
|
+
File.open(#{path.inspect}, "w") { |f| f << Process.pid }
|
72
|
+
RUBY
|
73
|
+
|
74
|
+
ruby_process tmp_script(code)
|
75
|
+
end
|
76
|
+
|
77
|
+
def write_pid_in_sleepy_grand_child(path)
|
78
|
+
code = <<-RUBY
|
79
|
+
system "ruby", "-e", 'File.open(#{path.inspect}, "w") { |f| f << Process.pid; f.flush }; sleep'
|
80
|
+
RUBY
|
81
|
+
|
82
|
+
ruby_process tmp_script(code)
|
83
|
+
end
|
84
|
+
|
85
|
+
def exit_with(exit_code)
|
86
|
+
ruby_process(tmp_script("exit(#{exit_code})"))
|
87
|
+
end
|
88
|
+
|
89
|
+
def with_env(hash)
|
90
|
+
hash.each { |k,v| ENV[k] = v }
|
91
|
+
begin
|
92
|
+
yield
|
93
|
+
ensure
|
94
|
+
hash.each_key { |k| ENV[k] = nil }
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def tmp_script(code)
|
99
|
+
# use an ivar to avoid GC
|
100
|
+
@tf = Tempfile.new("childprocess-temp")
|
101
|
+
@tf << code
|
102
|
+
@tf.close
|
103
|
+
|
104
|
+
puts code if $DEBUG
|
105
|
+
|
106
|
+
@tf.path
|
107
|
+
end
|
108
|
+
|
109
|
+
def cat
|
110
|
+
if ChildProcess.os == :windows
|
111
|
+
ruby(<<-CODE)
|
112
|
+
STDIN.sync = STDOUT.sync = true
|
113
|
+
IO.copy_stream(STDIN, STDOUT)
|
114
|
+
CODE
|
115
|
+
else
|
116
|
+
ChildProcess.build("cat")
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def echo
|
121
|
+
if ChildProcess.os == :windows
|
122
|
+
ruby(<<-CODE)
|
123
|
+
STDIN.sync = true
|
124
|
+
STDOUT.sync = true
|
125
|
+
|
126
|
+
puts "hello"
|
127
|
+
CODE
|
128
|
+
else
|
129
|
+
ChildProcess.build("echo", "hello")
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def ruby(code)
|
134
|
+
ruby_process(tmp_script(code))
|
135
|
+
end
|
136
|
+
|
137
|
+
def with_executable_at(path, &blk)
|
138
|
+
if ChildProcess.os == :windows
|
139
|
+
path << ".cmd"
|
140
|
+
content = "#{RUBY} -e 'sleep 10' \n @echo foo"
|
141
|
+
else
|
142
|
+
content = "#!/bin/sh\nsleep 10\necho foo"
|
143
|
+
end
|
144
|
+
|
145
|
+
File.open(path, 'w', 0744) { |io| io << content }
|
146
|
+
proc = ChildProcess.build(path)
|
147
|
+
|
148
|
+
begin
|
149
|
+
yield proc
|
150
|
+
ensure
|
151
|
+
proc.stop if proc.alive?
|
152
|
+
File.delete path
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def exit_timeout
|
157
|
+
10
|
158
|
+
end
|
159
|
+
|
160
|
+
def random_free_port
|
161
|
+
server = TCPServer.new('127.0.0.1', 0)
|
162
|
+
port = server.addr[1]
|
163
|
+
server.close
|
164
|
+
|
165
|
+
port
|
166
|
+
end
|
167
|
+
|
168
|
+
def with_tmpdir(&blk)
|
169
|
+
name = "#{Time.now.strftime("%Y%m%d")}-#{$$}-#{rand(0x100000000).to_s(36)}"
|
170
|
+
FileUtils.mkdir_p(name)
|
171
|
+
|
172
|
+
begin
|
173
|
+
yield File.expand_path(name)
|
174
|
+
ensure
|
175
|
+
FileUtils.rm_rf name
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def wait_until(timeout = 10, &blk)
|
180
|
+
end_time = Time.now + timeout
|
181
|
+
last_exception = nil
|
182
|
+
|
183
|
+
until Time.now >= end_time
|
184
|
+
begin
|
185
|
+
result = yield
|
186
|
+
return result if result
|
187
|
+
rescue RSpec::Expectations::ExpectationNotMetError => ex
|
188
|
+
last_exception = ex
|
189
|
+
end
|
190
|
+
|
191
|
+
sleep 0.01
|
192
|
+
end
|
193
|
+
|
194
|
+
msg = "timed out after #{timeout} seconds"
|
195
|
+
msg << ":\n#{last_exception.message}" if last_exception
|
196
|
+
|
197
|
+
raise msg
|
198
|
+
end
|
199
|
+
|
200
|
+
def can_bind?(host, port)
|
201
|
+
TCPServer.new(host, port).close
|
202
|
+
true
|
203
|
+
rescue
|
204
|
+
false
|
205
|
+
end
|
206
|
+
|
207
|
+
def rewind_and_read(io)
|
208
|
+
io.rewind
|
209
|
+
io.read
|
210
|
+
end
|
211
|
+
|
212
|
+
def alive?(pid)
|
213
|
+
if ChildProcess.windows?
|
214
|
+
ChildProcess::Windows::Lib.alive?(pid)
|
215
|
+
else
|
216
|
+
begin
|
217
|
+
Process.getpgid pid
|
218
|
+
true
|
219
|
+
rescue Errno::ESRCH
|
220
|
+
false
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
def capture_std
|
226
|
+
orig_out = STDOUT.clone
|
227
|
+
orig_err = STDERR.clone
|
228
|
+
|
229
|
+
out = Tempfile.new 'captured-stdout'
|
230
|
+
err = Tempfile.new 'captured-stderr'
|
231
|
+
out.sync = true
|
232
|
+
err.sync = true
|
233
|
+
|
234
|
+
STDOUT.reopen out
|
235
|
+
STDERR.reopen err
|
236
|
+
|
237
|
+
yield
|
238
|
+
|
239
|
+
OpenStruct.new stdout: rewind_and_read(out), stderr: rewind_and_read(err)
|
240
|
+
ensure
|
241
|
+
STDOUT.reopen orig_out
|
242
|
+
STDERR.reopen orig_err
|
243
|
+
end
|
244
|
+
|
245
|
+
def generate_log_messages
|
246
|
+
ChildProcess.logger.level = Logger::DEBUG
|
247
|
+
|
248
|
+
process = exit_with(0).start
|
249
|
+
process.wait
|
250
|
+
process.poll_for_exit(0.1)
|
251
|
+
end
|
252
|
+
|
253
|
+
end # ChildProcessSpecHelper
|
254
|
+
|
255
|
+
Thread.abort_on_exception = true
|
256
|
+
|
257
|
+
RSpec.configure do |c|
|
258
|
+
c.include(ChildProcessSpecHelper)
|
259
|
+
c.after(:each) {
|
260
|
+
defined?(@process) && @process.alive? && @process.stop
|
261
|
+
}
|
262
|
+
|
263
|
+
if ChildProcess.jruby? && ChildProcess.new("true").instance_of?(ChildProcess::JRuby::Process)
|
264
|
+
c.filter_run_excluding :process_builder => false
|
265
|
+
end
|
266
|
+
|
267
|
+
if ChildProcess.linux? && ChildProcess.posix_spawn?
|
268
|
+
c.filter_run_excluding :posix_spawn_on_linux => false
|
269
|
+
end
|
270
|
+
end
|
data/spec/unix_spec.rb
CHANGED
@@ -1,57 +1,57 @@
|
|
1
|
-
require File.expand_path('../spec_helper', __FILE__)
|
2
|
-
require "pid_behavior"
|
3
|
-
|
4
|
-
if ChildProcess.unix? && !ChildProcess.jruby? && !ChildProcess.posix_spawn?
|
5
|
-
|
6
|
-
describe ChildProcess::Unix::Process do
|
7
|
-
it_behaves_like "a platform that provides the child's pid"
|
8
|
-
|
9
|
-
it "handles ECHILD race condition where process dies between timeout and KILL" do
|
10
|
-
process = sleeping_ruby
|
11
|
-
|
12
|
-
allow(process).to receive(:fork).and_return('fakepid')
|
13
|
-
allow(process).to receive(:send_term)
|
14
|
-
allow(process).to receive(:poll_for_exit).and_raise(ChildProcess::TimeoutError)
|
15
|
-
allow(process).to receive(:send_kill).and_raise(Errno::ECHILD.new)
|
16
|
-
|
17
|
-
process.start
|
18
|
-
expect { process.stop }.not_to raise_error
|
19
|
-
|
20
|
-
allow(process).to receive(:alive?).and_return(false)
|
21
|
-
|
22
|
-
process.send(:send_signal, 'TERM')
|
23
|
-
end
|
24
|
-
|
25
|
-
it "handles ESRCH race condition where process dies between timeout and KILL" do
|
26
|
-
process = sleeping_ruby
|
27
|
-
|
28
|
-
allow(process).to receive(:fork).and_return('fakepid')
|
29
|
-
allow(process).to receive(:send_term)
|
30
|
-
allow(process).to receive(:poll_for_exit).and_raise(ChildProcess::TimeoutError)
|
31
|
-
allow(process).to receive(:send_kill).and_raise(Errno::ESRCH.new)
|
32
|
-
|
33
|
-
process.start
|
34
|
-
expect { process.stop }.not_to raise_error
|
35
|
-
|
36
|
-
allow(process).to receive(:alive?).and_return(false)
|
37
|
-
|
38
|
-
process.send(:send_signal, 'TERM')
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
describe ChildProcess::Unix::IO do
|
43
|
-
let(:io) { ChildProcess::Unix::IO.new }
|
44
|
-
|
45
|
-
it "raises an ArgumentError if given IO does not respond to :to_io" do
|
46
|
-
expect { io.stdout = nil }.to raise_error(ArgumentError, /to respond to :to_io/)
|
47
|
-
end
|
48
|
-
|
49
|
-
it "raises a TypeError if #to_io does not return an IO" do
|
50
|
-
fake_io = Object.new
|
51
|
-
def fake_io.to_io() StringIO.new end
|
52
|
-
|
53
|
-
expect { io.stdout = fake_io }.to raise_error(TypeError, /expected IO, got/)
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
end
|
1
|
+
require File.expand_path('../spec_helper', __FILE__)
|
2
|
+
require "pid_behavior"
|
3
|
+
|
4
|
+
if ChildProcess.unix? && !ChildProcess.jruby? && !ChildProcess.posix_spawn?
|
5
|
+
|
6
|
+
describe ChildProcess::Unix::Process do
|
7
|
+
it_behaves_like "a platform that provides the child's pid"
|
8
|
+
|
9
|
+
it "handles ECHILD race condition where process dies between timeout and KILL" do
|
10
|
+
process = sleeping_ruby
|
11
|
+
|
12
|
+
allow(process).to receive(:fork).and_return('fakepid')
|
13
|
+
allow(process).to receive(:send_term)
|
14
|
+
allow(process).to receive(:poll_for_exit).and_raise(ChildProcess::TimeoutError)
|
15
|
+
allow(process).to receive(:send_kill).and_raise(Errno::ECHILD.new)
|
16
|
+
|
17
|
+
process.start
|
18
|
+
expect { process.stop }.not_to raise_error
|
19
|
+
|
20
|
+
allow(process).to receive(:alive?).and_return(false)
|
21
|
+
|
22
|
+
process.send(:send_signal, 'TERM')
|
23
|
+
end
|
24
|
+
|
25
|
+
it "handles ESRCH race condition where process dies between timeout and KILL" do
|
26
|
+
process = sleeping_ruby
|
27
|
+
|
28
|
+
allow(process).to receive(:fork).and_return('fakepid')
|
29
|
+
allow(process).to receive(:send_term)
|
30
|
+
allow(process).to receive(:poll_for_exit).and_raise(ChildProcess::TimeoutError)
|
31
|
+
allow(process).to receive(:send_kill).and_raise(Errno::ESRCH.new)
|
32
|
+
|
33
|
+
process.start
|
34
|
+
expect { process.stop }.not_to raise_error
|
35
|
+
|
36
|
+
allow(process).to receive(:alive?).and_return(false)
|
37
|
+
|
38
|
+
process.send(:send_signal, 'TERM')
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe ChildProcess::Unix::IO do
|
43
|
+
let(:io) { ChildProcess::Unix::IO.new }
|
44
|
+
|
45
|
+
it "raises an ArgumentError if given IO does not respond to :to_io" do
|
46
|
+
expect { io.stdout = nil }.to raise_error(ArgumentError, /to respond to :to_io/)
|
47
|
+
end
|
48
|
+
|
49
|
+
it "raises a TypeError if #to_io does not return an IO" do
|
50
|
+
fake_io = Object.new
|
51
|
+
def fake_io.to_io() StringIO.new end
|
52
|
+
|
53
|
+
expect { io.stdout = fake_io }.to raise_error(TypeError, /expected IO, got/)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|