require 'spec_helper' describe Docker::Exec do let(:container) { Docker::Container.create( 'Cmd' => %w(sleep 300), 'Image' => 'debian:wheezy' ).start! } describe '#to_s' do subject { described_class.send(:new, Docker.connection, 'id' => rand(10000).to_s) } let(:id) { 'bf119e2' } let(:connection) { Docker.connection } let(:expected_string) { "Docker::Exec { :id => #{id}, :connection => #{connection} }" } before do { :@id => id, :@connection => connection }.each { |k, v| subject.instance_variable_set(k, v) } end its(:to_s) { should == expected_string } end describe '.create' do subject { described_class } context 'when the HTTP request returns a 201' do let(:options) do { 'AttachStdin' => false, 'AttachStdout' => false, 'AttachStderr' => false, 'Tty' => false, 'Cmd' => [ 'date' ], 'Container' => container.id } end let(:process) { subject.create(options) } after { container.kill!.remove } it 'sets the id', :vcr do expect(process).to be_a Docker::Exec expect(process.id).to_not be_nil expect(process.connection).to_not be_nil end end context 'when the parent container does not exist' do before do Docker.options = { :mock => true } Excon.stub({ :method => :post }, { :status => 404 }) end after do Excon.stubs.shift Docker.options = {} end it 'raises an error' do expect { subject.create }.to raise_error(Docker::Error::NotFoundError) end end end describe '#json' do subject { described_class.create( 'Container' => container.id, 'Detach' => true, 'Cmd' => %w[true] ) } let(:description) { subject.json } before { subject.start! } after { container.kill!.remove } it 'returns the description as a Hash', :vcr do expect(description).to be_a Hash expect(description['ID']).to start_with(subject.id) end end describe '#start!' do context 'when the exec instance does not exist' do subject do described_class.send(:new, Docker.connection, 'id' => rand(10000).to_s) end it 'raises an error', :vcr do skip 'The Docker API returns a 200 (docker/docker#9341)' expect { subject.start! }.to raise_error(Docker::Error::NotFoundError) end end context 'when :detach is set to false' do subject { described_class.create( 'Container' => container.id, 'AttachStdout' => true, 'Cmd' => ['bash','-c','sleep 2; echo hello'] ) } after { container.kill!.remove } it 'returns the stdout and stderr messages', :vcr do expect(subject.start!).to eq([["hello\n"],[],0]) end context 'block is passed' do it 'attaches to the stream', :vcr do chunk = nil result = subject.start! do |stream, c| chunk ||= c end expect(chunk).to eq("hello\n") expect(result).to eq([["hello\n"], [], 0]) end end end context 'when :detach is set to true' do subject { described_class.create('Container' => container.id, 'Cmd' => %w[date]) } after { container.kill!.remove } it 'returns empty stdout/stderr messages with exitcode', :vcr do expect(subject.start!(:detach => true)).to eq([[],[], 0]) end end context 'when the command has already run' do subject { described_class.create('Container' => container.id, 'Cmd' => ['date']) } before { subject.start! } after { container.kill!.remove } it 'raises an error', :vcr do skip 'The Docker API returns a 200 (docker/docker#9341)' expect { subject.start! }.to raise_error(Docker::Error::NotFoundError) end end context 'when the HTTP request returns a 201' do subject { described_class.create('Container' => container.id, 'Cmd' => ['date']) } after { container.kill!.remove } it 'starts the exec instance', :vcr do expect { subject.start! }.not_to raise_error end end end end