require 'spec_helper' class ClassMixedWithDSLHelpers include Beaker::DSL::Helpers include Beaker::DSL::Wrappers include Beaker::DSL::Roles include Beaker::DSL::Patterns def logger RSpec::Mocks::Double.new('logger').as_null_object end end describe ClassMixedWithDSLHelpers do let( :opts ) { Beaker::Options::Presets.env_vars } let( :command ){ 'ls' } let( :host ) { double.as_null_object } let( :result ) { Beaker::Result.new( host, command ) } let( :master ) { make_host( 'master', :roles => %w( master agent default) ) } let( :agent ) { make_host( 'agent', :roles => %w( agent ) ) } let( :custom ) { make_host( 'custom', :roles => %w( custom agent ) ) } let( :dash ) { make_host( 'console', :roles => %w( dashboard agent ) ) } let( :db ) { make_host( 'db', :roles => %w( database agent ) ) } let( :hosts ) { [ master, agent, dash, db, custom ] } describe '#on' do before :each do result.stdout = 'stdout' result.stderr = 'stderr' result.exit_code = 0 end it 'allows the environment the command is run within to be specified' do allow( subject ).to receive( :hosts ).and_return( hosts ) expect( Beaker::Command ).to receive( :new ). with( 'ls ~/.bin', [], {'ENV' => { :HOME => '/tmp/test_home' }} ) subject.on( host, 'ls ~/.bin', :environment => {:HOME => '/tmp/test_home' } ) end describe 'with a beaker command object passed in as the command argument' do let( :command ) { Beaker::Command.new('commander command', [], :environment => {:HOME => 'default'}) } it 'overwrites the command environment with the environment specified in #on' do expect( host ).to receive( :exec ) do |command| expect(command.environment).to eq({:HOME => 'override'}) end subject.on( host, command, :environment => {:HOME => 'override'}) end it 'uses the command environment if there is no overriding argument in #on' do expect( host ).to receive( :exec ) do |command| expect(command.environment).to eq({:HOME => 'default'}) end subject.on( host, command ) end end it 'if the host is a String Object, finds the matching hosts with that String as role' do allow( subject ).to receive( :hosts ).and_return( hosts ) expect( master ).to receive( :exec ).once subject.on( 'master', 'echo hello') end it 'if the host is a Symbol Object, finds the matching hosts with that Symbol as role' do allow( subject ).to receive( :hosts ).and_return( hosts ) expect( master ).to receive( :exec ).once subject.on( :master, 'echo hello') end it 'executes in parallel if run_in_parallel=true' do InParallel::InParallelExecutor.logger = logger FakeFS.deactivate! allow( subject ).to receive( :hosts ).and_return( hosts ) expected = [] hosts.each_with_index do |host, i| expected << i allow( host ).to receive( :exec ).and_return( i ) end # This will only get hit if forking processes is supported and at least 2 items are being submitted to run in parallel expect( InParallel::InParallelExecutor ).to receive(:_execute_in_parallel).with(any_args).and_call_original.exactly(5).times results = subject.on( hosts, command, {:run_in_parallel => true}) expect( results ).to be == expected end it 'delegates to itself for each host passed' do allow( subject ).to receive( :hosts ).and_return( hosts ) expected = [] hosts.each_with_index do |host, i| expected << i expect( host ).to receive( :exec ).and_return( i ) end results = subject.on( hosts, command ) expect( results ).to be == expected end context 'upon command completion' do before :each do allow( subject ).to receive( :hosts ).and_return( hosts ) expect( host ).to receive( :exec ).and_return( result ) @res = subject.on( host, command ) end it 'returns the result of the action' do expect( @res ).to be == result end it 'provides access to stdout' do expect( @res.stdout ).to be == 'stdout' end it 'provides access to stderr' do expect( @res.stderr ).to be == 'stderr' end it 'provides access to exit_code' do expect( @res.exit_code ).to be == 0 end end context 'when passed a block with arity of 1' do before :each do allow( subject ).to receive( :hosts ).and_return( hosts ) expect( host ).to receive( :exec ).and_return( result ) end it 'yields result' do subject.on host, command do |containing_class| expect( containing_class ). to be_an_instance_of( Beaker::Result ) end end it 'provides access to stdout' do subject.on host, command do |containing_class| expect( containing_class.stdout ).to be == 'stdout' end end it 'provides access to stderr' do subject.on host, command do |containing_class| expect( containing_class.stderr ).to be == 'stderr' end end it 'provides access to exit_code' do subject.on host, command do |containing_class| expect( containing_class.exit_code ).to be == 0 end end end context 'when passed a block with arity of 0' do before :each do allow( subject ).to receive( :hosts ).and_return( hosts ) expect( host ).to receive( :exec ).and_return( result ) end it 'yields self' do subject.on host, command do expect( subject ). to be_an_instance_of( ClassMixedWithDSLHelpers ) end end it 'provides access to stdout' do subject.on host, command do expect( subject.stdout ).to be == 'stdout' end end it 'provides access to stderr' do subject.on host, command do expect( subject.stderr ).to be == 'stderr' end end it 'provides access to exit_code' do subject.on host, command do expect( subject.exit_code ).to be == 0 end end end it 'errors if command is not a String or Beaker::Command' do expect { subject.on( host, Object.new ) }.to raise_error( ArgumentError, /called\ with\ a\ String\ or\ Beaker/ ) end it 'executes the passed Beaker::Command if given as command argument' do command_test = Beaker::Command.new( 'echo face_testing' ) expect( master ).to receive( :exec ).with( command_test, anything ) subject.on( master, command_test ) end end describe "#retry_on" do it 'fails correctly when command never succeeds' do result.stdout = 'stdout' result.stderr = 'stderr' result.exit_code = 1 retries = 5 opts = { :max_retries => retries, :retry_interval => 0.0001, } allow( subject ).to receive(:on).and_return(result) expect( subject ).to receive(:on).exactly(retries+2) expect { subject.retry_on(host, command, opts) }.to raise_error(RuntimeError) end it 'will return success correctly if it succeeds the first time' do result.stdout = 'stdout' result.stderr = 'stderr' result.exit_code = 0 opts = { :max_retries => 5, :retry_interval => 0.0001, } allow( subject ).to receive(:on).and_return(result) expect( subject ).to receive(:on).once result_given = subject.retry_on(host, command, opts) expect(result_given.exit_code).to be === 0 end it 'will return success correctly if it succeeds after failing a few times' do result.stdout = 'stdout' result.stderr = 'stderr' opts = { :max_retries => 10, :retry_interval => 0.1, } reps_num = 4 count = 0 allow( subject ).to receive(:on) do result.exit_code = count > reps_num ? 0 : 1 count += 1 result end expect( subject ).to receive(:on).exactly(reps_num + 2) result_given = subject.retry_on(host, command, opts) expect(result_given.exit_code).to be === 0 end end describe "shell" do it 'delegates to #on with the default host' do allow( subject ).to receive( :hosts ).and_return( hosts ) expect( subject ).to receive( :on ).with( master, "echo hello", {}).once subject.shell( "echo hello" ) end end describe '#scp_from' do it 'delegates to the host' do allow( subject ).to receive( :hosts ).and_return( hosts ) expect( subject ).to receive( :logger ).exactly( hosts.length ).times expect( result ).to receive( :log ).exactly( hosts.length ).times hosts.each do |host| expect( host ).to receive( :do_scp_from ).and_return( result ) end subject.scp_from( hosts, '/var/log/my.log', 'log/my.log' ) end end describe '#scp_to' do it 'delegates to the host' do allow( subject ).to receive( :hosts ).and_return( hosts ) expect( subject ).to receive( :logger ).exactly( hosts.length ).times expect( result ).to receive( :log ).exactly( hosts.length ).times hosts.each do |host| expect( host ).to receive( :do_scp_to ).and_return( result ) end subject.scp_to( hosts, '/var/log/my.log', 'log/my.log' ) end end describe '#rsync_to' do it 'delegates to the host' do allow( subject ).to receive( :hosts ).and_return( hosts ) hosts.each do |host| expect( host ).to receive( :do_rsync_to ).and_return( result ) end subject.rsync_to( hosts, '/var/log/my.log', 'log/my.log' ) end end describe '#create_remote_file using scp' do it 'scps the contents passed in to the hosts' do my_opts = { :silent => true } tmpfile = double expect( tmpfile ).to receive( :path ).exactly( 2 ).times. and_return( '/local/path/to/blah' ) expect( Tempfile ).to receive( :open ).and_yield( tmpfile ) expect( File ).to receive( :open ) expect( subject ).to receive( :scp_to ). with( hosts, '/local/path/to/blah', '/remote/path', my_opts ) subject.create_remote_file( hosts, '/remote/path', 'blah', my_opts ) end end describe '#create_remote_file using rsync' do it 'scps the contents passed in to the hosts' do my_opts = { :silent => true, :protocol => 'rsync' } tmpfile = double expect( tmpfile ).to receive( :path ).exactly( 2 ).times. and_return( '/local/path/to/blah' ) expect( Tempfile ).to receive( :open ).and_yield( tmpfile ) expect( File ).to receive( :open ) expect( subject ).to receive( :rsync_to ). with( hosts, '/local/path/to/blah', '/remote/path', my_opts ) subject.create_remote_file( hosts, '/remote/path', 'blah', my_opts ) end end describe '#run_script_on' do it 'scps the script to a tmpdir and executes it on host(s)' do expect( subject ).to receive( :scp_to ) expect( subject ).to receive( :on ) subject.run_script_on( 'host', '~/.bin/make-enterprisy' ) end end describe '#run_script' do it 'delegates to #run_script_on with the default host' do allow( subject ).to receive( :hosts ).and_return( hosts ) expect( subject ).to receive( :run_script_on ).with( master, "/tmp/test.sh", {}).once subject.run_script( '/tmp/test.sh' ) end end describe '#install_package' do it 'delegates to Host#install_package with arguments on the passed Host' do expect( host ).to receive( :install_package ).with( 'pkg_name', '', '1.2.3' ) subject.install_package( host, 'pkg_name', '1.2.3' ) end end describe '#uninstall_package' do it 'delegates to Host#uninstall_package on the passed Host' do expect( host ).to receive( :uninstall_package ).with( 'pkg_name' ) subject.uninstall_package( host, 'pkg_name' ) end end end