require 'spec_helper' def load_yaml_file(path) # Ruby 2.x has no safe_load_file if YAML.respond_to?(:safe_load_file) permitted = [Beaker::Options::OptionsHash, Symbol, RSpec::Mocks::Double, Time] YAML.safe_load_file(path, permitted_classes: permitted, aliases: true) else YAML.load_file(path) end end module Beaker describe CLI do let(:cli) do allow(File).to receive(:exist?).and_call_original allow(File).to receive(:exist?).with('.beaker.yml').and_return(false) described_class.new.parse_options end context 'initializing and parsing' do let(:cli) do described_class.new end describe 'instance variable initialization' do it 'creates a logger for use before parse is called' do expect(Beaker::Logger).to receive(:new).once.and_call_original expect(cli.logger).to be_instance_of(Beaker::Logger) end it 'generates the timestamp' do expect(Time).to receive(:now).once cli end end describe '#parse_options' do it 'returns self' do expect(cli.parse_options).to be_instance_of(described_class) end it 'replaces the logger object with a new one' do expect(Beaker::Logger).to receive(:new).with(no_args).once.and_call_original expect(Beaker::Logger).to receive(:new).once.and_call_original cli.parse_options end end describe '#parse_options special behavior' do # NOTE: this `describe` block must be separate, with the following `before` block. # Use the above `describe` block for #parse_options when access to the logger object is not needed before do # Within parse_options() the reassignment of cli.logger makes it impossible to capture subsequent logger calls. # So, hijack the reassignment call so that we can keep a reference to it. allow(Beaker::Logger).to receive(:new).with(no_args).once.and_call_original allow(Beaker::Logger).to receive(:new).once.and_return(cli.instance_variable_get(:@logger)) end it 'prints the version and exits cleanly' do expect(cli.logger).to receive(:notify).once expect { cli.parse_options(['--version']) }.to raise_exception(SystemExit) { |e| expect(e.success?).to eq(true) } end it 'prints the help and exits cleanly' do expect(cli.logger).to receive(:notify).once expect { cli.parse_options(['--help']) }.to raise_exception(SystemExit) { |e| expect(e.success?).to eq(true) } end end describe '#print_version_and_options' do before do options = Beaker::Options::OptionsHash.new options[:beaker_version] = 'version_number' cli.instance_variable_set(:@options, options) end it 'prints the version and dumps the options' do expect(cli.logger).to receive(:info).exactly(3).times cli.print_version_and_options end end end describe '#configured_options' do it 'returns a list of options that were not presets' do attribution = cli.instance_variable_get(:@attribution) attribution.each do |attribute, setter| expect(cli.configured_options[attribute]).to be_nil if setter == 'preset' end end end describe '#combined_instance_and_options_hosts' do let(:options_host) { { 'HOSTS' => { 'ubuntu' => { :options_attribute => 'options' } } } } let(:instance_host) do [Beaker::Host.create('ubuntu', { :platform => 'host' }, {})] end before do cli.instance_variable_set(:@options, options_host) cli.instance_variable_set(:@hosts, instance_host) end it 'combines the options and instance host objects' do merged_host = cli.combined_instance_and_options_hosts expect(merged_host).to have_key('ubuntu') expect(merged_host['ubuntu']).to have_key(:options_attribute) expect(merged_host['ubuntu']).to have_key(:platform) expect(merged_host['ubuntu'][:options_attribute]).to eq('options') expect(merged_host['ubuntu'][:platform]).to eq('host') end context 'when hosts share IP addresses' do let(:options_host) do { 'HOSTS' => { 'host1' => { :options_attribute => 'options' }, 'host2' => { :options_attribute => 'options' }, } } end let(:instance_host) do [Beaker::Host.create('host1', { :platform => 'host', :ip => '127.0.0.1' }, {}), Beaker::Host.create('host2', { :platform => 'host', :ip => '127.0.0.1' }, {}),] end it 'creates separate entries for each host' do expected_hosts = instance_host.map(&:hostname) merged_hosts = cli.combined_instance_and_options_hosts expect(merged_hosts.keys).to eq(expected_hosts) end end end context 'execute!' do before do stub_const("Beaker::Logger", double.as_null_object) File.open("sample.cfg", "w+") do |file| file.write("HOSTS:\n") file.write(" myhost:\n") file.write(" roles:\n") file.write(" - master\n") file.write(" platform: ubuntu-x-x\n") file.write("CONFIG:\n") end allow(cli).to receive(:setup).and_return(true) allow(cli).to receive(:validate).and_return(true) allow(cli).to receive(:provision).and_return(true) end describe "test fail mode" do it 'runs pre_cleanup after a failed pre_suite if using slow fail_mode' do options = cli.instance_variable_get(:@options) options[:fail_mode] = 'slow' cli.instance_variable_set(:@options, options) allow(cli).to receive(:run_suite).with(:pre_suite, :fast).and_throw("bad test") allow(cli).to receive(:run_suite).with(:tests, options[:fail_mode]) allow(cli).to receive(:run_suite).with(:post_suite).and_return(true) allow(cli).to receive(:run_suite).with(:pre_cleanup).and_return(true) expect(cli).to receive(:run_suite).twice expect { cli.execute! }.to raise_error expect(cli.instance_variable_get(:@attribution)[:logger]).to eq 'runtime' expect(cli.instance_variable_get(:@attribution)[:timestamp]).to eq 'runtime' expect(cli.instance_variable_get(:@attribution)[:beaker_version]).to eq 'runtime' end it 'continues testing after failed test if using slow fail_mode' do options = cli.instance_variable_get(:@options) options[:fail_mode] = 'slow' cli.instance_variable_set(:@options, options) allow(cli).to receive(:run_suite).with(:pre_suite, :fast).and_return(true) allow(cli).to receive(:run_suite).with(:tests, options[:fail_mode]).and_throw("bad test") allow(cli).to receive(:run_suite).with(:post_suite).and_return(true) allow(cli).to receive(:run_suite).with(:pre_cleanup).and_return(true) expect(cli).to receive(:run_suite).exactly(4).times expect { cli.execute! }.to raise_error end it 'stops testing after failed test if using fast fail_mode' do options = cli.instance_variable_get(:@options) options[:fail_mode] = 'fast' cli.instance_variable_set(:@options, options) allow(cli).to receive(:run_suite).with(:pre_suite, :fast).and_return(true) allow(cli).to receive(:run_suite).with(:tests, options[:fail_mode]).and_throw("bad test") allow(cli).to receive(:run_suite).with(:pre_cleanup).and_return(true) expect(cli).to receive(:run_suite).exactly(3).times expect { cli.execute! }.to raise_error end end describe "SUT preserve mode" do it 'cleans up SUTs post testing if tests fail and preserve_hosts = never' do options = cli.instance_variable_get(:@options) options[:fail_mode] = 'fast' options[:preserve_hosts] = 'never' cli.instance_variable_set(:@options, options) allow(cli).to receive(:run_suite).with(:pre_suite, :fast).and_return(true) allow(cli).to receive(:run_suite).with(:tests, options[:fail_mode]).and_throw("bad test") allow(cli).to receive(:run_suite).with(:pre_cleanup).and_return(true) netmanager = double(:netmanager) cli.instance_variable_set(:@network_manager, netmanager) expect(netmanager).to receive(:cleanup).once expect { cli.execute! }.to raise_error end it 'cleans up SUTs post testing if no tests fail and preserve_hosts = never' do options = cli.instance_variable_get(:@options) options[:fail_mode] = 'fast' options[:preserve_hosts] = 'never' cli.instance_variable_set(:@options, options) allow(cli).to receive(:run_suite).with(:pre_suite, :fast).and_return(true) allow(cli).to receive(:run_suite).with(:tests, options[:fail_mode]).and_return(true) allow(cli).to receive(:run_suite).with(:post_suite).and_return(true) allow(cli).to receive(:run_suite).with(:pre_cleanup).and_return(true) netmanager = double(:netmanager) cli.instance_variable_set(:@network_manager, netmanager) expect(netmanager).to receive(:cleanup).once expect { cli.execute! }.not_to raise_error end it 'preserves SUTs post testing if no tests fail and preserve_hosts = always' do options = cli.instance_variable_get(:@options) options[:fail_mode] = 'fast' options[:preserve_hosts] = 'always' options[:log_dated_dir] = '.' options[:hosts_file] = 'sample.cfg' cli.instance_variable_set(:@options, options) allow(cli).to receive(:run_suite).with(:pre_suite, :fast).and_return(true) allow(cli).to receive(:run_suite).with(:tests, options[:fail_mode]).and_return(true) allow(cli).to receive(:run_suite).with(:post_suite).and_return(true) allow(cli).to receive(:run_suite).with(:pre_cleanup).and_return(true) cli.instance_variable_set(:@hosts, {}) netmanager = double(:netmanager) cli.instance_variable_set(:@network_manager, netmanager) expect(netmanager).not_to receive(:cleanup) expect { cli.execute! }.not_to raise_error end it 'preserves SUTs post testing if no tests fail and preserve_hosts = always' do options = cli.instance_variable_get(:@options) options[:fail_mode] = 'fast' options[:preserve_hosts] = 'always' cli.instance_variable_set(:@options, options) allow(cli).to receive(:run_suite).with(:pre_suite, :fast).and_return(true) allow(cli).to receive(:run_suite).with(:tests, options[:fail_mode]).and_throw("bad test") allow(cli).to receive(:run_suite).with(:post_suite).and_return(true) allow(cli).to receive(:run_suite).with(:pre_cleanup).and_return(true) netmanager = double(:netmanager) cli.instance_variable_set(:@network_manager, netmanager) expect(netmanager).not_to receive(:cleanup) expect { cli.execute! }.to raise_error end it 'cleans up SUTs post testing if no tests fail and preserve_hosts = onfail' do options = cli.instance_variable_get(:@options) options[:fail_mode] = 'fast' options[:preserve_hosts] = 'onfail' cli.instance_variable_set(:@options, options) allow(cli).to receive(:run_suite).with(:pre_suite, :fast).and_return(true) allow(cli).to receive(:run_suite).with(:tests, options[:fail_mode]).and_return(true) allow(cli).to receive(:run_suite).with(:post_suite).and_return(true) allow(cli).to receive(:run_suite).with(:pre_cleanup).and_return(true) netmanager = double(:netmanager) cli.instance_variable_set(:@network_manager, netmanager) expect(netmanager).to receive(:cleanup).once expect { cli.execute! }.not_to raise_error end it 'preserves SUTs post testing if tests fail and preserve_hosts = onfail' do options = cli.instance_variable_get(:@options) options[:fail_mode] = 'fast' options[:preserve_hosts] = 'onfail' cli.instance_variable_set(:@options, options) allow(cli).to receive(:run_suite).with(:pre_suite, :fast).and_return(true) allow(cli).to receive(:run_suite).with(:tests, options[:fail_mode]).and_throw("bad test") allow(cli).to receive(:run_suite).with(:post_suite).and_return(true) allow(cli).to receive(:run_suite).with(:pre_cleanup).and_return(true) netmanager = double(:netmanager) cli.instance_variable_set(:@network_manager, netmanager) expect(netmanager).not_to receive(:cleanup) expect { cli.execute! }.to raise_error end it 'cleans up SUTs post testing if tests fail and preserve_hosts = onpass' do options = cli.instance_variable_get(:@options) options[:fail_mode] = 'fast' options[:preserve_hosts] = 'onpass' cli.instance_variable_set(:@options, options) allow(cli).to receive(:run_suite).with(:pre_suite, :fast).and_return(true) allow(cli).to receive(:run_suite).with(:tests, options[:fail_mode]).and_throw("bad test") allow(cli).to receive(:run_suite).with(:post_suite).and_return(true) allow(cli).to receive(:run_suite).with(:pre_cleanup).and_return(true) netmanager = double(:netmanager) cli.instance_variable_set(:@network_manager, netmanager) expect(netmanager).to receive(:cleanup).once expect { cli.execute! }.to raise_error end it 'preserves SUTs post testing if no tests fail and preserve_hosts = onpass' do options = cli.instance_variable_get(:@options) options[:fail_mode] = 'fast' options[:preserve_hosts] = 'onpass' options[:log_dated_dir] = '.' options[:hosts_file] = 'sample.cfg' cli.instance_variable_set(:@hosts, {}) cli.instance_variable_set(:@options, options) allow(cli).to receive(:run_suite).with(:pre_suite, :fast).and_return(true) allow(cli).to receive(:run_suite).with(:tests, options[:fail_mode]).and_return(true) allow(cli).to receive(:run_suite).with(:post_suite).and_return(true) allow(cli).to receive(:run_suite).with(:pre_cleanup).and_return(true) netmanager = double(:netmanager) cli.instance_variable_set(:@network_manager, netmanager) expect(netmanager).not_to receive(:cleanup) expect { cli.execute! }.not_to raise_error end end describe "#preserve_hosts_file" do it 'removes the pre-suite/post-suite/tests and sets to []' do hosts = make_hosts options = cli.instance_variable_get(:@options) options[:log_dated_dir] = Dir.mktmpdir File.open("sample.cfg", "w+") do |file| file.write("HOSTS:\n") hosts.each do |host| file.write(" #{host.name}:\n") file.write(" roles:\n") host[:roles].each do |role| file.write(" - #{role}\n") end file.write(" platform: #{host[:platform]}\n") end file.write("CONFIG:\n") end options[:hosts_file] = 'sample.cfg' options[:pre_suite] = %w[pre1 pre2 pre3] options[:post_suite] = ['post1'] options[:pre_cleanup] = ['preclean1'] options[:tests] = %w[test1 test2] cli.instance_variable_set(:@options, options) cli.instance_variable_set(:@hosts, hosts) preserved_file = cli.preserve_hosts_file hosts_yaml = load_yaml_file(preserved_file) expect(hosts_yaml['CONFIG'][:tests]).to eq [] expect(hosts_yaml['CONFIG'][:pre_suite]).to eq [] expect(hosts_yaml['CONFIG'][:post_suite]).to eq [] expect(hosts_yaml['CONFIG'][:pre_cleanup]).to eq [] end end describe 'hosts file saving when preserve_hosts should happen' do before do options = cli.instance_variable_get(:@options) options[:fail_mode] = 'fast' options[:preserve_hosts] = 'onpass' options[:hosts_file] = 'sample.cfg' cli.instance_variable_set(:@options, options) allow(cli).to receive(:run_suite).with(:pre_suite, :fast).and_return(true) allow(cli).to receive(:run_suite).with(:tests, options[:fail_mode]).and_return(true) allow(cli).to receive(:run_suite).with(:post_suite).and_return(true) allow(cli).to receive(:run_suite).with(:pre_cleanup).and_return(true) hosts = [ make_host('petey', { :hypervisor => 'peterPan' }), make_host('hatty', { :hypervisor => 'theMadHatter' }), ] cli.instance_variable_set(:@hosts, hosts) netmanager = double(:netmanager) cli.instance_variable_set(:@network_manager, netmanager) expect(netmanager).not_to receive(:cleanup) allow(cli).to receive(:print_env_vars_affecting_beaker) logger = cli.instance_variable_get(:@logger) expect(logger).to receive(:send).with(anything, anything).ordered expect(logger).to receive(:send).with(anything, anything).ordered end it 'executes without error' do options = cli.instance_variable_get(:@options) Dir.mktmpdir do |dir| options[:log_dated_dir] = File.absolute_path(dir) expect { cli.execute! }.not_to raise_error end end it 'copies a file into the correct location' do options = cli.instance_variable_get(:@options) Dir.mktmpdir do |dir| options[:log_dated_dir] = File.absolute_path(dir) cli.execute! copied_hosts_file = File.join(File.absolute_path(dir), 'hosts_preserved.yml') expect(File).to exist(copied_hosts_file) end end it 'generates a valid YAML file when it copies' do options = cli.instance_variable_get(:@options) Dir.mktmpdir do |dir| options[:log_dated_dir] = File.absolute_path(dir) cli.execute! copied_hosts_file = File.join(File.absolute_path(dir), 'hosts_preserved.yml') expect { load_yaml_file(copied_hosts_file) }.not_to raise_error end end it 'sets :provision to false in the copied hosts file' do options = cli.instance_variable_get(:@options) Dir.mktmpdir do |dir| options[:log_dated_dir] = File.absolute_path(dir) cli.execute! copied_hosts_file = File.join(File.absolute_path(dir), 'hosts_preserved.yml') yaml_content = load_yaml_file(copied_hosts_file) expect(yaml_content['CONFIG']['provision']).to be_falsy end end it 'sets the @options :hosts_preserved_yaml_file to the copied file' do options = cli.instance_variable_get(:@options) Dir.mktmpdir do |dir| options[:log_dated_dir] = File.absolute_path(dir) expect(options).not_to have_key(:hosts_preserved_yaml_file) cli.execute! expect(options).to have_key(:hosts_preserved_yaml_file) copied_hosts_file = File.join(File.absolute_path(dir), 'hosts_preserved.yml') expect(options[:hosts_preserved_yaml_file]).to be === copied_hosts_file end end describe 'output text informing the user that re-use is possible' do it 'if unsupported, does not output extra text' do options = cli.instance_variable_get(:@options) Dir.mktmpdir do |dir| options[:log_dated_dir] = File.absolute_path(dir) copied_hosts_file = File.join(File.absolute_path(dir), options[:hosts_file]) logger = cli.instance_variable_get(:@logger) expect(logger).not_to receive(:send).with(anything, "\nYou can re-run commands against the already provisioned SUT(s) by following these steps:\n") expect(logger).not_to receive(:send).with(anything, "- change the hosts file to #{copied_hosts_file}") expect(logger).not_to receive(:send).with(anything, '- use the --no-provision flag') cli.execute! end end it 'if supported, outputs the text letting the user know they can re-use these hosts' do options = cli.instance_variable_get(:@options) Dir.mktmpdir do |dir| options[:log_dated_dir] = File.absolute_path(dir) hosts = cli.instance_variable_get(:@hosts) hosts << make_host('fusion', { :hypervisor => 'fusion' }) reproducing_cmd = "the faith of the people" allow(cli).to receive(:build_hosts_preserved_reproducing_command).and_return(reproducing_cmd) logger = cli.instance_variable_get(:@logger) expect(logger).to receive(:send).with(anything, "\nYou can re-run commands against the already provisioned SUT(s) with:\n").ordered expect(logger).to receive(:send).with(anything, reproducing_cmd).ordered cli.execute! end end it 'if supported && docker is a hypervisor, outputs text + the untested warning' do options = cli.instance_variable_get(:@options) Dir.mktmpdir do |dir| options[:log_dated_dir] = File.absolute_path(dir) hosts = cli.instance_variable_get(:@hosts) hosts << make_host('fusion', { :hypervisor => 'fusion' }) hosts << make_host('docker', { :hypervisor => 'docker' }) reproducing_cmd = "the crow flies true says the shoe to you" allow(cli).to receive(:build_hosts_preserved_reproducing_command).and_return(reproducing_cmd) logger = cli.instance_variable_get(:@logger) expect(logger).to receive(:send).with(anything, "\nYou can re-run commands against the already provisioned SUT(s) with:\n").ordered expect(logger).to receive(:send).with(anything, '(docker support is untested for this feature. please reference the docs for more info)').ordered expect(logger).to receive(:send).with(anything, reproducing_cmd).ordered cli.execute! end end it 'if unsupported && docker is a hypervisor, no extra text output' do options = cli.instance_variable_get(:@options) Dir.mktmpdir do |dir| options[:log_dated_dir] = File.absolute_path(dir) copied_hosts_file = File.join(File.absolute_path(dir), options[:hosts_file]) hosts = cli.instance_variable_get(:@hosts) hosts << make_host('docker', { :hypervisor => 'docker' }) logger = cli.instance_variable_get(:@logger) expect(logger).not_to receive(:send).with(anything, "\nYou can re-run commands against the already provisioned SUT(s) with:\n") expect(logger).not_to receive(:send).with(anything, '(docker support is untested for this feature. please reference the docs for more info)') expect(logger).not_to receive(:send).with(anything, "- change the hosts file to #{copied_hosts_file}") expect(logger).not_to receive(:send).with(anything, '- use the --no-provision flag') cli.execute! end end end end describe '#build_hosts_preserved_reproducing_command' do it 'replaces the hosts file' do new_hosts_file = 'john/deer/was/here.txt' command_to_sub = 'p --log-level debug --hosts pants/of/plan.poo jam --jankies --flag-business' command_correct = "p --log-level debug --hosts #{new_hosts_file} jam --jankies --flag-business" answer = cli.build_hosts_preserved_reproducing_command(command_to_sub, new_hosts_file) expect(answer).to start_with(command_correct) end it 'doesn\'t replace an entry if no --hosts key is found' do command_to_sub = 'p --log-level debug johnnypantaloons7 --jankies --flag-business' command_correct = 'p --log-level debug johnnypantaloons7 --jankies --flag-business' answer = cli.build_hosts_preserved_reproducing_command(command_to_sub, 'john/deer/plans.txt') expect(answer).to start_with(command_correct) end it 'removes any old --provision flags' do command_to_sub = '--provision jam --provision --jankies --flag-business' command_correct = 'jam --jankies --flag-business' answer = cli.build_hosts_preserved_reproducing_command(command_to_sub, 'can/talk/to/pigs.yml') expect(answer).to start_with(command_correct) end it 'removes any old --no-provision flags' do command_to_sub = 'jam --no-provision --jankoos --no-provision --flag-businesses' command_correct = 'jam --jankoos --flag-businesses' answer = cli.build_hosts_preserved_reproducing_command(command_to_sub, 'can/talk/to/bears.yml') expect(answer).to start_with(command_correct) end end end end end