Change Mutant::Isolation.call to use block form of IO.pipe

* The block form automatically closes the reader and writer if they have
  not already been closed. This prevents resource leakage if the method
  exits due to an exception.
* Refactor specs to have common setup performed in a before block.
This commit is contained in:
Dan Kubb 2015-07-23 13:45:43 -07:00
parent d4426ce93a
commit 63c586f6bb
2 changed files with 43 additions and 39 deletions

View file

@ -38,23 +38,25 @@ module Mutant
#
# @api private
def self.call(&block)
reader, writer = IO.pipe.map(&:binmode)
IO.pipe(binmode: true) do |reader, writer|
begin
pid = Process.fork do
File.open(File::NULL, 'w') do |file|
$stderr.reopen(file)
reader.close
writer.write(Marshal.dump(block.call))
writer.close
end
end
pid = Process.fork do
File.open('/dev/null', 'w') do |file|
$stderr.reopen(file)
reader.close
writer.write(Marshal.dump(block.call))
writer.close
Marshal.load(reader.read)
ensure
Process.waitpid(pid) if pid
end
end
writer.close
Marshal.load(reader.read)
rescue => exception
raise Error, exception
ensure
Process.waitpid(pid) if pid
end
end # Fork

View file

@ -64,37 +64,39 @@ RSpec.describe Mutant::Isolation::Fork do
end
end
it 'uses primitives in correct order when fork succeeds' do
reader, writer = double('reader'), double('writer')
expect(IO).to receive(:pipe).ordered.and_return([reader, writer])
expect(reader).to receive(:binmode).and_return(reader).ordered
expect(writer).to receive(:binmode).and_return(writer).ordered
pid = double('PID')
expect(Process).to receive(:fork).ordered.and_yield.and_return(pid)
file = double('file')
expect(File).to receive(:open).ordered.with('/dev/null', 'w').and_yield(file)
expect($stderr).to receive(:reopen).ordered.with(file)
expect(reader).to receive(:close).ordered
expect(writer).to receive(:write).ordered.with(Marshal.dump(:foo))
expect(writer).to receive(:close).ordered
expect(writer).to receive(:close).ordered
expect(reader).to receive(:read).ordered.and_return(Marshal.dump(:foo))
expect(Process).to receive(:waitpid).with(pid)
context 'uses primitives in correct order' do
let(:reader) { double('reader') }
let(:writer) { double('writer') }
expect(object.call { :foo }).to be(:foo)
end
before do
expect(IO).to receive(:pipe).with(binmode: true).ordered do |&block|
block.call([reader, writer])
end
end
it 'uses primitives in correct order when fork fails' do
reader, writer = double('reader'), double('writer')
expect(IO).to receive(:pipe).ordered.and_return([reader, writer])
expect(reader).to receive(:binmode).and_return(reader).ordered
expect(writer).to receive(:binmode).and_return(writer).ordered
expect(Process).to receive(:fork).ordered.and_return(nil)
expect(Process).to_not receive(:waitpid)
it 'when fork succeeds' do
pid = double('PID')
expect(Process).to receive(:fork).ordered.and_yield.and_return(pid)
file = double('file')
expect(File).to receive(:open).ordered.with('/dev/null', 'w').and_yield(file)
expect($stderr).to receive(:reopen).ordered.with(file)
expect(reader).to receive(:close).ordered
expect(writer).to receive(:write).ordered.with(Marshal.dump(:foo))
expect(writer).to receive(:close).ordered
expect(writer).to receive(:close).ordered
expect(reader).to receive(:read).ordered.and_return(Marshal.dump(:foo))
expect(Process).to receive(:waitpid).with(pid)
expect(writer).to receive(:close).ordered
expect(reader).to receive(:read).ordered.and_return(Marshal.dump(:foo))
expect(object.call).to be(:foo)
expect(object.call { :foo }).to be(:foo)
end
it 'when fork fails' do
expect(Process).to receive(:fork).ordered.and_return(nil)
expect(Process).to_not receive(:waitpid)
expect(writer).to receive(:close).ordered
expect(reader).to receive(:read).ordered.and_return(Marshal.dump(:foo))
expect(object.call).to be(:foo)
end
end
end
end